1 /*
2 Hatari - gemdos.c
3
4 This file is distributed under the GNU General Public License, version 2
5 or at your option any later version. Read the file gpl.txt for details.
6
7 GEMDOS intercept routines.
8 These are used mainly for hard drive redirection of high level file routines.
9
10 Host file names are handled case insensitively, so files on GEMDOS
11 drive emulation directories may be either in lower or upper case.
12
13 Too long file and directory names and names with invalid characters
14 are converted to TOS compatible 8+3 names, but matching them back to
15 host names is slower and may match several such filenames (of which
16 first one will be returned), so using them should be avoided.
17
18 Bugs/things to fix:
19 * Host filenames are in many places limited to 255 chars (same as
20 on TOS), FILENAME_MAX should be used if that's a problem.
21 * rmdir routine, can't remove dir with files in it. (another tos/unix difference)
22 * Fix bugs, there are probably a few lurking around in here..
23 */
24 const char Gemdos_fileid[] = "Hatari gemdos.c : " __DATE__ " " __TIME__;
25
26 #include <config.h>
27
28 #include <sys/stat.h>
29 #if HAVE_STATVFS
30 #include <sys/statvfs.h>
31 #endif
32 #include <sys/types.h>
33 #if HAVE_UTIME_H
34 #include <utime.h>
35 #elif HAVE_SYS_UTIME_H
36 #include <sys/utime.h>
37 #endif
38 #include <time.h>
39 #include <ctype.h>
40 #include <unistd.h>
41 #include <errno.h>
42
43 #include "main.h"
44 #include "cart.h"
45 #include "configuration.h"
46 #include "file.h"
47 #include "floppy.h"
48 #include "ide.h"
49 #include "inffile.h"
50 #include "hdc.h"
51 #include "gemdos.h"
52 #include "gemdos_defines.h"
53 #include "log.h"
54 #include "m68000.h"
55 #include "memorySnapShot.h"
56 #include "printer.h"
57 #include "statusbar.h"
58 #include "scandir.h"
59 #include "stMemory.h"
60 #include "str.h"
61 #include "tos.h"
62 #include "hatari-glue.h"
63 #include "maccess.h"
64 #include "symbols.h"
65
66 /* Maximum supported length of a GEMDOS path: */
67 #define MAX_GEMDOS_PATH 256
68
69 #define BASEPAGE_SIZE (0x80+0x80) /* info + command line */
70 #define BASEPAGE_OFFSET_DTA 0x20
71 #define BASEPAGE_OFFSET_PARENT 0x24
72
73 /* Have we re-directed GemDOS vector to our own routines yet? */
74 bool bInitGemDOS;
75
76 /* structure with all the drive-specific data for our emulated drives,
77 * used by GEMDOS_EMU_ON macro
78 */
79 EMULATEDDRIVE **emudrives = NULL;
80
81 #define ISHARDDRIVE(Drive) (Drive!=-1)
82
83 /*
84 Disk Transfer Address (DTA)
85 */
86 #define TOS_NAMELEN 14
87
88 typedef struct {
89 /* GEMDOS internals */
90 Uint8 index[2];
91 Uint8 magic[4];
92 char dta_pat[TOS_NAMELEN]; /* unused */
93 char dta_sattrib; /* unused */
94 /* TOS API */
95 char dta_attrib;
96 Uint8 dta_time[2];
97 Uint8 dta_date[2];
98 Uint8 dta_size[4];
99 char dta_name[TOS_NAMELEN];
100 } DTA;
101
102 #define DTA_MAGIC_NUMBER 0x12983476
103 #define MAX_DTAS_FILES 256 /* Must be ^2 */
104 #define MAX_DTAS_MASK (MAX_DTAS_FILES-1)
105 #define CALL_PEXEC_ROUTINE 3 /* Call our cartridge pexec routine */
106
107 #define BASE_FILEHANDLE 64 /* Our emulation handles - MUST not be valid TOS ones, but MUST be <256 */
108 #define MAX_FILE_HANDLES 32 /* We can allow 32 files open at once */
109
110 /*
111 DateTime structure used by TOS call $57 f_dtatime
112 Changed to fix potential problem with alignment.
113 */
114 typedef struct {
115 Uint16 timeword;
116 Uint16 dateword;
117 } DATETIME;
118
119 #define UNFORCED_HANDLE -1
120 static struct {
121 int Handle;
122 Uint32 Basepage;
123 } ForcedHandles[5]; /* (standard) handles aliased to emulated handles */
124
125 typedef struct
126 {
127 bool bUsed;
128 char szMode[4]; /* enough for all used fopen() modes: rb/rb+/wb+ */
129 Uint32 Basepage;
130 FILE *FileHandle;
131 /* TODO: host path might not fit into this */
132 char szActualName[MAX_GEMDOS_PATH]; /* used by F_DATIME (0x57) */
133 } FILE_HANDLE;
134
135 typedef struct
136 {
137 bool bUsed;
138 int nentries; /* number of entries in fs directory */
139 int centry; /* current entry # */
140 struct dirent **found; /* legal files */
141 char path[MAX_GEMDOS_PATH]; /* sfirst path */
142 } INTERNAL_DTA;
143
144 static FILE_HANDLE FileHandles[MAX_FILE_HANDLES];
145 static INTERNAL_DTA InternalDTAs[MAX_DTAS_FILES];
146 static int DTAIndex; /* Circular index into above */
147 static Uint16 CurrentDrive; /* Current drive (0=A,1=B,2=C etc...) */
148 static Uint32 act_pd; /* Used to get a pointer to the current basepage */
149 static Uint16 nAttrSFirst; /* File attribute for SFirst/Snext */
150 static Uint32 CallingPC; /* Program counter from caller */
151
152 /* last program opened by GEMDOS emulation */
153 static bool PexecCalled;
154
155 #if defined(WIN32) && !defined(mkdir)
156 #define mkdir(name,mode) mkdir(name)
157 #endif /* WIN32 */
158
159 #ifndef S_IRGRP
160 #define S_IRGRP 0
161 #define S_IROTH 0
162 #endif
163
164 /* set to 1 if you want to see debug output from pattern matching */
165 #define DEBUG_PATTERN_MATCH 0
166
167
168 /*-------------------------------------------------------*/
169 /**
170 * Routine to convert time and date to GEMDOS format.
171 * Originally from the STonX emulator. (cheers!)
172 */
GemDOS_DateTime2Tos(time_t t,DATETIME * DateTime,const char * fname)173 static void GemDOS_DateTime2Tos(time_t t, DATETIME *DateTime, const char *fname)
174 {
175 struct tm *x;
176
177 /* localtime takes DST into account */
178 x = localtime(&t);
179
180 if (x == NULL)
181 {
182 Log_Printf(LOG_WARN, "'%s' timestamp is invalid for (Windows?) localtime(), defaulting to TOS epoch!", fname);
183 DateTime->dateword = 1|(1<<5); /* 1980-01-01 */
184 DateTime->timeword = 0;
185 return;
186 }
187 /* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */
188 DateTime->timeword = (x->tm_sec>>1)|(x->tm_min<<5)|(x->tm_hour<<11);
189
190 /* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */
191 DateTime->dateword = x->tm_mday | ((x->tm_mon+1)<<5)
192 | (((x->tm_year-80 > 0) ? x->tm_year-80 : 0) << 9);
193 }
194
195 /*-----------------------------------------------------------------------*/
196 /**
197 * Populate a DATETIME structure with file info. Handle needs to be
198 * validated before calling. Return true on success.
199 */
GemDOS_GetFileInformation(int Handle,DATETIME * DateTime)200 static bool GemDOS_GetFileInformation(int Handle, DATETIME *DateTime)
201 {
202 const char *fname = FileHandles[Handle].szActualName;
203 struct stat fstat;
204
205 if (stat(fname, &fstat) == 0)
206 {
207 GemDOS_DateTime2Tos(fstat.st_mtime, DateTime, fname);
208 return true;
209 }
210 return false;
211 }
212
213 /*-----------------------------------------------------------------------*/
214 /**
215 * Set given file date/time from given DATETIME. Handle needs to be
216 * validated before calling. Return true on success.
217 */
GemDOS_SetFileInformation(int Handle,DATETIME * DateTime)218 static bool GemDOS_SetFileInformation(int Handle, DATETIME *DateTime)
219 {
220 const char *filename;
221 struct utimbuf timebuf;
222 struct stat filestat;
223 struct tm timespec;
224
225 /* make sure Hatari itself doesn't need to write/modify
226 * the file after it's modification time is changed.
227 */
228 fflush(FileHandles[Handle].FileHandle);
229
230 /* use host modification times instead of Atari ones? */
231 if (ConfigureParams.HardDisk.bGemdosHostTime)
232 return true;
233
234 filename = FileHandles[Handle].szActualName;
235
236 /* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */
237 timespec.tm_sec = (DateTime->timeword & 0x1F) << 1;
238 timespec.tm_min = (DateTime->timeword & 0x7E0) >> 5;
239 timespec.tm_hour = (DateTime->timeword & 0xF800) >> 11;
240 /* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */
241 timespec.tm_mday = (DateTime->dateword & 0x1F);
242 timespec.tm_mon = ((DateTime->dateword & 0x1E0) >> 5) - 1;
243 timespec.tm_year = ((DateTime->dateword & 0xFE00) >> 9) + 80;
244 /* check whether DST should be taken into account */
245 timespec.tm_isdst = -1;
246
247 /* set new modification time */
248 timebuf.modtime = mktime(×pec);
249
250 /* but keep previous access time */
251 if (stat(filename, &filestat) != 0)
252 return false;
253 timebuf.actime = filestat.st_atime;
254
255 if (utime(filename, &timebuf) != 0)
256 return false;
257 // fprintf(stderr, "set date '%s' for %s\n", asctime(×pec), name);
258 return true;
259 }
260
261
262 /*-----------------------------------------------------------------------*/
263 /**
264 * Convert from FindFirstFile/FindNextFile attribute to GemDOS format
265 */
GemDOS_ConvertAttribute(mode_t mode)266 static Uint8 GemDOS_ConvertAttribute(mode_t mode)
267 {
268 Uint8 Attrib = 0;
269
270 /* Directory attribute */
271 if (S_ISDIR(mode))
272 Attrib |= GEMDOS_FILE_ATTRIB_SUBDIRECTORY;
273
274 /* Read-only attribute */
275 if (!(mode & S_IWUSR))
276 Attrib |= GEMDOS_FILE_ATTRIB_READONLY;
277
278 /* TODO, Other attributes:
279 * - GEMDOS_FILE_ATTRIB_HIDDEN (file not visible on desktop/fsel)
280 * - GEMDOS_FILE_ATTRIB_ARCHIVE (file written after being backed up)
281 * ?
282 */
283 return Attrib;
284 }
285
286
287 /*-----------------------------------------------------------------------*/
288 /**
289 * Populate the DTA buffer with file info.
290 * @return 0 if entry is ok, 1 if entry should be skipped, < 0 for errors.
291 */
PopulateDTA(char * path,struct dirent * file,DTA * pDTA,Uint32 DTA_Gemdos)292 static int PopulateDTA(char *path, struct dirent *file, DTA *pDTA, Uint32 DTA_Gemdos)
293 {
294 /* TODO: host file path can be longer than MAX_GEMDOS_PATH */
295 char tempstr[MAX_GEMDOS_PATH];
296 struct stat filestat;
297 DATETIME DateTime;
298 int nFileAttr, nAttrMask;
299
300 if (snprintf(tempstr, sizeof(tempstr), "%s%c%s",
301 path, PATHSEP, file->d_name) >= (int)sizeof(tempstr))
302 {
303 Log_Printf(LOG_ERROR, "PopulateDTA: path is too long.\n");
304 return -1;
305 }
306
307 if (stat(tempstr, &filestat) != 0)
308 {
309 perror(tempstr);
310 return -1; /* return on error */
311 }
312
313 if (!pDTA)
314 return -2; /* no DTA pointer set */
315
316 /* Check file attributes (check is done according to the Profibuch) */
317 nFileAttr = GemDOS_ConvertAttribute(filestat.st_mode);
318 nAttrMask = nAttrSFirst|GEMDOS_FILE_ATTRIB_WRITECLOSE|GEMDOS_FILE_ATTRIB_READONLY;
319 if (nFileAttr != 0 && !(nAttrMask & nFileAttr))
320 return 1;
321
322 GemDOS_DateTime2Tos(filestat.st_mtime, &DateTime, tempstr);
323
324 /* Atari memory modified directly through pDTA members -> flush the data cache */
325 M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA));
326
327 /* convert to atari-style uppercase */
328 Str_Filename2TOSname(file->d_name, pDTA->dta_name);
329 #if DEBUG_PATTERN_MATCH
330 fprintf(stderr, "DEBUG: GEMDOS: host: %s -> GEMDOS: %s\n",
331 file->d_name, pDTA->dta_name);
332 #endif
333 do_put_mem_long(pDTA->dta_size, filestat.st_size);
334 do_put_mem_word(pDTA->dta_time, DateTime.timeword);
335 do_put_mem_word(pDTA->dta_date, DateTime.dateword);
336 pDTA->dta_attrib = nFileAttr;
337
338 return 0;
339 }
340
341
342 /*-----------------------------------------------------------------------*/
343 /**
344 * Clear given DTA cache structure.
345 */
ClearInternalDTA(int idx)346 static void ClearInternalDTA(int idx)
347 {
348 int i;
349
350 /* clear the old DTA structure */
351 if (InternalDTAs[idx].found != NULL)
352 {
353 for (i = 0; i < InternalDTAs[idx].nentries; i++)
354 free(InternalDTAs[idx].found[i]);
355 free(InternalDTAs[idx].found);
356 InternalDTAs[idx].found = NULL;
357 }
358 InternalDTAs[idx].nentries = 0;
359 InternalDTAs[idx].bUsed = false;
360 }
361
362 /*-----------------------------------------------------------------------*/
363 /**
364 * Clear all DTA cache structures.
365 */
GemDOS_ClearAllInternalDTAs(void)366 static void GemDOS_ClearAllInternalDTAs(void)
367 {
368 int i;
369 for(i = 0; i < ARRAY_SIZE(InternalDTAs); i++)
370 {
371 ClearInternalDTA(i);
372 }
373 DTAIndex = 0;
374 }
375
376
377 /*-----------------------------------------------------------------------*/
378 /**
379 * Match a TOS file name to a dir mask.
380 */
fsfirst_match(const char * pat,const char * name)381 static bool fsfirst_match(const char *pat, const char *name)
382 {
383 const char *dot, *p=pat, *n=name;
384
385 if (name[0] == '.')
386 return false; /* skip .* files */
387
388 dot = strrchr(name, '.'); /* '*' matches everything except last dot in name */
389 if (dot && p[0] == '*' && p[1] == 0)
390 return false; /* plain '*' must not match anything with extension */
391
392 while (*n)
393 {
394 if (*p=='*')
395 {
396 while (*n && n != dot)
397 n++;
398 p++;
399 }
400 else if (*p=='?' && *n)
401 {
402 n++;
403 p++;
404 }
405 else if (toupper((unsigned char)*p++) != toupper((unsigned char)*n++))
406 return false;
407 }
408
409 /* printf("'%s': '%s' -> '%s' : '%s' -> %d\n", name, pat, n, p); */
410
411 /* The traversed name matches the pattern, if pattern also
412 * ends here, or with '*'. '*' for extension matches also
413 * filenames without extension, so pattern ending with
414 * '.*' will also be a match.
415 */
416 return (
417 (p[0] == 0) ||
418 (p[0] == '*' && p[1] == 0) ||
419 (p[0] == '.' && p[1] == '*' && p[2] == 0)
420 );
421 }
422
423
424 /*-----------------------------------------------------------------------*/
425 /**
426 * Parse directory from sfirst mask
427 * - e.g.: input: "hdemudir/auto/mask*.*" outputs: "hdemudir/auto"
428 */
fsfirst_dirname(const char * string,char * newstr)429 static void fsfirst_dirname(const char *string, char *newstr)
430 {
431 int i=0;
432
433 strcpy(newstr, string);
434
435 /* convert to front slashes and go to end of string. */
436 while (newstr[i] != '\0')
437 {
438 if (newstr[i] == '\\')
439 newstr[i] = PATHSEP;
440 i++;
441 }
442 /* find last slash and terminate string */
443 while (i && newstr[i] != PATHSEP)
444 i--;
445 newstr[i] = '\0';
446 }
447
448
449 /*-----------------------------------------------------------------------*/
450 /**
451 * Return directory mask part from the given string
452 */
fsfirst_dirmask(const char * string)453 static const char* fsfirst_dirmask(const char *string)
454 {
455 const char *lastsep;
456
457 lastsep = strrchr(string, PATHSEP);
458 if (lastsep)
459 return lastsep + 1;
460 else
461 return string;
462 }
463
464 /*-----------------------------------------------------------------------*/
465 /**
466 * Close given internal file handle if it's still in use
467 * and (always) reset handle variables
468 */
GemDOS_CloseFileHandle(int i)469 static void GemDOS_CloseFileHandle(int i)
470 {
471 if (FileHandles[i].bUsed)
472 fclose(FileHandles[i].FileHandle);
473 FileHandles[i].FileHandle = NULL;
474 FileHandles[i].Basepage = 0;
475 FileHandles[i].bUsed = false;
476 }
477
478 /**
479 * Un-force given file handle
480 */
GemDOS_UnforceFileHandle(int i)481 static void GemDOS_UnforceFileHandle(int i)
482 {
483 ForcedHandles[i].Handle = UNFORCED_HANDLE;
484 ForcedHandles[i].Basepage = 0;
485 }
486
487 /**
488 * Clear & un-force all file handles
489 */
GemDOS_ClearAllFileHandles(void)490 static void GemDOS_ClearAllFileHandles(void)
491 {
492 int i;
493
494 for(i = 0; i < ARRAY_SIZE(FileHandles); i++)
495 {
496 GemDOS_CloseFileHandle(i);
497 }
498 for(i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
499 {
500 GemDOS_UnforceFileHandle(i);
501 }
502 }
503
504 /*-----------------------------------------------------------------------*/
505
506 /**
507 * If program was executed, store path to it
508 * (should be called only by Fopen)
509 */
GemDOS_UpdateCurrentProgram(int Handle)510 static void GemDOS_UpdateCurrentProgram(int Handle)
511 {
512 /* only first Fopen after Pexec needs to be handled */
513 if (!PexecCalled)
514 return;
515 PexecCalled = false;
516
517 /* store program path */
518 Symbols_ChangeCurrentProgram(FileHandles[Handle].szActualName);
519 }
520
521 /*-----------------------------------------------------------------------*/
522 /**
523 * Initialize GemDOS/PC file system
524 */
GemDOS_Init(void)525 void GemDOS_Init(void)
526 {
527 bInitGemDOS = false;
528
529 GemDOS_ClearAllFileHandles();
530 GemDOS_ClearAllInternalDTAs();
531
532 }
533
534 /*-----------------------------------------------------------------------*/
535 /**
536 * Initialize GemDOS drives current paths (to drive root)
537 */
GemDOS_InitCurPaths(void)538 static void GemDOS_InitCurPaths(void)
539 {
540 int i;
541
542 if (emudrives)
543 {
544 for (i = 0; i < MAX_HARDDRIVES; i++)
545 {
546 if (emudrives[i])
547 {
548 /* Initialize current directory to the root of the drive */
549 strcpy(emudrives[i]->fs_currpath, emudrives[i]->hd_emulation_dir);
550 File_AddSlashToEndFileName(emudrives[i]->fs_currpath);
551 }
552 }
553 }
554 }
555
556 /*-----------------------------------------------------------------------*/
557 /**
558 * Reset GemDOS file system
559 */
GemDOS_Reset(void)560 void GemDOS_Reset(void)
561 {
562 GemDOS_Init();
563 GemDOS_InitCurPaths();
564
565 /* Reset */
566 act_pd = 0;
567 CurrentDrive = nBootDrive;
568 Symbols_RemoveCurrentProgram();
569 INF_CreateOverride();
570 }
571
572 /*-----------------------------------------------------------------------*/
573 /**
574 * Routine to check the Host OS HDD path for a Drive letter sub folder
575 */
GEMDOS_DoesHostDriveFolderExist(char * lpstrPath,int iDrive)576 static bool GEMDOS_DoesHostDriveFolderExist(char* lpstrPath, int iDrive)
577 {
578 bool bExist = false;
579
580 Log_Printf(LOG_DEBUG, "Checking GEMDOS %c: HDD: %s\n", 'A'+iDrive, lpstrPath);
581
582 if (access(lpstrPath, F_OK) != 0 )
583 {
584 /* Try lower case drive letter instead */
585 int iIndex = strlen(lpstrPath)-1;
586 lpstrPath[iIndex] = tolower((unsigned char)lpstrPath[iIndex]);
587 }
588
589 /* Check if it's a HDD identifier (or other emulated device)
590 * and if the file/folder is accessible (security basis) */
591 if (iDrive > 1 && access(lpstrPath, F_OK) == 0 )
592 {
593 struct stat status;
594 if (stat(lpstrPath, &status) == 0 && (status.st_mode & S_IFDIR) != 0)
595 {
596 bExist = true;
597 }
598 else
599 {
600 Log_Printf(LOG_WARN, "Not suitable as GEMDOS HDD dir: %s\n", lpstrPath);
601 }
602 }
603
604 return bExist;
605 }
606
607
608 /**
609 * Determine upper limit of partitions that should be emulated.
610 *
611 * @return true if multiple GEMDOS partitions should be emulated, false otherwise
612 */
GemDOS_DetermineMaxPartitions(int * pnMaxDrives)613 static bool GemDOS_DetermineMaxPartitions(int *pnMaxDrives)
614 {
615 struct dirent **files;
616 int count, i, last;
617 char letter;
618 bool bMultiPartitions;
619
620 *pnMaxDrives = 0;
621
622 /* Scan through the main directory to see whether there are just single
623 * letter sub-folders there (then use multi-partition mode) or if
624 * arbitrary sub-folders are there (then use single-partition mode)
625 */
626 count = scandir(ConfigureParams.HardDisk.szHardDiskDirectories[0], &files, 0, alphasort);
627 if (count < 0)
628 {
629 Log_Printf(LOG_ERROR, "GEMDOS hard disk emulation failed:\n "
630 "Can not access '%s'.\n", ConfigureParams.HardDisk.szHardDiskDirectories[0]);
631 return false;
632 }
633 else if (count <= 2)
634 {
635 /* Empty directory Only "." and ".."), assume single partition mode */
636 last = 1;
637 bMultiPartitions = false;
638 }
639 else
640 {
641 bMultiPartitions = true;
642 /* Check all files in the directory */
643 last = 0;
644 for (i = 0; i < count; i++)
645 {
646 letter = toupper((unsigned char)files[i]->d_name[0]);
647 if (!letter || letter == '.')
648 {
649 /* Ignore hidden files like "." and ".." */
650 continue;
651 }
652
653 if (letter < 'C' || letter > 'Z' || files[i]->d_name[1])
654 {
655 /* folder with name other than C-Z...
656 * (until Z under MultiTOS, to P otherwise)
657 * ... so use single partition mode! */
658 last = 1;
659 bMultiPartitions = false;
660 break;
661 }
662
663 /* alphasort isn't case insensitive */
664 letter = letter - 'C' + 1;
665 if (letter > last)
666 last = letter;
667 }
668 }
669
670 if (last > MAX_HARDDRIVES)
671 *pnMaxDrives = MAX_HARDDRIVES;
672 else
673 *pnMaxDrives = last;
674
675 /* Free file list */
676 for (i = 0; i < count; i++)
677 free(files[i]);
678 free(files);
679
680 return bMultiPartitions;
681 }
682
683 /*-----------------------------------------------------------------------*/
684 /**
685 * Initialize a GEMDOS drive.
686 * Supports up to MAX_HARDDRIVES HDD units.
687 */
GemDOS_InitDrives(void)688 void GemDOS_InitDrives(void)
689 {
690 int i;
691 int nMaxDrives;
692 int DriveNumber;
693 int SkipPartitions;
694 int ImagePartitions;
695 bool bMultiPartitions;
696
697 bMultiPartitions = GemDOS_DetermineMaxPartitions(&nMaxDrives);
698
699 /* intialize data for harddrive emulation: */
700 if (nMaxDrives > 0 && !emudrives)
701 {
702 emudrives = calloc(MAX_HARDDRIVES, sizeof(EMULATEDDRIVE *));
703 if (!emudrives)
704 {
705 perror("GemDOS_InitDrives");
706 return;
707 }
708 }
709
710 ImagePartitions = nAcsiPartitions + nIDEPartitions;
711 if (ConfigureParams.HardDisk.nGemdosDrive == DRIVE_SKIP)
712 SkipPartitions = ImagePartitions;
713 else
714 SkipPartitions = ConfigureParams.HardDisk.nGemdosDrive;
715
716 /* Now initialize all available drives */
717 for(i = 0; i < nMaxDrives; i++)
718 {
719 /* If single partition mode, skip to specified / first free drive */
720 if (!bMultiPartitions)
721 {
722 i += SkipPartitions;
723 }
724
725 /* Allocate emudrives entry for this drive */
726 emudrives[i] = malloc(sizeof(EMULATEDDRIVE));
727 if (!emudrives[i])
728 {
729 perror("GemDOS_InitDrives");
730 continue;
731 }
732
733 /* set emulation directory string */
734 strcpy(emudrives[i]->hd_emulation_dir, ConfigureParams.HardDisk.szHardDiskDirectories[0]);
735
736 /* remove trailing slash, if any in the directory name */
737 File_CleanFileName(emudrives[i]->hd_emulation_dir);
738
739 /* Add Requisit Folder ID */
740 if (bMultiPartitions)
741 {
742 char sDriveLetter[] = { PATHSEP, (char)('C' + i), '\0' };
743 strcat(emudrives[i]->hd_emulation_dir, sDriveLetter);
744 }
745 /* drive number (C: = 2, D: = 3, etc.) */
746 DriveNumber = 2 + i;
747
748 // Check host file system to see if the drive folder for THIS
749 // drive letter/number exists...
750 if (GEMDOS_DoesHostDriveFolderExist(emudrives[i]->hd_emulation_dir, DriveNumber))
751 {
752 /* map drive */
753 Log_Printf(LOG_INFO, "GEMDOS HDD emulation, %c: <-> %s.\n",
754 'A'+DriveNumber, emudrives[i]->hd_emulation_dir);
755 emudrives[i]->drive_number = DriveNumber;
756 nNumDrives = i + 3;
757
758 /* This letter may already be allocated to the one supported physical disk images
759 * (depends on how well Atari HD driver and Hatari interpretation of partition
760 * table(s) match each other).
761 */
762 if (i < ImagePartitions)
763 Log_Printf(LOG_WARN, "GEMDOS HD drive %c: (may) override ACSI/IDE image partitions!\n", 'A'+DriveNumber);
764 }
765 else
766 {
767 free(emudrives[i]); // Deallocate Memory (save space)
768 emudrives[i] = NULL;
769 }
770 }
771
772 /* Set current paths in case Atari -> host GEMDOS path mapping
773 * is needed before TOS boots GEMDOS up (at which point they're
774 * also initialized), like happens with autostart INF file
775 * handling.
776 */
777 GemDOS_InitCurPaths();
778 }
779
780
781 /*-----------------------------------------------------------------------*/
782 /**
783 * Un-init GEMDOS drives
784 */
GemDOS_UnInitDrives(void)785 void GemDOS_UnInitDrives(void)
786 {
787 int i;
788
789 GemDOS_Reset(); /* Close all open files on emulated drive */
790
791 if (GEMDOS_EMU_ON)
792 {
793 for(i = 0; i < MAX_HARDDRIVES; i++)
794 {
795 if (emudrives[i])
796 {
797 free(emudrives[i]); /* Release memory */
798 emudrives[i] = NULL;
799 nNumDrives -= 1;
800 }
801 }
802
803 free(emudrives);
804 emudrives = NULL;
805 }
806 }
807
808
809 /*-----------------------------------------------------------------------*/
810 /**
811 * Save file handle info. If handle is used, save valid file modification
812 * timestamp and file position, otherwise dummies.
813 */
save_file_handle_info(FILE_HANDLE * handle)814 static void save_file_handle_info(FILE_HANDLE *handle)
815 {
816 struct stat fstat;
817 time_t mtime;
818 off_t offset;
819
820 MemorySnapShot_Store(&handle->bUsed, sizeof(handle->bUsed));
821 MemorySnapShot_Store(&handle->szMode, sizeof(handle->szMode));
822 MemorySnapShot_Store(&handle->Basepage, sizeof(handle->Basepage));
823 MemorySnapShot_Store(&handle->szActualName, sizeof(handle->szActualName));
824 if (handle->bUsed)
825 {
826 offset = ftello(handle->FileHandle);
827 stat(handle->szActualName, &fstat);
828 mtime = fstat.st_mtime; /* modification time */
829 }
830 else
831 {
832 /* avoid warnings about access to undefined data */
833 offset = 0;
834 stat("/", &fstat);
835 mtime = fstat.st_mtime;
836 }
837 MemorySnapShot_Store(&mtime, sizeof(mtime));
838 MemorySnapShot_Store(&offset, sizeof(offset));
839 }
840
841 /*-----------------------------------------------------------------------*/
842 /**
843 * Restore saved file handle info. If handle is used, open file, validate
844 * that file modification timestamp matches, then seek to saved position.
845 * Restoring order must match one used in save_file_handle_info().
846 */
restore_file_handle_info(int i,FILE_HANDLE * handle)847 static void restore_file_handle_info(int i, FILE_HANDLE *handle)
848 {
849 struct stat fstat;
850 time_t mtime;
851 off_t offset;
852 FILE *fp;
853
854 if (handle->bUsed)
855 fclose(handle->FileHandle);
856
857 /* read all to proceed correctly in snapshot */
858 MemorySnapShot_Store(&handle->bUsed, sizeof(handle->bUsed));
859 MemorySnapShot_Store(&handle->szMode, sizeof(handle->szMode));
860 MemorySnapShot_Store(&handle->Basepage, sizeof(handle->Basepage));
861 MemorySnapShot_Store(&handle->szActualName, sizeof(handle->szActualName));
862 MemorySnapShot_Store(&mtime, sizeof(mtime));
863 MemorySnapShot_Store(&offset, sizeof(offset));
864 handle->FileHandle = NULL;
865
866 if (!handle->bUsed)
867 return;
868
869 if (stat(handle->szActualName, &fstat) != 0)
870 {
871 handle->bUsed = false;
872 Log_Printf(LOG_WARN, "GEMDOS handle %d cannot be restored, file missing: %s\n",
873 i, handle->szActualName);
874 return;
875 }
876 /* assumes time_t is primitive type (unsigned long on Linux) */
877 if (fstat.st_mtime != mtime)
878 {
879 Log_Printf(LOG_WARN, "restored GEMDOS handle %d points to a file that has been modified in meanwhile: %s\n",
880 i, handle->szActualName);
881 }
882 fp = fopen(handle->szActualName, handle->szMode);
883 if (fp == NULL || fseeko(fp, offset, SEEK_SET) != 0)
884 {
885 handle->bUsed = false;
886 Log_Printf(LOG_WARN, "GEMDOS '%s' handle %d cannot be restored, seek to saved offset %"PRId64" failed for: %s\n",
887 handle->szMode, i, offset, handle->szActualName);
888 fclose(fp);
889 return;
890 }
891 handle->FileHandle = fp;
892 }
893
894 /*-----------------------------------------------------------------------*/
895 /**
896 * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
897 */
GemDOS_MemorySnapShot_Capture(bool bSave)898 void GemDOS_MemorySnapShot_Capture(bool bSave)
899 {
900 FILE_HANDLE *finfo;
901 int i, handles = ARRAY_SIZE(FileHandles);
902 bool bEmudrivesAvailable;
903
904 /* Save/Restore the emudrives structure */
905 bEmudrivesAvailable = (emudrives != NULL);
906 MemorySnapShot_Store(&bEmudrivesAvailable, sizeof(bEmudrivesAvailable));
907 if (bEmudrivesAvailable)
908 {
909 if (!emudrives)
910 {
911 /* As memory snapshot contained emulated drive(s),
912 * but currently there are none allocated yet...
913 * let's do it now!
914 */
915 GemDOS_InitDrives();
916 }
917
918 for(i = 0; i < MAX_HARDDRIVES; i++)
919 {
920 int bDummyDrive = false;
921 if (!emudrives[i])
922 {
923 /* Allocate a dummy drive */
924 emudrives[i] = malloc(sizeof(EMULATEDDRIVE));
925 if (!emudrives[i])
926 {
927 perror("GemDOS_MemorySnapShot_Capture");
928 continue;
929 }
930 memset(emudrives[i], 0, sizeof(EMULATEDDRIVE));
931 bDummyDrive = true;
932 }
933 MemorySnapShot_Store(emudrives[i]->hd_emulation_dir,
934 sizeof(emudrives[i]->hd_emulation_dir));
935 MemorySnapShot_Store(emudrives[i]->fs_currpath,
936 sizeof(emudrives[i]->fs_currpath));
937 MemorySnapShot_Store(&emudrives[i]->drive_number,
938 sizeof(emudrives[i]->drive_number));
939 if (bDummyDrive)
940 {
941 free(emudrives[i]);
942 emudrives[i] = NULL;
943 }
944 }
945 }
946
947 /* misc information */
948 MemorySnapShot_Store(&bInitGemDOS,sizeof(bInitGemDOS));
949 MemorySnapShot_Store(&act_pd, sizeof(act_pd));
950 MemorySnapShot_Store(&CurrentDrive, sizeof(CurrentDrive));
951
952 /* File handle related information */
953 MemorySnapShot_Store(&ForcedHandles, sizeof(ForcedHandles));
954 if (bSave)
955 {
956 MemorySnapShot_Store(&handles, sizeof(handles));
957
958 for (finfo = FileHandles, i = 0; i < handles; i++, finfo++)
959 save_file_handle_info(finfo);
960 }
961 else
962 {
963 int saved_handles;
964 MemorySnapShot_Store(&saved_handles, sizeof(saved_handles));
965 assert(saved_handles == handles);
966
967 for (finfo = FileHandles, i = 0; i < handles; i++, finfo++)
968 restore_file_handle_info(i, finfo);
969
970 /* DTA file name cache isn't valid anymore */
971 GemDOS_ClearAllInternalDTAs();
972 }
973 }
974
975
976 /*-----------------------------------------------------------------------*/
977 /**
978 * Return free PC file handle table index, or -1 if error
979 */
GemDOS_FindFreeFileHandle(void)980 static int GemDOS_FindFreeFileHandle(void)
981 {
982 int i;
983
984 /* Scan our file list for free slot */
985 for(i = 0; i < ARRAY_SIZE(FileHandles); i++)
986 {
987 if (!FileHandles[i].bUsed)
988 return i;
989 }
990
991 /* Cannot open any more files, return error */
992 return -1;
993 }
994
995 /*-----------------------------------------------------------------------*/
996 /**
997 * Check whether given basepage matches current program basepage
998 * or basepage for its parents. If yes, return true, otherwise false.
999 */
GemDOS_BasepageMatches(Uint32 checkbase)1000 static bool GemDOS_BasepageMatches(Uint32 checkbase)
1001 {
1002 int maxparents = 12; /* prevent basepage parent loops */
1003 Uint32 basepage = STMemory_ReadLong(act_pd);
1004 while (maxparents-- > 0 && STMemory_CheckAreaType(basepage, BASEPAGE_SIZE, ABFLAG_RAM))
1005 {
1006 if (basepage == checkbase)
1007 return true;
1008 basepage = STMemory_ReadLong(basepage + BASEPAGE_OFFSET_PARENT);
1009 }
1010 return false;
1011 }
1012
1013 /**
1014 * Check whether TOS handle is within our table range, or aliased,
1015 * return (positive) internal Handle if yes, (negative) -1 for error.
1016 */
GemDOS_GetValidFileHandle(int Handle)1017 static int GemDOS_GetValidFileHandle(int Handle)
1018 {
1019 int Forced = -1;
1020
1021 /* Has handle been aliased with Fforce()? */
1022 if (Handle >= 0 && Handle < ARRAY_SIZE(ForcedHandles)
1023 && ForcedHandles[Handle].Handle != UNFORCED_HANDLE)
1024 {
1025 if (GemDOS_BasepageMatches(ForcedHandles[Handle].Basepage))
1026 {
1027 Forced = Handle;
1028 Handle = ForcedHandles[Handle].Handle;
1029 }
1030 else
1031 {
1032 Log_Printf(LOG_WARN, "Removing (stale?) %d->%d file handle redirection.",
1033 Handle, ForcedHandles[Handle].Handle);
1034 GemDOS_UnforceFileHandle(Handle);
1035 return -1;
1036 }
1037 }
1038 else
1039 {
1040 Handle -= BASE_FILEHANDLE;
1041 }
1042 /* handle is valid for current program and in our handle table? */
1043 if (Handle >= 0 && Handle < ARRAY_SIZE(FileHandles)
1044 && FileHandles[Handle].bUsed)
1045 {
1046 Uint32 current = STMemory_ReadLong(act_pd);
1047 if (FileHandles[Handle].Basepage == current || Forced >= 0)
1048 return Handle;
1049 /* bug in Atari program or in Hatari GEMDOS emu */
1050 Log_Printf(LOG_WARN, "PREVENTED: program 0x%x accessing program 0x%x file handle %d.",
1051 current, FileHandles[Handle].Basepage, Handle);
1052 }
1053 /* invalid handle */
1054 return -1;
1055 }
1056
1057 /*-----------------------------------------------------------------------*/
1058 /**
1059 * Find drive letter from a filename, eg C,D... and return as drive ID(C:2, D:3...)
1060 * returns the current drive number if no drive is specified. For special
1061 * devices (CON:, AUX:, PRN:), returns an invalid drive number.
1062 */
GemDOS_FindDriveNumber(char * pszFileName)1063 static int GemDOS_FindDriveNumber(char *pszFileName)
1064 {
1065 /* Does have 'A:' or 'C:' etc.. at start of string? */
1066 if (pszFileName[0] != '\0' && pszFileName[1] == ':')
1067 {
1068 char letter = toupper((unsigned char)pszFileName[0]);
1069 if (letter >= 'A' && letter <= 'Z')
1070 return (letter-'A');
1071 }
1072 else if (strlen(pszFileName) == 4 && pszFileName[3] == ':')
1073 {
1074 /* ':' can be used only as drive indicator, not otherwise,
1075 * so no need to check even special device name.
1076 */
1077 return 0;
1078 }
1079 return CurrentDrive;
1080 }
1081
1082
1083 /**
1084 * Return true if drive ID (C:2, D:3 etc...) matches emulated hard-drive
1085 */
GemDOS_IsDriveEmulated(int drive)1086 bool GemDOS_IsDriveEmulated(int drive)
1087 {
1088 drive -= 2;
1089 if (drive < 0 || drive >= MAX_HARDDRIVES)
1090 return false;
1091 if (!(emudrives && emudrives[drive]))
1092 return false;
1093 assert(emudrives[drive]->drive_number == drive+2);
1094 return true;
1095 }
1096
1097 /*-----------------------------------------------------------------------*/
1098 /**
1099 * Return drive ID(C:2, D:3 etc...) or -1 if not one of our emulation hard-drives
1100 */
GemDOS_FileName2HardDriveID(char * pszFileName)1101 static int GemDOS_FileName2HardDriveID(char *pszFileName)
1102 {
1103 /* Do we even have a hard-drive? */
1104 if (GEMDOS_EMU_ON)
1105 {
1106 int DriveNumber;
1107
1108 /* Find drive letter (as number) */
1109 DriveNumber = GemDOS_FindDriveNumber(pszFileName);
1110 if (GemDOS_IsDriveEmulated(DriveNumber))
1111 return DriveNumber;
1112 }
1113
1114 /* Not a high-level redirected drive, let TOS handle it */
1115 return -1;
1116 }
1117
1118
1119 /*-----------------------------------------------------------------------*/
1120 /**
1121 * Check whether a file in given path matches given case-insensitive pattern.
1122 * Return first matched name which caller needs to free, or NULL for no match.
1123 */
match_host_dir_entry(const char * path,const char * name,bool pattern)1124 static char* match_host_dir_entry(const char *path, const char *name, bool pattern)
1125 {
1126 #define MAX_UTF8_NAME_LEN (3*(8+1+3)+1) /* UTF-8 can have up to 3 bytes per character */
1127 struct dirent *entry;
1128 char *match = NULL;
1129 DIR *dir;
1130 char nameHost[MAX_UTF8_NAME_LEN];
1131
1132 Str_AtariToHost(name, nameHost, MAX_UTF8_NAME_LEN, INVALID_CHAR);
1133 name = nameHost;
1134
1135 dir = opendir(path);
1136 if (!dir)
1137 return NULL;
1138
1139 #if DEBUG_PATTERN_MATCH
1140 fprintf(stderr, "DEBUG: GEMDOS match '%s'%s in '%s'", name, pattern?" (pattern)":"", path);
1141 #endif
1142 if (pattern)
1143 {
1144 while ((entry = readdir(dir)))
1145 {
1146 Str_DecomposedToPrecomposedUtf8(entry->d_name, entry->d_name); /* for OSX */
1147 if (fsfirst_match(name, entry->d_name))
1148 {
1149 match = strdup(entry->d_name);
1150 break;
1151 }
1152 }
1153 }
1154 else
1155 {
1156 while ((entry = readdir(dir)))
1157 {
1158 Str_DecomposedToPrecomposedUtf8(entry->d_name, entry->d_name); /* for OSX */
1159 if (strcasecmp(name, entry->d_name) == 0)
1160 {
1161 match = strdup(entry->d_name);
1162 break;
1163 }
1164 }
1165 }
1166 closedir(dir);
1167 #if DEBUG_PATTERN_MATCH
1168 fprintf(stderr, "-> '%s'\n", match);
1169 #endif
1170 return match;
1171 }
1172
1173
to_same(int ch)1174 static int to_same(int ch)
1175 {
1176 return ch;
1177 }
1178
1179 /**
1180 * Clip given file name to 8+3 length like TOS does,
1181 * return resulting name length.
1182 */
clip_to_83(char * name)1183 static int clip_to_83(char *name)
1184 {
1185 int diff, len;
1186 char *dot;
1187
1188 dot = strchr(name, '.');
1189 if (dot) {
1190 diff = strlen(dot) - 4;
1191 if (diff > 0)
1192 {
1193 Log_Printf(LOG_WARN, "have to clip %d chars from '%s' extension!\n", diff, name);
1194 dot[4] = '\0';
1195 }
1196 diff = dot - name - 8;
1197 if (diff > 0)
1198 {
1199 Log_Printf(LOG_WARN, "have to clip %d chars from '%s' base!\n", diff, name);
1200 memmove(name + 8, dot, strlen(dot) + 1);
1201 }
1202 return strlen(name);
1203 }
1204 len = strlen(name);
1205 if (len > 8)
1206 {
1207 Log_Printf(LOG_WARN, "have to clip %d chars from '%s'!\n", len - 8, name);
1208 name[8] = '\0';
1209 len = 8;
1210 }
1211 return len;
1212 }
1213
1214 /*-----------------------------------------------------------------------*/
1215 /**
1216 * Check whether given TOS file/dir exists in given host path.
1217 * If it does, add the matched host filename to the given path,
1218 * otherwise add the given filename as is to it. Guarantees
1219 * that the resulting string doesn't exceed maxlen+1.
1220 *
1221 * Return true if match found, false otherwise.
1222 */
add_path_component(char * path,int maxlen,const char * origname,bool is_dir)1223 static bool add_path_component(char *path, int maxlen, const char *origname, bool is_dir)
1224 {
1225 char *tmp, *match;
1226 int dot, namelen, pathlen;
1227 int (*chr_conv)(int);
1228 bool modified;
1229 char *name = alloca(strlen(origname) + 3);
1230
1231 /* append separator */
1232 pathlen = strlen(path);
1233 if (pathlen >= maxlen)
1234 return false;
1235 path[pathlen++] = PATHSEP;
1236 path[pathlen] = '\0';
1237
1238 /* TOS clips names to 8+3 length */
1239 strcpy(name, origname);
1240 namelen = clip_to_83(name);
1241
1242 /* first try exact (case insensitive) match */
1243 match = match_host_dir_entry(path, name, false);
1244 if (match)
1245 {
1246 /* use strncat so that string is always nul terminated */
1247 strncat(path+pathlen, match, maxlen-pathlen);
1248 free(match);
1249 return true;
1250 }
1251
1252 /* Here comes a work-around for a bug in the file selector
1253 * of TOS 1.02: When a folder name has exactly 8 characters,
1254 * it appends a '.' at the end of the name...
1255 */
1256 if (is_dir && namelen == 9 && name[8] == '.')
1257 {
1258 name[8] = '\0';
1259 match = match_host_dir_entry(path, name, false);
1260 if (match)
1261 {
1262 strncat(path+pathlen, match, maxlen-pathlen);
1263 free(match);
1264 return true;
1265 }
1266 }
1267
1268 /* Assume there were invalid characters or that the host file
1269 * was too long to fit into GEMDOS 8+3 filename limits.
1270 * If that's the case, modify the name to a pattern that
1271 * will match such host files and try again.
1272 */
1273 modified = false;
1274
1275 /* catch potentially invalid characters */
1276 for (tmp = name; *tmp; tmp++)
1277 {
1278 if (*tmp == INVALID_CHAR)
1279 {
1280 *tmp = '?';
1281 modified = true;
1282 }
1283 }
1284
1285 /* catch potentially too long extension */
1286 for (dot = 0; name[dot] && name[dot] != '.'; dot++);
1287 if (namelen - dot > 3)
1288 {
1289 dot++;
1290 /* "emulated.too" -> "emulated.too*" */
1291 name[namelen++] = '*';
1292 name[namelen] = '\0';
1293 modified = true;
1294 }
1295 /* catch potentially too long part before extension */
1296 if (namelen > 8 && name[8] == '.')
1297 {
1298 dot++;
1299 /* "emulated.too*" -> "emulated*.too*" */
1300 memmove(name+9, name+8, namelen-7);
1301 namelen++;
1302 name[8] = '*';
1303 modified = true;
1304 }
1305 /* catch potentially too long part without extension */
1306 else if (namelen == 8 && !name[dot])
1307 {
1308 /* "emulated" -> "emulated*" */
1309 name[8] = '*';
1310 name[9] = '\0';
1311 namelen++;
1312 modified = true;
1313 }
1314
1315 if (modified)
1316 {
1317 match = match_host_dir_entry(path, name, true);
1318 if (match)
1319 {
1320 strncat(path+pathlen, match, maxlen-pathlen);
1321 free(match);
1322 return true;
1323 }
1324 }
1325
1326 /* not found, copy file/dirname as is */
1327 switch (ConfigureParams.HardDisk.nGemdosCase) {
1328 case GEMDOS_UPPER:
1329 chr_conv = toupper;
1330 break;
1331 case GEMDOS_LOWER:
1332 chr_conv = tolower;
1333 break;
1334 default:
1335 chr_conv = to_same;
1336 }
1337 tmp = name;
1338 while (*origname)
1339 *tmp++ = chr_conv(*origname++);
1340 *tmp = '\0';
1341 /* strncat(path+pathlen, name, maxlen-pathlen); */
1342 Str_AtariToHost(name, path+pathlen, maxlen-pathlen, INVALID_CHAR);
1343 return false;
1344 }
1345
1346
1347 /**
1348 * Join remaining path without matching. This helper is used after host
1349 * file name matching fails, to append the failing part of the TOS path
1350 * to the host path, so that it won't be a valid host path.
1351 *
1352 * Specifically, the path separators need to be converted, otherwise things
1353 * like Fcreate() could create files that have TOS directory names as part
1354 * of file names on Unix (as \ is valid filename char on Unix). Fcreate()
1355 * needs to create them only when just the file name isn't found, but all
1356 * the directory components have.
1357 */
add_remaining_path(const char * src,char * dstpath,int dstlen)1358 static void add_remaining_path(const char *src, char *dstpath, int dstlen)
1359 {
1360 char *dst;
1361 int i = strlen(dstpath);
1362
1363 Str_AtariToHost(src, dstpath+i, dstlen-i, INVALID_CHAR);
1364
1365 for (dst = dstpath + i; *dst; dst++)
1366 if (*dst == '\\')
1367 *dst = PATHSEP;
1368 }
1369
1370
1371 /*-----------------------------------------------------------------------*/
1372 /**
1373 * Use hard-drive directory, current ST directory and filename
1374 * to create correct path to host file system. If given filename
1375 * isn't found on host file system, just append GEMDOS filename
1376 * to the path as is.
1377 *
1378 * TODO: currently there are many callers which give this dest buffer of
1379 * MAX_GEMDOS_PATH size i.e. don't take into account that host filenames
1380 * can be up to FILENAME_MAX long. Plain GEMDOS paths themselves may be
1381 * MAX_GEMDOS_PATH long even before host dir is prepended to it!
1382 * Way forward: allocate the host path here as FILENAME_MAX so that
1383 * it's always long enough and let callers free it. Assert if alloc
1384 * fails so that callers' don't need to.
1385 */
GemDOS_CreateHardDriveFileName(int Drive,const char * pszFileName,char * pszDestName,int nDestNameLen)1386 void GemDOS_CreateHardDriveFileName(int Drive, const char *pszFileName,
1387 char *pszDestName, int nDestNameLen)
1388 {
1389 const char *s, *filename = pszFileName;
1390 int minlen;
1391
1392 /* make sure that more convenient strncat() can be used on the
1393 * destination string (it always null terminates unlike strncpy()) */
1394 *pszDestName = 0;
1395
1396 /* Is it a valid hard drive? */
1397 assert(GemDOS_IsDriveEmulated(Drive));
1398
1399 /* Check for valid string */
1400 if (filename[0] == '\0')
1401 return;
1402
1403 /* strcat writes n+1 chars, so decrease len */
1404 nDestNameLen--;
1405
1406 /* full filename with drive "C:\foo\bar" */
1407 if (filename[1] == ':')
1408 {
1409 strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen);
1410 filename += 2;
1411 }
1412 /* filename referenced from root: "\foo\bar" */
1413 else if (filename[0] == '\\')
1414 {
1415 strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen);
1416 }
1417 /* filename relative to current directory */
1418 else
1419 {
1420 strncat(pszDestName, emudrives[Drive-2]->fs_currpath, nDestNameLen);
1421 }
1422
1423 minlen = strlen(emudrives[Drive-2]->hd_emulation_dir);
1424 /* this doesn't take into account possible long host filenames
1425 * that will make dest name longer than pszFileName 8.3 paths,
1426 * or GEMDOS paths using "../" which make it smaller. Both
1427 * should(?) be rare in paths, so this info to user should be
1428 * good enough.
1429 */
1430 if (nDestNameLen < minlen + (int)strlen(pszFileName) + 2)
1431 {
1432 Log_AlertDlg(LOG_ERROR, "Appending GEMDOS path '%s' to HDD emu host root dir doesn't fit to %d chars (current Hatari limit)!",
1433 pszFileName, nDestNameLen);
1434 add_remaining_path(filename, pszDestName, nDestNameLen);
1435 return;
1436 }
1437
1438 /* "../" handling breaks if there are extra slashes */
1439 File_CleanFileName(pszDestName);
1440
1441 /* go through path directory components, advancing 'filename'
1442 * pointer while parsing them.
1443 */
1444 for (;;)
1445 {
1446 /* skip extra path separators */
1447 while (*filename == '\\')
1448 filename++;
1449
1450 // fprintf(stderr, "filename: '%s', path: '%s'\n", filename, pszDestName);
1451
1452 /* skip "." references to current directory */
1453 if (filename[0] == '.' &&
1454 (filename[1] == '\\' || !filename[1]))
1455 {
1456 filename++;
1457 continue;
1458 }
1459
1460 /* ".." path component -> strip last dir from dest path */
1461 if (filename[0] == '.' &&
1462 filename[1] == '.' &&
1463 (filename[2] == '\\' || !filename[2]))
1464 {
1465 char *sep = strrchr(pszDestName, PATHSEP);
1466 if (sep)
1467 {
1468 if (sep - pszDestName < minlen)
1469 Log_Printf(LOG_WARN, "GEMDOS path '%s' tried to back out of GEMDOS drive!\n", pszFileName);
1470 else
1471 *sep = '\0';
1472 }
1473 filename += 2;
1474 continue;
1475 }
1476
1477 /* handle directory component */
1478 if ((s = strchr(filename, '\\')))
1479 {
1480 int dirlen = s - filename;
1481 char *dirname = alloca(dirlen + 1);
1482 /* copy dirname */
1483 strncpy(dirname, filename, dirlen);
1484 dirname[dirlen] = '\0';
1485 /* and advance filename */
1486 filename = s;
1487
1488 if (strchr(dirname, '?') || strchr(dirname, '*'))
1489 Log_Printf(LOG_WARN, "GEMDOS dir name '%s' with wildcards in %s!\n", dirname, pszFileName);
1490
1491 /* convert and append dirname to host path */
1492 if (!add_path_component(pszDestName, nDestNameLen, dirname, true))
1493 {
1494 Log_Printf(LOG_WARN, "No GEMDOS dir '%s'\n", pszDestName);
1495 add_remaining_path(filename, pszDestName, nDestNameLen);
1496 return;
1497 }
1498 continue;
1499 }
1500
1501 /* path directory components done */
1502 break;
1503 }
1504
1505 if (*filename)
1506 {
1507 /* a wildcard instead of a complete file name? */
1508 if (strchr(filename,'?') || strchr(filename,'*'))
1509 {
1510 int len = strlen(pszDestName);
1511 if (len < nDestNameLen)
1512 {
1513 pszDestName[len++] = PATHSEP;
1514 pszDestName[len] = '\0';
1515 }
1516 /* use strncat so that string is always nul terminated */
1517 /* strncat(pszDestName+len, filename, nDestNameLen-len); */
1518 Str_AtariToHost(filename, pszDestName+len, nDestNameLen-len, INVALID_CHAR);
1519 }
1520 else if (!add_path_component(pszDestName, nDestNameLen, filename, false))
1521 {
1522 /* It's often normal, that GEM uses this to test for
1523 * existence of desktop.inf or newdesk.inf for example.
1524 */
1525 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS didn't find filename %s\n", pszDestName);
1526 return;
1527 }
1528 }
1529 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS: %s -> host: %s\n", pszFileName, pszDestName);
1530 }
1531
1532
1533 /**
1534 * GEMDOS Cconws
1535 * Call 0x9
1536 */
GemDOS_Cconws(Uint32 Params)1537 static bool GemDOS_Cconws(Uint32 Params)
1538 {
1539 Uint32 Addr;
1540 char *pBuffer;
1541
1542 Addr = STMemory_ReadLong(Params);
1543
1544 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x9 Cconws(0x%X) at PC 0x%X\n",
1545 Addr, CallingPC);
1546
1547 /* We only intercept this call in non-TOS mode */
1548 if (bUseTos)
1549 return false;
1550
1551 /* Check that write is from valid memory area */
1552 if ((CallingPC < TosAddress || CallingPC >= TosAddress + TosSize)
1553 && !STMemory_CheckAreaType(Addr, 80 * 25, ABFLAG_RAM))
1554 {
1555 Log_Printf(LOG_WARN, "GEMDOS Cconws() failed due to invalid RAM range at 0x%x\n", Addr);
1556 Regs[REG_D0] = GEMDOS_ERANGE;
1557 return true;
1558 }
1559
1560 pBuffer = (char *)STMemory_STAddrToPointer(Addr);
1561 if (fwrite(pBuffer, strnlen(pBuffer, 80 * 25), 1, stdout) < 1)
1562 Regs[REG_D0] = GEMDOS_ERROR;
1563 else
1564 Regs[REG_D0] = GEMDOS_EOK;
1565
1566 return true;
1567 }
1568
1569 /*-----------------------------------------------------------------------*/
1570 /**
1571 * GEMDOS Set drive (0=A,1=B,2=C etc...)
1572 * Call 0xE
1573 */
GemDOS_SetDrv(Uint32 Params)1574 static bool GemDOS_SetDrv(Uint32 Params)
1575 {
1576 /* Read details from stack for our own use */
1577 CurrentDrive = STMemory_ReadWord(Params);
1578
1579 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x0E Dsetdrv(0x%x) at PC=0x%X\n", (int)CurrentDrive,
1580 CallingPC);
1581
1582 /* Still re-direct to TOS */
1583 return false;
1584 }
1585
1586
1587 /*-----------------------------------------------------------------------*/
1588 /**
1589 * GEMDOS Dfree Free disk space.
1590 * Call 0x36
1591 */
GemDOS_DFree(Uint32 Params)1592 static bool GemDOS_DFree(Uint32 Params)
1593 {
1594 #ifdef HAVE_STATVFS
1595 struct statvfs buf;
1596 #endif
1597 int Drive, Total, Free;
1598 Uint32 Address;
1599
1600 Address = STMemory_ReadLong(Params);
1601 Drive = STMemory_ReadWord(Params+SIZE_LONG);
1602
1603 /* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */
1604 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x36 Dfree(0x%x, %i) at PC 0x%X\n", Address, Drive,
1605 CallingPC);
1606 if (Drive == 0)
1607 Drive = CurrentDrive;
1608 else
1609 Drive--;
1610
1611 /* is it our drive? */
1612 if (!GemDOS_IsDriveEmulated(Drive))
1613 {
1614 /* no, redirect to TOS */
1615 return false;
1616 }
1617 /* Check that write is requested to valid memory area */
1618 if ( !STMemory_CheckAreaType ( Address, 16, ABFLAG_RAM ) )
1619 {
1620 Log_Printf(LOG_WARN, "GEMDOS Dfree() failed due to invalid RAM range 0x%x+%i\n", Address, 16);
1621 Regs[REG_D0] = GEMDOS_ERANGE;
1622 return true;
1623 }
1624
1625 #ifdef HAVE_STATVFS
1626 if (statvfs(emudrives[Drive-2]->hd_emulation_dir, &buf) == 0)
1627 {
1628 Total = buf.f_blocks/1024 * buf.f_frsize;
1629 if (buf.f_bavail)
1630 Free = buf.f_bavail; /* free for unprivileged user */
1631 else
1632 Free = buf.f_bfree;
1633 Free = Free/1024 * buf.f_bsize;
1634
1635 /* TOS version limits based on:
1636 * http://hddriver.seimet.de/en/faq.html
1637 */
1638 if (TosVersion >= 0x0400)
1639 {
1640 if (Total > 1024*1024)
1641 Total = 1024*1024;
1642 }
1643 else
1644 {
1645 if (TosVersion >= 0x0106)
1646 {
1647 if (Total > 512*1024)
1648 Total = 512*1024;
1649 }
1650 else
1651 {
1652 if (Total > 256*1024)
1653 Total = 256*1024;
1654 }
1655 }
1656 if (Free > Total)
1657 Free = Total;
1658 }
1659 else
1660 #endif
1661 {
1662 /* fake 32MB drive with 16MB free */
1663 Total = 32*1024;
1664 Free = 16*1024;
1665 }
1666 STMemory_WriteLong(Address, Free); /* free clusters */
1667 STMemory_WriteLong(Address+SIZE_LONG, Total); /* total clusters */
1668
1669 STMemory_WriteLong(Address+SIZE_LONG*2, 512); /* bytes per sector */
1670 STMemory_WriteLong(Address+SIZE_LONG*3, 2); /* sectors per cluster (cluster = 1KB) */
1671 Regs[REG_D0] = GEMDOS_EOK;
1672 return true;
1673 }
1674
1675
1676
1677 /*-----------------------------------------------------------------------*/
1678 /**
1679 * Helper to map Unix errno to GEMDOS error value
1680 */
1681 typedef enum {
1682 ERROR_FILE,
1683 ERROR_PATH
1684 } etype_t;
1685
errno2gemdos(const int error,const etype_t etype)1686 static Uint32 errno2gemdos(const int error, const etype_t etype)
1687 {
1688 LOG_TRACE(TRACE_OS_GEMDOS, "-> ERROR (errno = %d)\n", error);
1689 switch (error)
1690 {
1691 case ENOENT:
1692 if (etype == ERROR_FILE)
1693 return GEMDOS_EFILNF;/* File not found */
1694 case ENOTDIR:
1695 return GEMDOS_EPTHNF; /* Path not found */
1696 case ENOTEMPTY:
1697 case EEXIST:
1698 case EPERM:
1699 case EACCES:
1700 case EROFS:
1701 return GEMDOS_EACCDN; /* Access denied */
1702 default:
1703 return GEMDOS_ERROR; /* Misc error */
1704 }
1705 }
1706
1707 /*-----------------------------------------------------------------------*/
1708 /**
1709 * GEMDOS MkDir
1710 * Call 0x39
1711 */
GemDOS_MkDir(Uint32 Params)1712 static bool GemDOS_MkDir(Uint32 Params)
1713 {
1714 char *pDirName, *psDirPath;
1715 int Drive;
1716
1717 /* Find directory to make */
1718 pDirName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1719
1720 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x39 Dcreate(\"%s\") at PC 0x%X\n", pDirName,
1721 CallingPC);
1722
1723 Drive = GemDOS_FileName2HardDriveID(pDirName);
1724
1725 if (!ISHARDDRIVE(Drive))
1726 {
1727 /* redirect to TOS */
1728 return false;
1729 }
1730
1731 /* write protected device? */
1732 if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
1733 {
1734 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Dcreate(\"%s\")\n", pDirName);
1735 Regs[REG_D0] = GEMDOS_EWRPRO;
1736 return true;
1737 }
1738
1739 psDirPath = malloc(FILENAME_MAX);
1740 if (!psDirPath)
1741 {
1742 perror("GemDOS_MkDir");
1743 Regs[REG_D0] = GEMDOS_ENSMEM;
1744 return true;
1745 }
1746
1747 /* Copy old directory, as if calls fails keep this one */
1748 GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX);
1749
1750 /* Attempt to make directory */
1751 if (mkdir(psDirPath, 0755) == 0)
1752 Regs[REG_D0] = GEMDOS_EOK;
1753 else
1754 Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH);
1755 free(psDirPath);
1756 return true;
1757 }
1758
1759 /*-----------------------------------------------------------------------*/
1760 /**
1761 * GEMDOS RmDir
1762 * Call 0x3A
1763 */
GemDOS_RmDir(Uint32 Params)1764 static bool GemDOS_RmDir(Uint32 Params)
1765 {
1766 char *pDirName, *psDirPath;
1767 int Drive;
1768
1769 /* Find directory to make */
1770 pDirName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1771
1772 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3A Ddelete(\"%s\") at PC 0x%X\n", pDirName,
1773 CallingPC);
1774
1775 Drive = GemDOS_FileName2HardDriveID(pDirName);
1776
1777 if (!ISHARDDRIVE(Drive))
1778 {
1779 /* redirect to TOS */
1780 return false;
1781 }
1782
1783 /* write protected device? */
1784 if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
1785 {
1786 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Ddelete(\"%s\")\n", pDirName);
1787 Regs[REG_D0] = GEMDOS_EWRPRO;
1788 return true;
1789 }
1790
1791 psDirPath = malloc(FILENAME_MAX);
1792 if (!psDirPath)
1793 {
1794 perror("GemDOS_RmDir");
1795 Regs[REG_D0] = GEMDOS_ENSMEM;
1796 return true;
1797 }
1798
1799 /* Copy old directory, as if calls fails keep this one */
1800 GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX);
1801
1802 /* Attempt to remove directory */
1803 if (rmdir(psDirPath) == 0)
1804 Regs[REG_D0] = GEMDOS_EOK;
1805 else
1806 Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH);
1807 free(psDirPath);
1808 return true;
1809 }
1810
1811
1812 /*-----------------------------------------------------------------------*/
1813 /**
1814 * GEMDOS ChDir
1815 * Call 0x3B
1816 */
GemDOS_ChDir(Uint32 Params)1817 static bool GemDOS_ChDir(Uint32 Params)
1818 {
1819 char *pDirName, *psTempDirPath;
1820 struct stat buf;
1821 int Drive;
1822
1823 /* Find new directory */
1824 pDirName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1825
1826 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3B Dsetpath(\"%s\") at PC 0x%X\n", pDirName,
1827 CallingPC);
1828
1829 Drive = GemDOS_FileName2HardDriveID(pDirName);
1830
1831 if (!ISHARDDRIVE(Drive))
1832 {
1833 /* redirect to TOS */
1834 return false;
1835 }
1836
1837 /* Allocate temporary memory for path name: */
1838 psTempDirPath = malloc(FILENAME_MAX);
1839 if (!psTempDirPath)
1840 {
1841 perror("GemDOS_ChDir");
1842 Regs[REG_D0] = GEMDOS_ENSMEM;
1843 return true;
1844 }
1845
1846 GemDOS_CreateHardDriveFileName(Drive, pDirName, psTempDirPath, FILENAME_MAX);
1847
1848 /* Remove trailing slashes (stat on Windows does not like that) */
1849 File_CleanFileName(psTempDirPath);
1850
1851 if (stat(psTempDirPath, &buf))
1852 {
1853 /* error */
1854 free(psTempDirPath);
1855 Regs[REG_D0] = GEMDOS_EPTHNF;
1856 return true;
1857 }
1858
1859 File_AddSlashToEndFileName(psTempDirPath);
1860 File_MakeAbsoluteName(psTempDirPath);
1861
1862 /* Prevent '..' commands moving BELOW the root HDD folder */
1863 /* by double checking if path is valid */
1864 if (strncmp(psTempDirPath, emudrives[Drive-2]->hd_emulation_dir,
1865 strlen(emudrives[Drive-2]->hd_emulation_dir)) == 0)
1866 {
1867 strlcpy(emudrives[Drive-2]->fs_currpath, psTempDirPath,
1868 sizeof(emudrives[Drive-2]->fs_currpath));
1869 Regs[REG_D0] = GEMDOS_EOK;
1870 }
1871 else
1872 {
1873 Regs[REG_D0] = GEMDOS_EPTHNF;
1874 }
1875 free(psTempDirPath);
1876
1877 return true;
1878
1879 }
1880
1881
1882 /*-----------------------------------------------------------------------*/
1883 /**
1884 * Helper to check whether given file's path is missing.
1885 * Returns true if missing, false if found.
1886 * Modifies the argument buffer.
1887 */
GemDOS_FilePathMissing(char * szActualFileName)1888 static bool GemDOS_FilePathMissing(char *szActualFileName)
1889 {
1890 char *ptr = strrchr(szActualFileName, PATHSEP);
1891 if (ptr)
1892 {
1893 *ptr = 0; /* Strip filename from string */
1894 if (!File_DirExists(szActualFileName))
1895 return true;
1896 }
1897 return false;
1898 }
1899
1900
1901 /*-----------------------------------------------------------------------*/
redirect_to_TOS(void)1902 static inline bool redirect_to_TOS(void)
1903 {
1904 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> to TOS\n");
1905 return false;
1906 }
1907
1908 /*-----------------------------------------------------------------------*/
1909 /**
1910 * GEMDOS Create file
1911 * Call 0x3C
1912 */
GemDOS_Create(Uint32 Params)1913 static bool GemDOS_Create(Uint32 Params)
1914 {
1915 /* TODO: host filenames might not fit into this */
1916 char szActualFileName[MAX_GEMDOS_PATH];
1917 char *pszFileName;
1918 int Drive,Index, Mode;
1919
1920 /* Find filename */
1921 pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1922 Mode = STMemory_ReadWord(Params+SIZE_LONG);
1923
1924 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE,
1925 "GEMDOS 0x3C Fcreate(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, Mode,
1926 CallingPC);
1927
1928 Drive = GemDOS_FileName2HardDriveID(pszFileName);
1929
1930 if (!ISHARDDRIVE(Drive))
1931 {
1932 /* redirect to TOS */
1933 return redirect_to_TOS();
1934 }
1935
1936 if (Mode == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
1937 {
1938 Log_Printf(LOG_WARN, "Warning: Hatari doesn't support GEMDOS volume"
1939 " label setting\n(for '%s')\n", pszFileName);
1940 Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */
1941 return true;
1942 }
1943
1944 /* write protected device? */
1945 if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
1946 {
1947 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fcreate(\"%s\")\n", pszFileName);
1948 Regs[REG_D0] = GEMDOS_EWRPRO;
1949 return true;
1950 }
1951
1952 /* Now convert to hard drive filename */
1953 GemDOS_CreateHardDriveFileName(Drive, pszFileName,
1954 szActualFileName, sizeof(szActualFileName));
1955
1956 /* Find slot to store file handle, as need to return WORD handle for ST */
1957 Index = GemDOS_FindFreeFileHandle();
1958 if (Index == -1)
1959 {
1960 /* No free handles, return error code */
1961 Regs[REG_D0] = GEMDOS_ENHNDL; /* No more handles */
1962 return true;
1963 }
1964
1965 /* truncate and open for reading & writing */
1966 FileHandles[Index].FileHandle = fopen(szActualFileName, "wb+");
1967
1968 if (FileHandles[Index].FileHandle != NULL)
1969 {
1970 /* FIXME: implement other Mode attributes
1971 * - GEMDOS_FILE_ATTRIB_HIDDEN (FA_HIDDEN)
1972 * - GEMDOS_FILE_ATTRIB_SYSTEM_FILE (FA_SYSTEM)
1973 * - GEMDOS_FILE_ATTRIB_SUBDIRECTORY (FA_DIR)
1974 * - GEMDOS_FILE_ATTRIB_WRITECLOSE (FA_ARCHIVE)
1975 * (set automatically by GemDOS >= 0.15)
1976 */
1977 if (Mode & GEMDOS_FILE_ATTRIB_READONLY)
1978 {
1979 /* after closing, file should be read-only */
1980 if (chmod(szActualFileName, S_IRUSR|S_IRGRP|S_IROTH))
1981 {
1982 perror("Failed to set file to read-only");
1983 }
1984 }
1985 /* Tag handle table entry as used in this process and return handle */
1986 FileHandles[Index].bUsed = true;
1987 strcpy(FileHandles[Index].szMode, "wb+");
1988 FileHandles[Index].Basepage = STMemory_ReadLong(act_pd);
1989 snprintf(FileHandles[Index].szActualName,
1990 sizeof(FileHandles[Index].szActualName),
1991 "%s", szActualFileName);
1992
1993 /* Return valid ST file handle from our range (from BASE_FILEHANDLE upwards) */
1994 Regs[REG_D0] = Index+BASE_FILEHANDLE;
1995 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> FD %d (%s)\n", Regs[REG_D0],
1996 Mode & GEMDOS_FILE_ATTRIB_READONLY ? "read-only":"read/write");
1997 return true;
1998 }
1999 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> ERROR (errno = %d)\n", errno);
2000
2001 /* We failed to create the file, did we have required access rights? */
2002 if (errno == EACCES || errno == EROFS ||
2003 errno == EPERM || errno == EISDIR)
2004 {
2005 Log_Printf(LOG_WARN, "GEMDOS failed to create/truncate '%s'\n",
2006 szActualFileName);
2007 Regs[REG_D0] = GEMDOS_EACCDN;
2008 return true;
2009 }
2010
2011 /* Or was path to file missing? (ST-Zip 2.6 relies on getting
2012 * correct error about that during extraction of ZIP files.)
2013 */
2014 if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName))
2015 {
2016 Regs[REG_D0] = GEMDOS_EPTHNF; /* Path not found */
2017 return true;
2018 }
2019
2020 Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */
2021 return true;
2022 }
2023
2024
2025 /**
2026 * GEMDOS Open file
2027 * Call 0x3D
2028 */
GemDOS_Open(Uint32 Params)2029 static bool GemDOS_Open(Uint32 Params)
2030 {
2031 /* TODO: host filenames might not fit into this */
2032 char szActualFileName[MAX_GEMDOS_PATH];
2033 char *pszFileName;
2034 const char *ModeStr, *RealMode;
2035 const char *Modes[] = {
2036 "read-only", "write-only", "read/write", "read/write"
2037 };
2038 int Drive, Index, Mode;
2039 FILE *OverrideHandle;
2040 bool bToTos = false;
2041
2042 /* Find filename */
2043 pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2044 Mode = STMemory_ReadWord(Params+SIZE_LONG);
2045 Mode &= 3;
2046
2047 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE,
2048 "GEMDOS 0x3D Fopen(\"%s\", %s) at PC=0x%X\n",
2049 pszFileName, Modes[Mode], CallingPC);
2050
2051 Drive = GemDOS_FileName2HardDriveID(pszFileName);
2052
2053 if (!ISHARDDRIVE(Drive))
2054 {
2055 if (INF_Overriding(AUTOSTART_FOPEN))
2056 bToTos = true;
2057 else
2058 return redirect_to_TOS();
2059 }
2060
2061 /* Find slot to store file handle, as need to return WORD handle for ST */
2062 Index = GemDOS_FindFreeFileHandle();
2063 if (Index == -1)
2064 {
2065 if (bToTos)
2066 return redirect_to_TOS();
2067
2068 /* No free handles, return error code */
2069 Regs[REG_D0] = GEMDOS_ENHNDL; /* No more handles */
2070 return true;
2071 }
2072
2073 if ((OverrideHandle = INF_OpenOverride(pszFileName)))
2074 {
2075 strcpy(szActualFileName, pszFileName);
2076 FileHandles[Index].FileHandle = OverrideHandle;
2077 RealMode = "read-only";
2078 ModeStr = "rb";
2079 }
2080 else
2081 {
2082 struct stat FileStat;
2083 if (bToTos)
2084 return redirect_to_TOS();
2085
2086 /* Convert to hard drive filename */
2087 GemDOS_CreateHardDriveFileName(Drive, pszFileName,
2088 szActualFileName, sizeof(szActualFileName));
2089
2090 /* Fread/Fwrite calls succeed in all TOS versions
2091 * regardless of access rights specified in Fopen().
2092 * Only time when things can fail is when file is
2093 * opened, if file mode doesn't allow given opening
2094 * mode. As there's no write-only file mode, access
2095 * failures happen only when trying to open read-only
2096 * file with (read+)write mode.
2097 *
2098 * Therefore only read-only & read+write modes need
2099 * to be supported (ANSI-C fopen() doesn't even
2100 * support write-only without truncating the file).
2101 *
2102 * Read-only status is used if:
2103 * - requested by Atari program
2104 * - Hatari write protection is enabled
2105 * - File itself is read-only
2106 * Latter is done to help cases where application
2107 * needlessly requests write access, but file is
2108 * on read-only media (like CD/DVD).
2109 */
2110 if (Mode == 0 ||
2111 ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON ||
2112 (stat(szActualFileName, &FileStat) == 0 && !(FileStat.st_mode & S_IWUSR)))
2113 {
2114 ModeStr = "rb";
2115 RealMode = "read-only";
2116 }
2117 else
2118 {
2119 ModeStr = "rb+";
2120 RealMode = "read+write";
2121 }
2122 FileHandles[Index].FileHandle = fopen(szActualFileName, ModeStr);
2123 }
2124
2125 if (FileHandles[Index].FileHandle != NULL)
2126 {
2127 /* Tag handle table entry as used in this process and return handle */
2128 FileHandles[Index].bUsed = true;
2129 strcpy(FileHandles[Index].szMode, ModeStr);
2130 FileHandles[Index].Basepage = STMemory_ReadLong(act_pd);
2131 snprintf(FileHandles[Index].szActualName,
2132 sizeof(FileHandles[Index].szActualName),
2133 "%s", szActualFileName);
2134
2135 GemDOS_UpdateCurrentProgram(Index);
2136
2137 /* Return valid ST file handle from our range (BASE_FILEHANDLE upwards) */
2138 Regs[REG_D0] = Index+BASE_FILEHANDLE;
2139 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> FD %d (%s -> %s)\n",
2140 Regs[REG_D0], Modes[Mode], RealMode);
2141 return true;
2142 }
2143
2144 if (errno == EACCES || errno == EROFS ||
2145 errno == EPERM || errno == EISDIR)
2146 {
2147 Log_Printf(LOG_WARN, "GEMDOS missing %s permission to file '%s'\n",
2148 Modes[Mode], szActualFileName);
2149 Regs[REG_D0] = GEMDOS_EACCDN;
2150 }
2151 else if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName))
2152 {
2153 /* Path not found */
2154 Regs[REG_D0] = GEMDOS_EPTHNF;
2155 }
2156 else
2157 {
2158 /* File not found / error opening */
2159 Regs[REG_D0] = GEMDOS_EFILNF;
2160 }
2161 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> ERROR %d (errno = %d)\n", Regs[REG_D0], errno);
2162 return true;
2163 }
2164
2165 /*-----------------------------------------------------------------------*/
2166 /**
2167 * GEMDOS Close file
2168 * Call 0x3E
2169 */
GemDOS_Close(Uint32 Params)2170 static bool GemDOS_Close(Uint32 Params)
2171 {
2172 int i, Handle;
2173
2174 /* Find our handle - may belong to TOS */
2175 Handle = STMemory_ReadWord(Params);
2176
2177 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE,
2178 "GEMDOS 0x3E Fclose(%i) at PC 0x%X\n",
2179 Handle, CallingPC);
2180
2181 /* Get internal handle */
2182 if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
2183 {
2184 /* no, assume it was TOS one -> redirect */
2185 return false;
2186 }
2187
2188 /* Close file and free up handle table */
2189 if (INF_CloseOverride(FileHandles[Handle].FileHandle))
2190 {
2191 FileHandles[Handle].bUsed = false;
2192 }
2193 GemDOS_CloseFileHandle(Handle);
2194
2195 /* unalias handle */
2196 for (i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
2197 {
2198 if (ForcedHandles[i].Handle == Handle)
2199 GemDOS_UnforceFileHandle(i);
2200 }
2201 /* Return no error */
2202 Regs[REG_D0] = GEMDOS_EOK;
2203 return true;
2204 }
2205
2206 /*-----------------------------------------------------------------------*/
2207 /**
2208 * GEMDOS Read file
2209 * Call 0x3F
2210 */
GemDOS_Read(Uint32 Params)2211 static bool GemDOS_Read(Uint32 Params)
2212 {
2213 char *pBuffer;
2214 off_t CurrentPos, FileSize;
2215 long nBytesRead, nBytesLeft;
2216 Uint32 Addr;
2217 Uint32 Size;
2218 int Handle;
2219
2220 /* Read details from stack */
2221 Handle = STMemory_ReadWord(Params);
2222 Size = STMemory_ReadLong(Params+SIZE_WORD);
2223 Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
2224
2225 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3F Fread(%i, %i, 0x%x) at PC 0x%X\n",
2226 Handle, Size, Addr,
2227 CallingPC);
2228
2229 /* Get internal handle */
2230 if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
2231 {
2232 /* assume it was TOS one -> redirect */
2233 return false;
2234 }
2235
2236 /* Old TOS versions treat the Size parameter as signed */
2237 if (TosVersion < 0x400 && (Size & 0x80000000))
2238 {
2239 /* return -1 as original GEMDOS */
2240 Regs[REG_D0] = -1;
2241 return true;
2242 }
2243
2244 /* To quick check to see where our file pointer is and how large the file is */
2245 CurrentPos = ftello(FileHandles[Handle].FileHandle);
2246 if (CurrentPos == -1L
2247 || fseeko(FileHandles[Handle].FileHandle, 0, SEEK_END) != 0)
2248 {
2249 Regs[REG_D0] = GEMDOS_E_SEEK;
2250 return true;
2251 }
2252 FileSize = ftello(FileHandles[Handle].FileHandle);
2253 if (FileSize == -1L
2254 || fseeko(FileHandles[Handle].FileHandle, CurrentPos, SEEK_SET) != 0)
2255 {
2256 Regs[REG_D0] = GEMDOS_E_SEEK;
2257 return true;
2258 }
2259
2260 nBytesLeft = FileSize-CurrentPos;
2261
2262 /* Check for bad size and End Of File */
2263 if (Size <= 0 || nBytesLeft <= 0)
2264 {
2265 /* return zero (bytes read) as original GEMDOS/EmuTOS */
2266 Regs[REG_D0] = 0;
2267 return true;
2268 }
2269
2270 /* Limit to size of file to prevent errors */
2271 if (Size > (Uint32)nBytesLeft)
2272 Size = nBytesLeft;
2273
2274 /* Check that read is to valid memory area */
2275 if ( !STMemory_CheckAreaType ( Addr, Size, ABFLAG_RAM ) )
2276 {
2277 Log_Printf(LOG_WARN, "GEMDOS Fread() failed due to invalid RAM range 0x%x+%i\n", Addr, Size);
2278 Regs[REG_D0] = GEMDOS_ERANGE;
2279 return true;
2280 }
2281
2282 /* Atari memory modified directly with fread() -> flush the instr/data caches */
2283 M68000_Flush_All_Caches(Addr, Size);
2284
2285 /* And read data in */
2286 pBuffer = (char *)STMemory_STAddrToPointer(Addr);
2287 nBytesRead = fread(pBuffer, 1, Size, FileHandles[Handle].FileHandle);
2288
2289 if (ferror(FileHandles[Handle].FileHandle))
2290 {
2291 Log_Printf(LOG_WARN, "GEMDOS failed to read from '%s': %s\n",
2292 FileHandles[Handle].szActualName, strerror(errno));
2293 Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
2294 } else
2295 /* Return number of bytes read */
2296 Regs[REG_D0] = nBytesRead;
2297
2298 return true;
2299 }
2300
2301 /*-----------------------------------------------------------------------*/
2302 /**
2303 * GEMDOS Write file
2304 * Call 0x40
2305 */
GemDOS_Write(Uint32 Params)2306 static bool GemDOS_Write(Uint32 Params)
2307 {
2308 char *pBuffer;
2309 long nBytesWritten;
2310 Uint32 Addr;
2311 Sint32 Size;
2312 int Handle, fh_idx;
2313 FILE *fp;
2314
2315 /* Read details from stack */
2316 Handle = STMemory_ReadWord(Params);
2317 Size = STMemory_ReadLong(Params+SIZE_WORD);
2318 Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
2319
2320 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x40 Fwrite(%i, %i, 0x%x) at PC 0x%X\n",
2321 Handle, Size, Addr,
2322 CallingPC);
2323
2324 /* Get internal handle */
2325 fh_idx = GemDOS_GetValidFileHandle(Handle);
2326 if (fh_idx >= 0)
2327 {
2328 /* write protected device? */
2329 if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
2330 {
2331 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fwrite(%d,...)\n", Handle);
2332 Regs[REG_D0] = GEMDOS_EWRPRO;
2333 return true;
2334 }
2335 fp = FileHandles[fh_idx].FileHandle;
2336 }
2337 else
2338 {
2339 if (!bUseTos && Handle == 1)
2340 fp = stdout;
2341 else if (!bUseTos && (Handle == 2 || Handle == -1))
2342 fp = stderr;
2343 else
2344 return false; /* assume it was TOS one -> redirect */
2345 }
2346
2347 /* Check that write is from valid memory area */
2348 if (!STMemory_CheckAreaType(Addr, Size, ABFLAG_RAM | ABFLAG_ROM))
2349 {
2350 Log_Printf(LOG_WARN, "GEMDOS Fwrite() failed due to invalid RAM range 0x%x+%i\n", Addr, Size);
2351 Regs[REG_D0] = GEMDOS_ERANGE;
2352 return true;
2353 }
2354
2355 pBuffer = (char *)STMemory_STAddrToPointer(Addr);
2356 nBytesWritten = fwrite(pBuffer, 1, Size, fp);
2357 if (fh_idx >= 0 && ferror(fp))
2358 {
2359 Log_Printf(LOG_WARN, "GEMDOS failed to write to '%s'\n",
2360 FileHandles[fh_idx].szActualName);
2361 Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
2362 }
2363 else
2364 {
2365 fflush(fp);
2366 Regs[REG_D0] = nBytesWritten; /* OK */
2367 }
2368 return true;
2369 }
2370
2371
2372 /*-----------------------------------------------------------------------*/
2373 /**
2374 * GEMDOS Delete file
2375 * Call 0x41
2376 */
GemDOS_FDelete(Uint32 Params)2377 static bool GemDOS_FDelete(Uint32 Params)
2378 {
2379 char *pszFileName, *psActualFileName;
2380 int Drive;
2381
2382 /* Find filename */
2383 pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2384
2385 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x41 Fdelete(\"%s\") at PC 0x%X\n", pszFileName,
2386 CallingPC);
2387
2388 Drive = GemDOS_FileName2HardDriveID(pszFileName);
2389
2390 if (!ISHARDDRIVE(Drive))
2391 {
2392 /* redirect to TOS */
2393 return false;
2394 }
2395
2396 /* write protected device? */
2397 if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
2398 {
2399 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdelete(\"%s\")\n", pszFileName);
2400 Regs[REG_D0] = GEMDOS_EWRPRO;
2401 return true;
2402 }
2403
2404 psActualFileName = malloc(FILENAME_MAX);
2405 if (!psActualFileName)
2406 {
2407 perror("GemDOS_FDelete");
2408 Regs[REG_D0] = GEMDOS_ENSMEM;
2409 return true;
2410 }
2411
2412 /* And convert to hard drive filename */
2413 GemDOS_CreateHardDriveFileName(Drive, pszFileName, psActualFileName, FILENAME_MAX);
2414
2415 /* Now delete file?? */
2416 if (unlink(psActualFileName) == 0)
2417 Regs[REG_D0] = GEMDOS_EOK; /* OK */
2418 else
2419 Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
2420
2421 free(psActualFileName);
2422 return true;
2423 }
2424
2425
2426 /*-----------------------------------------------------------------------*/
2427 /**
2428 * GEMDOS File seek
2429 * Call 0x42
2430 */
GemDOS_LSeek(Uint32 Params)2431 static bool GemDOS_LSeek(Uint32 Params)
2432 {
2433 long Offset;
2434 int Handle, Mode;
2435 long nFileSize;
2436 long nOldPos, nDestPos;
2437 FILE *fhndl;
2438
2439 /* Read details from stack */
2440 Offset = (Sint32)STMemory_ReadLong(Params);
2441 Handle = STMemory_ReadWord(Params+SIZE_LONG);
2442 Mode = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);
2443
2444 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x42 Fseek(%li, %i, %i) at PC 0x%X\n", Offset, Handle, Mode,
2445 CallingPC);
2446
2447 /* get internal handle */
2448 if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
2449 {
2450 /* assume it was TOS one -> redirect */
2451 return false;
2452 }
2453
2454 fhndl = FileHandles[Handle].FileHandle;
2455
2456 /* Save old position in file */
2457 nOldPos = ftell(fhndl);
2458
2459 /* Determine the size of the file */
2460 if (fseek(fhndl, 0L, SEEK_END) != 0 || nOldPos < 0)
2461 {
2462 Regs[REG_D0] = GEMDOS_E_SEEK;
2463 return true;
2464 }
2465 nFileSize = ftell(fhndl);
2466
2467 switch (Mode)
2468 {
2469 case 0: nDestPos = Offset; break; /* positive offset */
2470 case 1: nDestPos = nOldPos + Offset; break;
2471 case 2: nDestPos = nFileSize + Offset; break; /* negative offset */
2472 default: nDestPos = -1;
2473 }
2474
2475 if (nDestPos < 0 || nDestPos > nFileSize)
2476 {
2477 /* Restore old position and return error */
2478 if (fseek(fhndl, nOldPos, SEEK_SET) != 0)
2479 perror("GemDOS_LSeek");
2480 Regs[REG_D0] = GEMDOS_ERANGE;
2481 return true;
2482 }
2483
2484 /* Seek to new position and return offset from start of file */
2485 if (fseek(fhndl, nDestPos, SEEK_SET) != 0)
2486 perror("GemDOS_LSeek");
2487 Regs[REG_D0] = ftell(fhndl);
2488
2489 return true;
2490 }
2491
2492
2493 /*-----------------------------------------------------------------------*/
2494 /**
2495 * GEMDOS Fattrib() - get or set file and directory attributes
2496 * Call 0x43
2497 */
GemDOS_Fattrib(Uint32 Params)2498 static bool GemDOS_Fattrib(Uint32 Params)
2499 {
2500 /* TODO: host filenames might not fit into this */
2501 char sActualFileName[MAX_GEMDOS_PATH];
2502 char *psFileName;
2503 int nDrive;
2504 int nRwFlag, nAttrib;
2505 struct stat FileStat;
2506
2507 /* Find filename */
2508 psFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2509 nDrive = GemDOS_FileName2HardDriveID(psFileName);
2510
2511 nRwFlag = STMemory_ReadWord(Params+SIZE_LONG);
2512 nAttrib = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);
2513
2514 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x43 Fattrib(\"%s\", %d, 0x%x) at PC 0x%X\n",
2515 psFileName, nRwFlag, nAttrib,
2516 CallingPC);
2517
2518 if (!ISHARDDRIVE(nDrive))
2519 {
2520 /* redirect to TOS */
2521 return false;
2522 }
2523
2524 /* Convert to hard drive filename */
2525 GemDOS_CreateHardDriveFileName(nDrive, psFileName,
2526 sActualFileName, sizeof(sActualFileName));
2527
2528 if (nAttrib == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
2529 {
2530 Log_Printf(LOG_WARN, "Hatari doesn't support GEMDOS volume label setting\n(for '%s')\n", sActualFileName);
2531 Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */
2532 return true;
2533 }
2534 if (stat(sActualFileName, &FileStat) != 0)
2535 {
2536 Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */
2537 return true;
2538 }
2539 if (nRwFlag == 0)
2540 {
2541 /* Read attributes */
2542 Regs[REG_D0] = GemDOS_ConvertAttribute(FileStat.st_mode);
2543 return true;
2544 }
2545
2546 /* prevent modifying access rights both on write & auto-protected devices */
2547 if (ConfigureParams.HardDisk.nWriteProtection != WRITEPROT_OFF)
2548 {
2549 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fattrib(\"%s\",...)\n", psFileName);
2550 Regs[REG_D0] = GEMDOS_EWRPRO;
2551 return true;
2552 }
2553
2554 if (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY)
2555 {
2556 if (!S_ISDIR(FileStat.st_mode))
2557 {
2558 /* file, not dir -> path not found */
2559 Regs[REG_D0] = GEMDOS_EPTHNF;
2560 return true;
2561 }
2562 }
2563 else
2564 {
2565 if (S_ISDIR(FileStat.st_mode))
2566 {
2567 /* dir, not file -> file not found */
2568 Regs[REG_D0] = GEMDOS_EFILNF;
2569 return true;
2570 }
2571 }
2572
2573 if (nAttrib & GEMDOS_FILE_ATTRIB_READONLY)
2574 {
2575 /* set read-only (readable by all) */
2576 if (chmod(sActualFileName, S_IRUSR|S_IRGRP|S_IROTH) == 0)
2577 {
2578 Regs[REG_D0] = nAttrib;
2579 return true;
2580 }
2581 }
2582 else
2583 {
2584 /* set writable (by user, readable by all) */
2585 if (chmod(sActualFileName, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH) == 0)
2586 {
2587 Regs[REG_D0] = nAttrib;
2588 return true;
2589 }
2590 }
2591
2592 /* FIXME: support hidden/system/archive flags?
2593 * System flag is from DOS, not used by TOS.
2594 * Archive bit is cleared by backup programs
2595 * and set whenever file is written to.
2596 */
2597
2598 Regs[REG_D0] = errno2gemdos(errno, (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY) ? ERROR_PATH : ERROR_FILE);
2599 return true;
2600 }
2601
2602
2603 /*-----------------------------------------------------------------------*/
2604 /**
2605 * GEMDOS Force (file handle aliasing)
2606 * Call 0x46
2607 */
GemDOS_Force(Uint32 Params)2608 static bool GemDOS_Force(Uint32 Params)
2609 {
2610 int std, own;
2611
2612 /* Read details from stack */
2613 std = STMemory_ReadWord(Params);
2614 own = STMemory_ReadWord(Params+SIZE_WORD);
2615
2616 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x46 Fforce(%d, %d) at PC 0x%X\n", std, own,
2617 CallingPC);
2618
2619 /* Get internal handle */
2620 if (std > own)
2621 {
2622 int tmp = std;
2623 std = own;
2624 own = tmp;
2625 }
2626 if ((own = GemDOS_GetValidFileHandle(own)) < 0)
2627 {
2628 /* assume it was TOS one -> let TOS handle it */
2629 return false;
2630 }
2631 if (std < 0 || std >= ARRAY_SIZE(ForcedHandles))
2632 {
2633 Log_Printf(LOG_WARN, "forcing of non-standard %d (> %d) handle ignored.\n", std, ARRAY_SIZE(ForcedHandles));
2634 return false;
2635 }
2636 /* mark given standard handle redirected by this process */
2637 ForcedHandles[std].Basepage = STMemory_ReadLong(act_pd);
2638 ForcedHandles[std].Handle = own;
2639
2640 Regs[REG_D0] = GEMDOS_EOK;
2641 return true;
2642 }
2643
2644
2645 /*-----------------------------------------------------------------------*/
2646 /**
2647 * GEMDOS Get Directory
2648 * Call 0x47
2649 */
GemDOS_GetDir(Uint32 Params)2650 static bool GemDOS_GetDir(Uint32 Params)
2651 {
2652 Uint32 Address;
2653 Uint16 Drive;
2654
2655 Address = STMemory_ReadLong(Params);
2656 Drive = STMemory_ReadWord(Params+SIZE_LONG);
2657
2658 /* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */
2659 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x47 Dgetpath(0x%x, %i) at PC 0x%X\n", Address, (int)Drive,
2660 CallingPC);
2661 if (Drive == 0)
2662 Drive = CurrentDrive;
2663 else
2664 Drive--;
2665
2666 /* is it our drive? */
2667 if (GemDOS_IsDriveEmulated(Drive))
2668 {
2669 char path[MAX_GEMDOS_PATH];
2670 int i,len,c;
2671
2672 *path = '\0';
2673 strncat(path,&emudrives[Drive-2]->fs_currpath[strlen(emudrives[Drive-2]->hd_emulation_dir)], sizeof(path)-1);
2674
2675 // convert it to ST path (DOS)
2676 File_CleanFileName(path);
2677 len = strlen(path);
2678 /* Check that write is requested to valid memory area */
2679 if ( !STMemory_CheckAreaType ( Address, len, ABFLAG_RAM ) )
2680 {
2681 Log_Printf(LOG_WARN, "GEMDOS Dgetpath() failed due to invalid RAM range 0x%x+%i\n", Address, len);
2682 Regs[REG_D0] = GEMDOS_ERANGE;
2683 return true;
2684 }
2685 for (i = 0; i <= len; i++)
2686 {
2687 c = path[i];
2688 STMemory_WriteByte(Address+i, (c==PATHSEP ? '\\' : c) );
2689 }
2690 LOG_TRACE(TRACE_OS_GEMDOS, "-> '%s'\n", (char *)STMemory_STAddrToPointer(Address));
2691
2692 Regs[REG_D0] = GEMDOS_EOK; /* OK */
2693
2694 return true;
2695 }
2696 /* redirect to TOS */
2697 return false;
2698 }
2699
2700
2701 /*-----------------------------------------------------------------------*/
2702 /**
2703 * GEMDOS PExec handler
2704 * Call 0x4B
2705 */
GemDOS_Pexec(Uint32 Params)2706 static int GemDOS_Pexec(Uint32 Params)
2707 {
2708 int Drive;
2709 Uint16 Mode;
2710 char *pszFileName;
2711
2712 /* Find PExec mode */
2713 Mode = STMemory_ReadWord(Params);
2714
2715 if (LOG_TRACE_LEVEL(TRACE_OS_GEMDOS|TRACE_OS_BASE))
2716 {
2717 Uint32 fname, cmdline, env_string;
2718 fname = STMemory_ReadLong(Params+SIZE_WORD);
2719 cmdline = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
2720 env_string = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG+SIZE_LONG);
2721 if (Mode == 0 || Mode == 3)
2722 {
2723 int cmdlen;
2724 char *str;
2725 const char *name, *cmd;
2726 name = (const char *)STMemory_STAddrToPointer(fname);
2727 cmd = (const char *)STMemory_STAddrToPointer(cmdline);
2728 cmdlen = *cmd++;
2729 str = malloc(cmdlen+1);
2730 memcpy(str, cmd, cmdlen);
2731 str[cmdlen] = '\0';
2732 LOG_TRACE_PRINT ( "GEMDOS 0x4B Pexec(%i, \"%s\", [%d]\"%s\", 0x%x) at PC 0x%X\n", Mode, name, cmdlen, str, env_string,
2733 CallingPC);
2734 free(str);
2735 }
2736 else
2737 {
2738 LOG_TRACE_PRINT ( "GEMDOS 0x4B Pexec(%i, 0x%x, 0x%x, 0x%x) at PC 0x%X\n", Mode, fname, cmdline, env_string,
2739 CallingPC);
2740 }
2741 }
2742
2743 /* Re-direct as needed */
2744 switch(Mode)
2745 {
2746 case 0: /* Load and go */
2747 case 3: /* Load, don't go */
2748 pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params+SIZE_WORD));
2749 Drive = GemDOS_FileName2HardDriveID(pszFileName);
2750
2751 /* If not using A: or B:, use my own routines to load */
2752 if (ISHARDDRIVE(Drive))
2753 {
2754 /* Redirect to cart' routine at address 0xFA1000 */
2755 PexecCalled = true;
2756 return CALL_PEXEC_ROUTINE;
2757 }
2758 return false;
2759 case 4: /* Just go */
2760 return false;
2761 case 5: /* Create basepage */
2762 return false;
2763 case 6:
2764 return false;
2765 }
2766
2767 /* Default: Still re-direct to TOS */
2768 return false;
2769 }
2770
2771
2772 /*-----------------------------------------------------------------------*/
2773 /**
2774 * GEMDOS Search Next
2775 * Call 0x4F
2776 */
GemDOS_SNext(void)2777 static bool GemDOS_SNext(void)
2778 {
2779 struct dirent **temp;
2780 int Index;
2781 int ret;
2782 DTA *pDTA;
2783 Uint32 DTA_Gemdos;
2784
2785 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4F Fsnext() at PC 0x%X\n" , CallingPC);
2786
2787 /* Refresh pDTA pointer (from the current basepage) */
2788 DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd) + BASEPAGE_OFFSET_DTA);
2789
2790 if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) )
2791 {
2792 Log_Printf(LOG_WARN, "GEMDOS Fsnext() failed due to invalid DTA address 0x%x\n", DTA_Gemdos);
2793 Regs[REG_D0] = GEMDOS_EINTRN; /* "internal error */
2794 return true;
2795 }
2796 pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos);
2797
2798 /* Was DTA ours or TOS? */
2799 if (do_get_mem_long(pDTA->magic) != DTA_MAGIC_NUMBER)
2800 {
2801 /* redirect to TOS */
2802 return false;
2803 }
2804
2805 /* Find index into our list of structures */
2806 Index = do_get_mem_word(pDTA->index) & MAX_DTAS_MASK;
2807
2808 if (nAttrSFirst == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
2809 {
2810 /* Volume label was given already in Sfirst() */
2811 Regs[REG_D0] = GEMDOS_ENMFIL;
2812 return true;
2813 }
2814 if (!InternalDTAs[Index].bUsed)
2815 {
2816 /* Invalid handle, TOS returns ENMFIL
2817 * (if Fsetdta() has been used by any process)
2818 */
2819 Log_Printf(LOG_WARN, "GEMDOS Fsnext(): Invalid DTA\n");
2820 Regs[REG_D0] = GEMDOS_ENMFIL;
2821 }
2822
2823 temp = InternalDTAs[Index].found;
2824 do
2825 {
2826 if (InternalDTAs[Index].centry >= InternalDTAs[Index].nentries)
2827 {
2828 /* older TOS versions zero file name if there are no (further) matches */
2829 if (TosVersion < 0x0400)
2830 pDTA->dta_name[0] = 0;
2831 Regs[REG_D0] = GEMDOS_ENMFIL; /* No more files */
2832 return true;
2833 }
2834
2835 ret = PopulateDTA(InternalDTAs[Index].path,
2836 temp[InternalDTAs[Index].centry++],
2837 pDTA, DTA_Gemdos);
2838 } while (ret == 1);
2839
2840 if (ret < 0)
2841 {
2842 Log_Printf(LOG_WARN, "GEMDOS Fsnext(): Error setting DTA\n");
2843 Regs[REG_D0] = GEMDOS_EINTRN;
2844 return true;
2845 }
2846
2847 Regs[REG_D0] = GEMDOS_EOK;
2848 return true;
2849 }
2850
2851
2852 /*-----------------------------------------------------------------------*/
2853 /**
2854 * GEMDOS Find first file
2855 * Call 0x4E
2856 */
GemDOS_SFirst(Uint32 Params)2857 static bool GemDOS_SFirst(Uint32 Params)
2858 {
2859 /* TODO: host filenames might not fit into this */
2860 char szActualFileName[MAX_GEMDOS_PATH];
2861 char *pszFileName;
2862 const char *dirmask;
2863 struct dirent **files;
2864 int Drive;
2865 DIR *fsdir;
2866 int i,j,count;
2867 DTA *pDTA;
2868 Uint32 DTA_Gemdos;
2869
2870 /* Find filename to search for */
2871 pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2872 nAttrSFirst = STMemory_ReadWord(Params+SIZE_LONG);
2873
2874 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4E Fsfirst(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, nAttrSFirst,
2875 CallingPC);
2876
2877 Drive = GemDOS_FileName2HardDriveID(pszFileName);
2878 if (!ISHARDDRIVE(Drive))
2879 {
2880 /* redirect to TOS */
2881 return false;
2882 }
2883
2884 /* Convert to hard drive filename */
2885 GemDOS_CreateHardDriveFileName(Drive, pszFileName,
2886 szActualFileName, sizeof(szActualFileName));
2887
2888 /* Refresh pDTA pointer (from the current basepage) */
2889 DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd) + BASEPAGE_OFFSET_DTA);
2890
2891 if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) )
2892 {
2893 Log_Printf(LOG_WARN, "GEMDOS Fsfirst() failed due to invalid DTA address 0x%x\n", DTA_Gemdos);
2894 Regs[REG_D0] = GEMDOS_EINTRN; /* "internal error */
2895 return true;
2896 }
2897
2898 /* Atari memory modified directly with do_mem_* + strcpy() -> flush the data cache */
2899 M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA));
2900
2901 pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos);
2902
2903 /* Populate DTA, set index for our use */
2904 do_put_mem_word(pDTA->index, DTAIndex);
2905 /* set our dta magic num */
2906 do_put_mem_long(pDTA->magic, DTA_MAGIC_NUMBER);
2907
2908 if (InternalDTAs[DTAIndex].bUsed == true)
2909 ClearInternalDTA(DTAIndex);
2910 InternalDTAs[DTAIndex].bUsed = true;
2911
2912 /* Were we looking for the volume label? */
2913 if (nAttrSFirst == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
2914 {
2915 /* Volume name */
2916 strcpy(pDTA->dta_name,"EMULATED.001");
2917 pDTA->dta_name[11] = '0' + Drive;
2918 Regs[REG_D0] = GEMDOS_EOK; /* Got volume */
2919 return true;
2920 }
2921
2922 /* open directory
2923 * TODO: host path may not fit into InternalDTA
2924 */
2925 fsfirst_dirname(szActualFileName, InternalDTAs[DTAIndex].path);
2926 fsdir = opendir(InternalDTAs[DTAIndex].path);
2927
2928 if (fsdir == NULL)
2929 {
2930 Regs[REG_D0] = GEMDOS_EPTHNF; /* Path not found */
2931 return true;
2932 }
2933 /* close directory */
2934 closedir(fsdir);
2935
2936 count = scandir(InternalDTAs[DTAIndex].path, &files, 0, alphasort);
2937 /* File (directory actually) not found */
2938 if (count < 0)
2939 {
2940 Regs[REG_D0] = GEMDOS_EFILNF;
2941 return true;
2942 }
2943
2944 InternalDTAs[DTAIndex].centry = 0; /* current entry is 0 */
2945 dirmask = fsfirst_dirmask(szActualFileName);/* directory mask part */
2946 InternalDTAs[DTAIndex].found = files; /* get files */
2947
2948 /* count & copy the entries that match our mask and discard the rest */
2949 j = 0;
2950 for (i=0; i < count; i++)
2951 {
2952 Str_DecomposedToPrecomposedUtf8(files[i]->d_name, files[i]->d_name); /* for OSX */
2953 if (fsfirst_match(dirmask, files[i]->d_name))
2954 {
2955 InternalDTAs[DTAIndex].found[j] = files[i];
2956 j++;
2957 }
2958 else
2959 {
2960 free(files[i]);
2961 files[i] = NULL;
2962 }
2963 }
2964 InternalDTAs[DTAIndex].nentries = j; /* set number of legal entries */
2965
2966 /* No files of that match, return error code */
2967 if (j==0)
2968 {
2969 free(files);
2970 InternalDTAs[DTAIndex].found = NULL;
2971 Regs[REG_D0] = GEMDOS_EFILNF; /* File not found */
2972 return true;
2973 }
2974
2975 /* Scan for first file (SNext uses no parameters) */
2976 GemDOS_SNext();
2977 /* increment DTA index */
2978 DTAIndex++;
2979 DTAIndex &= MAX_DTAS_MASK;
2980
2981 return true;
2982 }
2983
2984
2985 /*-----------------------------------------------------------------------*/
2986 /**
2987 * GEMDOS Rename
2988 * Call 0x56
2989 */
GemDOS_Rename(Uint32 Params)2990 static bool GemDOS_Rename(Uint32 Params)
2991 {
2992 char *pszNewFileName,*pszOldFileName;
2993 /* TODO: host filenames might not fit into this */
2994 char szNewActualFileName[MAX_GEMDOS_PATH];
2995 char szOldActualFileName[MAX_GEMDOS_PATH];
2996 int NewDrive, OldDrive;
2997
2998 /* Read details from stack, skip first (dummy) arg */
2999 pszOldFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params+SIZE_WORD));
3000 pszNewFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG));
3001
3002 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x56 Frename(\"%s\", \"%s\") at PC 0x%X\n", pszOldFileName, pszNewFileName,
3003 CallingPC);
3004
3005 NewDrive = GemDOS_FileName2HardDriveID(pszNewFileName);
3006 OldDrive = GemDOS_FileName2HardDriveID(pszOldFileName);
3007 if (!(ISHARDDRIVE(NewDrive) && ISHARDDRIVE(OldDrive)))
3008 {
3009 /* redirect to TOS */
3010 return false;
3011 }
3012
3013 /* write protected device? */
3014 if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
3015 {
3016 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Frename(\"%s\", \"%s\")\n", pszOldFileName, pszNewFileName);
3017 Regs[REG_D0] = GEMDOS_EWRPRO;
3018 return true;
3019 }
3020
3021 /* And convert to hard drive filenames */
3022 GemDOS_CreateHardDriveFileName(NewDrive, pszNewFileName,
3023 szNewActualFileName, sizeof(szNewActualFileName));
3024 GemDOS_CreateHardDriveFileName(OldDrive, pszOldFileName,
3025 szOldActualFileName, sizeof(szOldActualFileName));
3026
3027 /* Rename files */
3028 if (rename(szOldActualFileName,szNewActualFileName) == 0)
3029 Regs[REG_D0] = GEMDOS_EOK;
3030 else
3031 Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
3032 return true;
3033 }
3034
3035
3036 /*-----------------------------------------------------------------------*/
3037 /**
3038 * GEMDOS GSDToF
3039 * Call 0x57
3040 */
GemDOS_GSDToF(Uint32 Params)3041 static bool GemDOS_GSDToF(Uint32 Params)
3042 {
3043 DATETIME DateTime;
3044 Uint32 pBuffer;
3045 int Handle,Flag;
3046
3047 /* Read details from stack */
3048 pBuffer = STMemory_ReadLong(Params);
3049 Handle = STMemory_ReadWord(Params+SIZE_LONG);
3050 Flag = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);
3051
3052 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x57 Fdatime(0x%x, %i, %i) at PC 0x%X\n", pBuffer,
3053 Handle, Flag,
3054 CallingPC);
3055
3056 /* get internal handle */
3057 if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
3058 {
3059 /* No, assume was TOS -> redirect */
3060 return false;
3061 }
3062
3063 if (Flag == 1)
3064 {
3065 /* write protected device? */
3066 if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
3067 {
3068 Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdatime(,%d,)\n", Handle);
3069 Regs[REG_D0] = GEMDOS_EWRPRO;
3070 return true;
3071 }
3072 DateTime.timeword = STMemory_ReadWord(pBuffer);
3073 DateTime.dateword = STMemory_ReadWord(pBuffer+SIZE_WORD);
3074 if (GemDOS_SetFileInformation(Handle, &DateTime) == true)
3075 Regs[REG_D0] = GEMDOS_EOK;
3076 else
3077 Regs[REG_D0] = GEMDOS_EACCDN; /* Access denied */
3078 return true;
3079 }
3080
3081 if (GemDOS_GetFileInformation(Handle, &DateTime) == true)
3082 {
3083 /* Check that write is requested to valid memory area */
3084 if ( STMemory_CheckAreaType ( pBuffer, 4, ABFLAG_RAM ) )
3085 {
3086 STMemory_WriteWord(pBuffer, DateTime.timeword);
3087 STMemory_WriteWord(pBuffer+SIZE_WORD, DateTime.dateword);
3088 Regs[REG_D0] = GEMDOS_EOK;
3089 }
3090 else
3091 {
3092 Log_Printf(LOG_WARN, "GEMDOS Fdatime() failed due to invalid RAM range 0x%x+%i\n", pBuffer, 4);
3093 Regs[REG_D0] = GEMDOS_ERANGE;
3094 }
3095 }
3096 else
3097 {
3098 Regs[REG_D0] = GEMDOS_ERROR; /* Generic error */
3099 }
3100 return true;
3101 }
3102
3103
3104 /*-----------------------------------------------------------------------*/
3105 /**
3106 * Do implicit file handle closing/unforcing on program termination
3107 */
GemDOS_TerminateClose(void)3108 static void GemDOS_TerminateClose(void)
3109 {
3110 int i, closed, unforced;
3111 Uint32 current = STMemory_ReadLong(act_pd);
3112
3113 closed = 0;
3114 for (i = 0; i < ARRAY_SIZE(FileHandles); i++)
3115 {
3116 if (FileHandles[i].Basepage == current)
3117 {
3118 GemDOS_CloseFileHandle(i);
3119 closed++;
3120 }
3121 }
3122 unforced = 0;
3123 for (i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
3124 {
3125 if (ForcedHandles[i].Basepage == current)
3126 {
3127 GemDOS_UnforceFileHandle(i);
3128 unforced++;
3129 }
3130 }
3131 if (!(closed || unforced))
3132 return;
3133 Log_Printf(LOG_WARN, "Closing %d & unforcing %d file handle(s) remaining at program 0x%x exit.\n",
3134 closed, unforced, current);
3135 }
3136
3137 /**
3138 * GEMDOS Pterm0
3139 * Call 0x00
3140 */
GemDOS_Pterm0(Uint32 Params)3141 static bool GemDOS_Pterm0(Uint32 Params)
3142 {
3143 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x00 Pterm0() at PC 0x%X\n",
3144 CallingPC);
3145 GemDOS_TerminateClose();
3146 Symbols_RemoveCurrentProgram();
3147
3148 if (!bUseTos)
3149 {
3150 Main_SetQuitValue(0);
3151 return true;
3152 }
3153
3154 return false;
3155 }
3156
3157 /**
3158 * GEMDOS Ptermres
3159 * Call 0x31
3160 */
GemDOS_Ptermres(Uint32 Params)3161 static bool GemDOS_Ptermres(Uint32 Params)
3162 {
3163 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x31 Ptermres(0x%X, %hd) at PC 0x%X\n",
3164 STMemory_ReadLong(Params), (Sint16)STMemory_ReadWord(Params+SIZE_WORD),
3165 CallingPC);
3166 GemDOS_TerminateClose();
3167 return false;
3168 }
3169
3170 /**
3171 * GEMDOS Pterm
3172 * Call 0x4c
3173 */
GemDOS_Pterm(Uint32 Params)3174 static bool GemDOS_Pterm(Uint32 Params)
3175 {
3176 uint16_t nExitVal = STMemory_ReadWord(Params);
3177
3178 LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x4C Pterm(%hd) at PC 0x%X\n",
3179 nExitVal, CallingPC);
3180
3181 GemDOS_TerminateClose();
3182 Symbols_RemoveCurrentProgram();
3183
3184 if (!bUseTos)
3185 {
3186 Main_SetQuitValue(nExitVal);
3187 return true;
3188 }
3189
3190 return false;
3191 }
3192
3193 /**
3194 * GEMDOS Super
3195 * Call 0x20
3196 */
GemDOS_Super(Uint32 Params)3197 static bool GemDOS_Super(Uint32 Params)
3198 {
3199 uint32_t nParam = STMemory_ReadLong(Params);
3200 uint32_t nExcFrameSize, nRetAddr;
3201 uint16_t nSR, nVec = 0;
3202
3203 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x20 Super(0x%X) at PC 0x%X\n",
3204 nParam, CallingPC);
3205
3206 /* This call is normally fully handled by TOS - we only
3207 * need to emulate it for TOS-less testing mode */
3208 if (bUseTos)
3209 return false;
3210
3211 /* Get SR, return address and vector offset from stack frame */
3212 nSR = STMemory_ReadWord(Regs[REG_A7]);
3213 nRetAddr = STMemory_ReadLong(Regs[REG_A7] + SIZE_WORD);
3214 if (currprefs.cpu_level > 0)
3215 nVec = STMemory_ReadWord(Regs[REG_A7] + SIZE_WORD + SIZE_LONG);
3216
3217 if (nParam == 1) /* Query mode? */
3218 {
3219 Regs[REG_D0] = (nSR & SR_SUPERMODE) ? -1 : 0;
3220 return true;
3221 }
3222
3223 if (nParam == 0)
3224 {
3225 nParam = regs.usp;
3226 }
3227
3228 if (currprefs.cpu_level > 0)
3229 nExcFrameSize = SIZE_WORD + SIZE_LONG + SIZE_WORD;
3230 else
3231 nExcFrameSize = SIZE_WORD + SIZE_LONG;
3232
3233
3234 Regs[REG_D0] = Regs[REG_A7] + nExcFrameSize;
3235 Regs[REG_A7] = nParam - nExcFrameSize;
3236
3237 nSR ^= SR_SUPERMODE;
3238
3239 STMemory_WriteWord(Regs[REG_A7], nSR);
3240 STMemory_WriteLong(Regs[REG_A7] + SIZE_WORD, nRetAddr);
3241 STMemory_WriteWord(Regs[REG_A7] + SIZE_WORD + SIZE_LONG, nVec);
3242
3243 return true;
3244 }
3245
3246
3247 /**
3248 * Map GEMDOS call opcodes to their names
3249 *
3250 * Mapping is based on TOSHYP information:
3251 * http://toshyp.atari.org/en/005013.html
3252 */
GemDOS_Opcode2Name(Uint16 opcode)3253 static const char* GemDOS_Opcode2Name(Uint16 opcode)
3254 {
3255 static const char* names[] = {
3256 "Pterm0",
3257 "Cconin",
3258 "Cconout",
3259 "Cauxin",
3260 "Cauxout",
3261 "Cprnout",
3262 "Crawio",
3263 "Crawcin",
3264 "Cnecin",
3265 "Cconws",
3266 "Cconrs",
3267 "Cconis",
3268 "-", /* 0C */
3269 "-", /* 0D */
3270 "Dsetdrv",
3271 "-", /* 0F */
3272 "Cconos",
3273 "Cprnos",
3274 "Cauxis",
3275 "Cauxos",
3276 "Maddalt",
3277 "Srealloc", /* TOS4 */
3278 "-", /* 16 */
3279 "-", /* 17 */
3280 "-", /* 18 */
3281 "Dgetdrv",
3282 "Fsetdta",
3283 "-", /* 1B */
3284 "-", /* 1C */
3285 "-", /* 1D */
3286 "-", /* 1E */
3287 "-", /* 1F */
3288 "Super",
3289 "-", /* 21 */
3290 "-", /* 22 */
3291 "-", /* 23 */
3292 "-", /* 24 */
3293 "-", /* 25 */
3294 "-", /* 26 */
3295 "-", /* 27 */
3296 "-", /* 28 */
3297 "-", /* 29 */
3298 "Tgetdate",
3299 "Tsetdate",
3300 "Tgettime",
3301 "Tsettime",
3302 "-", /* 2E */
3303 "Fgetdta",
3304 "Sversion",
3305 "Ptermres",
3306 "-", /* 32 */
3307 "-", /* 33 */
3308 "-", /* 34 */
3309 "-", /* 35 */
3310 "Dfree",
3311 "-", /* 37 */
3312 "-", /* 38 */
3313 "Dcreate",
3314 "Ddelete",
3315 "Dsetpath",
3316 "Fcreate",
3317 "Fopen",
3318 "Fclose",
3319 "Fread",
3320 "Fwrite",
3321 "Fdelete",
3322 "Fseek",
3323 "Fattrib",
3324 "Mxalloc",
3325 "Fdup",
3326 "Fforce",
3327 "Dgetpath",
3328 "Malloc",
3329 "Mfree",
3330 "Mshrink",
3331 "Pexec",
3332 "Pterm",
3333 "-", /* 4D */
3334 "Fsfirst",
3335 "Fsnext",
3336 "-", /* 50 */
3337 "-", /* 51 */
3338 "-", /* 52 */
3339 "-", /* 53 */
3340 "-", /* 54 */
3341 "-", /* 55 */
3342 "Frename",
3343 "Fdatime",
3344 "-", /* 58 */
3345 "-", /* 59 */
3346 "-", /* 5A */
3347 "-", /* 5B */
3348 "Flock", /* 5C */
3349 "-", /* 5D */
3350 "-", /* 5E */
3351 "-", /* 5F */
3352 "Nversion", /* 60 */
3353 "-", /* 61 */
3354 "-", /* 62 */
3355 "-", /* 63 */
3356 "-", /* 64 */
3357 "-", /* 65 */
3358 "-", /* 66 */
3359 "-", /* 67 */
3360 "-", /* 68 */
3361 "-", /* 69 */
3362 "-", /* 6A */
3363 "-", /* 6B */
3364 "-", /* 6C */
3365 "-", /* 6D */
3366 "-", /* 6E */
3367 "-", /* 6F */
3368 "-", /* 70 */
3369 "-", /* 71 */
3370 "-", /* 72 */
3371 "-", /* 73 */
3372 "-", /* 74 */
3373 "-", /* 75 */
3374 "-", /* 76 */
3375 "-", /* 77 */
3376 "-", /* 78 */
3377 "-", /* 79 */
3378 "-", /* 7A */
3379 "-", /* 7B */
3380 "-", /* 7C */
3381 "-", /* 7D */
3382 "-", /* 7E */
3383 "-", /* 7F */
3384 "-", /* 80 */
3385 "-", /* 81 */
3386 "-", /* 82 */
3387 "-", /* 83 */
3388 "-", /* 84 */
3389 "-", /* 85 */
3390 "-", /* 86 */
3391 "-", /* 87 */
3392 "-", /* 88 */
3393 "-", /* 89 */
3394 "-", /* 8A */
3395 "-", /* 8B */
3396 "-", /* 8C */
3397 "-", /* 8D */
3398 "-", /* 8E */
3399 "-", /* 8F */
3400 "-", /* 90 */
3401 "-", /* 91 */
3402 "-", /* 92 */
3403 "-", /* 93 */
3404 "-", /* 94 */
3405 "-", /* 95 */
3406 "-", /* 96 */
3407 "-", /* 97 */
3408 "-", /* 98 */
3409 "-", /* 99 */
3410 "-", /* 9A */
3411 "-", /* 9B */
3412 "-", /* 9C */
3413 "-", /* 9D */
3414 "-", /* 9E */
3415 "-", /* 9F */
3416 "-", /* A0 */
3417 "-", /* A1 */
3418 "-", /* A2 */
3419 "-", /* A3 */
3420 "-", /* A4 */
3421 "-", /* A5 */
3422 "-", /* A6 */
3423 "-", /* A7 */
3424 "-", /* A8 */
3425 "-", /* A9 */
3426 "-", /* AA */
3427 "-", /* AB */
3428 "-", /* AC */
3429 "-", /* AD */
3430 "-", /* AE */
3431 "-", /* AF */
3432 "-", /* B0 */
3433 "-", /* B1 */
3434 "-", /* B2 */
3435 "-", /* B3 */
3436 "-", /* B4 */
3437 "-", /* B5 */
3438 "-", /* B6 */
3439 "-", /* B7 */
3440 "-", /* B8 */
3441 "-", /* B9 */
3442 "-", /* BA */
3443 "-", /* BB */
3444 "-", /* BC */
3445 "-", /* BD */
3446 "-", /* BE */
3447 "-", /* BF */
3448 "-", /* C0 */
3449 "-", /* C1 */
3450 "-", /* C2 */
3451 "-", /* C3 */
3452 "-", /* C4 */
3453 "-", /* C5 */
3454 "-", /* C6 */
3455 "-", /* C7 */
3456 "-", /* C8 */
3457 "-", /* C9 */
3458 "-", /* CA */
3459 "-", /* CB */
3460 "-", /* CC */
3461 "-", /* CD */
3462 "-", /* CE */
3463 "-", /* CF */
3464 "-", /* D0 */
3465 "-", /* D1 */
3466 "-", /* D2 */
3467 "-", /* D3 */
3468 "-", /* D4 */
3469 "-", /* D5 */
3470 "-", /* D6 */
3471 "-", /* D7 */
3472 "-", /* D8 */
3473 "-", /* D9 */
3474 "-", /* DA */
3475 "-", /* DB */
3476 "-", /* DC */
3477 "-", /* DD */
3478 "-", /* DE */
3479 "-", /* DF */
3480 "-", /* E0 */
3481 "-", /* E1 */
3482 "-", /* E2 */
3483 "-", /* E3 */
3484 "-", /* E4 */
3485 "-", /* E5 */
3486 "-", /* E6 */
3487 "-", /* E7 */
3488 "-", /* E8 */
3489 "-", /* E9 */
3490 "-", /* EA */
3491 "-", /* EB */
3492 "-", /* EC */
3493 "-", /* ED */
3494 "-", /* EE */
3495 "-", /* EF */
3496 "-", /* F0 */
3497 "-", /* F1 */
3498 "-", /* F2 */
3499 "-", /* F3 */
3500 "-", /* F4 */
3501 "-", /* F5 */
3502 "-", /* F6 */
3503 "-", /* F7 */
3504 "-", /* F8 */
3505 "-", /* F9 */
3506 "-", /* FA */
3507 "-", /* FB */
3508 "-", /* FC */
3509 "-", /* FD */
3510 "-", /* FE */
3511 "Syield", /* FF */
3512 "Fpipe", /* 100 */
3513 "Ffchown", /* 101 */
3514 "Ffchmod", /* 102 */
3515 "Fsync", /* 103 */
3516 "Fcntl", /* 104 */
3517 "Finstat", /* 105 */
3518 "Foutstat", /* 106 */
3519 "Fgetchar", /* 107 */
3520 "Fputchar", /* 108 */
3521 "Pwait", /* 109 */
3522 "Pnice", /* 10A */
3523 "Pgetpid", /* 10B */
3524 "Pgetppid", /* 10C */
3525 "Pgetpgrp", /* 10D */
3526 "Psetpgrp", /* 10E */
3527 "Pgetuid", /* 10F */
3528 "Psetuid", /* 110 */
3529 "Pkill", /* 111 */
3530 "Psignal", /* 112 */
3531 "Pvfork", /* 113 */
3532 "Pgetgid", /* 114 */
3533 "Psetgid", /* 115 */
3534 "Psigblock", /* 116 */
3535 "Psigsetmask", /* 117 */
3536 "Pusrval", /* 118 */
3537 "Pdomain", /* 119 */
3538 "Psigreturn", /* 11A */
3539 "Pfork", /* 11B */
3540 "Pwait3", /* 11C */
3541 "Fselect", /* 11D */
3542 "Prusage", /* 11E */
3543 "Psetlimit", /* 11F */
3544 "Talarm", /* 120 */
3545 "Pause", /* 121 */
3546 "Sysconf", /* 122 */
3547 "Psigpending", /* 123 */
3548 "Dpathconf", /* 124 */
3549 "Pmsg", /* 125 */
3550 "Fmidipipe", /* 126 */
3551 "Prenice", /* 127 */
3552 "Dopendir", /* 128 */
3553 "Dreaddir", /* 129 */
3554 "Drewinddir", /* 12A */
3555 "Dclosedir", /* 12B */
3556 "Fxattr", /* 12C */
3557 "Flink", /* 12D */
3558 "Fsymlink", /* 12E */
3559 "Freadlink", /* 12F */
3560 "Dcntl", /* 130 */
3561 "Fchown", /* 131 */
3562 "Fchmod", /* 132 */
3563 "Pumask", /* 133 */
3564 "Psemaphore", /* 134 */
3565 "Dlock", /* 135 */
3566 "Psigpause", /* 136 */
3567 "Psigaction", /* 137 */
3568 "Pgeteuid", /* 138 */
3569 "Pgetegid", /* 139 */
3570 "Pwaitpid", /* 13A */
3571 "Dgetcwd", /* 13B */
3572 "Salert", /* 13C */
3573 "Tmalarm", /* 13D */
3574 "Psigintr", /* 13E */
3575 "Suptime", /* 13F */
3576 "Ptrace", /* 140 */
3577 "Mvalidate", /* 141 */
3578 "Dxreaddir", /* 142 */
3579 "Pseteuid", /* 143 */
3580 "Psetegid", /* 144 */
3581 "Pgetauid", /* 145 */
3582 "Psetauid", /* 146 */
3583 "Pgetgroups", /* 147 */
3584 "Psetgroups", /* 148 */
3585 "Tsetitimer", /* 149 */
3586 "Dchroot", /* 14A; was Scookie */
3587 "Fstat64", /* 14B */
3588 "Fseek64", /* 14C */
3589 "Dsetkey", /* 14D */
3590 "Psetreuid", /* 14E */
3591 "Psetregid", /* 14F */
3592 "Sync", /* 150 */
3593 "Shutdown", /* 151 */
3594 "Dreadlabel", /* 152 */
3595 "Dwritelabel", /* 153 */
3596 "Ssystem", /* 154 */
3597 "Tgettimeofday", /* 155 */
3598 "Tsettimeofday", /* 156 */
3599 "Tadjtime", /* 157 */
3600 "Pgetpriority", /* 158 */
3601 "Psetpriority", /* 159 */
3602 "Fpoll", /* 15A */
3603 "Fwritev", /* 15B */
3604 "Freadv", /* 15C */
3605 "Ffstat64", /* 15D */
3606 "Psysctl", /* 15E */
3607 "Semulation", /* 15F */
3608 "Fsocket", /* 160 */
3609 "Fsocketpair", /* 161 */
3610 "Faccept", /* 162 */
3611 "Fconnect", /* 163 */
3612 "Fbind", /* 164 */
3613 "Flisten", /* 165 */
3614 "Frecvmsg", /* 166 */
3615 "Fsendmsg", /* 167 */
3616 "Frecvfrom", /* 168 */
3617 "Fsendto", /* 169 */
3618 "Fsetsockopt", /* 16A */
3619 "Fgetsockopt", /* 16B */
3620 "Fgetpeername", /* 16C */
3621 "Fgetsockname", /* 16D */
3622 "Fshutdown", /* 16E */
3623 "-", /* 16F */
3624 "Pshmget", /* 170 */
3625 "Pshmctl", /* 171 */
3626 "Pshmat", /* 172 */
3627 "Pshmdt", /* 173 */
3628 "Psemget", /* 174 */
3629 "Psemctl", /* 175 */
3630 "Psemop", /* 176 */
3631 "Psemconfig", /* 177 */
3632 "Pmsgget", /* 178 */
3633 "Pmsgctl", /* 179 */
3634 "Pmsgsnd", /* 17A */
3635 "Pmsgrcv", /* 17B */
3636 "-", /* 17C */
3637 "Maccess", /* 17D */
3638 "-", /* 17E */
3639 "-", /* 17F */
3640 "Fchown16", /* 180 */
3641 "Fchdir", /* 181 */
3642 "Ffdopendir", /* 182 */
3643 "Fdirfd" /* 183 */
3644 };
3645
3646 if (opcode < ARRAY_SIZE(names))
3647 return names[opcode];
3648 return "-";
3649 }
3650
3651
3652 /**
3653 * If bShowOpcodes is true, show GEMDOS call opcode/function name table,
3654 * otherwise GEMDOS HDD emulation information.
3655 */
GemDOS_Info(FILE * fp,Uint32 bShowOpcodes)3656 void GemDOS_Info(FILE *fp, Uint32 bShowOpcodes)
3657 {
3658 int i, used;
3659
3660 if (bShowOpcodes)
3661 {
3662 Uint16 opcode;
3663 /* list just normal TOS GEMDOS calls
3664 *
3665 * MiNT ones would need separate table as their names
3666 * are much longer and 0x60 - 0xFE range is unused.
3667 */
3668 for (opcode = 0; opcode < 0x5A; )
3669 {
3670 fprintf(fp, "%02x %-9s",
3671 opcode, GemDOS_Opcode2Name(opcode));
3672 if (++opcode % 6 == 0)
3673 fputs("\n", fp);
3674 }
3675 return;
3676 }
3677
3678 if (!GEMDOS_EMU_ON)
3679 {
3680 fputs("GEMDOS HDD emulation isn't enabled!\n", fp);
3681 return;
3682 }
3683
3684 /* GEMDOS vector set by Hatari can be overwritten e.g. MiNT */
3685 fprintf(fp, "Current GEMDOS handler: (0x84) = 0x%x, emu one = 0x%x\n", STMemory_ReadLong(0x0084), CART_GEMDOS);
3686 fprintf(fp, "Stored GEMDOS handler: (0x%x) = 0x%x\n\n", CART_OLDGEMDOS, STMemory_ReadLong(CART_OLDGEMDOS));
3687
3688 fprintf(fp, "Connected drives mask: 0x%x\n\n", ConnectedDriveMask);
3689 fputs("GEMDOS HDD emulation drives:\n", fp);
3690 for(i = 0; i < MAX_HARDDRIVES; i++)
3691 {
3692 if (!emudrives[i])
3693 continue;
3694 fprintf(fp, "- %c: %s\n curpath: %s\n",
3695 'A' + emudrives[i]->drive_number,
3696 emudrives[i]->hd_emulation_dir,
3697 emudrives[i]->fs_currpath);
3698 }
3699
3700 fputs("\nInternal Fsfirst() DTAs:\n", fp);
3701 for(used = i = 0; i < ARRAY_SIZE(InternalDTAs); i++)
3702 {
3703 int j, centry, entries;
3704
3705 if (!InternalDTAs[i].bUsed)
3706 continue;
3707
3708 fprintf(fp, "+ %d: %s\n", i, InternalDTAs[i].path);
3709
3710 centry = InternalDTAs[i].centry;
3711 entries = InternalDTAs[i].nentries;
3712 for (j = 0; j < entries; j++)
3713 {
3714 fprintf(fp, " - %d: %s%s\n",
3715 j, InternalDTAs[i].found[j]->d_name,
3716 j == centry ? " *" : "");
3717 }
3718 fprintf(fp, " Fsnext entry = %d.\n", centry);
3719 used++;
3720 }
3721 if (!used)
3722 fputs("- None in use.\n", fp);
3723
3724 fputs("\nOpen GEMDOS HDD file handles:\n", fp);
3725 for (used = i = 0; i < ARRAY_SIZE(FileHandles); i++)
3726 {
3727 if (!FileHandles[i].bUsed)
3728 continue;
3729 fprintf(fp, "- %d (0x%x): %s\n", i + BASE_FILEHANDLE,
3730 FileHandles[i].Basepage, FileHandles[i].szActualName);
3731 used++;
3732 }
3733 if (!used)
3734 fputs("- None.\n", fp);
3735 fputs("\nForced GEMDOS HDD file handles:\n", fp);
3736 for (used = i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
3737 {
3738 if (ForcedHandles[i].Handle == UNFORCED_HANDLE)
3739 continue;
3740 fprintf(fp, "- %d -> %d (0x%x)\n", i,
3741 ForcedHandles[i].Handle + BASE_FILEHANDLE,
3742 ForcedHandles[i].Basepage);
3743 used++;
3744 }
3745 if (!used)
3746 fputs("- None.\n", fp);
3747 }
3748
3749 /**
3750 * Show given DTA info
3751 * (works also without GEMDOS HD emu)
3752 */
GemDOS_InfoDTA(FILE * fp,Uint32 dta_addr)3753 void GemDOS_InfoDTA(FILE *fp, Uint32 dta_addr)
3754 {
3755 DTA *dta;
3756 Uint32 magic;
3757 char name[TOS_NAMELEN+1];
3758
3759 fprintf(fp, "DTA (0x%x):\n", dta_addr);
3760 if (act_pd)
3761 {
3762 Uint32 basepage = STMemory_ReadLong(act_pd);
3763 Uint32 dta_curr = STMemory_ReadLong(basepage + BASEPAGE_OFFSET_DTA);
3764 if (dta_addr != dta_curr)
3765 {
3766 fprintf(fp, "- NOTE: given DTA (0x%x) is not current program one (0x%x)\n",
3767 dta_addr, dta_curr);
3768 }
3769 if (dta_addr >= basepage && dta_addr + sizeof(DTA) < basepage + BASEPAGE_SIZE)
3770 {
3771 const char *msg = (dta_addr == basepage + 0x80) ? ", replacing command line" : "";
3772 fprintf(fp, "- NOTE: DTA (0x%x) is within current program basepage (0x%x)%s!\n",
3773 dta_addr, basepage, msg);
3774 }
3775 }
3776 if (!STMemory_CheckAreaType(dta_addr, sizeof(DTA), ABFLAG_RAM)) {
3777 fprintf(fp, "- ERROR: invalid memory address!\n");
3778 return;
3779 }
3780 dta = (DTA *)STMemory_STAddrToPointer(dta_addr);
3781 memcpy(name, dta->dta_name, TOS_NAMELEN);
3782 name[TOS_NAMELEN] = '\0';
3783 magic = do_get_mem_long(dta->magic);
3784 fprintf(fp, "- magic: 0x%08x (GEMDOS HD = 0x%08x)\n", magic, DTA_MAGIC_NUMBER);
3785 if (magic == DTA_MAGIC_NUMBER)
3786 fprintf(fp, "- index: 0x%04x\n", do_get_mem_word(dta->index));
3787 fprintf(fp, "- attr: 0x%x\n", dta->dta_attrib);
3788 fprintf(fp, "- time: 0x%04x\n", do_get_mem_word(dta->dta_time));
3789 fprintf(fp, "- date: 0x%04x\n", do_get_mem_word(dta->dta_date));
3790 fprintf(fp, "- size: %d\n", do_get_mem_long(dta->dta_size));
3791 fprintf(fp, "- name: '%s'\n", name);
3792 }
3793
3794
3795 /**
3796 * Run GEMDos call, and re-direct if need to. Used to handle hard disk emulation etc...
3797 * This sets the condition codes (in SR), which are used in the 'cart_asm.s' program to
3798 * decide if we need to run old GEM vector, or PExec or nothing.
3799 *
3800 * This method keeps the stack and other states consistent with the original ST
3801 * which is very important for the PExec call and maximum compatibility through-out
3802 */
GemDOS_OpCode(void)3803 void GemDOS_OpCode(void)
3804 {
3805 Uint16 GemDOSCall, CallingSReg;
3806 Uint32 Params;
3807 int Finished;
3808 Uint16 SR;
3809
3810 SR = M68000_GetSR();
3811
3812 /* Read SReg from stack to see if parameters are on User or Super stack */
3813 CallingSReg = STMemory_ReadWord(Regs[REG_A7]);
3814 CallingPC = STMemory_ReadLong(Regs[REG_A7]+SIZE_WORD);
3815 if ((CallingSReg&SR_SUPERMODE)==0) /* Calling from user mode */
3816 Params = regs.usp;
3817 else
3818 {
3819 Params = Regs[REG_A7]+SIZE_WORD+SIZE_LONG; /* skip SR & PC pushed to super stack */
3820 if (currprefs.cpu_level > 0)
3821 Params += SIZE_WORD; /* Skip extra word whe CPU is >=68010 */
3822 }
3823
3824 /* Default to run TOS GemDos (SR_NEG run Gemdos, SR_ZERO already done, SR_OVERFLOW run own 'Pexec' */
3825 Finished = false;
3826 SR &= SR_CLEAR_OVERFLOW;
3827 SR &= SR_CLEAR_ZERO;
3828 SR |= SR_NEG;
3829
3830 /* Find pointer to call parameters */
3831 GemDOSCall = STMemory_ReadWord(Params);
3832 Params += SIZE_WORD;
3833
3834 /* Intercept call */
3835 switch(GemDOSCall)
3836 {
3837 case 0x00:
3838 Finished = GemDOS_Pterm0(Params);
3839 break;
3840 case 0x09:
3841 Finished = GemDOS_Cconws(Params);
3842 break;
3843 case 0x0e:
3844 Finished = GemDOS_SetDrv(Params);
3845 break;
3846 case 0x20:
3847 Finished = GemDOS_Super(Params);
3848 break;
3849 case 0x31:
3850 Finished = GemDOS_Ptermres(Params);
3851 break;
3852 case 0x36:
3853 Finished = GemDOS_DFree(Params);
3854 break;
3855 case 0x39:
3856 Finished = GemDOS_MkDir(Params);
3857 break;
3858 case 0x3a:
3859 Finished = GemDOS_RmDir(Params);
3860 break;
3861 case 0x3b: /* Dsetpath */
3862 Finished = GemDOS_ChDir(Params);
3863 break;
3864 case 0x3c:
3865 Finished = GemDOS_Create(Params);
3866 break;
3867 case 0x3d:
3868 Finished = GemDOS_Open(Params);
3869 break;
3870 case 0x3e:
3871 Finished = GemDOS_Close(Params);
3872 break;
3873 case 0x3f:
3874 Finished = GemDOS_Read(Params);
3875 break;
3876 case 0x40:
3877 Finished = GemDOS_Write(Params);
3878 break;
3879 case 0x41:
3880 Finished = GemDOS_FDelete(Params);
3881 break;
3882 case 0x42:
3883 Finished = GemDOS_LSeek(Params);
3884 break;
3885 case 0x43:
3886 Finished = GemDOS_Fattrib(Params);
3887 break;
3888 case 0x46:
3889 Finished = GemDOS_Force(Params);
3890 break;
3891 case 0x47: /* Dgetpath */
3892 Finished = GemDOS_GetDir(Params);
3893 break;
3894 case 0x4b:
3895 /* Either false or CALL_PEXEC_ROUTINE */
3896 Finished = GemDOS_Pexec(Params);
3897 break;
3898 case 0x4c:
3899 Finished = GemDOS_Pterm(Params);
3900 break;
3901 case 0x4e:
3902 Finished = GemDOS_SFirst(Params);
3903 break;
3904 case 0x4f:
3905 Finished = GemDOS_SNext();
3906 break;
3907 case 0x56:
3908 Finished = GemDOS_Rename(Params);
3909 break;
3910 case 0x57:
3911 Finished = GemDOS_GSDToF(Params);
3912 break;
3913
3914 /* print args for other calls */
3915
3916 case 0x01: /* Conin */
3917 case 0x03: /* Cauxin */
3918 case 0x12: /* Cauxis */
3919 case 0x13: /* Cauxos */
3920 case 0x0B: /* Conis */
3921 case 0x10: /* Conos */
3922 case 0x08: /* Cnecin */
3923 case 0x11: /* Cprnos */
3924 case 0x07: /* Crawcin */
3925 case 0x19: /* Dgetdrv */
3926 case 0x2F: /* Fgetdta */
3927 case 0x30: /* Sversion */
3928 case 0x2A: /* Tgetdate */
3929 case 0x2C: /* Tgettime */
3930 /* commands with no args */
3931 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s() at PC 0x%X\n",
3932 GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3933 CallingPC);
3934 break;
3935
3936 case 0x02: /* Cconout */
3937 case 0x04: /* Cauxout */
3938 case 0x05: /* Cprnout */
3939 case 0x06: /* Crawio */
3940 case 0x2b: /* Tsetdate */
3941 case 0x2d: /* Tsettime */
3942 case 0x45: /* Fdup */
3943 /* commands taking single word */
3944 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s(0x%hX) at PC 0x%X\n",
3945 GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3946 STMemory_ReadWord(Params),
3947 CallingPC);
3948 break;
3949
3950 case 0x0A: /* Cconrs */
3951 case 0x1A: /* Fsetdta */
3952 case 0x48: /* Malloc */
3953 case 0x49: /* Mfree */
3954 /* commands taking long/pointer */
3955 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s(0x%X) at PC 0x%X\n",
3956 GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3957 STMemory_ReadLong(Params),
3958 CallingPC);
3959 break;
3960
3961 case 0x44: /* Mxalloc */
3962 /* commands taking long/pointer + word */
3963 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x44 Mxalloc(0x%X, 0x%hX) at PC 0x%X\n",
3964 STMemory_ReadLong(Params),
3965 STMemory_ReadWord(Params+SIZE_LONG),
3966 CallingPC);
3967 break;
3968 case 0x14: /* Maddalt */
3969 /* commands taking 2 longs/pointers */
3970 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x14 Maddalt(0x%X, 0x%X) at PC 0x%X\n",
3971 STMemory_ReadLong(Params),
3972 STMemory_ReadLong(Params+SIZE_LONG),
3973 CallingPC);
3974 break;
3975 case 0x4A: /* Mshrink */
3976 /* Mshrink's two pointers are prefixed by reserved zero word:
3977 * http://toshyp.atari.org/en/00500c.html#Bindings_20for_20Mshrink
3978 */
3979 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4A Mshrink(0x%X, 0x%X) at PC 0x%X\n",
3980 STMemory_ReadLong(Params+SIZE_WORD),
3981 STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG),
3982 CallingPC);
3983 if (!bUseTos)
3984 Finished = true;
3985 break;
3986
3987 default:
3988 /* rest of commands */
3989 LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX (%s) at PC 0x%X\n",
3990 GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3991 CallingPC);
3992 }
3993
3994 switch(Finished)
3995 {
3996 case true:
3997 /* skip over branch to pexec to RTE */
3998 SR |= SR_ZERO;
3999 /* visualize GemDOS emu HD access? */
4000 switch (GemDOSCall)
4001 {
4002 case 0x36:
4003 case 0x39:
4004 case 0x3a:
4005 case 0x3b:
4006 case 0x3c:
4007 case 0x3d:
4008 case 0x3e:
4009 case 0x3f:
4010 case 0x40:
4011 case 0x41:
4012 case 0x42:
4013 case 0x43:
4014 case 0x47:
4015 case 0x4e:
4016 case 0x4f:
4017 case 0x56:
4018 Statusbar_EnableHDLed( LED_STATE_ON );
4019 }
4020 break;
4021 case CALL_PEXEC_ROUTINE:
4022 /* branch to pexec, then redirect to old gemdos. */
4023 SR |= SR_OVERFLOW;
4024 break;
4025 case false:
4026 if (!bUseTos)
4027 {
4028 if (GemDOSCall >= 0x58) /* Ignore optional calls */
4029 {
4030 SR |= SR_ZERO;
4031 Regs[REG_D0] = GEMDOS_EINVFN;
4032 break;
4033 }
4034 Log_Printf(LOG_FATAL, "GEMDOS 0x%02hX %s at PC 0x%X unsupported in test mode\n",
4035 GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
4036 CallingPC);
4037 Main_SetQuitValue(1);
4038 }
4039 break;
4040 }
4041
4042 M68000_SetSR(SR); /* update the flags in the SR register */
4043 }
4044
4045
4046 /*-----------------------------------------------------------------------*/
4047 /**
4048 * GemDOS_Boot - routine called on the first occurrence of the gemdos opcode.
4049 * (this should be in the cartridge bootrom)
4050 * Sets up our gemdos handler (or, if we don't need one, just turn off keyclicks)
4051 */
GemDOS_Boot(void)4052 void GemDOS_Boot(void)
4053 {
4054 if (bInitGemDOS)
4055 GemDOS_Reset();
4056
4057 bInitGemDOS = true;
4058
4059 LOG_TRACE(TRACE_OS_GEMDOS, "Gemdos_Boot() at PC 0x%X\n", M68000_GetPC() );
4060
4061 /* install our gemdos handler, if user has enabled either
4062 * GEMDOS HD, autostarting or GEMDOS tracing
4063 */
4064 if (!GEMDOS_EMU_ON &&
4065 !INF_Overriding(AUTOSTART_INTERCEPT) &&
4066 !(LogTraceFlags & (TRACE_OS_GEMDOS|TRACE_OS_BASE)))
4067 return;
4068
4069 /* Get the address of the p_run variable that points to the actual basepage */
4070 if (TosVersion == 0x100)
4071 {
4072 /* We have to use fix addresses on TOS 1.00 :-( */
4073 if ((STMemory_ReadWord(TosAddress+28)>>1) == 4)
4074 act_pd = 0x873c; /* Spanish TOS is different from others! */
4075 else
4076 act_pd = 0x602c;
4077 }
4078 else
4079 {
4080 Uint32 osAddress = STMemory_ReadLong(0x4f2);
4081 act_pd = STMemory_ReadLong(osAddress + 0x28);
4082 }
4083
4084 /* Save old GEMDOS handler address */
4085 STMemory_WriteLong(CART_OLDGEMDOS, STMemory_ReadLong(0x0084));
4086 /* Setup new GEMDOS handler, see "cart_asm.s" */
4087 STMemory_WriteLong(0x0084, CART_GEMDOS);
4088 }
4089
4090
4091 /**
4092 * Load and relocate a PRG file into the memory of the emulated machine.
4093 */
GemDOS_LoadAndReloc(const char * psPrgName,uint32_t baseaddr)4094 int GemDOS_LoadAndReloc(const char *psPrgName, uint32_t baseaddr)
4095 {
4096 long nFileSize, nRelTabIdx;
4097 uint8_t *prg;
4098 uint32_t nTextLen, nDataLen, nBssLen, nSymLen;
4099 uint32_t nRelOff, nCurrAddr;
4100 uint32_t memtop;
4101
4102 prg = File_Read(psPrgName, &nFileSize, NULL);
4103 if (!prg || nFileSize < 30)
4104 {
4105 Log_Printf(LOG_ERROR, "Failed to load '%s'.\n", psPrgName);
4106 return -1;
4107 }
4108
4109 if (prg[0] != 0x60 || prg[1] != 0x1a) /* Check PRG magic */
4110 {
4111 Log_Printf(LOG_ERROR, "The file '%s' is not a valid PRG.\n", psPrgName);
4112 return -1;
4113 }
4114
4115 nTextLen = (prg[2] << 24) | (prg[3] << 16) | (prg[4] << 8) | prg[5];
4116 nDataLen = (prg[6] << 24) | (prg[7] << 16) | (prg[8] << 8) | prg[9];
4117 nBssLen = (prg[10] << 24) | (prg[11] << 16) | (prg[12] << 8) | prg[13];
4118 nSymLen = (prg[14] << 24) | (prg[15] << 16) | (prg[16] << 8) | prg[17];
4119
4120 memtop = STMemory_ReadLong(0x436);
4121 if (baseaddr + 0x100 + nTextLen + nDataLen + nBssLen > memtop)
4122 {
4123 Log_Printf(LOG_ERROR, "Program too large: '%s'.\n", psPrgName);
4124 return -1;
4125 }
4126
4127 if (!STMemory_SafeCopy(baseaddr + 0x100, prg + 28, nTextLen + nDataLen, psPrgName))
4128 return -1;
4129
4130 /* Clear BSS */
4131 if (!STMemory_SafeClear(baseaddr + 0x100 + nTextLen + nDataLen, nBssLen))
4132 {
4133 Log_Printf(LOG_ERROR, "Failed to clear BSS for '%s'.\n", psPrgName);
4134 return -1;
4135 }
4136
4137 /* Set up basepage - note: some of these values are rather dummies */
4138 STMemory_WriteLong(baseaddr, baseaddr); /* p_lowtpa */
4139 STMemory_WriteLong(baseaddr + 4, memtop); /* p_hitpa */
4140 STMemory_WriteLong(baseaddr + 8, baseaddr + 0x100); /* p_tbase */
4141 STMemory_WriteLong(baseaddr + 12, nTextLen); /* p_tlen */
4142 STMemory_WriteLong(baseaddr + 16, baseaddr + 0x100 + nTextLen); /* p_dbase */
4143 STMemory_WriteLong(baseaddr + 20, nDataLen); /* p_dlen */
4144 STMemory_WriteLong(baseaddr + 24, baseaddr + 0x100 + nTextLen + nDataLen); /* p_bbase */
4145 STMemory_WriteLong(baseaddr + 28, nBssLen); /* p_blen */
4146 STMemory_WriteLong(baseaddr + 32, baseaddr + 0x80); /* p_dta */
4147 STMemory_WriteLong(baseaddr + 36, baseaddr); /* p_parent */
4148 STMemory_WriteLong(baseaddr + 40, 0); /* p_reserved */
4149 /* The environment should point to an empty string - use p_reserved for that: */
4150 STMemory_WriteLong(baseaddr + 44, baseaddr + 40); /* p_env */
4151
4152 if (*(uint16_t *)&prg[26] != 0) /* No reloc information available? */
4153 return 0;
4154
4155 nRelTabIdx = 0x1c + nTextLen + nDataLen + nSymLen;
4156 if (nRelTabIdx > nFileSize - 3)
4157 {
4158 Log_Printf(LOG_ERROR, "Can not parse relocation table of '%s'.\n", psPrgName);
4159 return -1;
4160 }
4161 nRelOff = (prg[nRelTabIdx] << 24) | (prg[nRelTabIdx + 1] << 16)
4162 | (prg[nRelTabIdx + 2] << 8) | prg[nRelTabIdx + 3];
4163
4164 if (nRelOff == 0)
4165 return 0;
4166
4167 nCurrAddr = baseaddr + 0x100 + nRelOff;
4168 STMemory_WriteLong(nCurrAddr, STMemory_ReadLong(nCurrAddr) + baseaddr + 0x100);
4169 nRelTabIdx += 4;
4170
4171 while (nRelTabIdx < nFileSize && prg[nRelTabIdx])
4172 {
4173 if (prg[nRelTabIdx] == 1)
4174 {
4175 nRelOff += 254;
4176 nRelTabIdx += 1;
4177 continue;
4178 }
4179 nRelOff += prg[nRelTabIdx];
4180 nCurrAddr = baseaddr + 0x100 + nRelOff;
4181 STMemory_WriteLong(nCurrAddr, STMemory_ReadLong(nCurrAddr) + baseaddr + 0x100);
4182 nRelTabIdx += 1;
4183 }
4184
4185 if (nRelTabIdx >= nFileSize)
4186 {
4187 Log_Printf(LOG_ERROR, "Failed to parse relocation table of '%s'.\n", psPrgName);
4188 return -1;
4189 }
4190
4191 return 0;
4192 }
4193