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