/*
  Hatari - gemdos.c

  This file is distributed under the GNU General Public License, version 2
  or at your option any later version. Read the file gpl.txt for details.

  GEMDOS intercept routines.
  These are used mainly for hard drive redirection of high level file routines.

  Host file names are handled case insensitively, so files on GEMDOS
  drive emulation directories may be either in lower or upper case.

  Too long file and directory names and names with invalid characters
  are converted to TOS compatible 8+3 names, but matching them back to
  host names is slower and may match several such filenames (of which
  first one will be returned), so using them should be avoided.

  Bugs/things to fix:
  * Host filenames are in many places limited to 255 chars (same as
    on TOS), FILENAME_MAX should be used if that's a problem.
  * rmdir routine, can't remove dir with files in it. (another tos/unix difference)
  * Fix bugs, there are probably a few lurking around in here..
*/
const char Gemdos_fileid[] = "Hatari gemdos.c : " __DATE__ " " __TIME__;

#include <config.h>

#include <sys/stat.h>
#if HAVE_STATVFS
#include <sys/statvfs.h>
#endif
#include <sys/types.h>
#include <utime.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>

#include "main.h"
#include "cart.h"
#include "tos.h"
#include "configuration.h"
#include "file.h"
#include "floppy.h"
#include "ide.h"
#include "hdc.h"
#include "gemdos.h"
#include "gemdos_defines.h"
#include "log.h"
#include "m68000.h"
#include "memorySnapShot.h"
#include "printer.h"
#include "rs232.h"
#include "statusbar.h"
#include "scandir.h"
#include "stMemory.h"
#include "str.h"
#include "hatari-glue.h"
#include "maccess.h"
#include "symbols.h"

/* Maximum supported length of a GEMDOS path: */
#define MAX_GEMDOS_PATH 256

/* Have we re-directed GemDOS vector to our own routines yet? */
bool bInitGemDOS;

/* structure with all the drive-specific data for our emulated drives,
 * used by GEMDOS_EMU_ON macro
 */
EMULATEDDRIVE **emudrives = NULL;

#define  ISHARDDRIVE(Drive)  (Drive!=-1)

/*
  Disk Transfer Address (DTA)
*/
#define TOS_NAMELEN  14

typedef struct {
  Uint8 index[2];
  Uint8 magic[4];
  char dta_pat[TOS_NAMELEN];
  char dta_sattrib;
  char dta_attrib;
  Uint8 dta_time[2];
  Uint8 dta_date[2];
  Uint8 dta_size[4];
  char dta_name[TOS_NAMELEN];
} DTA;

#define DTA_MAGIC_NUMBER  0x12983476
#define MAX_DTAS_FILES    256      /* Must be ^2 */
#define CALL_PEXEC_ROUTINE 3       /* Call our cartridge pexec routine */

#define  BASE_FILEHANDLE     64    /* Our emulation handles - MUST not be valid TOS ones, but MUST be <256 */
#define  MAX_FILE_HANDLES    32    /* We can allow 32 files open at once */

/*
   DateTime structure used by TOS call $57 f_dtatime
   Changed to fix potential problem with alignment.
*/
typedef struct {
  Uint16 timeword;
  Uint16 dateword;
} DATETIME;

#define UNFORCED_HANDLE -1
static struct {
	int Handle;
	Uint32 Basepage;
} ForcedHandles[5]; /* (standard) handles aliased to emulated handles */

typedef struct
{
	bool bUsed;
	Uint32 Basepage;
	FILE *FileHandle;
	/* TODO: host path might not fit into this */
	char szActualName[MAX_GEMDOS_PATH];        /* used by F_DATIME (0x57) */
} FILE_HANDLE;

typedef struct
{
	bool bUsed;
	int  nentries;                      /* number of entries in fs directory */
	int  centry;                        /* current entry # */
	struct dirent **found;              /* legal files */
	char path[MAX_GEMDOS_PATH];                /* sfirst path */
} INTERNAL_DTA;

static FILE_HANDLE  FileHandles[MAX_FILE_HANDLES];
static INTERNAL_DTA InternalDTAs[MAX_DTAS_FILES];
static int DTAIndex;        /* Circular index into above */
static DTA *pDTA;           /* Our GEMDOS hard drive Disk Transfer Address structure */
static Uint16 CurrentDrive; /* Current drive (0=A,1=B,2=C etc...) */
static Uint32 act_pd;       /* Used to get a pointer to the current basepage */
static Uint16 nAttrSFirst;  /* File attribute for SFirst/Snext */

/* last program opened by GEMDOS emulation */
static bool PexecCalled;

#if defined(WIN32) && !defined(mkdir)
#define mkdir(name,mode) mkdir(name)
#endif  /* WIN32 */

#ifndef S_IRGRP
#define S_IRGRP 0
#define S_IROTH 0
#endif

/* set to 1 if you want to see debug output from pattern matching */
#define DEBUG_PATTERN_MATCH 0


/*-------------------------------------------------------*/
/**
 * Routine to convert time and date to GEMDOS format.
 * Originally from the STonX emulator. (cheers!)
 */
static void GemDOS_DateTime2Tos(time_t t, DATETIME *DateTime, const char *fname)
{
	struct tm *x;

	/* localtime takes DST into account */
	x = localtime(&t);

	if (x == NULL)
	{
		Log_Printf(LOG_WARN, "WARNING: '%s' timestamp is invalid for (Windows?) localtime(), defaulting to TOS epoch!",  fname);
		DateTime->dateword = 1|(1<<5);	/* 1980-01-01 */
		DateTime->timeword = 0;
		return;
	}
	/* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */
	DateTime->timeword = (x->tm_sec>>1)|(x->tm_min<<5)|(x->tm_hour<<11);
	
	/* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */
	DateTime->dateword = x->tm_mday | ((x->tm_mon+1)<<5)
		| (((x->tm_year-80 > 0) ? x->tm_year-80 : 0) << 9);
}

/*-----------------------------------------------------------------------*/
/**
 * Populate a DATETIME structure with file info.  Handle needs to be
 * validated before calling.  Return true on success.
 */
static bool GemDOS_GetFileInformation(int Handle, DATETIME *DateTime)
{
	const char *fname = FileHandles[Handle].szActualName;
	struct stat fstat;

	if (stat(fname, &fstat) == 0)
	{
		GemDOS_DateTime2Tos(fstat.st_mtime, DateTime, fname);
		return true;
	}
	return false;
}

/*-----------------------------------------------------------------------*/
/**
 * Set given file date/time from given DATETIME.  Handle needs to be
 * validated before calling.  Return true on success.
 */
