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 <math.h>
11 #ifdef _WIN32
12 #include <direct.h>
13 #include <io.h>
14 #define WIN32_MEAN_AND_LEAN
15 #include <windows.h>
16 #include <shlobj.h> // SHGetFolderPathW()
17 #else
18 #include <unistd.h>
19 #include <dirent.h>
20 #endif
21 #include <fcntl.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <time.h>
25 #include <limits.h>
26 #include "pt2_header.h"
27 #include "pt2_textout.h"
28 #include "pt2_diskop.h"
29 #include "pt2_tables.h"
30 #include "pt2_module_loader.h"
31 #include "pt2_audio.h"
32 #include "pt2_sampler.h"
33 #include "pt2_config.h"
34 #include "pt2_helpers.h"
35 #include "pt2_keyboard.h"
36 #include "pt2_visuals.h"
37 #include "pt2_sample_loader.h"
38 #include "pt2_bmp.h"
39 
40 typedef struct fileEntry_t
41 {
42 	UNICHAR *nameU;
43 	char firstAnsiChar; // for handleEntryJumping();
44 	char dateChanged[6 + 1];
45 	bool isDir;
46 	int32_t filesize;
47 } fileEntry_t;
48 
49 // "look for file" flags
50 enum
51 {
52 	LFF_DONE = 0,
53 	LFF_SKIP = 1,
54 	LFF_OK = 2
55 };
56 
57 #ifdef _WIN32
58 #define PARENT_DIR_STR L".."
59 static HANDLE hFind;
60 #else
61 #define PARENT_DIR_STR ".."
62 static DIR *hFind;
63 #endif
64 
65 static char fileNameBuffer[PATH_MAX + 1];
66 static uint32_t oldFileEntryRow;
67 static UNICHAR pathTmp[PATH_MAX + 2];
68 static fileEntry_t *diskOpEntry;
69 
addSampleFileExt(char * fileName)70 void addSampleFileExt(char *fileName)
71 {
72 	switch (diskop.smpSaveType)
73 	{
74 		case DISKOP_SMP_WAV: strcat(fileName, ".wav"); break;
75 		case DISKOP_SMP_IFF: strcat(fileName, ".iff"); break;
76 		default: case DISKOP_SMP_RAW: break;
77 	}
78 }
79 
bufferCreateEmptyDir(void)80 static fileEntry_t *bufferCreateEmptyDir(void) // special case: creates a dir entry with a ".." directory
81 {
82 	fileEntry_t *dirEntry;
83 
84 	dirEntry = (fileEntry_t *)malloc(sizeof (fileEntry_t));
85 	if (dirEntry == NULL)
86 		return NULL;
87 
88 	dirEntry->nameU = UNICHAR_STRDUP(PARENT_DIR_STR);
89 	if (dirEntry->nameU == NULL)
90 	{
91 		free(dirEntry);
92 		return NULL;
93 	}
94 
95 	dirEntry->isDir = true;
96 	dirEntry->filesize = 0;
97 
98 	return dirEntry;
99 }
100 
101 // deciding whether to load this entry into the buffer or not
listEntry(fileEntry_t * f)102 static bool listEntry(fileEntry_t *f)
103 {
104 	int32_t entryName = (int32_t)UNICHAR_STRLEN(f->nameU);
105 
106 #ifdef _WIN32
107 	if (f->isDir && entryName == 2 && !UNICHAR_STRNICMP(f->nameU, "..", 2))
108 		return true; // always show ".." directory
109 #else
110 	if (f->isDir && entryName == 2 && !UNICHAR_STRNICMP(f->nameU, "..", 2) && !(editor.currPath[0] == '/' && editor.currPath[1] == '\0'))
111 		return true; // always show ".." directory (unless in root dir)
112 #endif
113 
114 	if (!UNICHAR_STRNICMP(f->nameU, ".", 1))
115 		return false; // skip ".name" entries
116 
117 	if (f->isDir || diskop.mode == DISKOP_MODE_SMP)
118 		return true; // list all entries
119 
120 	if (entryName >= 4)
121 	{
122 		if (!UNICHAR_STRNICMP(f->nameU, "MOD.", 4) || !UNICHAR_STRNICMP(&f->nameU[entryName - 4], ".MOD", 4) ||
123 		    !UNICHAR_STRNICMP(f->nameU, "STK.", 4) || !UNICHAR_STRNICMP(&f->nameU[entryName - 4], ".STK", 4) ||
124 		    !UNICHAR_STRNICMP(f->nameU, "M15.", 4) || !UNICHAR_STRNICMP(&f->nameU[entryName - 4], ".M15", 4) ||
125 		    !UNICHAR_STRNICMP(f->nameU, "NST.", 4) || !UNICHAR_STRNICMP(&f->nameU[entryName - 4], ".NST", 4) ||
126 		    !UNICHAR_STRNICMP(f->nameU, "UST.", 4) || !UNICHAR_STRNICMP(&f->nameU[entryName - 4], ".UST", 4) ||
127 		    !UNICHAR_STRNICMP(f->nameU, "PP.",  3) || !UNICHAR_STRNICMP(&f->nameU[entryName - 3], ".PP",  3) ||
128 		    !UNICHAR_STRNICMP(f->nameU, "NT.",  3) || !UNICHAR_STRNICMP(&f->nameU[entryName - 3], ".NT",  3))
129 		{
130 			return true;
131 		}
132 	}
133 
134 	return false;
135 }
136 
findFirst(fileEntry_t * searchRec)137 static int8_t findFirst(fileEntry_t *searchRec)
138 {
139 #ifdef _WIN32
140 	WIN32_FIND_DATAW fData;
141 	SYSTEMTIME sysTime;
142 #else
143 	struct dirent *fData;
144 	struct stat st;
145 	int64_t fSize;
146 #endif
147 
148 	searchRec->nameU = NULL; // this one must be initialized
149 
150 #ifdef _WIN32
151 	hFind = FindFirstFileW(L"*", &fData);
152 	if (hFind == NULL || hFind == INVALID_HANDLE_VALUE)
153 		return LFF_DONE;
154 
155 	searchRec->nameU = UNICHAR_STRDUP(fData.cFileName);
156 	if (searchRec->nameU == NULL)
157 		return LFF_SKIP;
158 
159 	searchRec->filesize = (fData.nFileSizeHigh > 0) ? -1 : fData.nFileSizeLow;
160 	searchRec->isDir = (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false;
161 #else
162 	hFind = opendir(".");
163 	if (hFind == NULL)
164 		return LFF_DONE;
165 
166 	fData = readdir(hFind);
167 	if (fData == NULL)
168 		return LFF_DONE;
169 
170 	searchRec->nameU = UNICHAR_STRDUP(fData->d_name);
171 	if (searchRec->nameU == NULL)
172 		return LFF_SKIP;
173 
174 	searchRec->filesize = 0;
175 	searchRec->isDir = (fData->d_type == DT_DIR) ? true : false;
176 
177 	if (fData->d_type == DT_UNKNOWN || fData->d_type == DT_LNK)
178 	{
179 		if (stat(fData->d_name, &st) == 0)
180 		{
181 			fSize = (int64_t)st.st_size;
182 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
183 
184 			if ((st.st_mode & S_IFMT) == S_IFDIR)
185 				searchRec->isDir = true;
186 		}
187 	}
188 	else if (!searchRec->isDir)
189 	{
190 		if (stat(fData->d_name, &st) == 0)
191 		{
192 			fSize = (int64_t)st.st_size;
193 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
194 		}
195 	}
196 #endif
197 
198 	if (searchRec->filesize < -1)
199 		searchRec->filesize = -1;
200 
201 	if (!listEntry(searchRec))
202 	{
203 		// skip entry
204 		free(searchRec->nameU);
205 		searchRec->nameU = NULL;
206 		return LFF_SKIP;
207 	}
208 
209 #ifdef _WIN32
210 	FileTimeToSystemTime(&fData.ftLastWriteTime, &sysTime);
211 	snprintf(searchRec->dateChanged, 7, "%02d%02d%02d", sysTime.wDay, sysTime.wMonth, sysTime.wYear % 100);
212 	unicharToAnsi(&searchRec->firstAnsiChar, searchRec->nameU, 1);
213 #else
214 	strftime(searchRec->dateChanged, 7, "%d%m%y", localtime(&st.st_mtime));
215 	searchRec->firstAnsiChar = (char)searchRec->nameU[0];
216 #endif
217 
218 	return LFF_OK;
219 }
220 
findNext(fileEntry_t * searchRec)221 static int8_t findNext(fileEntry_t *searchRec)
222 {
223 #ifdef _WIN32
224 	WIN32_FIND_DATAW fData;
225 	SYSTEMTIME sysTime;
226 #else
227 	struct dirent *fData;
228 	struct stat st;
229 	int64_t fSize;
230 #endif
231 
232 	searchRec->nameU = NULL; // important
233 
234 #ifdef _WIN32
235 	if (hFind == NULL || FindNextFileW(hFind, &fData) == 0)
236 		return LFF_DONE;
237 
238 	searchRec->nameU = UNICHAR_STRDUP(fData.cFileName);
239 	if (searchRec->nameU == NULL)
240 		return LFF_SKIP;
241 
242 	searchRec->filesize = (fData.nFileSizeHigh > 0) ? -1 : fData.nFileSizeLow;
243 	searchRec->isDir = (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false;
244 
245 	FileTimeToSystemTime(&fData.ftLastWriteTime, &sysTime);
246 #else
247 	if (hFind == NULL || (fData = readdir(hFind)) == NULL)
248 		return LFF_DONE;
249 
250 	searchRec->nameU = UNICHAR_STRDUP(fData->d_name);
251 	if (searchRec->nameU == NULL)
252 		return LFF_SKIP;
253 
254 	searchRec->filesize = 0;
255 	searchRec->isDir = (fData->d_type == DT_DIR) ? true : false;
256 
257 	if (fData->d_type == DT_UNKNOWN || fData->d_type == DT_LNK)
258 	{
259 		if (stat(fData->d_name, &st) == 0)
260 		{
261 			fSize = (int64_t)st.st_size;
262 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
263 
264 			if ((st.st_mode & S_IFMT) == S_IFDIR)
265 				searchRec->isDir = true;
266 		}
267 	}
268 	else if (!searchRec->isDir)
269 	{
270 		if (stat(fData->d_name, &st) == 0)
271 		{
272 			fSize = (int64_t)st.st_size;
273 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
274 		}
275 	}
276 #endif
277 
278 	if (searchRec->filesize < -1)
279 		searchRec->filesize = -1;
280 
281 	if (!listEntry(searchRec))
282 	{
283 		// skip entry
284 		free(searchRec->nameU);
285 		searchRec->nameU = NULL;
286 		return LFF_SKIP;
287 	}
288 
289 #ifdef _WIN32
290 	FileTimeToSystemTime(&fData.ftLastWriteTime, &sysTime);
291 	snprintf(searchRec->dateChanged, 7, "%02d%02d%02d", sysTime.wDay, sysTime.wMonth, sysTime.wYear % 100);
292 	unicharToAnsi(&searchRec->firstAnsiChar, searchRec->nameU, 1);
293 #else
294 	strftime(searchRec->dateChanged, 7, "%d%m%y", localtime(&st.st_mtime));
295 	searchRec->firstAnsiChar = (char)searchRec->nameU[0];
296 #endif
297 
298 	return LFF_OK;
299 }
300 
findClose(void)301 static void findClose(void)
302 {
303 	if (hFind != NULL)
304 	{
305 #ifdef _WIN32
306 		FindClose(hFind);
307 #else
308 		closedir(hFind);
309 #endif
310 		hFind = NULL;
311 	}
312 }
313 
diskOpShowSelectText(void)314 void diskOpShowSelectText(void)
315 {
316 	if (!ui.diskOpScreenShown || ui.pointerMode == POINTER_MODE_MSG1 || editor.errorMsgActive)
317 		return;
318 
319 	if (diskop.mode == DISKOP_MODE_MOD)
320 		setStatusMessage("SELECT MODULE", NO_CARRY);
321 	else
322 		setStatusMessage("SELECT SAMPLE", NO_CARRY);
323 }
324 
handleEntryJumping(SDL_Keycode jumpToChar)325 void handleEntryJumping(SDL_Keycode jumpToChar) // SHIFT+character
326 {
327 	if (diskOpEntry != NULL)
328 	{
329 		for (int32_t i = 0; i < diskop.numEntries; i++)
330 		{
331 			if (jumpToChar == diskOpEntry[i].firstAnsiChar)
332 			{
333 				// fix visual overrun
334 				if (diskop.numEntries > DISKOP_LINES && i > diskop.numEntries-DISKOP_LINES)
335 					i = diskop.numEntries - DISKOP_LINES;
336 
337 				diskop.scrollOffset = i;
338 				ui.updateDiskOpFileList = true;
339 				return;
340 			}
341 		}
342 	}
343 
344 	// character not found in file list, show red mouse pointer (error)!
345 	editor.errorMsgActive = true;
346 	editor.errorMsgBlock = true;
347 	editor.errorMsgCounter = 0;
348 
349 	setErrPointer();
350 }
351 
diskOpEntryIsEmpty(int32_t fileIndex)352 bool diskOpEntryIsEmpty(int32_t fileIndex)
353 {
354 	if (diskop.scrollOffset+fileIndex >= diskop.numEntries)
355 		return true;
356 
357 	return false;
358 }
359 
diskOpEntryIsDir(int32_t fileIndex)360 bool diskOpEntryIsDir(int32_t fileIndex)
361 {
362 	if (diskOpEntry != NULL)
363 	{
364 		if (!diskOpEntryIsEmpty(fileIndex))
365 			return diskOpEntry[diskop.scrollOffset+fileIndex].isDir;
366 	}
367 
368 	return false; // couldn't look up entry
369 }
370 
diskOpGetAnsiEntry(int32_t fileIndex)371 char *diskOpGetAnsiEntry(int32_t fileIndex)
372 {
373 	UNICHAR *filenameU;
374 
375 	if (diskOpEntry != NULL && !diskOpEntryIsEmpty(fileIndex))
376 	{
377 		filenameU = diskOpEntry[diskop.scrollOffset+fileIndex].nameU;
378 		if (filenameU != NULL)
379 		{
380 			unicharToAnsi(fileNameBuffer, filenameU, PATH_MAX);
381 			return fileNameBuffer;
382 		}
383 	}
384 
385 	return NULL;
386 }
387 
diskOpGetUnicodeEntry(int32_t fileIndex)388 UNICHAR *diskOpGetUnicodeEntry(int32_t fileIndex)
389 {
390 	if (diskOpEntry != NULL && !diskOpEntryIsEmpty(fileIndex))
391 		return diskOpEntry[diskop.scrollOffset+fileIndex].nameU;
392 
393 	return NULL;
394 }
395 
setVisualPathToCwd(void)396 static void setVisualPathToCwd(void)
397 {
398 	memset(editor.currPath, 0, PATH_MAX + 10);
399 	memset(editor.currPathU, 0, (PATH_MAX + 2) * sizeof (UNICHAR));
400 	UNICHAR_GETCWD(editor.currPathU, PATH_MAX);
401 
402 	if (diskop.mode == DISKOP_MODE_MOD)
403 		UNICHAR_STRCPY(editor.modulesPathU, editor.currPathU);
404 	else
405 		UNICHAR_STRCPY(editor.samplesPathU, editor.currPathU);
406 
407 	unicharToAnsi(editor.currPath, editor.currPathU, PATH_MAX);
408 	ui.updateDiskOpPathText = true;
409 }
410 
changePathToHome(void)411 bool changePathToHome(void)
412 {
413 #ifdef _WIN32
414 	UNICHAR pathU[PATH_MAX + 2];
415 
416 	if (SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathU) >= 0)
417 	{
418 		if (UNICHAR_CHDIR(pathU) == 0)
419 			return true;
420 	}
421 
422 	return false;
423 #else
424 	char *homePath;
425 
426 	homePath = getenv("HOME");
427 	if (homePath != NULL && chdir(homePath) == 0)
428 		return true;
429 
430 	return false;
431 #endif
432 }
433 
setPathFromDiskOpMode(void)434 void setPathFromDiskOpMode(void)
435 {
436 	UNICHAR_CHDIR((diskop.mode == DISKOP_MODE_MOD) ? editor.modulesPathU : editor.samplesPathU);
437 	setVisualPathToCwd();
438 }
439 
diskOpSetPath(UNICHAR * path,bool cache)440 bool diskOpSetPath(UNICHAR *path, bool cache)
441 {
442 	if (path != NULL && UNICHAR_CHDIR(path) == 0)
443 	{
444 		setVisualPathToCwd();
445 
446 		if (cache)
447 			diskop.cached = false;
448 
449 		if (ui.diskOpScreenShown)
450 			ui.updateDiskOpFileList = true;
451 
452 		diskop.scrollOffset = 0;
453 		return true;
454 	}
455 	else
456 	{
457 		setVisualPathToCwd();
458 		displayErrorMsg("CAN'T OPEN DIR !");
459 		return false;
460 	}
461 }
462 
diskOpSetInitPath(void)463 void diskOpSetInitPath(void)
464 {
465 	// default module path
466 	if (config.defModulesDir[0] != '\0')
467 	{
468 #ifdef _WIN32
469 		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, config.defModulesDir, -1, pathTmp, PATH_MAX);
470 #else
471 		strcpy(pathTmp, config.defModulesDir);
472 #endif
473 		UNICHAR_STRCPY(editor.modulesPathU, pathTmp);
474 	}
475 	else
476 	{
477 		// no path set in config, set to current working directory
478 		UNICHAR_GETCWD(editor.modulesPathU, PATH_MAX);
479 	}
480 
481 	// default sample path
482 	if (config.defSamplesDir[0] != '\0')
483 	{
484 #ifdef _WIN32
485 		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, config.defSamplesDir, -1, pathTmp, PATH_MAX);
486 #else
487 		strcpy(pathTmp, config.defSamplesDir);
488 #endif
489 		UNICHAR_STRCPY(editor.samplesPathU, pathTmp);
490 	}
491 	else
492 	{
493 		// no path set in config, set to current working directory
494 		UNICHAR_GETCWD(editor.samplesPathU, PATH_MAX);
495 	}
496 
497 	setPathFromDiskOpMode();
498 }
499 
allocDiskOpVars(void)500 bool allocDiskOpVars(void)
501 {
502 	editor.fileNameTmpU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR));
503 	editor.entryNameTmp = (char *)calloc(PATH_MAX + 10, sizeof (char));
504 	editor.currPath = (char *)calloc(PATH_MAX + 10, sizeof (char));
505 	editor.currPathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR));
506 	editor.modulesPathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR));
507 	editor.samplesPathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR));
508 
509 	if (editor.fileNameTmpU == NULL || editor.entryNameTmp == NULL ||
510 		editor.currPath     == NULL || editor.currPathU    == NULL ||
511 		editor.modulesPathU == NULL || editor.samplesPathU == NULL)
512 	{
513 		// allocated leftovers are free'd lateron
514 		return false;
515 	}
516 
517 	return true;
518 }
519 
freeDiskOpMem(void)520 void freeDiskOpMem(void)
521 {
522 	if (editor.fileNameTmpU != NULL) free(editor.fileNameTmpU);
523 	if (editor.entryNameTmp != NULL) free(editor.entryNameTmp);
524 	if (editor.currPath != NULL) free(editor.currPath);
525 	if (editor.currPathU != NULL) free(editor.currPathU);
526 	if (editor.modulesPathU != NULL) free(editor.modulesPathU);
527 	if (editor.samplesPathU != NULL) free(editor.samplesPathU);
528 }
529 
freeDiskOpEntryMem(void)530 void freeDiskOpEntryMem(void)
531 {
532 	if (diskOpEntry != NULL)
533 	{
534 		for (int32_t i = 0; i < diskop.numEntries; i++)
535 		{
536 			if (diskOpEntry[i].nameU != NULL)
537 				free(diskOpEntry[i].nameU);
538 		}
539 
540 		free(diskOpEntry);
541 		diskOpEntry = NULL;
542 	}
543 
544 	diskop.numEntries = 0;
545 }
546 
547 // thanks to aTc for creating this simplified routine for qsort() (I edited it a little bit)
fileEntryCompare(const void * f1,const void * f2)548 static int32_t fileEntryCompare(const void *f1, const void *f2)
549 {
550 	static char fn_1[PATH_MAX+1], fn_2[PATH_MAX+1];
551 
552 	fileEntry_t *fe1 = (fileEntry_t *)f1;
553 	fileEntry_t *fe2 = (fileEntry_t *)f2;
554 
555 	unicharToAnsi(fn_1, fe1->nameU, PATH_MAX);
556 	unicharToAnsi(fn_2, fe2->nameU, PATH_MAX);
557 
558 	// ".." directories are always sorted first
559 	if (fe1->isDir && strcmp(fn_1, "..") == 0) return -1;
560 	if (fe2->isDir && strcmp(fn_2, "..") == 0) return  1;
561 
562 	/* ProTracker handles names in upper case during sorting,
563 	** convert the string case.
564 	*/
565 	const int32_t fe1_l = (int32_t)strlen(fn_1);
566 	const int32_t fe2_l = (int32_t)strlen(fn_2);
567 	for (int32_t i = 0; i < fe1_l; i++) fn_1[i] = (char)toupper(fn_1[i]);
568 	for (int32_t i = 0; i < fe2_l; i++) fn_2[i] = (char)toupper(fn_2[i]);
569 
570 	// if both entries are the same type
571 	if (fe1->isDir == fe2->isDir)
572 		return strcmp(fn_1, fn_2);
573 
574 	// different types, so one of them is a dir
575 	if (fe1->isDir)
576 		return -1; // first one is a dir
577 
578 	return 1; // second one is a dir
579 }
580 
sortEntries(void)581 static void sortEntries(void)
582 {
583 	if (diskop.numEntries >= 2)
584 		qsort(diskOpEntry, diskop.numEntries, sizeof (fileEntry_t), fileEntryCompare);
585 }
586 
diskOpFillBuffer(void)587 static bool diskOpFillBuffer(void)
588 {
589 	uint8_t lastFindFileFlag;
590 	fileEntry_t tmpBuffer, *newPtr;
591 
592 	diskop.scrollOffset = 0;
593 
594 	// do we have a path set?
595 	if (editor.currPathU[0] == '\0')
596 		setVisualPathToCwd();
597 
598 	freeDiskOpEntryMem();
599 
600 	// fill disk op. buffer (type, size, path, file name, date changed)
601 
602 	// read first file
603 	lastFindFileFlag = findFirst(&tmpBuffer);
604 	if (lastFindFileFlag != LFF_DONE && lastFindFileFlag != LFF_SKIP)
605 	{
606 		diskOpEntry = (fileEntry_t *)malloc(sizeof (fileEntry_t) * (diskop.numEntries + 1));
607 		if (diskOpEntry == NULL)
608 		{
609 			findClose();
610 			freeDiskOpEntryMem();
611 			statusOutOfMemory();
612 			return false;
613 		}
614 
615 		memcpy(&diskOpEntry[diskop.numEntries], &tmpBuffer, sizeof (fileEntry_t));
616 		diskop.numEntries++;
617 	}
618 
619 	// read remaining files
620 	while (lastFindFileFlag != LFF_DONE)
621 	{
622 		lastFindFileFlag = findNext(&tmpBuffer);
623 		if (lastFindFileFlag != LFF_DONE && lastFindFileFlag != LFF_SKIP)
624 		{
625 			newPtr = (fileEntry_t *)realloc(diskOpEntry, sizeof (fileEntry_t) * (diskop.numEntries + 1));
626 			if (newPtr == NULL)
627 			{
628 				findClose();
629 				freeDiskOpEntryMem();
630 				statusOutOfMemory();
631 				return false;
632 			}
633 			diskOpEntry = newPtr;
634 
635 			memcpy(&diskOpEntry[diskop.numEntries], &tmpBuffer, sizeof (fileEntry_t));
636 			diskop.numEntries++;
637 		}
638 	}
639 
640 	findClose();
641 
642 	if (diskop.numEntries > 0)
643 	{
644 		sortEntries();
645 	}
646 	else
647 	{
648 		// access denied or out of memory - create parent directory link
649 		diskOpEntry = bufferCreateEmptyDir();
650 		if (diskOpEntry != NULL)
651 			diskop.numEntries = 1;
652 		else
653 			statusOutOfMemory();
654 	}
655 
656 	return true;
657 }
658 
diskOpFillThreadFunc(void * ptr)659 static int32_t SDLCALL diskOpFillThreadFunc(void *ptr)
660 {
661 	(void)ptr;
662 
663 	diskop.isFilling = true;
664 	diskOpFillBuffer();
665 	diskop.isFilling = false;
666 
667 	ui.updateDiskOpFileList = true;
668 	return true;
669 }
670 
printFileSize(fileEntry_t * entry,uint16_t x,uint16_t y)671 static void printFileSize(fileEntry_t *entry, uint16_t x, uint16_t y)
672 {
673 	char tmpStr[7];
674 	uint32_t fileSize, j;
675 
676 	if (entry->filesize == -1) // -1 means that the original filesize is above 2GB in our directory reader
677 	{
678 		textOut(x, y, "  >2GB", video.palette[PAL_QADSCP]);
679 		return;
680 	}
681 
682 	fileSize = (uint32_t)entry->filesize;
683 	if (fileSize <= 999999)
684 	{
685 		// bytes
686 		snprintf(tmpStr, 7, "%06d", fileSize);
687 	}
688 	else if (fileSize <= 9999999)
689 	{
690 		// kilobytes
691 		fileSize /= 1000;
692 		snprintf(tmpStr, 7, "%04dKB", fileSize);
693 	}
694 	else
695 	{
696 		// megabytes
697 		fileSize /= 1000000;
698 		snprintf(tmpStr, 7, "%04dMB", fileSize);
699 	}
700 
701 	// turn zeroes on the left side into spaces
702 	for (j = 0; j < 7; j++)
703 	{
704 		if (tmpStr[j] != '0')
705 			break;
706 
707 		tmpStr[j] = ' ';
708 	}
709 
710 	textOut(x, y, tmpStr, video.palette[PAL_QADSCP]);
711 }
712 
printEntryName(char * entryName,int32_t entryLength,int32_t maxLength,uint16_t x,uint16_t y)713 static void printEntryName(char *entryName, int32_t entryLength, int32_t maxLength, uint16_t x, uint16_t y)
714 {
715 	if (entryLength > maxLength)
716 	{
717 		// shorten name and add ".." to end
718 		for (int32_t j = 0; j < maxLength-2; j++)
719 			charOut(x + (j * FONT_CHAR_W), y, entryName[j], video.palette[PAL_QADSCP]);
720 
721 		textOut(x + ((maxLength - 2) * FONT_CHAR_W), y, "..", video.palette[PAL_QADSCP]);
722 	}
723 	else
724 	{
725 		// print whole name
726 		textOut(x, y, entryName, video.palette[PAL_QADSCP]);
727 	}
728 }
729 
diskOpRenderFileList(void)730 void diskOpRenderFileList(void)
731 {
732 	char *entryName;
733 	uint8_t maxFilenameChars, maxDirNameChars;
734 	uint16_t x, y, textXStart;
735 	int32_t entryLength;
736 	fileEntry_t *entry;
737 
738 	if (config.hideDiskOpDates)
739 	{
740 		textXStart = 8;
741 		maxFilenameChars = 30;
742 		maxDirNameChars = 31;
743 	}
744 	else
745 	{
746 		textXStart = 64;
747 		maxFilenameChars = 23;
748 		maxDirNameChars = 24;
749 	}
750 
751 	diskOpShowSelectText();
752 
753 	if (diskop.forceStopReading)
754 		return;
755 
756 	// if needed, update the file list and add entries
757 	if (!diskop.cached)
758 	{
759 		diskop.fillThread = SDL_CreateThread(diskOpFillThreadFunc, NULL, NULL);
760 		if (diskop.fillThread != NULL)
761 			SDL_DetachThread(diskop.fillThread);
762 
763 		diskop.cached = true;
764 		return;
765 	}
766 
767 	// clear list
768 	fillRect(8, 35, 295, 59, video.palette[PAL_BACKGRD]);
769 
770 	if (diskop.isFilling || diskOpEntry == NULL)
771 		return;
772 
773 	// list entries
774 	for (int32_t i = 0; i < DISKOP_LINES; i++)
775 	{
776 		if (diskop.scrollOffset+i >= diskop.numEntries)
777 			break;
778 
779 		entry = &diskOpEntry[diskop.scrollOffset+i];
780 		entryName = diskOpGetAnsiEntry(i);
781 		entryLength = (int32_t)strlen(entryName);
782 
783 		x = textXStart;
784 		y = (uint8_t)(35 + (i * (FONT_CHAR_H + 1)));
785 
786 		if (!entry->isDir)
787 		{
788 			printEntryName(entryName, entryLength, maxFilenameChars, x, y);
789 
790 			// print modification date
791 			if (!config.hideDiskOpDates)
792 				textOut(8, y, entry->dateChanged, video.palette[PAL_QADSCP]);
793 
794 			// print file size
795 			printFileSize(entry, 256, y);
796 		}
797 		else
798 		{
799 			printEntryName(entryName, entryLength, maxDirNameChars, x, y);
800 			textOut(264, y, "(DIR)", video.palette[PAL_QADSCP]);
801 		}
802 	}
803 }
804 
diskOpLoadFile(uint32_t fileEntryRow,bool songModifiedCheck)805 void diskOpLoadFile(uint32_t fileEntryRow, bool songModifiedCheck)
806 {
807 	uint8_t oldMode, oldPlayMode;
808 	UNICHAR *filePath;
809 
810 	// if we clicked on an empty space, return...
811 	if (diskOpEntryIsEmpty(fileEntryRow))
812 		return;
813 
814 	if (diskOpEntryIsDir(fileEntryRow))
815 	{
816 		diskOpSetPath(diskOpGetUnicodeEntry(fileEntryRow), DISKOP_CACHE);
817 	}
818 	else
819 	{
820 		filePath = diskOpGetUnicodeEntry(fileEntryRow);
821 		if (filePath != NULL)
822 		{
823 			if (diskop.mode == DISKOP_MODE_MOD)
824 			{
825 				if (songModifiedCheck && song->modified)
826 				{
827 					oldFileEntryRow = fileEntryRow;
828 					showSongUnsavedAskBox(ASK_DISCARD_SONG);
829 					return;
830 				}
831 
832 				module_t *newSong = modLoad(filePath);
833 				if (newSong != NULL)
834 				{
835 					oldMode = editor.currMode;
836 					oldPlayMode = editor.playMode;
837 
838 					modStop();
839 					modFree();
840 
841 					song = newSong;
842 					setupLoadedMod();
843 					song->loaded = true;
844 
845 					statusAllRight();
846 
847 					if (config.autoCloseDiskOp)
848 						ui.diskOpScreenShown = false;
849 
850 					if (config.rememberPlayMode)
851 					{
852 						if (oldMode == MODE_PLAY || oldMode == MODE_RECORD)
853 						{
854 							editor.playMode = oldPlayMode;
855 
856 							if (oldPlayMode == PLAY_MODE_PATTERN || oldMode == MODE_RECORD)
857 								modPlay(0, 0, 0);
858 							else
859 								modPlay(DONT_SET_PATTERN, 0, 0);
860 
861 							if (oldMode == MODE_RECORD)
862 								pointerSetMode(POINTER_MODE_RECORD, DO_CARRY);
863 							else
864 								pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
865 
866 							editor.currMode = oldMode;
867 						}
868 					}
869 					else
870 					{
871 						editor.currMode = MODE_IDLE;
872 						editor.playMode = PLAY_MODE_NORMAL;
873 						editor.songPlaying = false;
874 
875 						pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
876 					}
877 
878 					displayMainScreen();
879 				}
880 				else
881 				{
882 					editor.errorMsgActive  = true;
883 					editor.errorMsgBlock = true;
884 					editor.errorMsgCounter = 0;
885 
886 					// status/error message is set in the mod loader
887 					setErrPointer();
888 				}
889 			}
890 			else if (diskop.mode == DISKOP_MODE_SMP)
891 			{
892 				loadSample(filePath, diskOpGetAnsiEntry(fileEntryRow));
893 			}
894 		}
895 	}
896 }
897 
diskOpLoadFile2(void)898 void diskOpLoadFile2(void)
899 {
900 	diskOpLoadFile(oldFileEntryRow, false);
901 }
902 
renderDiskOpScreen(void)903 void renderDiskOpScreen(void)
904 {
905 	blit32(0, 0, 320, 99, diskOpScreenBMP);
906 
907 	ui.updateDiskOpPathText = true;
908 	ui.updatePackText = true;
909 	ui.updateSaveFormatText = true;
910 	ui.updateLoadMode = true;
911 	ui.updateDiskOpFileList = true;
912 }
913 
updateDiskOp(void)914 void updateDiskOp(void)
915 {
916 	char tmpChar;
917 
918 	if (!ui.diskOpScreenShown || ui.posEdScreenShown)
919 		return;
920 
921 	if (ui.updateDiskOpFileList)
922 	{
923 		ui.updateDiskOpFileList = false;
924 		diskOpRenderFileList();
925 	}
926 
927 	if (ui.updateLoadMode)
928 	{
929 		ui.updateLoadMode = false;
930 
931 		// clear boxes
932 		fillRect(147,  3, FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
933 		fillRect(147, 14, FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
934 
935 		// draw load mode arrow
936 		if (diskop.mode == 0)
937 			charOut(147, 3, ARROW_RIGHT, video.palette[PAL_GENTXT]);
938 		else
939 			charOut(147,14, ARROW_RIGHT, video.palette[PAL_GENTXT]);
940 	}
941 
942 	if (ui.updatePackText)
943 	{
944 		ui.updatePackText = false;
945 		textOutBg(120, 3, diskop.modPackFlg ? "ON " : "OFF", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
946 	}
947 
948 	if (ui.updateSaveFormatText)
949 	{
950 		ui.updateSaveFormatText = false;
951 		     if (diskop.smpSaveType == DISKOP_SMP_WAV) textOutBg(120, 14, "WAV", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
952 		else if (diskop.smpSaveType == DISKOP_SMP_IFF) textOutBg(120, 14, "IFF", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
953 		else if (diskop.smpSaveType == DISKOP_SMP_RAW) textOutBg(120, 14, "RAW", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
954 	}
955 
956 	if (ui.updateDiskOpPathText)
957 	{
958 		ui.updateDiskOpPathText = false;
959 
960 		// print disk op. path
961 		for (int32_t i = 0; i < 26; i++)
962 		{
963 			tmpChar = editor.currPath[ui.diskOpPathTextOffset+i];
964 			if (tmpChar == '\0')
965 				tmpChar = '_';
966 
967 			charOutBg(24 + (i * FONT_CHAR_W), 25, tmpChar, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
968 		}
969 	}
970 }
971