mirror of
				https://github.com/Wiimpathy/HatariWii.git
				synced 2025-10-30 13:16:08 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			3486 lines
		
	
	
		
			87 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3486 lines
		
	
	
		
			87 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|   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"
 | |
| 
 | |
| #ifdef GEKKO
 | |
| #include <dirent.h>
 | |
| #include <sys/stat.h>
 | |
| 
 | |
| int access(const char *pathname, int mode) {
 | |
| 	struct stat sb;
 | |
| 
 | |
| 	if (stat(pathname, &sb) == -1) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /* 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 Uint32 DTA_Gemdos;   /* DTA address in ST memory space */
 | |
| static DTA *pDTA;           /* Our GEMDOS hard drive Disk Transfer Address structure */
 | |
| 			    /* This a direct pointer to DTA_Gemdos using STMemory_STAddrToPointer() */
 | |
| 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(×pec);
 | |
| 
 | |
| 	/* but keep previous access time */
 | |
| 	if (stat(filename, &filestat) != 0)
 | |
| 		return false;
 | |
| 	timebuf.actime = filestat.st_atime;
 | |
| 
 | |
| #ifdef GEKKO
 | |
| 	/* FIXME: no utime in devkitPPC/libogc, what's the best alternative here? */
 | |
| 	if (stat(filename, &timebuf) != 0)
 | |
| #else
 | |
| 	if (utime(filename, &timebuf) != 0)
 | |
| #endif
 | |
| 		return false;
 | |
| 	// fprintf(stderr, "set date '%s' for %s\n", asctime(×pec), 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);
 | |
| 
 | |
| 	/* Atari memory modified directly through pDTA members -> flush the data cache */
 | |
| 	M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA));
 | |
| 
 | |
| 	/* 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].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();
 | |
| 	DTA_Gemdos = 0x0;
 | |
| 	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 if it's a HDD identifier (or other emulated device)
 | |
| 	 * and if the file/folder is accessible (security basis) */
 | |
| 	if (iDrive > 1 && access(lpstrPath, F_OK) == 0 )
 | |
| 	{
 | |
| 		struct stat status;
 | |
| 		if (stat(lpstrPath, &status) == 0 && (status.st_mode & S_IFDIR) != 0)
 | |
| 		{
 | |
| 			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.nGemdosDrive == DRIVE_SKIP)
 | |
| 		SkipPartitions = ImagePartitions;
 | |
| 	else
 | |
| 		SkipPartitions = ConfigureParams.HardDisk.nGemdosDrive;
 | |
| 
 | |
| 	/* 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)
 | |
| {
 | |
| 	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)
 | |
| 	{
 | |
| 		/* Store the value in ST memory space */
 | |
| 		MemorySnapShot_Store ( &DTA_Gemdos , sizeof(DTA_Gemdos) );
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		/* Restore the value in ST memory space and update pDTA */
 | |
| 		MemorySnapShot_Store ( &DTA_Gemdos , sizeof(DTA_Gemdos) );
 | |
| 		if ( DTA_Gemdos == 0x0 )
 | |
| 			pDTA = NULL;
 | |
| 		else
 | |
| 			pDTA = (DTA *)STMemory_STAddrToPointer( DTA_Gemdos );
 | |
| 	}
 | |
| 	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_CheckAreaType ( basepage, 0x100, ABFLAG_RAM ) )
 | |
| 	{
 | |
| 		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 */
 | |
| 	DTA_Gemdos = STMemory_ReadLong(Params);
 | |
| 
 | |
| 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x1A Fsetdta(0x%x) at PC 0x%X\n", DTA_Gemdos,
 | |
| 		  M68000_GetPC());
 | |
| 
 | |
| 	if ( STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) )
 | |