static bool GemDOS_SetFileInformation(int Handle, DATETIME *DateTime)
{
	const char *filename;
	struct utimbuf timebuf;
	struct stat filestat;
	struct tm timespec;

	/* make sure Hatari itself doesn't need to write/modify
	 * the file after it's modification time is changed.
	 */
	fflush(FileHandles[Handle].FileHandle);
	filename = FileHandles[Handle].szActualName;
	
	/* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */
	timespec.tm_sec  = (DateTime->timeword & 0x1F) << 1;
	timespec.tm_min  = (DateTime->timeword & 0x7E0) >> 5;
	timespec.tm_hour = (DateTime->timeword & 0xF800) >> 11;
	/* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */
	timespec.tm_mday = (DateTime->dateword & 0x1F);
	timespec.tm_mon  = ((DateTime->dateword & 0x1E0) >> 5) - 1;
	timespec.tm_year = ((DateTime->dateword & 0xFE00) >> 9) + 80;
	/* check whether DST should be taken into account */
	timespec.tm_isdst = -1;

	/* set new modification time */
	timebuf.modtime = mktime(&timespec);

	/* but keep previous access time */
	if (stat(filename, &filestat) != 0)
		return false;
	timebuf.actime = filestat.st_atime;

	if (utime(filename, &timebuf) != 0)
		return false;
	// fprintf(stderr, "set date '%s' for %s\n", asctime(&timespec), name);
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * Convert from FindFirstFile/FindNextFile attribute to GemDOS format
 */
static Uint8 GemDOS_ConvertAttribute(mode_t mode)
{
	Uint8 Attrib = 0;

	/* Directory attribute */
	if (S_ISDIR(mode))
		Attrib |= GEMDOS_FILE_ATTRIB_SUBDIRECTORY;

	/* Read-only attribute */
	if (!(mode & S_IWUSR))
		Attrib |= GEMDOS_FILE_ATTRIB_READONLY;

	/* TODO, Other attributes:
	 * - GEMDOS_FILE_ATTRIB_HIDDEN (file not visible on desktop/fsel)
	 * - GEMDOS_FILE_ATTRIB_ARCHIVE (file written after being backed up)
	 * ?
	 */
	return Attrib;
}


/*-----------------------------------------------------------------------*/
/**
 * Populate the DTA buffer with file info.
 * @return   0 if entry is ok, 1 if entry should be skipped, < 0 for errors.
 */
static int PopulateDTA(char *path, struct dirent *file)
{
	/* TODO: host file path can be longer than MAX_GEMDOS_PATH */
	char tempstr[MAX_GEMDOS_PATH];
	struct stat filestat;
	DATETIME DateTime;
	int nFileAttr, nAttrMask;

	snprintf(tempstr, sizeof(tempstr), "%s%c%s", path, PATHSEP, file->d_name);

	if (stat(tempstr, &filestat) != 0)
	{
		perror(tempstr);
		return -1;   /* return on error */
	}

	if (!pDTA)
		return -2;   /* no DTA pointer set */

	/* Check file attributes (check is done according to the Profibuch) */
	nFileAttr = GemDOS_ConvertAttribute(filestat.st_mode);
	nAttrMask = nAttrSFirst|GEMDOS_FILE_ATTRIB_WRITECLOSE|GEMDOS_FILE_ATTRIB_READONLY;
	if (nFileAttr != 0 && !(nAttrMask & nFileAttr))
		return 1;

	GemDOS_DateTime2Tos(filestat.st_mtime, &DateTime, tempstr);

	/* convert to atari-style uppercase */
	Str_Filename2TOSname(file->d_name, pDTA->dta_name);
#if DEBUG_PATTERN_MATCH
	fprintf(stderr, "GEMDOS: host: %s -> GEMDOS: %s\n",
		file->d_name, pDTA->dta_name);
#endif
	do_put_mem_long(pDTA->dta_size, filestat.st_size);
	do_put_mem_word(pDTA->dta_time, DateTime.timeword);
	do_put_mem_word(pDTA->dta_date, DateTime.dateword);
	pDTA->dta_attrib = nFileAttr;

	return 0;
}


/*-----------------------------------------------------------------------*/
/**
 * Clear a used DTA structure.
 */
static void ClearInternalDTA(void)
{
	int i;

	/* clear the old DTA structure */
	if (InternalDTAs[DTAIndex].found != NULL)
	{
		for (i=0; i < InternalDTAs[DTAIndex].nentries; i++)
			free(InternalDTAs[DTAIndex].found[i]);
		free(InternalDTAs[DTAIndex].found);
		InternalDTAs[DTAIndex].found = NULL;
	}
	InternalDTAs[DTAIndex].nentries = 0;
	InternalDTAs[DTAIndex].bUsed = false;
}


/*-----------------------------------------------------------------------*/
/**
 * Match a TOS file name to a dir mask.
 */
static bool fsfirst_match(const char *pat, const char *name)
{
	const char *dot, *p=pat, *n=name;

	if (name[0] == '.')
		return false;           /* skip .* files */
	if (strcmp(pat,"*.*")==0)
		return true;            /* match everything */
	if (strcasecmp(pat,name)==0)
		return true;            /* exact case insensitive match */

	dot = strrchr(name, '.');	/* '*' matches everything except _last_ '.' */
	while (*n)
	{
		if (*p=='*')
		{
			while (*n && n != dot)
				n++;
			p++;
		}
		else
		{
			if (*p=='?' && *n)
			{
				n++;
				p++;
			}
			else
			{
				if (toupper((unsigned char)*p++) != toupper((unsigned char)*n++))
					return false;
			}
		}
	}

	/* The name matches the pattern if it ends here, too */
	return (*p == 0 || (*p == '*' && *(p+1) == 0));
}


/*-----------------------------------------------------------------------*/
/**
 * Parse directory from sfirst mask
 * - e.g.: input:  "hdemudir/auto/mask*.*" outputs: "hdemudir/auto"
 */
static void fsfirst_dirname(const char *string, char *newstr)
{
	int i=0;

	strcpy(newstr, string);

	/* convert to front slashes and go to end of string. */
	while (newstr[i] != '\0')
	{
		if (newstr[i] == '\\')
			newstr[i] = PATHSEP;
		i++;
	}
	/* find last slash and terminate string */
	while (i && newstr[i] != PATHSEP)
		i--;
	newstr[i] = '\0';
}


/*-----------------------------------------------------------------------*/
/**
 * Return directory mask part from the given string
 */
static const char* fsfirst_dirmask(const char *string)
{
	const char *lastsep;

	lastsep = strrchr(string, PATHSEP);
	if (lastsep)
		return lastsep + 1;
	else
		return string;
}

/*-----------------------------------------------------------------------*/
/**
 * Close given internal file handle if it's still in use
 * and (always) reset handle variables
 */
static void GemDOS_CloseFileHandle(int i)
{
	if (FileHandles[i].bUsed)
		fclose(FileHandles[i].FileHandle);
	FileHandles[i].FileHandle = NULL;
	FileHandles[i].Basepage = 0;
	FileHandles[i].bUsed = false;
}

/**
 * Un-force given file handle
 */
static void GemDOS_UnforceFileHandle(int i)
{
	ForcedHandles[i].Handle = UNFORCED_HANDLE;
	ForcedHandles[i].Basepage = 0;
}

/*-----------------------------------------------------------------------*/

/**
 * If program was executed, store path to it
 * (should be called only by Fopen)
 */
static void GemDOS_UpdateCurrentProgram(int Handle)
{
	/* only first Fopen after Pexec needs to be handled */
	if (!PexecCalled)
		return;
	PexecCalled = false;

	/* store program path */
	Symbols_ChangeCurrentProgram(FileHandles[Handle].FileHandle,
				     FileHandles[Handle].szActualName);
}

/*-----------------------------------------------------------------------*/
/**
 * Initialize GemDOS/PC file system
 */
void GemDOS_Init(void)
{
	int i;
	bInitGemDOS = false;

	/* Clear handles structure */
	memset(FileHandles, 0, sizeof(FileHandles));
	for(i = 0; i < ARRAYSIZE(ForcedHandles); i++)
	{
		GemDOS_UnforceFileHandle(i);
	}
	/* Clear DTAs */
	for(i = 0; i < ARRAYSIZE(InternalDTAs); i++)
	{
		InternalDTAs[i].bUsed = false;
		InternalDTAs[i].nentries = 0;
		InternalDTAs[i].found = NULL;
	}
	DTAIndex = 0;
}


/*-----------------------------------------------------------------------*/
/**
 * Reset GemDOS file system
 */
void GemDOS_Reset(void)
{
	int i;

	/* Init file handles table */
	for (i = 0; i < ARRAYSIZE(FileHandles); i++)
	{
		GemDOS_CloseFileHandle(i);
	}
	for(i = 0; i < ARRAYSIZE(ForcedHandles); i++)
	{
		GemDOS_UnforceFileHandle(i);
	}
	for (DTAIndex = 0; DTAIndex < MAX_DTAS_FILES; DTAIndex++)
	{
		ClearInternalDTA();
	}
	DTAIndex = 0;

	/* Reset */
	bInitGemDOS = false;
	CurrentDrive = nBootDrive;
	Symbols_RemoveCurrentProgram();
	pDTA = NULL;
}

/*-----------------------------------------------------------------------*/
/**
 * Routine to check the Host OS HDD path for a Drive letter sub folder
 */
static bool GEMDOS_DoesHostDriveFolderExist(char* lpstrPath, int iDrive)
{
	bool bExist = false;

	if (access(lpstrPath, F_OK) != 0 )
	{
		/* Try lower case drive letter instead */
		int	iIndex = strlen(lpstrPath)-1;
		lpstrPath[iIndex] = tolower((unsigned char)lpstrPath[iIndex]);
	}

	/* Check the file/folder is accessible (security basis) */
	if (access(lpstrPath, F_OK) == 0 )
	{
		 /* If its a HDD identifier (or other emulated device) */
		if (iDrive > 1)
		{
			struct stat status;
			stat( lpstrPath, &status );
			if ( status.st_mode & S_IFDIR )
				bExist = true;
		}
	}

	return bExist;
}


/**
 * Determine upper limit of partitions that should be emulated.
 *
 * @return true if multiple GEMDOS partitions should be emulated, false otherwise
 */
static bool GemDOS_DetermineMaxPartitions(int *pnMaxDrives)
{
	struct dirent **files;
	int count, i, last;
	char letter;
	bool bMultiPartitions;

	*pnMaxDrives = 0;

	/* Scan through the main directory to see whether there are just single
	 * letter sub-folders there (then use multi-partition mode) or if
	 * arbitrary sub-folders are there (then use single-partition mode */
	count = scandir(ConfigureParams.HardDisk.szHardDiskDirectories[0], &files, 0, alphasort);
	if (count < 0)
	{
		Log_Printf(LOG_ERROR, "Error: GEMDOS hard disk emulation failed:\n "
			   "Can not access '%s'.\n", ConfigureParams.HardDisk.szHardDiskDirectories[0]);
		return false;
	}
	else if (count <= 2)
	{
		/* Empty directory Only "." and ".."), assume single partition mode */
		last = 1;
		bMultiPartitions = false;
	}
	else
	{
		bMultiPartitions = true;
		/* Check all files in the directory */
		last = 0;
		for (i = 0; i < count; i++)
		{
			letter = toupper((unsigned char)files[i]->d_name[0]);
			if (!letter || letter == '.')
			{
				/* Ignore hidden files like "." and ".." */
				continue;
			}
			
			if (letter < 'C' || letter > 'Z' || files[i]->d_name[1])
			{
				/* folder with name other than C-Z...
				 * (until Z under MultiTOS, to P otherwise)
				 * ... so use single partition mode! */
				last = 1;
				bMultiPartitions = false;
				break;
			}

			/* alphasort isn't case insensitive */
			letter = letter - 'C' + 1;
			if (letter > last)
				last = letter;
		}
	}

	if (last > MAX_HARDDRIVES)
		*pnMaxDrives = MAX_HARDDRIVES;
	else
		*pnMaxDrives = last;

	/* Free file list */
	for (i = 0; i < count; i++)
		free(files[i]);
	free(files);

	return bMultiPartitions;
}

/*-----------------------------------------------------------------------*/
/**
 * Initialize a GEMDOS drive.
 * Supports up to MAX_HARDDRIVES HDD units.
 */
void GemDOS_InitDrives(void)
{
	int i;
	int nMaxDrives;
	int DriveNumber;
	int SkipPartitions;
	int ImagePartitions;
	bool bMultiPartitions;

	bMultiPartitions = GemDOS_DetermineMaxPartitions(&nMaxDrives);

	/* intialize data for harddrive emulation: */
	if (nMaxDrives > 0 && !emudrives)
	{
		emudrives = malloc(MAX_HARDDRIVES * sizeof(EMULATEDDRIVE *));
		if (!emudrives)
		{
			perror("GemDOS_InitDrives");
			return;
		}
		memset(emudrives, 0, MAX_HARDDRIVES * sizeof(EMULATEDDRIVE *));
	}

	ImagePartitions = nAcsiPartitions + nIDEPartitions;
	if (ConfigureParams.HardDisk.nHardDiskDrive == DRIVE_SKIP)
		SkipPartitions = ImagePartitions;
	else
		SkipPartitions = ConfigureParams.HardDisk.nHardDiskDrive;

	/* Now initialize all available drives */
	for(i = 0; i < nMaxDrives; i++)
	{
		/* If single partition mode, skip to specified / first free drive */
		if (!bMultiPartitions)
		{
			i += SkipPartitions;
		}

		/* Allocate emudrives entry for this drive */
		emudrives[i] = malloc(sizeof(EMULATEDDRIVE));
		if (!emudrives[i])
		{
			perror("GemDOS_InitDrives");
			continue;
		}

		/* set emulation directory string */
		strcpy(emudrives[i]->hd_emulation_dir, ConfigureParams.HardDisk.szHardDiskDirectories[0]);

		/* remove trailing slash, if any in the directory name */
		File_CleanFileName(emudrives[i]->hd_emulation_dir);

		/* Add Requisit Folder ID */
		if (bMultiPartitions)
		{
			char sDriveLetter[] = { PATHSEP, (char)('C' + i), '\0' };
			strcat(emudrives[i]->hd_emulation_dir, sDriveLetter);
		}
		/* drive number (C: = 2, D: = 3, etc.) */
		DriveNumber = 2 + i;

		// Check host file system to see if the drive folder for THIS
		// drive letter/number exists...
		if (GEMDOS_DoesHostDriveFolderExist(emudrives[i]->hd_emulation_dir, DriveNumber))
		{
			/* initialize current directory string, too (initially the same as hd_emulation_dir) */
			strcpy(emudrives[i]->fs_currpath, emudrives[i]->hd_emulation_dir);
			File_AddSlashToEndFileName(emudrives[i]->fs_currpath);    /* Needs trailing slash! */

			/* map drive */
			Log_Printf(LOG_INFO, "GEMDOS HDD emulation, %c: <-> %s.\n",
				   'A'+DriveNumber, emudrives[i]->hd_emulation_dir);
			emudrives[i]->drive_number = DriveNumber;
			nNumDrives = i + 3;

			/* This letter may already be allocated to the one supported physical disk images
			 * (depends on how well Atari HD driver and Hatari interpretation of partition
			 *  table(s) match each other).
			 */
			if (i < ImagePartitions)
				Log_Printf(LOG_WARN, "WARNING: GEMDOS HD drive %c: (may) override ACSI/IDE image partitions!\n", 'A'+DriveNumber);
		}
		else
		{
			free(emudrives[i]);	// Deallocate Memory (save space)
			emudrives[i] = NULL;
		}
	}
}


/*-----------------------------------------------------------------------*/
/**
 * Un-init the GEMDOS drive
 */
void GemDOS_UnInitDrives(void)
{
	int i;

	GemDOS_Reset();        /* Close all open files on emulated drive */

	if (GEMDOS_EMU_ON)
	{
		for(i = 0; i < MAX_HARDDRIVES; i++)
		{
			if (emudrives[i])
			{
				free(emudrives[i]);    /* Release memory */
				emudrives[i] = NULL;
				nNumDrives -= 1;
			}
		}

		free(emudrives);
		emudrives = NULL;
	}
}


/*-----------------------------------------------------------------------*/
/**
 * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
 */
void GemDOS_MemorySnapShot_Capture(bool bSave)
{
	unsigned int Addr;
	int i;
	bool bEmudrivesAvailable;

	/* Save/Restore the emudrives structure */
	bEmudrivesAvailable = (emudrives != NULL);
	MemorySnapShot_Store(&bEmudrivesAvailable, sizeof(bEmudrivesAvailable));
	if (bEmudrivesAvailable)
	{
		if (!emudrives)
		{
			/* As memory snapshot contained emulated drive(s),
			 * but currently there are none allocated yet...
			 * let's do it now!
			 */
			GemDOS_InitDrives();
		}

		for(i = 0; i < MAX_HARDDRIVES; i++)
		{
			int bDummyDrive = false;
			if (!emudrives[i])
			{
				/* Allocate a dummy drive */
				emudrives[i] = malloc(sizeof(EMULATEDDRIVE));
				if (!emudrives[i])
				{
					perror("GemDOS_MemorySnapShot_Capture");
					continue;
				}
				memset(emudrives[i], 0, sizeof(EMULATEDDRIVE));
				bDummyDrive = true;
			}
			MemorySnapShot_Store(emudrives[i]->hd_emulation_dir,
			                     sizeof(emudrives[i]->hd_emulation_dir));
			MemorySnapShot_Store(emudrives[i]->fs_currpath,
			                     sizeof(emudrives[i]->fs_currpath));
			MemorySnapShot_Store(&emudrives[i]->drive_number,
			                     sizeof(emudrives[i]->drive_number));
			if (bDummyDrive)
			{
				free(emudrives[i]);
				emudrives[i] = NULL;
			}
		}
	}

	/* Save/Restore details */
	MemorySnapShot_Store(&DTAIndex,sizeof(DTAIndex));
	MemorySnapShot_Store(&bInitGemDOS,sizeof(bInitGemDOS));
	MemorySnapShot_Store(&act_pd, sizeof(act_pd));
	if (bSave)
	{
		Addr = ((Uint8 *)pDTA - STRam);
		MemorySnapShot_Store(&Addr,sizeof(Addr));
	}
	else
	{
		MemorySnapShot_Store(&Addr,sizeof(Addr));
		pDTA = (DTA *)(STRam + Addr);
	}
	MemorySnapShot_Store(&CurrentDrive,sizeof(CurrentDrive));
	/* Don't save file handles as files may have changed which makes
	 * it impossible to get a valid handle back
	 */
	if (!bSave)
	{
		/* Clear file handles  */
		for(i = 0; i < ARRAYSIZE(FileHandles); i++)
		{
			GemDOS_CloseFileHandle(i);
		}
		for(i = 0; i < ARRAYSIZE(ForcedHandles); i++)
		{
			GemDOS_UnforceFileHandle(i);
		}
	}
}


/*-----------------------------------------------------------------------*/
/**
 * Return free PC file handle table index, or -1 if error
 */
static int GemDOS_FindFreeFileHandle(void)
{
	int i;

	/* Scan our file list for free slot */
	for(i = 0; i < ARRAYSIZE(FileHandles); i++)
	{
		if (!FileHandles[i].bUsed)
			return i;
	}

	/* Cannot open any more files, return error */
	return -1;
}

/*-----------------------------------------------------------------------*/
/**
 * Check whether given basepage matches current program basepage
 * or basepage for its parents.  If yes, return true, otherwise false.
 */
static bool GemDOS_BasepageMatches(Uint32 checkbase)
{
	int maxparents = 12; /* prevent basepage parent loops */
	Uint32 basepage = STMemory_ReadLong(act_pd);
	while (maxparents-- > 0 && STMemory_ValidArea(basepage, 0x100))
	{
		if (basepage == checkbase)
			return true;
		basepage = STMemory_ReadLong(basepage + 0x24);	/* parent */
	}
	return false;
}

/**
 * Check whether TOS handle is within our table range, or aliased,
 * return (positive) internal Handle if yes, (negative) -1 for error.
 */
static int GemDOS_GetValidFileHandle(int Handle)
{
	int Forced = -1;

	/* Has handle been aliased with Fforce()? */
	if (Handle >= 0 && Handle < ARRAYSIZE(ForcedHandles)
	    && ForcedHandles[Handle].Handle != UNFORCED_HANDLE)
	{
		if (GemDOS_BasepageMatches(ForcedHandles[Handle].Basepage))
		{
			Forced = Handle;
			Handle = ForcedHandles[Handle].Handle;
		}
		else
		{
			Log_Printf(LOG_WARN, "Removing (stale?) %d->%d file handle redirection.",
				   Handle, ForcedHandles[Handle].Handle);
			GemDOS_UnforceFileHandle(Handle);
			return -1;
		}
	}
	else
	{
		Handle -= BASE_FILEHANDLE;
	}
	/* handle is valid for current program and in our handle table? */
	if (Handle >= 0 && Handle < ARRAYSIZE(FileHandles)
	    && FileHandles[Handle].bUsed)
	{
		Uint32 current = STMemory_ReadLong(act_pd);
		if (FileHandles[Handle].Basepage == current || Forced >= 0)
			return Handle;
		/* bug in Atari program or in Hatari GEMDOS emu */
		Log_Printf(LOG_WARN, "PREVENTED: program 0x%x accessing program 0x%x file handle %d.",
			     current, FileHandles[Handle].Basepage, Handle);
	}
	/* invalid handle */
	return -1;
}

/*-----------------------------------------------------------------------*/
/**
 * Find drive letter from a filename, eg C,D... and return as drive ID(C:2, D:3...)
 * returns the current drive number if no drive is specified.  For special
 * devices (CON:, AUX:, PRN:), returns an invalid drive number.
 */
static int GemDOS_FindDriveNumber(char *pszFileName)
{
	/* Does have 'A:' or 'C:' etc.. at start of string? */
	if (pszFileName[0] != '\0' && pszFileName[1] == ':')
	{
		char letter = toupper((unsigned char)pszFileName[0]);
		if (letter >= 'A' && letter <= 'Z')
			return (letter-'A');
	}
	else if (strlen(pszFileName) == 4 && pszFileName[3] == ':')
	{
		/* ':' can be used only as drive indicator, not otherwise,
		 * so no need to check even special device name.
		 */
		return 0;
	}
	return CurrentDrive;
}


/**
 * Return true if drive ID (C:2, D:3 etc...) matches emulated hard-drive
 */
bool GemDOS_IsDriveEmulated(int drive)
{
	drive -= 2;
	if (drive < 0 || drive >= MAX_HARDDRIVES)
		return false;
	if (!(emudrives && emudrives[drive]))
		return false;
	assert(emudrives[drive]->drive_number == drive+2);
	return true;
}

/*-----------------------------------------------------------------------*/
/**
 * Return drive ID(C:2, D:3 etc...) or -1 if not one of our emulation hard-drives
 */
static int GemDOS_FileName2HardDriveID(char *pszFileName)
{
	/* Do we even have a hard-drive? */
	if (GEMDOS_EMU_ON)
	{
		int DriveNumber;

		/* Find drive letter (as number) */
		DriveNumber = GemDOS_FindDriveNumber(pszFileName);
		if (GemDOS_IsDriveEmulated(DriveNumber))
			return DriveNumber;
	}

	/* Not a high-level redirected drive, let TOS handle it */
	return -1;
}


/*-----------------------------------------------------------------------*/
/**
 * Check whether a file in given path matches given case-insensitive pattern.
 * Return first matched name which caller needs to free, or NULL for no match.
 */
static char* match_host_dir_entry(const char *path, const char *name, bool pattern)
{
#define MAX_UTF8_NAME_LEN (3*(8+1+3)+1) /* UTF-8 can have up to 3 bytes per character */
	struct dirent *entry;
	char *match = NULL;
	DIR *dir;
	char nameHost[MAX_UTF8_NAME_LEN];

	Str_AtariToHost(name, nameHost, MAX_UTF8_NAME_LEN, INVALID_CHAR);
	name = nameHost;
	
	dir = opendir(path);
	if (!dir)
		return NULL;

#if DEBUG_PATTERN_MATCH
	fprintf(stderr, "GEMDOS match '%s'%s in '%s'", name, pattern?" (pattern)":"", path);
#endif
	if (pattern)
	{
		while ((entry = readdir(dir)))
		{
			Str_DecomposedToPrecomposedUtf8(entry->d_name, entry->d_name);   /* for OSX */
			if (fsfirst_match(name, entry->d_name))
			{
				match = strdup(entry->d_name);
				break;
			}
		}
	}
	else
	{
		while ((entry = readdir(dir)))
		{
			Str_DecomposedToPrecomposedUtf8(entry->d_name, entry->d_name);   /* for OSX */
			if (strcasecmp(name, entry->d_name) == 0)
			{
				match = strdup(entry->d_name);
				break;
			}
		}
	}
	closedir(dir);
#if DEBUG_PATTERN_MATCH
	fprintf(stderr, "-> '%s'\n", match);
#endif
	return match;
}


static int to_same(int ch)
{
	return ch;
}

/**
 * Clip given file name to 8+3 length like TOS does,
 * return resulting name length.
 */
static int clip_to_83(char *name)
{
	int diff, len;
	char *dot;
	
	dot = strchr(name, '.');
	if (dot) {
		diff = strlen(dot) - 4;
		if (diff > 0)
		{
			Log_Printf(LOG_WARN, "WARNING: have to clip %d chars from '%s' extension!\n", diff, name);
			dot[4] = '\0';
		}
		diff = dot - name - 8;
		if (diff > 0)
		{
			Log_Printf(LOG_WARN, "WARNING: have to clip %d chars from '%s' base!\n", diff, name);
			memmove(name + 8, dot, strlen(dot) + 1);
		}
		return strlen(name);
	}
	len = strlen(name);
	if (len > 8)
	{
		Log_Printf(LOG_WARN, "WARNING: have to clip %d chars from '%s'!\n", len - 8, name);
		name[8] = '\0';
		len = 8;
	}
	return len;
}

/*-----------------------------------------------------------------------*/
/**
 * Check whether given TOS file/dir exists in given host path.
 * If it does, add the matched host filename to the given path,
 * otherwise add the given filename as is to it.  Guarantees
 * that the resulting string doesn't exceed maxlen+1.
 * 
 * Return true if match found, false otherwise.
 */
static bool add_path_component(char *path, int maxlen, const char *origname, bool is_dir)
{
	char *tmp, *match, name[strlen(origname) + 3];
	int dot, namelen, pathlen;
	int (*chr_conv)(int);
	bool modified;

	/* append separator */
	pathlen = strlen(path);
	if (pathlen >= maxlen)
		return false;
	path[pathlen++] = PATHSEP;
	path[pathlen] = '\0';

	/* TOS clips names to 8+3 length */
	strcpy(name, origname);
	namelen = clip_to_83(name);

	/* first try exact (case insensitive) match */
	match = match_host_dir_entry(path, name, false);
	if (match)
	{
		/* use strncat so that string is always nul terminated */
		strncat(path+pathlen, match, maxlen-pathlen);
		free(match);
		return true;
	}

	/* Here comes a work-around for a bug in the file selector
	 * of TOS 1.02: When a folder name has exactly 8 characters,
	 * it appends a '.' at the end of the name...
	 */
	if (is_dir && namelen == 9 && name[8] == '.')
	{
		name[8] = '\0';
		match = match_host_dir_entry(path, name, false);
		if (match)
		{
			strncat(path+pathlen, match, maxlen-pathlen);
			free(match);
			return true;
		}
	}

	/* Assume there were invalid characters or that the host file
	 * was too long to fit into GEMDOS 8+3 filename limits.
	 * If that's the case, modify the name to a pattern that
	 * will match such host files and try again.
	 */
	modified = false;

	/* catch potentially invalid characters */
	for (tmp = name; *tmp; tmp++)
	{
		if (*tmp == INVALID_CHAR)
		{
			*tmp = '?';
			modified = true;
		}
	}

	/* catch potentially too long extension */
	for (dot = 0; name[dot] && name[dot] != '.'; dot++);
	if (namelen - dot > 3)
	{
		dot++;
		/* "emulated.too" -> "emulated.too*" */
		name[namelen++] = '*';
		name[namelen] = '\0';
		modified = true;
	}
	/* catch potentially too long part before extension */
	if (namelen > 8 && name[8] == '.')
	{
		dot++;
		/* "emulated.too*" -> "emulated*.too*" */
		memmove(name+9, name+8, namelen-7);
		namelen++;
		name[8] = '*';
		modified = true;
	}
	/* catch potentially too long part without extension */
	else if (namelen == 8 && !name[dot])
	{
		/* "emulated" -> "emulated*" */
		name[8] = '*';
		name[9] = '\0';
		namelen++;
		modified = true;
	}

	if (modified)
	{
		match = match_host_dir_entry(path, name, true);
		if (match)
		{
			strncat(path+pathlen, match, maxlen-pathlen);
			free(match);
			return true;
		}
	}

	/* not found, copy file/dirname as is */
	switch (ConfigureParams.HardDisk.nGemdosCase) {
	case GEMDOS_UPPER:
		chr_conv = toupper;
		break;
	case GEMDOS_LOWER:
		chr_conv = tolower;
		break;
	default:
		chr_conv = to_same;
	}
	tmp = name;
	while (*origname)
		*tmp++ = chr_conv(*origname++);
	*tmp = '\0';
	/* strncat(path+pathlen, name, maxlen-pathlen); */
	Str_AtariToHost(name, path+pathlen, maxlen-pathlen, INVALID_CHAR);
	return false;
}


/**
 * Join remaining path without matching. This helper is used after host
 * file name matching fails, to append the failing part of the TOS path
 * to the host path, so that it won't be a valid host path.
 *
 * Specifically, the path separators need to be converted, otherwise things
 * like Fcreate() could create files that have TOS directory names as part
 * of file names on Unix (as \ is valid filename char on Unix).  Fcreate()
 * needs to create them only when just the file name isn't found, but all
 * the directory components have.
 */
static void add_remaining_path(const char *src, char *dstpath, int dstlen)
{
	char *dst;
	int i = strlen(dstpath);

	Str_AtariToHost(src, dstpath+i, dstlen-i, INVALID_CHAR);

	for (dst = dstpath + i; *dst; dst++)
		if (*dst == '\\')
			*dst = PATHSEP;
}


/*-----------------------------------------------------------------------*/
/**
 * Use hard-drive directory, current ST directory and filename
 * to create correct path to host file system.  If given filename
 * isn't found on host file system, just append GEMDOS filename
 * to the path as is.
 * 
 * TODO: currently there are many callers which give this dest buffer of
 * MAX_GEMDOS_PATH size i.e. don't take into account that host filenames
 * can be up to FILENAME_MAX long.  Plain GEMDOS paths themselves may be
 * MAX_GEMDOS_PATH long even before host dir is prepended to it!
 * Way forward: allocate the host path here as FILENAME_MAX so that
 * it's always long enough and let callers free it. Assert if alloc
 * fails so that callers' don't need to.
 */
void GemDOS_CreateHardDriveFileName(int Drive, const char *pszFileName,
                                    char *pszDestName, int nDestNameLen)
{
	const char *s, *filename = pszFileName;
	int minlen;

	/* make sure that more convenient strncat() can be used on the
	 * destination string (it always null terminates unlike strncpy()) */
	*pszDestName = 0;

	/* Is it a valid hard drive? */
	assert(GemDOS_IsDriveEmulated(Drive));

	/* Check for valid string */
	if (filename[0] == '\0')
		return;

	/* strcat writes n+1 chars, so decrease len */
	nDestNameLen--;
	
	/* full filename with drive "C:\foo\bar" */
	if (filename[1] == ':')
	{
		strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen);
		filename += 2;
	}
	/* filename referenced from root: "\foo\bar" */
	else if (filename[0] == '\\')
	{
		strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen);
	}
	/* filename relative to current directory */
	else
	{
		strncat(pszDestName, emudrives[Drive-2]->fs_currpath, nDestNameLen);
	}

	minlen = strlen(emudrives[Drive-2]->hd_emulation_dir);
	/* this doesn't take into account possible long host filenames
	 * that will make dest name longer than pszFileName 8.3 paths,
	 * or GEMDOS paths using "../" which make it smaller.  Both
	 * should(?) be rare in paths, so this info to user should be
	 * good enough.
	 */
	if (nDestNameLen < minlen + (int)strlen(pszFileName) + 2)
	{
		Log_AlertDlg(LOG_ERROR, "Appending GEMDOS path '%s' to HDD emu host root dir doesn't fit to %d chars (current Hatari limit)!",
			     pszFileName, nDestNameLen);
		add_remaining_path(filename, pszDestName, nDestNameLen);
		return;
	}

	/* "../" handling breaks if there are extra slashes */
	File_CleanFileName(pszDestName);
	
	/* go through path directory components, advancing 'filename'
	 * pointer while parsing them.
	 */
	for (;;)
	{
		/* skip extra path separators */
		while (*filename == '\\')
			filename++;

		// fprintf(stderr, "filename: '%s', path: '%s'\n", filename, pszDestName);

		/* skip "." references to current directory */
		if (filename[0] == '.' &&
		    (filename[1] == '\\' || !filename[1]))
		{
			filename++;
			continue;
		}

		/* ".." path component -> strip last dir from dest path */
		if (filename[0] == '.' &&
		    filename[1] == '.' &&
		    (filename[2] == '\\' || !filename[2]))
		{
			char *sep = strrchr(pszDestName, PATHSEP);
			if (sep)
			{
				if (sep - pszDestName < minlen)
					Log_Printf(LOG_WARN, "GEMDOS path '%s' tried to back out of GEMDOS drive!\n", pszFileName);
				else
					*sep = '\0';
			}
			filename += 2;
			continue;
		}

		/* handle directory component */
		if ((s = strchr(filename, '\\')))
		{
			int dirlen = s - filename;
			char dirname[dirlen+1];
			/* copy dirname */
			strncpy(dirname, filename, dirlen);
			dirname[dirlen] = '\0';
			/* and advance filename */
			filename = s;

			if (strchr(dirname, '?') || strchr(dirname, '*'))
				Log_Printf(LOG_WARN, "GEMDOS dir name '%s' with wildcards in %s!\n", dirname, pszFileName);

			/* convert and append dirname to host path */
			if (!add_path_component(pszDestName, nDestNameLen, dirname, true))
			{
				Log_Printf(LOG_WARN, "No GEMDOS dir '%s'\n", pszDestName);
				add_remaining_path(filename, pszDestName, nDestNameLen);
				return;
			}
			continue;
		}

		/* path directory components done */
		break;
	}

	if (*filename)
	{
		/* a wildcard instead of a complete file name? */
		if (strchr(filename,'?') || strchr(filename,'*'))
		{
			int len = strlen(pszDestName);
			if (len < nDestNameLen)
			{
				pszDestName[len++] = PATHSEP;
				pszDestName[len] = '\0';
			}
			/* use strncat so that string is always nul terminated */
			/* strncat(pszDestName+len, filename, nDestNameLen-len); */
			Str_AtariToHost(filename, pszDestName+len, nDestNameLen-len, INVALID_CHAR);
		}
		else if (!add_path_component(pszDestName, nDestNameLen, filename, false))
		{
			/* It's often normal, that GEM uses this to test for
			 * existence of desktop.inf or newdesk.inf for example.
			 */
			LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS didn't find filename %s\n", pszDestName);
			return;
		}
	}
	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS: %s -> host: %s\n", pszFileName, pszDestName);
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Set drive (0=A,1=B,2=C etc...)
 * Call 0xE
 */
