1 // for finding memory leaks in debug mode with Visual Studio
2 #if defined _DEBUG && defined _MSC_VER
3 #include <crtdbg.h>
4 #endif
5 
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <stdint.h>
9 #include <stdbool.h>
10 #include <string.h>
11 #include <ctype.h>
12 #ifndef _WIN32
13 #include <unistd.h>
14 #include <limits.h>
15 #endif
16 #include "pt2_header.h"
17 #include "pt2_helpers.h"
18 #include "pt2_config.h"
19 #include "pt2_tables.h"
20 #include "pt2_audio.h"
21 #include "pt2_diskop.h"
22 #include "pt2_textout.h"
23 #include "pt2_sampler.h"
24 
25 #ifndef _WIN32
26 static char oldCwd[PATH_MAX];
27 #endif
28 
29 config_t config; // globalized
30 
31 static bool loadProTrackerDotIni(FILE *f);
32 static FILE *openPTDotConfig(void);
33 static bool loadPTDotConfig(FILE *f);
34 static bool loadColorsDotIni(void);
35 
loadConfig(void)36 void loadConfig(void)
37 {
38 	bool proTrackerDotIniFound, ptDotConfigFound;
39 #ifndef _WIN32
40 	bool colorsDotIniFound;
41 #endif
42 	FILE *f;
43 
44 	// set default config values first
45 	config.noDownsampleOnSmpLoad = false;
46 	config.disableE8xEffect = false;
47 	config.fullScreenStretch = false;
48 	config.pattDots = false;
49 	config.waveformCenterLine = true;
50 	config.filterModel = FILTERMODEL_A1200;
51 	config.soundFrequency = 48000;
52 	config.rememberPlayMode = false;
53 	config.stereoSeparation = 20;
54 	config.videoScaleFactor = 2;
55 	config.realVuMeters = false;
56 	config.modDot = false;
57 	config.accidental = 0; // sharp
58 	config.quantizeValue = 1;
59 	config.transDel = false;
60 	config.blankZeroFlag = false;
61 	config.compoMode = false;
62 	config.soundBufferSize = 1024;
63 	config.autoCloseDiskOp = true;
64 	config.vsyncOff = false;
65 	config.hwMouse = true;
66 	config.startInFullscreen = false;
67 	config.pixelFilter = PIXELFILTER_NEAREST;
68 	config.integerScaling = true;
69 	config.audioInputFrequency = 44100;
70 
71 #ifndef _WIN32
72 	getcwd(oldCwd, PATH_MAX);
73 #endif
74 
75 	// load protracker.ini
76 	proTrackerDotIniFound = false;
77 
78 #ifdef _WIN32
79 	f = fopen("protracker.ini", "r");
80 	if (f != NULL)
81 		proTrackerDotIniFound = true;
82 #else
83 	// check in program directory
84 	f = fopen("protracker.ini", "r");
85 	if (f != NULL)
86 		proTrackerDotIniFound = true;
87 
88 	// check in ~/.protracker/
89 	if (!proTrackerDotIniFound && changePathToHome() && chdir(".protracker") == 0)
90 	{
91 		f = fopen("protracker.ini", "r");
92 		if (f != NULL)
93 			proTrackerDotIniFound = true;
94 	}
95 
96 	chdir(oldCwd);
97 #endif
98 
99 	if (proTrackerDotIniFound)
100 		loadProTrackerDotIni(f);
101 
102 	editor.oldTempo = editor.initialTempo;
103 
104 	// load PT.Config (if available)
105 	ptDotConfigFound = false;
106 
107 #ifdef _WIN32
108 	f = openPTDotConfig();
109 	if (f != NULL)
110 		ptDotConfigFound = true;
111 #else
112 	// check in program directory
113 	f = openPTDotConfig();
114 	if (f != NULL)
115 		ptDotConfigFound = true;
116 
117 	// check in ~/.protracker/
118 	if (!ptDotConfigFound && changePathToHome() && chdir(".protracker") == 0)
119 	{
120 		f = openPTDotConfig();
121 		if (f != NULL)
122 			ptDotConfigFound = true;
123 	}
124 
125 	chdir(oldCwd);
126 #endif
127 
128 	if (ptDotConfigFound)
129 		loadPTDotConfig(f);
130 
131 	if (proTrackerDotIniFound || ptDotConfigFound)
132 		editor.configFound = true;
133 
134 	// load colors.ini (if available)
135 #ifdef _WIN32
136 	loadColorsDotIni();
137 #else
138 	// check in program directory
139 	colorsDotIniFound = loadColorsDotIni();
140 
141 	// check in ~/.protracker/
142 	if (!colorsDotIniFound && changePathToHome() && chdir(".protracker") == 0)
143 		loadColorsDotIni();
144 #endif
145 
146 #ifndef _WIN32
147 	chdir(oldCwd);
148 #endif
149 
150 	// use palette for generating sample data mark (invert) table
151 	createSampleMarkTable();
152 }
153 
loadProTrackerDotIni(FILE * f)154 static bool loadProTrackerDotIni(FILE *f)
155 {
156 	char *configBuffer, *configLine;
157 	uint32_t configFileSize, lineLen, i;
158 
159 	fseek(f, 0, SEEK_END);
160 	configFileSize = ftell(f);
161 	rewind(f);
162 
163 	configBuffer = (char *)malloc(configFileSize + 1);
164 	if (configBuffer == NULL)
165 	{
166 		fclose(f);
167 		showErrorMsgBox("Couldn't parse protracker.ini: Out of memory!");
168 		return false;
169 	}
170 
171 	fread(configBuffer, 1, configFileSize, f);
172 	configBuffer[configFileSize] = '\0';
173 	fclose(f);
174 
175 	configLine = strtok(configBuffer, "\n");
176 	while (configLine != NULL)
177 	{
178 		lineLen = (uint32_t)strlen(configLine);
179 
180 		// remove CR in CRLF linefeed (if present)
181 		if (lineLen > 1)
182 		{
183 			if (configLine[lineLen-1] == '\r')
184 			{
185 				configLine[lineLen-1] = '\0';
186 				lineLen--;
187 			}
188 		}
189 
190 		// COMMENT OR CATEGORY
191 		if (*configLine == ';' || *configLine == '[')
192 		{
193 			configLine = strtok(NULL, "\n");
194 			continue;
195 		}
196 
197 		// NO_DWNSMP_ON_SMP_LOAD (no dialog for 2x downsample after >22kHz sample load)
198 		else if (!_strnicmp(configLine, "NO_DWNSMP_ON_SMP_LOAD=", 22))
199 		{
200 			if (!_strnicmp(&configLine[22], "TRUE", 4)) config.noDownsampleOnSmpLoad = true;
201 			else if (!_strnicmp(&configLine[22], "FALSE", 5)) config.noDownsampleOnSmpLoad = false;
202 		}
203 
204 		// DISABLE_E8X (Karplus-Strong command)
205 		else if (!_strnicmp(configLine, "DISABLE_E8X=", 12))
206 		{
207 			     if (!_strnicmp(&configLine[12], "TRUE",  4)) config.disableE8xEffect = true;
208 			else if (!_strnicmp(&configLine[12], "FALSE", 5)) config.disableE8xEffect = false;
209 		}
210 
211 		// HWMOUSE
212 		else if (!_strnicmp(configLine, "HWMOUSE=", 8))
213 		{
214 			     if (!_strnicmp(&configLine[8], "TRUE",  4)) config.hwMouse = true;
215 			else if (!_strnicmp(&configLine[8], "FALSE", 5)) config.hwMouse = false;
216 		}
217 
218 		// VSYNCOFF
219 		else if (!_strnicmp(configLine, "VSYNCOFF=", 9))
220 		{
221 			     if (!_strnicmp(&configLine[9], "TRUE",  4)) config.vsyncOff = true;
222 			else if (!_strnicmp(&configLine[9], "FALSE", 5)) config.vsyncOff = false;
223 		}
224 
225 		// INTEGERSCALING
226 		else if (!_strnicmp(configLine, "INTEGERSCALING=", 15))
227 		{
228 			     if (!_strnicmp(&configLine[15], "TRUE",  4)) config.integerScaling = true;
229 			else if (!_strnicmp(&configLine[15], "FALSE", 5)) config.integerScaling = false;
230 		}
231 
232 		// FULLSCREENSTRETCH
233 		else if (!_strnicmp(configLine, "FULLSCREENSTRETCH=", 18))
234 		{
235 			     if (!_strnicmp(&configLine[18], "TRUE",  4)) config.fullScreenStretch = true;
236 			else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.fullScreenStretch = false;
237 		}
238 
239 		// HIDEDISKOPDATES
240 		else if (!_strnicmp(configLine, "HIDEDISKOPDATES=", 16))
241 		{
242 			     if (!_strnicmp(&configLine[16], "TRUE",  4)) config.hideDiskOpDates = true;
243 			else if (!_strnicmp(&configLine[16], "FALSE", 5)) config.hideDiskOpDates = false;
244 		}
245 
246 		// AUTOCLOSEDISKOP
247 		else if (!_strnicmp(configLine, "AUTOCLOSEDISKOP=", 16))
248 		{
249 			     if (!_strnicmp(&configLine[16], "TRUE",  4)) config.autoCloseDiskOp = true;
250 			else if (!_strnicmp(&configLine[16], "FALSE", 5)) config.autoCloseDiskOp = false;
251 		}
252 
253 		// FULLSCREEN
254 		else if (!_strnicmp(configLine, "FULLSCREEN=", 11))
255 		{
256 			     if (!_strnicmp(&configLine[11], "TRUE",  4)) config.startInFullscreen = true;
257 			else if (!_strnicmp(&configLine[11], "FALSE", 5)) config.startInFullscreen = false;
258 		}
259 
260 		// PIXELFILTER
261 		else if (!_strnicmp(configLine, "PIXELFILTER=", 12))
262 		{
263 			     if (!_strnicmp(&configLine[12], "NEAREST", 7)) config.pixelFilter = PIXELFILTER_NEAREST;
264 			else if (!_strnicmp(&configLine[12], "LINEAR", 6)) config.pixelFilter = PIXELFILTER_LINEAR;
265 			else if (!_strnicmp(&configLine[12], "BEST", 4)) config.pixelFilter = PIXELFILTER_BEST;
266 		}
267 
268 		// COMPOMODE
269 		else if (!_strnicmp(configLine, "COMPOMODE=", 10))
270 		{
271 			     if (!_strnicmp(&configLine[10], "TRUE",  4)) config.compoMode = true;
272 			else if (!_strnicmp(&configLine[10], "FALSE", 5)) config.compoMode = false;
273 		}
274 
275 		// PATTDOTS
276 		else if (!_strnicmp(configLine, "PATTDOTS=", 9))
277 		{
278 			     if (!_strnicmp(&configLine[9], "TRUE",  4)) config.pattDots = true;
279 			else if (!_strnicmp(&configLine[9], "FALSE", 5)) config.pattDots = false;
280 		}
281 
282 		// BLANKZERO
283 		else if (!_strnicmp(configLine, "BLANKZERO=", 10))
284 		{
285 			     if (!_strnicmp(&configLine[10], "TRUE",  4)) config.blankZeroFlag = true;
286 			else if (!_strnicmp(&configLine[10], "FALSE", 5)) config.blankZeroFlag = false;
287 		}
288 
289 		// REALVUMETERS
290 		else if (!_strnicmp(configLine, "REALVUMETERS=", 13))
291 		{
292 			     if (!_strnicmp(&configLine[13], "TRUE",  4)) config.realVuMeters = true;
293 			else if (!_strnicmp(&configLine[13], "FALSE", 5)) config.realVuMeters = false;
294 		}
295 
296 		// ACCIDENTAL
297 		else if (!_strnicmp(configLine, "ACCIDENTAL=", 11))
298 		{
299 			     if (!_strnicmp(&configLine[11], "SHARP", 4)) config.accidental = 0;
300 			else if (!_strnicmp(&configLine[11], "FLAT",  5)) config.accidental = 1;
301 		}
302 
303 		// QUANTIZE
304 		else if (!_strnicmp(configLine, "QUANTIZE=", 9))
305 		{
306 			if (configLine[9] != '\0')
307 			{
308 				const int32_t num = atoi(&configLine[9]);
309 				config.quantizeValue = (int16_t)(CLAMP(num, 0, 63));
310 			}
311 		}
312 
313 		// TRANSDEL
314 		else if (!_strnicmp(configLine, "TRANSDEL=", 9))
315 		{
316 			     if (!_strnicmp(&configLine[9], "TRUE",  4)) config.transDel = true;
317 			else if (!_strnicmp(&configLine[9], "FALSE", 5)) config.transDel = false;
318 		}
319 
320 		// DOTTEDCENTER
321 		else if (!_strnicmp(configLine, "DOTTEDCENTER=", 13))
322 		{
323 			     if (!_strnicmp(&configLine[13], "TRUE",  4)) config.waveformCenterLine = true;
324 			else if (!_strnicmp(&configLine[13], "FALSE", 5)) config.waveformCenterLine = false;
325 		}
326 
327 		// MODDOT
328 		else if (!_strnicmp(configLine, "MODDOT=", 7))
329 		{
330 			     if (!_strnicmp(&configLine[7], "TRUE",  4)) config.modDot = true;
331 			else if (!_strnicmp(&configLine[7], "FALSE", 5)) config.modDot = false;
332 		}
333 
334 		// SCALE3X (deprecated)
335 		else if (!_strnicmp(configLine, "SCALE3X=", 8))
336 		{
337 			     if (!_strnicmp(&configLine[8], "TRUE",  4)) config.videoScaleFactor = 3;
338 			else if (!_strnicmp(&configLine[8], "FALSE", 5)) config.videoScaleFactor = 2;
339 		}
340 
341 		// VIDEOSCALE
342 		else if (!_strnicmp(configLine, "VIDEOSCALE=", 11))
343 		{
344 			if (lineLen >= 13 && configLine[12] == 'X' && isdigit(configLine[11]))
345 				config.videoScaleFactor = configLine[11] - '0';
346 		}
347 
348 		// REMEMBERPLAYMODE
349 		else if (!_strnicmp(configLine, "REMEMBERPLAYMODE=", 17))
350 		{
351 			     if (!_strnicmp(&configLine[17], "TRUE",  4)) config.rememberPlayMode = true;
352 			else if (!_strnicmp(&configLine[17], "FALSE", 5)) config.rememberPlayMode = false;
353 		}
354 
355 		// DEFAULTDIR
356 		else if (!_strnicmp(configLine, "DEFAULTDIR=", 11))
357 		{
358 			if (lineLen > 11)
359 			{
360 				i = 11;
361 				while (configLine[i] == ' ') i++; // remove spaces before string (if present)
362 				while (configLine[lineLen-1] == ' ') lineLen--; // remove spaces after string (if present)
363 
364 				lineLen -= i;
365 				if (lineLen > 0)
366 					strncpy(config.defModulesDir, &configLine[i], (lineLen > PATH_MAX) ? PATH_MAX : lineLen);
367 			}
368 		}
369 
370 		// DEFAULTSMPDIR
371 		else if (!_strnicmp(configLine, "DEFAULTSMPDIR=", 14))
372 		{
373 			if (lineLen > 14)
374 			{
375 				i = 14;
376 				while (configLine[i] == ' ') i++; // remove spaces before string (if present)
377 				while (configLine[lineLen-1] == ' ') lineLen--; // remove spaces after string (if present)
378 
379 				lineLen -= i;
380 				if (lineLen > 0)
381 					strncpy(config.defSamplesDir, &configLine[i], (lineLen > PATH_MAX) ? PATH_MAX : lineLen);
382 			}
383 		}
384 
385 		// FILTERMODEL
386 		else if (!_strnicmp(configLine, "FILTERMODEL=", 12))
387 		{
388 			     if (!_strnicmp(&configLine[12], "A500",  4)) config.filterModel = FILTERMODEL_A500;
389 			else if (!_strnicmp(&configLine[12], "A1200", 5)) config.filterModel = FILTERMODEL_A1200;
390 		}
391 
392 		// A500LOWPASSFILTER (deprecated, same as A4000LOWPASSFILTER)
393 		else if (!_strnicmp(configLine, "A500LOWPASSFILTER=", 18))
394 		{
395 			     if (!_strnicmp(&configLine[18], "TRUE",  4)) config.filterModel = FILTERMODEL_A500;
396 			else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.filterModel = FILTERMODEL_A1200;
397 		}
398 
399 		// A4000LOWPASSFILTER (deprecated)
400 		else if (!_strnicmp(configLine, "A4000LOWPASSFILTER=", 19))
401 		{
402 			     if (!_strnicmp(&configLine[19], "TRUE",  4)) config.filterModel = FILTERMODEL_A500;
403 			else if (!_strnicmp(&configLine[19], "FALSE", 5)) config.filterModel = FILTERMODEL_A1200;
404 		}
405 
406 		// SAMPLINGFREQ
407 		else if (!_strnicmp(configLine, "SAMPLINGFREQ=", 13))
408 		{
409 			if (configLine[10] != '\0')
410 			{
411 				const int32_t num = atoi(&configLine[13]);
412 				config.audioInputFrequency = CLAMP(num, 44100, 192000);
413 			}
414 		}
415 
416 		// FREQUENCY
417 		else if (!_strnicmp(configLine, "FREQUENCY=", 10))
418 		{
419 			if (configLine[10] != '\0')
420 			{
421 				const int32_t num = atoi(&configLine[10]);
422 				config.soundFrequency = CLAMP(num, 44100, 192000);
423 			}
424 		}
425 
426 		// BUFFERSIZE
427 		else if (!_strnicmp(configLine, "BUFFERSIZE=", 11))
428 		{
429 			if (configLine[11] != '\0')
430 			{
431 				const int32_t num = atoi(&configLine[11]);
432 				config.soundBufferSize = CLAMP(num, 128, 8192);
433 			}
434 		}
435 
436 		// STEREOSEPARATION
437 		else if (!_strnicmp(configLine, "STEREOSEPARATION=", 17))
438 		{
439 			if (configLine[17] != '\0')
440 			{
441 				const int32_t num = atoi(&configLine[17]);
442 				config.stereoSeparation = (int8_t)(CLAMP(num, 0, 100));
443 			}
444 		}
445 
446 		configLine = strtok(NULL, "\n");
447 	}
448 
449 	free(configBuffer);
450 	return true;
451 }
452 
openPTDotConfig(void)453 static FILE *openPTDotConfig(void)
454 {
455 	char tmpFilename[16];
456 	uint8_t i;
457 	FILE *f;
458 
459 	f = fopen("PT.Config", "rb"); // PT didn't read PT.Config with no number, but let's support it
460 	if (f == NULL)
461 	{
462 		for (i = 0; i < 100; i++)
463 		{
464 			sprintf(tmpFilename, "PT.Config-%02d", i);
465 			f = fopen(tmpFilename, "rb");
466 			if (f != NULL)
467 				break;
468 		}
469 
470 		if (i == 100)
471 			return NULL;
472 	}
473 
474 	return f;
475 }
476 
loadPTDotConfig(FILE * f)477 static bool loadPTDotConfig(FILE *f)
478 {
479 	char cfgString[24];
480 	uint8_t tmp8;
481 	uint16_t tmp16;
482 	int32_t i;
483 	uint32_t configFileSize;
484 
485 	// get filesize
486 	fseek(f, 0, SEEK_END);
487 	configFileSize = ftell(f);
488 	if (configFileSize != 1024)
489 	{
490 		// not a valid PT.Config file
491 		fclose(f);
492 		return false;
493 	}
494 	rewind(f);
495 
496 	// check if file is a PT.Config file
497 	fread(cfgString, 1, 24, f);
498 
499 	/* force version string to 2.3 so that we'll accept all versions.
500 	** AFAIK we're only loading values that were present since 1.0,
501 	** so it should be safe. */
502 	cfgString[2] = '2';
503 	cfgString[4] = '3';
504 
505 	if (strncmp(cfgString, "PT2.3 Configuration File", 24) != 0)
506 	{
507 		fclose(f);
508 		return false;
509 	}
510 
511 	// Palette
512 	fseek(f, 154, SEEK_SET);
513 	for (i = 0; i < 8; i++)
514 	{
515 		fread(&tmp16, 2, 1, f); // stored as Big-Endian
516 		tmp16 = SWAP16(tmp16);
517 		video.palette[i] = RGB12_to_RGB24(tmp16);
518 	}
519 
520 	// Transpose Delete (delete out of range notes on transposing)
521 	fseek(f, 174, SEEK_SET);
522 	fread(&tmp8, 1, 1, f);
523 	config.transDel = tmp8 ? true : false;
524 	config.transDel = config.transDel;
525 
526 	// Note style (sharps/flats)
527 	fseek(f, 200, SEEK_SET);
528 	fread(&tmp8, 1, 1, f);
529 	config.accidental = tmp8 ? 1 : 0;
530 	config.accidental = config.accidental;
531 
532 	// Multi Mode Next
533 	fseek(f, 462, SEEK_SET);
534 	fread(&editor.multiModeNext[0], 1, 1, f);
535 	fread(&editor.multiModeNext[1], 1, 1, f);
536 	fread(&editor.multiModeNext[2], 1, 1, f);
537 	fread(&editor.multiModeNext[3], 1, 1, f);
538 
539 	// Effect Macros
540 	fseek(f, 466, SEEK_SET);
541 	for (i = 0; i < 10; i++)
542 	{
543 		fread(&tmp16, 2, 1, f); // stored as Big-Endian
544 		tmp16 = SWAP16(tmp16);
545 		editor.effectMacros[i] = tmp16;
546 	}
547 
548 	// Timing Mode (CIA/VBLANK)
549 	fseek(f, 487, SEEK_SET);
550 	fread(&tmp8, 1, 1, f);
551 	editor.timingMode = tmp8 ? TEMPO_MODE_CIA : TEMPO_MODE_VBLANK;
552 
553 	// Blank Zeroes
554 	fseek(f, 490, SEEK_SET);
555 	fread(&tmp8, 1, 1, f);
556 	config.blankZeroFlag = tmp8 ? true : false;
557 	config.blankZeroFlag = config.blankZeroFlag;
558 
559 	// Initial Tempo (don't load if timing is set to VBLANK)
560 	if (editor.timingMode == TEMPO_MODE_CIA)
561 	{
562 		fseek(f, 497, SEEK_SET);
563 		fread(&tmp8, 1, 1, f);
564 		if (tmp8 < 32) tmp8 = 32;
565 		editor.initialTempo = tmp8;
566 		editor.oldTempo = tmp8;
567 	}
568 
569 	// Tuning Tone Note
570 	fseek(f, 501, SEEK_SET);
571 	fread(&tmp8, 1, 1, f);
572 	if (tmp8 > 35) tmp8 = 35;
573 	editor.tuningNote = tmp8;
574 
575 	// Tuning Tone Volume
576 	fseek(f, 503, SEEK_SET);
577 	fread(&tmp8, 1, 1, f);
578 	if (tmp8 > 64) tmp8 = 64;
579 	editor.tuningVol = tmp8;
580 
581 	// Initial Speed
582 	fseek(f, 545, SEEK_SET);
583 	fread(&tmp8, 1, 1, f);
584 	if (editor.timingMode == TEMPO_MODE_VBLANK)
585 	{
586 		editor.initialSpeed = tmp8;
587 	}
588 	else
589 	{
590 		if (tmp8 > 0x20) tmp8 = 0x20;
591 		editor.initialSpeed = tmp8;
592 	}
593 
594 	// VU-Meter Colors
595 	fseek(f, 546, SEEK_SET);
596 	for (i = 0; i < 48; i++)
597 	{
598 		fread(&vuMeterColors[i], 2, 1, f); // stored as Big-Endian
599 		vuMeterColors[i] = SWAP16(vuMeterColors[i]);
600 	}
601 
602 	// Spectrum Analyzer Colors
603 	fseek(f, 642, SEEK_SET);
604 	for (i = 0; i < 36; i++)
605 	{
606 		fread(&analyzerColors[i], 2, 1, f); // stored as Big-Endian
607 		analyzerColors[i] = SWAP16(analyzerColors[i]);
608 	}
609 
610 	fclose(f);
611 	return true;
612 }
613 
hex2int(char ch)614 static uint8_t hex2int(char ch)
615 {
616 	ch = (char)toupper(ch);
617 
618 	     if (ch >= 'A' && ch <= 'F') return 10 + (ch - 'A');
619 	else if (ch >= '0' && ch <= '9') return ch - '0';
620 
621 	return 0; // not a hex
622 }
623 
loadColorsDotIni(void)624 static bool loadColorsDotIni(void)
625 {
626 	char *configBuffer, *configLine;
627 	uint16_t color;
628 	uint32_t line, fileSize, lineLen;
629 	FILE *f;
630 
631 	f = fopen("colors.ini", "r");
632 	if (f == NULL)
633 		return false;
634 
635 	// get filesize
636 	fseek(f, 0, SEEK_END);
637 	fileSize = ftell(f);
638 	rewind(f);
639 
640 	configBuffer = (char *)malloc(fileSize + 1);
641 	if (configBuffer == NULL)
642 	{
643 		fclose(f);
644 		showErrorMsgBox("Couldn't parse colors.ini: Out of memory!");
645 		return false;
646 	}
647 
648 	fread(configBuffer, 1, fileSize, f);
649 	configBuffer[fileSize] = '\0';
650 	fclose(f);
651 
652 	// do parsing
653 	configLine = strtok(configBuffer, "\n");
654 	while (configLine != NULL)
655 	{
656 		lineLen = (uint32_t)strlen(configLine);
657 
658 		// read palette
659 		if (lineLen >= (sizeof ("[Palette]")-1))
660 		{
661 			if (!_strnicmp("[Palette]", configLine, sizeof ("[Palette]")-1))
662 			{
663 				configLine = strtok(NULL, "\n");
664 
665 				line = 0;
666 				while (configLine != NULL && line < 8)
667 				{
668 					color = (hex2int(configLine[0]) << 8) | (hex2int(configLine[1]) << 4) | hex2int(configLine[2]);
669 					color &= 0xFFF;
670 					video.palette[line] = RGB12_to_RGB24(color);
671 
672 					configLine = strtok(NULL, "\n");
673 					line++;
674 				}
675 			}
676 
677 			if (configLine == NULL)
678 				break;
679 
680 			lineLen = (uint32_t)strlen(configLine);
681 		}
682 
683 		// read VU-meter colors
684 		if (lineLen >= sizeof ("[VU-meter]")-1)
685 		{
686 			if (!_strnicmp("[VU-meter]", configLine, sizeof ("[VU-meter]")-1))
687 			{
688 				configLine = strtok(NULL, "\n");
689 
690 				line = 0;
691 				while (configLine != NULL && line < 48)
692 				{
693 					color = (hex2int(configLine[0]) << 8) | (hex2int(configLine[1]) << 4) | hex2int(configLine[2]);
694 					vuMeterColors[line] = color & 0xFFF;
695 
696 					configLine = strtok(NULL, "\n");
697 					line++;
698 				}
699 			}
700 
701 			if (configLine == NULL)
702 				break;
703 
704 			lineLen = (uint32_t)strlen(configLine);
705 		}
706 
707 		// read spectrum analyzer colors
708 		if (lineLen >= sizeof ("[SpectrumAnalyzer]")-1)
709 		{
710 			if (!_strnicmp("[SpectrumAnalyzer]", configLine, sizeof ("[SpectrumAnalyzer]")-1))
711 			{
712 				configLine = strtok(NULL, "\n");
713 
714 				line = 0;
715 				while (configLine != NULL && line < 36)
716 				{
717 					color = (hex2int(configLine[0]) << 8) | (hex2int(configLine[1]) << 4) | hex2int(configLine[2]);
718 					analyzerColors[line] = color & 0xFFF;
719 
720 					configLine = strtok(NULL, "\n");
721 					line++;
722 				}
723 			}
724 
725 			if (configLine == NULL)
726 				break;
727 		}
728 
729 		configLine = strtok(NULL, "\n");
730 	}
731 
732 	free(configBuffer);
733 	return true;
734 }
735