ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

ft2_diskop.c (56973B)


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