static bool GemDOS_SetDrv(Uint32 Params)
{
	/* Read details from stack for our own use */
	CurrentDrive = STMemory_ReadWord(Params);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x0E Dsetdrv(0x%x) at PC=0x%X\n", (int)CurrentDrive,
		  M68000_GetPC());

	/* Still re-direct to TOS */
	return false;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Set Disk Transfer Address (DTA)
 * Call 0x1A
 */
static bool GemDOS_SetDTA(Uint32 Params)
{
	/*  Look up on stack to find where DTA is */
	Uint32 nDTA = STMemory_ReadLong(Params);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x1A Fsetdta(0x%x) at PC 0x%X\n", nDTA,
		  M68000_GetPC());

	if (STMemory_ValidArea(nDTA, sizeof(DTA)))
	{
		/* Store as PC pointer */
		pDTA = (DTA *)STRAM_ADDR(nDTA);
	}
	else
	{
		pDTA = NULL;
		Log_Printf(LOG_WARN, "GEMDOS Fsetdta() failed due to invalid DTA address 0x%x\n", nDTA);
	}
	/* redirect to TOS */
	return false;
}

/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Dfree Free disk space.
 * Call 0x36
 */
static bool GemDOS_DFree(Uint32 Params)
{
#ifdef HAVE_STATVFS
	struct statvfs buf;
#endif
	int Drive, Total, Free;
	Uint32 Address;

	Address = STMemory_ReadLong(Params);
	Drive = STMemory_ReadWord(Params+SIZE_LONG);

	/* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */
	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x36 Dfree(0x%x, %i) at PC 0x%X\n", Address, Drive,
		  M68000_GetPC());
	if (Drive == 0)
		Drive = CurrentDrive;
	else
		Drive--;

	/* is it our drive? */
	if (!GemDOS_IsDriveEmulated(Drive))
	{
		/* no, redirect to TOS */
		return false;
	}
	/* Check that write is requested to valid memory area */
	if (!STMemory_ValidArea(Address, 16))
	{
		Log_Printf(LOG_WARN, "GEMDOS Dfree() failed due to invalid RAM range 0x%x+%i\n", Address, 16);
		Regs[REG_D0] = GEMDOS_ERANGE;
		return true;
	}