| 	{
 | |
| 		/* Store as PC pointer */
 | |
| 		pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		Log_Printf(LOG_WARN, "GEMDOS Fsetdta() failed due to invalid DTA address 0x%x\n", DTA_Gemdos);
 | |
| 		DTA_Gemdos = 0x0;
 | |
| 		pDTA = NULL;
 | |
| 	}
 | |
| 	/* 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_CheckAreaType ( Address, 16, ABFLAG_RAM ) )
 | |
| 	{
 | |
| 		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 *)STMemory_STAddrToPointer(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 *)STMemory_STAddrToPointer(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 *)STMemory_STAddrToPointer(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 *)STMemory_STAddrToPointer(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 *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
 | |
| 	Mode = STMemory_ReadWord(Params+SIZE_LONG);
 | |
| 	Mode &= 3;
 | |
| 
 | |
| 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE,
 | |
| 		  "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|TRACE_OS_BASE, "-> 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|TRACE_OS_BASE, "-> 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|TRACE_OS_BASE, "-> 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|TRACE_OS_BASE,
 | |
| 		  "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;
 | |
| 	off_t CurrentPos, FileSize;
 | |
| 	long 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);
 | |
| 
 | |
| 	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 = ftello(FileHandles[Handle].FileHandle);
 | |
| 	if (CurrentPos == -1L
 | |
| 	    || fseeko(FileHandles[Handle].FileHandle, 0, SEEK_END) != 0)
 | |
| 	{
 | |
| 		Regs[REG_D0] = GEMDOS_E_SEEK;
 | |
| 		return true;
 | |
| 	}
 | |
| 	FileSize = ftello(FileHandles[Handle].FileHandle);
 | |
| 	if (FileSize == -1L
 | |
| 	    || fseeko(FileHandles[Handle].FileHandle, CurrentPos, SEEK_SET) != 0)
 | |
| 	{
 | |
| 		Regs[REG_D0] = GEMDOS_E_SEEK;
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	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_CheckAreaType ( Addr, Size, ABFLAG_RAM ) )
 | |
| 	{
 | |
| 		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;
 | |
| 	}
 | |
| 
 | |
| 	/* Atari memory modified directly with fread() -> flush the instr/data caches */
 | |
| 	M68000_Flush_All_Caches(Addr, Size);
 | |
| 
 | |
| 	/* And read data in */
 | |
| 	pBuffer = (char *)STMemory_STAddrToPointer(Addr);
 | |
| 	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);
 | |
| 
 | |
| 	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_CheckAreaType ( Addr, Size, ABFLAG_RAM ) )
 | |
| 	{
 | |
| 		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;
 | |
| 	}
 | |
| 
 | |
| 	pBuffer = (char *)STMemory_STAddrToPointer(Addr);
 | |
| 	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 *)STMemory_STAddrToPointer(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 */
 | |
| 	if (fseek(fhndl, 0L, SEEK_END) != 0)
 | |
| 	{
 | |
| 		Regs[REG_D0] = GEMDOS_E_SEEK;
 | |
| 		return true;
 | |
| 	}
 | |
| 	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: nDestPos = -1;
 | |
| 	}
 | |
| 
 | |
| 	if (nDestPos < 0 || nDestPos > nFileSize)
 | |
| 	{
 | |
| 		/* Restore old position and return error */
 | |
| 		if (fseek(fhndl, nOldPos, SEEK_SET) != 0)
 | |
| 			perror("GemDOS_LSeek");
 | |
| 		Regs[REG_D0] = GEMDOS_ERANGE;
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/* Seek to new position and return offset from start of file */
 | |
| 	if (fseek(fhndl, nDestPos, SEEK_SET) != 0)
 | |
| 		perror("GemDOS_LSeek");
 | |
| 	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 *)STMemory_STAddrToPointer(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_CheckAreaType ( Address, len, ABFLAG_RAM ) )
 | |
| 		{
 | |
| 			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 *)STMemory_STAddrToPointer(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|TRACE_OS_BASE))
 | |
