1 // Functions for recording & replaying input
2 #include "burner.h"
3 #include "dynhuff.h"
4 #include <commdlg.h>
5
6 #include <io.h>
7
8 #ifndef W_OK
9 #define W_OK 4
10 #endif
11
12 #define MAX_METADATA 1024
13 wchar_t wszMetadata[MAX_METADATA];
14 wchar_t wszStartupGame[MAX_PATH];
15 wchar_t wszAuthorInfo[MAX_METADATA-64];
16
17 INT32 nReplayStatus = 0; // 1 record, 2 replay, 0 nothing
18 bool bReplayReadOnly = false;
19 bool bReplayShowMovement = false;
20 bool bReplayDontClose = false;
21 INT32 nReplayUndoCount = 0;
22 bool bReplayFrameCounterDisplay = 1;
23 TCHAR szFilter[1024];
24 INT32 movieFlags = 0;
25 bool bStartFromReset = true;
26 TCHAR szCurrentMovieFilename[MAX_PATH] = _T("");
27 UINT32 nTotalFrames = 0;
28 UINT32 nReplayCurrentFrame;
29
30 // If a driver has external data that needs to be recorded every frame.
31 INT32 nReplayExternalDataCount = 0;
32 UINT8 *ReplayExternalData = NULL;
33
34 #define MOVIE_FLAG_FROM_POWERON (1<<1)
35
36 const UINT8 nMovieVersion = 0x01;
37 UINT32 nStartFrame = 0;
38 static UINT32 nEndFrame;
39
40 static FILE* fp = NULL;
41 static INT32 nSizeOffset;
42
43 static short nPrevInputs[0x0100];
44
45 static INT32 nPrintInputsActive[2] = { 0, 0 };
46
47 static INT32 ReplayDialog();
48 static INT32 RecordDialog();
49
RecordInput()50 INT32 RecordInput()
51 {
52 struct BurnInputInfo bii;
53 memset(&bii, 0, sizeof(bii));
54
55 for (UINT32 i = 0; i < nGameInpCount; i++) {
56 BurnDrvGetInputInfo(&bii, i);
57 if (bii.pVal) {
58 if (bii.nType & BIT_GROUP_ANALOG) {
59 if (*bii.pShortVal != nPrevInputs[i]) {
60 EncodeBuffer(i);
61 EncodeBuffer(*bii.pShortVal >> 8);
62 EncodeBuffer(*bii.pShortVal & 0xFF);
63 nPrevInputs[i] = *bii.pShortVal;
64 }
65 } else {
66 if (*bii.pVal != nPrevInputs[i]) {
67 EncodeBuffer(i);
68 EncodeBuffer(*bii.pVal);
69 nPrevInputs[i] = *bii.pVal;
70 }
71 }
72 }
73 }
74 EncodeBuffer(0xFF);
75
76 if (nReplayExternalDataCount && ReplayExternalData) {
77 for (INT32 i = 0; i < nReplayExternalDataCount; i++) {
78 EncodeBuffer(ReplayExternalData[i]);
79 }
80 }
81
82 if (bReplayFrameCounterDisplay) {
83 wchar_t framestring[15];
84 swprintf(framestring, L"%d", GetCurrentFrame() - nStartFrame);
85 VidSNewTinyMsg(framestring);
86 }
87
88 return 0;
89 }
90
CheckRedraw()91 static void CheckRedraw()
92 {
93 if (bRunPause) {
94 INT32 oldOk = bDrvOkay; // Redraw w/o running a frame.
95 bDrvOkay = 0;
96 VidRedraw(); // Redraw screen so status doesn't get clobbered
97 VidPaint(0); // ""
98 bDrvOkay = oldOk;
99 }
100 }
101
PrintInputsReset()102 static void PrintInputsReset()
103 {
104 nPrintInputsActive[0] = nPrintInputsActive[1] = -(60 * 5);
105 }
106
PrintInputsSetActive(UINT8 plyrnum)107 static inline void PrintInputsSetActive(UINT8 plyrnum)
108 {
109 nPrintInputsActive[plyrnum] = GetCurrentFrame();
110 }
111
PrintInputs()112 static void PrintInputs()
113 {
114 struct BurnInputInfo bii;
115
116 UINT8 UDLR[2][4]; // P1 & P2 joystick movements
117 UINT8 BUTTONS[2][8]; // P1 and P2 buttons
118 UINT8 OFFBUTTONS[2][8]; // P1 and P2 buttons
119 wchar_t lines[3][64];
120
121 memset(&lines, 0, sizeof(lines));
122 memset(UDLR, 0, sizeof(UDLR));
123 memset(BUTTONS, ' ', sizeof(BUTTONS));
124 memset(OFFBUTTONS, ' ', sizeof(OFFBUTTONS));
125
126 for (UINT32 i = 0; i < nGameInpCount; i++) {
127 memset(&bii, 0, sizeof(bii));
128 BurnDrvGetInputInfo(&bii, i);
129 if (bii.pVal && bii.szInfo && bii.szInfo[0]) {
130 // Translate X/Y axis to UDLR, TODO: (maybe) support mouse axis/buttons
131 if ((bii.nType & BIT_GROUP_ANALOG) && bii.pShortVal && *bii.pShortVal) {
132 if (stricmp(bii.szInfo+2, " x-axis")==0 && bii.nType == BIT_GROUP_ANALOG) {
133 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
134 if ((INT16)*bii.pShortVal > 0x80)
135 UDLR[(bii.szInfo[1] - '1')][3] = 1; // Right
136 if ((INT16)*bii.pShortVal < -0x80)
137 UDLR[(bii.szInfo[1] - '1')][2] = 1; // Left
138 PrintInputsSetActive(bii.szInfo[1] - '1');
139 }
140 }
141 if (stricmp(bii.szInfo+2, " y-axis")==0 && bii.nType == BIT_GROUP_ANALOG) {
142 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
143 if ((INT16)*bii.pShortVal > 0x80)
144 UDLR[(bii.szInfo[1] - '1')][1] = 1; // Down
145 if ((INT16)*bii.pShortVal < -0x80)
146 UDLR[(bii.szInfo[1] - '1')][0] = 1; // Up
147 PrintInputsSetActive(bii.szInfo[1] - '1');
148 }
149 }
150 }
151 if (*bii.pVal) { // Button pressed
152 if (stricmp(bii.szInfo+2, " Up")==0) {
153 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
154 UDLR[(bii.szInfo[1] - '1')][0] = 1;
155 PrintInputsSetActive(bii.szInfo[1] - '1');
156 }
157 }
158 if (stricmp(bii.szInfo+2, " Down")==0) {
159 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
160 UDLR[(bii.szInfo[1] - '1')][1] = 1;
161 PrintInputsSetActive(bii.szInfo[1] - '1');
162 }
163 }
164 if (stricmp(bii.szInfo+2, " Left")==0) {
165 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
166 UDLR[(bii.szInfo[1] - '1')][2] = 1;
167 PrintInputsSetActive(bii.szInfo[1] - '1');
168 }
169 }
170 if (stricmp(bii.szInfo+2, " Right")==0) {
171 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
172 UDLR[(bii.szInfo[1] - '1')][3] = 1;
173 PrintInputsSetActive(bii.szInfo[1] - '1');
174 }
175 }
176 if (strnicmp(bii.szInfo+2, " fire ", 6)==0) {
177 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
178 if (bii.szInfo[8] - '1' < 6) { // avoid overflow
179 BUTTONS[(bii.szInfo[1] - '1')][bii.szInfo[8] - '1'] = bii.szInfo[8];
180 PrintInputsSetActive(bii.szInfo[1] - '1');
181 }
182 }
183 }
184 } else { // get "off" buttons
185 if (strnicmp(bii.szInfo+2, " fire ", 6)==0) {
186 if (bii.szInfo[1] == '1' || bii.szInfo[1] == '2') {
187 if (bii.szInfo[8] - '1' < 6) // avoid overflow
188 OFFBUTTONS[(bii.szInfo[1] - '1')][bii.szInfo[8] - '1'] = bii.szInfo[8];
189 }
190 }
191 }
192 }
193 }
194
195 VidSNewJoystickMsg(NULL); // Clear surface.
196 // Draw shadows
197 if (GetCurrentFrame() < nPrintInputsActive[0] + (60*5)) {
198 swprintf(lines[0], L" ^ %c%c ", OFFBUTTONS[0][0], OFFBUTTONS[0][1]);
199 swprintf(lines[1], L" < > %c%c ", OFFBUTTONS[0][2], OFFBUTTONS[0][3]);
200 swprintf(lines[2], L" v %c%c ", OFFBUTTONS[0][4], OFFBUTTONS[0][5]);
201 VidSNewJoystickMsg(lines[0], 0x404040, 20, 0);
202 VidSNewJoystickMsg(lines[1], 0x404040, 20, 1);
203 VidSNewJoystickMsg(lines[2], 0x404040, 20, 2);
204 }
205
206 if (GetCurrentFrame() < nPrintInputsActive[1] + (60*5)) { // time out np2active after 200 frames or so...
207 swprintf(lines[0], L" ^ %c%c ", OFFBUTTONS[1][0], OFFBUTTONS[1][1]);
208 swprintf(lines[1], L" < > %c%c ", OFFBUTTONS[1][2], OFFBUTTONS[1][3]);
209 swprintf(lines[2], L" v %c%c ", OFFBUTTONS[1][4], OFFBUTTONS[1][5]);
210 VidSNewJoystickMsg(lines[0], 0x404040, 20, 0);
211 VidSNewJoystickMsg(lines[1], 0x404040, 20, 1);
212 VidSNewJoystickMsg(lines[2], 0x404040, 20, 2);
213 }
214
215 // Draw active buttons
216 INT32 nLen = 0;
217 for (INT32 i = 0; i < 2; i++) {
218 if (i == 1) nLen = _tcslen(lines[0]); // Create the textual mini-joystick icons
219 swprintf(lines[0] + nLen, L" %c %c%c ", UDLR[i][0] ? '^' : ' ', BUTTONS[i][0], BUTTONS[i][1]);
220 swprintf(lines[1] + nLen, L" %c %c %c%c ", UDLR[i][2] ? '<' : ' ', UDLR[i][3] ? '>' : ' ', BUTTONS[i][2], BUTTONS[i][3]);
221 swprintf(lines[2] + nLen, L" %c %c%c ", UDLR[i][1] ? 'v' : ' ', BUTTONS[i][4], BUTTONS[i][5]);
222 }
223 VidSNewJoystickMsg(lines[0], 0xffffff, 20, 0); // Draw them
224 VidSNewJoystickMsg(lines[1], 0xffffff, 20, 1);
225 VidSNewJoystickMsg(lines[2], 0xffffff, 20, 2);
226 }
227
ReplayInput()228 INT32 ReplayInput()
229 {
230 UINT8 n;
231 struct BurnInputInfo bii;
232 memset(&bii, 0, sizeof(bii));
233
234 // Just to be safe, restore the inputs to the known correct settings
235 for (UINT32 i = 0; i < nGameInpCount; i++) {
236 BurnDrvGetInputInfo(&bii, i);
237 if (bii.pVal) {
238 if (bii.nType & BIT_GROUP_ANALOG) {
239 *bii.pShortVal = nPrevInputs[i];
240 } else {
241 *bii.pVal = nPrevInputs[i];
242 }
243 }
244 }
245
246 // Now read all inputs that need to change from the .fr file
247 while ((n = DecodeBuffer()) != 0xFF) {
248 BurnDrvGetInputInfo(&bii, n);
249 if (bii.pVal) {
250 if (bii.nType & BIT_GROUP_ANALOG) {
251 *bii.pShortVal = nPrevInputs[n] = (DecodeBuffer() << 8) | DecodeBuffer();
252 } else {
253 *bii.pVal = nPrevInputs[n] = DecodeBuffer();
254 }
255 } else {
256 DecodeBuffer();
257 }
258 }
259
260 if (nReplayExternalDataCount && ReplayExternalData) {
261 for (INT32 i = 0; i < nReplayExternalDataCount; i++) {
262 ReplayExternalData[i] = DecodeBuffer();
263 }
264 }
265
266 if (bReplayFrameCounterDisplay) {
267 wchar_t framestring[32];
268 swprintf(framestring, L"%d / %d", GetCurrentFrame() - nStartFrame,nTotalFrames);
269 VidSNewTinyMsg(framestring);
270 }
271
272 if (bReplayShowMovement) {
273 PrintInputs();
274 }
275
276 #if 0
277 if ( (GetCurrentFrame()-nStartFrame) == (nTotalFrames-1) ) {
278 //bRunPause = 1; // not needed, but reenable if problems -dink.
279 }
280 #endif
281
282 if (end_of_buffer) {
283 StopReplay();
284 return 1;
285 } else {
286 return 0;
287 }
288 }
289
MakeOfn(TCHAR * pszFilter)290 static void MakeOfn(TCHAR* pszFilter)
291 {
292 _stprintf(pszFilter, FBALoadStringEx(hAppInst, IDS_DISK_FILE_REPLAY, true), _T(APP_TITLE));
293 memcpy(pszFilter + _tcslen(pszFilter), _T(" (*.fr)\0*.fr\0\0"), 14 * sizeof(TCHAR));
294
295 memset(&ofn, 0, sizeof(ofn));
296 ofn.lStructSize = sizeof(ofn);
297 ofn.hwndOwner = hScrnWnd;
298 ofn.lpstrFilter = pszFilter;
299 ofn.lpstrFile = szChoice;
300 ofn.nMaxFile = sizeof(szChoice) / sizeof(TCHAR);
301 ofn.lpstrInitialDir = _T(".\\recordings");
302 ofn.Flags = OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
303 ofn.lpstrDefExt = _T("fr");
304
305 return;
306 }
307
StartRecord()308 INT32 StartRecord()
309 {
310 INT32 nRet;
311 INT32 bOldPause;
312
313 fp = NULL;
314 movieFlags = 0;
315
316 bOldPause = bRunPause;
317 bRunPause = 1;
318 nRet = RecordDialog();
319 bRunPause = bOldPause;
320
321 if (nRet == 0) {
322 return 1;
323 }
324
325 bReplayReadOnly = false;
326 bReplayShowMovement = false;
327
328 if (bStartFromReset) {
329 if(!StartFromReset(NULL)) return 1;
330 movieFlags |= MOVIE_FLAG_FROM_POWERON;
331 }
332 {
333 const char szFileHeader[] = "FB1 "; // File identifier
334 fp = _tfopen(szChoice, _T("w+b"));
335 _tcscpy(szCurrentMovieFilename, szChoice);
336
337 nRet = 0;
338 if (fp == NULL) {
339 nRet = 1;
340 } else {
341 fwrite(&szFileHeader, 1, 4, fp);
342 fwrite(&movieFlags, 1, 4, fp);
343 if (bStartFromReset)
344 nRet = 1;
345 else
346 nRet = BurnStateSaveEmbed(fp, -1, 1);
347 if (nRet >= 0) {
348 const char szChunkHeader[] = "FR1 "; // Chunk identifier
349 INT32 nZero = 0;
350
351 fwrite(&szChunkHeader, 1, 4, fp); // Write chunk identifier
352
353 nSizeOffset = ftell(fp);
354
355 fwrite(&nZero, 1, 4, fp); // reserve space for chunk size
356
357 fwrite(&nZero, 1, 4, fp); // reserve space for number of frames
358
359 fwrite(&nZero, 1, 4, fp); // undo count
360 fwrite(&nZero, 1, 4, fp); // reserved
361 fwrite(&nZero, 1, 4, fp); //
362
363 nRet = EmbedCompressedFile(fp, -1);
364 }
365 }
366 }
367
368 if (nRet) {
369 if (fp) {
370 fclose(fp);
371 fp = NULL;
372 }
373
374 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_CREATE));
375 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_REPLAY));
376 FBAPopupDisplay(PUF_TYPE_ERROR);
377 return 1;
378 } else {
379 struct BurnInputInfo bii;
380 memset(&bii, 0, sizeof(bii));
381
382 nReplayStatus = 1; // Set record status
383 CheckRedraw();
384 MenuEnableItems();
385
386 nStartFrame = GetCurrentFrame();
387 nReplayUndoCount = 0;
388
389 // Create a baseline so we can just record the deltas afterwards
390 for (UINT32 i = 0; i < nGameInpCount; i++) {
391 BurnDrvGetInputInfo(&bii, i);
392 if (bii.pVal) {
393 if (bii.nType & BIT_GROUP_ANALOG) {
394 EncodeBuffer(*bii.pShortVal >> 8);
395 EncodeBuffer(*bii.pShortVal & 0xFF);
396 nPrevInputs[i] = *bii.pShortVal;
397 } else {
398 EncodeBuffer(*bii.pVal);
399 nPrevInputs[i] = *bii.pVal;
400 }
401 } else {
402 EncodeBuffer(0);
403 }
404 }
405
406 #ifdef FBA_DEBUG
407 dprintf(_T("*** Recording of file %s started.\n"), szChoice);
408 #endif
409
410 return 0;
411 }
412 }
413
StartReplay(const TCHAR * szFileName)414 INT32 StartReplay(const TCHAR* szFileName) // const char* szFileName = NULL
415 {
416 INT32 nRet;
417 INT32 bOldPause;
418
419 PrintInputsReset();
420
421 fp = NULL;
422
423 if (szFileName) {
424 _tcscpy(szChoice, szFileName);
425 if (!bReplayDontClose) {
426 // if bStartFromReset, get file "wszStartupGame" from metadata!!
427 DisplayReplayProperties(0, false);
428 }
429 } else {
430 bOldPause = bRunPause;
431 bRunPause = 1;
432 nRet = ReplayDialog();
433 bRunPause = bOldPause;
434
435 if (nRet == 0) {
436 return 1;
437 }
438
439 }
440 _tcscpy(szCurrentMovieFilename, szChoice);
441
442 // init metadata
443 wszMetadata[0] = L'\0';
444 {
445 const char szFileHeader[] = "FB1 "; // File identifier
446 char ReadHeader[] = " ";
447 fp = _tfopen(szChoice, _T("r+b"));
448 if (!fp) return 1;
449 memset(ReadHeader, 0, 4);
450 fread(ReadHeader, 1, 4, fp); // Read identifier
451 if (memcmp(ReadHeader, szFileHeader, 4)) { // Not the right file type
452 fclose(fp);
453 fp = NULL;
454 nRet = 2;
455 } else {
456 memset(ReadHeader, 0, 4);
457 fread(&movieFlags, 1, 4, fp); // Read movie flags
458 if (movieFlags&MOVIE_FLAG_FROM_POWERON) { // Starts from reset
459 bStartFromReset = 1;
460 if (!bReplayDontClose)
461 if(!StartFromReset(wszStartupGame)) return 0;
462 nRet = 0;
463 }
464 else {// Load the savestate associated with the recording
465 bStartFromReset = 0;
466 nRet = BurnStateLoadEmbed(fp, -1, 1, &DrvInitCallback);
467 }
468 if (nRet == 0) {
469 const char szChunkHeader[] = "FR1 "; // Chunk identifier
470 fread(ReadHeader, 1, 4, fp); // Read identifier
471 if (memcmp(ReadHeader, szChunkHeader, 4)) { // Not the right file type
472 fclose(fp);
473 fp = NULL;
474 nRet = 2;
475 } else {
476 INT32 nChunkSize = 0;
477 // Open the recording itself
478 nSizeOffset = ftell(fp); // Save chunk size offset in case the file is re-recorded
479 fread(&nChunkSize, 1, 0x04, fp); // Read chunk size
480 INT32 nChunkPosition = ftell(fp); // For seeking to the metadata
481 fread(&nEndFrame, 1, 4, fp); // Read framecount
482 nTotalFrames = nEndFrame;
483
484 nStartFrame = GetCurrentFrame();
485 bReplayDontClose = 0; // we don't need it anymore from this point
486 nEndFrame += nStartFrame;
487 fread(&nReplayUndoCount, 1, 4, fp);
488 fseek(fp, 0x08, SEEK_CUR); // Skip rest of header
489 INT32 nEmbedPosition = ftell(fp);
490
491 // Read metadata
492 const char szMetadataHeader[] = "FRM1";
493 fseek(fp, nChunkPosition + nChunkSize, SEEK_SET);
494 memset(ReadHeader, 0, 4);
495 fread(ReadHeader, 1, 4, fp);
496 if(memcmp(ReadHeader, szMetadataHeader, 4) == 0) {
497 INT32 nMetaSize;
498 fread(&nMetaSize, 1, 4, fp);
499 INT32 nMetaLen = nMetaSize >> 1;
500 if(nMetaLen >= MAX_METADATA) {
501 nMetaLen = MAX_METADATA-1;
502 }
503 INT32 i;
504 for(i=0; i<nMetaLen; ++i) {
505 wchar_t c = 0;
506 c |= fgetc(fp) & 0xff;
507 c |= (fgetc(fp) & 0xff) << 8;
508 wszMetadata[i] = c;
509 }
510 wszMetadata[i] = L'\0';
511 }
512
513 // Seek back to the beginning of compressed data
514 fseek(fp, nEmbedPosition, SEEK_SET);
515 nRet = EmbedCompressedFile(fp, -1);
516
517 }
518 }
519 }
520
521 // Describe any possible errors:
522 if (nRet == 3) {
523 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_THIS_REPLAY));
524 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_UNAVAIL));
525 } else {
526 if (nRet == 4) {
527 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_THIS_REPLAY));
528 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_TOOOLD), _T(APP_TITLE));
529 } else {
530 if (nRet == 5) {
531 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_THIS_REPLAY));
532 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_TOONEW), _T(APP_TITLE));
533 } else {
534 if (nRet) {
535 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_LOAD));
536 FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_REPLAY));
537 }
538 }
539 }
540 }
541
542 if (nRet) {
543 if (fp) {
544 fclose(fp);
545 fp = NULL;
546 }
547
548 FBAPopupDisplay(PUF_TYPE_ERROR);
549
550 return 1;
551 }
552 }
553
554 nReplayStatus = 2; // Set replay status
555 CheckRedraw();
556
557 MenuEnableItems();
558
559 {
560 struct BurnInputInfo bii;
561 memset(&bii, 0, sizeof(bii));
562
563 LoadCompressedFile();
564
565 // Get the baseline
566 for (UINT32 i = 0; i < nGameInpCount; i++) {
567 BurnDrvGetInputInfo(&bii, i);
568 if (bii.pVal) {
569 if (bii.nType & BIT_GROUP_ANALOG) {
570 *bii.pShortVal = nPrevInputs[i] = (DecodeBuffer() << 8) | DecodeBuffer();
571
572 } else {
573 *bii.pVal = nPrevInputs[i] = DecodeBuffer();
574 }
575 } else {
576 DecodeBuffer();
577 }
578 }
579 }
580
581 #ifdef FBA_DEBUG
582 dprintf(_T("*** Replay of file %s started.\n"), szChoice);
583 #endif
584
585 return 0;
586 }
587
CloseRecord()588 static void CloseRecord()
589 {
590 INT32 nFrames = GetCurrentFrame() - nStartFrame;
591
592 WriteCompressedFile();
593
594 fseek(fp, 0, SEEK_END);
595 INT32 nMetadataOffset = ftell(fp);
596 INT32 nChunkSize = ftell(fp) - 4 - nSizeOffset; // Fill in chunk size and no of recorded frames
597 fseek(fp, nSizeOffset, SEEK_SET);
598 fwrite(&nChunkSize, 1, 4, fp);
599 fwrite(&nFrames, 1, 4, fp);
600 fwrite(&nReplayUndoCount, 1, 4, fp);
601
602 // NOTE: chunk should be aligned here, since the compressed
603 // file code writes 4 bytes at a time
604
605 // write metadata
606 INT32 nMetaLen = wcslen(wszMetadata);
607 if(nMetaLen > 0) {
608 fseek(fp, nMetadataOffset, SEEK_SET);
609 const char szChunkHeader[] = "FRM1";
610 fwrite(szChunkHeader, 1, 4, fp);
611 INT32 nMetaSize = nMetaLen * 2;
612 fwrite(&nMetaSize, 1, 4, fp);
613 UINT8* metabuf = (UINT8*)malloc(nMetaSize);
614 INT32 i;
615 for(i=0; i<nMetaLen; ++i) {
616 metabuf[i*2 + 0] = wszMetadata[i] & 0xff;
617 metabuf[i*2 + 1] = (wszMetadata[i] >> 8) & 0xff;
618 }
619 fwrite(metabuf, 1, nMetaSize, fp);
620 free(metabuf);
621 }
622
623 fclose(fp);
624 fp = NULL;
625 if (bReplayDontClose) {
626 if(!StartReplay(szCurrentMovieFilename)) return;
627 }
628 }
629
CloseReplay()630 static void CloseReplay()
631 {
632 CloseCompressedFile();
633
634 if(fp) {
635 fclose(fp);
636 fp = NULL;
637 }
638 }
639
StopReplay()640 void StopReplay()
641 {
642 if (nReplayStatus) {
643 if (nReplayStatus == 1) {
644
645 #ifdef FBA_DEBUG
646 dprintf(_T(" ** Recording stopped, recorded %d frames.\n"), GetCurrentFrame() - nStartFrame);
647 #endif
648 CloseRecord();
649 #ifdef FBA_DEBUG
650 PrintResult();
651 #endif
652 } else {
653 #ifdef FBA_DEBUG
654 dprintf(_T(" ** Replay stopped, replayed %d frames.\n"), GetCurrentFrame() - nStartFrame);
655 #endif
656
657 CloseReplay();
658 }
659 nReplayStatus = 0;
660 nStartFrame = 0;
661 CheckRedraw();
662 MenuEnableItems();
663 }
664 }
665
666
667 //#
668 //# Input Status Freezing
669 //#
670 //##############################################################################
671
Write32(UINT8 * & ptr,const unsigned long v)672 static inline void Write32(UINT8*& ptr, const unsigned long v)
673 {
674 *ptr++ = (UINT8)(v&0xff);
675 *ptr++ = (UINT8)((v>>8)&0xff);
676 *ptr++ = (UINT8)((v>>16)&0xff);
677 *ptr++ = (UINT8)((v>>24)&0xff);
678 }
679
Read32(const UINT8 * & ptr)680 static inline UINT32 Read32(const UINT8*& ptr)
681 {
682 UINT32 v;
683 v = (UINT32)(*ptr++);
684 v |= (UINT32)((*ptr++)<<8);
685 v |= (UINT32)((*ptr++)<<16);
686 v |= (UINT32)((*ptr++)<<24);
687 return v;
688 }
689
Write16(UINT8 * & ptr,const UINT16 v)690 static inline void Write16(UINT8*& ptr, const UINT16 v)
691 {
692 *ptr++ = (UINT8)(v&0xff);
693 *ptr++ = (UINT8)((v>>8)&0xff);
694 }
695
Read16(const UINT8 * & ptr)696 static inline UINT16 Read16(const UINT8*& ptr)
697 {
698 UINT16 v;
699 v = (UINT16)(*ptr++);
700 v |= (UINT16)((*ptr++)<<8);
701 return v;
702 }
703
FreezeInput(UINT8 ** buf,INT32 * size)704 INT32 FreezeInput(UINT8** buf, INT32* size)
705 {
706 *size = 4 + 2*nGameInpCount;
707 *buf = (UINT8*)malloc(*size);
708 if(!*buf)
709 {
710 return -1;
711 }
712
713 UINT8* ptr=*buf;
714 Write32(ptr, nGameInpCount);
715
716 for (UINT32 i = 0; i < nGameInpCount; i++)
717 {
718 Write16(ptr, nPrevInputs[i]);
719 }
720
721 return 0;
722 }
723
UnfreezeInput(const UINT8 * buf,INT32 size)724 INT32 UnfreezeInput(const UINT8* buf, INT32 size)
725 {
726 UINT32 n=Read32(buf);
727 if(n>0x100 || (unsigned)size < (4 + 2*n))
728 {
729 return -1;
730 }
731
732 for (UINT32 i = 0; i < n; i++)
733 {
734 nPrevInputs[i]=Read16(buf);
735 }
736
737 return 0;
738 }
739
740 //------------------------------------------------------
741
GetRecordingPath(wchar_t * szPath)742 static void GetRecordingPath(wchar_t* szPath)
743 {
744 wchar_t szDrive[MAX_PATH];
745 wchar_t szDirectory[MAX_PATH];
746 wchar_t szFilename[MAX_PATH];
747 wchar_t szExt[MAX_PATH];
748 szDrive[0] = '\0';
749 szDirectory[0] = '\0';
750 szFilename[0] = '\0';
751 szExt[0] = '\0';
752 _wsplitpath(szPath, szDrive, szDirectory, szFilename, szExt);
753 if (szDrive[0] == '\0' && szDirectory[0] == '\0') {
754 wchar_t szTmpPath[MAX_PATH];
755 wcscpy(szTmpPath, L"recordings\\");
756 wcsncpy(szTmpPath + wcslen(szTmpPath), szPath, MAX_PATH - wcslen(szTmpPath));
757 szTmpPath[MAX_PATH-1] = '\0';
758 wcscpy(szPath, szTmpPath);
759 }
760 }
761
DisplayPropertiesError(HWND hDlg,INT32 nErrType)762 static void DisplayPropertiesError(HWND hDlg, INT32 nErrType)
763 {
764 if (hDlg != 0) {
765 switch (nErrType) {
766 case 0:
767 SetDlgItemTextW(hDlg, IDC_METADATA, _T("ERROR: Not a FBAlpha input recording file.\0"));
768 break;
769 case 1:
770 SetDlgItemTextW(hDlg, IDC_METADATA, _T("ERROR: Incompatible file-type. Try playback with an earlier version of FBAlpha.\0"));
771 break;
772 case 2:
773 SetDlgItemTextW(hDlg, IDC_METADATA, _T("ERROR: Recording is corrupt :(\0"));
774 break;
775 }
776 }
777 }
778
DisplayReplayProperties(HWND hDlg,bool bClear)779 void DisplayReplayProperties(HWND hDlg, bool bClear)
780 {
781 if (hDlg != 0) {
782 // save status of read only checkbox
783 static bool bReadOnlyStatus = true;
784 if (IsWindowEnabled(GetDlgItem(hDlg, IDC_READONLY))) {
785 bReadOnlyStatus = (BST_CHECKED == SendDlgItemMessage(hDlg, IDC_READONLY, BM_GETCHECK, 0, 0));
786 }
787
788 //bReplayShowMovement = false;
789 if (IsWindowEnabled(GetDlgItem(hDlg, IDC_SHOWMOVEMENT))) {
790 if (BST_CHECKED == SendDlgItemMessage(hDlg, IDC_SHOWMOVEMENT, BM_GETCHECK, 0, 0)) {
791 bReplayShowMovement = true;
792 }
793 }
794
795 bReplayReadOnly = bReadOnlyStatus;
796
797 // set default values
798 SetDlgItemTextA(hDlg, IDC_LENGTH, "");
799 SetDlgItemTextA(hDlg, IDC_FRAMES, "");
800 SetDlgItemTextA(hDlg, IDC_UNDO, "");
801 SetDlgItemTextA(hDlg, IDC_METADATA, "");
802 SetDlgItemTextA(hDlg, IDC_REPLAYRESET, "");
803 EnableWindow(GetDlgItem(hDlg, IDC_READONLY), FALSE);
804 SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_UNCHECKED, 0);
805
806 EnableWindow(GetDlgItem(hDlg, IDC_SHOWMOVEMENT), FALSE);
807 SendDlgItemMessage(hDlg, IDC_SHOWMOVEMENT, BM_SETCHECK, BST_UNCHECKED, 0);
808
809 EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
810
811 if(bClear) {
812 return;
813 }
814
815 long lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
816 long lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
817 if (lIndex == CB_ERR) {
818 return;
819 }
820
821 if (lIndex == lCount - 1) { // Last item is "Browse..."
822 EnableWindow(GetDlgItem(hDlg, IDOK), TRUE); // Browse is selectable
823 return;
824 }
825
826 long lStringLength = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETLBTEXTLEN, (WPARAM)lIndex, 0);
827 if(lStringLength + 1 > MAX_PATH) {
828 return;
829 }
830
831 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETLBTEXT, (WPARAM)lIndex, (LPARAM)szChoice);
832
833 // check relative path
834 GetRecordingPath(szChoice);
835 }
836
837 const char szFileHeader[] = "FB1 "; // File identifier
838 const char szSavestateHeader[] = "FS1 "; // Chunk identifier
839 const char szRecordingHeader[] = "FR1 "; // Chunk identifier
840 const char szMetadataHeader[] = "FRM1"; // Chunk identifier
841 char ReadHeader[4];
842 INT32 nChunkSize = 0;
843 INT32 nChunkDataPosition = 0;
844 INT32 nFileVer = 0;
845 INT32 nFileMin = 0;
846 INT32 t1 = 0, t2 = 0;
847 INT32 nFrames = 0;
848 INT32 nUndoCount = 0;
849 wchar_t* local_metadata = NULL;
850
851 memset(&wszStartupGame, 0, sizeof(wszStartupGame));
852 memset(&wszAuthorInfo, 0, sizeof(wszAuthorInfo));
853
854 FILE* fd = _wfopen(szChoice, L"r+b");
855 if (!fd) {
856 return;
857 }
858
859 if (hDlg != 0) {
860 if (_waccess(szChoice, W_OK)) {
861 SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_CHECKED, 0);
862 } else {
863 EnableWindow(GetDlgItem(hDlg, IDC_READONLY), TRUE);
864 SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, (bReplayReadOnly) ? BST_CHECKED : BST_UNCHECKED, 0); //read-only by default
865 }
866
867 EnableWindow(GetDlgItem(hDlg, IDC_SHOWMOVEMENT), TRUE);
868 SendDlgItemMessage(hDlg, IDC_SHOWMOVEMENT, BM_SETCHECK, (bReplayShowMovement) ? BST_CHECKED : BST_UNCHECKED, 0);
869 }
870
871 memset(ReadHeader, 0, 4);
872 fread(ReadHeader, 1, 4, fd); // Read identifier
873 if (memcmp(ReadHeader, szFileHeader, 4)) { // Not the right file type
874 fclose(fd);
875 DisplayPropertiesError(hDlg, 0 /* not our file */);
876 return;
877 }
878
879 fread(&movieFlags, 1, 4, fd); // Read identifier
880
881 bStartFromReset = (movieFlags&MOVIE_FLAG_FROM_POWERON) ? 1 : 0; // Starts from reset
882
883 if (!bStartFromReset) {
884 memset(ReadHeader, 0, 4);
885 fread(ReadHeader, 1, 4, fd); // Read identifier
886 if (memcmp(ReadHeader, szSavestateHeader, 4)) { // Not the chunk type
887 fclose(fd);
888 DisplayPropertiesError(hDlg, 1 /* most likely recorded w/ an earlier version */);
889 return;
890 }
891
892 fread(&nChunkSize, 1, 4, fd);
893 if (nChunkSize <= 0x40) { // Not big enough
894 fclose(fd);
895 DisplayPropertiesError(hDlg, 2 /* corrupt. */);
896 return;
897 }
898
899 nChunkDataPosition = ftell(fd);
900
901 fread(&nFileVer, 1, 4, fd); // Version of FB that this file was saved from
902
903 fread(&t1, 1, 4, fd); // Min version of FB that NV data will work with
904 fread(&t2, 1, 4, fd); // Min version of FB that All data will work with
905
906 nFileMin = t2; // Replays require a full state
907
908 // if (nBurnVer < nFileMin) { // Error - emulator is too old to load this state
909 // fclose(fd);
910 // return;
911 // }
912
913 fseek(fd, nChunkDataPosition + nChunkSize, SEEK_SET);
914 }
915
916 memset(ReadHeader, 0, 4);
917 fread(ReadHeader, 1, 4, fd); // Read identifier
918 if (memcmp(ReadHeader, szRecordingHeader, 4)) { // Not the chunk type
919 fclose(fd);
920 DisplayPropertiesError(hDlg, 1 /* most likely recorded w/ an earlier version */);
921 return;
922 }
923
924 nChunkSize = 0;
925 fread(&nChunkSize, 1, 4, fd);
926 if (nChunkSize <= 0x10) { // Not big enough
927 fclose(fd);
928 DisplayPropertiesError(hDlg, 2 /* corrupt. */);
929 return;
930 }
931
932 nChunkDataPosition = ftell(fd);
933 fread(&nFrames, 1, 4, fd);
934 fread(&nUndoCount, 1, 4, fd);
935
936 // read metadata
937 fseek(fd, nChunkDataPosition + nChunkSize, SEEK_SET);
938 memset(ReadHeader, 0, 4);
939 fread(ReadHeader, 1, 4, fd); // Read identifier
940 if (memcmp(ReadHeader, szMetadataHeader, 4) == 0) {
941 nChunkSize = 0;
942 fread(&nChunkSize, 1, 4, fd);
943 INT32 nMetaLen = nChunkSize >> 1;
944 if(nMetaLen >= MAX_METADATA) {
945 nMetaLen = MAX_METADATA-1;
946 }
947
948 local_metadata = (wchar_t*)malloc((nMetaLen+1)*sizeof(wchar_t));
949 memset(local_metadata, 0, (nMetaLen+1)*sizeof(wchar_t));
950
951 INT32 i;
952 for(i=0; i<nMetaLen; ++i) {
953 wchar_t c = 0;
954 c |= fgetc(fd) & 0xff;
955 c |= (fgetc(fd) & 0xff) << 8;
956 local_metadata[i] = c;
957 }
958 local_metadata[i] = L'\0';
959
960 if (bStartFromReset) {
961 swscanf(local_metadata, L"%[^','],%959c", wszStartupGame, wszAuthorInfo);
962 bprintf(0, _T("startup game: %s.\n"), wszStartupGame);
963 bprintf(0, _T("author info: %s.\n"), wszAuthorInfo);
964 } else {
965 wcsncpy(wszAuthorInfo, local_metadata, MAX_METADATA-64-1);
966 }
967 }
968
969 // done reading file
970 fclose(fd);
971 free(local_metadata);
972
973 if (hDlg != 0) {
974 // file exists and is the correct format,
975 // so enable the "Ok" button
976 EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
977
978 // turn nFrames into a length string
979 INT32 nSeconds = (nFrames * 100 + (nBurnFPS>>1)) / nBurnFPS;
980 INT32 nMinutes = nSeconds / 60;
981 INT32 nHours = nSeconds / 3600;
982
983 // write strings to dialog
984 char szFramesString[32];
985 char szLengthString[32];
986 char szUndoCountString[32];
987 char szRecordedFrom[32];
988
989 sprintf(szFramesString, "%d", nFrames);
990 sprintf(szLengthString, "%02d:%02d:%02d", nHours, nMinutes % 60, nSeconds % 60);
991 sprintf(szUndoCountString, "%d", nUndoCount);
992 if (nFileVer)
993 sprintf(szRecordedFrom, "v%x.%x.%x.%02x, %s", nFileVer >> 20, (nFileVer >> 16) & 0x0F, (nFileVer >> 8) & 0xFF, nFileVer & 0xFF, (bStartFromReset) ? "Power-On" : "Savestate");
994 else
995 sprintf(szRecordedFrom, "%s", (bStartFromReset) ? "Power-On" : "Savestate");
996
997 SetDlgItemTextA(hDlg, IDC_LENGTH, szLengthString);
998 SetDlgItemTextA(hDlg, IDC_FRAMES, szFramesString);
999 SetDlgItemTextA(hDlg, IDC_UNDO, szUndoCountString);
1000 SetDlgItemTextW(hDlg, IDC_METADATA, wszAuthorInfo);
1001 SetDlgItemTextA(hDlg, IDC_REPLAYRESET, szRecordedFrom);
1002 }
1003 }
1004
ReplayDialogProc(HWND hDlg,UINT Msg,WPARAM wParam,LPARAM)1005 static BOOL CALLBACK ReplayDialogProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM)
1006 {
1007 if (Msg == WM_INITDIALOG) {
1008 wchar_t szFindPath[MAX_PATH] = L"recordings\\*.fr";
1009 WIN32_FIND_DATA wfd;
1010 HANDLE hFind;
1011 INT32 i = 0;
1012
1013 SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_CHECKED, 0);
1014
1015 memset(&wfd, 0, sizeof(WIN32_FIND_DATA));
1016 if (bDrvOkay) {
1017 _stprintf(szFindPath, _T("recordings\\%s*.fr"), BurnDrvGetText(DRV_NAME));
1018 }
1019
1020 hFind = FindFirstFile(szFindPath, &wfd);
1021 if (hFind != INVALID_HANDLE_VALUE) {
1022 do {
1023 if(!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1024 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_INSERTSTRING, i++, (LPARAM)wfd.cFileName);
1025 } while(FindNextFile(hFind, &wfd));
1026 FindClose(hFind);
1027 }
1028 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_INSERTSTRING, i, (LPARAM)_T("Browse..."));
1029 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_SETCURSEL, i, 0);
1030
1031 if (i>=1) {
1032 DisplayReplayProperties(hDlg, false);
1033 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_SETCURSEL, i, 0);
1034 }
1035
1036 SetFocus(GetDlgItem(hDlg, IDC_CHOOSE_LIST));
1037 return FALSE;
1038 }
1039
1040 if (Msg == WM_COMMAND) {
1041 if (HIWORD(wParam) == CBN_SELCHANGE) {
1042 LONG lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
1043 LONG lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
1044 if (lIndex != CB_ERR) {
1045 DisplayReplayProperties(hDlg, (lIndex == lCount - 1)); // Selecting "Browse..." will clear the replay properties display
1046 }
1047 } else if (HIWORD(wParam) == CBN_CLOSEUP) {
1048 LONG lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
1049 LONG lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
1050 if (lIndex != CB_ERR) {
1051 if (lIndex == lCount - 1) {
1052 // send an OK notification to open the file browser
1053 SendMessage(hDlg, WM_COMMAND, (WPARAM)IDOK, 0);
1054 }
1055 }
1056 } else {
1057 INT32 wID = LOWORD(wParam);
1058 switch (wID) {
1059 case IDOK:
1060 {
1061 LONG lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
1062 LONG lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
1063 if (lIndex != CB_ERR) {
1064 if (lIndex == lCount - 1) {
1065 MakeOfn(szFilter);
1066 ofn.lpstrTitle = FBALoadStringEx(hAppInst, IDS_REPLAY_REPLAY, true);
1067 //ofn.Flags &= ~OFN_HIDEREADONLY;
1068
1069 INT32 nRet = GetOpenFileName(&ofn); // Browse...
1070 if (nRet != 0) {
1071 LONG lOtherIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_FINDSTRING, (WPARAM)-1, (LPARAM)szChoice);
1072 if (lOtherIndex != CB_ERR) {
1073 // select already existing string
1074 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_SETCURSEL, lOtherIndex, 0);
1075 } else {
1076 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_INSERTSTRING, lIndex, (LPARAM)szChoice);
1077 SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_SETCURSEL, lIndex, 0);
1078 }
1079 // restore focus to the dialog
1080 SetFocus(GetDlgItem(hDlg, IDC_CHOOSE_LIST));
1081 DisplayReplayProperties(hDlg, false);
1082 if (ofn.Flags & OFN_READONLY || bReplayReadOnly) {
1083 SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_CHECKED, 0);
1084 } else {
1085 SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_UNCHECKED, 0);
1086 }
1087 }
1088 } else {
1089 // get readonly status
1090 bReplayReadOnly = false;
1091 if (BST_CHECKED == SendDlgItemMessage(hDlg, IDC_READONLY, BM_GETCHECK, 0, 0)) {
1092 bReplayReadOnly = true;
1093 }
1094
1095 // get show movements status
1096 bReplayShowMovement = false;
1097 if (BST_CHECKED == SendDlgItemMessage(hDlg, IDC_SHOWMOVEMENT, BM_GETCHECK, 0, 0)) {
1098 bReplayShowMovement = true;
1099 }
1100
1101 EndDialog(hDlg, 1); // only allow OK if a valid selection was made
1102 }
1103 }
1104 }
1105 return TRUE;
1106 case IDCANCEL:
1107 szChoice[0] = '\0';
1108 EndDialog(hDlg, 0);
1109 return FALSE;
1110 }
1111 }
1112 }
1113
1114 return FALSE;
1115 }
1116
ReplayDialog()1117 static INT32 ReplayDialog()
1118 {
1119 return FBADialogBox(hAppInst, MAKEINTRESOURCE(IDD_REPLAYINP), hScrnWnd, (DLGPROC)ReplayDialogProc);
1120 }
1121
VerifyRecordingAccessMode(wchar_t * szFilename,INT32 mode)1122 static INT32 VerifyRecordingAccessMode(wchar_t* szFilename, INT32 mode)
1123 {
1124 GetRecordingPath(szFilename);
1125 if(_waccess(szFilename, mode)) {
1126 return 0; // not writeable, return failure
1127 }
1128
1129 return 1;
1130 }
1131
VerifyRecordingFilename(HWND hDlg)1132 static void VerifyRecordingFilename(HWND hDlg)
1133 {
1134 wchar_t szFilename[MAX_PATH];
1135 GetDlgItemText(hDlg, IDC_FILENAME, szFilename, MAX_PATH);
1136
1137 // if filename null, or, file exists and is not writeable
1138 // then disable the dialog controls
1139 if(szFilename[0] == '\0' ||
1140 (VerifyRecordingAccessMode(szFilename, 0) != 0 && VerifyRecordingAccessMode(szFilename, W_OK) == 0)) {
1141 EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
1142 EnableWindow(GetDlgItem(hDlg, IDC_METADATA), FALSE);
1143 } else {
1144 EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
1145 EnableWindow(GetDlgItem(hDlg, IDC_METADATA), TRUE);
1146 }
1147 }
1148
RecordDialogProc(HWND hDlg,UINT Msg,WPARAM wParam,LPARAM)1149 static BOOL CALLBACK RecordDialogProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM)
1150 {
1151 wchar_t szAuthInfo[MAX_METADATA];
1152
1153 if (Msg == WM_INITDIALOG) {
1154 // come up with a unique name
1155 wchar_t szPath[MAX_PATH];
1156 wchar_t szFilename[MAX_PATH];
1157
1158 INT32 i = 0;
1159 _stprintf(szFilename, _T("%s.fr"), BurnDrvGetText(DRV_NAME));
1160 wcscpy(szPath, szFilename);
1161 while(VerifyRecordingAccessMode(szPath, 0) == 1) {
1162 _stprintf(szFilename, _T("%s-%d.fr"), BurnDrvGetText(DRV_NAME), ++i);
1163 wcscpy(szPath, szFilename);
1164 }
1165
1166 SetDlgItemText(hDlg, IDC_FILENAME, szFilename);
1167 SetDlgItemTextW(hDlg, IDC_METADATA, L"");
1168 CheckDlgButton(hDlg, IDC_REPLAYRESET, BST_UNCHECKED);
1169
1170 VerifyRecordingFilename(hDlg);
1171
1172 SetFocus(GetDlgItem(hDlg, IDC_METADATA));
1173 return FALSE;
1174 }
1175
1176 if (Msg == WM_COMMAND) {
1177 if (HIWORD(wParam) == EN_CHANGE) {
1178 VerifyRecordingFilename(hDlg);
1179 } else {
1180 INT32 wID = LOWORD(wParam);
1181 switch (wID) {
1182 case IDC_BROWSE:
1183 {
1184 _stprintf(szChoice, _T("%s"), BurnDrvGetText(DRV_NAME));
1185 MakeOfn(szFilter);
1186 ofn.lpstrTitle = FBALoadStringEx(hAppInst, IDS_REPLAY_RECORD, true);
1187 ofn.Flags |= OFN_OVERWRITEPROMPT;
1188 INT32 nRet = GetSaveFileName(&ofn);
1189 if (nRet != 0) {
1190 // this should trigger an EN_CHANGE message
1191 SetDlgItemText(hDlg, IDC_FILENAME, szChoice);
1192 }
1193 }
1194 return TRUE;
1195 case IDOK:
1196 GetDlgItemText(hDlg, IDC_FILENAME, szChoice, MAX_PATH);
1197 GetDlgItemTextW(hDlg, IDC_METADATA, szAuthInfo, MAX_METADATA-64-1);
1198 bStartFromReset = false;
1199 if (BST_CHECKED == SendDlgItemMessage(hDlg, IDC_REPLAYRESET, BM_GETCHECK, 0, 0)) {
1200 bStartFromReset = true;
1201 // add "romset," to beginning of metadata
1202 _stprintf(wszMetadata, _T("%s,%s"), BurnDrvGetText(DRV_NAME), szAuthInfo);
1203 } else {
1204 _tcscpy(wszMetadata, szAuthInfo);
1205 }
1206 wszMetadata[MAX_METADATA-1] = L'\0';
1207
1208 // ensure a relative path has the "recordings\" path in prepended to it
1209 GetRecordingPath(szChoice);
1210 EndDialog(hDlg, 1);
1211 return TRUE;
1212 case IDCANCEL:
1213 szChoice[0] = '\0';
1214 EndDialog(hDlg, 0);
1215 return FALSE;
1216 }
1217 }
1218 }
1219
1220 return FALSE;
1221 }
1222
RecordDialog()1223 static INT32 RecordDialog()
1224 {
1225 return FBADialogBox(hAppInst, MAKEINTRESOURCE(IDD_RECORDINP), hScrnWnd, (DLGPROC)RecordDialogProc);
1226 }
1227