#ifdef HAVE_STATVFS
	if (statvfs(emudrives[Drive-2]->hd_emulation_dir, &buf) == 0)
	{
		Total = buf.f_blocks/1024 * buf.f_frsize;
		if (buf.f_bavail)
			Free = buf.f_bavail;	/* free for unprivileged user */
		else
			Free = buf.f_bfree;
		Free = Free/1024 * buf.f_bsize;

		/* TOS version limits based on:
		 *   http://hddriver.seimet.de/en/faq.html
		 */
		if (TosVersion >= 0x0400)
		{
			if (Total > 1024*1024)
				Total = 1024*1024;
		}
		else
		{
			if (TosVersion >= 0x0106)
			{
				if (Total > 512*1024)
					Total = 512*1024;
			}
			else
			{
				if (Total > 256*1024)
					Total = 256*1024;
			}
		}
		if (Free > Total)
			Free = Total;
	}
	else
#endif
	{
		/* fake 32MB drive with 16MB free */
		Total = 32*1024;
		Free = 16*1024;
	}
	STMemory_WriteLong(Address,  Free);             /* free clusters */
	STMemory_WriteLong(Address+SIZE_LONG, Total);   /* total clusters */

	STMemory_WriteLong(Address+SIZE_LONG*2, 512);   /* bytes per sector */
	STMemory_WriteLong(Address+SIZE_LONG*3, 2);     /* sectors per cluster (cluster = 1KB) */
	Regs[REG_D0] = GEMDOS_EOK;
	return true;
}



/*-----------------------------------------------------------------------*/
/**
 * Helper to map Unix errno to GEMDOS error value
 */
typedef enum {
	ERROR_FILE,
	ERROR_PATH
} etype_t;

static Uint32 errno2gemdos(const int error, const etype_t etype)
{
	LOG_TRACE(TRACE_OS_GEMDOS, "-> ERROR (errno = %d)\n", error);
	switch (error)
	{
	case ENOENT:
		if (etype == ERROR_FILE)
			return GEMDOS_EFILNF;/* File not found */
	case ENOTDIR:
		return GEMDOS_EPTHNF;        /* Path not found */
	case ENOTEMPTY:
	case EEXIST:
	case EPERM:
	case EACCES:
	case EROFS:
		return GEMDOS_EACCDN;        /* Acess denied */
	default:
		return GEMDOS_ERROR;         /* Misc error */
	}
}

/*-----------------------------------------------------------------------*/
/**
 * GEMDOS MkDir
 * Call 0x39
 */
static bool GemDOS_MkDir(Uint32 Params)
{
	char *pDirName, *psDirPath;
	int Drive;

	/* Find directory to make */
	pDirName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x39 Dcreate(\"%s\") at PC 0x%X\n", pDirName,
		  M68000_GetPC());

	Drive = GemDOS_FileName2HardDriveID(pDirName);

	if (!ISHARDDRIVE(Drive))
	{
		/* redirect to TOS */
		return false;
	}

	/* write protected device? */
	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
	{
		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Dcreate(\"%s\")\n", pDirName);
		Regs[REG_D0] = GEMDOS_EWRPRO;
		return true;
	}

	psDirPath = malloc(FILENAME_MAX);
	if (!psDirPath)
	{
		perror("GemDOS_MkDir");
		Regs[REG_D0] = GEMDOS_ENSMEM;
		return true;
	}
	
	/* Copy old directory, as if calls fails keep this one */
	GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX);
	
	/* Attempt to make directory */
	if (mkdir(psDirPath, 0755) == 0)
		Regs[REG_D0] = GEMDOS_EOK;
	else
		Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH);
	free(psDirPath);
	return true;
}

/*-----------------------------------------------------------------------*/
/**
 * GEMDOS RmDir
 * Call 0x3A
 */
static bool GemDOS_RmDir(Uint32 Params)
{
	char *pDirName, *psDirPath;
	int Drive;

	/* Find directory to make */
	pDirName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3A Ddelete(\"%s\") at PC 0x%X\n", pDirName,
		  M68000_GetPC());

	Drive = GemDOS_FileName2HardDriveID(pDirName);

	if (!ISHARDDRIVE(Drive))
	{
		/* redirect to TOS */
		return false;
	}

	/* write protected device? */
	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
	{
		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Ddelete(\"%s\")\n", pDirName);
		Regs[REG_D0] = GEMDOS_EWRPRO;
		return true;
	}

	psDirPath = malloc(FILENAME_MAX);
	if (!psDirPath)
	{
		perror("GemDOS_RmDir");
		Regs[REG_D0] = GEMDOS_ENSMEM;
		return true;
	}

	/* Copy old directory, as if calls fails keep this one */
	GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX);

	/* Attempt to remove directory */
	if (rmdir(psDirPath) == 0)
		Regs[REG_D0] = GEMDOS_EOK;
	else
		Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH);
	free(psDirPath);
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS ChDir
 * Call 0x3B
 */
static bool GemDOS_ChDir(Uint32 Params)
{
	char *pDirName, *psTempDirPath;
	struct stat buf;
	int Drive;

	/* Find new directory */
	pDirName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3B Dsetpath(\"%s\") at PC 0x%X\n", pDirName,
		  M68000_GetPC());

	Drive = GemDOS_FileName2HardDriveID(pDirName);

	if (!ISHARDDRIVE(Drive))
	{
		/* redirect to TOS */
		return false;
	}

	/* Allocate temporary memory for path name: */
	psTempDirPath = malloc(FILENAME_MAX);
	if (!psTempDirPath)
	{
		perror("GemDOS_ChDir");
		Regs[REG_D0] = GEMDOS_ENSMEM;
		return true;
	}

	GemDOS_CreateHardDriveFileName(Drive, pDirName, psTempDirPath, FILENAME_MAX);

	/* Remove trailing slashes (stat on Windows does not like that) */
	File_CleanFileName(psTempDirPath);

	if (stat(psTempDirPath, &buf))
	{
		/* error */
		free(psTempDirPath);
		Regs[REG_D0] = GEMDOS_EPTHNF;
		return true;
	}

	File_AddSlashToEndFileName(psTempDirPath);
	File_MakeAbsoluteName(psTempDirPath);

	/* Prevent '..' commands moving BELOW the root HDD folder */
	/* by double checking if path is valid */
	if (strncmp(psTempDirPath, emudrives[Drive-2]->hd_emulation_dir,
		    strlen(emudrives[Drive-2]->hd_emulation_dir)) == 0)
	{
		strcpy(emudrives[Drive-2]->fs_currpath, psTempDirPath);
		Regs[REG_D0] = GEMDOS_EOK;
	}
	else
	{
		Regs[REG_D0] = GEMDOS_EPTHNF;
	}
	free(psTempDirPath);

	return true;

}


/*-----------------------------------------------------------------------*/
/**
 * Helper to check whether given file's path is missing.
 * Returns true if missing, false if found.
 * Modifies the argument buffer.
 */
