Mercurial > vba-linux
comparison src/common/movie.cpp @ 1:f9f4f1b99eed
importing src directory
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 03 Mar 2012 10:31:27 -0600 |
parents | |
children | 44974c3e093b |
comparison
equal
deleted
inserted
replaced
0:8ced16adf2e1 | 1:f9f4f1b99eed |
---|---|
1 #include <cstdio> | |
2 #include <cctype> | |
3 #include <cstdlib> | |
4 #include <cstring> | |
5 #include <cassert> | |
6 #include <algorithm> | |
7 | |
8 using namespace std; | |
9 | |
10 #ifdef HAVE_STRINGS_H | |
11 # include <strings.h> | |
12 #endif | |
13 | |
14 #if defined(__unix) || defined(__linux) || defined(__sun) || defined(__DJGPP) | |
15 # include <unistd.h> | |
16 # include <sys/types.h> | |
17 # include <sys/stat.h> | |
18 # include <climits> | |
19 # define stricmp strcasecmp | |
20 // FIXME: this is wrong, but we don't want buffer overflow | |
21 # if defined _MAX_PATH | |
22 # undef _MAX_PATH | |
23 //# define _MAX_PATH 128 | |
24 # define _MAX_PATH 260 | |
25 # endif | |
26 #endif | |
27 | |
28 #ifdef WIN32 | |
29 # include <io.h> | |
30 # ifndef W_OK | |
31 # define W_OK 2 | |
32 # endif | |
33 # define ftruncate chsize | |
34 #endif | |
35 | |
36 #include "movie.h" | |
37 #include "System.h" | |
38 #include "../gba/GBA.h" | |
39 #include "../gba/GBAGlobals.h" | |
40 #include "../gba/RTC.h" | |
41 #include "../gb/GB.h" | |
42 #include "../gb/gbGlobals.h" | |
43 #include "inputGlobal.h" | |
44 #include "unzip.h" | |
45 #include "Util.h" | |
46 | |
47 #include "vbalua.h" | |
48 | |
49 #if (defined(WIN32) && !defined(SDL)) | |
50 # include "../win32/stdafx.h" | |
51 # include "../win32/MainWnd.h" | |
52 # include "../win32/VBA.h" | |
53 # include "../win32/WinMiscUtil.h" | |
54 #endif | |
55 | |
56 extern int emulating; // from system.cpp | |
57 extern u16 currentButtons[4]; // from System.cpp | |
58 extern u16 lastKeys; | |
59 | |
60 SMovie Movie; | |
61 bool loadingMovie = false; | |
62 | |
63 // probably bad idea to have so many global variables, but I hate to recompile almost everything after editing VBA.h | |
64 bool autoConvertMovieWhenPlaying = false; | |
65 | |
66 static u16 initialInputs[4] = { 0 }; | |
67 | |
68 static bool resetSignaled = false; | |
69 static bool resetSignaledLast = false; | |
70 | |
71 static int prevEmulatorType, prevBorder, prevWinBorder, prevBorderAuto; | |
72 | |
73 // little-endian integer pop/push functions: | |
74 static inline uint32 Pop32(const uint8 * &ptr) | |
75 { | |
76 uint32 v = (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24)); | |
77 ptr += 4; | |
78 return v; | |
79 } | |
80 | |
81 static inline uint16 Pop16(const uint8 * &ptr) /* const version */ | |
82 { | |
83 uint16 v = (ptr[0] | (ptr[1] << 8)); | |
84 ptr += 2; | |
85 return v; | |
86 } | |
87 | |
88 static inline uint16 Pop16(uint8 * &ptr) /* non-const version */ | |
89 { | |
90 uint16 v = (ptr[0] | (ptr[1] << 8)); | |
91 ptr += 2; | |
92 return v; | |
93 } | |
94 | |
95 static inline uint8 Pop8(const uint8 * &ptr) | |
96 { | |
97 return *(ptr)++; | |
98 } | |
99 | |
100 static inline void Push32(uint32 v, uint8 * &ptr) | |
101 { | |
102 ptr[0] = (uint8)(v & 0xff); | |
103 ptr[1] = (uint8)((v >> 8) & 0xff); | |
104 ptr[2] = (uint8)((v >> 16) & 0xff); | |
105 ptr[3] = (uint8)((v >> 24) & 0xff); | |
106 ptr += 4; | |
107 } | |
108 | |
109 static inline void Push16(uint16 v, uint8 * &ptr) | |
110 { | |
111 ptr[0] = (uint8)(v & 0xff); | |
112 ptr[1] = (uint8)((v >> 8) & 0xff); | |
113 ptr += 2; | |
114 } | |
115 | |
116 static inline void Push8(uint8 v, uint8 * &ptr) | |
117 { | |
118 *ptr++ = v; | |
119 } | |
120 | |
121 // little-endian integer read/write functions: | |
122 static inline uint16 Read16(const uint8 *ptr) | |
123 { | |
124 return ptr[0] | (ptr[1] << 8); | |
125 } | |
126 | |
127 static inline void Write16(uint16 v, uint8 *ptr) | |
128 { | |
129 ptr[0] = uint8(v & 0xff); | |
130 ptr[1] = uint8((v >> 8) & 0xff); | |
131 } | |
132 | |
133 static long file_length(FILE *fp) | |
134 { | |
135 long cur_pos = ftell(fp); | |
136 fseek(fp, 0, SEEK_END); | |
137 long length = ftell(fp); | |
138 fseek(fp, cur_pos, SEEK_SET); | |
139 return length; | |
140 } | |
141 | |
142 static int bytes_per_frame(SMovie &mov) | |
143 { | |
144 int num_controllers = 0; | |
145 | |
146 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) | |
147 if (mov.header.controllerFlags & MOVIE_CONTROLLER(i)) | |
148 ++num_controllers; | |
149 | |
150 return CONTROLLER_DATA_SIZE * num_controllers; | |
151 } | |
152 | |
153 static void reserve_buffer_space(uint32 space_needed) | |
154 { | |
155 if (space_needed > Movie.inputBufferSize) | |
156 { | |
157 uint32 ptr_offset = Movie.inputBufferPtr - Movie.inputBuffer; | |
158 uint32 alloc_chunks = (space_needed - 1) / BUFFER_GROWTH_SIZE + 1; | |
159 uint32 old_size = Movie.inputBufferSize; | |
160 Movie.inputBufferSize = BUFFER_GROWTH_SIZE * alloc_chunks; | |
161 Movie.inputBuffer = (uint8 *)realloc(Movie.inputBuffer, Movie.inputBufferSize); | |
162 // FIXME: this only fixes the random input problem during dma-frame-skip, but not the skip | |
163 memset(Movie.inputBuffer + old_size, 0, Movie.inputBufferSize - old_size); | |
164 Movie.inputBufferPtr = Movie.inputBuffer + ptr_offset; | |
165 } | |
166 } | |
167 | |
168 static int read_movie_header(FILE *file, SMovie &movie) | |
169 { | |
170 assert(file != NULL); | |
171 assert(VBM_HEADER_SIZE == sizeof(SMovieFileHeader)); // sanity check on the header type definition | |
172 | |
173 uint8 headerData [VBM_HEADER_SIZE]; | |
174 | |
175 if (fread(headerData, 1, VBM_HEADER_SIZE, file) != VBM_HEADER_SIZE) | |
176 return MOVIE_WRONG_FORMAT; // if we failed to read in all VBM_HEADER_SIZE bytes of the header | |
177 | |
178 const uint8 * ptr = headerData; | |
179 SMovieFileHeader &header = movie.header; | |
180 | |
181 header.magic = Pop32(ptr); | |
182 if (header.magic != VBM_MAGIC) | |
183 return MOVIE_WRONG_FORMAT; | |
184 | |
185 header.version = Pop32(ptr); | |
186 if (header.version != VBM_VERSION) | |
187 return MOVIE_WRONG_VERSION; | |
188 | |
189 header.uid = Pop32(ptr); | |
190 header.length_frames = Pop32(ptr) + 1; // HACK: add 1 to the length for compatibility | |
191 header.rerecord_count = Pop32(ptr); | |
192 | |
193 header.startFlags = Pop8(ptr); | |
194 header.controllerFlags = Pop8(ptr); | |
195 header.typeFlags = Pop8(ptr); | |
196 header.optionFlags = Pop8(ptr); | |
197 | |
198 header.saveType = Pop32(ptr); | |
199 header.flashSize = Pop32(ptr); | |
200 header.gbEmulatorType = Pop32(ptr); | |
201 | |
202 for (int i = 0; i < 12; i++) | |
203 header.romTitle[i] = Pop8(ptr); | |
204 | |
205 header.minorVersion = Pop8(ptr); | |
206 | |
207 header.romCRC = Pop8(ptr); | |
208 header.romOrBiosChecksum = Pop16(ptr); | |
209 header.romGameCode = Pop32(ptr); | |
210 | |
211 header.offset_to_savestate = Pop32(ptr); | |
212 header.offset_to_controller_data = Pop32(ptr); | |
213 | |
214 return MOVIE_SUCCESS; | |
215 } | |
216 | |
217 static void write_movie_header(FILE *file, const SMovie &movie) | |
218 { | |
219 assert(ftell(file) == 0); // we assume file points to beginning of movie file | |
220 | |
221 uint8 headerData [VBM_HEADER_SIZE]; | |
222 uint8 *ptr = headerData; | |
223 const SMovieFileHeader &header = movie.header; | |
224 | |
225 Push32(header.magic, ptr); | |
226 Push32(header.version, ptr); | |
227 | |
228 Push32(header.uid, ptr); | |
229 Push32(header.length_frames - 1, ptr); // HACK: reduce the length by 1 for compatibility with certain faulty old tools | |
230 // like TME | |
231 Push32(header.rerecord_count, ptr); | |
232 | |
233 Push8(header.startFlags, ptr); | |
234 Push8(header.controllerFlags, ptr); | |
235 Push8(header.typeFlags, ptr); | |
236 Push8(header.optionFlags, ptr); | |
237 | |
238 Push32(header.saveType, ptr); | |
239 Push32(header.flashSize, ptr); | |
240 Push32(header.gbEmulatorType, ptr); | |
241 | |
242 for (int i = 0; i < 12; ++i) | |
243 Push8(header.romTitle[i], ptr); | |
244 | |
245 Push8(header.minorVersion, ptr); | |
246 | |
247 Push8(header.romCRC, ptr); | |
248 Push16(header.romOrBiosChecksum, ptr); | |
249 Push32(header.romGameCode, ptr); | |
250 | |
251 Push32(header.offset_to_savestate, ptr); | |
252 Push32(header.offset_to_controller_data, ptr); | |
253 | |
254 fwrite(headerData, 1, VBM_HEADER_SIZE, file); | |
255 } | |
256 | |
257 static void flush_movie_header() | |
258 { | |
259 assert(Movie.file != 0 && "logical error!"); | |
260 if (!Movie.file) | |
261 return; | |
262 | |
263 long originalPos = ftell(Movie.file); | |
264 | |
265 // (over-)write the header | |
266 fseek(Movie.file, 0, SEEK_SET); | |
267 write_movie_header(Movie.file, Movie); | |
268 | |
269 fflush(Movie.file); | |
270 | |
271 fseek(Movie.file, originalPos, SEEK_SET); | |
272 } | |
273 | |
274 static void flush_movie_frames() | |
275 { | |
276 assert(Movie.file && "logical error!"); | |
277 if (!Movie.file) | |
278 return; | |
279 | |
280 long originalPos = ftell(Movie.file); | |
281 | |
282 // overwrite the controller data | |
283 fseek(Movie.file, Movie.header.offset_to_controller_data, SEEK_SET); | |
284 fwrite(Movie.inputBuffer, 1, Movie.bytesPerFrame * Movie.header.length_frames, Movie.file); | |
285 | |
286 fflush(Movie.file); | |
287 | |
288 fseek(Movie.file, originalPos, SEEK_SET); | |
289 } | |
290 | |
291 static void truncate_movie(long length) | |
292 { | |
293 // truncate movie to length | |
294 // NOTE: it's certain that the savestate block is never after the | |
295 // controller data block, because the VBM format decrees it. | |
296 | |
297 assert(Movie.file && length >= 0); | |
298 if (!Movie.file || length < 0) | |
299 return; | |
300 | |
301 assert(Movie.header.offset_to_savestate <= Movie.header.offset_to_controller_data); | |
302 if (Movie.header.offset_to_savestate > Movie.header.offset_to_controller_data) | |
303 return; | |
304 | |
305 Movie.header.length_frames = length; | |
306 flush_movie_header(); | |
307 const long truncLen = long(Movie.header.offset_to_controller_data + Movie.bytesPerFrame * length); | |
308 if (file_length(Movie.file) != truncLen) | |
309 { | |
310 ftruncate(fileno(Movie.file), truncLen); | |
311 } | |
312 } | |
313 | |
314 static void remember_input_state() | |
315 { | |
316 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) | |
317 { | |
318 if (systemCartridgeType == 0) | |
319 { | |
320 initialInputs[i] = u16(~P1 & 0x03FF); | |
321 } | |
322 else | |
323 { | |
324 extern int32 gbJoymask[4]; | |
325 for (int i = 0; i < 4; ++i) | |
326 initialInputs[i] = u16(gbJoymask[i] & 0xFFFF); | |
327 } | |
328 } | |
329 } | |
330 | |
331 static void change_state(MovieState new_state) | |
332 { | |
333 #if (defined(WIN32) && !defined(SDL)) | |
334 theApp.frameSearching = false; | |
335 theApp.frameSearchSkipping = false; | |
336 #endif | |
337 | |
338 if (new_state == MOVIE_STATE_NONE) | |
339 { | |
340 Movie.pauseFrame = -1; | |
341 | |
342 if (Movie.state == MOVIE_STATE_NONE) | |
343 return; | |
344 | |
345 truncate_movie(Movie.header.length_frames); | |
346 | |
347 fclose(Movie.file); | |
348 Movie.file = NULL; | |
349 Movie.currentFrame = 0; | |
350 #if (defined(WIN32) && !defined(SDL)) | |
351 // undo changes to border settings | |
352 { | |
353 gbBorderOn = prevBorder; | |
354 theApp.winGbBorderOn = prevWinBorder; | |
355 gbBorderAutomatic = prevBorderAuto; | |
356 systemGbBorderOn(); | |
357 } | |
358 #endif | |
359 gbEmulatorType = prevEmulatorType; | |
360 | |
361 extern int32 gbDMASpeedVersion; | |
362 gbDMASpeedVersion = 1; | |
363 | |
364 extern int32 gbEchoRAMFixOn; | |
365 gbEchoRAMFixOn = 1; | |
366 | |
367 gbNullInputHackTempEnabled = gbNullInputHackEnabled; | |
368 | |
369 if (Movie.inputBuffer) | |
370 { | |
371 free(Movie.inputBuffer); | |
372 Movie.inputBuffer = NULL; | |
373 } | |
374 } | |
375 else if (new_state == MOVIE_STATE_PLAY) | |
376 { | |
377 assert(Movie.file); | |
378 | |
379 // this would cause problems if not dealt with | |
380 if (Movie.currentFrame >= Movie.header.length_frames) | |
381 { | |
382 new_state = MOVIE_STATE_END; | |
383 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames; | |
384 } | |
385 } | |
386 else if (new_state == MOVIE_STATE_RECORD) | |
387 { | |
388 assert(Movie.file); | |
389 | |
390 // this would cause problems if not dealt with | |
391 if (Movie.currentFrame > Movie.header.length_frames) | |
392 { | |
393 new_state = MOVIE_STATE_END; | |
394 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames; | |
395 } | |
396 | |
397 fseek(Movie.file, Movie.header.offset_to_controller_data + Movie.bytesPerFrame * Movie.currentFrame, SEEK_SET); | |
398 } | |
399 | |
400 if (new_state == MOVIE_STATE_END && Movie.state != MOVIE_STATE_END) | |
401 { | |
402 #if defined(SDL) | |
403 systemClearJoypads(); | |
404 #endif | |
405 systemScreenMessage("Movie end"); | |
406 } | |
407 | |
408 Movie.state = new_state; | |
409 | |
410 // checking for movie end | |
411 bool willPause = false; | |
412 | |
413 // if the movie's been set to pause at a certain frame | |
414 if (Movie.state != MOVIE_STATE_NONE && Movie.pauseFrame >= 0 && Movie.currentFrame == (uint32)Movie.pauseFrame) | |
415 { | |
416 Movie.pauseFrame = -1; | |
417 willPause = true; | |
418 } | |
419 | |
420 if (Movie.state == MOVIE_STATE_END) | |
421 { | |
422 if (Movie.currentFrame == Movie.header.length_frames) | |
423 { | |
424 #if (defined(WIN32) && !defined(SDL)) | |
425 if (theApp.movieOnEndPause) | |
426 { | |
427 willPause = true; | |
428 } | |
429 #else | |
430 // SDL FIXME | |
431 #endif | |
432 | |
433 #if (defined(WIN32) && !defined(SDL)) | |
434 switch (theApp.movieOnEndBehavior) | |
435 { | |
436 case 1: | |
437 // the old behavior | |
438 //VBAMovieRestart(); | |
439 break; | |
440 case 2: | |
441 #else | |
442 // SDL FIXME | |
443 #endif | |
444 if (Movie.RecordedThisSession) | |
445 { | |
446 // if user has been recording this movie since the last time it started playing, | |
447 // they probably don't want the movie to end now during playback, | |
448 // so switch back to recording when it reaches the end | |
449 VBAMovieSwitchToRecording(); | |
450 systemScreenMessage("Recording resumed"); | |
451 willPause = true; | |
452 } | |
453 #if (defined(WIN32) && !defined(SDL)) | |
454 break; | |
455 case 3: | |
456 // keep open | |
457 break; | |
458 case 0: | |
459 // fall through | |
460 default: | |
461 // close movie | |
462 //VBAMovieStop(false); | |
463 break; | |
464 } | |
465 #else | |
466 // SDL FIXME | |
467 #endif | |
468 } | |
469 #if 1 | |
470 else if (Movie.currentFrame > Movie.header.length_frames) | |
471 { | |
472 #if (defined(WIN32) && !defined(SDL)) | |
473 switch (theApp.movieOnEndBehavior) | |
474 { | |
475 case 1: | |
476 // FIXME: this should be delayed till the current frame ends | |
477 VBAMovieRestart(); | |
478 break; | |
479 case 2: | |
480 // nothing | |
481 break; | |
482 case 3: | |
483 // keep open | |
484 break; | |
485 case 0: | |
486 // fall through | |
487 default: | |
488 // close movie | |
489 VBAMovieStop(false); | |
490 break; | |
491 } | |
492 #else | |
493 // SDLFIXME | |
494 #endif | |
495 } | |
496 #endif | |
497 } // end if (Movie.state == MOVIE_STATE_END) | |
498 | |
499 if (willPause) | |
500 { | |
501 systemSetPause(true); | |
502 } | |
503 } | |
504 | |
505 void VBAMovieInit() | |
506 { | |
507 memset(&Movie, 0, sizeof(Movie)); | |
508 Movie.state = MOVIE_STATE_NONE; | |
509 Movie.pauseFrame = -1; | |
510 | |
511 resetSignaled = false; | |
512 resetSignaledLast = false; | |
513 } | |
514 | |
515 void VBAMovieGetRomInfo(const SMovie &movieInfo, char romTitle [12], uint32 &romGameCode, uint16 &checksum, uint8 &crc) | |
516 { | |
517 if (systemCartridgeType == 0) // GBA | |
518 { | |
519 extern u8 *bios, *rom; | |
520 memcpy(romTitle, &rom[0xa0], 12); // GBA TITLE | |
521 memcpy(&romGameCode, &rom[0xac], 4); // GBA ROM GAME CODE | |
522 if ((movieInfo.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0) | |
523 checksum = utilCalcBIOSChecksum(bios, 4); // GBA BIOS CHECKSUM | |
524 else | |
525 checksum = 0; | |
526 crc = rom[0xbd]; // GBA ROM CRC | |
527 } | |
528 else // non-GBA | |
529 { | |
530 extern u8 *gbRom; | |
531 memcpy(romTitle, &gbRom[0x134], 12); // GB TITLE (note this can be 15 but is truncated to 12) | |
532 romGameCode = (uint32)gbRom[0x146]; // GB ROM UNIT CODE | |
533 | |
534 checksum = (gbRom[0x14e] << 8) | gbRom[0x14f]; // GB ROM CHECKSUM, read from big-endian | |
535 crc = gbRom[0x14d]; // GB ROM CRC | |
536 } | |
537 } | |
538 | |
539 #ifdef SDL | |
540 static void GetBatterySaveName(char *buffer) | |
541 { | |
542 extern char batteryDir[2048], filename[2048]; // from SDL.cpp | |
543 extern char *sdlGetFilename(char *name); // from SDL.cpp | |
544 if (batteryDir[0]) | |
545 sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename)); | |
546 else | |
547 sprintf(buffer, "%s.sav", filename); | |
548 } | |
549 | |
550 #endif | |
551 | |
552 static void SetPlayEmuSettings() | |
553 { | |
554 prevEmulatorType = gbEmulatorType; | |
555 gbEmulatorType = Movie.header.gbEmulatorType; | |
556 | |
557 #if (defined(WIN32) && !defined(SDL)) | |
558 // theApp.removeIntros = false; | |
559 theApp.skipBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0; | |
560 theApp.useBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0; | |
561 #else | |
562 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp | |
563 extern bool8 useBios, skipBios, removeIntros; // from SDL.cpp | |
564 useBios = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0; | |
565 skipBios = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0; | |
566 removeIntros = false /*(Movie.header.optionFlags & MOVIE_SETTING_REMOVEINTROS) != 0*/; | |
567 #endif | |
568 | |
569 extern void SetPrefetchHack(bool); | |
570 if (systemCartridgeType == 0) // lag disablement applies only to GBA | |
571 SetPrefetchHack((Movie.header.optionFlags & MOVIE_SETTING_LAGHACK) != 0); | |
572 | |
573 gbNullInputHackTempEnabled = ((Movie.header.optionFlags & MOVIE_SETTING_GBINPUTHACK) != 0); | |
574 | |
575 // some GB/GBC games depend on the sound rate, so just use the highest one | |
576 systemSoundSetQuality(1); | |
577 useOldFrameTiming = false; | |
578 | |
579 extern int32 gbDMASpeedVersion; | |
580 if ((Movie.header.optionFlags & MOVIE_SETTING_GBCFF55FIX) != 0) | |
581 gbDMASpeedVersion = 1; | |
582 else | |
583 gbDMASpeedVersion = 0; // old CGB HDMA5 timing was used | |
584 | |
585 extern int32 gbEchoRAMFixOn; | |
586 if ((Movie.header.optionFlags & MOVIE_SETTING_GBECHORAMFIX) != 0) | |
587 gbEchoRAMFixOn = 1; | |
588 else | |
589 gbEchoRAMFixOn = 0; | |
590 | |
591 #if (defined(WIN32) && !defined(SDL)) | |
592 rtcEnable((Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0); | |
593 theApp.winSaveType = Movie.header.saveType; | |
594 theApp.winFlashSize = Movie.header.flashSize; | |
595 | |
596 prevBorder = gbBorderOn; | |
597 prevWinBorder = theApp.winGbBorderOn; | |
598 prevBorderAuto = gbBorderAutomatic; | |
599 if ((gbEmulatorType == 2 || gbEmulatorType == 5) | |
600 && !theApp.hideMovieBorder) // games played in SGB mode can have a border | |
601 { | |
602 gbBorderOn = true; | |
603 theApp.winGbBorderOn = true; | |
604 gbBorderAutomatic = false; | |
605 } | |
606 else | |
607 { | |
608 gbBorderOn = false; | |
609 theApp.winGbBorderOn = false; | |
610 gbBorderAutomatic = false; | |
611 if (theApp.hideMovieBorder) | |
612 { | |
613 theApp.hideMovieBorder = false; | |
614 prevBorder = false; // it might be expected behaviour that it stays hidden after the movie | |
615 } | |
616 } | |
617 systemGbBorderOn(); | |
618 #else | |
619 sdlRtcEnable = (Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0; | |
620 saveType = Movie.header.saveType; | |
621 sdlFlashSize = Movie.header.flashSize; | |
622 #endif | |
623 } | |
624 | |
625 static void HardResetAndSRAMClear() | |
626 { | |
627 #if (defined(WIN32) && !defined(SDL)) | |
628 winEraseBatteryFile(); // delete the damn SRAM file and keep it from being resurrected from RAM | |
629 MainWnd *temp = ((MainWnd *)theApp.m_pMainWnd); | |
630 if (!temp->winFileRun(true)) // restart running the game | |
631 { | |
632 temp->winFileClose(); | |
633 } | |
634 #else | |
635 char fname [1024]; | |
636 GetBatterySaveName(fname); | |
637 remove(fname); // delete the damn SRAM file | |
638 | |
639 // Henceforth, emuCleanUp means "clear out SRAM" | |
640 //theEmulator.emuCleanUp(); // keep it from being resurrected from RAM <--This is wrong, it'll deallocate all variables --Felipe | |
641 | |
642 /// FIXME the correct SDL code to call for a full restart isn't in a function yet | |
643 theEmulator.emuReset(false); | |
644 #endif | |
645 } | |
646 | |
647 int VBAMovieOpen(const char *filename, bool8 read_only) | |
648 { | |
649 loadingMovie = true; | |
650 uint8 movieReadOnly = read_only ? 1 : 0; | |
651 | |
652 FILE * file; | |
653 STREAM stream; | |
654 int result; | |
655 int fn; | |
656 | |
657 char movie_filename[_MAX_PATH]; | |
658 #ifdef WIN32 | |
659 _fullpath(movie_filename, filename, _MAX_PATH); | |
660 #else | |
661 // SDL FIXME: convert to fullpath | |
662 strncpy(movie_filename, filename, _MAX_PATH); | |
663 movie_filename[_MAX_PATH - 1] = '\0'; | |
664 #endif | |
665 | |
666 if (movie_filename[0] == '\0') | |
667 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
668 | |
669 if (!emulating) | |
670 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } | |
671 | |
672 // bool alreadyOpen = (Movie.file != NULL && _stricmp(movie_filename, Movie.filename) == 0); | |
673 | |
674 // if (alreadyOpen) | |
675 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it | |
676 | |
677 if (!(file = fopen(movie_filename, "rb+"))) | |
678 if (!(file = fopen(movie_filename, "rb"))) | |
679 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
680 //else | |
681 // movieReadOnly = 2; // we have to open the movie twice, no need to do this both times | |
682 | |
683 // if (!alreadyOpen) | |
684 // change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one | |
685 // | |
686 // if (!(file = fopen(movie_filename, "rb+"))) | |
687 // if(!(file = fopen(movie_filename, "rb"))) | |
688 // {loadingMovie = false; return MOVIE_FILE_NOT_FOUND;} | |
689 // else | |
690 // movieReadOnly = 2; | |
691 | |
692 // clear out the current movie | |
693 VBAMovieInit(); | |
694 | |
695 // read header | |
696 if ((result = read_movie_header(file, Movie)) != MOVIE_SUCCESS) | |
697 { | |
698 fclose(file); | |
699 { loadingMovie = false; return result; } | |
700 } | |
701 | |
702 // set emulator settings that make the movie more likely to stay synchronized | |
703 SetPlayEmuSettings(); | |
704 | |
705 // extern bool systemLoadBIOS(); | |
706 // if (!systemLoadBIOS()) | |
707 // { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } | |
708 | |
709 // read the metadata / author info from file | |
710 fread(Movie.authorInfo, 1, MOVIE_METADATA_SIZE, file); | |
711 fn = dup(fileno(file)); // XXX: why does this fail?? it returns -1 but errno == 0 | |
712 fclose(file); | |
713 | |
714 // apparently this lseek is necessary | |
715 lseek(fn, Movie.header.offset_to_savestate, SEEK_SET); | |
716 if (!(stream = utilGzReopen(fn, "rb"))) | |
717 if (!(stream = utilGzOpen(movie_filename, "rb"))) | |
718 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
719 else | |
720 fn = dup(fileno(file)); | |
721 // in case the above dup failed but opening the file normally doesn't fail | |
722 | |
723 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT) | |
724 { | |
725 // load the snapshot | |
726 result = theEmulator.emuReadStateFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT; | |
727 | |
728 // FIXME: Kludge for conversion | |
729 remember_input_state(); | |
730 } | |
731 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM) | |
732 { | |
733 // 'soft' reset: | |
734 theEmulator.emuReset(false); | |
735 | |
736 // load the SRAM | |
737 result = theEmulator.emuReadBatteryFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT; | |
738 } | |
739 else | |
740 { | |
741 HardResetAndSRAMClear(); | |
742 } | |
743 | |
744 utilGzClose(stream); | |
745 | |
746 if (result != MOVIE_SUCCESS) | |
747 { loadingMovie = false; return result; } | |
748 | |
749 // if (!(file = fopen(movie_filename, /*read_only ? "rb" :*/ "rb+"))) // want to be able to switch out of read-only later | |
750 // { | |
751 // if(!Movie.readOnly || !(file = fopen(movie_filename, "rb"))) // try read-only if failed | |
752 // return MOVIE_FILE_NOT_FOUND; | |
753 // } | |
754 if (!(file = fopen(movie_filename, "rb+"))) | |
755 if (!(file = fopen(movie_filename, "rb"))) | |
756 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
757 else | |
758 movieReadOnly = 2; | |
759 | |
760 // recalculate length of movie from the file size | |
761 Movie.bytesPerFrame = bytes_per_frame(Movie); | |
762 fseek(file, 0, SEEK_END); | |
763 long fileSize = ftell(file); | |
764 Movie.header.length_frames = (fileSize - Movie.header.offset_to_controller_data) / Movie.bytesPerFrame; | |
765 | |
766 if (fseek(file, Movie.header.offset_to_controller_data, SEEK_SET)) | |
767 { fclose(file); loadingMovie = false; return MOVIE_WRONG_FORMAT; } | |
768 | |
769 strcpy(Movie.filename, movie_filename); | |
770 Movie.file = file; | |
771 Movie.inputBufferPtr = Movie.inputBuffer; | |
772 Movie.currentFrame = 0; | |
773 Movie.readOnly = movieReadOnly; | |
774 Movie.RecordedThisSession = false; | |
775 | |
776 // read controller data | |
777 uint32 to_read = Movie.bytesPerFrame * Movie.header.length_frames; | |
778 reserve_buffer_space(to_read); | |
779 fread(Movie.inputBuffer, 1, to_read, file); | |
780 | |
781 change_state(MOVIE_STATE_PLAY); | |
782 | |
783 char messageString[64] = "Movie "; | |
784 bool converted = false; | |
785 if (autoConvertMovieWhenPlaying) | |
786 { | |
787 int result = VBAMovieConvertCurrent(); | |
788 if (result == MOVIE_SUCCESS) | |
789 strcat(messageString, "converted and "); | |
790 else if (result == MOVIE_WRONG_VERSION) | |
791 strcat(messageString, "higher revision "); | |
792 } | |
793 | |
794 if (Movie.state == MOVIE_STATE_PLAY) | |
795 strcat(messageString, "replaying "); | |
796 else | |
797 strcat(messageString, "finished "); | |
798 if (Movie.readOnly) | |
799 strcat(messageString, "(read)"); | |
800 else | |
801 strcat(messageString, "(edit)"); | |
802 systemScreenMessage(messageString); | |
803 | |
804 VBAUpdateButtonPressDisplay(); | |
805 VBAUpdateFrameCountDisplay(); | |
806 systemRefreshScreen(); | |
807 | |
808 { loadingMovie = false; return MOVIE_SUCCESS; } | |
809 } | |
810 | |
811 static void SetRecordEmuSettings() | |
812 { | |
813 Movie.header.optionFlags = 0; | |
814 #if (defined(WIN32) && !defined(SDL)) | |
815 if (theApp.useBiosFile) | |
816 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE; | |
817 if (theApp.skipBiosFile) | |
818 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE; | |
819 if (rtcIsEnabled()) | |
820 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE; | |
821 Movie.header.saveType = theApp.winSaveType; | |
822 Movie.header.flashSize = theApp.winFlashSize; | |
823 #else | |
824 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp | |
825 extern bool8 useBios, skipBios; // from SDL.cpp | |
826 if (useBios) | |
827 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE; | |
828 if (skipBios) | |
829 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE; | |
830 if (sdlRtcEnable) | |
831 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE; | |
832 Movie.header.saveType = saveType; | |
833 Movie.header.flashSize = sdlFlashSize; | |
834 #endif | |
835 prevEmulatorType = Movie.header.gbEmulatorType = gbEmulatorType; | |
836 | |
837 if (!memLagTempEnabled) | |
838 Movie.header.optionFlags |= MOVIE_SETTING_LAGHACK; | |
839 | |
840 if (gbNullInputHackTempEnabled) | |
841 Movie.header.optionFlags |= MOVIE_SETTING_GBINPUTHACK; | |
842 | |
843 Movie.header.optionFlags |= MOVIE_SETTING_GBCFF55FIX; | |
844 extern int32 gbDMASpeedVersion; | |
845 gbDMASpeedVersion = 1; | |
846 | |
847 Movie.header.optionFlags |= MOVIE_SETTING_GBECHORAMFIX; | |
848 extern int32 gbEchoRAMFixOn; | |
849 gbEchoRAMFixOn = 1; | |
850 | |
851 // some GB/GBC games depend on the sound rate, so just use the highest one | |
852 systemSoundSetQuality(1); | |
853 | |
854 useOldFrameTiming = false; | |
855 | |
856 #if (defined(WIN32) && !defined(SDL)) | |
857 // theApp.removeIntros = false; | |
858 | |
859 prevBorder = gbBorderOn; | |
860 prevWinBorder = theApp.winGbBorderOn; | |
861 prevBorderAuto = gbBorderAutomatic; | |
862 if (gbEmulatorType == 2 || gbEmulatorType == 5) // only games played in SGB mode will have a border | |
863 { | |
864 gbBorderOn = true; | |
865 theApp.winGbBorderOn = true; | |
866 gbBorderAutomatic = false; | |
867 } | |
868 else | |
869 { | |
870 gbBorderOn = false; | |
871 theApp.winGbBorderOn = false; | |
872 gbBorderAutomatic = false; | |
873 } | |
874 systemGbBorderOn(); | |
875 #else | |
876 /// SDLFIXME | |
877 #endif | |
878 } | |
879 | |
880 uint16 VBAMovieGetCurrentInputOf(int controllerNum, bool normalOnly) | |
881 { | |
882 if (controllerNum < 0 || controllerNum >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS) | |
883 return 0; | |
884 | |
885 return normalOnly ? (currentButtons[controllerNum] & BUTTON_REGULAR_MASK) : currentButtons[controllerNum]; | |
886 } | |
887 | |
888 int VBAMovieCreate(const char *filename, const char *authorInfo, uint8 startFlags, uint8 controllerFlags, uint8 typeFlags) | |
889 { | |
890 // make sure at least one controller is enabled | |
891 if ((controllerFlags & MOVIE_CONTROLLERS_ANY_MASK) == 0) | |
892 return MOVIE_WRONG_FORMAT; | |
893 | |
894 if (!emulating) | |
895 return MOVIE_UNKNOWN_ERROR; | |
896 | |
897 loadingMovie = true; | |
898 | |
899 FILE * file; | |
900 STREAM stream; | |
901 int fn; | |
902 | |
903 char movie_filename [_MAX_PATH]; | |
904 #ifdef WIN32 | |
905 _fullpath(movie_filename, filename, _MAX_PATH); | |
906 #else | |
907 // FIXME: convert to fullpath | |
908 strncpy(movie_filename, filename, _MAX_PATH); | |
909 movie_filename[_MAX_PATH - 1] = '\0'; | |
910 #endif | |
911 | |
912 bool alreadyOpen = (Movie.file != NULL && stricmp(movie_filename, Movie.filename) == 0); | |
913 | |
914 if (alreadyOpen) | |
915 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it | |
916 | |
917 if (movie_filename[0] == '\0') | |
918 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
919 | |
920 if (!(file = fopen(movie_filename, "wb"))) | |
921 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
922 | |
923 if (!alreadyOpen) | |
924 change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one | |
925 | |
926 // clear out the current movie | |
927 VBAMovieInit(); | |
928 | |
929 // fill in the movie's header | |
930 Movie.header.uid = (uint32)time(NULL); | |
931 Movie.header.magic = VBM_MAGIC; | |
932 Movie.header.version = VBM_VERSION; | |
933 Movie.header.rerecord_count = 0; | |
934 Movie.header.length_frames = 0; | |
935 Movie.header.startFlags = startFlags; | |
936 Movie.header.controllerFlags = controllerFlags; | |
937 Movie.header.typeFlags = typeFlags; | |
938 Movie.header.minorVersion = VBM_REVISION; | |
939 | |
940 // set emulator settings that make the movie more likely to stay synchronized when it's later played back | |
941 SetRecordEmuSettings(); | |
942 | |
943 // set ROM and BIOS checksums and stuff | |
944 VBAMovieGetRomInfo(Movie, Movie.header.romTitle, Movie.header.romGameCode, Movie.header.romOrBiosChecksum, Movie.header.romCRC); | |
945 | |
946 // write the header to file | |
947 write_movie_header(file, Movie); | |
948 | |
949 // copy over the metadata / author info | |
950 VBAMovieSetMetadata(authorInfo); | |
951 | |
952 // write the metadata / author info to file | |
953 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file); | |
954 | |
955 // write snapshot or SRAM if applicable | |
956 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT | |
957 || Movie.header.startFlags & MOVIE_START_FROM_SRAM) | |
958 { | |
959 Movie.header.offset_to_savestate = (uint32)ftell(file); | |
960 | |
961 // close the file and reopen it as a stream: | |
962 | |
963 fn = dup(fileno(file)); | |
964 fclose(file); | |
965 | |
966 if (!(stream = utilGzReopen(fn, "ab"))) // append mode to start at end, no seek necessary | |
967 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
968 | |
969 // write the save data: | |
970 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT) | |
971 { | |
972 // save snapshot | |
973 if (!theEmulator.emuWriteStateToStream(stream)) | |
974 { | |
975 utilGzClose(stream); | |
976 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } | |
977 } | |
978 } | |
979 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM) | |
980 { | |
981 // save SRAM | |
982 if (!theEmulator.emuWriteBatteryToStream(stream)) | |
983 { | |
984 utilGzClose(stream); | |
985 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } | |
986 } | |
987 | |
988 // 'soft' reset: | |
989 theEmulator.emuReset(false); | |
990 } | |
991 | |
992 utilGzClose(stream); | |
993 | |
994 // reopen the file and seek back to the end | |
995 | |
996 if (!(file = fopen(movie_filename, "rb+"))) | |
997 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } | |
998 | |
999 fseek(file, 0, SEEK_END); | |
1000 } | |
1001 else // no snapshot or SRAM | |
1002 { | |
1003 HardResetAndSRAMClear(); | |
1004 } | |
1005 | |
1006 Movie.header.offset_to_controller_data = (uint32)ftell(file); | |
1007 | |
1008 strcpy(Movie.filename, movie_filename); | |
1009 Movie.file = file; | |
1010 Movie.bytesPerFrame = bytes_per_frame(Movie); | |
1011 Movie.inputBufferPtr = Movie.inputBuffer; | |
1012 Movie.currentFrame = 0; | |
1013 Movie.readOnly = false; | |
1014 Movie.RecordedThisSession = true; | |
1015 | |
1016 change_state(MOVIE_STATE_RECORD); | |
1017 | |
1018 systemScreenMessage("Recording movie..."); | |
1019 { loadingMovie = false; return MOVIE_SUCCESS; } | |
1020 } | |
1021 | |
1022 void VBAUpdateButtonPressDisplay() | |
1023 { | |
1024 uint32 keys = currentButtons[0] & BUTTON_REGULAR_RECORDING_MASK; | |
1025 | |
1026 const static char KeyMap[] = { 'A', 'B', 's', 'S', '>', '<', '^', 'v', 'R', 'L', '!', '?', '{', '}', 'v', '^' }; | |
1027 const static int KeyOrder[] = { 5, 6, 4, 7, 0, 1, 9, 8, 3, 2, 12, 15, 13, 14, 11, 10 }; // < ^ > v A B L R S s { = } _ | |
1028 // ? ! | |
1029 char buffer[256]; | |
1030 sprintf(buffer, " "); | |
1031 | |
1032 #ifndef WIN32 | |
1033 // don't bother color-coding autofire and such | |
1034 int i; | |
1035 for (i = 0; i < 15; i++) | |
1036 { | |
1037 int j = KeyOrder[i]; | |
1038 int mask = (1 << (j)); | |
1039 buffer[strlen(" ") + i] = ((keys & mask) != 0) ? KeyMap[j] : ' '; | |
1040 } | |
1041 | |
1042 systemScreenMessage(buffer, 2, -1); | |
1043 #else | |
1044 const bool eraseAll = !theApp.inputDisplay; | |
1045 uint32 autoHeldKeys = eraseAll ? 0 : theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK; | |
1046 uint32 autoFireKeys = eraseAll ? 0 : (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK; | |
1047 uint32 pressedKeys = eraseAll ? 0 : keys; | |
1048 | |
1049 char colorList[64]; | |
1050 memset(colorList, 1, strlen(buffer)); | |
1051 | |
1052 if (!eraseAll) | |
1053 { | |
1054 for (int i = 0; i < 15; i++) | |
1055 { | |
1056 const int j = KeyOrder[i]; | |
1057 const int mask = (1 << (j)); | |
1058 bool pressed = (pressedKeys & mask) != 0; | |
1059 const bool autoHeld = (autoHeldKeys & mask) != 0; | |
1060 const bool autoFired = (autoFireKeys & mask) != 0; | |
1061 const bool erased = (lastKeys & mask) != 0 && (!pressed && !autoHeld && !autoFired); | |
1062 extern int textMethod; | |
1063 if (textMethod != 2 && (autoHeld || (autoFired && !pressed) || erased)) | |
1064 { | |
1065 int colorNum = 1; // default is white | |
1066 if (autoHeld) | |
1067 colorNum += (pressed ? 2 : 1); // yellow if pressed, red if not | |
1068 else if (autoFired) | |
1069 colorNum += 5; // blue if autofired and not currently pressed | |
1070 else if (erased) | |
1071 colorNum += 8; // black on black | |
1072 | |
1073 colorList[strlen(" ") + i] = colorNum; | |
1074 pressed = true; | |
1075 } | |
1076 buffer[strlen(" ") + i] = pressed ? KeyMap[j] : ' '; | |
1077 } | |
1078 } | |
1079 | |
1080 lastKeys = currentButtons[0]; | |
1081 lastKeys |= theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK; | |
1082 lastKeys |= (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK; | |
1083 | |
1084 systemScreenMessage(buffer, 2, -1, colorList); | |
1085 #endif | |
1086 } | |
1087 | |
1088 void VBAUpdateFrameCountDisplay() | |
1089 { | |
1090 const int MAGICAL_NUMBER = 64; // FIXME: this won't do any better, but only to remind you of sz issues | |
1091 char frameDisplayString[MAGICAL_NUMBER]; | |
1092 char lagFrameDisplayString[MAGICAL_NUMBER]; | |
1093 char extraCountDisplayString[MAGICAL_NUMBER]; | |
1094 | |
1095 #if (defined(WIN32) && !defined(SDL)) | |
1096 if (theApp.frameCounter) | |
1097 #else | |
1098 /// SDL FIXME | |
1099 #endif | |
1100 { | |
1101 switch (Movie.state) | |
1102 { | |
1103 case MOVIE_STATE_PLAY: | |
1104 case MOVIE_STATE_END: | |
1105 { | |
1106 sprintf(frameDisplayString, "%d / %d", Movie.currentFrame, Movie.header.length_frames); | |
1107 if (!Movie.readOnly) | |
1108 strcat(frameDisplayString, " (edit)"); | |
1109 break; | |
1110 } | |
1111 case MOVIE_STATE_RECORD: | |
1112 { | |
1113 sprintf(frameDisplayString, "%d (record)", Movie.currentFrame); | |
1114 break; | |
1115 } | |
1116 default: | |
1117 { | |
1118 sprintf(frameDisplayString, "%d (no movie)", systemCounters.frameCount); | |
1119 break; | |
1120 } | |
1121 } | |
1122 | |
1123 #if (defined(WIN32) && !defined(SDL)) | |
1124 if (theApp.lagCounter) | |
1125 #else | |
1126 /// SDL FIXME | |
1127 #endif | |
1128 { | |
1129 // sprintf(lagFrameDisplayString, " %c %d", systemCounters.laggedLast ? '*' : '|', systemCounters.lagCount); | |
1130 sprintf(lagFrameDisplayString, " | %d%s", systemCounters.lagCount, systemCounters.laggedLast ? " *" : ""); | |
1131 strcat(frameDisplayString, lagFrameDisplayString); | |
1132 } | |
1133 | |
1134 #if (defined(WIN32) && !defined(SDL)) | |
1135 if (theApp.extraCounter) | |
1136 #else | |
1137 /// SDL FIXME | |
1138 #endif | |
1139 { | |
1140 sprintf(extraCountDisplayString, " | %d", systemCounters.frameCount - systemCounters.extraCount); | |
1141 strcat(frameDisplayString, extraCountDisplayString); | |
1142 } | |
1143 } | |
1144 #if (defined(WIN32) && !defined(SDL)) | |
1145 else | |
1146 { | |
1147 frameDisplayString[0] = '\0'; | |
1148 } | |
1149 #else | |
1150 /// SDL FIXME | |
1151 #endif | |
1152 systemScreenMessage(frameDisplayString, 1, -1); | |
1153 } | |
1154 | |
1155 // this function should only be called once every frame | |
1156 void VBAMovieUpdateState() | |
1157 { | |
1158 ++Movie.currentFrame; | |
1159 | |
1160 if (Movie.state == MOVIE_STATE_PLAY) | |
1161 { | |
1162 Movie.inputBufferPtr += Movie.bytesPerFrame; | |
1163 if (Movie.currentFrame >= Movie.header.length_frames) | |
1164 { | |
1165 // the movie ends anyway; what to do next depends on the settings | |
1166 change_state(MOVIE_STATE_END); | |
1167 } | |
1168 } | |
1169 else if (Movie.state == MOVIE_STATE_RECORD) | |
1170 { | |
1171 // use first fseek? | |
1172 fwrite(Movie.inputBufferPtr, 1, Movie.bytesPerFrame, Movie.file); | |
1173 Movie.header.length_frames = Movie.currentFrame; | |
1174 Movie.inputBufferPtr += Movie.bytesPerFrame; | |
1175 Movie.RecordedThisSession = true; | |
1176 flush_movie_header(); | |
1177 } | |
1178 else if (Movie.state == MOVIE_STATE_END) | |
1179 { | |
1180 change_state(MOVIE_STATE_END); | |
1181 } | |
1182 } | |
1183 | |
1184 void VBAMovieRead(int i, bool /*sensor*/) | |
1185 { | |
1186 if (Movie.state != MOVIE_STATE_PLAY) | |
1187 return; | |
1188 | |
1189 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS) | |
1190 return; // not a controller we're recognizing | |
1191 | |
1192 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) | |
1193 { | |
1194 currentButtons[i] = Read16(Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i); | |
1195 } | |
1196 else | |
1197 { | |
1198 currentButtons[i] = 0; // pretend the controller is disconnected | |
1199 } | |
1200 | |
1201 if ((currentButtons[i] & BUTTON_MASK_NEW_RESET) != 0) | |
1202 resetSignaled = true; | |
1203 } | |
1204 | |
1205 void VBAMovieWrite(int i, bool /*sensor*/) | |
1206 { | |
1207 if (Movie.state != MOVIE_STATE_RECORD) | |
1208 return; | |
1209 | |
1210 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS) | |
1211 return; // not a controller we're recognizing | |
1212 | |
1213 reserve_buffer_space((uint32)((Movie.inputBufferPtr - Movie.inputBuffer) + Movie.bytesPerFrame)); | |
1214 | |
1215 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) | |
1216 { | |
1217 // get the current controller data | |
1218 uint16 buttonData = currentButtons[i]; | |
1219 | |
1220 // mask away the irrelevent bits | |
1221 buttonData &= BUTTON_REGULAR_MASK | BUTTON_MOTION_MASK; | |
1222 | |
1223 // soft-reset "button" for 1 frame if the game is reset while recording | |
1224 if (resetSignaled) | |
1225 { | |
1226 buttonData |= BUTTON_MASK_NEW_RESET; | |
1227 } | |
1228 | |
1229 // backward compatibility kludge | |
1230 if (resetSignaledLast) | |
1231 { | |
1232 buttonData |= BUTTON_MASK_OLD_RESET; | |
1233 } | |
1234 | |
1235 Write16(buttonData, Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i); | |
1236 | |
1237 // and for display | |
1238 currentButtons[i] = buttonData; | |
1239 } | |
1240 else | |
1241 { | |
1242 // pretend the controller is disconnected (otherwise input it gives could cause desync since we're not writing it to the | |
1243 // movie) | |
1244 currentButtons[i] = 0; | |
1245 } | |
1246 } | |
1247 | |
1248 void VBAMovieStop(bool8 suppress_message) | |
1249 { | |
1250 if (Movie.state != MOVIE_STATE_NONE) | |
1251 { | |
1252 change_state(MOVIE_STATE_NONE); | |
1253 if (!suppress_message) | |
1254 systemScreenMessage("Movie stop"); | |
1255 } | |
1256 } | |
1257 | |
1258 int VBAMovieGetInfo(const char *filename, SMovie *info) | |
1259 { | |
1260 assert(info != NULL); | |
1261 if (info == NULL) | |
1262 return -1; | |
1263 | |
1264 FILE * file; | |
1265 int result; | |
1266 SMovie &local_movie = *info; | |
1267 | |
1268 memset(info, 0, sizeof(*info)); | |
1269 if (filename[0] == '\0') | |
1270 return MOVIE_FILE_NOT_FOUND; | |
1271 if (!(file = fopen(filename, "rb"))) | |
1272 return MOVIE_FILE_NOT_FOUND; | |
1273 | |
1274 // read header | |
1275 if ((result = (read_movie_header(file, local_movie))) != MOVIE_SUCCESS) | |
1276 { | |
1277 fclose(file); | |
1278 return result; | |
1279 } | |
1280 | |
1281 // read the metadata / author info from file | |
1282 fread(local_movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file); | |
1283 | |
1284 strncpy(local_movie.filename, filename, _MAX_PATH); | |
1285 local_movie.filename[_MAX_PATH - 1] = '\0'; | |
1286 | |
1287 if (Movie.file != NULL && stricmp(local_movie.filename, Movie.filename) == 0) // alreadyOpen | |
1288 { | |
1289 local_movie.bytesPerFrame = Movie.bytesPerFrame; | |
1290 local_movie.header.length_frames = Movie.header.length_frames; | |
1291 } | |
1292 else | |
1293 { | |
1294 // recalculate length of movie from the file size | |
1295 local_movie.bytesPerFrame = bytes_per_frame(local_movie); | |
1296 fseek(file, 0, SEEK_END); | |
1297 int fileSize = ftell(file); | |
1298 local_movie.header.length_frames = | |
1299 (fileSize - local_movie.header.offset_to_controller_data) / local_movie.bytesPerFrame; | |
1300 } | |
1301 | |
1302 fclose(file); | |
1303 | |
1304 if (access(filename, W_OK)) | |
1305 info->readOnly = true; | |
1306 | |
1307 return MOVIE_SUCCESS; | |
1308 } | |
1309 | |
1310 bool8 VBAMovieActive() | |
1311 { | |
1312 return (Movie.state != MOVIE_STATE_NONE); | |
1313 } | |
1314 | |
1315 bool8 VBAMovieLoading() | |
1316 { | |
1317 return loadingMovie; | |
1318 } | |
1319 | |
1320 bool8 VBAMoviePlaying() | |
1321 { | |
1322 return (Movie.state == MOVIE_STATE_PLAY); | |
1323 } | |
1324 | |
1325 bool8 VBAMovieRecording() | |
1326 { | |
1327 return (Movie.state == MOVIE_STATE_RECORD); | |
1328 } | |
1329 | |
1330 bool8 VBAMovieReadOnly() | |
1331 { | |
1332 if (!VBAMovieActive()) | |
1333 return false; | |
1334 | |
1335 return Movie.readOnly; | |
1336 } | |
1337 | |
1338 void VBAMovieToggleReadOnly() | |
1339 { | |
1340 if (!VBAMovieActive()) | |
1341 return; | |
1342 | |
1343 if (Movie.readOnly != 2) | |
1344 { | |
1345 Movie.readOnly = !Movie.readOnly; | |
1346 | |
1347 systemScreenMessage(Movie.readOnly ? "Movie now read-only" : "Movie now editable"); | |
1348 } | |
1349 else | |
1350 { | |
1351 systemScreenMessage("Can't toggle read-only movie"); | |
1352 } | |
1353 } | |
1354 | |
1355 uint32 VBAMovieGetVersion() | |
1356 { | |
1357 if (!VBAMovieActive()) | |
1358 return 0; | |
1359 | |
1360 return Movie.header.version; | |
1361 } | |
1362 | |
1363 uint32 VBAMovieGetMinorVersion() | |
1364 { | |
1365 if (!VBAMovieActive()) | |
1366 return 0; | |
1367 | |
1368 return Movie.header.minorVersion; | |
1369 } | |
1370 | |
1371 uint32 VBAMovieGetId() | |
1372 { | |
1373 if (!VBAMovieActive()) | |
1374 return 0; | |
1375 | |
1376 return Movie.header.uid; | |
1377 } | |
1378 | |
1379 uint32 VBAMovieGetLength() | |
1380 { | |
1381 if (!VBAMovieActive()) | |
1382 return 0; | |
1383 | |
1384 return Movie.header.length_frames; | |
1385 } | |
1386 | |
1387 uint32 VBAMovieGetFrameCounter() | |
1388 { | |
1389 if (!VBAMovieActive()) | |
1390 return 0; | |
1391 | |
1392 return Movie.currentFrame; | |
1393 } | |
1394 | |
1395 uint32 VBAMovieGetRerecordCount() | |
1396 { | |
1397 if (!VBAMovieActive()) | |
1398 return 0; | |
1399 | |
1400 return Movie.header.rerecord_count; | |
1401 } | |
1402 | |
1403 uint32 VBAMovieSetRerecordCount(uint32 newRerecordCount) | |
1404 { | |
1405 uint32 oldRerecordCount = 0; | |
1406 if (!VBAMovieActive()) | |
1407 return 0; | |
1408 | |
1409 oldRerecordCount = Movie.header.rerecord_count; | |
1410 Movie.header.rerecord_count = newRerecordCount; | |
1411 return oldRerecordCount; | |
1412 } | |
1413 | |
1414 std::string VBAMovieGetAuthorInfo() | |
1415 { | |
1416 if (!VBAMovieActive()) | |
1417 return ""; | |
1418 | |
1419 return Movie.authorInfo; | |
1420 } | |
1421 | |
1422 std::string VBAMovieGetFilename() | |
1423 { | |
1424 if (!VBAMovieActive()) | |
1425 return ""; | |
1426 | |
1427 return Movie.filename; | |
1428 } | |
1429 | |
1430 void VBAMovieFreeze(uint8 * *buf, uint32 *size) | |
1431 { | |
1432 // sanity check | |
1433 if (!VBAMovieActive()) | |
1434 { | |
1435 return; | |
1436 } | |
1437 | |
1438 *buf = NULL; | |
1439 *size = 0; | |
1440 | |
1441 // compute size needed for the buffer | |
1442 // room for header.uid, currentFrame, and header.length_frames | |
1443 uint32 size_needed = sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames); | |
1444 size_needed += (uint32)(Movie.bytesPerFrame * Movie.header.length_frames); | |
1445 *buf = new uint8[size_needed]; | |
1446 *size = size_needed; | |
1447 | |
1448 uint8 *ptr = *buf; | |
1449 if (!ptr) | |
1450 { | |
1451 return; | |
1452 } | |
1453 | |
1454 Push32(Movie.header.uid, ptr); | |
1455 Push32(Movie.currentFrame, ptr); | |
1456 Push32(Movie.header.length_frames - 1, ptr); // HACK: shorten the length by 1 for backward compatibility | |
1457 | |
1458 memcpy(ptr, Movie.inputBuffer, Movie.bytesPerFrame * Movie.header.length_frames); | |
1459 } | |
1460 | |
1461 int VBAMovieUnfreeze(const uint8 *buf, uint32 size) | |
1462 { | |
1463 // sanity check | |
1464 if (!VBAMovieActive()) | |
1465 { | |
1466 return MOVIE_NOT_FROM_A_MOVIE; | |
1467 } | |
1468 | |
1469 const uint8 *ptr = buf; | |
1470 if (size < sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames)) | |
1471 { | |
1472 return MOVIE_WRONG_FORMAT; | |
1473 } | |
1474 | |
1475 uint32 movie_id = Pop32(ptr); | |
1476 uint32 current_frame = Pop32(ptr); | |
1477 uint32 end_frame = Pop32(ptr) + 1; // HACK: restore the length for backward compatibility | |
1478 uint32 space_needed = Movie.bytesPerFrame * end_frame; | |
1479 | |
1480 if (movie_id != Movie.header.uid) | |
1481 return MOVIE_NOT_FROM_THIS_MOVIE; | |
1482 | |
1483 if (space_needed > size) | |
1484 return MOVIE_WRONG_FORMAT; | |
1485 | |
1486 if (Movie.readOnly) | |
1487 { | |
1488 // here, we are going to keep the input data from the movie file | |
1489 // and simply rewind to the currentFrame pointer | |
1490 // this will cause a desync if the savestate is not in sync // <-- NOT ANYMORE | |
1491 // with the on-disk recording data, but it's easily solved | |
1492 // by loading another savestate or playing the movie from the beginning | |
1493 | |
1494 // don't allow loading a state inconsistent with the current movie | |
1495 uint32 length_history = min(current_frame, Movie.header.length_frames); | |
1496 if (end_frame < length_history) | |
1497 return MOVIE_SNAPSHOT_INCONSISTENT; | |
1498 | |
1499 uint32 space_shared = Movie.bytesPerFrame * length_history; | |
1500 if (memcmp(Movie.inputBuffer, ptr, space_shared)) | |
1501 return MOVIE_SNAPSHOT_INCONSISTENT; | |
1502 | |
1503 Movie.currentFrame = current_frame; | |
1504 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames); | |
1505 } | |
1506 else | |
1507 { | |
1508 // here, we are going to take the input data from the savestate | |
1509 // and make it the input data for the current movie, then continue | |
1510 // writing new input data at the currentFrame pointer | |
1511 Movie.currentFrame = current_frame; | |
1512 Movie.header.length_frames = end_frame; | |
1513 if (!VBALuaRerecordCountSkip()) | |
1514 ++Movie.header.rerecord_count; | |
1515 | |
1516 Movie.RecordedThisSession = true; | |
1517 | |
1518 // do this before calling reserve_buffer_space() | |
1519 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames); | |
1520 reserve_buffer_space(space_needed); | |
1521 memcpy(Movie.inputBuffer, ptr, space_needed); | |
1522 | |
1523 // for consistency, no auto movie conversion here since we don't auto convert the corresponding savestate | |
1524 flush_movie_header(); | |
1525 flush_movie_frames(); | |
1526 } | |
1527 | |
1528 change_state(MOVIE_STATE_PLAY); // check for movie end | |
1529 | |
1530 // necessary! | |
1531 resetSignaled = false; | |
1532 resetSignaledLast = false; | |
1533 | |
1534 // necessary to check if there's a reset signal at the previous frame | |
1535 if (current_frame > 0) | |
1536 { | |
1537 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8); | |
1538 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) | |
1539 { | |
1540 if ((Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) && (*(Movie.inputBufferPtr+1- Movie.bytesPerFrame) & NEW_RESET)) | |
1541 { | |
1542 resetSignaledLast = true; | |
1543 break; | |
1544 } | |
1545 } | |
1546 } | |
1547 | |
1548 return MOVIE_SUCCESS; | |
1549 } | |
1550 | |
1551 bool VBAMovieEnded() | |
1552 { | |
1553 return (Movie.state == MOVIE_STATE_END); | |
1554 // return (Movie.state != MOVIE_STATE_NONE && Movie.currentFrame >= Movie.header.length_frames); | |
1555 } | |
1556 | |
1557 bool VBAMovieAllowsRerecording() | |
1558 { | |
1559 bool allows = (Movie.state != MOVIE_STATE_NONE) && (Movie.currentFrame <= Movie.header.length_frames); | |
1560 return /*!VBAMovieReadOnly() &&*/ allows; | |
1561 } | |
1562 | |
1563 bool VBAMovieSwitchToPlaying() | |
1564 { | |
1565 if (!VBAMovieActive()) | |
1566 return false; | |
1567 | |
1568 if (!Movie.readOnly) | |
1569 { | |
1570 VBAMovieToggleReadOnly(); | |
1571 } | |
1572 | |
1573 change_state(MOVIE_STATE_PLAY); | |
1574 if (Movie.state == MOVIE_STATE_PLAY) | |
1575 systemScreenMessage("Movie replay (continue)"); | |
1576 else | |
1577 systemScreenMessage("Movie end"); | |
1578 | |
1579 return true; | |
1580 } | |
1581 | |
1582 bool VBAMovieSwitchToRecording() | |
1583 { | |
1584 if (!VBAMovieAllowsRerecording()) | |
1585 return false; | |
1586 | |
1587 if (Movie.readOnly) | |
1588 { | |
1589 VBAMovieToggleReadOnly(); | |
1590 } | |
1591 | |
1592 if (!VBALuaRerecordCountSkip()) | |
1593 ++Movie.header.rerecord_count; | |
1594 | |
1595 change_state(MOVIE_STATE_RECORD); | |
1596 systemScreenMessage("Movie re-record"); | |
1597 | |
1598 //truncate_movie(Movie.currentFrame); | |
1599 | |
1600 return true; | |
1601 } | |
1602 | |
1603 uint32 VBAMovieGetState() | |
1604 { | |
1605 // ? | |
1606 if (!VBAMovieActive()) | |
1607 return MOVIE_STATE_NONE; | |
1608 | |
1609 return Movie.state; | |
1610 } | |
1611 | |
1612 void VBAMovieSignalReset() | |
1613 { | |
1614 if (VBAMovieActive()) | |
1615 resetSignaled = true; | |
1616 } | |
1617 | |
1618 void VBAMovieResetIfRequested() | |
1619 { | |
1620 if (resetSignaled) | |
1621 { | |
1622 theEmulator.emuReset(false); | |
1623 resetSignaled = false; | |
1624 resetSignaledLast = true; | |
1625 } | |
1626 else | |
1627 { | |
1628 resetSignaledLast = false; | |
1629 } | |
1630 } | |
1631 | |
1632 void VBAMovieSetMetadata(const char *info) | |
1633 { | |
1634 if (!memcmp(Movie.authorInfo, info, MOVIE_METADATA_SIZE)) | |
1635 return; | |
1636 | |
1637 memcpy(Movie.authorInfo, info, MOVIE_METADATA_SIZE); // strncpy would omit post-0 bytes | |
1638 Movie.authorInfo[MOVIE_METADATA_SIZE - 1] = '\0'; | |
1639 | |
1640 if (Movie.file) | |
1641 { | |
1642 // (over-)write the header | |
1643 fseek(Movie.file, 0, SEEK_SET); | |
1644 write_movie_header(Movie.file, Movie); | |
1645 | |
1646 // write the metadata / author info to file | |
1647 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, Movie.file); | |
1648 | |
1649 fflush(Movie.file); | |
1650 } | |
1651 } | |
1652 | |
1653 void VBAMovieRestart() | |
1654 { | |
1655 if (VBAMovieActive()) | |
1656 { | |
1657 systemSoundClearBuffer(); | |
1658 | |
1659 bool8 modified = Movie.RecordedThisSession; | |
1660 | |
1661 VBAMovieStop(true); | |
1662 | |
1663 char movieName [_MAX_PATH]; | |
1664 strncpy(movieName, Movie.filename, _MAX_PATH); | |
1665 movieName[_MAX_PATH - 1] = '\0'; | |
1666 VBAMovieOpen(movieName, Movie.readOnly); // can't just pass in Movie.filename, since VBAMovieOpen clears out Movie's | |
1667 // variables | |
1668 | |
1669 Movie.RecordedThisSession = modified; | |
1670 | |
1671 systemScreenMessage("Movie replay (restart)"); | |
1672 } | |
1673 } | |
1674 | |
1675 int VBAMovieGetPauseAt() | |
1676 { | |
1677 return Movie.pauseFrame; | |
1678 } | |
1679 | |
1680 void VBAMovieSetPauseAt(int at) | |
1681 { | |
1682 Movie.pauseFrame = at; | |
1683 } | |
1684 | |
1685 /////////////////////// | |
1686 // movie tools | |
1687 | |
1688 // FIXME: is it safe to convert/flush a movie while recording it (considering fseek() problem)? | |
1689 int VBAMovieConvertCurrent() | |
1690 { | |
1691 if (!VBAMovieActive()) | |
1692 { | |
1693 return MOVIE_NOTHING; | |
1694 } | |
1695 | |
1696 if (Movie.header.minorVersion > VBM_REVISION) | |
1697 { | |
1698 return MOVIE_WRONG_VERSION; | |
1699 } | |
1700 | |
1701 if (Movie.header.minorVersion == VBM_REVISION) | |
1702 { | |
1703 return MOVIE_NOTHING; | |
1704 } | |
1705 | |
1706 Movie.header.minorVersion = VBM_REVISION; | |
1707 | |
1708 if (Movie.header.length_frames == 0) // this could happen | |
1709 { | |
1710 truncate_movie(0); | |
1711 return MOVIE_SUCCESS; | |
1712 } | |
1713 | |
1714 // fix movies recorded from snapshots | |
1715 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT) | |
1716 { | |
1717 uint8 *firstFramePtr = Movie.inputBuffer; | |
1718 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) | |
1719 { | |
1720 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) | |
1721 { | |
1722 Push16(initialInputs[i], firstFramePtr); | |
1723 // note: this is correct since Push16 advances the dest pointer by sizeof u16 | |
1724 } | |
1725 } | |
1726 } | |
1727 | |
1728 // convert old resets to new ones | |
1729 const u8 OLD_RESET = u8(BUTTON_MASK_OLD_RESET >> 8); | |
1730 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8); | |
1731 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) | |
1732 { | |
1733 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) | |
1734 { | |
1735 uint8 *startPtr = Movie.inputBuffer + sizeof(u16) * i + 1; | |
1736 uint8 *endPtr = Movie.inputBuffer + Movie.bytesPerFrame * (Movie.header.length_frames - 1); | |
1737 for (; startPtr < endPtr; startPtr += Movie.bytesPerFrame) | |
1738 { | |
1739 if (startPtr[Movie.bytesPerFrame] & OLD_RESET) | |
1740 { | |
1741 startPtr[0] |= NEW_RESET; | |
1742 } | |
1743 } | |
1744 } | |
1745 } | |
1746 | |
1747 flush_movie_header(); | |
1748 flush_movie_frames(); | |
1749 return MOVIE_SUCCESS; | |
1750 } | |
1751 | |
1752 bool VBAMovieTuncateAtCurrentFrame() | |
1753 { | |
1754 if (!VBAMovieActive()) | |
1755 return false; | |
1756 | |
1757 truncate_movie(Movie.currentFrame); | |
1758 change_state(MOVIE_STATE_END); | |
1759 systemScreenMessage("Movie truncated"); | |
1760 | |
1761 return true; | |
1762 } | |
1763 | |
1764 bool VBAMovieFixHeader() | |
1765 { | |
1766 if (!VBAMovieActive()) | |
1767 return false; | |
1768 | |
1769 flush_movie_header(); | |
1770 systemScreenMessage("Movie header fixed"); | |
1771 return true; | |
1772 } |