| 	{
 | |
| 		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;
 | |
| 			char *str;
 | |
| 			const char *name, *cmd;
 | |
| 			name = (const char *)STMemory_STAddrToPointer(fname);
 | |
| 			cmd = (const char *)STMemory_STAddrToPointer(cmdline);
 | |
| 			cmdlen = *cmd++;
 | |
| 			str = malloc(cmdlen+1);
 | |
| 			memcpy(str, cmd, cmdlen);
 | |
| 			str[cmdlen] = '\0';
 | |
| 			LOG_TRACE_PRINT ( "GEMDOS 0x4B Pexec(%i, \"%s\", [%d]\"%s\", 0x%x) at PC 0x%X\n", Mode, name, cmdlen, str, env_string,
 | |
| 				M68000_GetPC());
 | |
| 			free(str);
 | |
| 		}
 | |
| 		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 *)STMemory_STAddrToPointer(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;
 | |
| 	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) */
 | |
| 	DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd)+32);
 | |
| 
 | |
| 	if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) )
 | |
| 	{
 | |
| 		Log_Printf(LOG_WARN, "GEMDOS Fsnext() failed due to invalid DTA address 0x%x\n", DTA_Gemdos);
 | |
| 		DTA_Gemdos = 0x0;
 | |
| 		pDTA = NULL;
 | |
| 		Regs[REG_D0] = GEMDOS_EINTRN;    /* "internal error */
 | |
| 		return true;
 | |
| 	}
 | |
| 	pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos);
 | |
| 
 | |
| 	/* 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;
 | |
| 	int Drive;
 | |
| 	DIR *fsdir;
 | |
| 	int i,j,count;
 | |
| 
 | |
| 	/* Find filename to search for */
 | |
| 	pszFileName = (char *)STMemory_STAddrToPointer(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) */
 | |
| 	DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd)+32);
 | |
| 
 | |
| 	if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) )
 | |
| 	{
 | |
| 		Log_Printf(LOG_WARN, "GEMDOS Fsfirst() failed due to invalid DTA address 0x%x\n", DTA_Gemdos);
 | |
| 		DTA_Gemdos = 0x0;
 | |
| 		pDTA = NULL;
 | |
| 		Regs[REG_D0] = GEMDOS_EINTRN;    /* "internal error */
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/* Atari memory modified directly with do_mem_* + strcpy() -> flush the data cache */
 | |
| 	M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA));
 | |
| 
 | |
| 	pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos);
 | |
| 
 | |
| 	/* 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 *)STMemory_STAddrToPointer(STMemory_ReadLong(Params+SIZE_WORD));
 | |
| 	pszNewFileName = (char *)STMemory_STAddrToPointer(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_CheckAreaType ( pBuffer, 4, ABFLAG_RAM ) )
 | |
| 		{
 | |
| 			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|TRACE_OS_BASE, "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|TRACE_OS_BASE, "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|TRACE_OS_BASE, "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(FILE *fp, Uint32 bShowOpcodes)
 | |
| {
 | |
| 	int i, used;
 | |
| 
 | |
| 	if (bShowOpcodes)
 | |
| 	{
 | |
| 		Uint16 opcode;
 | |
| 		for (opcode = 0; opcode < 0x5A; )
 | |
| 		{
 | |
| 			fprintf(fp, "%02x %-9s",
 | |
| 				opcode, GemDOS_Opcode2Name(opcode));
 | |
| 			if (++opcode % 6 == 0)
 | |
| 				fputs("\n", fp);
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!GEMDOS_EMU_ON)
 | |
| 	{
 | |
| 		fputs("GEMDOS HDD emulation isn't enabled!\n", fp);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* GEMDOS vector set by Hatari can be overwritten e.g. MiNT */
 | |
| 	fprintf(fp, "Current GEMDOS handler: (0x84) = 0x%x, emu one = 0x%x\n", STMemory_ReadLong(0x0084), CART_GEMDOS);
 | |