static bool GemDOS_FilePathMissing(char *szActualFileName)
{
	char *ptr = strrchr(szActualFileName, PATHSEP);
	if (ptr)
	{
		*ptr = 0;   /* Strip filename from string */
		if (!File_DirExists(szActualFileName))
			return true;
	}
	return false;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Create file
 * Call 0x3C
 */
static bool GemDOS_Create(Uint32 Params)
{
	/* TODO: host filenames might not fit into this */
	char szActualFileName[MAX_GEMDOS_PATH];
	char *pszFileName;
	int Drive,Index, Mode;

	/* Find filename */
	pszFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));
	Mode = STMemory_ReadWord(Params+SIZE_LONG);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3C Fcreate(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, Mode,
		  M68000_GetPC());

	Drive = GemDOS_FileName2HardDriveID(pszFileName);

	if (!ISHARDDRIVE(Drive))
	{
		/* redirect to TOS */
		return false;
	}

	if (Mode == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
	{
		Log_Printf(LOG_WARN, "Warning: Hatari doesn't support GEMDOS volume"
			   " label setting\n(for '%s')\n", pszFileName);
		Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
		return true;
	}

	/* write protected device? */
	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
	{
		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fcreate(\"%s\")\n", pszFileName);
		Regs[REG_D0] = GEMDOS_EWRPRO;
		return true;
	}

	/* Now convert to hard drive filename */
	GemDOS_CreateHardDriveFileName(Drive, pszFileName,
	                            szActualFileName, sizeof(szActualFileName));

	/* Find slot to store file handle, as need to return WORD handle for ST */
	Index = GemDOS_FindFreeFileHandle();
	if (Index == -1)
	{
		/* No free handles, return error code */
		Regs[REG_D0] = GEMDOS_ENHNDL;       /* No more handles */
		return true;
	}
	
	/* truncate and open for reading & writing */
	FileHandles[Index].FileHandle = fopen(szActualFileName, "wb+");

	if (FileHandles[Index].FileHandle != NULL)
	{
		/* FIXME: implement other Mode attributes
		 * - GEMDOS_FILE_ATTRIB_HIDDEN       (FA_HIDDEN)
		 * - GEMDOS_FILE_ATTRIB_SYSTEM_FILE  (FA_SYSTEM)
		 * - GEMDOS_FILE_ATTRIB_SUBDIRECTORY (FA_DIR)
		 * - GEMDOS_FILE_ATTRIB_WRITECLOSE   (FA_ARCHIVE)
		 *   (set automatically by GemDOS >= 0.15)
		 */
		if (Mode & GEMDOS_FILE_ATTRIB_READONLY)
		{
			/* after closing, file should be read-only */
			chmod(szActualFileName, S_IRUSR|S_IRGRP|S_IROTH);
		}
		/* Tag handle table entry as used in this process and return handle */
		FileHandles[Index].bUsed = true;
		FileHandles[Index].Basepage = STMemory_ReadLong(act_pd);
		snprintf(FileHandles[Index].szActualName,
			 sizeof(FileHandles[Index].szActualName),
			 "%s", szActualFileName);

		/* Return valid ST file handle from our range (from BASE_FILEHANDLE upwards) */
		Regs[REG_D0] = Index+BASE_FILEHANDLE;
		LOG_TRACE(TRACE_OS_GEMDOS, "-> FD %d (%s)\n", Regs[REG_D0],
			  Mode & GEMDOS_FILE_ATTRIB_READONLY ? "read-only":"read/write");
		return true;
	}
	LOG_TRACE(TRACE_OS_GEMDOS, "-> ERROR (errno = %d)\n", errno);

	/* We failed to create the file, did we have required access rights? */
	if (errno == EACCES || errno == EROFS ||
	    errno == EPERM || errno == EISDIR)
	{
		Log_Printf(LOG_WARN, "GEMDOS failed to create/truncate '%s'\n",
			   szActualFileName);
		Regs[REG_D0] = GEMDOS_EACCDN;
		return true;
	}

	/* Or was path to file missing? (ST-Zip 2.6 relies on getting
	 * correct error about that during extraction of ZIP files.)
	 */
	if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName))
	{
		Regs[REG_D0] = GEMDOS_EPTHNF; /* Path not found */
		return true;
	}

	Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Open file
 * Call 0x3D
 */
static bool GemDOS_Open(Uint32 Params)
{
	/* TODO: host filenames might not fit into this */
	char szActualFileName[MAX_GEMDOS_PATH];
	char *pszFileName;
	const char *ModeStr, *RealMode;
	const char *Modes[] = {
		"read-only", "write-only", "read/write", "read/write"
	};
	int Drive, Index, Mode;
	FILE *AutostartHandle;

	/* Find filename */
	pszFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));
	Mode = STMemory_ReadWord(Params+SIZE_LONG);
	Mode &= 3;

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3D Fopen(\"%s\", %s) at PC=0x%X\n",
		  pszFileName, Modes[Mode], M68000_GetPC());

	Drive = GemDOS_FileName2HardDriveID(pszFileName);

	if (!ISHARDDRIVE(Drive))
	{
		/* redirect to TOS */
		LOG_TRACE(TRACE_OS_GEMDOS, "-> to TOS\n");
		return false;
	}

	/* Find slot to store file handle, as need to return WORD handle for ST  */
	Index = GemDOS_FindFreeFileHandle();
	if (Index == -1)
	{
		/* No free handles, return error code */
		Regs[REG_D0] = GEMDOS_ENHNDL;       /* No more handles */
		return true;
	}

	if ((AutostartHandle = TOS_AutoStartOpen(pszFileName)))
	{
		strcpy(szActualFileName, pszFileName);
		FileHandles[Index].FileHandle = AutostartHandle;
		RealMode = "read-only";
	}
	else
	{
		struct stat FileStat;

		/* Convert to hard drive filename */
		GemDOS_CreateHardDriveFileName(Drive, pszFileName,
			szActualFileName, sizeof(szActualFileName));

		/* Fread/Fwrite calls succeed in all TOS versions
		 * regardless of access rights specified in Fopen().
		 * Only time when things can fail is when file is
		 * opened, if file mode doesn't allow given opening
		 * mode.  As there's no write-only file mode, access
		 * failures happen only when trying to open read-only
		 * file with (read+)write mode.
		 *
		 * Therefore only read-only & read+write modes need
		 * to be supported (ANSI-C fopen() doesn't even
		 * support write-only without truncating the file).
		 *
		 * Read-only status is used if:
		 * - requested by Atari program
		 * - Hatari write protection is enabled
		 * - File itself is read-only
		 * Latter is done to help cases where application
		 * needlessly requests write access, but file is
		 * on read-only media (like CD/DVD).
		 */
		if (Mode == 0 ||
		    ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON ||
		    (stat(szActualFileName, &FileStat) == 0 && !(FileStat.st_mode & S_IWUSR)))
		{
			ModeStr = "rb";
			RealMode = "read-only";
		}
		else
		{
			ModeStr = "rb+";
			RealMode = "read+write";
		}
		FileHandles[Index].FileHandle = fopen(szActualFileName, ModeStr);
	}

	if (FileHandles[Index].FileHandle != NULL)
	{
		/* Tag handle table entry as used in this process and return handle */
		FileHandles[Index].bUsed = true;
		FileHandles[Index].Basepage = STMemory_ReadLong(act_pd);
		snprintf(FileHandles[Index].szActualName,
			 sizeof(FileHandles[Index].szActualName),
			 "%s", szActualFileName);

		GemDOS_UpdateCurrentProgram(Index);

		/* Return valid ST file handle from our range (BASE_FILEHANDLE upwards) */
		Regs[REG_D0] = Index+BASE_FILEHANDLE;
		LOG_TRACE(TRACE_OS_GEMDOS, "-> FD %d (%s -> %s)\n",
			  Regs[REG_D0], Modes[Mode], RealMode);
		return true;
	}

	if (errno == EACCES || errno == EROFS ||
	    errno == EPERM || errno == EISDIR)
	{
		Log_Printf(LOG_WARN, "GEMDOS missing %s permission to file '%s'\n",
			   Modes[Mode], szActualFileName);
		Regs[REG_D0] = GEMDOS_EACCDN;
	}
	else if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName))
	{
		/* Path not found */
		Regs[REG_D0] = GEMDOS_EPTHNF;
	}
	else
	{
		/* File not found / error opening */
		Regs[REG_D0] = GEMDOS_EFILNF;
	}
	LOG_TRACE(TRACE_OS_GEMDOS, "-> ERROR %d (errno = %d)\n", Regs[REG_D0], errno);
	return true;
}

/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Close file
 * Call 0x3E
 */
static bool GemDOS_Close(Uint32 Params)
{
	int i, Handle;

	/* Find our handle - may belong to TOS */
	Handle = STMemory_ReadWord(Params);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3E Fclose(%i) at PC 0x%X\n", Handle,
		  M68000_GetPC());

	/* Get internal handle */
	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
	{
		/* no, assume it was TOS one -> redirect */
		return false;
	}
	
	/* Close file and free up handle table */
	if (TOS_AutoStartClose(FileHandles[Handle].FileHandle))
	{
		FileHandles[Handle].bUsed = false;
	}
	GemDOS_CloseFileHandle(Handle);

	/* unalias handle */
	for (i = 0; i < ARRAYSIZE(ForcedHandles); i++)
	{
		if (ForcedHandles[i].Handle == Handle)
			GemDOS_UnforceFileHandle(i);
	}
	/* Return no error */
	Regs[REG_D0] = GEMDOS_EOK;
	return true;
}

/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Read file
 * Call 0x3F
 */
static bool GemDOS_Read(Uint32 Params)
{
	char *pBuffer;
	long CurrentPos, FileSize, nBytesRead, nBytesLeft;
	Uint32 Addr;
	Uint32 Size;
	int Handle;

	/* Read details from stack */
	Handle = STMemory_ReadWord(Params);
	Size = STMemory_ReadLong(Params+SIZE_WORD);
	Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
	pBuffer = (char *)STRAM_ADDR(Addr);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3F Fread(%i, %i, 0x%x) at PC 0x%X\n",
	          Handle, Size, Addr,
		  M68000_GetPC());

	/* Get internal handle */
	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
	{
		/* assume it was TOS one -> redirect */
		return false;
	}

	/* Old TOS versions treat the Size parameter as signed */
	if (TosVersion < 0x400 && (Size & 0x80000000))
	{
		/* return -1 as original GEMDOS */
		Regs[REG_D0] = -1;
		return true;
	}
	
	/* To quick check to see where our file pointer is and how large the file is */
	CurrentPos = ftell(FileHandles[Handle].FileHandle);
	fseek(FileHandles[Handle].FileHandle, 0, SEEK_END);
	FileSize = ftell(FileHandles[Handle].FileHandle);
	fseek(FileHandles[Handle].FileHandle, CurrentPos, SEEK_SET);

	nBytesLeft = FileSize-CurrentPos;
	
	/* Check for bad size and End Of File */
	if (Size <= 0 || nBytesLeft <= 0)
	{
		/* return zero (bytes read) as original GEMDOS/EmuTOS */
		Regs[REG_D0] = 0;
		return true;
	}

	/* Limit to size of file to prevent errors */
	if (Size > (Uint32)nBytesLeft)
		Size = nBytesLeft;

	/* Check that read is to valid memory area */
	if (!STMemory_ValidArea(Addr, Size))
	{
		Log_Printf(LOG_WARN, "GEMDOS Fread() failed due to invalid RAM range 0x%x+%i\n", Addr, Size);
		Regs[REG_D0] = GEMDOS_ERANGE;
		return true;
	}
	/* And read data in */
	nBytesRead = fread(pBuffer, 1, Size, FileHandles[Handle].FileHandle);
	
	if (ferror(FileHandles[Handle].FileHandle))
	{
		Log_Printf(LOG_WARN, "GEMDOS failed to read from '%s'\n",
			   FileHandles[Handle].szActualName );
		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
	} else
		/* Return number of bytes read */
		Regs[REG_D0] = nBytesRead;

	return true;
}

/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Write file
 * Call 0x40
 */
