1 // VGMPlayUI.c: C Source File for the Console User Interface
2 
3 // Note: In order to make MS VC6 NOT crash when using fprintf with stdout, stderr, etc.
4 //		 if linked to msvcrt.lib, the following project setting is important:
5 //		 C/C++ -> Code Generation -> Runtime libraries: Multithreaded DLL
6 
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <stdarg.h>
11 #include <wchar.h>
12 #include <ctype.h>	// for toupper
13 #include <locale.h>	// for setlocale
14 #include "stdbool.h"
15 #include <math.h>
16 
17 #ifdef WIN32
18 #include <conio.h>
19 #include <windows.h>
20 #else
21 #include <limits.h>	// for PATH_MAX
22 #include <termios.h>
23 #include <unistd.h>	// for STDIN_FILENO and usleep()
24 #include <sys/time.h>	// for struct timeval in _kbhit()
25 #include <signal.h> // for signal()
26 
27 #define	Sleep(msec)	usleep(msec * 1000)
28 #define _vsnwprintf	vswprintf
29 #endif
30 
31 #define printerr(x)	fprintf(stderr, x)
32 
33 #include "chips/mamedef.h"
34 
35 #include "Stream.h"
36 #include "VGMPlay.h"
37 #include "VGMPlay_Intf.h"
38 
39 #ifdef XMAS_EXTRA
40 #include "XMasFiles/XMasBonus.h"
41 #endif
42 #ifdef WS_DEMO
43 #include "XMasFiles/SWJ-SQRC01_1C.h"
44 #endif
45 
46 #ifndef WIN32
47 void WaveOutLinuxCallBack(void);
48 #endif
49 
50 #ifdef WIN32
51 #define DIR_CHR		'\\'
52 #define DIR_STR		"\\"
53 #define QMARK_CHR	'\"'
54 #else
55 #define DIR_CHR		'/'
56 #define DIR_STR		"/"
57 #define QMARK_CHR	'\''
58 
59 #ifndef SHARE_PREFIX
60 #define SHARE_PREFIX	"/usr/local"
61 #endif
62 
63 #endif
64 
65 #define APP_NAME	"VGM Player"
66 #define APP_NAME_L	L"VGM Player"
67 
68 
69 int main(int argc, char* argv[]);
70 static void RemoveNewLines(char* String);
71 static void RemoveQuotationMarks(char* String);
72 static char* GetLastDirSeparator(const char* FilePath);
73 static bool IsAbsolutePath(const char* FilePath);
74 static char* GetFileExtension(const char* FilePath);
75 static void StandardizeDirSeparators(char* FilePath);
76 #ifdef WIN32
77 static void WinNT_Check(void);
78 #endif
79 static char* GetAppFileName(void);
80 static void cls(void);
81 #ifndef WIN32
82 static void changemode(bool);
83 static int _kbhit(void);
84 static int _getch(void);
85 #endif
86 static INT8 stricmp_u(const char *string1, const char *string2);
87 static INT8 strnicmp_u(const char *string1, const char *string2, size_t count);
88 static void ReadOptions(const char* AppName);
89 static bool GetBoolFromStr(const char* TextStr);
90 #if defined(XMAS_EXTRA) || defined(WS_DEMO)
91 static bool XMas_Extra(char* FileName, bool Mode);
92 #endif
93 #ifndef WIN32
94 static void ConvertCP1252toUTF8(char** DstStr, const char* SrcStr);
95 #endif
96 static bool OpenPlayListFile(const char* FileName);
97 static bool OpenMusicFile(const char* FileName);
98 extern bool OpenVGMFile(const char* FileName);
99 extern bool OpenOtherFile(const char* FileName);
100 
101 static void wprintc(const wchar_t* format, ...);
102 static void PrintChipStr(UINT8 ChipID, UINT8 SubType, UINT32 Clock);
103 static const wchar_t* GetTagStrEJ(const wchar_t* EngTag, const wchar_t* JapTag);
104 static void ShowVGMTag(void);
105 
106 static void PlayVGM_UI(void);
107 INLINE INT8 sign(double Value);
108 INLINE long int Round(double Value);
109 INLINE double RoundSpecial(double Value, double RoundTo);
110 static void PrintMinSec(UINT32 SamplePos, UINT32 SmplRate);
111 
112 
113 // Options Variables
114 extern UINT32 SampleRate;	// Note: also used by some sound cores to
115 							//       determinate the chip sample rate
116 
117 extern UINT32 VGMPbRate;
118 extern UINT32 VGMMaxLoop;
119 extern UINT32 CMFMaxLoop;
120 UINT32 FadeTimeN;	// normal fade time
121 UINT32 FadeTimePL;	// in-playlist fade time
122 extern UINT32 FadeTime;
123 UINT32 PauseTimeJ;	// Pause Time for Jingles
124 UINT32 PauseTimeL;	// Pause Time for Looping Songs
125 extern UINT32 PauseTime;
126 static UINT8 Show95Cmds;
127 
128 extern float VolumeLevel;
129 extern bool SurroundSound;
130 extern UINT8 HardStopOldVGMs;
131 extern bool FadeRAWLog;
132 static UINT8 LogToWave;
133 //extern bool FullBufFill;
134 extern bool PauseEmulate;
135 extern bool DoubleSSGVol;
136 static UINT16 ForceAudioBuf;
137 static UINT8 OutputDevID;
138 
139 extern UINT8 ResampleMode;	// 00 - HQ both, 01 - LQ downsampling, 02 - LQ both
140 extern UINT8 CHIP_SAMPLING_MODE;
141 extern INT32 CHIP_SAMPLE_RATE;
142 
143 extern UINT16 FMPort;
144 extern bool UseFM;
145 extern bool FMForce;
146 //extern bool FMAccurate;
147 extern bool FMBreakFade;
148 extern float FMVol;
149 extern bool FMOPL2Pan;
150 
151 extern CHIPS_OPTION ChipOpts[0x02];
152 
153 
154 extern bool ThreadPauseEnable;
155 extern volatile bool ThreadPauseConfrm;
156 extern bool ThreadNoWait;	// don't reset the timer
157 extern UINT16 AUDIOBUFFERU;
158 extern UINT32 SMPL_P_BUFFER;
159 extern char SoundLogFile[MAX_PATH];
160 
161 extern UINT8 OPL_MODE;
162 extern UINT8 OPL_CHIPS;
163 //extern bool WINNT_MODE;
164 UINT8 NEED_LARGE_AUDIOBUFS;
165 
166 extern char* AppPaths[8];
167 static char AppPathBuffer[MAX_PATH * 2];
168 
169 static char PLFileBase[MAX_PATH];
170 static char PLFileName[MAX_PATH];
171 static UINT32 PLFileCount;
172 static char** PlayListFile;
173 static UINT32 CurPLFile;
174 static UINT8 NextPLCmd;
175 static UINT8 PLMode;	// set to 1 to show Playlist text
176 static bool FirstInit;
177 extern bool AutoStopSkip;
178 
179 static char VgmFileName[MAX_PATH];
180 static UINT8 FileMode;
181 extern VGM_HEADER VGMHead;
182 extern UINT32 VGMDataLen;
183 extern UINT8* VGMData;
184 extern GD3_TAG VGMTag;
185 static bool PreferJapTag;
186 
187 extern volatile bool PauseThread;
188 static bool StreamStarted;
189 
190 extern float MasterVol;
191 
192 extern UINT32 VGMPos;
193 extern INT32 VGMSmplPos;
194 extern INT32 VGMSmplPlayed;
195 extern INT32 VGMSampleRate;
196 extern UINT32 BlocksSent;
197 extern UINT32 BlocksPlayed;
198 static bool IsRAWLog;
199 extern bool EndPlay;
200 extern bool PausePlay;
201 extern bool FadePlay;
202 extern bool ForceVGMExec;
203 extern UINT8 PlayingMode;
204 
205 extern UINT32 PlayingTime;
206 
207 extern UINT32 FadeStart;
208 extern UINT32 VGMMaxLoopM;
209 extern UINT32 VGMCurLoop;
210 extern float VolumeLevelM;
211 bool ErrorHappened;	// used by VGMPlay.c and VGMPlay_AddFmts.c
212 extern float FinalVol;
213 extern bool ResetPBTimer;
214 
215 #ifndef WIN32
216 static struct termios oldterm;
217 static bool termmode;
218 #endif
219 static volatile bool sigint = false;
220 
221 UINT8 CmdList[0x100];
222 
223 //extern UINT8 DISABLE_YMZ_FIX;
224 extern UINT8 IsVGMInit;
225 extern UINT16 Last95Drum;	// for optvgm debugging
226 extern UINT16 Last95Max;	// for optvgm debugging
227 extern UINT32 Last95Freq;	// for optvgm debugging
228 
229 static bool PrintMSHours;
230 
231 #ifdef WIN32
signal_handler(DWORD dwCtrlType)232 static BOOL WINAPI signal_handler(DWORD dwCtrlType)
233 {
234 	switch(dwCtrlType)
235 	{
236 	case CTRL_C_EVENT:		// Ctrl + C
237 	case CTRL_CLOSE_EVENT:	// close console window via X button
238 	case CTRL_BREAK_EVENT:	// Ctrl + Break
239 		sigint = true;
240 		return TRUE;
241 	case CTRL_LOGOFF_EVENT:
242 	case CTRL_SHUTDOWN_EVENT:
243 		return FALSE;
244 	}
245 	return FALSE;
246 }
247 #else
signal_handler(int signal)248 static void signal_handler(int signal)
249 {
250 	if(signal == SIGINT)
251 		sigint = true;
252 }
253 #endif
254 
main(int argc,char * argv[])255 int main(int argc, char* argv[])
256 {
257 	int argbase;
258 	int ErrRet;
259 	char* AppName;
260 #if defined(XMAS_EXTRA) || defined(WS_DEMO)
261 	bool XMasEnable;
262 #endif
263 	char* AppPathPtr;
264 	const char* StrPtr;
265 	const char* FileExt;
266 	UINT8 CurPath;
267 	UINT32 ChrPos;
268 
269 	// set locale to "current system locale"
270 	// (makes Unicode characters (like umlauts) work under Linux and fixes some
271 	//  Unicode -> ANSI conversions)
272 	setlocale(LC_CTYPE, "");
273 
274 #ifndef WIN32
275 	tcgetattr(STDIN_FILENO, &oldterm);
276 	termmode = false;
277 	signal(SIGINT, signal_handler);
278 #else
279 	SetConsoleCtrlHandler(signal_handler, TRUE);
280 #endif
281 
282 	if (argc > 1)
283 	{
284 		if (! stricmp_u(argv[1], "-v") || ! stricmp_u(argv[1], "--version"))
285 		{
286 			printf("VGMPlay %s"
287 #if defined(APLHA)
288 					" alpha"
289 #elif defined(BETA)
290 					" beta"
291 #endif
292 					", supports VGM %s\n", VGMPLAY_VER_STR, VGM_VER_STR);
293 			return 0;
294 		}
295 	}
296 
297 #ifdef SET_CONSOLE_TITLE
298 #ifdef WIN32
299 	SetConsoleTitle(APP_NAME);			// Set Windows Console Title
300 #else
301 	printf("\x1B]0;%s\x07", APP_NAME);	// Set xterm/rxvt Terminal Title
302 #endif
303 #endif
304 
305 	printf(APP_NAME);
306 #ifdef XMAS_EXTRA
307 	printf(" - XMas Release");
308 #endif
309 	printf("\n----------\n");
310 
311 	//if (argv[0x00] == NULL)
312 	//	printf("Argument \"Application-Name\" is NULL!\n");
313 
314 	// Warning! It's dangerous to use Argument 0!
315 	// AppName may be "vgmplay" instead of "vgmplay.exe"
316 
317 	VGMPlay_Init();
318 
319 	// Note: Paths are checked from last to first.
320 	CurPath = 0x00;
321 	AppPathPtr = AppPathBuffer;
322 #ifndef WIN32
323 	// Path 1: global share directory
324 	AppPaths[CurPath] = SHARE_PREFIX "/share/vgmplay/";
325 	CurPath ++;
326 #endif
327 
328 	// Path 2: exe's directory
329 	AppName = GetAppFileName();	// "C:\VGMPlay\VGMPlay.exe"
330 	// Note: GetAppFileName always returns native directory separators.
331 	StrPtr = strrchr(AppName, DIR_CHR);
332 	if (StrPtr != NULL)
333 	{
334 		ChrPos = StrPtr + 1 - AppName;
335 		strncpy(AppPathPtr, AppName, ChrPos);
336 		AppPathPtr[ChrPos] = 0x00;	// "C:\VGMPlay\"
337 		AppPaths[CurPath] = AppPathPtr;
338 		CurPath ++;
339 		AppPathPtr += ChrPos + 1;
340 	}
341 
342 #ifndef WIN32
343 	// Path 3: home directory
344 	StrPtr = getenv("XDG_CONFIG_HOME");
345 	if (StrPtr != NULL && StrPtr[0] == '\0')
346 	{
347 		strcpy(AppPathPtr, StrPtr);
348 	}
349 	else
350 	{
351 		StrPtr = getenv("HOME");
352 		if (StrPtr != NULL)
353 			strcpy(AppPathPtr, StrPtr);
354 		else
355 			strcpy(AppPathPtr, "");
356 		strcat(AppPathPtr, "/.config");
357 	}
358 	strcat(AppPathPtr, "/vgmplay/");
359 	AppPaths[CurPath] = AppPathPtr;
360 	CurPath ++;
361 	AppPathPtr += strlen(AppPathPtr) + 1;
362 #endif
363 
364 	// Path 4: working directory ("\0")
365 	AppPathPtr[0] = '\0';
366 	AppPaths[CurPath] = AppPathPtr;
367 	CurPath ++;
368 
369 #if 0	// set to 1 to print all selected search paths
370 	CurPath = 0;
371 	while(AppPaths[CurPath] != NULL)
372 	{
373 		printf("Path %u: %s\n", CurPath + 1, AppPaths[CurPath]);
374 		CurPath ++;
375 	}
376 #endif
377 
378 	ReadOptions(AppName);
379 	VGMPlay_Init2();
380 
381 	ErrRet = 0;
382 	argbase = 0x01;
383 	while(argbase < argc)
384 	{
385 		if (! strnicmp_u(argv[argbase], "-LogSound:", 10))
386 		{
387 			LogToWave = (UINT8)strtoul(argv[argbase] + 10, NULL, 0);
388 			argbase ++;
389 		}
390 		else if (! strnicmp_u(argv[argbase], "-DeviceId:", 10))
391 		{
392 			OutputDevID = (UINT8)strtoul(argv[argbase] + 10, NULL, 0);
393 			argbase ++;
394 		}
395 		else
396 		{
397 			break;
398 		}
399 	}
400 
401 	printf("\nFile Name:\t");
402 	if (argc <= argbase)
403 	{
404 #ifdef WIN32
405 		INT32 OldCP;
406 
407 		OldCP = GetConsoleCP();
408 
409 		// Set the Console Input Codepage to ANSI.
410 		// The Output Codepage must be left at OEM, else the displayed characters are wrong.
411 		ChrPos = GetACP();
412 		ErrRet = SetConsoleCP(ChrPos);			// set input codepage
413 		//ErrRet = SetConsoleOutputCP(ChrPos);	// set output codepage (would be a bad idea)
414 
415 		StrPtr = fgets(VgmFileName, MAX_PATH, stdin);
416 		if (StrPtr == NULL)
417 			VgmFileName[0] = '\0';
418 
419 		// Playing with the console font resets the Console Codepage to OEM, so I have to
420 		// convert the file name in this case.
421 		if (GetConsoleCP() == GetOEMCP())
422 			OemToChar(VgmFileName, VgmFileName);	// OEM -> ANSI conversion
423 
424 		// This fixes the display of non-ANSI characters.
425 		ErrRet = SetConsoleCP(OldCP);
426 
427 		// This codepage stuff drives me insane.
428 		// Debug and Release build behave differently - WHAT??
429 		//
430 		// There a list of behaviours.
431 		// Debug and Release were tested by dropping a file on it and via Visual Studio.
432 		//
433 		// Input CP 850, Output CP 850
434 		//	Debug build:	Dynamite D�x
435 		//	Release build:	Dynamite D�x
436 		// Input CP 1252, Output CP 850
437 		//	Debug build:	Dynamite D�x
438 		//	Release build:	Dynamite D�x
439 		// Input CP 850, Output CP 1252
440 		//	Debug build:	Dynamite D�x [tag display wrong]
441 		//	Release build:	Dynamite D�x [tag display wrong]
442 		// Input CP 1252, Output CP 1252
443 		//	Debug build:	Dynamite D�x [tag display wrong]
444 		//	Release build:	Dynamite D�x [tag display wrong]
445 #else
446 		StrPtr = fgets(VgmFileName, MAX_PATH, stdin);
447 		if (StrPtr == NULL)
448 			VgmFileName[0] = '\0';
449 #endif
450 
451 		RemoveNewLines(VgmFileName);
452 		RemoveQuotationMarks(VgmFileName);
453 	}
454 	else
455 	{
456 		// The argument should already use the ANSI codepage.
457 		strcpy(VgmFileName, argv[argbase]);
458 		printf("%s\n", VgmFileName);
459 	}
460 	if (! strlen(VgmFileName))
461 		goto ExitProgram;
462 	StandardizeDirSeparators(VgmFileName);
463 
464 #if defined(XMAS_EXTRA) || defined(WS_DEMO)
465 	XMasEnable = XMas_Extra(VgmFileName, 0x00);
466 #endif
467 
468 #if 0
469 	{	// Print hex characters of file name (for vgm-player script debugging)
470 		const char* CurChr;
471 
472 #ifdef WIN32
473 		printf("Input CP: %d, Output CP: %d\n", GetConsoleCP(), GetConsoleOutputCP());
474 #endif
475 		printf("VgmFileName: ");
476 
477 		CurChr = VgmFileName;
478 		while(*CurChr != '\0')
479 		{
480 			printf("%02X ", (UINT8)*CurChr);
481 			CurChr ++;
482 		}
483 		printf("%02X\n", (UINT8)*CurChr);
484 		_getch();
485 	}
486 #endif
487 #if 0
488 	{	// strip spaces and \n (fixed bugs with vgm-player script with un-7z)
489 		char* CurChr;
490 
491 		// trim \n and spaces off
492 		CurChr = strchr(VgmFileName, '\n');
493 		if (CurChr != NULL)
494 			*CurChr = '\0';
495 		CurChr = VgmFileName + strlen(VgmFileName) - 1;
496 		while(CurChr > VgmFileName && *CurChr == ' ')
497 			*(CurChr --) = '\0';
498 	}
499 #endif
500 
501 	FirstInit = true;
502 	StreamStarted = false;
503 	FileExt = GetFileExtension(VgmFileName);
504 	if (FileExt == NULL || stricmp_u(FileExt, "m3u"))
505 		PLMode = 0x00;
506 	else
507 		PLMode = 0x01;
508 
509 	if (! PLMode)
510 	{
511 		PLFileCount = 0x00;
512 		CurPLFile = 0x00;
513 		// no Play List File
514 		if (! OpenMusicFile(VgmFileName))
515 		{
516 			printerr("Error opening the file!\n");
517 			if (argv[0][1] == ':')
518 				_getch();
519 			ErrRet = 1;
520 			goto ExitProgram;
521 		}
522 		printf("\n");
523 
524 		ErrorHappened = false;
525 		FadeTime = FadeTimeN;
526 		PauseTime = PauseTimeL;
527 		PrintMSHours = (VGMHead.lngTotalSamples >= 158760000);	// 44100 smpl * 60 sec * 60 min
528 		ShowVGMTag();
529 		NextPLCmd = 0x80;
530 		PlayVGM_UI();
531 
532 		CloseVGMFile();
533 	}
534 	else
535 	{
536 		strcpy(PLFileName, VgmFileName);
537 		if (! OpenPlayListFile(PLFileName))
538 		{
539 			printerr("Error opening the playlist!\n");
540 			if (argv[0][1] == ':')
541 				_getch();
542 			ErrRet = 1;
543 			goto ExitProgram;
544 		}
545 
546 		for (CurPLFile = 0x00; CurPLFile < PLFileCount; CurPLFile ++)
547 		{
548 			cls();
549 			printf(APP_NAME);
550 			printf("\n----------\n");
551 			printf("\nPlaylist File:\t%s\n", PLFileName);
552 			printf("Playlist Entry:\t%u / %u\n", CurPLFile + 1, PLFileCount);
553 			printf("File Name:\t%s\n", PlayListFile[CurPLFile]);
554 
555 			if (IsAbsolutePath(PlayListFile[CurPLFile]))
556 			{
557 				strcpy(VgmFileName, PlayListFile[CurPLFile]);
558 			}
559 			else
560 			{
561 				strcpy(VgmFileName, PLFileBase);
562 				strcat(VgmFileName, PlayListFile[CurPLFile]);
563 			}
564 
565 			if (! OpenMusicFile(VgmFileName))
566 			{
567 				printf("Error opening the file!\n");
568 				_getch();
569 				while(_kbhit())
570 					_getch();
571 				continue;
572 			}
573 			printf("\n");
574 
575 			ErrorHappened = false;
576 			if (CurPLFile < PLFileCount - 1)
577 				FadeTime = FadeTimePL;
578 			else
579 				FadeTime = FadeTimeN;
580 			PauseTime = VGMHead.lngLoopOffset ? PauseTimeL : PauseTimeJ;
581 			PrintMSHours = (VGMHead.lngTotalSamples >= 158760000);
582 			ShowVGMTag();
583 			NextPLCmd = 0x00;
584 			PlayVGM_UI();
585 			CloseVGMFile();
586 
587 			if (ErrorHappened)
588 			{
589 				if (_kbhit())
590 					_getch();
591 				_getch();
592 				ErrorHappened = false;
593 			}
594 			if (NextPLCmd == 0xFF)
595 				break;
596 			else if (NextPLCmd == 0x01)
597 				CurPLFile -= 0x02;	// Jump to last File (-2 + 1 = -1)
598 		}
599 	}
600 
601 	if (ErrorHappened && argv[0][1] == ':')
602 	{
603 		if (_kbhit())
604 			_getch();
605 		_getch();
606 	}
607 
608 #ifdef _DEBUG
609 	printf("Press any key ...");
610 	_getch();
611 #endif
612 
613 ExitProgram:
614 #if defined(XMAS_EXTRA) || defined(WS_DEMO)
615 	if (XMasEnable)
616 		XMas_Extra(VgmFileName, 0x01);
617 #endif
618 #ifndef WIN32
619 	changemode(false);
620 #ifdef SET_CONSOLE_TITLE
621 //	printf("\x1B]0;${USER}@${HOSTNAME}: ${PWD/$HOME/~}\x07", APP_NAME);	// Reset xterm/rxvt Terminal Title
622 #endif
623 #endif
624 	VGMPlay_Deinit();
625 	free(AppName);
626 
627 	return ErrRet;
628 }
629 
RemoveNewLines(char * String)630 static void RemoveNewLines(char* String)
631 {
632 	char* StrPtr;
633 
634 	StrPtr = String + strlen(String) - 1;
635 	while(StrPtr >= String && (UINT8)*StrPtr < 0x20)
636 	{
637 		*StrPtr = '\0';
638 		StrPtr --;
639 	}
640 
641 	return;
642 }
643 
RemoveQuotationMarks(char * String)644 static void RemoveQuotationMarks(char* String)
645 {
646 	UINT32 StrLen;
647 	char* EndQMark;
648 
649 	if (String[0x00] != QMARK_CHR)
650 		return;
651 
652 	StrLen = strlen(String);
653 	memmove(String, String + 0x01, StrLen);	// Remove first char
654 	EndQMark = strrchr(String, QMARK_CHR);
655 	if (EndQMark != NULL)
656 		*EndQMark = 0x00;	// Remove last Quot.-Mark
657 
658 	return;
659 }
660 
GetLastDirSeparator(const char * FilePath)661 static char* GetLastDirSeparator(const char* FilePath)
662 {
663 	char* SepPos1;
664 	char* SepPos2;
665 
666 	SepPos1 = strrchr(FilePath, '/');
667 	SepPos2 = strrchr(FilePath, '\\');
668 	if (SepPos1 < SepPos2)
669 		return SepPos2;
670 	else
671 		return SepPos1;
672 }
673 
IsAbsolutePath(const char * FilePath)674 static bool IsAbsolutePath(const char* FilePath)
675 {
676 #ifdef WIN32
677 	if (FilePath[0] == '\0')
678 		return false;	// empty string
679 	if (FilePath[1] == ':')
680 		return true;	// Device Path: C:\path
681 	if (! strncmp(FilePath, "\\\\", 2))
682 		return true;	// Network Path: \\computername\path
683 #else
684 	if (FilePath[0] == '/')
685 		return true;	// absolute UNIX path
686 #endif
687 	return false;
688 }
689 
GetFileExtension(const char * FilePath)690 static char* GetFileExtension(const char* FilePath)
691 {
692 	char* DirSepPos;
693 	char* ExtDotPos;
694 
695 	DirSepPos = GetLastDirSeparator(FilePath);
696 	if (DirSepPos == NULL)
697 		DirSepPos = (char*)FilePath;
698 	ExtDotPos = strrchr(DirSepPos, '.');
699 	if (ExtDotPos == NULL)
700 		return NULL;
701 	else
702 		return ExtDotPos + 1;
703 }
704 
StandardizeDirSeparators(char * FilePath)705 static void StandardizeDirSeparators(char* FilePath)
706 {
707 	char* CurChr;
708 
709 	CurChr = FilePath;
710 	while(*CurChr != '\0')
711 	{
712 		if (*CurChr == '\\' || *CurChr == '/')
713 			*CurChr = DIR_CHR;
714 		CurChr ++;
715 	}
716 
717 	return;
718 }
719 
720 #ifdef WIN32
WinNT_Check(void)721 static void WinNT_Check(void)
722 {
723 	OSVERSIONINFO VerInf;
724 
725 	VerInf.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
726 	GetVersionEx(&VerInf);
727 	//WINNT_MODE = (VerInf.dwPlatformId == VER_PLATFORM_WIN32_NT);
728 
729 	/* Following Systems need larger Audio Buffers:
730 		- Windows 95 (500+ ms)
731 		- Windows Vista (200+ ms)
732 	Tested Systems:
733 		- Windows 95B
734 		- Windows 98 SE
735 		- Windows 2000
736 		- Windows XP (32-bit)
737 		- Windows Vista (32-bit)
738 		- Windows 7 (64-bit)
739 	*/
740 
741 	NEED_LARGE_AUDIOBUFS = 0;
742 	if (VerInf.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
743 	{
744 		if (VerInf.dwMajorVersion == 4 && VerInf.dwMinorVersion == 0)
745 			NEED_LARGE_AUDIOBUFS = 50;	// Windows 95
746 	}
747 	else if (VerInf.dwPlatformId == VER_PLATFORM_WIN32_NT)
748 	{
749 		if (VerInf.dwMajorVersion == 6 && VerInf.dwMinorVersion == 0)
750 			NEED_LARGE_AUDIOBUFS = 20;	// Windows Vista
751 	}
752 
753 	return;
754 }
755 #endif
756 
GetAppFileName(void)757 static char* GetAppFileName(void)
758 {
759 	char* AppPath;
760 	int RetVal;
761 
762 	AppPath = (char*)malloc(MAX_PATH * sizeof(char));
763 #ifdef WIN32
764 	RetVal = GetModuleFileName(NULL, AppPath, MAX_PATH);
765 	if (! RetVal)
766 		AppPath[0] = '\0';
767 #else
768 	RetVal = readlink("/proc/self/exe", AppPath, MAX_PATH);
769 	if (RetVal == -1)
770 		AppPath[0] = '\0';
771 #endif
772 
773 	return AppPath;
774 }
775 
cls(void)776 static void cls(void)
777 {
778 #ifdef WIN32
779 	// CLS-Function from the MSDN Help
780 	HANDLE hConsole;
781 	COORD coordScreen = {0, 0};
782 	BOOL bSuccess;
783 	DWORD cCharsWritten;
784 	CONSOLE_SCREEN_BUFFER_INFO csbi;
785 	DWORD dwConSize;
786 
787 	hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
788 
789 	// get the number of character cells in the current buffer
790 	bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi);
791 
792 	// fill the entire screen with blanks
793 	dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
794 	bSuccess = FillConsoleOutputCharacter(hConsole, (TCHAR)' ', dwConSize, coordScreen,
795 											&cCharsWritten);
796 
797 	// get the current text attribute
798 	//bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi);
799 
800 	// now set the buffer's attributes accordingly
801 	//bSuccess = FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen,
802 	//										&cCharsWritten);
803 
804 	// put the cursor at (0, 0)
805 	bSuccess = SetConsoleCursorPosition(hConsole, coordScreen);
806 #else
807 	int retVal;
808 
809 	retVal = system("clear");
810 #endif
811 
812 	return;
813 }
814 
815 #ifndef WIN32
816 
changemode(bool dir)817 static void changemode(bool dir)
818 {
819 	static struct termios newterm;
820 
821 	if (termmode == dir)
822 		return;
823 
824 	if (dir)
825 	{
826 		newterm = oldterm;
827 		newterm.c_lflag &= ~(ICANON | ECHO);
828 		tcsetattr(STDIN_FILENO, TCSANOW, &newterm);
829 	}
830 	else
831 	{
832 		tcsetattr(STDIN_FILENO, TCSANOW, &oldterm);
833 	}
834 	termmode = dir;
835 
836 	return;
837 }
838 
_kbhit(void)839 static int _kbhit(void)
840 {
841 	struct timeval tv;
842 	fd_set rdfs;
843 	int kbret;
844 	bool needchg;
845 
846 	needchg = (! termmode);
847 	if (needchg)
848 		changemode(true);
849 	tv.tv_sec = 0;
850 	tv.tv_usec = 0;
851 
852 	FD_ZERO(&rdfs);
853 	FD_SET(STDIN_FILENO, &rdfs);
854 
855 	select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
856 	kbret = FD_ISSET(STDIN_FILENO, &rdfs);
857 	if (needchg)
858 		changemode(false);
859 
860 	return kbret;
861 }
862 
_getch(void)863 static int _getch(void)
864 {
865 	int ch;
866 	bool needchg;
867 
868 	needchg = (! termmode);
869 	if (needchg)
870 		changemode(true);
871 	ch = getchar();
872 	if (needchg)
873 		changemode(false);
874 
875 	return ch;
876 }
877 #endif
878 
stricmp_u(const char * string1,const char * string2)879 static INT8 stricmp_u(const char *string1, const char *string2)
880 {
881 	// my own stricmp, because VC++6 doesn't find _stricmp when compiling without
882 	// standard libraries
883 	const char* StrPnt1;
884 	const char* StrPnt2;
885 	char StrChr1;
886 	char StrChr2;
887 
888 	StrPnt1 = string1;
889 	StrPnt2 = string2;
890 	while(true)
891 	{
892 		StrChr1 = toupper(*StrPnt1);
893 		StrChr2 = toupper(*StrPnt2);
894 
895 		if (StrChr1 < StrChr2)
896 			return -1;
897 		else if (StrChr1 > StrChr2)
898 			return +1;
899 		if (StrChr1 == 0x00)
900 			return 0;
901 
902 		StrPnt1 ++;
903 		StrPnt2 ++;
904 	}
905 
906 	return 0;
907 }
908 
strnicmp_u(const char * string1,const char * string2,size_t count)909 static INT8 strnicmp_u(const char *string1, const char *string2, size_t count)
910 {
911 	// my own strnicmp, because GCC doesn't seem to have _strnicmp
912 	const char* StrPnt1;
913 	const char* StrPnt2;
914 	char StrChr1;
915 	char StrChr2;
916 	size_t CurChr;
917 
918 	StrPnt1 = string1;
919 	StrPnt2 = string2;
920 	CurChr = 0x00;
921 	while(CurChr < count)
922 	{
923 		StrChr1 = toupper(*StrPnt1);
924 		StrChr2 = toupper(*StrPnt2);
925 
926 		if (StrChr1 < StrChr2)
927 			return -1;
928 		else if (StrChr1 > StrChr2)
929 			return +1;
930 		if (StrChr1 == 0x00)
931 			return 0;
932 
933 		StrPnt1 ++;
934 		StrPnt2 ++;
935 		CurChr ++;
936 	}
937 
938 	return 0;
939 }
940 
ReadOptions(const char * AppName)941 static void ReadOptions(const char* AppName)
942 {
943 	const UINT8 CHN_COUNT[CHIP_COUNT] =
944 	{	0x04, 0x09, 0x06, 0x08, 0x10, 0x08, 0x03, 0x00,
945 		0x00, 0x09, 0x09, 0x09, 0x12, 0x00, 0x0C, 0x08,
946 		0x08, 0x00, 0x03, 0x04, 0x05, 0x1C, 0x00, 0x00,
947 		0x04, 0x05, 0x08, 0x08, 0x18, 0x04, 0x04, 0x10,
948 		0x20, 0x04, 0x06, 0x06, 0x20, 0x20, 0x10, 0x20,
949 		0x04
950 	};
951 	const UINT8 CHN_MASK_CNT[CHIP_COUNT] =
952 	{	0x04, 0x0E, 0x07, 0x08, 0x10, 0x08, 0x03, 0x06,
953 		0x06, 0x0E, 0x0E, 0x0E, 0x17, 0x18, 0x0C, 0x08,
954 		0x08, 0x00, 0x03, 0x04, 0x05, 0x1C, 0x00, 0x00,
955 		0x04, 0x05, 0x08, 0x08, 0x18, 0x04, 0x04, 0x10,
956 		0x20, 0x04, 0x06, 0x06, 0x20, 0x20, 0x10, 0x20,
957 		0x04
958 	};
959 	const char* FNList[3];
960 	char* FileName;
961 	FILE* hFile;
962 	char TempStr[0x40];
963 	UINT32 StrLen;
964 	UINT32 TempLng;
965 	char* LStr;
966 	char* RStr;
967 	UINT8 IniSection;
968 	UINT8 CurChip;
969 	CHIP_OPTS* TempCOpt;
970 	CHIP_OPTS* TempCOpt2;
971 	UINT8 CurChn;
972 	char* TempPnt;
973 	bool TempFlag;
974 
975 	// most defaults are set by VGMPlay_Init()
976 	FadeTimeN = FadeTime;
977 	PauseTimeJ = PauseTime;
978 	PauseTimeL = 0;
979 	Show95Cmds = 0x00;
980 	LogToWave = 0x00;
981 	OutputDevID = 0;
982 	ForceAudioBuf = 0x00;
983 	PreferJapTag = false;
984 
985 	if (AppName == NULL)
986 	{
987 		printerr("Argument \"Application-Path\" is NULL!\nSkip loading INI.\n");
988 		return;
989 	}
990 
991 	// AppName: "C:\VGMPlay\VGMPlay.exe"
992 	RStr = strrchr(AppName, DIR_CHR);
993 	if (RStr != NULL)
994 		RStr ++;
995 	else
996 		RStr = (char*)AppName;
997 	FileName = (char*)malloc(strlen(RStr) + 0x05);	// ".ini" + 00
998 	strcpy(FileName, RStr);
999 	// FileName: "VGMPlay.exe"
1000 
1001 	RStr = GetFileExtension(FileName);
1002 	if (RStr == NULL)
1003 	{
1004 		RStr = FileName + strlen(FileName);
1005 		*RStr = '.';
1006 		RStr ++;
1007 	}
1008 	strcpy(RStr, "ini");
1009 	// FileName: "VGMPlay.ini" or "vgmplay.ini"
1010 
1011 	// on Linux platforms, it searches for "vgmplay.ini" first and
1012 	// file names are case sensitive
1013 	FNList[0] = FileName;
1014 	FNList[1] = "VGMPlay.ini";
1015 	FNList[2] = NULL;
1016 	LStr = FileName;
1017 	FileName = FindFile_List(FNList);
1018 	free(LStr);
1019 	if (FileName == NULL)
1020 	{
1021 		printerr("Failed to load INI.\n");
1022 		return;
1023 	}
1024 	hFile = fopen(FileName, "rt");
1025 	free(FileName);
1026 	if (hFile == NULL)
1027 	{
1028 		printerr("Failed to load INI.\n");
1029 		return;
1030 	}
1031 
1032 	IniSection = 0x00;
1033 	while(! feof(hFile))
1034 	{
1035 		LStr = fgets(TempStr, 0x40, hFile);
1036 		if (LStr == NULL)
1037 			break;
1038 		if (TempStr[0x00] == ';')	// Comment line
1039 			continue;
1040 
1041 		StrLen = strlen(TempStr) - 0x01;
1042 		//if (TempStr[StrLen] == '\n')
1043 		//	TempStr[StrLen] = '\0';
1044 		while(TempStr[StrLen] < 0x20)
1045 		{
1046 			TempStr[StrLen] = '\0';
1047 			if (! StrLen)
1048 				break;
1049 			StrLen --;
1050 		}
1051 		if (! StrLen)
1052 			continue;
1053 		StrLen ++;
1054 
1055 		LStr = &TempStr[0x00];
1056 		while(*LStr == ' ')
1057 			LStr ++;
1058 		if (LStr[0x00] == ';')	// Comment line
1059 			continue;
1060 
1061 		if (LStr[0x00] == '[')
1062 			RStr = strchr(TempStr, ']');
1063 		else
1064 			RStr = strchr(TempStr, '=');
1065 		if (RStr == NULL)
1066 			continue;
1067 
1068 		if (LStr[0x00] == '[')
1069 		{
1070 			// Line pattern: [Group]
1071 			LStr ++;
1072 			RStr = strchr(TempStr, ']');
1073 			if (RStr != NULL)
1074 				RStr[0x00] = '\0';
1075 
1076 			if (! stricmp_u(LStr, "General"))
1077 			{
1078 				IniSection = 0x00;
1079 			}
1080 			else
1081 			{
1082 				IniSection = 0xFF;
1083 				for (CurChip = 0x00; CurChip < CHIP_COUNT; CurChip ++)
1084 				{
1085 					if (! stricmp_u(LStr, GetChipName(CurChip)))
1086 					{
1087 						IniSection = 0x80 | CurChip;
1088 						break;
1089 					}
1090 				}
1091 				if (IniSection == 0xFF)
1092 					continue;
1093 			}
1094 		}
1095 		else
1096 		{
1097 			// Line pattern: Option = Value
1098 			TempLng = RStr - TempStr;
1099 			TempStr[TempLng] = '\0';
1100 
1101 			// Prepare Strings (trim the spaces)
1102 			RStr = &TempStr[TempLng - 0x01];
1103 			while(*RStr == ' ')
1104 				*(RStr --) = '\0';
1105 
1106 			RStr = &TempStr[StrLen - 0x01];
1107 			while(*RStr == ' ')
1108 				*(RStr --) = '\0';
1109 			RStr = &TempStr[TempLng + 0x01];
1110 			while(*RStr == ' ')
1111 				RStr ++;
1112 
1113 			switch(IniSection)
1114 			{
1115 			case 0x00:	// General Sction
1116 				if (! stricmp_u(LStr, "SampleRate"))
1117 				{
1118 					SampleRate = strtoul(RStr, NULL, 0);
1119 				}
1120 				else if (! stricmp_u(LStr, "PlaybackRate"))
1121 				{
1122 					VGMPbRate = strtoul(RStr, NULL, 0);
1123 				}
1124 				else if (! stricmp_u(LStr, "DoubleSSGVol"))
1125 				{
1126 					DoubleSSGVol = GetBoolFromStr(RStr);
1127 				}
1128 				else if (! stricmp_u(LStr, "PreferJapTag"))
1129 				{
1130 					PreferJapTag = GetBoolFromStr(RStr);
1131 				}
1132 				else if (! stricmp_u(LStr, "FadeTime"))
1133 				{
1134 					FadeTimeN = strtoul(RStr, NULL, 0);
1135 				}
1136 				else if (! stricmp_u(LStr, "FadeTimePL"))
1137 				{
1138 					FadeTimePL = strtoul(RStr, NULL, 0);
1139 				}
1140 				else if (! stricmp_u(LStr, "JinglePause"))
1141 				{
1142 					PauseTimeJ = strtoul(RStr, NULL, 0);
1143 				}
1144 				else if (! stricmp_u(LStr, "HardStopOld"))
1145 				{
1146 					HardStopOldVGMs = (UINT8)strtoul(RStr, &TempPnt, 0);
1147 					if (TempPnt == RStr)
1148 						HardStopOldVGMs = GetBoolFromStr(RStr) ? 0x01 : 0x00;
1149 				}
1150 				else if (! stricmp_u(LStr, "FadeRAWLogs"))
1151 				{
1152 					FadeRAWLog = GetBoolFromStr(RStr);
1153 				}
1154 				else if (! stricmp_u(LStr, "Volume"))
1155 				{
1156 					VolumeLevel = (float)strtod(RStr, NULL);
1157 				}
1158 				else if (! stricmp_u(LStr, "LogSound"))
1159 				{
1160 					//LogToWave = GetBoolFromStr(RStr);
1161 					LogToWave = (UINT8)strtoul(RStr, NULL, 0);
1162 				}
1163 				else if (! stricmp_u(LStr, "MaxLoops"))
1164 				{
1165 					VGMMaxLoop = strtoul(RStr, NULL, 0);
1166 				}
1167 				else if (! stricmp_u(LStr, "MaxLoopsCMF"))
1168 				{
1169 					CMFMaxLoop = strtoul(RStr, NULL, 0);
1170 				}
1171 				else if (! stricmp_u(LStr, "ResamplingMode"))
1172 				{
1173 					ResampleMode = (UINT8)strtol(RStr, NULL, 0);
1174 				}
1175 				else if (! stricmp_u(LStr, "ChipSmplMode"))
1176 				{
1177 					CHIP_SAMPLING_MODE = (UINT8)strtol(RStr, NULL, 0);
1178 				}
1179 				else if (! stricmp_u(LStr, "ChipSmplRate"))
1180 				{
1181 					CHIP_SAMPLE_RATE = strtol(RStr, NULL, 0);
1182 				}
1183 				else if (! stricmp_u(LStr, "OutputDevice"))
1184 				{
1185 					OutputDevID = (UINT8)strtol(RStr, NULL, 0);
1186 				}
1187 				else if (! stricmp_u(LStr, "AudioBuffers"))
1188 				{
1189 					ForceAudioBuf = (UINT16)strtol(RStr, NULL, 0);
1190 					if (ForceAudioBuf < 0x04)
1191 						ForceAudioBuf = 0x00;
1192 				}
1193 				else if (! stricmp_u(LStr, "SurroundSound"))
1194 				{
1195 					SurroundSound = GetBoolFromStr(RStr);
1196 				}
1197 				else if (! stricmp_u(LStr, "EmulatePause"))
1198 				{
1199 					PauseEmulate = GetBoolFromStr(RStr);
1200 				}
1201 				else if (! stricmp_u(LStr, "ShowStreamCmds"))
1202 				{
1203 					Show95Cmds = (UINT8)strtol(RStr, NULL, 0);
1204 				}
1205 				else if (! stricmp_u(LStr, "FMPort"))
1206 				{
1207 					FMPort = (UINT16)strtoul(RStr, NULL, 16);
1208 				}
1209 				else if (! stricmp_u(LStr, "FMForce"))
1210 				{
1211 					FMForce = GetBoolFromStr(RStr);
1212 				}
1213 				else if (! stricmp_u(LStr, "FMVolume"))
1214 				{
1215 					FMVol = (float)strtod(RStr, NULL);
1216 				}
1217 				else if (! stricmp_u(LStr, "FMOPL2Pan"))
1218 				{
1219 					FMOPL2Pan = GetBoolFromStr(RStr);
1220 				}
1221 				/*else if (! stricmp_u(LStr, "AccurateFM"))
1222 				{
1223 					FMAccurate = GetBoolFromStr(RStr);
1224 				}*/
1225 				else if (! stricmp_u(LStr, "FMSoftStop"))
1226 				{
1227 					FMBreakFade = GetBoolFromStr(RStr);
1228 				}
1229 				break;
1230 			case 0x80:	// SN76496
1231 			case 0x81:	// YM2413
1232 			case 0x82:	// YM2612
1233 			case 0x83:	// YM2151
1234 			case 0x84:	// SegaPCM
1235 			case 0x85:	// RF5C68
1236 			case 0x86:	// YM2203
1237 			case 0x87:	// YM2608
1238 			case 0x88:	// YM2610
1239 			case 0x89:	// YM3812
1240 			case 0x8A:	// YM3526
1241 			case 0x8B:	// Y8950
1242 			case 0x8C:	// YMF262
1243 			case 0x8D:	// YMF278B
1244 			case 0x8E:	// YMF271
1245 			case 0x8F:	// YMZ280B
1246 			case 0x90:	// RF5C164
1247 			case 0x91:	// PWM
1248 			case 0x92:	// AY8910
1249 			case 0x93:	// GameBoy
1250 			case 0x94:	// NES
1251 			case 0x95:	// MultiPCM
1252 			case 0x96:	// UPD7759
1253 			case 0x97:	// OKIM6258
1254 			case 0x98:	// OKIM6295
1255 			case 0x99:	// K051649
1256 			case 0x9A:	// K054539
1257 			case 0x9B:	// HuC6280
1258 			case 0x9C:	// C140
1259 			case 0x9D:	// K053260
1260 			case 0x9E:	// Pokey
1261 			case 0x9F:	// QSound
1262 			case 0xA0:	// SCSP
1263 			case 0xA1:	// WonderSwan
1264 			case 0xA2:	// VSU
1265 			case 0xA3:	// SAA1099
1266 			case 0xA4:	// ES5503
1267 			case 0xA5:	// ES5506
1268 			case 0xA6:	// X1_010
1269 			case 0xA7:	// C352
1270 			case 0xA8:	// GA20
1271 				CurChip = IniSection & 0x7F;
1272 				TempCOpt = (CHIP_OPTS*)&ChipOpts[0x00] + CurChip;
1273 
1274 				if (! stricmp_u(LStr, "Disabled"))
1275 				{
1276 					TempCOpt->Disabled = GetBoolFromStr(RStr);
1277 				}
1278 				else if (! stricmp_u(LStr, "EmulatorType"))
1279 				{
1280 					TempCOpt->EmuCore = (UINT8)strtol(RStr, NULL, 0);
1281 				}
1282 				else if (! stricmp_u(LStr, "MuteMask"))
1283 				{
1284 					if (! CHN_COUNT[CurChip])
1285 						break;	// must use MuteMaskFM and MuteMask???
1286 					TempCOpt->ChnMute1 = strtoul(RStr, NULL, 0);
1287 					if (CHN_MASK_CNT[CurChip] < 0x20)
1288 						TempCOpt->ChnMute1 &= (1 << CHN_MASK_CNT[CurChip]) - 1;
1289 				}
1290 				else if (! strnicmp_u(LStr, "MuteCh", 0x06))
1291 				{
1292 					if (! CHN_COUNT[CurChip])
1293 						break;	// must use MuteFM and Mute???
1294 					CurChn = (UINT8)strtol(LStr + 0x06, &TempPnt, 0);
1295 					if (TempPnt == NULL || *TempPnt)
1296 						break;
1297 					if (CurChn >= CHN_COUNT[CurChip])
1298 						break;
1299 					TempFlag = GetBoolFromStr(RStr);
1300 					TempCOpt->ChnMute1 &= ~(0x01 << CurChn);
1301 					TempCOpt->ChnMute1 |= TempFlag << CurChn;
1302 				}
1303 				else
1304 				{
1305 					switch(CurChip)
1306 					{
1307 					//case 0x00:	// SN76496
1308 					case 0x02:	// YM2612
1309 						if (! stricmp_u(LStr, "MuteDAC"))
1310 						{
1311 							CurChn = 0x06;
1312 							TempFlag = GetBoolFromStr(RStr);
1313 							TempCOpt->ChnMute1 &= ~(0x01 << CurChn);
1314 							TempCOpt->ChnMute1 |= TempFlag << CurChn;
1315 						}
1316 						else if (! stricmp_u(LStr, "DACHighpass"))
1317 						{
1318 							TempFlag = GetBoolFromStr(RStr);
1319 							TempCOpt->SpecialFlags &= ~(0x01 << 0);
1320 							TempCOpt->SpecialFlags |= TempFlag << 0;
1321 						}
1322 						else if (! stricmp_u(LStr, "SSG-EG"))
1323 						{
1324 							TempFlag = GetBoolFromStr(RStr);
1325 							TempCOpt->SpecialFlags &= ~(0x01 << 1);
1326 							TempCOpt->SpecialFlags |= TempFlag << 1;
1327 						}
1328 						else if (! stricmp_u(LStr, "PseudoStereo"))
1329 						{
1330 							TempFlag = GetBoolFromStr(RStr);
1331 							TempCOpt->SpecialFlags &= ~(0x01 << 2);
1332 							TempCOpt->SpecialFlags |= TempFlag << 2;
1333 						}
1334 						else if (! stricmp_u(LStr, "NukedType"))
1335 						{
1336 							TempLng = (UINT32)strtoul(RStr, NULL, 0) & 0x03;
1337 							TempCOpt->SpecialFlags &= ~(0x03 << 3);
1338 							TempCOpt->SpecialFlags |= TempLng << 3;
1339 						}
1340 						break;
1341 					//case 0x03:	// YM2151
1342 					//case 0x04:	// SegaPCM
1343 					//case 0x05:	// RF5C68
1344 					case 0x06:	// YM2203
1345 						if (! stricmp_u(LStr, "DisableAY"))
1346 						{
1347 							TempFlag = GetBoolFromStr(RStr);
1348 							TempCOpt->SpecialFlags &= ~(0x01 << 0);
1349 							TempCOpt->SpecialFlags |= TempFlag << 0;
1350 						}
1351 						break;
1352 					case 0x07:	// YM2608
1353 					case 0x08:	// YM2610
1354 						if (! stricmp_u(LStr, "DisableAY"))
1355 						{
1356 							TempFlag = GetBoolFromStr(RStr);
1357 							TempCOpt->SpecialFlags &= ~(0x01 << 0);
1358 							TempCOpt->SpecialFlags |= TempFlag << 0;
1359 						}
1360 						else if (! stricmp_u(LStr, "MuteMask_FM"))
1361 						{
1362 							TempCOpt->ChnMute1 = strtoul(RStr, NULL, 0);
1363 							TempCOpt->ChnMute1 &= (1 << CHN_MASK_CNT[CurChip]) - 1;
1364 						}
1365 						else if (! stricmp_u(LStr, "MuteMask_PCM"))
1366 						{
1367 							TempCOpt->ChnMute2 = strtoul(RStr, NULL, 0);
1368 							TempCOpt->ChnMute2 &= (1 << (CHN_MASK_CNT[CurChip] + 1)) - 1;
1369 						}
1370 						else if (! strnicmp_u(LStr, "MuteFMCh", 0x08))
1371 						{
1372 							CurChn = (UINT8)strtol(LStr + 0x08, &TempPnt, 0);
1373 							if (TempPnt == NULL || *TempPnt)
1374 								break;
1375 							if (CurChn >= CHN_MASK_CNT[CurChip])
1376 								break;
1377 							TempFlag = GetBoolFromStr(RStr);
1378 							TempCOpt->ChnMute1 &= ~(0x01 << CurChn);
1379 							TempCOpt->ChnMute1 |= TempFlag << CurChn;
1380 						}
1381 						else if (! strnicmp_u(LStr, "MutePCMCh", 0x09))
1382 						{
1383 							CurChn = (UINT8)strtol(LStr + 0x09, &TempPnt, 0);
1384 							if (TempPnt == NULL || *TempPnt)
1385 								break;
1386 							if (CurChn >= CHN_MASK_CNT[CurChip])
1387 								break;
1388 							TempFlag = GetBoolFromStr(RStr);
1389 							TempCOpt->ChnMute2 &= ~(0x01 << CurChn);
1390 							TempCOpt->ChnMute2 |= TempFlag << CurChn;
1391 						}
1392 						else if (! stricmp_u(LStr, "MuteDT"))
1393 						{
1394 							CurChn = 0x06;
1395 							TempFlag = GetBoolFromStr(RStr);
1396 							TempCOpt->ChnMute2 &= ~(0x01 << CurChn);
1397 							TempCOpt->ChnMute2 |= TempFlag << CurChn;
1398 						}
1399 						break;
1400 					case 0x01:	// YM2413
1401 					case 0x09:	// YM3812
1402 					case 0x0A:	// YM3526
1403 					case 0x0B:	// Y8950
1404 					case 0x0C:	// YMF262
1405 						CurChn = 0xFF;
1406 						if (! stricmp_u(LStr, "MuteBD"))
1407 							CurChn = 0x00;
1408 						else if (! stricmp_u(LStr, "MuteSD"))
1409 							CurChn = 0x01;
1410 						else if (! stricmp_u(LStr, "MuteTOM"))
1411 							CurChn = 0x02;
1412 						else if (! stricmp_u(LStr, "MuteTC"))
1413 							CurChn = 0x03;
1414 						else if (! stricmp_u(LStr, "MuteHH"))
1415 							CurChn = 0x04;
1416 						else if (CurChip == 0x0B && ! stricmp_u(LStr, "MuteDT"))
1417 							CurChn = 0x05;
1418 						if (CurChn != 0xFF)
1419 						{
1420 							if (CurChip < 0x0C)
1421 								CurChn += 9;
1422 							else
1423 								CurChn += 18;
1424 							TempFlag = GetBoolFromStr(RStr);
1425 							TempCOpt->ChnMute1 &= ~(0x01 << CurChn);
1426 							TempCOpt->ChnMute1 |= TempFlag << CurChn;
1427 						}
1428 						break;
1429 					case 0x0D:	// YMF278B
1430 						if (! stricmp_u(LStr, "MuteMask_FM"))
1431 						{
1432 							TempCOpt->ChnMute1 = strtoul(RStr, NULL, 0);
1433 							TempCOpt->ChnMute1 &= (1 << CHN_MASK_CNT[CurChip - 0x01]) - 1;
1434 						}
1435 						else if (! stricmp_u(LStr, "MuteMask_WT"))
1436 						{
1437 							TempCOpt->ChnMute2 = strtoul(RStr, NULL, 0);
1438 							TempCOpt->ChnMute2 &= (1 << CHN_MASK_CNT[CurChip]) - 1;
1439 						}
1440 						else if (! strnicmp_u(LStr, "MuteFMCh", 0x08))
1441 						{
1442 							CurChn = (UINT8)strtol(LStr + 0x08, &TempPnt, 0);
1443 							if (TempPnt == NULL || *TempPnt)
1444 								break;
1445 							if (CurChn >= CHN_COUNT[CurChip - 0x01])
1446 								break;
1447 							TempFlag = GetBoolFromStr(RStr);
1448 							TempCOpt->ChnMute1 &= ~(0x01 << CurChn);
1449 							TempCOpt->ChnMute1 |= TempFlag << CurChn;
1450 						}
1451 						else if (! strnicmp_u(LStr, "MuteFM", 0x06))
1452 						{
1453 							CurChn = 0xFF;
1454 							if (! stricmp_u(LStr + 6, "BD"))
1455 								CurChn = 0x00;
1456 							else if (! stricmp_u(LStr + 6, "SD"))
1457 								CurChn = 0x01;
1458 							else if (! stricmp_u(LStr + 6, "TOM"))
1459 								CurChn = 0x02;
1460 							else if (! stricmp_u(LStr + 6, "TC"))
1461 								CurChn = 0x03;
1462 							else if (! stricmp_u(LStr + 6, "HH"))
1463 								CurChn = 0x04;
1464 							if (CurChn != 0xFF)
1465 							{
1466 								CurChn += 18;
1467 								TempFlag = GetBoolFromStr(RStr);
1468 								TempCOpt->ChnMute1 &= ~(0x01 << CurChn);
1469 								TempCOpt->ChnMute1 |= TempFlag << CurChn;
1470 							}
1471 						}
1472 						else if (! strnicmp_u(LStr, "MuteWTCh", 0x08))
1473 						{
1474 							CurChn = (UINT8)strtol(LStr + 0x08, &TempPnt, 0);
1475 							if (TempPnt == NULL || *TempPnt)
1476 								break;
1477 							if (CurChn >= CHN_MASK_CNT[CurChip])
1478 								break;
1479 							TempFlag = GetBoolFromStr(RStr);
1480 							TempCOpt->ChnMute2 &= ~(0x01 << CurChn);
1481 							TempCOpt->ChnMute2 |= TempFlag << CurChn;
1482 						}
1483 						break;
1484 					//case 0x0E:	// YMF271
1485 					//case 0x0F:	// YMZ280B
1486 						/*if (! stricmp_u(LStr, "DisableFix"))
1487 						{
1488 							DISABLE_YMZ_FIX = GetBoolFromStr(RStr);
1489 						}
1490 						break;*/
1491 					//case 0x10:	// RF5C164
1492 					//case 0x11:	// PWM
1493 					//case 0x12:	// AY8910
1494 					case 0x13:	// GameBoy
1495 						if (! stricmp_u(LStr, "BoostWaveChn"))
1496 						{
1497 							TempFlag = GetBoolFromStr(RStr);
1498 							TempCOpt->SpecialFlags &= ~(0x01 << 0);
1499 							TempCOpt->SpecialFlags |= TempFlag << 0;
1500 						}
1501 						break;
1502 					case 0x14:	// NES
1503 						if (! stricmp_u(LStr, "SharedOpts"))
1504 						{
1505 							// 2 bits
1506 							TempLng = (UINT32)strtol(RStr, NULL, 0) & 0x03;
1507 							TempCOpt->SpecialFlags &= ~(0x03 << 0) & 0x7FFF;
1508 							TempCOpt->SpecialFlags |= TempLng << 0;
1509 						}
1510 						else if (! stricmp_u(LStr, "APUOpts"))
1511 						{
1512 							// 2 bits
1513 							TempLng = (UINT32)strtol(RStr, NULL, 0) & 0x03;
1514 							TempCOpt->SpecialFlags &= ~(0x03 << 2) & 0x7FFF;
1515 							TempCOpt->SpecialFlags |= TempLng << 2;
1516 						}
1517 						else if (! stricmp_u(LStr, "DMCOpts"))
1518 						{
1519 							// 8 bits (6 bits used)
1520 							TempLng = (UINT32)strtol(RStr, NULL, 0) & 0xFF;
1521 							TempCOpt->SpecialFlags &= ~(0xFF << 4) & 0x7FFF;
1522 							TempCOpt->SpecialFlags |= TempLng << 4;
1523 						}
1524 						else if (! stricmp_u(LStr, "FDSOpts"))
1525 						{
1526 							// 1 bit
1527 							TempLng = (UINT32)strtol(RStr, NULL, 0) & 0x01;
1528 							TempCOpt->SpecialFlags &= ~(0x01 << 12) & 0x7FFF;
1529 							TempCOpt->SpecialFlags |= TempLng << 12;
1530 						}
1531 						break;
1532 					case 0x17:	// OKIM6258
1533 						if (! stricmp_u(LStr, "Enable10Bit"))
1534 						{
1535 							TempFlag = GetBoolFromStr(RStr);
1536 							TempCOpt->SpecialFlags &= ~(0x01 << 0);
1537 							TempCOpt->SpecialFlags |= TempFlag << 0;
1538 						}
1539 						break;
1540 					case 0x20:	// SCSP
1541 						if (! stricmp_u(LStr, "BypassDSP"))
1542 						{
1543 							TempFlag = GetBoolFromStr(RStr);
1544 							TempCOpt->SpecialFlags &= ~(0x01 << 0);
1545 							TempCOpt->SpecialFlags |= TempFlag << 0;
1546 						}
1547 						break;
1548 					case 0x27:	// C352
1549 						if (! stricmp_u(LStr, "DisableRear"))
1550 						{
1551 							TempFlag = GetBoolFromStr(RStr);
1552 							TempCOpt->SpecialFlags &= ~(0x01 << 0);
1553 							TempCOpt->SpecialFlags |= TempFlag << 0;
1554 						}
1555 						break;
1556 					}
1557 				}
1558 				break;
1559 			case 0xFF:	// Dummy Section
1560 				break;
1561 			}
1562 		}
1563 	}
1564 
1565 	TempCOpt = (CHIP_OPTS*)&ChipOpts[0x00];
1566 	TempCOpt2 = (CHIP_OPTS*)&ChipOpts[0x01];
1567 	for (CurChip = 0x00; CurChip < CHIP_COUNT; CurChip ++, TempCOpt ++, TempCOpt2 ++)
1568 	{
1569 		TempCOpt2->Disabled = TempCOpt->Disabled;
1570 		TempCOpt2->EmuCore = TempCOpt->EmuCore;
1571 		TempCOpt2->SpecialFlags = TempCOpt->SpecialFlags;
1572 		TempCOpt2->ChnMute1 = TempCOpt->ChnMute1;
1573 		TempCOpt2->ChnMute2 = TempCOpt->ChnMute2;
1574 		TempCOpt2->ChnMute3 = TempCOpt->ChnMute3;
1575 	}
1576 
1577 	fclose(hFile);
1578 
1579 #ifdef WIN32
1580 	WinNT_Check();
1581 #endif
1582 	if (CHIP_SAMPLE_RATE <= 0)
1583 		CHIP_SAMPLE_RATE = SampleRate;
1584 
1585 	return;
1586 }
1587 
GetBoolFromStr(const char * TextStr)1588 static bool GetBoolFromStr(const char* TextStr)
1589 {
1590 	if (! stricmp_u(TextStr, "True"))
1591 		return true;
1592 	else if (! stricmp_u(TextStr, "False"))
1593 		return false;
1594 	else
1595 		return strtol(TextStr, NULL, 0) ? true : false;
1596 }
1597 
1598 #if defined(XMAS_EXTRA) || defined(WS_DEMO)
XMas_Extra(char * FileName,bool Mode)1599 static bool XMas_Extra(char* FileName, bool Mode)
1600 {
1601 	char* FileTitle;
1602 	const UINT8* XMasData;
1603 	UINT32 XMasSize;
1604 	FILE* hFile;
1605 
1606 	if (! Mode)
1607 	{	// Prepare Mode
1608 		FileTitle = NULL;
1609 		XMasData = NULL;
1610 #ifdef XMAS_EXTRA
1611 		if (! stricmp_u(FileName, "WEWISH")
1612 		{
1613 			FileTitle = "WEWISH.CMF";
1614 			XMasSize = sizeof(WEWISH_CMF);
1615 			XMasData = WEWISH_CMF;
1616 		}
1617 		else if (! stricmp_u(FileName, "tim7")
1618 		{
1619 			FileTitle = "lem_tim7.vgz";
1620 			XMasSize = sizeof(TIM7_VGZ);
1621 			XMasData = TIM7_VGZ;
1622 		}
1623 		else if (! stricmp_u(FileName, "jingleb")
1624 		{
1625 			FileTitle = "lxmas_jb.dro";
1626 			XMasSize = sizeof(JB_DRO);
1627 			XMasData = JB_DRO;
1628 		}
1629 		else if (! stricmp_u(FileName, "rudolph")
1630 		{
1631 			FileTitle = "rudolph.dro";
1632 			XMasSize = sizeof(RODOLPH_DRO);
1633 			XMasData = RODOLPH_DRO;
1634 		}
1635 		else if (! stricmp_u(FileName, "clyde"))
1636 		{
1637 			FileTitle = "clyde1_1.dro";
1638 			XMasSize = sizeof(clyde1_1_dro);
1639 			XMasData = clyde1_1_dro;
1640 		}
1641 #elif defined(WS_DEMO)
1642 		if (! stricmp_u(FileName, "wswan"))
1643 		{
1644 			FileTitle = "SWJ-SQRC01_1C.vgz";
1645 			XMasSize = sizeof(FF1ws_1C);
1646 			XMasData = FF1ws_1C;
1647 		}
1648 #endif
1649 
1650 		if (XMasData)
1651 		{
1652 #ifdef WIN32
1653 			GetEnvironmentVariable("Temp", FileName, MAX_PATH);
1654 #else
1655 			strcpy(FileName, "/tmp");
1656 #endif
1657 			strcat(FileName, DIR_STR);
1658 			if (FileTitle == NULL)
1659 				FileTitle = "XMas.dat";
1660 			strcat(FileName, FileTitle);
1661 
1662 			hFile = fopen(FileName, "wb");
1663 			if (hFile == NULL)
1664 			{
1665 				FileName[0x00] = '\0';
1666 				printerr("Critical XMas-Error!\n");
1667 				return false;
1668 			}
1669 			fwrite(XMasData, 0x01, XMasSize, hFile);
1670 			fclose(hFile);
1671 		}
1672 		else
1673 		{
1674 			FileName = NULL;
1675 			return false;
1676 		}
1677 	}
1678 	else
1679 	{	// Unprepare Mode
1680 		if (! remove(FileName))
1681 			return false;
1682 		// btw: it's intentional that the user can grab the file from the temp-folder
1683 	}
1684 
1685 	return true;
1686 }
1687 #endif
1688 
1689 #ifndef WIN32
1690 static void ConvertCP1252toUTF8(char** DstStr, const char* SrcStr)
1691 {
1692 	const UINT16 CONV_TBL[0x20] =
1693 	{	0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,		// 80-87
1694 		0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x017D, 0x0000,		// 88-8F
1695 		0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,		// 90-97
1696 		0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0000, 0x017E, 0x0178};	// 98-9F
1697 	UINT32 StrLen;
1698 	UINT16 UnicodeChr;
1699 	char* DstPtr;
1700 	const unsigned char* SrcPtr;
1701 
1702 	SrcPtr = (const unsigned char*)SrcStr;
1703 	StrLen = 0x00;
1704 	while(*SrcPtr != '\0')
1705 	{
1706 		if (*SrcPtr < 0x80 || *SrcPtr >= 0xA0)
1707 			UnicodeChr = *SrcPtr;
1708 		else
1709 			UnicodeChr = CONV_TBL[*SrcPtr - 0x80];
1710 		if (UnicodeChr < 0x0080)
1711 			StrLen ++;
1712 		else if (UnicodeChr < 0x0800)
1713 			StrLen += 2;
1714 		else
1715 			StrLen += 3;
1716 		SrcPtr ++;
1717 	}
1718 
1719 	*DstStr = (char*)malloc((StrLen + 0x01) * sizeof(char));
1720 	SrcPtr = (const unsigned char*)SrcStr;
1721 	DstPtr = *DstStr;
1722 	while(*SrcPtr != '\0')
1723 	{
1724 		if (*SrcPtr < 0x80 || *SrcPtr >= 0xA0)
1725 			UnicodeChr = (unsigned char)*SrcPtr;
1726 		else
1727 			UnicodeChr = CONV_TBL[*SrcPtr - 0x80];
1728 		if (UnicodeChr < 0x0080)
1729 		{
1730 			*DstPtr = UnicodeChr & 0xFF;
1731 			DstPtr ++;
1732 		}
1733 		else if (UnicodeChr < 0x0800)
1734 		{
1735 			DstPtr[0x00] = 0xC0 | ((UnicodeChr >> 6) & 0x1F);
1736 			DstPtr[0x01] = 0x80 | ((UnicodeChr >> 0) & 0x3F);
1737 			DstPtr += 0x02;
1738 		}
1739 		else
1740 		{
1741 			DstPtr[0x00] = 0xE0 | ((UnicodeChr >> 12) & 0x0F);
1742 			DstPtr[0x01] = 0x80 | ((UnicodeChr >>  6) & 0x3F);
1743 			DstPtr[0x02] = 0x80 | ((UnicodeChr >>  0) & 0x3F);
1744 			DstPtr += 0x03;
1745 		}
1746 		SrcPtr ++;
1747 	}
1748 	*DstPtr = '\0';
1749 
1750 	return;
1751 }
1752 #endif
1753 
1754 static bool OpenPlayListFile(const char* FileName)
1755 {
1756 	const char M3UV2_HEAD[] = "#EXTM3U";
1757 	const char M3UV2_META[] = "#EXTINF:";
1758 	const UINT8 UTF8_SIG[] = {0xEF, 0xBB, 0xBF};
1759 	UINT32 METASTR_LEN;
1760 	size_t RetVal;
1761 
1762 	FILE* hFile;
1763 	UINT32 LineNo;
1764 	bool IsV2Fmt;
1765 	UINT32 PLAlloc;
1766 	char TempStr[0x1000];	// 4096 chars should be enough
1767 	char* RetStr;
1768 	bool IsUTF8;
1769 
1770 	hFile = fopen(FileName, "rt");
1771 	if (hFile == NULL)
1772 		return false;
1773 
1774 	RetVal = fread(TempStr, 0x01, 0x03, hFile);
1775 	if (RetVal >= 0x03)
1776 		IsUTF8 = ! memcmp(TempStr, UTF8_SIG, 0x03);
1777 	else
1778 		IsUTF8 = false;
1779 
1780 	rewind(hFile);
1781 
1782 	PLAlloc = 0x0100;
1783 	PLFileCount = 0x00;
1784 	LineNo = 0x00;
1785 	IsV2Fmt = false;
1786 	METASTR_LEN = strlen(M3UV2_META);
1787 	PlayListFile = (char**)malloc(PLAlloc * sizeof(char*));
1788 	while(! feof(hFile))
1789 	{
1790 		RetStr = fgets(TempStr, 0x1000, hFile);
1791 		if (RetStr == NULL)
1792 			break;
1793 		//RetStr = strchr(TempStr, 0x0D);
1794 		//if (RetStr)
1795 		//	*RetStr = 0x00;	// remove NewLine-Character
1796 		RetStr = TempStr + strlen(TempStr) - 0x01;
1797 		while(RetStr >= TempStr && *RetStr < 0x20)
1798 		{
1799 			*RetStr = '\0';	// remove NewLine-Characters
1800 			RetStr --;
1801 		}
1802 		if (! strlen(TempStr))
1803 			continue;
1804 
1805 		if (! LineNo)
1806 		{
1807 			if (! strcmp(TempStr, M3UV2_HEAD))
1808 			{
1809 				IsV2Fmt = true;
1810 				LineNo ++;
1811 				continue;
1812 			}
1813 		}
1814 		if (IsV2Fmt)
1815 		{
1816 			if (! strncmp(TempStr, M3UV2_META, METASTR_LEN))
1817 			{
1818 				// Ignore Metadata of m3u Version 2
1819 				LineNo ++;
1820 				continue;
1821 			}
1822 		}
1823 
1824 		if (PLFileCount >= PLAlloc)
1825 		{
1826 			PLAlloc += 0x0100;
1827 			PlayListFile = (char**)realloc(PlayListFile, PLAlloc * sizeof(char*));
1828 		}
1829 
1830 		// TODO:
1831 		//	- supprt UTF-8 m3us under Windows
1832 		//	- force IsUTF8 via Commandline
1833 #ifdef WIN32
1834 		// Windows uses the 1252 Codepage by default
1835 		PlayListFile[PLFileCount] = (char*)malloc((strlen(TempStr) + 0x01) * sizeof(char));
1836 		strcpy(PlayListFile[PLFileCount], TempStr);
1837 #else
1838 		if (! IsUTF8)
1839 		{
1840 			// Most recent Linux versions use UTF-8, so I need to convert all strings.
1841 			ConvertCP1252toUTF8(&PlayListFile[PLFileCount], TempStr);
1842 		}
1843 		else
1844 		{
1845 			PlayListFile[PLFileCount] = (char*)malloc((strlen(TempStr) + 0x01) * sizeof(char));
1846 			strcpy(PlayListFile[PLFileCount], TempStr);
1847 		}
1848 #endif
1849 		StandardizeDirSeparators(PlayListFile[PLFileCount]);
1850 		PLFileCount ++;
1851 		LineNo ++;
1852 	}
1853 
1854 	fclose(hFile);
1855 
1856 	RetStr = GetLastDirSeparator(FileName);
1857 	if (RetStr != NULL)
1858 	{
1859 		RetStr ++;
1860 		strncpy(PLFileBase, FileName, RetStr - FileName);
1861 		PLFileBase[RetStr - FileName] = '\0';
1862 		StandardizeDirSeparators(PLFileBase);
1863 	}
1864 	else
1865 	{
1866 		strcpy(PLFileBase, "");
1867 	}
1868 
1869 	return true;
1870 }
1871 
1872 static bool OpenMusicFile(const char* FileName)
1873 {
1874 	if (OpenVGMFile(FileName))
1875 		return true;
1876 	else if (OpenOtherFile(FileName))
1877 		return true;
1878 
1879 	return false;
1880 }
1881 
1882 static void wprintc(const wchar_t* format, ...)
1883 {
1884 	va_list arg_list;
1885 	int RetVal;
1886 	UINT32 BufSize;
1887 	wchar_t* printbuf;
1888 #ifdef WIN32
1889 	UINT32 StrLen;
1890 	char* oembuf;
1891 	DWORD CPMode;
1892 #endif
1893 
1894 	BufSize = 0x00;
1895 	printbuf = NULL;
1896 	do
1897 	{
1898 		BufSize += 0x100;
1899 		printbuf = (wchar_t*)realloc(printbuf, BufSize * sizeof(wchar_t));
1900 
1901 		// Note: On Linux every vprintf call needs its own set of va_start/va_end commands.
1902 		//       Under Windows (with VC6) one only one block for all calls works, too.
1903 		va_start(arg_list, format);
1904 		RetVal = _vsnwprintf(printbuf, BufSize - 0x01, format, arg_list);
1905 		va_end(arg_list);
1906 	} while(RetVal == -1 && BufSize < 0x1000);
1907 #ifdef WIN32
1908 	StrLen = wcslen(printbuf);
1909 
1910 	// This is the only way to print Unicode stuff to the Windows console.
1911 	// No, wprintf doesn't work.
1912 	RetVal = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), printbuf, StrLen, &CPMode, NULL);
1913 	if (! RetVal)	// call failed (e.g. with ERROR_CALL_NOT_IMPLEMENTED on Win95)
1914 	{
1915 		// fallback to printf with OEM codepage
1916 		oembuf = (char*)malloc(BufSize);
1917 		/*if (GetConsoleOutputCP() == GetOEMCP())
1918 			CPMode = CP_OEMCP;
1919 		else
1920 			CPMode = CP_ACP;*/
1921 		CPMode = GetConsoleOutputCP();
1922 		WideCharToMultiByte(CPMode, 0x00, printbuf, StrLen + 1, oembuf, BufSize, NULL, NULL);
1923 
1924 		printf("%s", oembuf);
1925 		free(oembuf);
1926 	}
1927 #else
1928 	// on Linux, it's easy
1929 	printf("%ls", printbuf);
1930 #endif
1931 
1932 	free(printbuf);
1933 
1934 	return;
1935 }
1936 
1937 static void PrintChipStr(UINT8 ChipID, UINT8 SubType, UINT32 Clock)
1938 {
1939 	if (! Clock)
1940 		return;
1941 
1942 	if (ChipID == 0x00 && (Clock & 0x80000000))
1943 		Clock &= ~0x40000000;
1944 	if (Clock & 0x80000000)
1945 	{
1946 		Clock &= ~0x80000000;
1947 		ChipID |= 0x80;
1948 	}
1949 
1950 	if (Clock & 0x40000000)
1951 		printf("2x");
1952 	printf("%s, ", GetAccurateChipName(ChipID, SubType));
1953 
1954 	return;
1955 }
1956 
1957 static const wchar_t* GetTagStrEJ(const wchar_t* EngTag, const wchar_t* JapTag)
1958 {
1959 	const wchar_t* RetTag;
1960 
1961 	if (EngTag == NULL || ! wcslen(EngTag))
1962 	{
1963 		RetTag = JapTag;
1964 	}
1965 	else if (JapTag == NULL || ! wcslen(JapTag))
1966 	{
1967 		RetTag = EngTag;
1968 	}
1969 	else
1970 	{
1971 		if (! PreferJapTag)
1972 			RetTag = EngTag;
1973 		else
1974 			RetTag = JapTag;
1975 	}
1976 
1977 	if (RetTag == NULL)
1978 		return L"";
1979 	else
1980 		return RetTag;
1981 }
1982 
1983 static void ShowVGMTag(void)
1984 {
1985 	const wchar_t* TitleTag;
1986 	const wchar_t* GameTag;
1987 	const wchar_t* AuthorTag;
1988 	const wchar_t* SystemTag;
1989 	UINT8 CurChip;
1990 	UINT32 ChpClk;
1991 	UINT8 ChpType;
1992 	INT16 VolMod;
1993 #ifdef SET_CONSOLE_TITLE
1994 	wchar_t TitleStr[0x80];
1995 	UINT32 StrLen;
1996 #endif
1997 
1998 	TitleTag = GetTagStrEJ(VGMTag.strTrackNameE, VGMTag.strTrackNameJ);
1999 	GameTag = GetTagStrEJ(VGMTag.strGameNameE, VGMTag.strGameNameJ);
2000 	AuthorTag = GetTagStrEJ(VGMTag.strAuthorNameE, VGMTag.strAuthorNameJ);
2001 	SystemTag = GetTagStrEJ(VGMTag.strSystemNameE, VGMTag.strSystemNameJ);
2002 
2003 #ifdef SET_CONSOLE_TITLE
2004 	// --- Show "Song (Game) - VGM Player" as Console Title ---
2005 	if (! wcslen(TitleTag))
2006 	{
2007 		char* TempPtr1;
2008 		char* TempPtr2;
2009 
2010 		TempPtr1 = strrchr(VgmFileName, '\\');
2011 		TempPtr2 = strrchr(VgmFileName, '/');
2012 		if (TempPtr1 < TempPtr2)
2013 			TempPtr1 = TempPtr2;
2014 		if (TempPtr1 == NULL)
2015 			TempPtr1 = VgmFileName;
2016 		else
2017 			TempPtr1 ++;
2018 		//strncpy(TitleStr, TempPtr1, 0x70);
2019 		mbstowcs(TitleStr, TempPtr1, 0x7F);
2020 		TitleStr[0x70] = '\0';
2021 	}
2022 	else
2023 	{
2024 #if (defined(_MSC_VER) && _MSC_VER < 1400) || defined(OLD_SWPRINTF)
2025 		swprintf(TitleStr, L"%.*ls", 0x70, TitleTag);
2026 #else
2027 		swprintf(TitleStr, 0x80, L"%.*ls", 0x70, TitleTag);
2028 #endif
2029 	}
2030 	StrLen = wcslen(TitleStr);
2031 
2032 	if (wcslen(GameTag) && StrLen < 0x6C)
2033 	{
2034 #if (defined(_MSC_VER) && _MSC_VER < 1400) || defined(OLD_SWPRINTF)
2035 		swprintf(TitleStr + StrLen, L" (%.*ls)", 0x70 - 3 - StrLen, GameTag);
2036 #else
2037 		swprintf(TitleStr + StrLen, 0x80, L" (%.*ls)", 0x70 - 3 - StrLen, GameTag);
2038 #endif
2039 		StrLen = wcslen(TitleStr);
2040 	}
2041 
2042 	wcscat(TitleStr, L" - " APP_NAME_L);
2043 #ifdef WIN32
2044 	SetConsoleTitleW(TitleStr);			// Set Windows Console Title
2045 #else
2046 	printf("\x1B]0;%ls\x07", TitleStr);	// Set xterm/rxvt Terminal Title
2047 #endif
2048 #endif
2049 
2050 	// --- Display Tag Data ---
2051 	if (VGMHead.bytVolumeModifier <= VOLUME_MODIF_WRAP)
2052 		VolMod = VGMHead.bytVolumeModifier;
2053 	else if (VGMHead.bytVolumeModifier == (VOLUME_MODIF_WRAP + 0x01))
2054 		VolMod = VOLUME_MODIF_WRAP - 0x100;
2055 	else
2056 		VolMod = VGMHead.bytVolumeModifier - 0x100;
2057 
2058 	wprintc(L"Track Title:\t%ls\n", TitleTag);
2059 	wprintc(L"Game Name:\t%ls\n", GameTag);
2060 	wprintc(L"System:\t\t%ls\n", SystemTag);
2061 	wprintc(L"Composer:\t%ls\n", AuthorTag);
2062 	wprintc(L"Release:\t%ls\n", VGMTag.strReleaseDate);
2063 	printf("Version:\t%X.%02X\t", VGMHead.lngVersion >> 8, VGMHead.lngVersion & 0xFF);
2064 	printf("  Gain:%5.2f\t", pow(2.0, VolMod / (double)0x20));
2065 	printf("Loop: ");
2066 	if (VGMHead.lngLoopOffset)
2067 	{
2068 		UINT32 PbRateMul;
2069 		UINT32 PbRateDiv;
2070 		UINT32 PbSamples;
2071 
2072 		// calculate samples for correct display with changed playback rate
2073 		if (! VGMPbRate || ! VGMHead.lngRate)
2074 		{
2075 			PbRateMul = 1;
2076 			PbRateDiv = 1;
2077 		}
2078 		else
2079 		{
2080 			PbRateMul = VGMHead.lngRate;
2081 			PbRateDiv = VGMPbRate;
2082 		}
2083 		PbSamples = (UINT32)((UINT64)VGMHead.lngLoopSamples * PbRateMul / PbRateDiv);
2084 
2085 		printf("Yes (");
2086 		PrintMinSec(PbSamples, VGMSampleRate);
2087 		printf(")\n");
2088 	}
2089 	else
2090 	{
2091 		printf("No\n");
2092 	}
2093 	wprintc(L"VGM by:\t\t%ls\n", VGMTag.strCreator);
2094 	wprintc(L"Notes:\t\t%ls\n", VGMTag.strNotes);
2095 	printf("\n");
2096 
2097 	printf("Used chips:\t");
2098 	for (CurChip = 0x00; CurChip < CHIP_COUNT; CurChip ++)
2099 	{
2100 		ChpClk = GetChipClock(&VGMHead, CurChip, &ChpType);
2101 		if (ChpClk && GetChipClock(&VGMHead, 0x80 | CurChip, NULL))
2102 			ChpClk |= 0x40000000;
2103 		PrintChipStr(CurChip, ChpType, ChpClk);
2104 	}
2105 	printf("\b\b \n");
2106 	printf("\n");
2107 
2108 	return;
2109 }
2110 
2111 
2112 #define LOG_SAMPLES	(SampleRate / 5)
2113 static void PlayVGM_UI(void)
2114 {
2115 	INT32 VGMPbSmplCount;
2116 	INT32 PlaySmpl;
2117 	UINT8 KeyCode;
2118 	UINT32 VGMPlaySt;
2119 	UINT32 VGMPlayEnd;
2120 	char WavFileName[MAX_PATH];
2121 	char* TempStr;
2122 	WAVE_16BS* TempBuf;
2123 	UINT8 RetVal;
2124 	UINT32 TempLng;
2125 	bool PosPrint;
2126 	bool LastUninit;
2127 	bool QuitPlay;
2128 	UINT32 PlayTimeEnd;
2129 
2130 	printf("Initializing ...\r");
2131 
2132 	PlayVGM();
2133 
2134 	/*switch(LogToWave)
2135 	{
2136 	case 0x00:
2137 		break;
2138 	case 0x01:
2139 		// Currently there's no record for Hardware FM
2140 		PlayingMode = 0x00;	// Impossible to log at full speed AND use FMPort
2141 		break;
2142 	case 0x02:
2143 		if (PlayingMode == 0x01)
2144 			LogToWave = 0x00;	// Output and log sound (FM isn't logged)
2145 		break;
2146 	}*/
2147 	switch(PlayingMode)
2148 	{
2149 	case 0x00:
2150 		AUDIOBUFFERU = 10;
2151 		break;
2152 	case 0x01:
2153 		AUDIOBUFFERU = 0;	// no AudioBuffers needed
2154 		break;
2155 	case 0x02:
2156 		AUDIOBUFFERU = 5;	// try to sync Hardware/Software Emulator as well as possible
2157 		break;
2158 	}
2159 	if (AUDIOBUFFERU < NEED_LARGE_AUDIOBUFS)
2160 		AUDIOBUFFERU = NEED_LARGE_AUDIOBUFS;
2161 	if (ForceAudioBuf && AUDIOBUFFERU)
2162 		AUDIOBUFFERU = ForceAudioBuf;
2163 
2164 	switch(FileMode)
2165 	{
2166 	case 0x00:	// VGM
2167 		// RAW Log: no loop, no Creator, System Name set
2168 		IsRAWLog = (! VGMHead.lngLoopOffset && ! wcslen(VGMTag.strCreator) &&
2169 					(wcslen(VGMTag.strSystemNameE) || wcslen(VGMTag.strSystemNameJ)));
2170 		break;
2171 	case 0x01:	// CMF
2172 		IsRAWLog = false;
2173 		break;
2174 	case 0x02:	// DRO
2175 		IsRAWLog = true;
2176 		break;
2177 	}
2178 	if (! VGMHead.lngTotalSamples)
2179 		IsRAWLog = false;
2180 
2181 #ifndef WIN32
2182 	changemode(true);
2183 #endif
2184 
2185 	switch(PlayingMode)
2186 	{
2187 	case 0x00:
2188 	case 0x02:
2189 		if (LogToWave)
2190 		{
2191 			strcpy(WavFileName, VgmFileName);
2192 			TempStr = GetFileExtension(WavFileName);
2193 			if (TempStr == NULL)
2194 				TempStr = WavFileName + strlen(WavFileName);
2195 			else
2196 				TempStr --;
2197 			strcpy(TempStr, ".wav");
2198 
2199 			strcpy(SoundLogFile, WavFileName);
2200 		}
2201 		//FullBufFill = ! LogToWave;
2202 
2203 		switch(LogToWave)
2204 		{
2205 		case 0x00:
2206 		case 0x02:
2207 			SoundLogging(LogToWave ? true : false);
2208 			if (FirstInit || ! StreamStarted)
2209 			{
2210 				// support smooth transistions between songs
2211 				RetVal = StartStream(OutputDevID);
2212 				if (RetVal)
2213 				{
2214 					printf("Error openning Sound Device!\n");
2215 					return;
2216 				}
2217 				StreamStarted = true;
2218 			}
2219 			PauseStream(PausePlay);
2220 			break;
2221 		case 0x01:
2222 			TempBuf = (WAVE_16BS*)malloc(SAMPLESIZE * LOG_SAMPLES);
2223 			if (TempBuf == NULL)
2224 			{
2225 				printf("Allocation Error!\n");
2226 				return;
2227 			}
2228 
2229 			StartStream(0xFF);
2230 			RetVal = SaveFile(0x00000000, NULL);
2231 			if (RetVal)
2232 			{
2233 				printf("Can't open %s!\n", SoundLogFile);
2234 				return;
2235 			}
2236 			break;
2237 		}
2238 		break;
2239 	case 0x01:
2240 		// PlayVGM() does it all
2241 		//FullBufFill = true;
2242 		break;
2243 	}
2244 	FirstInit = false;
2245 
2246 	VGMPlaySt = VGMPos;
2247 	if (VGMHead.lngGD3Offset)
2248 		VGMPlayEnd = VGMHead.lngGD3Offset;
2249 	else
2250 		VGMPlayEnd = VGMHead.lngEOFOffset;
2251 	VGMPlayEnd -= VGMPlaySt;
2252 	if (! FileMode)
2253 		VGMPlayEnd --;	// EOF Command doesn't count
2254 	PosPrint = true;
2255 
2256 	PlayTimeEnd = 0;
2257 	QuitPlay = false;
2258 	while(! QuitPlay)
2259 	{
2260 		if(sigint)
2261 		{
2262 			QuitPlay = true;
2263 			NextPLCmd = 0xFF;
2264 		}
2265 
2266 		if (! PausePlay || PosPrint)
2267 		{
2268 			PosPrint = false;
2269 
2270 			VGMPbSmplCount = SampleVGM2Playback(VGMHead.lngTotalSamples);
2271 			PlaySmpl = VGMPos - VGMPlaySt;
2272 #ifdef WIN32
2273 			printf("Playing %01.2f%%\t", 100.0 * PlaySmpl / VGMPlayEnd);
2274 #else
2275 			// \t doesn't display correctly under Linux
2276 			// but \b causes flickering under Windows
2277 			printf("Playing %01.2f%%   \b\b\b\t", 100.0 * PlaySmpl / VGMPlayEnd);
2278 #endif
2279 			if (LogToWave != 0x01)
2280 			{
2281 				PlaySmpl = (BlocksSent - BlocksPlayed) * SMPL_P_BUFFER;
2282 				PlaySmpl = VGMSmplPlayed - PlaySmpl;
2283 			}
2284 			else
2285 			{
2286 				PlaySmpl = VGMSmplPlayed;
2287 			}
2288 			if (! VGMCurLoop)
2289 			{
2290 				if (PlaySmpl < 0)
2291 					PlaySmpl = 0;
2292 			}
2293 			else
2294 			{
2295 				while(PlaySmpl < SampleVGM2Playback(VGMHead.lngTotalSamples -
2296 					VGMHead.lngLoopSamples))
2297 					PlaySmpl += SampleVGM2Playback(VGMHead.lngLoopSamples);
2298 			}
2299 			//if (PlaySmpl > VGMPbSmplCount)
2300 			//	PlaySmpl = VGMPbSmplCount;
2301 			PrintMinSec(PlaySmpl, SampleRate);
2302 			printf(" / ");
2303 			PrintMinSec(VGMPbSmplCount, SampleRate);
2304 			printf(" seconds");
2305 			if (Show95Cmds && Last95Max != 0xFFFF)
2306 			{
2307 				if (Show95Cmds == 0x01)
2308 					printf("  %02X / %02hX", 1 + Last95Drum, Last95Max);
2309 				else if (Show95Cmds == 0x02)
2310 					printf("  %02X / %02hX at %5u Hz", 1 + Last95Drum, Last95Max, Last95Freq);
2311 				else if (Show95Cmds == 0x03)
2312 					printf("  %02X / %02hX at %4.1f KHz", 1 + Last95Drum, Last95Max,
2313 							Last95Freq / 1000.0);
2314 			}
2315 			//printf("  %u / %u", multipcm_get_channels(0, NULL), 28);
2316 			printf("\r");
2317 #ifndef WIN32
2318 			fflush(stdout);
2319 #endif
2320 
2321 			if (LogToWave == 0x01 && ! PausePlay)
2322 			{
2323 				TempLng = FillBuffer(TempBuf, LOG_SAMPLES);
2324 				if (TempLng)
2325 					SaveFile(TempLng, TempBuf);
2326 				if (EndPlay)
2327 					break;
2328 			}
2329 			else
2330 			{
2331 #ifdef WIN32
2332 				Sleep(50);
2333 #endif
2334 			}
2335 		}
2336 		else
2337 		{
2338 #ifdef WIN32
2339 			Sleep(1);
2340 #endif
2341 		}
2342 #ifndef WIN32
2343 		if (! PausePlay && PlayingMode != 0x01)
2344 			WaveOutLinuxCallBack();
2345 		else
2346 			Sleep(100);
2347 #endif
2348 
2349 		if (EndPlay)
2350 		{
2351 			if (! PlayTimeEnd)
2352 			{
2353 				PlayTimeEnd = PlayingTime;
2354 				// quitting now terminates the program, so I need some special
2355 				// checks to make sure that the rest of the audio buffer is played
2356 				if (! PLFileCount || CurPLFile >= PLFileCount - 0x01)
2357 				{
2358 					if (FileMode == 0x01)
2359 						PlayTimeEnd += SampleRate << 1;	// Add 2 secs
2360 					PlayTimeEnd += AUDIOBUFFERU * SMPL_P_BUFFER;
2361 				}
2362 			}
2363 
2364 			if (PlayingTime >= PlayTimeEnd)
2365 				QuitPlay = true;
2366 		}
2367 		if (_kbhit())
2368 		{
2369 			KeyCode = _getch();
2370 			if (KeyCode < 0x80)
2371 				KeyCode = toupper(KeyCode);
2372 			switch(KeyCode)
2373 			{
2374 #ifndef WIN32
2375 			case 0x1B:	// Special Key
2376 				KeyCode = _getch();
2377 				if (KeyCode == 0x1B || KeyCode == 0x00)
2378 				{
2379 					// ESC Key pressed
2380 					QuitPlay = true;
2381 					NextPLCmd = 0xFF;
2382 					break;
2383 				}
2384 				switch(KeyCode)
2385 				{
2386 				case 0x5B:
2387 					// Cursor-Key Table
2388 					//	Key		KeyCode
2389 					//	Up		41
2390 					//	Down	42
2391 					//	Left	44
2392 					//	Right	43
2393 					// Cursor only: CursorKey
2394 					// Ctrl: 0x31 + 0x3B + 0x35 + CursorKey
2395 					// Alt: 0x31 + 0x3B + 0x33 + CursorKey
2396 
2397 					// Page-Keys: PageKey + 0x7E
2398 					//	PageUp		35
2399 					//	PageDown	36
2400 					KeyCode = _getch();	// Get 2nd Key
2401 					// Convert Cursor Key Code from Linux to Windows
2402 					switch(KeyCode)
2403 					{
2404 					case 0x31:	// Ctrl or Alt key
2405 						KeyCode = _getch();
2406 						if (KeyCode == 0x3B)
2407 						{
2408 							KeyCode = _getch();
2409 							if (KeyCode == 0x35)
2410 							{
2411 								KeyCode = _getch();
2412 								switch(KeyCode)
2413 								{
2414 								case 0x41:
2415 									KeyCode = 0x8D;
2416 									break;
2417 								case 0x42:
2418 									KeyCode = 0x91;
2419 									break;
2420 								case 0x43:
2421 									KeyCode = 0x74;
2422 									break;
2423 								case 0x44:
2424 									KeyCode = 0x73;
2425 									break;
2426 								default:
2427 									KeyCode = 0x00;
2428 									break;
2429 								}
2430 							}
2431 						}
2432 
2433 						if ((KeyCode & 0xF0) == 0x30)
2434 							KeyCode = 0x00;
2435 						break;
2436 					case 0x35:
2437 						KeyCode = 0x49;
2438 						_getch();
2439 						break;
2440 					case 0x36:
2441 						KeyCode = 0x51;
2442 						_getch();
2443 						break;
2444 					case 0x41:
2445 						KeyCode = 0x48;
2446 						break;
2447 					case 0x42:
2448 						KeyCode = 0x50;
2449 						break;
2450 					case 0x43:
2451 						KeyCode = 0x4D;
2452 						break;
2453 					case 0x44:
2454 						KeyCode = 0x4B;
2455 						break;
2456 					default:
2457 						KeyCode = 0x00;
2458 						break;
2459 					}
2460 				}
2461 				// At this point I have Windows-style keys.
2462 #else	//#ifdef WIN32
2463 			case 0xE0:	// Special Key
2464 				// Cursor-Key Table
2465 				// Shift + Cursor results in the usual value for the Cursor Key
2466 				// Alt + Cursor results in 0x00 + (0x50 + CursorKey) (0x00 instead of 0xE0)
2467 				//	Key		None	Ctrl
2468 				//	Up		48		8D
2469 				//	Down	50		91
2470 				//	Left	4B		73
2471 				//	Right	4D		74
2472 				KeyCode = _getch();	// Get 2nd Key
2473 #endif
2474 				switch(KeyCode)
2475 				{
2476 				case 0x4B:	// Cursor Left
2477 					PlaySmpl = -5;
2478 					break;
2479 				case 0x4D:	// Cursor Right
2480 					PlaySmpl = 5;
2481 					break;
2482 				case 0x73:	// Ctrl + Cursor Left
2483 					PlaySmpl = -60;
2484 					break;
2485 				case 0x74:	// Ctrl + Cursor Right
2486 					PlaySmpl = 60;
2487 					break;
2488 				case 0x49:	// Page Up
2489 					if (PLFileCount && /*! NextPLCmd &&*/ CurPLFile)
2490 					{
2491 						NextPLCmd = 0x01;
2492 						QuitPlay = true;
2493 					}
2494 					PlaySmpl = 0;
2495 					break;
2496 				case 0x51:	// Page Down
2497 					if (PLFileCount && /*! NextPLCmd &&*/ CurPLFile < PLFileCount - 0x01)
2498 					{
2499 						NextPLCmd = 0x00;
2500 						QuitPlay = true;
2501 					}
2502 					PlaySmpl = 0;
2503 					break;
2504 				default:
2505 					PlaySmpl = 0;
2506 					break;
2507 				}
2508 				if (PlaySmpl)
2509 				{
2510 					SeekVGM(true, PlaySmpl * SampleRate);
2511 					PosPrint = true;
2512 				}
2513 				break;
2514 #ifdef WIN32
2515 			case 0x1B:	// ESC
2516 #endif
2517 			case 'Q':
2518 				QuitPlay = true;
2519 				NextPLCmd = 0xFF;
2520 				break;
2521 			case ' ':
2522 				PauseVGM(! PausePlay);
2523 				PosPrint = true;
2524 				break;
2525 			case 'F':	// Fading
2526 				FadeTime = FadeTimeN;
2527 				FadePlay = true;
2528 				break;
2529 			case 'R':	// Restart
2530 				RestartVGM();
2531 				PosPrint = true;
2532 				break;
2533 			case 'B':	// Previous file (Back)
2534 				if (PLFileCount && /*! NextPLCmd &&*/ CurPLFile)
2535 				{
2536 					NextPLCmd = 0x01;
2537 					QuitPlay = true;
2538 				}
2539 				break;
2540 			case 'N':	// Next file
2541 				if (PLFileCount && /*! NextPLCmd &&*/ CurPLFile < PLFileCount - 0x01)
2542 				{
2543 					NextPLCmd = 0x00;
2544 					QuitPlay = true;
2545 				}
2546 				break;
2547 			}
2548 		}
2549 
2550 		/*if (! PauseThread && FadePlay && (! FadeTime || MasterVol == 0.0f))
2551 		{
2552 			QuitPlay = true;
2553 		}*/
2554 		if (FadeRAWLog && IsRAWLog && ! PausePlay && ! FadePlay && FadeTimeN)
2555 		{
2556 			PlaySmpl = (INT32)VGMHead.lngTotalSamples -
2557 						FadeTimeN * VGMSampleRate / 1500;
2558 			if (VGMSmplPos >= PlaySmpl)
2559 			{
2560 				FadeTime = FadeTimeN;
2561 				FadePlay = true;	// (FadeTime / 1500) ends at 33%
2562 			}
2563 		}
2564 	}
2565 	ThreadNoWait = false;
2566 
2567 	// Last Uninit: ESC pressed, no playlist, last file in playlist
2568 	LastUninit = (NextPLCmd & 0x80) || ! PLFileCount ||
2569 				(NextPLCmd == 0x00 && CurPLFile >= PLFileCount - 0x01);
2570 	switch(PlayingMode)
2571 	{
2572 	case 0x00:
2573 		switch(LogToWave)
2574 		{
2575 		case 0x00:
2576 		case 0x02:
2577 			if (LastUninit)
2578 			{
2579 				StopStream();
2580 				StreamStarted = false;
2581 			}
2582 			else
2583 			{
2584 				if (ThreadPauseEnable)
2585 				{
2586 					ThreadPauseConfrm = false;
2587 					PauseThread = true;
2588 					while(! ThreadPauseConfrm)
2589 						Sleep(1);	// Wait until the Thread is finished
2590 				}
2591 				else
2592 				{
2593 					PauseThread = true;
2594 				}
2595 				if (LogToWave)
2596 					SaveFile(0xFFFFFFFF, NULL);
2597 			}
2598 			break;
2599 		case 0x01:
2600 			SaveFile(0xFFFFFFFF, NULL);
2601 			break;
2602 		}
2603 		break;
2604 	case 0x01:
2605 		if (StreamStarted)
2606 		{
2607 			StopStream();
2608 			StreamStarted = false;
2609 		}
2610 		break;
2611 	case 0x02:
2612 		if (LastUninit)
2613 		{
2614 			StopStream();
2615 			StreamStarted = false;
2616 #ifdef MIXER_MUTING
2617 #ifdef WIN32
2618 			mixerClose(hmixer);
2619 #else
2620 			close(hmixer);
2621 #endif
2622 #endif
2623 		}
2624 		else
2625 		{
2626 			if (ThreadPauseEnable)
2627 			{
2628 				ThreadPauseConfrm = false;
2629 				PauseThread = true;
2630 				while(! ThreadPauseConfrm)
2631 					Sleep(1);	// Wait until the Thread is finished
2632 				PauseStream(true);
2633 			}
2634 			else
2635 			{
2636 				PauseThread = true;
2637 			}
2638 		}
2639 		break;
2640 	}
2641 #ifndef WIN32
2642 	changemode(false);
2643 #endif
2644 
2645 	StopVGM();
2646 
2647 	printf("\nPlaying finished.\n");
2648 
2649 	return;
2650 }
2651 
2652 INLINE INT8 sign(double Value)
2653 {
2654 	if (Value > 0.0)
2655 		return 1;
2656 	else if (Value < 0.0)
2657 		return -1;
2658 	else
2659 		return 0;
2660 }
2661 
2662 INLINE long int Round(double Value)
2663 {
2664 	// Alternative:	(fabs(Value) + 0.5) * sign(Value);
2665 	return (long int)(Value + 0.5 * sign(Value));
2666 }
2667 
2668 INLINE double RoundSpecial(double Value, double RoundTo)
2669 {
2670 	return (long int)(Value / RoundTo + 0.5 * sign(Value)) * RoundTo;
2671 }
2672 
2673 static void PrintMinSec(UINT32 SamplePos, UINT32 SmplRate)
2674 {
2675 	float TimeSec;
2676 	UINT16 TimeMin;
2677 	UINT16 TimeHours;
2678 
2679 	TimeSec = (float)RoundSpecial(SamplePos / (double)SmplRate, 0.01);
2680 	//TimeSec = SamplePos / (float)SmplRate;
2681 	TimeMin = (UINT16)TimeSec / 60;
2682 	TimeSec -= TimeMin * 60;
2683 	if (! PrintMSHours)
2684 	{
2685 		printf("%02hu:%05.2f", TimeMin, TimeSec);
2686 	}
2687 	else
2688 	{
2689 		TimeHours = TimeMin / 60;
2690 		TimeMin %= 60;
2691 		printf("%hu:%02hu:%05.2f", TimeHours, TimeMin, TimeSec);
2692 	}
2693 
2694 	return;
2695 }
2696