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 #define _FILE_OFFSET_BITS 64
7 
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <math.h>
11 #ifdef _WIN32
12 #define WIN32_MEAN_AND_LEAN
13 #include <shlwapi.h>
14 #include <windows.h>
15 #include <direct.h>
16 #include <shlobj.h> // SHGetFolderPathW()
17 #else
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <fts.h> // for fts_open() and stuff in recursiveDelete()
21 #include <unistd.h>
22 #include <dirent.h>
23 #endif
24 #include <wchar.h>
25 #include <sys/stat.h>
26 #include "ft2_header.h"
27 #include "ft2_unicode.h"
28 #include "ft2_config.h"
29 #include "ft2_mouse.h"
30 #include "ft2_gui.h"
31 #include "ft2_pattern_ed.h"
32 #include "ft2_sample_loader.h"
33 #include "ft2_sample_saver.h"
34 #include "ft2_diskop.h"
35 #include "ft2_wav_renderer.h"
36 #include "ft2_module_loader.h"
37 #include "ft2_module_saver.h"
38 #include "ft2_events.h"
39 #include "ft2_video.h"
40 #include "ft2_inst_ed.h"
41 #include "ft2_structs.h"
42 
43 // hide POSIX warnings for chdir()
44 #ifdef _MSC_VER
45 #pragma warning(disable: 4996)
46 #endif
47 
48 #define FILENAME_TEXT_X 170
49 #define FILESIZE_TEXT_X 295
50 #define DISKOP_MAX_DRIVE_BUTTONS 8
51 
52 #ifdef _WIN32
53 #define PARENT_DIR_STR L".."
54 static HANDLE hFind;
55 #else
56 #define PARENT_DIR_STR ".."
57 static DIR *hFind;
58 #endif
59 
60 // "look for file" flags
61 enum
62 {
63 	LFF_DONE = 0,
64 	LFF_SKIP = 1,
65 	LFF_OK = 2
66 };
67 
68 typedef struct DirRec
69 {
70 	UNICHAR *nameU;
71 	bool isDir;
72 	int32_t filesize;
73 } DirRec;
74 
75 static char FReq_SysReqText[256], *FReq_FileName, *FReq_NameTemp;
76 static char *modTmpFName, *insTmpFName, *smpTmpFName, *patTmpFName, *trkTmpFName;
77 static char *modTmpFNameUTF8; // for window title
78 static uint8_t FReq_Item;
79 static bool FReq_ShowAllFiles, insPathSet, smpPathSet, patPathSet, trkPathSet, firstTimeOpeningDiskOp = true;
80 static int32_t FReq_EntrySelected = -1, FReq_FileCount, FReq_DirPos, lastMouseY;
81 static UNICHAR *FReq_CurPathU, *FReq_ModCurPathU, *FReq_InsCurPathU, *FReq_SmpCurPathU, *FReq_PatCurPathU, *FReq_TrkCurPathU;
82 static DirRec *FReq_Buffer;
83 static SDL_Thread *thread;
84 
85 static void setDiskOpItem(uint8_t item);
86 
setupExecutablePath(void)87 bool setupExecutablePath(void)
88 {
89 	editor.binaryPathU = (UNICHAR *)malloc((PATH_MAX + 1) * sizeof (UNICHAR));
90 	if (editor.binaryPathU == NULL)
91 		return false;
92 
93 	editor.binaryPathU[0] = 0;
94 	UNICHAR_GETCWD(editor.binaryPathU, PATH_MAX);
95 
96 	return true;
97 }
98 
getFileSize(UNICHAR * fileNameU)99 int32_t getFileSize(UNICHAR *fileNameU) // returning -1 = filesize over 2GB
100 {
101 	int64_t fSize;
102 
103 #ifdef _WIN32
104 	FILE *f = UNICHAR_FOPEN(fileNameU, "rb");
105 	if (f == NULL)
106 		return 0;
107 
108 	_fseeki64(f, 0, SEEK_END);
109 	fSize = _ftelli64(f);
110 	fclose(f);
111 #else
112 	struct stat st;
113 	if (stat(fileNameU, &st) != 0)
114 		return 0;
115 
116 	fSize = (int64_t)st.st_size;
117 #endif
118 	if (fSize < 0)
119 		fSize = 0;
120 
121 	if (fSize > INT32_MAX)
122 		return -1; // -1 = ">2GB" flag
123 
124 	return (int32_t)fSize;
125 }
126 
getDiskOpItem(void)127 uint8_t getDiskOpItem(void)
128 {
129 	return FReq_Item;
130 }
131 
getCurrSongFilename(void)132 char *getCurrSongFilename(void) // for window title
133 {
134 	return modTmpFNameUTF8;
135 }
136 
updateCurrSongFilename(void)137 void updateCurrSongFilename(void) // for window title
138 {
139 	if (modTmpFNameUTF8 != NULL)
140 	{
141 		free(modTmpFNameUTF8);
142 		modTmpFNameUTF8 = NULL;
143 	}
144 
145 	if (modTmpFName == NULL)
146 		return;
147 
148 	modTmpFNameUTF8 = cp437ToUtf8(modTmpFName);
149 }
150 
151 // drive buttons for Windows
152 #ifdef _WIN32
153 static char logicalDriveNames[26][3] =
154 {
155 	"A:", "B:", "C:", "D:", "E:", "F:", "G:", "H:", "I:", "J:", "K:", "L:", "M:",
156 	"N:", "O:", "P:", "Q:", "R:", "S:", "T:", "U:", "V:", "W:", "X:", "Y:", "Z:"
157 };
158 static uint32_t numLogicalDrives;
159 static uint32_t driveIndexes[DISKOP_MAX_DRIVE_BUTTONS];
160 #endif
161 
getDiskOpFilename(void)162 char *getDiskOpFilename(void)
163 {
164 	return FReq_FileName;
165 }
166 
getDiskOpCurPath(void)167 const UNICHAR *getDiskOpCurPath(void)
168 {
169 	return FReq_CurPathU;
170 }
171 
getDiskOpModPath(void)172 const UNICHAR *getDiskOpModPath(void)
173 {
174 	return FReq_ModCurPathU;
175 }
176 
getDiskOpSmpPath(void)177 const UNICHAR *getDiskOpSmpPath(void)
178 {
179 	return FReq_SmpCurPathU;
180 }
181 
setupInitialPaths(void)182 static void setupInitialPaths(void)
183 {
184 	// the UNICHAR paths are already zeroed out
185 
186 #ifdef _WIN32
187 	if (config.modulesPath[0] != '\0')
188 	{
189 		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, config.modulesPath, -1, FReq_ModCurPathU, 80);
190 		FReq_ModCurPathU[80] = 0;
191 	}
192 
193 	if (config.instrPath[0] != '\0')
194 	{
195 		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, config.instrPath, -1, FReq_InsCurPathU, 80);
196 		FReq_InsCurPathU[80] = 0;
197 		insPathSet = true;
198 	}
199 
200 	if (config.samplesPath[0] != '\0')
201 	{
202 		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, config.samplesPath, -1, FReq_SmpCurPathU, 80);
203 		FReq_SmpCurPathU[80] = 0;
204 		smpPathSet = true;
205 	}
206 
207 	if (config.patternsPath[0] != '\0')
208 	{
209 		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, config.patternsPath, -1, FReq_PatCurPathU, 80);
210 		FReq_PatCurPathU[80] = 0;
211 		patPathSet = true;
212 	}
213 
214 	if (config.tracksPath[0] != '\0')
215 	{
216 		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, config.tracksPath, -1, FReq_TrkCurPathU, 80);
217 		FReq_TrkCurPathU[80] = 0;
218 		trkPathSet = true;
219 	}
220 #else
221 	if (config.modulesPath[0] != '\0')
222 	{
223 		strncpy(FReq_ModCurPathU, config.modulesPath, 80);
224 		FReq_ModCurPathU[80] = 0;
225 	}
226 
227 	if (config.instrPath[0] != '\0')
228 	{
229 		strncpy(FReq_InsCurPathU, config.instrPath, 80);
230 		FReq_InsCurPathU[80] = 0;
231 		insPathSet = true;
232 	}
233 
234 	if (config.samplesPath[0] != '\0')
235 	{
236 		strncpy(FReq_SmpCurPathU, config.samplesPath, 80);
237 		FReq_SmpCurPathU[80] = 0;
238 		smpPathSet = true;
239 	}
240 
241 	if (config.patternsPath[0] != '\0')
242 	{
243 		strncpy(FReq_PatCurPathU, config.patternsPath, 80);
244 		FReq_PatCurPathU[80] = 0;
245 		patPathSet = true;
246 	}
247 
248 	if (config.tracksPath[0] != '\0')
249 	{
250 		strncpy(FReq_TrkCurPathU, config.tracksPath, 80);
251 		FReq_TrkCurPathU[80] = 0;
252 		trkPathSet = true;
253 	}
254 #endif
255 }
256 
freeDirRecBuffer(void)257 static void freeDirRecBuffer(void)
258 {
259 	if (FReq_Buffer != NULL)
260 	{
261 		for (int32_t i = 0; i < FReq_FileCount; i++)
262 		{
263 			if (FReq_Buffer[i].nameU != NULL)
264 				free(FReq_Buffer[i].nameU);
265 		}
266 
267 		free(FReq_Buffer);
268 		FReq_Buffer = NULL;
269 	}
270 
271 	FReq_FileCount = 0;
272 }
273 
freeDiskOp(void)274 void freeDiskOp(void)
275 {
276 	if (editor.tmpFilenameU != NULL)
277 	{
278 		free(editor.tmpFilenameU);
279 		editor.tmpFilenameU = NULL;
280 	}
281 
282 	if (editor.tmpInstrFilenameU != NULL)
283 	{
284 		free(editor.tmpInstrFilenameU);
285 		editor.tmpInstrFilenameU = NULL;
286 	}
287 
288 	if (modTmpFName != NULL) { free(modTmpFName); modTmpFName = NULL; }
289 	if (insTmpFName != NULL) { free(insTmpFName); insTmpFName = NULL; }
290 	if (smpTmpFName != NULL) { free(smpTmpFName); smpTmpFName = NULL; }
291 	if (patTmpFName != NULL) { free(patTmpFName); patTmpFName = NULL; }
292 	if (trkTmpFName != NULL) { free(trkTmpFName); trkTmpFName = NULL; }
293 	if (FReq_NameTemp != NULL) { free(FReq_NameTemp); FReq_NameTemp = NULL; }
294 	if (FReq_ModCurPathU != NULL) { free(FReq_ModCurPathU); FReq_ModCurPathU = NULL; }
295 	if (FReq_InsCurPathU != NULL) { free(FReq_InsCurPathU); FReq_InsCurPathU = NULL; }
296 	if (FReq_SmpCurPathU != NULL) { free(FReq_SmpCurPathU); FReq_SmpCurPathU = NULL; }
297 	if (FReq_PatCurPathU != NULL) { free(FReq_PatCurPathU); FReq_PatCurPathU = NULL; }
298 	if (FReq_TrkCurPathU != NULL) { free(FReq_TrkCurPathU); FReq_TrkCurPathU = NULL; }
299 	if (modTmpFNameUTF8 != NULL) { free(modTmpFNameUTF8); modTmpFNameUTF8 = NULL; }
300 
301 	freeDirRecBuffer();
302 }
303 
setupDiskOp(void)304 bool setupDiskOp(void)
305 {
306 	modTmpFName = (char *)malloc((PATH_MAX + 1) * sizeof (char));
307 	insTmpFName = (char *)malloc((PATH_MAX + 1) * sizeof (char));
308 	smpTmpFName = (char *)malloc((PATH_MAX + 1) * sizeof (char));
309 	patTmpFName = (char *)malloc((PATH_MAX + 1) * sizeof (char));
310 	trkTmpFName = (char *)malloc((PATH_MAX + 1) * sizeof (char));
311 	FReq_NameTemp = (char *)malloc((PATH_MAX + 1) * sizeof (char));
312 
313 	FReq_ModCurPathU = (UNICHAR *)malloc((PATH_MAX + 1) * sizeof (UNICHAR));
314 	FReq_InsCurPathU = (UNICHAR *)malloc((PATH_MAX + 1) * sizeof (UNICHAR));
315 	FReq_SmpCurPathU = (UNICHAR *)malloc((PATH_MAX + 1) * sizeof (UNICHAR));
316 	FReq_PatCurPathU = (UNICHAR *)malloc((PATH_MAX + 1) * sizeof (UNICHAR));
317 	FReq_TrkCurPathU = (UNICHAR *)malloc((PATH_MAX + 1) * sizeof (UNICHAR));
318 
319 	if (modTmpFName      == NULL || insTmpFName      == NULL || smpTmpFName      == NULL ||
320 		patTmpFName      == NULL || trkTmpFName      == NULL || FReq_NameTemp    == NULL ||
321 		FReq_ModCurPathU == NULL || FReq_InsCurPathU == NULL || FReq_SmpCurPathU == NULL ||
322 		FReq_PatCurPathU == NULL || FReq_TrkCurPathU == NULL)
323 	{
324 		// allocated memory is free'd lateron
325 		showErrorMsgBox("Not enough memory!");
326 		return false;
327 	}
328 
329 	// clear first entry of strings
330 	modTmpFName[0] = '\0';
331 	insTmpFName[0] = '\0';
332 	smpTmpFName[0] = '\0';
333 	patTmpFName[0] = '\0';
334 	trkTmpFName[0] = '\0';
335 	FReq_NameTemp[0] = '\0';
336 	FReq_ModCurPathU[0] = 0;
337 	FReq_InsCurPathU[0] = 0;
338 	FReq_SmpCurPathU[0] = 0;
339 	FReq_PatCurPathU[0] = 0;
340 	FReq_TrkCurPathU[0] = 0;
341 
342 	strcpy(modTmpFName, "untitled.xm");
343 	strcpy(insTmpFName, "untitled.xi");
344 	strcpy(smpTmpFName, "untitled.wav");
345 	strcpy(patTmpFName, "untitled.xp");
346 	strcpy(trkTmpFName, "untitled.xt");
347 
348 	setupInitialPaths();
349 	setDiskOpItem(0);
350 
351 	updateCurrSongFilename(); // for window title
352 	updateWindowTitle(true);
353 
354 	return true;
355 }
356 
getExtOffset(char * s,int32_t stringLen)357 int32_t getExtOffset(char *s, int32_t stringLen) // get byte offset of file extension (last '.')
358 {
359 	if (s == NULL || stringLen < 1)
360 		return -1;
361 
362 	for (int32_t i = stringLen - 1; i >= 0; i--)
363 	{
364 		if (i != 0 && s[i] == '.')
365 			return i;
366 	}
367 
368 	return -1;
369 }
370 
removeQuestionmarksFromString(char * s)371 static void removeQuestionmarksFromString(char *s)
372 {
373 	if (s == NULL || *s == '\0')
374 		return;
375 
376 	const int32_t len = (int32_t)strlen(s);
377 	for (int32_t i = 0; i < len; i++)
378 	{
379 		if (s[i] == '?')
380 			s[i] = ' ' ;
381 	}
382 }
383 
384 #ifdef _WIN32 // WINDOWS SPECIFIC FILE OPERATION ROUTINES
385 
fileExistsAnsi(char * str)386 bool fileExistsAnsi(char *str)
387 {
388 	UNICHAR *strU = cp437ToUnichar(str);
389 	if (strU == NULL)
390 		return false;
391 
392 	bool retVal = PathFileExistsW(strU);
393 	free(strU);
394 
395 	return retVal;
396 }
397 
deleteDirRecursive(UNICHAR * strU)398 static bool deleteDirRecursive(UNICHAR *strU)
399 {
400 	SHFILEOPSTRUCTW shfo;
401 
402 	memset(&shfo, 0, sizeof (shfo));
403 	shfo.wFunc = FO_DELETE;
404 	shfo.fFlags = FOF_SILENT | FOF_NOERRORUI | FOF_NOCONFIRMATION;
405 	shfo.pFrom = strU;
406 
407 	return (SHFileOperationW(&shfo) == 0);
408 }
409 
makeDirAnsi(char * str)410 static bool makeDirAnsi(char *str)
411 {
412 	UNICHAR *strU = cp437ToUnichar(str);
413 	if (strU == NULL)
414 		return false;
415 
416 	int32_t retVal = _wmkdir(strU);
417 	free(strU);
418 
419 	return (retVal == 0);
420 }
421 
renameAnsi(UNICHAR * oldNameU,char * newName)422 static bool renameAnsi(UNICHAR *oldNameU, char *newName)
423 {
424 	UNICHAR *newNameU = cp437ToUnichar(newName);
425 	if (newNameU == NULL)
426 		return false;
427 
428 	int32_t retVal = UNICHAR_RENAME(oldNameU, newNameU);
429 	free(newNameU);
430 
431 	return (retVal == 0);
432 }
433 
setupDiskOpDrives(void)434 static void setupDiskOpDrives(void) // Windows only
435 {
436 	fillRect(134, 29, 31, 111, PAL_DESKTOP);
437 	numLogicalDrives = 0;
438 
439 	// get number of drives and drive names
440 	const uint32_t drivesBitmask = GetLogicalDrives();
441 	for (int32_t i = 0; i < 8*sizeof (uint32_t); i++)
442 	{
443 		if ((drivesBitmask & (1 << i)) != 0)
444 		{
445 			driveIndexes[numLogicalDrives++] = i;
446 			if (numLogicalDrives == DISKOP_MAX_DRIVE_BUTTONS)
447 				break;
448 		}
449 	}
450 
451 	// hide all buttons
452 	for (uint16_t i = 0; i < DISKOP_MAX_DRIVE_BUTTONS; i++)
453 		hidePushButton(PB_DISKOP_DRIVE1 + i);
454 
455 	// set button names and show buttons
456 	for (uint16_t i = 0; i < numLogicalDrives; i++)
457 	{
458 		pushButtons[PB_DISKOP_DRIVE1 + i].caption = logicalDriveNames[driveIndexes[i]];
459 		showPushButton(PB_DISKOP_DRIVE1 + i);
460 	}
461 }
462 
openDrive(char * str)463 static void openDrive(char *str) // Windows only
464 {
465 	if (mouse.mode == MOUSE_MODE_DELETE)
466 	{
467 		okBox(8, "System complaint", "Very funny.");
468 		return;
469 	}
470 
471 	if (str == NULL || *str == '\0')
472 	{
473 		okBox(0, "System message", "Couldn't open drive!");
474 		return;
475 	}
476 
477 	if (chdir(str) != 0)
478 		okBox(0, "System message", "Couldn't open drive! Please make sure there's a disk in it.");
479 	else
480 		editor.diskOpReadDir = true;
481 }
482 
483 #else // NON-WINDOWS SPECIFIC FILE OPERATION ROUTINES
484 
fileExistsAnsi(char * str)485 bool fileExistsAnsi(char *str)
486 {
487 	UNICHAR *strU = cp437ToUnichar(str);
488 	if (strU == NULL)
489 		return false;
490 
491 	int32_t retVal = access(strU, F_OK);
492 	free(strU);
493 
494 	return (retVal != -1);
495 }
496 
deleteDirRecursive(UNICHAR * strU)497 static bool deleteDirRecursive(UNICHAR *strU)
498 {
499 	FTSENT *curr;
500 	char *files[] = { (char *)(strU), NULL };
501 
502 	FTS *ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
503 	if (!ftsp)
504 		return false;
505 
506 	bool ret = true;
507 	while ((curr = fts_read(ftsp)))
508 	{
509 		switch (curr->fts_info)
510 		{
511 			default:
512 			case FTS_NS:
513 			case FTS_DNR:
514 			case FTS_ERR:
515 				ret = false;
516 			break;
517 
518 			case FTS_D:
519 			case FTS_DC:
520 			case FTS_DOT:
521 			case FTS_NSOK:
522 				break;
523 
524 			case FTS_DP:
525 			case FTS_F:
526 			case FTS_SL:
527 			case FTS_SLNONE:
528 			case FTS_DEFAULT:
529 			{
530 				if (remove(curr->fts_accpath) < 0)
531 					ret = false;
532 			}
533 			break;
534 		}
535 	}
536 
537 	if (ftsp != NULL)
538 		fts_close(ftsp);
539 
540 	return ret;
541 }
542 
makeDirAnsi(char * str)543 static bool makeDirAnsi(char *str)
544 {
545 	UNICHAR *strU = cp437ToUnichar(str);
546 	if (strU == NULL)
547 		return false;
548 
549 	int32_t retVal = mkdir(str, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
550 	free(strU);
551 
552 	return (retVal == 0);
553 }
554 
renameAnsi(UNICHAR * oldNameU,char * newName)555 static bool renameAnsi(UNICHAR *oldNameU, char *newName)
556 {
557 	int32_t retVal;
558 	UNICHAR *newNameU;
559 
560 	newNameU = cp437ToUnichar(newName);
561 	if (newNameU == NULL)
562 		return false;
563 
564 	retVal = UNICHAR_RENAME(oldNameU, newNameU);
565 	free(newNameU);
566 
567 	return (retVal == 0);
568 }
569 #endif
570 
openDirectory(UNICHAR * strU)571 static void openDirectory(UNICHAR *strU)
572 {
573 	if (strU == NULL || UNICHAR_STRLEN(strU) == 0)
574 	{
575 		okBox(0, "System message", "Couldn't open directory! No permission or in use?");
576 		return;
577 	}
578 
579 	if (UNICHAR_CHDIR(strU) != 0)
580 		okBox(0, "System message", "Couldn't open directory! No permission or in use?");
581 	else
582 		editor.diskOpReadDir = true;
583 }
584 
diskOpGoParent(void)585 bool diskOpGoParent(void)
586 {
587 	if (chdir("..") == 0)
588 	{
589 		editor.diskOpReadDir = true;
590 		FReq_EntrySelected = -1;
591 
592 		return true;
593 	}
594 
595 	return false;
596 }
597 
getFilenameFromPath(char * p)598 static char *getFilenameFromPath(char *p)
599 {
600 	int32_t i;
601 
602 	if (p == NULL || p[0] == '\0')
603 		return p;
604 
605 	const int32_t len = (int32_t)strlen(p);
606 	if (len < 2 || p[len-1] == DIR_DELIMITER)
607 		return p;
608 
609 	// search for last directory delimiter
610 	for (i = len - 1; i >= 0; i--)
611 	{
612 		if (p[i] == DIR_DELIMITER)
613 			break;
614 	}
615 
616 	if (i != 0)
617 		p += i+1; // we found a directory delimiter - skip to the last one
618 
619 	return p;
620 }
621 
sanitizeFilename(const char * src)622 void sanitizeFilename(const char *src)
623 {
624 	// some of these are legal on GNU/Linux and macOS, but whatever...
625 	const char illegalChars[] = "\\/:*?\"<>|";
626 	char *ptr;
627 
628 	if (src == NULL || src[0] == '\0')
629 		return;
630 
631 	// convert illegal characters to space (for making a filename the OS will accept)
632 	while ((ptr = (char *)strpbrk(src, illegalChars)) != NULL)
633 		*ptr = ' ';
634 }
635 
diskOpSetFilename(uint8_t type,UNICHAR * pathU)636 void diskOpSetFilename(uint8_t type, UNICHAR *pathU)
637 {
638 	char *ansiPath = unicharToCp437(pathU, true);
639 	if (ansiPath == NULL)
640 		return;
641 
642 	char *filename = getFilenameFromPath(ansiPath);
643 	uint32_t filenameLen = (uint32_t)strlen(filename);
644 
645 	if (filenameLen > PATH_MAX)
646 	{
647 		free(ansiPath);
648 		return; // filename is too long, don't bother to copy it over
649 	}
650 
651 	sanitizeFilename(filename);
652 
653 	switch (type)
654 	{
655 		default:
656 		case DISKOP_ITEM_MODULE:
657 		{
658 			strcpy(modTmpFName, filename);
659 			updateCurrSongFilename(); // for window title
660 
661 			if (editor.moduleSaveMode == MOD_SAVE_MODE_MOD)
662 				changeFilenameExt(modTmpFName, ".mod", PATH_MAX);
663 			else if (editor.moduleSaveMode == MOD_SAVE_MODE_XM)
664 				changeFilenameExt(modTmpFName, ".xm", PATH_MAX);
665 			else if (editor.moduleSaveMode == MOD_SAVE_MODE_WAV)
666 				changeFilenameExt(modTmpFName, ".wav", PATH_MAX);
667 
668 			updateWindowTitle(true);
669 		}
670 		break;
671 
672 		case DISKOP_ITEM_INSTR:
673 			strcpy(insTmpFName, filename);
674 		break;
675 
676 		case DISKOP_ITEM_SAMPLE:
677 		{
678 			strcpy(smpTmpFName, filename);
679 
680 			if (editor.sampleSaveMode == SMP_SAVE_MODE_RAW)
681 				changeFilenameExt(smpTmpFName, ".raw", PATH_MAX);
682 			else if (editor.sampleSaveMode == SMP_SAVE_MODE_IFF)
683 				changeFilenameExt(smpTmpFName, ".iff", PATH_MAX);
684 			else if (editor.sampleSaveMode == SMP_SAVE_MODE_WAV)
685 				changeFilenameExt(smpTmpFName, ".wav", PATH_MAX);
686 		}
687 		break;
688 
689 		case DISKOP_ITEM_PATTERN:
690 			strcpy(patTmpFName, filename);
691 		break;
692 
693 		case DISKOP_ITEM_TRACK:
694 			strcpy(trkTmpFName, filename);
695 		break;
696 	}
697 
698 	free(ansiPath);
699 
700 	if (ui.diskOpShown)
701 		drawTextBox(TB_DISKOP_FILENAME);
702 }
703 
openFile(UNICHAR * filenameU,bool songModifiedCheck)704 static void openFile(UNICHAR *filenameU, bool songModifiedCheck)
705 {
706 	// first check if we can actually open the requested file
707 	FILE *f = UNICHAR_FOPEN(filenameU, "rb");
708 	if (f == NULL)
709 	{
710 		okBox(0, "System message", "Couldn't open file/directory! No permission or in use?");
711 		return;
712 	}
713 	fclose(f);
714 
715 	const int32_t filesize = getFileSize(filenameU);
716 	if (filesize == -1) // >2GB
717 	{
718 		okBox(0, "System message", "The file is too big and can't be loaded (over 2GB).");
719 		return;
720 	}
721 
722 	if (filesize >= 128L*1024*1024) // 128MB
723 	{
724 		if (okBox(2, "System request", "Are you sure you want to load such a big file?") != 1)
725 			return;
726 	}
727 
728 	// file is readable, handle file...
729 	switch (FReq_Item)
730 	{
731 		default:
732 		case DISKOP_ITEM_MODULE:
733 		{
734 			if (songModifiedCheck && song.isModified)
735 			{
736 				// remove file selection before okBox() opens up
737 				FReq_EntrySelected = -1;
738 				diskOp_DrawFilelist();
739 
740 				if (okBox(2, "System request", "You have unsaved changes in your song. Load new song and lose all changes?") != 1)
741 					return;
742 			}
743 
744 			editor.loadMusicEvent = EVENT_LOADMUSIC_DISKOP;
745 			loadMusic(filenameU);
746 		}
747 		break;
748 
749 		case DISKOP_ITEM_INSTR:
750 			loadInstr(filenameU);
751 		break;
752 
753 		case DISKOP_ITEM_SAMPLE:
754 			loadSample(filenameU, editor.curSmp, false);
755 		break;
756 
757 		case DISKOP_ITEM_PATTERN:
758 			loadPattern(filenameU);
759 		break;
760 
761 		case DISKOP_ITEM_TRACK:
762 			loadTrack(filenameU);
763 		break;
764 	}
765 }
766 
removeFilenameExt(char * name)767 static void removeFilenameExt(char *name)
768 {
769 	if (name == NULL || *name == '\0')
770 		return;
771 
772 	const int32_t len = (int32_t)strlen(name);
773 
774 	const int32_t extOffset = getExtOffset(name, len);
775 	if (extOffset != -1)
776 		name[extOffset] = '\0';
777 }
778 
changeFilenameExt(char * name,char * ext,int32_t nameMaxLen)779 void changeFilenameExt(char *name, char *ext, int32_t nameMaxLen)
780 {
781 	if (name == NULL || name[0] == '\0' || ext == NULL)
782 		return;
783 
784 	removeFilenameExt(name);
785 
786 	const int32_t len = (int32_t)strlen(name);
787 	int32_t extLen = (int32_t)strlen(ext);
788 
789 	if (len+extLen > nameMaxLen)
790 		extLen = nameMaxLen-len;
791 
792 	strncat(name, ext, extLen);
793 
794 	if (ui.diskOpShown)
795 		diskOp_DrawDirectory();
796 }
797 
diskOpChangeFilenameExt(char * ext)798 void diskOpChangeFilenameExt(char *ext)
799 {
800 	changeFilenameExt(FReq_FileName, ext, PATH_MAX);
801 }
802 
trimEntryName(char * name,bool isDir)803 void trimEntryName(char *name, bool isDir)
804 {
805 	char extBuffer[24];
806 
807 	int32_t j = (int32_t)strlen(name);
808 	const int32_t extOffset = getExtOffset(name, j);
809 	int32_t extLen = (int32_t)strlen(&name[extOffset]);
810 	j--;
811 
812 	if (isDir)
813 	{
814 		// directory
815 		while (textWidth(name) > 160-8 && j >= 2)
816 		{
817 			name[j-2] = '.';
818 			name[j-1] = '.';
819 			name[j-0] = '\0';
820 			j--;
821 		}
822 
823 		return;
824 	}
825 
826 	if (extOffset != -1 && extLen <= 4)
827 	{
828 		// has extension
829 		sprintf(extBuffer, ".. %s", &name[extOffset]); // "testtestte... .xm"
830 
831 		extLen = (int32_t)strlen(extBuffer);
832 		while (textWidth(name) >= FILESIZE_TEXT_X-FILENAME_TEXT_X && j >= extLen+1)
833 		{
834 			memcpy(&name[j - extLen], extBuffer, extLen + 1);
835 			j--;
836 		}
837 	}
838 	else
839 	{
840 		// no extension
841 		while (textWidth(name) >= FILESIZE_TEXT_X-FILENAME_TEXT_X && j >= 2)
842 		{
843 			name[j-2] = '.';
844 			name[j-1] = '.';
845 			name[j-0] = '\0';
846 			j--;
847 		}
848 	}
849 }
850 
createFileOverwriteText(char * filename,char * buffer)851 void createFileOverwriteText(char *filename, char *buffer)
852 {
853 	char nameTmp[128];
854 
855 	// read entry name to a small buffer
856 	const uint32_t nameLen = (uint32_t)strlen(filename);
857 	memcpy(nameTmp, filename, (nameLen >= sizeof (nameTmp)) ? sizeof (nameTmp) : (nameLen + 1));
858 	nameTmp[sizeof (nameTmp) - 1] = '\0';
859 
860 	trimEntryName(nameTmp, false);
861 
862 	sprintf(buffer, "Overwrite file \"%s\"?", nameTmp);
863 }
864 
diskOpSave(bool checkOverwrite)865 static void diskOpSave(bool checkOverwrite)
866 {
867 	UNICHAR *fileNameU;
868 
869 	if (FReq_FileName[0] == '\0')
870 	{
871 		okBox(0, "System message", "Filename can't be empty!");
872 		return;
873 	}
874 
875 	// test if the very first character has a dot...
876 	if (FReq_FileName[0] == '.')
877 	{
878 		okBox(0, "System message", "The very first character in the filename can't be '.' (dot)!");
879 		return;
880 	}
881 
882 	// test for illegal file name
883 	if (FReq_FileName[0] == '\0' || strpbrk(FReq_FileName, "\\/:*?\"<>|") != NULL)
884 	{
885 		okBox(0, "System message", "The filename can't contain the following characters: \\ / : * ? \" < > |");
886 		return;
887 	}
888 
889 	switch (FReq_Item)
890 	{
891 		default:
892 		case DISKOP_ITEM_MODULE:
893 		{
894 			switch (editor.moduleSaveMode)
895 			{
896 				         case MOD_SAVE_MODE_MOD: diskOpChangeFilenameExt(".mod"); break;
897 				default: case MOD_SAVE_MODE_XM:  diskOpChangeFilenameExt(".xm");  break;
898 				         case MOD_SAVE_MODE_WAV: diskOpChangeFilenameExt(".wav"); break;
899 			}
900 
901 			// enter WAV renderer if needed
902 			if (editor.moduleSaveMode == MOD_SAVE_MODE_WAV)
903 			{
904 				exitDiskOpScreen();
905 				showWavRenderer();
906 				return;
907 			}
908 
909 			if (checkOverwrite && fileExistsAnsi(FReq_FileName))
910 			{
911 				createFileOverwriteText(FReq_FileName, FReq_SysReqText);
912 				if (okBox(2, "System request", FReq_SysReqText) != 1)
913 					return;
914 			}
915 
916 			fileNameU = cp437ToUnichar(FReq_FileName);
917 			if (fileNameU == NULL)
918 			{
919 				okBox(0, "System message", "General I/O error during saving! Is the file in use?");
920 				return;
921 			}
922 
923 			saveMusic(fileNameU);
924 			free(fileNameU);
925 			// sets editor.diskOpReadDir after thread is done
926 		}
927 		break;
928 
929 		case DISKOP_ITEM_INSTR:
930 		{
931 			diskOpChangeFilenameExt(".xi");
932 
933 			if (checkOverwrite && fileExistsAnsi(FReq_FileName))
934 			{
935 				createFileOverwriteText(FReq_FileName, FReq_SysReqText);
936 				if (okBox(2, "System request", FReq_SysReqText) != 1)
937 					return;
938 			}
939 
940 			fileNameU = cp437ToUnichar(FReq_FileName);
941 			if (fileNameU == NULL)
942 			{
943 				okBox(0, "System message", "General I/O error during saving! Is the file in use?");
944 				return;
945 			}
946 
947 			saveInstr(fileNameU, editor.curInstr);
948 			free(fileNameU);
949 			// editor.diskOpReadDir is set after thread is done
950 		}
951 		break;
952 
953 		case DISKOP_ITEM_SAMPLE:
954 		{
955 			switch (editor.sampleSaveMode)
956 			{
957 				         case SMP_SAVE_MODE_RAW: diskOpChangeFilenameExt(".raw"); break;
958 				         case SMP_SAVE_MODE_IFF: diskOpChangeFilenameExt(".iff"); break;
959 				default: case SMP_SAVE_MODE_WAV: diskOpChangeFilenameExt(".wav"); break;
960 			}
961 
962 			if (checkOverwrite && fileExistsAnsi(FReq_FileName))
963 			{
964 				createFileOverwriteText(FReq_FileName, FReq_SysReqText);
965 				if (okBox(2, "System request", FReq_SysReqText) != 1)
966 					return;
967 			}
968 
969 			fileNameU = cp437ToUnichar(FReq_FileName);
970 			if (fileNameU == NULL)
971 			{
972 				okBox(0, "System message", "General I/O error during saving! Is the file in use?");
973 				return;
974 			}
975 
976 			saveSample(fileNameU, SAVE_NORMAL);
977 			free(fileNameU);
978 			// editor.diskOpReadDir is set after thread is done
979 		}
980 		break;
981 
982 		case DISKOP_ITEM_PATTERN:
983 		{
984 			diskOpChangeFilenameExt(".xp");
985 
986 			if (checkOverwrite && fileExistsAnsi(FReq_FileName))
987 			{
988 				createFileOverwriteText(FReq_FileName, FReq_SysReqText);
989 				if (okBox(2, "System request", FReq_SysReqText) != 1)
990 					return;
991 			}
992 
993 			fileNameU = cp437ToUnichar(FReq_FileName);
994 			if (fileNameU == NULL)
995 			{
996 				okBox(0, "System message", "General I/O error during saving! Is the file in use?");
997 				return;
998 			}
999 
1000 			editor.diskOpReadDir = savePattern(fileNameU);
1001 			free(fileNameU);
1002 		}
1003 		break;
1004 
1005 		case DISKOP_ITEM_TRACK:
1006 		{
1007 			diskOpChangeFilenameExt(".xt");
1008 
1009 			if (checkOverwrite && fileExistsAnsi(FReq_FileName))
1010 			{
1011 				createFileOverwriteText(FReq_FileName, FReq_SysReqText);
1012 				if (okBox(2, "System request", FReq_SysReqText) != 1)
1013 					return;
1014 			}
1015 
1016 			fileNameU = cp437ToUnichar(FReq_FileName);
1017 			if (fileNameU == NULL)
1018 			{
1019 				okBox(0, "System message", "General I/O error during saving! Is the file in use?");
1020 				return;
1021 			}
1022 
1023 			editor.diskOpReadDir = saveTrack(fileNameU);
1024 			free(fileNameU);
1025 		}
1026 		break;
1027 	}
1028 }
1029 
pbDiskOpSave(void)1030 void pbDiskOpSave(void)
1031 {
1032 	diskOpSave(config.cfg_OverwriteWarning ? true : false); // check if about to overwrite
1033 }
1034 
fileListPressed(int32_t index)1035 static void fileListPressed(int32_t index)
1036 {
1037 	char *nameTmp;
1038 	int32_t result;
1039 
1040 	const int32_t entryIndex = FReq_DirPos + index;
1041 	if (entryIndex >= FReq_FileCount || FReq_FileCount == 0)
1042 		return; // illegal entry
1043 
1044 	const int8_t mode = mouse.mode;
1045 
1046 	// set normal mouse cursor
1047 	if (mouse.mode != MOUSE_MODE_NORMAL)
1048 		setMouseMode(MOUSE_MODE_NORMAL);
1049 
1050 	// remove file selection
1051 	FReq_EntrySelected = -1;
1052 	diskOp_DrawFilelist();
1053 
1054 	DirRec *dirEntry = &FReq_Buffer[entryIndex];
1055 	switch (mode)
1056 	{
1057 		// open file/folder
1058 		default:
1059 		case MOUSE_MODE_NORMAL:
1060 		{
1061 			if (dirEntry->isDir)
1062 				openDirectory(dirEntry->nameU);
1063 			else
1064 				openFile(dirEntry->nameU, true);
1065 		}
1066 		break;
1067 
1068 		// delete file/folder
1069 		case MOUSE_MODE_DELETE:
1070 		{
1071 			if (!dirEntry->isDir || UNICHAR_STRCMP(dirEntry->nameU, PARENT_DIR_STR)) // don't handle ".." dir
1072 			{
1073 				nameTmp = unicharToCp437(dirEntry->nameU, true);
1074 				if (nameTmp == NULL)
1075 					break;
1076 
1077 				trimEntryName(nameTmp, dirEntry->isDir);
1078 
1079 				if (dirEntry->isDir)
1080 					sprintf(FReq_SysReqText, "Delete directory \"%s\"?", nameTmp);
1081 				else
1082 					sprintf(FReq_SysReqText, "Delete file \"%s\"?", nameTmp);
1083 
1084 				free(nameTmp);
1085 
1086 				if (okBox(2, "System request", FReq_SysReqText) == 1)
1087 				{
1088 					if (dirEntry->isDir)
1089 					{
1090 						result = deleteDirRecursive(dirEntry->nameU);
1091 						if (!result)
1092 							okBox(0, "System message", "Couldn't delete folder: Access denied!");
1093 						else
1094 							editor.diskOpReadDir = true;
1095 					}
1096 					else
1097 					{
1098 						result = (UNICHAR_REMOVE(dirEntry->nameU) == 0);
1099 						if (!result)
1100 							okBox(0, "System message", "Couldn't delete file: Access denied!");
1101 						else
1102 							editor.diskOpReadDir = true;
1103 					}
1104 				}
1105 			}
1106 		}
1107 		break;
1108 
1109 		// rename file/folder
1110 		case MOUSE_MODE_RENAME:
1111 		{
1112 			if (dirEntry->isDir || UNICHAR_STRCMP(dirEntry->nameU, PARENT_DIR_STR)) // don't handle ".." dir
1113 			{
1114 				nameTmp = unicharToCp437(dirEntry->nameU, true);
1115 				if (nameTmp == NULL)
1116 					break;
1117 
1118 				strncpy(FReq_NameTemp, nameTmp, PATH_MAX);
1119 				FReq_NameTemp[PATH_MAX] = '\0';
1120 				free(nameTmp);
1121 
1122 				// in case of UTF8 -> CP437 encoding failure, there can be question marks. Remove them...
1123 				removeQuestionmarksFromString(FReq_NameTemp);
1124 
1125 				if (inputBox(1, dirEntry->isDir ? "Enter new directory name:" : "Enter new filename:", FReq_NameTemp, PATH_MAX) == 1)
1126 				{
1127 					if (FReq_NameTemp == NULL || FReq_NameTemp[0] == '\0')
1128 					{
1129 						okBox(0, "System message", "New name can't be empty!");
1130 						break;
1131 					}
1132 
1133 					if (!renameAnsi(dirEntry->nameU, FReq_NameTemp))
1134 					{
1135 						if (dirEntry->isDir)
1136 							okBox(0, "System message", "Couldn't rename directory: Access denied, or dir already exists!");
1137 						else
1138 							okBox(0, "System message", "Couldn't rename file: Access denied, or file already exists!");
1139 					}
1140 					else
1141 					{
1142 						editor.diskOpReadDir = true;
1143 					}
1144 				}
1145 			}
1146 		}
1147 		break;
1148 	}
1149 }
1150 
testDiskOpMouseDown(bool mouseHeldDlown)1151 bool testDiskOpMouseDown(bool mouseHeldDlown)
1152 {
1153 	int32_t tmpEntry;
1154 
1155 	if (!ui.diskOpShown || FReq_FileCount == 0)
1156 		return false;
1157 
1158 	int32_t max = FReq_FileCount - FReq_DirPos;
1159 	if (max > DISKOP_ENTRY_NUM) // needed kludge when mouse-scrolling
1160 		max = DISKOP_ENTRY_NUM;
1161 
1162 	if (!mouseHeldDlown) // select file
1163 	{
1164 		FReq_EntrySelected = -1;
1165 
1166 		if (mouse.x >= 169 && mouse.x <= 331 && mouse.y >= 4 && mouse.y <= 168)
1167 		{
1168 			tmpEntry = (mouse.y - 4) / (FONT1_CHAR_H + 1);
1169 			if (tmpEntry >= 0 && tmpEntry < max)
1170 			{
1171 				FReq_EntrySelected = tmpEntry;
1172 				diskOp_DrawFilelist();
1173 			}
1174 
1175 			mouse.lastUsedObjectType = OBJECT_DISKOPLIST;
1176 			return true;
1177 		}
1178 
1179 		return false;
1180 	}
1181 
1182 	// handle scrolling if outside of disk op. list area
1183 	if (mouse.y < 4)
1184 	{
1185 		scrollBarScrollUp(SB_DISKOP_LIST, 1);
1186 		FReq_EntrySelected = -1;
1187 	}
1188 	else if (mouse.y > 168)
1189 	{
1190 		scrollBarScrollDown(SB_DISKOP_LIST, 1);
1191 		FReq_EntrySelected = -1;
1192 	}
1193 
1194 	if (mouse.y == lastMouseY)
1195 		return true;
1196 
1197 	lastMouseY = mouse.y;
1198 
1199 	tmpEntry = (mouse.y - 4) / (FONT1_CHAR_H + 1);
1200 	if (mouse.x < 169 || mouse.x > 331 || mouse.y < 4 || tmpEntry < 0 || tmpEntry >= max)
1201 	{
1202 		FReq_EntrySelected = -1;
1203 		diskOp_DrawFilelist();
1204 
1205 		return true;
1206 	}
1207 
1208 	if (tmpEntry != FReq_EntrySelected)
1209 	{
1210 		FReq_EntrySelected = tmpEntry;
1211 		diskOp_DrawFilelist();
1212 	}
1213 
1214 	return true;
1215 }
1216 
testDiskOpMouseRelease(void)1217 void testDiskOpMouseRelease(void)
1218 {
1219 	if (ui.diskOpShown && FReq_EntrySelected != -1)
1220 	{
1221 		if (mouse.x >= 169 && mouse.x <= 329 && mouse.y >= 4 && mouse.y <= 168)
1222 			fileListPressed((mouse.y - 4) / (FONT1_CHAR_H + 1));
1223 
1224 		FReq_EntrySelected = -1;
1225 		diskOp_DrawFilelist();
1226 	}
1227 }
1228 
moduleExtensionAccepted(char * extPtr)1229 static bool moduleExtensionAccepted(char *extPtr)
1230 {
1231 	int32_t i = 0;
1232 	while (true)
1233 	{
1234 		const char *str = supportedModExtensions[i++];
1235 		if (!_stricmp(str, "END_OF_LIST"))
1236 			return false;
1237 
1238 		if (!_stricmp(str, extPtr))
1239 			break;
1240 	}
1241 
1242 	return true;
1243 }
1244 
sampleExtensionAccepted(char * extPtr)1245 static bool sampleExtensionAccepted(char *extPtr)
1246 {
1247 	int32_t i = 0;
1248 	while (true)
1249 	{
1250 		const char *str = supportedSmpExtensions[i++];
1251 		if (!_stricmp(str, "END_OF_LIST"))
1252 			return false;
1253 
1254 		if (!_stricmp(str, extPtr))
1255 			break;
1256 	}
1257 
1258 	return true;
1259 }
1260 
handleEntrySkip(UNICHAR * nameU,bool isDir)1261 static uint8_t handleEntrySkip(UNICHAR *nameU, bool isDir)
1262 {
1263 	// skip if illegal name or filesize >32-bit
1264 	if (nameU == NULL)
1265 		return true;
1266 
1267 	char *name = unicharToCp437(nameU, false);
1268 	if (name == NULL)
1269 		return true;
1270 
1271 	if (name[0] == '\0')
1272 		goto skipEntry;
1273 
1274 	const int32_t nameLen = (int32_t)strlen(name);
1275 
1276 	// skip ".name" dirs/files
1277 	if (nameLen >= 2 && name[0] == '.' && name[1] != '.')
1278 		goto skipEntry;
1279 
1280 	if (isDir)
1281 	{
1282 		// skip '.' directory
1283 		if (nameLen == 1 && name[0] == '.')
1284 			goto skipEntry;
1285 
1286 		// macOS/Linux: skip '..' directory if we're in root
1287 #ifndef _WIN32
1288 		if (nameLen == 2 && name[0] == '.' && name[1] == '.')
1289 		{
1290 			if (FReq_CurPathU[0] == '/' && FReq_CurPathU[1] == '\0')
1291 				goto skipEntry;
1292 		}
1293 #endif
1294 	}
1295 	else if (!FReq_ShowAllFiles)
1296 	{
1297 		int32_t extOffset = getExtOffset(name, nameLen);
1298 		if (extOffset == -1)
1299 			goto skipEntry;
1300 		extOffset++; // skip '.'
1301 
1302 		const int32_t extLen = (int32_t)strlen(&name[extOffset]);
1303 		if (extLen < 2 || extLen > 6)
1304 			goto skipEntry; // no possibly known extensions to filter out
1305 
1306 		char *extPtr = &name[extOffset];
1307 
1308 		// decide what entries to keep based on file extension
1309 		switch (FReq_Item)
1310 		{
1311 			default:
1312 			case DISKOP_ITEM_MODULE:
1313 			{
1314 				if (editor.moduleSaveMode == MOD_SAVE_MODE_WAV && !_stricmp("wav", extPtr))
1315 					break; // show .wav files when save mode is "WAV"
1316 
1317 				if (!moduleExtensionAccepted(extPtr))
1318 					goto skipEntry;
1319 			}
1320 			break;
1321 
1322 			case DISKOP_ITEM_INSTR:
1323 			{
1324 				if (!_stricmp("xi", extPtr))
1325 					break;
1326 
1327 				if (!sampleExtensionAccepted(extPtr))
1328 					goto skipEntry;
1329 			}
1330 			break;
1331 
1332 			case DISKOP_ITEM_SAMPLE:
1333 			{
1334 				if (!sampleExtensionAccepted(extPtr))
1335 					goto skipEntry;
1336 			}
1337 			break;
1338 
1339 			case DISKOP_ITEM_PATTERN:
1340 			{
1341 				if (!_stricmp("xp", extPtr))
1342 					break;
1343 
1344 				goto skipEntry;
1345 			}
1346 			break;
1347 
1348 			case DISKOP_ITEM_TRACK:
1349 			{
1350 				if (!_stricmp("xt", extPtr))
1351 					break;
1352 
1353 				goto skipEntry;
1354 			}
1355 			break;
1356 		}
1357 	}
1358 
1359 	free(name);
1360 	return false; // "Show All Files" mode is enabled, don't skip entry
1361 
1362 skipEntry:
1363 	free(name);
1364 	return true;
1365 }
1366 
findFirst(DirRec * searchRec)1367 static int8_t findFirst(DirRec *searchRec)
1368 {
1369 #ifdef _WIN32
1370 	WIN32_FIND_DATAW fData;
1371 #else
1372 	struct dirent *fData;
1373 	struct stat st;
1374 	int64_t fSize;
1375 #endif
1376 
1377 #if defined(__sun) || defined(sun)
1378 	struct stat s;
1379 #endif
1380 
1381 	searchRec->nameU = NULL; // this one must be initialized
1382 
1383 #ifdef _WIN32
1384 	hFind = FindFirstFileW(L"*", &fData);
1385 	if (hFind == NULL || hFind == INVALID_HANDLE_VALUE)
1386 		return LFF_DONE;
1387 
1388 	searchRec->nameU = UNICHAR_STRDUP(fData.cFileName);
1389 	if (searchRec->nameU == NULL)
1390 		return LFF_SKIP;
1391 
1392 	searchRec->filesize = (fData.nFileSizeHigh > 0) ? -1 : fData.nFileSizeLow;
1393 	searchRec->isDir = (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false;
1394 #else
1395 	hFind = opendir(".");
1396 	if (hFind == NULL)
1397 		return LFF_DONE;
1398 
1399 	fData = readdir(hFind);
1400 	if (fData == NULL)
1401 		return LFF_DONE;
1402 
1403 	searchRec->nameU = UNICHAR_STRDUP(fData->d_name);
1404 	if (searchRec->nameU == NULL)
1405 		return LFF_SKIP;
1406 
1407 	searchRec->filesize = 0;
1408 
1409 #if defined(__sun) || defined(sun)
1410 	stat(fData->d_name, &s);
1411 	searchRec->isDir = (s.st_mode != S_IFDIR) ? true : false;
1412 #else
1413 	searchRec->isDir = (fData->d_type == DT_DIR) ? true : false;
1414 #endif
1415 
1416 #if defined(__sun) || defined(sun)
1417 	if (s.st_mode == S_IFLNK)
1418 #else
1419 	if (fData->d_type == DT_UNKNOWN || fData->d_type == DT_LNK)
1420 #endif
1421 	{
1422 		if (stat(fData->d_name, &st) == 0)
1423 		{
1424 			fSize = (int64_t)st.st_size;
1425 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
1426 
1427 			if ((st.st_mode & S_IFMT) == S_IFDIR)
1428 				searchRec->isDir = true;
1429 		}
1430 	}
1431 	else if (!searchRec->isDir)
1432 	{
1433 		if (stat(fData->d_name, &st) == 0)
1434 		{
1435 			fSize = (int64_t)st.st_size;
1436 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
1437 		}
1438 	}
1439 #endif
1440 
1441 	if (searchRec->filesize < -1)
1442 		searchRec->filesize = -1;
1443 
1444 	if (handleEntrySkip(searchRec->nameU, searchRec->isDir))
1445 	{
1446 		// skip file
1447 		free(searchRec->nameU);
1448 		searchRec->nameU = NULL;
1449 
1450 		return LFF_SKIP;
1451 	}
1452 
1453 	return LFF_OK;
1454 }
1455 
findNext(DirRec * searchRec)1456 static int8_t findNext(DirRec *searchRec)
1457 {
1458 #ifdef _WIN32
1459 	WIN32_FIND_DATAW fData;
1460 #else
1461 	struct dirent *fData;
1462 	struct stat st;
1463 	int64_t fSize;
1464 #endif
1465 
1466 #if defined(__sun) || defined(sun)
1467 	struct stat s;
1468 #endif
1469 
1470 	searchRec->nameU = NULL; // important
1471 
1472 #ifdef _WIN32
1473 	if (hFind == NULL || FindNextFileW(hFind, &fData) == 0)
1474 		return LFF_DONE;
1475 
1476 	searchRec->nameU = UNICHAR_STRDUP(fData.cFileName);
1477 	if (searchRec->nameU == NULL)
1478 		return LFF_SKIP;
1479 
1480 	searchRec->filesize = (fData.nFileSizeHigh > 0) ? -1 : fData.nFileSizeLow;
1481 	searchRec->isDir = (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false;
1482 #else
1483 	if (hFind == NULL || (fData = readdir(hFind)) == NULL)
1484 		return LFF_DONE;
1485 
1486 	searchRec->nameU = UNICHAR_STRDUP(fData->d_name);
1487 	if (searchRec->nameU == NULL)
1488 		return LFF_SKIP;
1489 
1490 	searchRec->filesize = 0;
1491 
1492 #if defined(__sun) || defined(sun)
1493 	stat(fData->d_name, &s);
1494 	searchRec->isDir = (s.st_mode != S_IFDIR) ? true : false;
1495 #else
1496 	searchRec->isDir = (fData->d_type == DT_DIR) ? true : false;
1497 #endif
1498 
1499 #if defined(__sun) || defined(sun)
1500 	if (s.st_mode == S_IFLNK)
1501 #else
1502 	if (fData->d_type == DT_UNKNOWN || fData->d_type == DT_LNK)
1503 #endif
1504 	{
1505 		if (stat(fData->d_name, &st) == 0)
1506 		{
1507 			fSize = (int64_t)st.st_size;
1508 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
1509 
1510 			if ((st.st_mode & S_IFMT) == S_IFDIR)
1511 				searchRec->isDir = true;
1512 		}
1513 	}
1514 	else if (!searchRec->isDir)
1515 	{
1516 		if (stat(fData->d_name, &st) == 0)
1517 		{
1518 			fSize = (int64_t)st.st_size;
1519 			searchRec->filesize = (fSize > INT32_MAX) ? -1 : (fSize & 0xFFFFFFFF);
1520 		}
1521 	}
1522 #endif
1523 
1524 	if (searchRec->filesize < -1)
1525 		searchRec->filesize = -1;
1526 
1527 	if (handleEntrySkip(searchRec->nameU, searchRec->isDir))
1528 	{
1529 		// skip file
1530 		free(searchRec->nameU);
1531 		searchRec->nameU = NULL;
1532 
1533 		return LFF_SKIP;
1534 	}
1535 
1536 	return LFF_OK;
1537 }
1538 
findClose(void)1539 static void findClose(void)
1540 {
1541 	if (hFind != NULL)
1542 	{
1543 #ifdef _WIN32
1544 		FindClose(hFind);
1545 #else
1546 		closedir(hFind);
1547 #endif
1548 		hFind = NULL;
1549 	}
1550 }
1551 
swapBufferEntry(int32_t a,int32_t b)1552 static bool swapBufferEntry(int32_t a, int32_t b) // used for sorting
1553 {
1554 	if (a >= FReq_FileCount || b >= FReq_FileCount)
1555 		return false;
1556 
1557 	DirRec tmpBuffer = FReq_Buffer[a];
1558 	FReq_Buffer[a] = FReq_Buffer[b];
1559 	FReq_Buffer[b] = tmpBuffer;
1560 
1561 	return true;
1562 }
1563 
ach(int32_t rad)1564 static char *ach(int32_t rad) // used for sortDirectory()
1565 {
1566 	DirRec *dirEntry = &FReq_Buffer[rad];
1567 
1568 	char *name = unicharToCp437(dirEntry->nameU, true);
1569 	if (name == NULL)
1570 		return NULL;
1571 
1572 	const int32_t nameLen = (int32_t)strlen(name);
1573 	if (nameLen == 0)
1574 	{
1575 		free(name);
1576 		return NULL;
1577 	}
1578 
1579 	char *p = (char *)malloc(nameLen+1+1);
1580 	if (p == NULL)
1581 	{
1582 		free(name);
1583 		return NULL;
1584 	}
1585 
1586 	if (dirEntry->isDir)
1587 	{
1588 		// directory
1589 
1590 		if (nameLen == 2 && name[0] == '.' && name[1] == '.')
1591 			p[0] = 0x01; // make ".." directory first priority
1592 		else
1593 			p[0] = 0x02; // make second priority
1594 
1595 		strcpy(&p[1], name);
1596 
1597 		free(name);
1598 		return p;
1599 	}
1600 	else
1601 	{
1602 		// file
1603 
1604 		const int32_t i = getExtOffset(name, nameLen);
1605 		if (config.cfg_SortPriority == 1 || i == -1)
1606 		{
1607 			// sort by filename
1608 			strcpy(p, name);
1609 			free(name);
1610 			return p;
1611 		}
1612 		else
1613 		{
1614 			// sort by filename extension
1615 			const int32_t extLen = nameLen - i;
1616 			if (extLen <= 1)
1617 			{
1618 				strcpy(p, name);
1619 				free(name);
1620 				return p;
1621 			}
1622 
1623 			// FILENAME.EXT -> EXT.FILENAME (for sorting)
1624 			memcpy(p, &name[i+1], extLen - 1);
1625 			memcpy(&p[extLen-1], name, i);
1626 			p[nameLen-1] = '\0';
1627 
1628 			free(name);
1629 			return p;
1630 		}
1631 	}
1632 }
1633 
sortDirectory(void)1634 static void sortDirectory(void)
1635 {
1636 	bool didSwap;
1637 
1638 	if (FReq_FileCount < 2)
1639 		return; // no need to sort
1640 
1641 	uint32_t offset = FReq_FileCount >> 1;
1642 	while (offset > 0)
1643 	{
1644 		const uint32_t limit = FReq_FileCount - offset;
1645 		do
1646 		{
1647 			didSwap = false;
1648 			for (uint32_t i = 0; i < limit; i++)
1649 			{
1650 				char *p1 = ach(i);
1651 				char *p2 = ach(offset+i);
1652 
1653 				if (p1 == NULL || p2 == NULL)
1654 				{
1655 					if (p1 != NULL) free(p1);
1656 					if (p2 != NULL) free(p2);
1657 					okBox(0, "System message", "Not enough memory!");
1658 					return;
1659 				}
1660 
1661 				if (_stricmp(p1, p2) > 0)
1662 				{
1663 					if (!swapBufferEntry(i, offset + i))
1664 					{
1665 						free(p1);
1666 						free(p2);
1667 						return;
1668 					}
1669 
1670 					didSwap = true;
1671 				}
1672 
1673 				free(p1);
1674 				free(p2);
1675 			}
1676 		}
1677 		while (didSwap);
1678 
1679 		offset >>= 1;
1680 	}
1681 }
1682 
numDigits32(uint32_t x)1683 static uint8_t numDigits32(uint32_t x)
1684 {
1685 	if (x >= 1000000000) return 10;
1686 	if (x >=  100000000) return  9;
1687 	if (x >=   10000000) return  8;
1688 	if (x >=    1000000) return  7;
1689 	if (x >=     100000) return  6;
1690 	if (x >=      10000) return  5;
1691 	if (x >=       1000) return  4;
1692 	if (x >=        100) return  3;
1693 	if (x >=         10) return  2;
1694 
1695 	return 1;
1696 }
1697 
printFormattedFilesize(uint16_t x,uint16_t y,uint32_t bufEntry)1698 static void printFormattedFilesize(uint16_t x, uint16_t y, uint32_t bufEntry)
1699 {
1700 	char sizeStrBuffer[16];
1701 	int32_t printFilesize;
1702 
1703 	const int32_t filesize = FReq_Buffer[bufEntry].filesize;
1704 	if (filesize == -1)
1705 	{
1706 		x += 6;
1707 		textOut(x, y, PAL_BLCKTXT, ">2GB");
1708 		return;
1709 	}
1710 
1711 	assert(filesize >= 0);
1712 
1713 	if (filesize >= 1024*1024*10) // >= 10MB?
1714 	{
1715 forceMB:
1716 		printFilesize = (int32_t)ceil(filesize / (1024.0*1024.0));
1717 		x += (4 - numDigits32(printFilesize)) * (FONT1_CHAR_W - 1);
1718 		sprintf(sizeStrBuffer, "%dM", printFilesize);
1719 	}
1720 	else if (filesize >= 1024*10) // >= 10kB?
1721 	{
1722 		printFilesize = (int32_t)ceil(filesize / 1024.0);
1723 		if (printFilesize > 9999)
1724 			goto forceMB; // ceil visual overflow kludge
1725 
1726 		x += (4 - numDigits32(printFilesize)) * (FONT1_CHAR_W - 1);
1727 		sprintf(sizeStrBuffer, "%dk", printFilesize);
1728 	}
1729 	else // bytes
1730 	{
1731 		printFilesize = filesize;
1732 		x += (5 - numDigits32(printFilesize)) * (FONT1_CHAR_W - 1);
1733 		sprintf(sizeStrBuffer, "%d", printFilesize);
1734 	}
1735 
1736 	textOut(x, y, PAL_BLCKTXT, sizeStrBuffer);
1737 }
1738 
displayCurrPath(void)1739 static void displayCurrPath(void)
1740 {
1741 	fillRect(4, 145, 162, 10, PAL_DESKTOP);
1742 
1743 	if (FReq_CurPathU == NULL)
1744 		return;
1745 
1746 	const uint32_t pathLen = (uint32_t)UNICHAR_STRLEN(FReq_CurPathU);
1747 	if (pathLen == 0)
1748 		return;
1749 
1750 	char *asciiPath = unicharToCp437(FReq_CurPathU, true);
1751 	if (asciiPath == NULL)
1752 	{
1753 		okBox(0, "System message", "Not enough memory!");
1754 		return;
1755 	}
1756 
1757 	char *p = asciiPath;
1758 	if (textWidth(p) <= 162)
1759 	{
1760 		// path fits, print it all
1761 		textOut(4, 145, PAL_FORGRND, p);
1762 	}
1763 	else
1764 	{
1765 		// path doesn't fit, print drive + ".." + last directory
1766 
1767 #ifdef _WIN32
1768 		memcpy(FReq_NameTemp, p, 3); // get drive (f.ex. C:\)
1769 		FReq_NameTemp[3] = '\0';
1770 
1771 		strcat(FReq_NameTemp, ".\001"); // special character in font
1772 		FReq_NameTemp[5] = '\0';
1773 #else
1774 		FReq_NameTemp[0] = '\0';
1775 		strcpy(FReq_NameTemp, "/");
1776 		strcat(FReq_NameTemp, "..");
1777 #endif
1778 
1779 		char *delimiter = strrchr(p, DIR_DELIMITER);
1780 		if (delimiter != NULL)
1781 		{
1782 #ifdef _WIN32
1783 			strcat(FReq_NameTemp, delimiter+1);
1784 #else
1785 			strcat(FReq_NameTemp, delimiter);
1786 #endif
1787 		}
1788 
1789 		int32_t j = (int32_t)strlen(FReq_NameTemp);
1790 		if (j > 6)
1791 		{
1792 			j--;
1793 
1794 			p = FReq_NameTemp;
1795 			while (j >= 6 && textWidth(p) >= 162)
1796 			{
1797 				p[j-2] = '.';
1798 				p[j-1] = '.';
1799 				p[j-0] = '\0';
1800 				j--;
1801 			}
1802 		}
1803 
1804 		textOutClipX(4, 145, PAL_FORGRND, FReq_NameTemp, 165);
1805 	}
1806 
1807 	free(asciiPath);
1808 }
1809 
diskOp_DrawFilelist(void)1810 void diskOp_DrawFilelist(void)
1811 {
1812 	clearRect(FILENAME_TEXT_X-1, 4, 162, 164);
1813 
1814 	if (FReq_FileCount == 0)
1815 		return;
1816 
1817 	// draw "selected file" rectangle
1818 	if (FReq_EntrySelected != -1)
1819 	{
1820 		const uint16_t y = 4 + (uint16_t)((FONT1_CHAR_H + 1) * FReq_EntrySelected);
1821 		fillRect(FILENAME_TEXT_X - 1, y, 162, FONT1_CHAR_H, PAL_PATTEXT);
1822 	}
1823 
1824 	for (uint16_t i = 0; i < DISKOP_ENTRY_NUM; i++)
1825 	{
1826 		const int32_t bufEntry = FReq_DirPos + i;
1827 		if (bufEntry >= FReq_FileCount)
1828 			break;
1829 
1830 		if (FReq_Buffer[bufEntry].nameU == NULL)
1831 			continue;
1832 
1833 		// convert unichar name to codepage 437
1834 		char *readName = unicharToCp437(FReq_Buffer[bufEntry].nameU, true);
1835 		if (readName == NULL)
1836 			continue;
1837 
1838 		const uint16_t y = 4 + (i * (FONT1_CHAR_H + 1));
1839 
1840 		// shrink entry name and add ".." if it doesn't fit on screen
1841 		trimEntryName(readName, FReq_Buffer[bufEntry].isDir);
1842 
1843 		if (FReq_Buffer[bufEntry].isDir)
1844 		{
1845 			// directory
1846 			charOut(FILENAME_TEXT_X, y, PAL_BLCKTXT, DIR_DELIMITER);
1847 			textOut(FILENAME_TEXT_X + FONT1_CHAR_W, y, PAL_BLCKTXT, readName);
1848 		}
1849 		else
1850 		{
1851 			// filename
1852 			textOut(FILENAME_TEXT_X, y, PAL_BLCKTXT, readName);
1853 		}
1854 
1855 		free(readName);
1856 
1857 		if (!FReq_Buffer[bufEntry].isDir)
1858 			printFormattedFilesize(FILESIZE_TEXT_X, y, bufEntry);
1859 	}
1860 }
1861 
diskOp_DrawDirectory(void)1862 void diskOp_DrawDirectory(void)
1863 {
1864 	drawTextBox(TB_DISKOP_FILENAME);
1865 
1866 	displayCurrPath();
1867 #ifdef _WIN32
1868 	setupDiskOpDrives();
1869 #endif
1870 
1871 	setScrollBarEnd(SB_DISKOP_LIST, FReq_FileCount);
1872 	setScrollBarPos(SB_DISKOP_LIST, FReq_DirPos, false);
1873 
1874 	diskOp_DrawFilelist();
1875 }
1876 
bufferCreateEmptyDir(void)1877 static DirRec *bufferCreateEmptyDir(void) // special case: creates a dir entry with a ".." directory
1878 {
1879 	DirRec *dirEntry = (DirRec *)malloc(sizeof (DirRec));
1880 	if (dirEntry == NULL)
1881 		return NULL;
1882 
1883 	dirEntry->nameU = UNICHAR_STRDUP(PARENT_DIR_STR);
1884 	if (dirEntry->nameU == NULL)
1885 	{
1886 		free(dirEntry);
1887 		return NULL;
1888 	}
1889 
1890 	dirEntry->isDir = true;
1891 	dirEntry->filesize = 0;
1892 
1893 	return dirEntry;
1894 }
1895 
diskOp_ReadDirectoryThread(void * ptr)1896 static int32_t SDLCALL diskOp_ReadDirectoryThread(void *ptr)
1897 {
1898 	DirRec tmpBuffer;
1899 
1900 	FReq_DirPos = 0;
1901 
1902 	// free old buffer
1903 	freeDirRecBuffer();
1904 
1905 	UNICHAR_GETCWD(FReq_CurPathU, PATH_MAX);
1906 
1907 	// read first file
1908 	int8_t lastFindFileFlag = findFirst(&tmpBuffer);
1909 	if (lastFindFileFlag != LFF_DONE && lastFindFileFlag != LFF_SKIP)
1910 	{
1911 		FReq_Buffer = (DirRec *)malloc(sizeof (DirRec) * (FReq_FileCount+1));
1912 		if (FReq_Buffer == NULL)
1913 		{
1914 			findClose();
1915 
1916 			okBoxThreadSafe(0, "System message", "Not enough memory!");
1917 
1918 			FReq_Buffer = bufferCreateEmptyDir();
1919 			if (FReq_Buffer != NULL)
1920 				FReq_FileCount = 1;
1921 			else
1922 				okBoxThreadSafe(0, "System message", "Not enough memory!");
1923 
1924 			setMouseBusy(false);
1925 			return false;
1926 		}
1927 
1928 		memcpy(&FReq_Buffer[FReq_FileCount], &tmpBuffer, sizeof (DirRec));
1929 		FReq_FileCount++;
1930 	}
1931 
1932 	// read remaining files
1933 	while (lastFindFileFlag != LFF_DONE)
1934 	{
1935 		lastFindFileFlag = findNext(&tmpBuffer);
1936 		if (lastFindFileFlag != LFF_DONE && lastFindFileFlag != LFF_SKIP)
1937 		{
1938 			DirRec *newPtr = (DirRec *)realloc(FReq_Buffer, sizeof (DirRec) * (FReq_FileCount + 1));
1939 			if (newPtr == NULL)
1940 			{
1941 				freeDirRecBuffer();
1942 				okBoxThreadSafe(0, "System message", "Not enough memory!");
1943 				break;
1944 			}
1945 
1946 			FReq_Buffer = newPtr;
1947 
1948 			memcpy(&FReq_Buffer[FReq_FileCount], &tmpBuffer, sizeof (DirRec));
1949 			FReq_FileCount++;
1950 		}
1951 	}
1952 
1953 	findClose();
1954 
1955 	if (FReq_FileCount > 0)
1956 	{
1957 		sortDirectory();
1958 	}
1959 	else
1960 	{
1961 		// access denied or out of memory - create parent directory link
1962 		FReq_Buffer = bufferCreateEmptyDir();
1963 		if (FReq_Buffer != NULL)
1964 			FReq_FileCount = 1;
1965 		else
1966 			okBoxThreadSafe(0, "System message", "Not enough memory!");
1967 	}
1968 
1969 	editor.diskOpReadDone = true;
1970 	setMouseBusy(false);
1971 
1972 	return true;
1973 
1974 	(void)ptr;
1975 }
1976 
diskOp_StartDirReadThread(void)1977 void diskOp_StartDirReadThread(void)
1978 {
1979 	editor.diskOpReadDone = false;
1980 
1981 	mouseAnimOn();
1982 	thread = SDL_CreateThread(diskOp_ReadDirectoryThread, NULL, NULL);
1983 	if (thread == NULL)
1984 	{
1985 		editor.diskOpReadDone = true;
1986 		okBox(0, "System message", "Couldn't create thread!");
1987 		return;
1988 	}
1989 
1990 	SDL_DetachThread(thread);
1991 }
1992 
drawSaveAsElements(void)1993 static void drawSaveAsElements(void)
1994 {
1995 	switch (FReq_Item)
1996 	{
1997 		default:
1998 		case DISKOP_ITEM_MODULE:
1999 		{
2000 			textOutShadow(19, 101, PAL_FORGRND, PAL_DSKTOP2, "MOD");
2001 			textOutShadow(19, 115, PAL_FORGRND, PAL_DSKTOP2, "XM");
2002 			textOutShadow(19, 129, PAL_FORGRND, PAL_DSKTOP2, "WAV");
2003 		}
2004 		break;
2005 
2006 		case DISKOP_ITEM_INSTR:
2007 			textOutShadow(19, 101, PAL_FORGRND, PAL_DSKTOP2, "XI");
2008 		break;
2009 
2010 		case DISKOP_ITEM_SAMPLE:
2011 		{
2012 			textOutShadow(19, 101, PAL_FORGRND, PAL_DSKTOP2, "RAW");
2013 			textOutShadow(19, 115, PAL_FORGRND, PAL_DSKTOP2, "IFF");
2014 			textOutShadow(19, 129, PAL_FORGRND, PAL_DSKTOP2, "WAV");
2015 		}
2016 		break;
2017 
2018 		case DISKOP_ITEM_PATTERN:
2019 			textOutShadow(19, 101, PAL_FORGRND, PAL_DSKTOP2, "XP");
2020 		break;
2021 
2022 		case DISKOP_ITEM_TRACK:
2023 			textOutShadow(19, 101, PAL_FORGRND, PAL_DSKTOP2, "XT");
2024 		break;
2025 	}
2026 }
2027 
setDiskOpItemRadioButtons(void)2028 static void setDiskOpItemRadioButtons(void)
2029 {
2030 	uncheckRadioButtonGroup(RB_GROUP_DISKOP_MOD_SAVEAS);
2031 	uncheckRadioButtonGroup(RB_GROUP_DISKOP_INS_SAVEAS);
2032 	uncheckRadioButtonGroup(RB_GROUP_DISKOP_SMP_SAVEAS);
2033 	uncheckRadioButtonGroup(RB_GROUP_DISKOP_PAT_SAVEAS);
2034 	uncheckRadioButtonGroup(RB_GROUP_DISKOP_TRK_SAVEAS);
2035 
2036 	hideRadioButtonGroup(RB_GROUP_DISKOP_MOD_SAVEAS);
2037 	hideRadioButtonGroup(RB_GROUP_DISKOP_INS_SAVEAS);
2038 	hideRadioButtonGroup(RB_GROUP_DISKOP_SMP_SAVEAS);
2039 	hideRadioButtonGroup(RB_GROUP_DISKOP_PAT_SAVEAS);
2040 	hideRadioButtonGroup(RB_GROUP_DISKOP_TRK_SAVEAS);
2041 
2042 	if (editor.moduleSaveMode > 3)
2043 		editor.moduleSaveMode = 3;
2044 
2045 	if (editor.sampleSaveMode > 3)
2046 		editor.sampleSaveMode = 3;
2047 
2048 	radioButtons[RB_DISKOP_MOD_SAVEAS_MOD + editor.moduleSaveMode].state = RADIOBUTTON_CHECKED;
2049 	radioButtons[RB_DISKOP_SMP_SAVEAS_RAW + editor.sampleSaveMode].state = RADIOBUTTON_CHECKED;
2050 
2051 	if (FReq_Item == DISKOP_ITEM_INSTR)   radioButtons[RB_DISKOP_INS_SAVEAS_XI].state = RADIOBUTTON_CHECKED;
2052 	if (FReq_Item == DISKOP_ITEM_PATTERN) radioButtons[RB_DISKOP_PAT_SAVEAS_XP].state = RADIOBUTTON_CHECKED;
2053 	if (FReq_Item == DISKOP_ITEM_TRACK)   radioButtons[RB_DISKOP_TRK_SAVEAS_XT].state = RADIOBUTTON_CHECKED;
2054 
2055 	if (ui.diskOpShown)
2056 	{
2057 		switch (FReq_Item)
2058 		{
2059 			default: case DISKOP_ITEM_MODULE:  showRadioButtonGroup(RB_GROUP_DISKOP_MOD_SAVEAS); break;
2060 			         case DISKOP_ITEM_INSTR:   showRadioButtonGroup(RB_GROUP_DISKOP_INS_SAVEAS); break;
2061 			         case DISKOP_ITEM_SAMPLE:  showRadioButtonGroup(RB_GROUP_DISKOP_SMP_SAVEAS); break;
2062 			         case DISKOP_ITEM_PATTERN: showRadioButtonGroup(RB_GROUP_DISKOP_PAT_SAVEAS); break;
2063 			         case DISKOP_ITEM_TRACK:   showRadioButtonGroup(RB_GROUP_DISKOP_TRK_SAVEAS); break;
2064 		}
2065 	}
2066 }
2067 
setDiskOpItem(uint8_t item)2068 static void setDiskOpItem(uint8_t item)
2069 {
2070 	hideRadioButtonGroup(RB_GROUP_DISKOP_MOD_SAVEAS);
2071 	hideRadioButtonGroup(RB_GROUP_DISKOP_INS_SAVEAS);
2072 	hideRadioButtonGroup(RB_GROUP_DISKOP_SMP_SAVEAS);
2073 	hideRadioButtonGroup(RB_GROUP_DISKOP_PAT_SAVEAS);
2074 	hideRadioButtonGroup(RB_GROUP_DISKOP_TRK_SAVEAS);
2075 
2076 	if (item > 4)
2077 		item = 4;
2078 
2079 	FReq_Item = item;
2080 	switch (FReq_Item)
2081 	{
2082 		default:
2083 		case DISKOP_ITEM_MODULE:
2084 		{
2085 			FReq_FileName = modTmpFName;
2086 
2087 			// FReq_ModCurPathU is always set at this point
2088 
2089 			FReq_CurPathU = FReq_ModCurPathU;
2090 			if (FReq_CurPathU != NULL && FReq_CurPathU[0] != '\0')
2091 				UNICHAR_CHDIR(FReq_CurPathU);
2092 		}
2093 		break;
2094 
2095 		case DISKOP_ITEM_INSTR:
2096 		{
2097 			FReq_FileName = insTmpFName;
2098 
2099 			if (!insPathSet && FReq_CurPathU != NULL && FReq_CurPathU[0] != '\0')
2100 			{
2101 				UNICHAR_STRCPY(FReq_InsCurPathU, FReq_CurPathU);
2102 				insPathSet = true;
2103 			}
2104 
2105 			FReq_CurPathU = FReq_InsCurPathU;
2106 			if (FReq_CurPathU != NULL)
2107 				UNICHAR_CHDIR(FReq_CurPathU);
2108 		}
2109 		break;
2110 
2111 		case DISKOP_ITEM_SAMPLE:
2112 		{
2113 			FReq_FileName = smpTmpFName;
2114 
2115 			if (!smpPathSet && FReq_CurPathU != NULL && FReq_CurPathU[0] != '\0')
2116 			{
2117 				UNICHAR_STRCPY(FReq_SmpCurPathU, FReq_CurPathU);
2118 				smpPathSet = true;
2119 			}
2120 
2121 			FReq_CurPathU = FReq_SmpCurPathU;
2122 			if (FReq_CurPathU != NULL)
2123 				UNICHAR_CHDIR(FReq_CurPathU);
2124 		}
2125 		break;
2126 
2127 		case DISKOP_ITEM_PATTERN:
2128 		{
2129 			FReq_FileName = patTmpFName;
2130 
2131 			if (!patPathSet && FReq_CurPathU != NULL && FReq_CurPathU[0] != '\0')
2132 			{
2133 				UNICHAR_STRCPY(FReq_PatCurPathU, FReq_CurPathU);
2134 				patPathSet = true;
2135 			}
2136 
2137 			FReq_CurPathU = FReq_PatCurPathU;
2138 			if (FReq_CurPathU != NULL)
2139 				UNICHAR_CHDIR(FReq_CurPathU);
2140 		}
2141 		break;
2142 
2143 		case DISKOP_ITEM_TRACK:
2144 		{
2145 			FReq_FileName = trkTmpFName;
2146 
2147 			if (!trkPathSet && FReq_CurPathU != NULL && FReq_CurPathU[0] != '\0')
2148 			{
2149 				UNICHAR_STRCPY(FReq_TrkCurPathU, FReq_CurPathU);
2150 				trkPathSet = true;
2151 			}
2152 
2153 			FReq_CurPathU = FReq_TrkCurPathU;
2154 			if (FReq_CurPathU != NULL)
2155 				UNICHAR_CHDIR(FReq_CurPathU);
2156 		}
2157 		break;
2158 	}
2159 
2160 	if (FReq_CurPathU != NULL && FReq_ModCurPathU != NULL)
2161 	{
2162 		if (FReq_CurPathU[0] == '\0' && FReq_ModCurPathU[0] != '\0')
2163 			UNICHAR_STRCPY(FReq_CurPathU, FReq_ModCurPathU);
2164 	}
2165 
2166 	textBoxes[TB_DISKOP_FILENAME].textPtr = FReq_FileName;
2167 	FReq_ShowAllFiles = false;
2168 
2169 	if (ui.diskOpShown)
2170 	{
2171 		editor.diskOpReadDir = true;
2172 
2173 		fillRect(4, 101, 40, 38, PAL_DESKTOP);
2174 		drawSaveAsElements();
2175 		setDiskOpItemRadioButtons();
2176 
2177 		diskOp_DrawDirectory();
2178 		drawTextBox(TB_DISKOP_FILENAME);
2179 	}
2180 	else
2181 	{
2182 		editor.diskOpReadOnOpen = true;
2183 	}
2184 }
2185 
drawDiskOpScreen(void)2186 static void drawDiskOpScreen(void)
2187 {
2188 	drawFramework(0,     0,  67,  86, FRAMEWORK_TYPE1);
2189 	drawFramework(67,    0,  64, 142, FRAMEWORK_TYPE1);
2190 	drawFramework(131,   0,  37, 142, FRAMEWORK_TYPE1);
2191 	drawFramework(0,    86,  67,  56, FRAMEWORK_TYPE1);
2192 	drawFramework(0,   142, 168,  31, FRAMEWORK_TYPE1);
2193 	drawFramework(168,   0, 164,   3, FRAMEWORK_TYPE1);
2194 	drawFramework(168, 170, 164,   3, FRAMEWORK_TYPE1);
2195 	drawFramework(332,   0,  24, 173, FRAMEWORK_TYPE1);
2196 	drawFramework(30,  157, 136,  14, FRAMEWORK_TYPE2);
2197 
2198 	clearRect(168, 2, 164, 168);
2199 
2200 	showPushButton(PB_DISKOP_SAVE);
2201 	showPushButton(PB_DISKOP_DELETE);
2202 	showPushButton(PB_DISKOP_RENAME);
2203 	showPushButton(PB_DISKOP_MAKEDIR);
2204 	showPushButton(PB_DISKOP_REFRESH);
2205 	showPushButton(PB_DISKOP_EXIT);
2206 	showPushButton(PB_DISKOP_PARENT);
2207 	showPushButton(PB_DISKOP_ROOT);
2208 	showPushButton(PB_DISKOP_SHOW_ALL);
2209 	showPushButton(PB_DISKOP_SET_PATH);
2210 	showPushButton(PB_DISKOP_LIST_UP);
2211 	showPushButton(PB_DISKOP_LIST_DOWN);
2212 
2213 	showScrollBar(SB_DISKOP_LIST);
2214 	showTextBox(TB_DISKOP_FILENAME);
2215 
2216 	textBoxes[TB_DISKOP_FILENAME].textPtr = FReq_FileName;
2217 
2218 	if (FReq_Item > 4)
2219 		FReq_Item = 4;
2220 
2221 	uncheckRadioButtonGroup(RB_GROUP_DISKOP_ITEM);
2222 	radioButtons[RB_DISKOP_MODULE + FReq_Item].state = RADIOBUTTON_CHECKED;
2223 	showRadioButtonGroup(RB_GROUP_DISKOP_ITEM);
2224 
2225 	// item selector
2226 	textOutShadow(5,   3, PAL_FORGRND, PAL_DSKTOP2, "Item:");
2227 	textOutShadow(19, 17, PAL_FORGRND, PAL_DSKTOP2, "Module");
2228 	textOutShadow(19, 31, PAL_FORGRND, PAL_DSKTOP2, "Instr.");
2229 	textOutShadow(19, 45, PAL_FORGRND, PAL_DSKTOP2, "Sample");
2230 	textOutShadow(19, 59, PAL_FORGRND, PAL_DSKTOP2, "Pattern");
2231 	textOutShadow(19, 73, PAL_FORGRND, PAL_DSKTOP2, "Track");
2232 
2233 	// file format
2234 	textOutShadow(5,  89, PAL_FORGRND, PAL_DSKTOP2, "Save as:");
2235 	drawSaveAsElements();
2236 	setDiskOpItemRadioButtons();
2237 
2238 	// filename
2239 	textOutShadow(4, 159, PAL_FORGRND, PAL_DSKTOP2, "File:");
2240 
2241 	diskOp_DrawDirectory();
2242 }
2243 
showDiskOpScreen(void)2244 void showDiskOpScreen(void)
2245 {
2246 	// if first time opening Disk Op., set initial directory
2247 	if (firstTimeOpeningDiskOp)
2248 	{
2249 		assert(FReq_ModCurPathU != NULL);
2250 
2251 		// first test if we can change the dir to the one stored in the config (if present)
2252 		if (FReq_ModCurPathU[0] == '\0' || UNICHAR_CHDIR(FReq_ModCurPathU) != 0)
2253 		{
2254 			// nope, couldn't do that, set Disk Op. path to user/home directory
2255 #ifdef _WIN32
2256 			SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, FReq_ModCurPathU);
2257 #else
2258 			char *home = getenv("HOME");
2259 			if (home != NULL)
2260 				UNICHAR_STRCPY(FReq_ModCurPathU, home);
2261 #endif
2262 			UNICHAR_CHDIR(FReq_ModCurPathU);
2263 		}
2264 
2265 		UNICHAR_GETCWD(FReq_ModCurPathU, PATH_MAX);
2266 		firstTimeOpeningDiskOp = false;
2267 	}
2268 
2269 	if (ui.extended)
2270 		exitPatternEditorExtended();
2271 
2272 	hideTopScreen();
2273 	ui.diskOpShown = true;
2274 	ui.scopesShown = false;
2275 
2276 	showTopRightMainScreen();
2277 	drawDiskOpScreen();
2278 
2279 	// a flag that says if we need to update the filelist after disk op. was shown
2280 	if (editor.diskOpReadOnOpen)
2281 	{
2282 		editor.diskOpReadOnOpen = false;
2283 		diskOp_StartDirReadThread();
2284 	}
2285 }
2286 
hideDiskOpScreen(void)2287 void hideDiskOpScreen(void)
2288 {
2289 #ifdef _WIN32
2290 	for (uint16_t i = 0; i < DISKOP_MAX_DRIVE_BUTTONS; i++)
2291 		hidePushButton(PB_DISKOP_DRIVE1 + i);
2292 #endif
2293 
2294 	hidePushButton(PB_DISKOP_SAVE);
2295 	hidePushButton(PB_DISKOP_DELETE);
2296 	hidePushButton(PB_DISKOP_RENAME);
2297 	hidePushButton(PB_DISKOP_MAKEDIR);
2298 	hidePushButton(PB_DISKOP_REFRESH);
2299 	hidePushButton(PB_DISKOP_EXIT);
2300 	hidePushButton(PB_DISKOP_PARENT);
2301 	hidePushButton(PB_DISKOP_ROOT);
2302 	hidePushButton(PB_DISKOP_SHOW_ALL);
2303 	hidePushButton(PB_DISKOP_SET_PATH);
2304 	hidePushButton(PB_DISKOP_LIST_UP);
2305 	hidePushButton(PB_DISKOP_LIST_DOWN);
2306 
2307 	hideScrollBar(SB_DISKOP_LIST);
2308 	hideTextBox(TB_DISKOP_FILENAME);
2309 	hideRadioButtonGroup(RB_GROUP_DISKOP_ITEM);
2310 	hideRadioButtonGroup(RB_GROUP_DISKOP_MOD_SAVEAS);
2311 	hideRadioButtonGroup(RB_GROUP_DISKOP_INS_SAVEAS);
2312 	hideRadioButtonGroup(RB_GROUP_DISKOP_SMP_SAVEAS);
2313 	hideRadioButtonGroup(RB_GROUP_DISKOP_PAT_SAVEAS);
2314 	hideRadioButtonGroup(RB_GROUP_DISKOP_TRK_SAVEAS);
2315 
2316 	ui.diskOpShown = false;
2317 }
2318 
exitDiskOpScreen(void)2319 void exitDiskOpScreen(void)
2320 {
2321 	hideDiskOpScreen();
2322 	ui.oldTopLeftScreen = 0; // disk op. ignores previously opened top screens
2323 	showTopScreen(true);
2324 }
2325 
toggleDiskOpScreen(void)2326 void toggleDiskOpScreen(void)
2327 {
2328 	if (ui.diskOpShown)
2329 		exitDiskOpScreen();
2330 	else
2331 		showDiskOpScreen();
2332 }
2333 
sbDiskOpSetPos(uint32_t pos)2334 void sbDiskOpSetPos(uint32_t pos)
2335 {
2336 	if ((int32_t)pos != FReq_DirPos && FReq_FileCount > DISKOP_ENTRY_NUM)
2337 	{
2338 		FReq_DirPos = (int32_t)pos;
2339 		diskOp_DrawFilelist();
2340 	}
2341 }
2342 
pbDiskOpListUp(void)2343 void pbDiskOpListUp(void)
2344 {
2345 	if (FReq_DirPos > 0 && FReq_FileCount > DISKOP_ENTRY_NUM)
2346 		scrollBarScrollUp(SB_DISKOP_LIST, 1);
2347 }
2348 
pbDiskOpListDown(void)2349 void pbDiskOpListDown(void)
2350 {
2351 	if (FReq_DirPos < FReq_FileCount-DISKOP_ENTRY_NUM && FReq_FileCount > DISKOP_ENTRY_NUM)
2352 		scrollBarScrollDown(SB_DISKOP_LIST, 1);
2353 }
2354 
pbDiskOpParent(void)2355 void pbDiskOpParent(void)
2356 {
2357 	diskOpGoParent();
2358 }
2359 
pbDiskOpRoot(void)2360 void pbDiskOpRoot(void)
2361 {
2362 #ifdef _WIN32
2363 	openDirectory(L"\\");
2364 #else
2365 	openDirectory("/");
2366 #endif
2367 }
2368 
pbDiskOpShowAll(void)2369 void pbDiskOpShowAll(void)
2370 {
2371 	FReq_ShowAllFiles = true;
2372 	editor.diskOpReadDir = true; // refresh dir
2373 }
2374 
2375 #ifdef _WIN32
pbDiskOpDrive1(void)2376 void pbDiskOpDrive1(void) { openDrive(logicalDriveNames[driveIndexes[0]]); }
pbDiskOpDrive2(void)2377 void pbDiskOpDrive2(void) { openDrive(logicalDriveNames[driveIndexes[1]]); }
pbDiskOpDrive3(void)2378 void pbDiskOpDrive3(void) { openDrive(logicalDriveNames[driveIndexes[2]]); }
pbDiskOpDrive4(void)2379 void pbDiskOpDrive4(void) { openDrive(logicalDriveNames[driveIndexes[3]]); }
pbDiskOpDrive5(void)2380 void pbDiskOpDrive5(void) { openDrive(logicalDriveNames[driveIndexes[4]]); }
pbDiskOpDrive6(void)2381 void pbDiskOpDrive6(void) { openDrive(logicalDriveNames[driveIndexes[5]]); }
pbDiskOpDrive7(void)2382 void pbDiskOpDrive7(void) { openDrive(logicalDriveNames[driveIndexes[6]]); }
pbDiskOpDrive8(void)2383 void pbDiskOpDrive8(void) { openDrive(logicalDriveNames[driveIndexes[7]]); }
2384 #endif
2385 
pbDiskOpDelete(void)2386 void pbDiskOpDelete(void)
2387 {
2388 	setMouseMode(MOUSE_MODE_DELETE);
2389 }
2390 
pbDiskOpRename(void)2391 void pbDiskOpRename(void)
2392 {
2393 	setMouseMode(MOUSE_MODE_RENAME);
2394 }
2395 
pbDiskOpMakeDir(void)2396 void pbDiskOpMakeDir(void)
2397 {
2398 	FReq_NameTemp[0] = '\0';
2399 	if (inputBox(1, "Enter directory name:", FReq_NameTemp, PATH_MAX) == 1)
2400 	{
2401 		if (FReq_NameTemp[0] == '\0')
2402 		{
2403 			okBox(0, "System message", "Name can't be empty!");
2404 			return;
2405 		}
2406 
2407 		if (makeDirAnsi(FReq_NameTemp))
2408 			editor.diskOpReadDir = true;
2409 		else
2410 			okBox(0, "System message", "Couldn't create directory: Access denied, or a dir with the same name already exists!");
2411 	}
2412 }
2413 
pbDiskOpRefresh(void)2414 void pbDiskOpRefresh(void)
2415 {
2416 	editor.diskOpReadDir = true; // refresh dir
2417 #ifdef _WIN32
2418 	setupDiskOpDrives();
2419 #endif
2420 }
2421 
pbDiskOpSetPath(void)2422 void pbDiskOpSetPath(void)
2423 {
2424 	FReq_NameTemp[0] = '\0';
2425 	if (inputBox(1, "Enter new directory path:", FReq_NameTemp, PATH_MAX) == 1)
2426 	{
2427 		if (FReq_NameTemp[0] == '\0')
2428 		{
2429 			okBox(0, "System message", "Name can't be empty!");
2430 			return;
2431 		}
2432 
2433 		if (chdir(FReq_NameTemp) == 0)
2434 			editor.diskOpReadDir = true;
2435 		else
2436 			okBox(0, "System message", "Couldn't set directory path!");
2437 	}
2438 }
2439 
pbDiskOpExit(void)2440 void pbDiskOpExit(void)
2441 {
2442 	exitDiskOpScreen();
2443 }
2444 
rbDiskOpModule(void)2445 void rbDiskOpModule(void)
2446 {
2447 	checkRadioButton(RB_DISKOP_MODULE);
2448 	setDiskOpItem(DISKOP_ITEM_MODULE);
2449 }
2450 
rbDiskOpInstr(void)2451 void rbDiskOpInstr(void)
2452 {
2453 	checkRadioButton(RB_DISKOP_INSTR);
2454 	setDiskOpItem(DISKOP_ITEM_INSTR);
2455 }
2456 
rbDiskOpSample(void)2457 void rbDiskOpSample(void)
2458 {
2459 	checkRadioButton(RB_DISKOP_SAMPLE);
2460 	setDiskOpItem(DISKOP_ITEM_SAMPLE);
2461 }
2462 
rbDiskOpPattern(void)2463 void rbDiskOpPattern(void)
2464 {
2465 	checkRadioButton(RB_DISKOP_PATTERN);
2466 	setDiskOpItem(DISKOP_ITEM_PATTERN);
2467 }
2468 
rbDiskOpTrack(void)2469 void rbDiskOpTrack(void)
2470 {
2471 	checkRadioButton(RB_DISKOP_TRACK);
2472 	setDiskOpItem(DISKOP_ITEM_TRACK);
2473 }
2474 
rbDiskOpModSaveMod(void)2475 void rbDiskOpModSaveMod(void)
2476 {
2477 	if (editor.moduleSaveMode == MOD_SAVE_MODE_WAV)
2478 		editor.diskOpReadDir = true;
2479 
2480 	editor.moduleSaveMode = MOD_SAVE_MODE_MOD;
2481 	checkRadioButton(RB_DISKOP_MOD_SAVEAS_MOD);
2482 	diskOpChangeFilenameExt(".mod");
2483 
2484 	updateCurrSongFilename(); // for window title
2485 	updateWindowTitle(true);
2486 }
2487 
rbDiskOpModSaveXm(void)2488 void rbDiskOpModSaveXm(void)
2489 {
2490 	if (editor.moduleSaveMode == MOD_SAVE_MODE_WAV)
2491 		editor.diskOpReadDir = true;
2492 
2493 	editor.moduleSaveMode = MOD_SAVE_MODE_XM;
2494 	checkRadioButton(RB_DISKOP_MOD_SAVEAS_XM);
2495 	diskOpChangeFilenameExt(".xm");
2496 
2497 	updateCurrSongFilename(); // for window title
2498 	updateWindowTitle(true);
2499 }
2500 
rbDiskOpModSaveWav(void)2501 void rbDiskOpModSaveWav(void)
2502 {
2503 	editor.moduleSaveMode = MOD_SAVE_MODE_WAV;
2504 	checkRadioButton(RB_DISKOP_MOD_SAVEAS_WAV);
2505 	diskOpChangeFilenameExt(".wav");
2506 
2507 	updateCurrSongFilename(); // for window title
2508 	updateWindowTitle(true);
2509 
2510 	editor.diskOpReadDir = true;
2511 }
2512 
rbDiskOpSmpSaveRaw(void)2513 void rbDiskOpSmpSaveRaw(void)
2514 {
2515 	editor.sampleSaveMode = SMP_SAVE_MODE_RAW;
2516 	checkRadioButton(RB_DISKOP_SMP_SAVEAS_RAW);
2517 	diskOpChangeFilenameExt(".raw");
2518 }
2519 
rbDiskOpSmpSaveIff(void)2520 void rbDiskOpSmpSaveIff(void)
2521 {
2522 	editor.sampleSaveMode = SMP_SAVE_MODE_IFF;
2523 	checkRadioButton(RB_DISKOP_SMP_SAVEAS_IFF);
2524 	diskOpChangeFilenameExt(".iff");
2525 }
2526 
rbDiskOpSmpSaveWav(void)2527 void rbDiskOpSmpSaveWav(void)
2528 {
2529 	editor.sampleSaveMode = SMP_SAVE_MODE_WAV;
2530 	checkRadioButton(RB_DISKOP_SMP_SAVEAS_WAV);
2531 	diskOpChangeFilenameExt(".wav");
2532 }
2533