static bool GemDOS_Write(Uint32 Params)
{
	char *pBuffer;
	long nBytesWritten;
	Uint32 Addr;
	Sint32 Size;
	int Handle;
	FILE *fp;

	/* Read details from stack */
	Handle = STMemory_ReadWord(Params);
	Size = STMemory_ReadLong(Params+SIZE_WORD);
	Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
	pBuffer = (char *)STRAM_ADDR(Addr);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x40 Fwrite(%i, %i, 0x%x) at PC 0x%X\n",
	          Handle, Size, Addr,
		  M68000_GetPC());

	/* Get internal handle */
	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
	{
		/* assume it was TOS one -> redirect */
		return false;
	}

	/* write protected device? */
	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
	{
		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fwrite(%d,...)\n", Handle);
		Regs[REG_D0] = GEMDOS_EWRPRO;
		return true;
	}

	/* Check that write is from valid memory area */
	if (!STMemory_ValidArea(Addr, Size))
	{
		Log_Printf(LOG_WARN, "GEMDOS Fwrite() failed due to invalid RAM range 0x%x+%i\n", Addr, Size);
		Regs[REG_D0] = GEMDOS_ERANGE;
		return true;
	}

	fp = FileHandles[Handle].FileHandle;
	nBytesWritten = fwrite(pBuffer, 1, Size, fp);
	if (ferror(fp))
	{
		Log_Printf(LOG_WARN, "GEMDOS failed to write to '%s'\n",
			   FileHandles[Handle].szActualName );
		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
	}
	else
	{
		fflush(fp);
		Regs[REG_D0] = nBytesWritten;      /* OK */
	}
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Delete file
 * Call 0x41
 */
static bool GemDOS_FDelete(Uint32 Params)
{
	char *pszFileName, *psActualFileName;
	int Drive;

	/* Find filename */
	pszFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x41 Fdelete(\"%s\") at PC 0x%X\n", pszFileName,
		  M68000_GetPC());

	Drive = GemDOS_FileName2HardDriveID(pszFileName);

	if (!ISHARDDRIVE(Drive))
	{
		/* redirect to TOS */
		return false;
	}

	/* write protected device? */
	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
	{
		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdelete(\"%s\")\n", pszFileName);
		Regs[REG_D0] = GEMDOS_EWRPRO;
		return true;
	}

	psActualFileName = malloc(FILENAME_MAX);
	if (!psActualFileName)
	{
		perror("GemDOS_FDelete");
		Regs[REG_D0] = GEMDOS_ENSMEM;
		return true;
	}

	/* And convert to hard drive filename */
	GemDOS_CreateHardDriveFileName(Drive, pszFileName, psActualFileName, FILENAME_MAX);

	/* Now delete file?? */
	if (unlink(psActualFileName) == 0)
		Regs[REG_D0] = GEMDOS_EOK;          /* OK */
	else
		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);

	free(psActualFileName);
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS File seek
 * Call 0x42
 */
static bool GemDOS_LSeek(Uint32 Params)
{
	long Offset;
	int Handle, Mode;
	long nFileSize;
	long nOldPos, nDestPos;
	FILE *fhndl;

	/* Read details from stack */
	Offset = (Sint32)STMemory_ReadLong(Params);
	Handle = STMemory_ReadWord(Params+SIZE_LONG);
	Mode = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x42 Fseek(%li, %i, %i) at PC 0x%X\n", Offset, Handle, Mode,
		  M68000_GetPC());

	/* get internal handle */
	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
	{
		/* assume it was TOS one -> redirect */
		return false;
	}

	fhndl = FileHandles[Handle].FileHandle;

	/* Save old position in file */
	nOldPos = ftell(fhndl);

	/* Determine the size of the file */
	fseek(fhndl, 0L, SEEK_END);
	nFileSize = ftell(fhndl);

	switch (Mode)
	{
	 case 0: nDestPos = Offset; break; /* positive offset */
	 case 1: nDestPos = nOldPos + Offset; break;
	 case 2: nDestPos = nFileSize + Offset; break; /* negative offset */
	 default:
		/* Restore old position and return error */
		fseek(fhndl, nOldPos, SEEK_SET);
		Regs[REG_D0] = GEMDOS_EINVFN;
		return true;
	}

	if (nDestPos < 0 || nDestPos > nFileSize)
	{
		/* Restore old position and return error */
		fseek(fhndl, nOldPos, SEEK_SET);
		Regs[REG_D0] = GEMDOS_ERANGE;
		return true;
	}

	/* Seek to new position and return offset from start of file */
	fseek(fhndl, nDestPos, SEEK_SET);
	Regs[REG_D0] = ftell(fhndl);

	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Fattrib() - get or set file and directory attributes
 * Call 0x43
 */
static bool GemDOS_Fattrib(Uint32 Params)
{
	/* TODO: host filenames might not fit into this */
	char sActualFileName[MAX_GEMDOS_PATH];
	char *psFileName;
	int nDrive;
	int nRwFlag, nAttrib;
	struct stat FileStat;

	/* Find filename */
	psFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));
	nDrive = GemDOS_FileName2HardDriveID(psFileName);

	nRwFlag = STMemory_ReadWord(Params+SIZE_LONG);
	nAttrib = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x43 Fattrib(\"%s\", %d, 0x%x) at PC 0x%X\n",
	          psFileName, nRwFlag, nAttrib,
		  M68000_GetPC());

	if (!ISHARDDRIVE(nDrive))
	{
		/* redirect to TOS */
		return false;
	}

	/* Convert to hard drive filename */
	GemDOS_CreateHardDriveFileName(nDrive, psFileName,
	                              sActualFileName, sizeof(sActualFileName));

	if (nAttrib == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
	{
		Log_Printf(LOG_WARN, "Warning: Hatari doesn't support GEMDOS volume label setting\n(for '%s')\n", sActualFileName);
		Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
		return true;
	}
	if (stat(sActualFileName, &FileStat) != 0)
	{
		Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
		return true;
	}
	if (nRwFlag == 0)
	{
		/* Read attributes */
		Regs[REG_D0] = GemDOS_ConvertAttribute(FileStat.st_mode);
		return true;
	}

	/* prevent modifying access rights both on write & auto-protected devices */
	if (ConfigureParams.HardDisk.nWriteProtection != WRITEPROT_OFF)
	{
		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fattrib(\"%s\",...)\n", psFileName);
		Regs[REG_D0] = GEMDOS_EWRPRO;
		return true;
	}

	if (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY)
	{
		if (!S_ISDIR(FileStat.st_mode))
		{
			/* file, not dir -> path not found */
			Regs[REG_D0] = GEMDOS_EPTHNF;
			return true;
		}
	}
	else
	{
		if (S_ISDIR(FileStat.st_mode))
		{
			/* dir, not file -> file not found */
			Regs[REG_D0] = GEMDOS_EFILNF;
			return true;
		}
	}
	
	if (nAttrib & GEMDOS_FILE_ATTRIB_READONLY)
	{
		/* set read-only (readable by all) */
		if (chmod(sActualFileName, S_IRUSR|S_IRGRP|S_IROTH) == 0)
		{
			Regs[REG_D0] = nAttrib;
			return true;
		}
	}
	else
	{
		/* set writable (by user, readable by all) */
		if (chmod(sActualFileName, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH) == 0)
		{
			Regs[REG_D0] = nAttrib;
			return true;
		}
	}
	
	/* FIXME: support hidden/system/archive flags?
	 * System flag is from DOS, not used by TOS.
	 * Archive bit is cleared by backup programs
	 * and set whenever file is written to.
	 */

	Regs[REG_D0] = errno2gemdos(errno, (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY) ? ERROR_PATH : ERROR_FILE);
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Force (file handle aliasing)
 * Call 0x46
 */
static bool GemDOS_Force(Uint32 Params)
{
	int std, own;

	/* Read details from stack */
	std = STMemory_ReadWord(Params);
        own = STMemory_ReadWord(Params+SIZE_WORD);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x46 Fforce(%d, %d) at PC 0x%X\n", std, own,
		  M68000_GetPC());

	/* Get internal handle */
	if (std > own)
	{
		int tmp = std;
		std = own;
		own = tmp;
	}
	if ((own = GemDOS_GetValidFileHandle(own)) < 0)
	{
		/* assume it was TOS one -> let TOS handle it */
		return false;
	}
	if (std < 0 || std >= ARRAYSIZE(ForcedHandles))
	{
		Log_Printf(LOG_WARN, "Warning: forcing of non-standard %d (> %d) handle ignored.\n", std, ARRAYSIZE(ForcedHandles));
		return false;
	}
	/* mark given standard handle redirected by this process */
	ForcedHandles[std].Basepage = STMemory_ReadLong(act_pd);
	ForcedHandles[std].Handle = own;

	Regs[REG_D0] = GEMDOS_EOK;
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Get Directory
 * Call 0x47
 */
static bool GemDOS_GetDir(Uint32 Params)
{
	Uint32 Address;
	Uint16 Drive;

	Address = STMemory_ReadLong(Params);
	Drive = STMemory_ReadWord(Params+SIZE_LONG);

	/* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */
	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x47 Dgetpath(0x%x, %i) at PC 0x%X\n", Address, (int)Drive,
		  M68000_GetPC());
	if (Drive == 0)
		Drive = CurrentDrive;
	else
		Drive--;

	/* is it our drive? */
	if (GemDOS_IsDriveEmulated(Drive))
	{
		char path[MAX_GEMDOS_PATH];
		int i,len,c;

		*path = '\0';
		strncat(path,&emudrives[Drive-2]->fs_currpath[strlen(emudrives[Drive-2]->hd_emulation_dir)], sizeof(path)-1);

		// convert it to ST path (DOS)
		File_CleanFileName(path);
		len = strlen(path);
		/* Check that write is requested to valid memory area */
		if (!STMemory_ValidArea(Address, len))
		{
			Log_Printf(LOG_WARN, "GEMDOS Dgetpath() failed due to invalid RAM range 0x%x+%i\n", Address, len);
			Regs[REG_D0] = GEMDOS_ERANGE;
			return true;
		}
		for (i = 0; i <= len; i++)
		{
			c = path[i];
			STMemory_WriteByte(Address+i, (c==PATHSEP ? '\\' : c) );
		}
		LOG_TRACE(TRACE_OS_GEMDOS, "-> '%s'\n", (char *)STRAM_ADDR(Address));

		Regs[REG_D0] = GEMDOS_EOK;          /* OK */

		return true;
	}
	/* redirect to TOS */
	return false;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS PExec handler
 * Call 0x4B
 */
static int GemDOS_Pexec(Uint32 Params)
{
	int Drive;
	Uint16 Mode;
	char *pszFileName;

	/* Find PExec mode */
	Mode = STMemory_ReadWord(Params);

	if (LOG_TRACE_LEVEL(TRACE_OS_GEMDOS))
	{
		Uint32 fname, cmdline, env_string;
		fname = STMemory_ReadLong(Params+SIZE_WORD);
		cmdline = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
		env_string = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG+SIZE_LONG);
		if (Mode == 0 || Mode == 3)
		{
			int cmdlen;
			const char *name, *cmd;
			name = (const char *)STRAM_ADDR(fname);
			cmd = (const char *)STRAM_ADDR(cmdline);
			cmdlen = *cmd++;
			LOG_TRACE_PRINT ( "GEMDOS 0x4B Pexec(%i, \"%s\", [%d]\"%s\", 0x%x) at PC 0x%X\n", Mode, name, cmdlen, cmdlen?cmd:"", env_string,
				M68000_GetPC());
		}
		else
		{
			LOG_TRACE_PRINT ( "GEMDOS 0x4B Pexec(%i, 0x%x, 0x%x, 0x%x) at PC 0x%X\n", Mode, fname, cmdline, env_string,
				M68000_GetPC());
		}
	}

	/* Re-direct as needed */
	switch(Mode)
	{
	 case 0:      /* Load and go */
	 case 3:      /* Load, don't go */
		pszFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params+SIZE_WORD));
		Drive = GemDOS_FileName2HardDriveID(pszFileName);
		
		/* If not using A: or B:, use my own routines to load */
		if (ISHARDDRIVE(Drive))
		{
			/* Redirect to cart' routine at address 0xFA1000 */
			PexecCalled = true;
			return CALL_PEXEC_ROUTINE;
		}
		return false;
	 case 4:      /* Just go */
		return false;
	 case 5:      /* Create basepage */
		return false;
	 case 6:
		return false;
	}

	/* Default: Still re-direct to TOS */
	return false;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Search Next
 * Call 0x4F
 */