| 	fprintf(fp, "Stored GEMDOS handler: (0x%x) = 0x%x\n\n", CART_OLDGEMDOS, STMemory_ReadLong(CART_OLDGEMDOS));
 | |
| 
 | |
| 	fprintf(fp, "Connected drives mask: 0x%x\n\n", ConnectedDriveMask);
 | |
| 	fputs("GEMDOS HDD emulation drives:\n", fp);
 | |
| 	for(i = 0; i < MAX_HARDDRIVES; i++)
 | |
| 	{
 | |
| 		if (!emudrives[i])
 | |
| 			continue;
 | |
| 		fprintf(fp, "- %c: %s\n",
 | |
| 			'A' + emudrives[i]->drive_number,
 | |
| 			emudrives[i]->hd_emulation_dir);
 | |
| 	}
 | |
| 
 | |
| 	fputs("\nInternal Fsfirst() DTAs:\n", fp);
 | |
| 	for(used = i = 0; i < ARRAYSIZE(InternalDTAs); i++)
 | |
| 	{
 | |
| 		int j, centry, entries;
 | |
| 
 | |
| 		if (!InternalDTAs[i].bUsed)
 | |
| 			continue;
 | |
| 
 | |
| 		fprintf(fp, "+ %d: %s\n", i, InternalDTAs[i].path);
 | |
| 		
 | |
| 		centry = InternalDTAs[i].centry;
 | |
| 		entries = InternalDTAs[i].nentries;
 | |
| 		for (j = 0; j < entries; j++)
 | |
| 		{
 | |
| 			fprintf(fp, "  - %d: %s%s\n",
 | |
| 				j, InternalDTAs[i].found[j]->d_name,
 | |
| 				j == centry ? " *" : "");
 | |
| 		}
 | |
| 		fprintf(fp, "  Fsnext entry = %d.\n", centry);
 | |
| 		used++;
 | |
| 	}
 | |
| 	if (!used)
 | |
| 		fputs("- None in use.\n", fp);
 | |
| 
 | |
| 	fputs("\nOpen GEMDOS HDD file handles:\n", fp);
 | |
| 	for (used = i = 0; i < ARRAYSIZE(FileHandles); i++)
 | |
| 	{
 | |
| 		if (!FileHandles[i].bUsed)
 | |
| 			continue;
 | |
| 		fprintf(fp, "- %d (0x%x): %s\n", i + BASE_FILEHANDLE,
 | |
| 			FileHandles[i].Basepage, FileHandles[i].szActualName);
 | |
| 		used++;
 | |
| 	}
 | |
| 	if (!used)
 | |
| 		fputs("- None.\n", fp);
 | |
| 	fputs("\nForced GEMDOS HDD file handles:\n", fp);
 | |
| 	for (used = i = 0; i < ARRAYSIZE(ForcedHandles); i++)
 | |
| 	{
 | |
| 		if (ForcedHandles[i].Handle == UNFORCED_HANDLE)
 | |
| 			continue;
 | |
| 		fprintf(fp, "- %d -> %d (0x%x)\n", i,
 | |
| 			ForcedHandles[i].Handle + BASE_FILEHANDLE,
 | |
| 			ForcedHandles[i].Basepage);
 | |
| 		used++;
 | |
| 	}
 | |
| 	if (!used)
 | |
| 		fputs("- None.\n", fp);
 | |
| }
 | |
| 
 | |
| #else /* !ENABLE_TRACING */
 | |
| void GemDOS_Info(FILE *fp, Uint32 bShowOpcodes)
 | |
| {
 | |
| 	fputs("Hatari isn't configured with ENABLE_TRACING\n", fp);
 | |
| }
 | |
| #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%02hX %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%02hX %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%02hX %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%02hX (%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|TRACE_OS_BASE)))
 | |
| 		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);
 | |
| }
 | 