static bool GemDOS_SNext(void)
{
	struct dirent **temp;
	Uint32 nDTA;
	int Index;
	int ret;

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4F Fsnext() at PC 0x%X\n" , M68000_GetPC());

	/* Refresh pDTA pointer (from the current basepage) */
	nDTA = STMemory_ReadLong(STMemory_ReadLong(act_pd)+32);

	if (!STMemory_ValidArea(nDTA, sizeof(DTA)))
	{
		pDTA = NULL;
		Log_Printf(LOG_WARN, "GEMDOS Fsnext() failed due to invalid DTA address 0x%x\n", nDTA);
		Regs[REG_D0] = GEMDOS_EINTRN;    /* "internal error */
		return true;
	}
	pDTA = (DTA *)STRAM_ADDR(nDTA);

	/* Was DTA ours or TOS? */
	if (do_get_mem_long(pDTA->magic) != DTA_MAGIC_NUMBER)
	{
		/* redirect to TOS */
		return false;
	}

	/* Find index into our list of structures */
	Index = do_get_mem_word(pDTA->index) & (MAX_DTAS_FILES-1);

	if (nAttrSFirst == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
	{
		/* Volume label was given already in Sfirst() */
		Regs[REG_D0] = GEMDOS_ENMFIL;
		return true;
	}

	temp = InternalDTAs[Index].found;
	do
	{
		if (InternalDTAs[Index].centry >= InternalDTAs[Index].nentries)
		{
			Regs[REG_D0] = GEMDOS_ENMFIL;    /* No more files */
			return true;
		}

		ret = PopulateDTA(InternalDTAs[Index].path,
				  temp[InternalDTAs[Index].centry++]);
	} while (ret == 1);

	if (ret < 0)
	{
		Log_Printf(LOG_WARN, "GEMDOS Fsnext(): Error setting DTA.\n");
		Regs[REG_D0] = GEMDOS_EINTRN;
		return true;
	}

	Regs[REG_D0] = GEMDOS_EOK;
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Find first file
 * Call 0x4E
 */
static bool GemDOS_SFirst(Uint32 Params)
{
	/* TODO: host filenames might not fit into this */
	char szActualFileName[MAX_GEMDOS_PATH];
	char *pszFileName;
	const char *dirmask;
	struct dirent **files;
	Uint32 nDTA;
	int Drive;
	DIR *fsdir;
	int i,j,count;

	/* Find filename to search for */
	pszFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params));
	nAttrSFirst = STMemory_ReadWord(Params+SIZE_LONG);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4E Fsfirst(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, nAttrSFirst,
		  M68000_GetPC());

	Drive = GemDOS_FileName2HardDriveID(pszFileName);
	if (!ISHARDDRIVE(Drive))
	{
		/* redirect to TOS */
		return false;
	}

	/* Convert to hard drive filename */
	GemDOS_CreateHardDriveFileName(Drive, pszFileName,
		                    szActualFileName, sizeof(szActualFileName));

	/* Refresh pDTA pointer (from the current basepage) */
	nDTA = STMemory_ReadLong(STMemory_ReadLong(act_pd)+32);

	if (!STMemory_ValidArea(nDTA, sizeof(DTA)))
	{
		pDTA = NULL;
		Log_Printf(LOG_WARN, "GEMDOS Fsfirst() failed due to invalid DTA address 0x%x\n", nDTA);
		Regs[REG_D0] = GEMDOS_EINTRN;    /* "internal error */
		return true;
	}
	pDTA = (DTA *)STRAM_ADDR(nDTA);

	/* Populate DTA, set index for our use */
	do_put_mem_word(pDTA->index, DTAIndex);
	/* set our dta magic num */
	do_put_mem_long(pDTA->magic, DTA_MAGIC_NUMBER);

	if (InternalDTAs[DTAIndex].bUsed == true)
		ClearInternalDTA();
	InternalDTAs[DTAIndex].bUsed = true;

	/* Were we looking for the volume label? */
	if (nAttrSFirst == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
	{
		/* Volume name */
		strcpy(pDTA->dta_name,"EMULATED.001");
		pDTA->dta_name[11] = '0' + Drive;
		Regs[REG_D0] = GEMDOS_EOK;          /* Got volume */
		return true;
	}

	/* open directory
	 * TODO: host path may not fit into InternalDTA
	 */
	fsfirst_dirname(szActualFileName, InternalDTAs[DTAIndex].path);
	fsdir = opendir(InternalDTAs[DTAIndex].path);

	if (fsdir == NULL)
	{
		Regs[REG_D0] = GEMDOS_EPTHNF;        /* Path not found */
		return true;
	}
	/* close directory */
	closedir(fsdir);

	count = scandir(InternalDTAs[DTAIndex].path, &files, 0, alphasort);
	/* File (directory actually) not found */
	if (count < 0)
	{
		Regs[REG_D0] = GEMDOS_EFILNF;
		return true;
	}

	InternalDTAs[DTAIndex].centry = 0;          /* current entry is 0 */
	dirmask = fsfirst_dirmask(szActualFileName);/* directory mask part */
	InternalDTAs[DTAIndex].found = files;       /* get files */

	/* count & copy the entries that match our mask and discard the rest */
	j = 0;
	for (i=0; i < count; i++)
	{
		Str_DecomposedToPrecomposedUtf8(files[i]->d_name, files[i]->d_name);   /* for OSX */
		if (fsfirst_match(dirmask, files[i]->d_name))
		{
			InternalDTAs[DTAIndex].found[j] = files[i];
			j++;
		}
		else
		{
			free(files[i]);
			files[i] = NULL;
		}
	}
	InternalDTAs[DTAIndex].nentries = j; /* set number of legal entries */

	/* No files of that match, return error code */
	if (j==0)
	{
		free(files);
		InternalDTAs[DTAIndex].found = NULL;
		Regs[REG_D0] = GEMDOS_EFILNF;        /* File not found */
		return true;
	}

	/* Scan for first file (SNext uses no parameters) */
	GemDOS_SNext();
	/* increment DTA index */
	DTAIndex++;
	DTAIndex &= (MAX_DTAS_FILES-1);

	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS Rename
 * Call 0x56
 */
static bool GemDOS_Rename(Uint32 Params)
{
	char *pszNewFileName,*pszOldFileName;
	/* TODO: host filenames might not fit into this */
	char szNewActualFileName[MAX_GEMDOS_PATH];
	char szOldActualFileName[MAX_GEMDOS_PATH];
	int NewDrive, OldDrive;

	/* Read details from stack, skip first (dummy) arg */
	pszOldFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params+SIZE_WORD));
	pszNewFileName = (char *)STRAM_ADDR(STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG));

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x56 Frename(\"%s\", \"%s\") at PC 0x%X\n", pszOldFileName, pszNewFileName,
		  M68000_GetPC());

	NewDrive = GemDOS_FileName2HardDriveID(pszNewFileName);
	OldDrive = GemDOS_FileName2HardDriveID(pszOldFileName);
	if (!(ISHARDDRIVE(NewDrive) && ISHARDDRIVE(OldDrive)))
	{
		/* redirect to TOS */
		return false;
	}

	/* write protected device? */
	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
	{
		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Frename(\"%s\", \"%s\")\n", pszOldFileName, pszNewFileName);
		Regs[REG_D0] = GEMDOS_EWRPRO;
		return true;
	}

	/* And convert to hard drive filenames */
	GemDOS_CreateHardDriveFileName(NewDrive, pszNewFileName,
		              szNewActualFileName, sizeof(szNewActualFileName));
	GemDOS_CreateHardDriveFileName(OldDrive, pszOldFileName,
		              szOldActualFileName, sizeof(szOldActualFileName));

	/* Rename files */
	if (rename(szOldActualFileName,szNewActualFileName) == 0)
		Regs[REG_D0] = GEMDOS_EOK;
	else
		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * GEMDOS GSDToF
 * Call 0x57
 */
static bool GemDOS_GSDToF(Uint32 Params)
{
	DATETIME DateTime;
	Uint32 pBuffer;
	int Handle,Flag;

	/* Read details from stack */
	pBuffer = STMemory_ReadLong(Params);
	Handle = STMemory_ReadWord(Params+SIZE_LONG);
	Flag = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);

	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x57 Fdatime(0x%x, %i, %i) at PC 0x%X\n", pBuffer,
	          Handle, Flag,
		  M68000_GetPC());

	/* get internal handle */
	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
	{
		/* No, assume was TOS -> redirect */
		return false;
	}

	if (Flag == 1)
	{
		/* write protected device? */
		if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
		{
			Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdatime(,%d,)\n", Handle);
			Regs[REG_D0] = GEMDOS_EWRPRO;
			return true;
		}
		DateTime.timeword = STMemory_ReadWord(pBuffer);
		DateTime.dateword = STMemory_ReadWord(pBuffer+SIZE_WORD);
		if (GemDOS_SetFileInformation(Handle, &DateTime) == true)
			Regs[REG_D0] = GEMDOS_EOK;
		else
			Regs[REG_D0] = GEMDOS_EACCDN;        /* Access denied */
		return true;
	}

	if (GemDOS_GetFileInformation(Handle, &DateTime) == true)
	{
		/* Check that write is requested to valid memory area */
		if (STMemory_ValidArea(pBuffer, 4))
		{
			STMemory_WriteWord(pBuffer, DateTime.timeword);
			STMemory_WriteWord(pBuffer+SIZE_WORD, DateTime.dateword);
			Regs[REG_D0] = GEMDOS_EOK;
		}
		else
		{
			Log_Printf(LOG_WARN, "GEMDOS Fdatime() failed due to invalid RAM range 0x%x+%i\n", pBuffer, 4);
			Regs[REG_D0] = GEMDOS_ERANGE;
		}
	}
	else
	{
		Regs[REG_D0] = GEMDOS_ERROR; /* Generic error */
	}
	return true;
}


/*-----------------------------------------------------------------------*/
/**
 * Do implicit file handle closing/unforcing on program termination
 */
static void GemDOS_TerminateClose(void)
{
	int i, closed, unforced;
	Uint32 current = STMemory_ReadLong(act_pd);

	closed = 0;
	for (i = 0; i < ARRAYSIZE(FileHandles); i++)
	{
		if (FileHandles[i].Basepage == current)
		{
			GemDOS_CloseFileHandle(i);
			closed++;
		}
	}
	unforced = 0;
	for (i = 0; i < ARRAYSIZE(ForcedHandles); i++)
	{
		if (ForcedHandles[i].Basepage == current)
		{
			GemDOS_UnforceFileHandle(i);
			unforced++;
		}
	}
	if (!(closed || unforced))
		return;
	Log_Printf(LOG_WARN, "Closing %d & unforcing %d file handle(s) remaining at program 0x%x exit.\n",
		   closed, unforced, current);
}

/**
 * GEMDOS Pterm0
 * Call 0x00
 */
static bool GemDOS_Pterm0(Uint32 Params)
{
	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x00 Pterm0() at PC 0x%X\n",
		  M68000_GetPC());
	GemDOS_TerminateClose();
	Symbols_RemoveCurrentProgram();
	return false;
}

/**
 * GEMDOS Ptermres
 * Call 0x31
 */
static bool GemDOS_Ptermres(Uint32 Params)
{
	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x31 Ptermres(0x%X, %hd) at PC 0x%X\n",
		  STMemory_ReadLong(Params), (Sint16)STMemory_ReadWord(Params+SIZE_WORD),
		  M68000_GetPC());
	GemDOS_TerminateClose();
	return false;
}

/**
 * GEMDOS Pterm
 * Call 0x4c
 */
static bool GemDOS_Pterm(Uint32 Params)
{
	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4C Pterm(%hd) at PC 0x%X\n",
		  (Sint16)STMemory_ReadWord(Params),
		  M68000_GetPC());
	GemDOS_TerminateClose();
	Symbols_RemoveCurrentProgram();
	return false;
}


#if ENABLE_TRACING
/*-----------------------------------------------------------------------*/
/**
 * Map GEMDOS call opcodes to their names
 * 
 * Mapping is based on TOSHYP information:
 *	http://toshyp.atari.org/en/005013.html
 */
static const char* GemDOS_Opcode2Name(Uint16 opcode)
{
	static const char* names[] = {
		"Pterm0",
		"Cconin",
		"Cconout",
		"Cauxin",
		"Cauxout",
		"Cprnout",
		"Crawio",
		"Crawcin",
		"Cnecin",
		"Cconws",
		"Cconrs",
		"Cconis",
		"-", /* 0C */
		"-", /* 0D */
		"Dsetdrv",
		"-", /* 0F */
		"Cconos",
		"Cprnos",
		"Cauxis",
		"Cauxos",
		"Maddalt",
		"Srealloc", /* TOS4 */
		"-", /* 16 */
		"-", /* 17 */
		"-", /* 18 */
		"Dgetdrv",
		"Fsetdta",
		"-", /* 1B */
		"-", /* 1C */
		"-", /* 1D */
		"-", /* 1E */
		"-", /* 1F */
		"Super",
		"-", /* 21 */
		"-", /* 22 */
		"-", /* 23 */
		"-", /* 24 */
		"-", /* 25 */
		"-", /* 26 */
		"-", /* 27 */
		"-", /* 28 */
		"-", /* 29 */
		"Tgetdate",
		"Tsetdate",
		"Tgettime",
		"Tsettime",
		"-", /* 2E */
		"Fgetdta",
		"Sversion",
		"Ptermres",
		"-", /* 32 */
		"-", /* 33 */
		"-", /* 34 */
		"-", /* 35 */
		"Dfree",
		"-", /* 37 */
		"-", /* 38 */
		"Dcreate",
		"Ddelete",
		"Dsetpath",
		"Fcreate",
		"Fopen",
		"Fclose",
		"Fread",
		"Fwrite",
		"Fdelete",
		"Fseek",
		"Fattrib",
		"Mxalloc",
		"Fdup",
		"Fforce",
		"Dgetpath",
		"Malloc",
		"Mfree",
		"Mshrink",
		"Pexec",
		"Pterm",
		"-", /* 4D */
		"Fsfirst",
		"Fsnext",
		"-", /* 50 */
		"-", /* 51 */
		"-", /* 52 */
		"-", /* 53 */
		"-", /* 54 */
		"-", /* 55 */
		"Frename",
		"Fdatime"
	};
	if (opcode < ARRAYSIZE(names))
		return names[opcode];
	return "MiNT?";
}

/**
 * If bShowOpcodes is true, show GEMDOS call opcode/function name table,
 * otherwise GEMDOS HDD emulation information.
 */
void GemDOS_Info(Uint32 bShowOpcodes)
{
	int i, used;

	if (bShowOpcodes)
	{
		Uint16 opcode;
		for (opcode = 0; opcode < 0x5A; )
		{
			fprintf(stderr, "%02x %-9s",
				opcode, GemDOS_Opcode2Name(opcode));
			if (++opcode % 6 == 0)
				fputs("\n", stderr);
		}
		return;
	}

	if (!GEMDOS_EMU_ON)
	{
		fputs("GEMDOS HDD emulation isn't enabled!\n", stderr);
		return;
	}

	/* GEMDOS vector set by Hatari can be overwritten e.g. MiNT */
	fprintf(stderr, "Current GEMDOS handler: (0x84) = 0x%x, emu one = 0x%x\n", STMemory_ReadLong(0x0084), CART_GEMDOS);
	fprintf(stderr, "Stored GEMDOS handler: (0x%x) = 0x%x\n\n", CART_OLDGEMDOS, STMemory_ReadLong(CART_OLDGEMDOS));

	fprintf(stderr, "Connected drives mask: 0x%x\n\n", ConnectedDriveMask);
	fputs("GEMDOS HDD emulation drives:\n", stderr);
	for(i = 0; i < MAX_HARDDRIVES; i++)
	{
		if (!emudrives[i])
			continue;
		fprintf(stderr, "- %c: %s\n",
			'A' + emudrives[i]->drive_number,
			emudrives[i]->hd_emulation_dir);
	}

	fputs("\nInternal Fsfirst() DTAs:\n", stderr);
	for(used = i = 0; i < ARRAYSIZE(InternalDTAs); i++)
	{
		int j, centry, entries;

		if (!InternalDTAs[i].bUsed)
			continue;

		fprintf(stderr, "+ %d: %s\n", i, InternalDTAs[i].path);
		
		centry = InternalDTAs[i].centry;
		entries = InternalDTAs[i].nentries;
		for (j = 0; j < entries; j++)
		{
			fprintf(stderr, "  - %d: %s%s\n",
				j, InternalDTAs[i].found[j]->d_name,
				j == centry ? " *" : "");
		}
		fprintf(stderr, "  Fsnext entry = %d.\n", centry);
		used++;
	}
	if (!used)
		fputs("- None in use.\n", stderr);

	fputs("\nOpen GEMDOS HDD file handles:\n", stderr);
	for (used = i = 0; i < ARRAYSIZE(FileHandles); i++)
	{
		if (!FileHandles[i].bUsed)
			continue;
		fprintf(stderr, "- %d (0x%x): %s\n", i + BASE_FILEHANDLE,
			FileHandles[i].Basepage, FileHandles[i].szActualName);
		used++;
	}
	if (!used)
		fputs("- None.\n", stderr);
	fputs("\nForced GEMDOS HDD file handles:\n", stderr);
	for (used = i = 0; i < ARRAYSIZE(ForcedHandles); i++)
	{
		if (ForcedHandles[i].Handle == UNFORCED_HANDLE)
			continue;
		fprintf(stderr, "- %d -> %d (0x%x)\n", i,
			ForcedHandles[i].Handle + BASE_FILEHANDLE,
			ForcedHandles[i].Basepage);
		used++;
	}
	if (!used)
		fputs("- None.\n", stderr);
}

#else /* !ENABLE_TRACING */
void GemDOS_Info(Uint32 bShowOpcodes)
{
	fputs("Hatari isn't configured with ENABLE_TRACING\n", stderr);
}
#endif /* !ENABLE_TRACING */


/**
 * Run GEMDos call, and re-direct if need to. Used to handle hard disk emulation etc...
 * This sets the condition codes (in SR), which are used in the 'cart_asm.s' program to
 * decide if we need to run old GEM vector, or PExec or nothing.
 *
 * This method keeps the stack and other states consistent with the original ST
 * which is very important for the PExec call and maximum compatibility through-out
 */
void GemDOS_OpCode(void)
{
	Uint16 GemDOSCall, CallingSReg;
	Uint32 Params;
	int Finished;
	Uint16 SR;

	SR = M68000_GetSR();

	/* Read SReg from stack to see if parameters are on User or Super stack  */
	CallingSReg = STMemory_ReadWord(Regs[REG_A7]);
	if ((CallingSReg&SR_SUPERMODE)==0)      /* Calling from user mode */
		Params = regs.usp;
	else
	{
		Params = Regs[REG_A7]+SIZE_WORD+SIZE_LONG;  /* skip SR & PC pushed to super stack */
		if (currprefs.cpu_level > 0)
			Params += SIZE_WORD;   /* Skip extra word whe CPU is >=68010 */
	}

	/* Default to run TOS GemDos (SR_NEG run Gemdos, SR_ZERO already done, SR_OVERFLOW run own 'Pexec' */
	Finished = false;
	SR &= SR_CLEAR_OVERFLOW;
	SR &= SR_CLEAR_ZERO;
	SR |= SR_NEG;

	/* Find pointer to call parameters */
	GemDOSCall = STMemory_ReadWord(Params);
	Params += SIZE_WORD;

	/* Intercept call */
	switch(GemDOSCall)
	{
	 case 0x00:
		Finished = GemDOS_Pterm0(Params);
		break;
	 case 0x0e:
		Finished = GemDOS_SetDrv(Params);
		break;
	 case 0x1a:
		Finished = GemDOS_SetDTA(Params);
		break;
	 case 0x31:
		Finished = GemDOS_Ptermres(Params);
		break;
	 case 0x36:
		Finished = GemDOS_DFree(Params);
		break;
	 case 0x39:
		Finished = GemDOS_MkDir(Params);
		break;
	 case 0x3a:
		Finished = GemDOS_RmDir(Params);
		break;
	 case 0x3b:	/* Dsetpath */
		Finished = GemDOS_ChDir(Params);
		break;
	 case 0x3c:
		Finished = GemDOS_Create(Params);
		break;
	 case 0x3d:
		Finished = GemDOS_Open(Params);
		break;
	 case 0x3e:
		Finished = GemDOS_Close(Params);
		break;
	 case 0x3f:
		Finished = GemDOS_Read(Params);
		break;
	 case 0x40:
		Finished = GemDOS_Write(Params);
		break;
	 case 0x41:
		Finished = GemDOS_FDelete(Params);
		break;
	 case 0x42:
		Finished = GemDOS_LSeek(Params);
		break;
	 case 0x43:
		Finished = GemDOS_Fattrib(Params);
		break;
	 case 0x46:
		Finished = GemDOS_Force(Params);
		break;
	 case 0x47:	/* Dgetpath */
		Finished = GemDOS_GetDir(Params);
		break;
	 case 0x4b:
		/* Either false or CALL_PEXEC_ROUTINE */
		Finished = GemDOS_Pexec(Params);
		break;
	 case 0x4c:
		Finished = GemDOS_Pterm(Params);
		break;
	 case 0x4e:
		Finished = GemDOS_SFirst(Params);
		break;
	 case 0x4f:
		Finished = GemDOS_SNext();
		break;
	 case 0x56:
		Finished = GemDOS_Rename(Params);
		break;
	 case 0x57:
		Finished = GemDOS_GSDToF(Params);
		break;

	/* print args for other calls */

	case 0x01:	/* Conin */
	case 0x03:	/* Cauxin */
	case 0x12:	/* Cauxis */
	case 0x13:	/* Cauxos */
	case 0x0B:	/* Conis */
	case 0x10:	/* Conos */
	case 0x08:	/* Cnecin */
	case 0x11:	/* Cprnos */
	case 0x07:	/* Crawcin */
	case 0x19:	/* Dgetdrv */
	case 0x2F:	/* Fgetdta */
	case 0x30:	/* Sversion */
	case 0x2A:	/* Tgetdate */
	case 0x2C:	/* Tgettime */
		/* commands with no args */
		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%2hX %s() at PC 0x%X\n",
			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
			  M68000_GetPC());
		break;
		
	case 0x02:	/* Cconout */
	case 0x04:	/* Cauxout */
	case 0x05:	/* Cprnout */
	case 0x06:	/* Crawio */
	case 0x2b:	/* Tsetdate */
	case 0x2d:	/* Tsettime */
	case 0x45:	/* Fdup */
		/* commands taking single word */
		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%2hX %s(0x%hX) at PC 0x%X\n",
			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
			  STMemory_ReadWord(Params),
			  M68000_GetPC());
		break;

	case 0x09:	/* Cconws */
	case 0x0A:	/* Cconrs */
	case 0x20:	/* Super */
	case 0x48:	/* Malloc */
	case 0x49:	/* Mfree */
		/* commands taking long/pointer */
		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%2hX %s(0x%X) at PC 0x%X\n",
			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
			  STMemory_ReadLong(Params),
			  M68000_GetPC());
		break;

	case 0x44:	/* Mxalloc */
		/* commands taking long/pointer + word */
		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x44 Mxalloc(0x%X, 0x%hX) at PC 0x%X\n",
			  STMemory_ReadLong(Params),
			  STMemory_ReadWord(Params+SIZE_LONG),
			  M68000_GetPC());
		break;
	case 0x14:	/* Maddalt */
		/* commands taking 2 longs/pointers */
		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x14 Maddalt(0x%X, 0x%X) at PC 0x%X\n",
			  STMemory_ReadLong(Params),
			  STMemory_ReadLong(Params+SIZE_LONG),
			  M68000_GetPC());
	case 0x4A:	/* Mshrink */
		/* Mshrink's two pointers are prefixed by reserved zero word:
		 * http://toshyp.atari.org/en/00500c.html#Bindings_20for_20Mshrink
		 */
		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4A Mshrink(0x%X, 0x%X) at PC 0x%X\n",
			  STMemory_ReadLong(Params+SIZE_WORD),
			  STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG),
			  M68000_GetPC());
		break;

	default:
		/* rest of commands */
		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%2hX (%s) at PC 0x%X\n",
			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
			  M68000_GetPC());
	}

	switch(Finished)
	{
	 case true:
		/* skip over branch to pexec to RTE */
		SR |= SR_ZERO;
		/* visualize GemDOS emu HD access? */
		switch (GemDOSCall)
		{
		 case 0x36:
		 case 0x39:
		 case 0x3a:
		 case 0x3b:
		 case 0x3c:
		 case 0x3d:
		 case 0x3e:
		 case 0x3f:
		 case 0x40:
		 case 0x41:
		 case 0x42:
		 case 0x43:
		 case 0x47:
		 case 0x4e:
		 case 0x4f:
		 case 0x56:
			Statusbar_EnableHDLed( LED_STATE_ON );
		}
		break;
	 case CALL_PEXEC_ROUTINE:
		/* branch to pexec, then redirect to old gemdos. */
		SR |= SR_OVERFLOW;
		break;
	}

	M68000_SetSR(SR);   /* update the flags in the SR register */
}


/*-----------------------------------------------------------------------*/
/**
 * GemDOS_Boot - routine called on the first occurrence of the gemdos opcode.
 * (this should be in the cartridge bootrom)
 * Sets up our gemdos handler (or, if we don't need one, just turn off keyclicks)
 */
void GemDOS_Boot(void)
{
	bInitGemDOS = true;

	LOG_TRACE(TRACE_OS_GEMDOS, "Gemdos_Boot() at PC 0x%X\n", M68000_GetPC() );

	/* install our gemdos handler, if -e or --harddrive option used,
	 * or user wants to do GEMDOS tracing
	 */
	if (!GEMDOS_EMU_ON && !(LogTraceFlags & TRACE_OS_GEMDOS))
		return;

	/* Get the address of the p_run variable that points to the actual basepage */
	if (TosVersion == 0x100)
	{
		/* We have to use fix addresses on TOS 1.00 :-( */
		if ((STMemory_ReadWord(TosAddress+28)>>1) == 4)
			act_pd = 0x873c;    /* Spanish TOS is different from others! */
		else
			act_pd = 0x602c;
	}
	else
	{
		act_pd = STMemory_ReadLong(TosAddress + 0x28);
	}

	/* Save old GEMDOS handler address */
	STMemory_WriteLong(CART_OLDGEMDOS, STMemory_ReadLong(0x0084));
	/* Setup new GEMDOS handler, see "cart_asm.s" */
	STMemory_WriteLong(0x0084, CART_GEMDOS);
}
