Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
hdos.c
Go to the documentation of this file.
1 /* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator
2  Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
3  Copyright (C)2016-2022 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
18 
19 #include "xemu/emutools.h"
20 #include "xemu/cpu65.h"
21 #define XEMU_MEGA65_HDOS_H_ALLOWED
22 #include "hypervisor.h"
23 #include "hdos.h"
24 #include "memory_mapper.h"
25 #include "io_mapper.h"
26 #include "sdcard.h"
27 
28 #include <fcntl.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 
33 // This source is meant to virtualize Hyppo's DOS functions to be able to access
34 // the host OS (what runs Xemu) filesystem via the normal HDOS calls (ie what
35 // would see the sd-card otherwise).
36 
37 //#define DEBUGHDOS(...) DEBUG(__VA_ARGS__)
38 #define DEBUGHDOS(...) DEBUGPRINT(__VA_ARGS__)
39 
40 static struct {
42  const char *func_name;
45  // The following ones are ONLY used in virtualized functions originated from hdos_enter()
47  char setname_fn[64];
48  const char *rootdir; // pointer to malloc'ed string. ALWAYS ends with directory separator of your host OS!
49  char *cwd; // ALWAYS ends with directory separator of your host OS! HDOS cwd with _FULL_ path of your host OS!
50  int cwd_is_root; // boolean: cwd of HDOS is in the emulated root directory (meaning: strings cwd and rootdir are the same)
51  int do_virt;
52  int error_code; // last error code
54 } hdos;
55 
56 static int hdos_init_is_done = 0;
57 
58 #define HDOS_DESCRIPTORS 4
59 
60 static struct {
61  union {
62  int fd;
64  };
65  enum { HDOS_DESC_CLOSED, HDOS_DESC_FILE, HDOS_DESC_DIR } status;
66  char *basedirpath; // pointer to malloc'ed string (when in use). ALWAYS ends with directory separator of your host OS!
68 } desc_table[HDOS_DESCRIPTORS];
69 
70 #define HDOSERR_INVALID_ADDRESS 0x10
71 #define HDOSERR_FILE_NOT_FOUND 0x88
72 #define HDOSERR_INVALID_DESC 0x89
73 #define HDOSERR_IS_DIRECTORY 0x86
74 #define HDOSERR_NOT_DIRECTORY 0x87
75 #define HDOSERR_IMAGE_WRONG_LEN 0x8A
76 #define HDOSERR_TOO_MANY_OPEN 0x84
77 #define HDOSERR_NO_SUCH_DISK 0x80
78 // Some questionable choice in Xemu as error code:
79 #define HDOSERR_END_DIR 0x85 // invalid cluster, it is returned by readdir if trying to read beyond end of directory
80 #define HDOSERR_CANNOT_OPEN_DIR HDOSERR_FILE_NOT_FOUND
81 
82 
83 
84 static int copy_mem_from_user ( Uint8 *target, int max_size, const int terminator_byte, unsigned int source_cpu_addr )
85 {
86  int len = 0;
87  if (terminator_byte >= 0)
88  max_size--;
89  for (;;) {
90  // DOS calls should not have user specified data >= $8000!
91  if (source_cpu_addr >= 0x8000)
92  return -1;
93  const Uint8 byte = memory_debug_read_cpu_addr(source_cpu_addr++);
94  *target++ = byte;
95  len++;
96  if (len >= max_size) {
97  if (terminator_byte >= 0)
98  *target = terminator_byte;
99  break;
100  }
101  if ((int)byte == terminator_byte)
102  break;
103  }
104  return len;
105 }
106 
107 
108 static int copy_string_from_user ( char *target, const int max_size, unsigned int source_cpu_addr )
109 {
110  return copy_mem_from_user((Uint8*)target, max_size, 0, source_cpu_addr);
111 }
112 
113 
114 static int copy_mem_to_user ( unsigned int target_cpu_addr, const Uint8 *source, int size )
115 {
116  int len = 0;
117  while (size) {
118  if (target_cpu_addr >= 0x8000)
119  return -1;
120  memory_debug_write_cpu_addr(target_cpu_addr++, *source++);
121  size--;
122  len++;
123  }
124  return len;
125 }
126 
127 
128 #ifdef TRAP_XEMU
129 #include "xemu/emutools_config.h"
130 // though not so much HDOS specfific, it's still Xemu related (Xemu's own trap handler)
131 // so we put it into hdos.c ...
132 
133 static void reconstruct_commandline ( char *p, unsigned int max_size )
134 {
135  int argc;
136  char **argv;
137  xemucfg_get_cli_info(NULL, &argc, &argv);
138  *p = '\0';
139  for (int a = 0; a < argc; a++) {
140  if (strlen(p) + strlen(argv[a]) >= max_size - 2)
141  return;
142  strcat(p, argv[a]);
143  if (a < argc - 1)
144  strcat(p, " ");
145  }
146 }
147 
148 void trap_for_xemu ( const int func_no )
149 {
150  switch (func_no) { // A register = function code
151  case 0x00: // Function $00: identify emulation
152  // On real hw, A=$FF, carry is clear on return, in emulation carry is set, and the above id values are returned in regs)
153  // It must be these identify values even for a future non-Xemu (?) MEGA65 emulator though! The real emulator info get
154  // function ($01) can be used to really tell the emulator name, version, etc.
155  D6XX_registers[0x40] = 'X'; // -> A [uppercase ascii!]
156  D6XX_registers[0x41] = 'e'; // -> X [lowercase ascii!]
157  D6XX_registers[0x42] = 'm'; // -> Y [lowercase ascii!]
158  D6XX_registers[0x43] = 'U'; // -> Z [uppercase ascii!]
159  break;
160  case 0x01: { // Function $01: get emulator textual information, subfunction = Z, memory pointer X(low-byte)/Y(hi-byte) [buffer must fit in the low 32K of CPU addr space]
161  const char *res = "";
162  char work[0x100];
163  switch (D6XX_registers[0x43]) {
164  case 0: // Z = 0: emulator base name (Xemu in our case, can be different for other emulators)
165  res = "Xemu";
166  break;
167  case 1: // version-kind-of-information
168  res = XEMU_BUILDINFO_CDATE;
169  break;
170  case 2: // git information
171  res = XEMU_BUILDINFO_GIT;
172  break;
173  case 3: // host-OS information
174  res = xemu_get_uname_string();
175  break;
176  case 4: // detailed info about hyppo (the real one, not Xemu's extensions)
177  res = hyppo_version_string;
178  break;
179  case 5: // executable name of Xemu
180  xemucfg_get_cli_info(&res, NULL, NULL);
181  break;
182  case 6: // CLI parameters of Xemu
183  reconstruct_commandline(work, sizeof work);
184  res = work;
185  break;
186  case 7: { // get mount info on drive-0
187  int i;
188  res = sdcard_get_mount_info(0, &i);
189  D6XX_registers[0x43] = !!i; // passes back "internal" boolean value in Z!
190  }
191  break;
192  case 8: { // get mount info on drive-1
193  int i;
194  res = sdcard_get_mount_info(1, &i);
195  D6XX_registers[0x43] = !!i; // passes back "internal" boolean value in Z!
196  }
197  break;
198  default:
199  break;
200  }
201  int len = strlen(res) + 1;
202  if (len >= sizeof work) {
203  D6XX_registers[0x40] = HDOSERR_INVALID_ADDRESS; // well, it should be another kind of error, but anyway ...
204  goto error_some;
205  }
206  for (int a = 0; a < len; a++) {
207  //work[a] = *res;
208  work[a] = (*res >= 'a' && *res <= 'z') ? *res - 32 : *res;
209  res++;
210  }
211  if (copy_mem_to_user(D6XX_registers[0x41] + (D6XX_registers[0x42] << 8), (const Uint8*)work, len) <= 0) {
213  goto error_some;
214  }
215  }
216  break;
217  default:
218  goto error_nofunc;
219  }
220  D6XX_registers[0x47] |= CPU65_PF_C; // signal OK status with setting carry
221  return;
222 error_nofunc:
223  D6XX_registers[0x40] = 0xFF; // set A register to 0xFF as error code
224 error_some:
225  D6XX_registers[0x47] &= ~CPU65_PF_C; // clear carry (error!)
226 }
227 #endif
228 
229 
230 static const char *hdos_get_func_name ( const int func_no )
231 {
232  if (XEMU_UNLIKELY((func_no & 1) || (unsigned int)func_no >= 0x80U)) {
233  FATAL("%s(%d) invalid DOS trap function number", __func__, func_no);
234  return "?";
235  }
236  // DOS function names are from hyppo's asm source from mega65-core repository
237  // It should be updated if there is a change there. Also maybe at other parts of
238  // this source too, to follow the ABI of the Hyppo-DOS here too.
239  static const char INVALID_SUBFUNCTION[] = "INVALID_DOS_FUNC";
240  static const char *func_names[] = {
241  "getversion", // 00
242  "getdefaultdrive", // 02
243  "getcurrentdrive", // 04
244  "selectdrive", // 06
245  "getdisksize [UNIMPLEMENTED]", // 08
246  "getcwd [UNIMPLEMENTED]", // 0A
247  "chdir", // 0C
248  "mkdir [UNIMPLEMENTED]", // 0E
249  "rmdir [UNIMPLEMENTED]", // 10
250  "opendir", // 12
251  "readdir", // 14
252  "closedir", // 16
253  "openfile", // 18
254  "readfile", // 1A
255  "writefile [UNIMPLEMENTED]", // 1C
256  "mkfile [WIP]", // 1E
257  "closefile", // 20
258  "closeall", // 22
259  "seekfile [UNIMPLEMENTED]", // 24
260  "rmfile [UNIMPLEMENTED]", // 26
261  "fstat [UNIMPLEMENTED]", // 28
262  "rename [UNIMPLEMENTED]", // 2A
263  "filedate [UNIMPLEMENTED]", // 2C
264  "setname", // 2E
265  "findfirst", // 30
266  "findnext", // 32
267  "findfile", // 34
268  "loadfile", // 36
269  "geterrorcode", // 38
270  "setup_transfer_area", // 3A
271  "cdrootdir", // 3C
272  "loadfile_attic", // 3E
273  "d81attach0", // 40
274  "d81detach", // 42
275  "d81write_en", // 44
276  "d81attach1", // 46
277  "get_proc_desc", // 48
278  INVALID_SUBFUNCTION, // 4A
279  INVALID_SUBFUNCTION, // 4C
280  INVALID_SUBFUNCTION, // 4E
281  "gettasklist [UNIMPLEMENTED]", // 50
282  "sendmessage [UNIMPLEMENTED]", // 52
283  "receivemessage [UNIMPLEMENTED]", // 54
284  "writeintotask [UNIMPLEMENTED]", // 56
285  "readoutoftask [UNIMPLEMENTED]", // 58
286  INVALID_SUBFUNCTION, // 5A
287  INVALID_SUBFUNCTION, // 5C
288  INVALID_SUBFUNCTION, // 5E
289  "terminateothertask [UNIMPLEMENTED]", // 60
290  "create_task_native [UNIMPLEMENTED]", // 62
291  "load_into_task [UNIMPLEMENTED]", // 64
292  "create_task_c64 [UNIMPLEMENTED]", // 66
293  "create_task_c65 [UNIMPLEMENTED]", // 68
294  "exit_and_switch_to_task [UNIMPLEMENTED]", // 6A
295  "switch_to_task [UNIMPLEMENTED]", // 6C
296  "exit_task [UNIMPLEMENTED]", // 6E
297  "trap_task_toggle_rom_writeprotect", // 70
298  "trap_task_toggle_force_4502", // 72
299  "trap_task_get_mapping", // 74
300  "trap_task_set_mapping", // 76
301  INVALID_SUBFUNCTION, // 78
302  INVALID_SUBFUNCTION, // 7A
303  "trap_serial_monitor_write", // 7C
304  "reset_entry" // 7E
305  };
306  return func_names[func_no >> 1];
307 }
308 
309 
310 static int find_empty_desc_tab_slot ( void )
311 {
312  for (int a = 0; a < HDOS_DESCRIPTORS; a++)
313  if (desc_table[a].status == HDOS_DESC_CLOSED)
314  return a;
315  return -1;
316 }
317 
318 
319 static int close_desc ( unsigned int entry )
320 {
321  if (entry >= HDOS_DESCRIPTORS)
322  return -1;
323  if (desc_table[entry].basedirpath) {
324  free(desc_table[entry].basedirpath);
325  desc_table[entry].basedirpath = NULL;
326  }
327  if (desc_table[entry].status == HDOS_DESC_FILE) {
328  const int ret = close(desc_table[entry].fd);
329  desc_table[entry].status = HDOS_DESC_CLOSED;
330  DEBUGHDOS("HDOS: closing file descriptor #$%02X: %d" NL, entry, ret);
331  if (ret)
332  return -1; // error value??
333  return 0;
334  } else if (desc_table[entry].status == HDOS_DESC_DIR) {
335  const int ret = xemu_os_closedir(desc_table[entry].dirp);
336  desc_table[entry].status = HDOS_DESC_CLOSED;
337  DEBUGHDOS("HDOS: closing directory descriptor #$%02X: %d" NL, entry, ret);
338  if (ret)
339  return -1; // error value??
340  return 0;
341  } else if (desc_table[entry].status == HDOS_DESC_CLOSED) {
342  return -1; // already closed?
343  }
344  FATAL("HDOS: %s() trying to close desc with unknown status!", __func__);
345  return -1;
346 }
347 
348 
349 // This is not an easy task, as:
350 // * we must deal with FS case insensivity madness (also on Host-OS side, lame Windows thing ...)
351 // * opening by "short file name"
352 // So at the end we need a directory scan unfortunately to be really sure we found the thing we want ...
353 static int try_open ( const char *basedirfn, const char *needfn, int open_mode, struct stat *st, char *fullpathout, void *result )
354 {
355  if (strchr(needfn, '/') || strchr(needfn, '\\') || !*needfn) // needfn could not contain directory component(s) and also cannot be empty
356  return HDOSERR_FILE_NOT_FOUND;
357  XDIR *dirp = xemu_os_opendir(basedirfn);
358  if (!dirp)
359  return HDOSERR_FILE_NOT_FOUND;
360  char fn_found[FILENAME_MAX];
361  while (!xemu_os_readdir(dirp, fn_found)) {
362  // TODO: add check for "." and ".." not done in root of emulated HDOS FS
363  if (!strcasecmp(fn_found, needfn) && strlen(fn_found) <= 63)
364  goto found;
365  }
366  // TODO: open by shortname!!!
367  // reason: first it should be tried with all the long file names.
368  // FIXME: however maybe this is NOT what real hyppo would do, but note, in case of
369  // real hyppo and sd-card it has access to _real_ short names, etc ...
370 #if 0
371  xemu_os_rewinddir(dirp);
372  // try again with short name policy now ...
373  while ((entry = xemu_os_readdir(dirp, &entry_storage))) {
374 
375  }
376 #endif
378  return HDOSERR_FILE_NOT_FOUND;
379 found:
380  strcpy(fullpathout, basedirfn);
381  if (fullpathout[strlen(fullpathout) - 1] != DIRSEP_CHR)
382  strcat(fullpathout, DIRSEP_STR);
383  strcat(fullpathout, fn_found);
384  if (xemu_os_stat(fullpathout, st))
385  return HDOSERR_FILE_NOT_FOUND;
386  const int type = st->st_mode & S_IFMT;
387  if (open_mode == -1) { // -1 as "open_mode" func argument means: open as a DIRECTORY
388  if (type != S_IFDIR)
389  return HDOSERR_NOT_DIRECTORY;
390  dirp = xemu_os_opendir(fullpathout);
391  if (!dirp)
392  return HDOSERR_FILE_NOT_FOUND;
393  strcat(fullpathout, DIRSEP_STR);
394  *(XDIR**)result = dirp;
395  } else { // if "open_mode" as not -1, then it means some kind of flags for open(), opening as a file
396  if (type != S_IFREG)
397  return HDOSERR_IS_DIRECTORY;
398  int fd = xemu_os_open(fullpathout, open_mode);
399  if (fd < 0)
400  return HDOSERR_FILE_NOT_FOUND;
401  *(int*)result = fd;
402  }
403  return 0;
404 }
405 
406 
407 static void hdos_virt_mount ( const int unit )
408 {
409  hdos.func_is_virtualized = 1;
410  int fd;
411  char fullpath[PATH_MAX + 1];
412  struct stat st;
413  const int ret = try_open(hdos.cwd, hdos.setname_fn, O_RDWR, &st, fullpath, &fd);
414  if (ret) {
415  DEBUGHDOS("HDOS: VIRT: >> mount << would fail on try_open() = %d!" NL, ret);
416  hdos.virt_out_a = ret; // pass back the error code got from try_open()
417  return;
418  }
419  xemu_os_close(fd); // we don't need it anymore
420  if (sdcard_force_external_mount(unit, fullpath, NULL)) {
421  DEBUGHDOS("HDOS: VIRT: mount of image \"%s\" on unit %d FAILED :(" NL, fullpath, unit);
422  hdos.virt_out_a = HDOSERR_IMAGE_WRONG_LEN; // this is probably the only reason the mount call could fail
423  return;
424  }
425  // it seems everything went out fine :D
426  DEBUGHDOS("HDOS: VIRT: mount of image \"%s\" on unit %d went well." NL, fullpath, unit);
427  hdos.virt_out_carry = 1; // signal OK status
428 }
429 
430 
431 static void hdos_virt_opendir ( void )
432 {
433  hdos.func_is_virtualized = 1;
434  const int e = find_empty_desc_tab_slot();
435  if (e < 0) {
436  hdos.virt_out_a = HDOSERR_TOO_MANY_OPEN;
437  return;
438  }
439  XDIR *dirp = xemu_os_opendir(hdos.cwd);
440  if (!dirp) {
441  hdos.virt_out_a = HDOSERR_CANNOT_OPEN_DIR; // some error code; directory cannot be open (this SHOULD not happen though!)
442  return;
443  }
444  desc_table[e].basedirpath = xemu_strdup(hdos.cwd);
445  desc_table[e].status = HDOS_DESC_DIR;
446  desc_table[e].dirp = dirp;
447  desc_table[e].dir_entry_no = 0;
448  hdos.virt_out_a = e; // return the file descriptor
449  hdos.virt_out_carry = 1; // signal OK status
450 }
451 
452 
453 static void hdos_virt_close_dir_or_file ( void )
454 {
455  hdos.func_is_virtualized = 1;
456  if (close_desc(hdos.in_x))
457  hdos.virt_out_a = HDOSERR_INVALID_DESC;
458  else
459  hdos.virt_out_carry = 1; // signal OK status
460 }
461 
462 
463 static void hdos_virt_readdir ( void )
464 {
465  hdos.func_is_virtualized = 1;
466  if (hdos.in_x >= HDOS_DESCRIPTORS || desc_table[hdos.in_x].status != HDOS_DESC_DIR) {
467  hdos.virt_out_a = HDOSERR_INVALID_DESC;
468  return;
469  }
470  if (hdos.in_y >= 0x80) {
471  hdos.virt_out_a = HDOSERR_INVALID_ADDRESS;
472  return;
473  }
474  Uint8 mem[87];
475  char fn_found[FILENAME_MAX];
476  // FIXME: remove this? it seems, Hyppo never returns with the volume label anyway
477 #if 0
478  if (in_emu_root && desc_table[hdos.in_x].dir_entry_no == 0) {
479  // fake a volume label as the response, in case of emulation as root dir
480  memset(mem, 0, sizeof mem);
481  static const char volume_name[] = "XEMU-VRT";
482  memcpy(mem, volume_name, strlen(volume_name));
483  memcpy(mem + 65, volume_name, strlen(volume_name));
484  mem[64] = strlen(volume_name);
485  mem[86] = 8; // type: volume label
486  desc_table[hdos.in_x].dir_entry_no = 1;
487  if (copy_mem_to_user(hdos.in_y << 8, mem, sizeof mem) != sizeof mem)
488  hdos.virt_out_a = HDOSERR_INVALID_ADDRESS;
489  else
490  hdos.virt_out_carry = 1; // signal OK status
491  return;
492  }
493 #endif
494 readdir_again:
495  if (xemu_os_readdir(desc_table[hdos.in_x].dirp, fn_found)) {
496  // FIXME: there should be error checking here, but we assume for now, that NULL means end of directory
497  // But anyway, what should we do in case of an error during readdir? Not so much ...
498  DEBUGHDOS("HDOS: VIRT: %s(): end-of-directory" NL, __func__);
499  hdos.virt_out_a = HDOSERR_END_DIR;
500  return;
501  }
502  desc_table[hdos.in_x].dir_entry_no++;
503  memset(mem, 0, sizeof mem); // pre-fill with zero for our whole buffer
504  memset(mem + 65, 0x20, 8 + 3); // pre-fill with spaces for the short name!
505  if (fn_found[0] == '.') {
506  if (fn_found[1] == '\0' || (fn_found[1] == '.' && fn_found[2] == '\0')) {
507  if (hdos.cwd_is_root)
508  goto readdir_again; // if emulated as root dir, do not pass back "." and ".."
509  else {
510  // . or .. is accepted, however it would cause problems with name resolution, so let's do it at our own for this case
511  mem[64] = fn_found[1] ? 2 : 1; // fn length: 1 or 2 ("." or "..")
512  memcpy(mem, fn_found, mem[64]);
513  memcpy(mem + 65, fn_found, mem[64]);
514  }
515  } else
516  goto readdir_again; // entry names starting with '.' (other than '.' and '..' handled above!) can be problematic, let's ignore them!
517  } else {
518  // Copy, check (length and chars) and convert filename
519  for (Uint8 *s = (Uint8*)fn_found, *t = mem, *sn = mem + 65; *s; s++, t++, mem[64]++) {
520  if (mem[64] >= 63)
521  goto readdir_again; // skip file: too long name
522  Uint8 c = *s;
523  if (c < 0x20 || c >= 0x80)
524  goto readdir_again; // skip file: contains invalid character (as considered by us, at least) Also catches UTF-8 sequences, fortunately
525  if (c >= 'a' && c <= 'z')
526  c -= 32;
527  *t = c;
528  // For the faked 'short name' part of the story ... This is a very faulty and bad algorithm, but who cares about short names ;)
529  // It would be exteremly hard to do correctly, anyway ...
530  if (c == '.') {
531  sn = mem + 65 + 8;
532  memset(sn, 0x20, 3);
533  } else if (sn < mem + 65 + 11)
534  *sn++ = c;
535  }
536  }
537  // Stat our file about details
538  char fn[strlen(desc_table[hdos.in_x].basedirpath) + strlen(fn_found) + 1];
539  sprintf(fn, "%s%s", desc_table[hdos.in_x].basedirpath, fn_found);
540  struct stat st;
541  if (xemu_os_stat(fn, &st))
542  goto readdir_again;
543  const int type = st.st_mode & S_IFMT;
544  if (type != S_IFDIR && type != S_IFREG) // some irregular file, we should not pass back
545  goto readdir_again;
546  if (type == S_IFREG && st.st_size >= 0x80000000U) // do not pass back crazily large file
547  goto readdir_again;
548  // -- OK, directory entry - finally - seems to be OK to pass back as the result --
549  DEBUGHDOS("HDOS: VIRT: %s(): accepted filename = \"%s\"" NL, __func__, fn_found);
550  const unsigned int fake_start_cluster = desc_table[hdos.in_x].dir_entry_no + 0x10; // don't use too low cluster numbers
551  mem[78] = fake_start_cluster & 0xFF;
552  mem[79] = (fake_start_cluster >> 8) & 0xFF;
553  mem[80] = (fake_start_cluster >> 16) & 0xFF;
554  mem[80] = (fake_start_cluster >> 24) & 0xFF;
555  // File type: set "subdir" bit, if entry is a directory
556  if (type == S_IFDIR) {
557  mem[86] = 0x10;
558  // not so critical but let's present size zero if it's a directory (also size got by stat() on directories varies
559  // a lot in meaning on Windows or UNIX'es, so let's keep a consistent behaviour across Xemu platforms!)
560  st.st_size = 0;
561  }
562  // File size in bytes
563  mem[82] = st.st_size & 0xFF;
564  mem[83] = (st.st_size >> 8) & 0xFF;
565  mem[84] = (st.st_size >> 16) & 0xFF;
566  mem[85] = (st.st_size >> 24) & 0xFF;
567  // Copy the answer to the memory of the caller requested
568  if (copy_mem_to_user(hdos.in_y << 8, mem, sizeof mem) != sizeof mem) {
569  hdos.virt_out_a = HDOSERR_INVALID_ADDRESS;
570  return;
571  }
572  hdos.virt_out_carry = 1; // signal OK status
573 }
574 
575 
576 static void hdos_virt_cdroot ( void )
577 {
578  hdos.func_is_virtualized = 1;
579  if (!hdos.cwd_is_root) {
580  strcpy(hdos.cwd, hdos.rootdir); // cwd malloced area must be enough, since this is the shortest possible data to put in
581  hdos.cwd_is_root = 1;
582  }
583  hdos.virt_out_carry = 1; // signal OK status
584 }
585 
586 
587 static void hdos_virt_cd ( void )
588 {
589  hdos.func_is_virtualized = 1;
590  if (hdos.setname_fn[0] == '.') {
591  if (hdos.setname_fn[1] == '\0') {
592  // '.': in this case we don't need to do anything too much ...
593  hdos.virt_out_carry = 1; // signal OK status
594  return;
595  }
596  if (hdos.setname_fn[1] == '.' && hdos.setname_fn[2] == '\0') {
597  // '..': "cd-up" situation
598  if (hdos.cwd_is_root) {
599  // cannot "cd-up" from the emulated root
600  hdos.virt_out_a = HDOSERR_FILE_NOT_FOUND;
601  return;
602  }
603  // -- Otherwise, let's do the "cd-up" with manipulating 'cwd' only --
604  for (char *p = hdos.cwd + strlen(hdos.cwd) - 2;; p--)
605  if (*p == DIRSEP_CHR) {
606  p[1] = '\0';
607  break;
608  }
609  hdos.cwd_is_root = !strcmp(hdos.cwd, hdos.rootdir); // set 'is CWD root' flag now, it's possible that user cd-up'ed into the the emulated root
610  DEBUGHDOS("HDOS: VIRT: %s(\"..\"): now at \"%s\" is_root=%d" NL, __func__, hdos.cwd, hdos.cwd_is_root);
611  hdos.virt_out_carry = 1; // signal OK status
612  return;
613  }
614  // if not '.' nor '..', then we simply refuse to deal with directories starts with '.'
615  hdos.virt_out_a = HDOSERR_FILE_NOT_FOUND;
616  return;
617  }
618  // -- the rest: 'cd' into some subdir other than '.' or '..' --
619  struct stat st;
620  XDIR *dirp;
621  char pathout[PATH_MAX + 1];
622  //static int try_open ( const char *basedirfn, const char *needfn, int open_mode, struct stat *st, char *pathout, void *result );
623  int ret = try_open(hdos.cwd, hdos.setname_fn, -1, &st, pathout, &dirp);
624  if (ret) {
625  hdos.virt_out_a = ret;
626  return;
627  }
628  xemu_os_closedir(dirp); // not needed to much here, let's close it
629  strcat(pathout, DIRSEP_STR);
630  xemu_restrdup(&hdos.cwd, pathout);
631  hdos.cwd_is_root = 0; // if we managed to 'cd' into something (and it is cannot be '.' or '..' at this point!) we cannot be in the root anymore!
632  hdos.virt_out_carry = 1; // signal OK status
633 }
634 
635 
636 #define HDOS_VIRT_HYPPO_UNIMPLEMENTED() hdos.func_is_virtualized = 1
637 #define HDOS_VIRT_XEMU_UNIMPLEMENTED() do { \
638  DEBUGPRINT("HDOS: VIRT: Unimplemented by Xemu!! %s ~ #$%02X)" NL, hdos.func_name, hdos.func); \
639  hdos.func_is_virtualized = 1; \
640  } while (0)
641 
642 
643 // Called when DOS trap is triggered.
644 // Can be used to take control (without hyppo doing it) but setting the needed register values,
645 // and calling hypervisor_leave() to avoid Hyppo to run.
646 void hdos_enter ( const Uint8 func_no )
647 {
648  if (XEMU_UNLIKELY(!hdos_init_is_done))
649  FATAL("%s() is called before HDOS subsystem init!", __func__);
650  hdos.func = func_no;
651  hdos.func_name = hdos_get_func_name(hdos.func);
652  hdos.func_is_virtualized = 0;
653  // NOTE: hdos.in_ things are the *INPUT* registers of the trap, cannot be used
654  // to override the result passing back by the trap!
655  // Here we store input register values can be even examined at the hdos_leave() stage
656  hdos.in_x = cpu65.x;
657  hdos.in_y = cpu65.y;
658  hdos.in_z = cpu65.z;
659  DEBUGHDOS("HDOS: entering function #$%02X (%s) A=$%02X X=$%02X Y=$%02X Z=$%02X" NL, hdos.func, hdos.func_name, cpu65.a, cpu65.x, cpu65.y, cpu65.z);
660  if (hdos.do_virt) {
661  // Can be overriden by virtualized functions.
662  // these virt_out stuffs won't be used otherwise, if a virtualized function does not set hdos.func_is_virtualized
663  hdos.virt_out_a = 0xFF; // set to $FF by default, override in virtualized function implementations if needed
664  hdos.virt_out_x = cpu65.x;
665  hdos.virt_out_y = cpu65.y;
666  hdos.virt_out_z = cpu65.z;
667  hdos.virt_out_carry = 0; // assumming **ERROR** (carry flag is clear) by default! Override that to '1' in virt functions if it's OK!
668  // Let's see the virualized functions.
669  // Those should set hdos.func_is_virtualized, also the hdos.virt_out_carry is operation was OK, and probably they want to
670  // set some register values as output in hdos.virt_out_a, hdos.virt_out_x, hdos.virt_out_y and hdos.virt_out_z
671  // The reason of this complicated method: the switch-case below contains the call of virtualized DOS function implementations and
672  // they can decide to NOT set hdos.func_is_virtualized conditionally, so it's even possible to have virtualized DOS file functions
673  // just for a certain directory / file names, and so on! (though it's currently not so much planned to use ...).
674  // The reason for ">> 1" everywhere: to have a continous space of switch values and allow better chance for C compiler to generate a jump-table.
675  switch (hdos.func >> 1) {
676  case 0x00 >> 1: // get version, do NOT virtualize this!
677  case 0x02 >> 1: // get default drive
678  case 0x04 >> 1: // get current drive
679  hdos.func_is_virtualized = 1;
680  hdos.virt_out_a = 0;
681  hdos.virt_out_carry = 1;
682  break;
683  case 0x06 >> 1: // select drive (we allow zero only in Xemu)
684  hdos.func_is_virtualized = 1;
685  if (hdos.in_x)
686  hdos.virt_out_a = HDOSERR_NO_SUCH_DISK;
687  else
688  hdos.virt_out_carry = 1; // OK
689  break;
690  case 0x08 >> 1: // getdisksize [NOT IMPLEMENTED]
692  break;
693  case 0x0A >> 1: // getcwd [NOT IMPLEMENTED]
695  break;
696  case 0x0C >> 1:
697  hdos_virt_cd();
698  break;
699  case 0x0E >> 1: // mkdir [NOT IMPLEMENTED]
701  break;
702  case 0x10 >> 1: // rmdir [NOT IMPLEMENETED]
704  break;
705  case 0x12 >> 1:
706  hdos_virt_opendir();
707  break;
708  case 0x14 >> 1:
709  hdos_virt_readdir();
710  break;
711  case 0x16 >> 1:
712  case 0x20 >> 1:
713  hdos_virt_close_dir_or_file();
714  break;
715  case 0x2E >> 1: // setname, do NOT virtualize this! (though we track/store result in hdos_leave) It's also great that we have hyppo's check on filename syntax, etc.
716  break;
717  case 0x38 >> 1: // get last error code
718  hdos.func_is_virtualized = 1;
719  hdos.virt_out_a = hdos.error_code;
720  hdos.virt_out_carry = 1;
721  break;
722  case 0x3A >> 1: // setup transfer area, do NOT virtualize this! (though we track/store result in hdos_leave)
723  break;
724  case 0x3C >> 1:
725  hdos_virt_cdroot();
726  break;
727  case 0x40 >> 1:
728  hdos_virt_mount(0);
729  break;
730  case 0x46 >> 1:
731  hdos_virt_mount(1);
732  break;
733  }
734  if (hdos.func_is_virtualized) {
735  D6XX_registers[0x40] = hdos.virt_out_a;
736  D6XX_registers[0x41] = hdos.virt_out_x;
737  D6XX_registers[0x42] = hdos.virt_out_y;
738  D6XX_registers[0x43] = hdos.virt_out_z;
739  if (hdos.virt_out_carry) {
740  D6XX_registers[0x47] |= CPU65_PF_C; // was OK (carry is _SET_ if OK)
741  if (hdos.func != 0x38) // FIXME: allow get last error function to querty error code more than once. Is this really needed?
742  hdos.error_code = 0; // FIXME: not sure ... it would also cause to reset last error when query on get last error code
743  } else {
744  D6XX_registers[0x47] &= ~CPU65_PF_C; // error (carry is _CLEAR_ if ERROR)
745  hdos.error_code = hdos.virt_out_a; // also remember the error code for the get last error function
746  }
747  DEBUGHDOS("HDOS: VIRT: returning %s (A=$%02X) from virtualized function #$%02X bypassing Hyppo" NL, hdos.virt_out_carry ? "OK" : "ERROR", hdos.virt_out_a, hdos.func);
748  // forced leave of hypervisor mode now, bypassing hyppo to process this DOS trap function
750  } else
751  DEBUGHDOS("HDOS: VIRT: unvirtualized DOS function #$%02X pass-through to Hyppo" NL, hdos.func);
752  }
753 }
754 
755 
756 // Called when DOS trap is leaving.
757 // Can be used to examine the result hyppo did with a call, or even do some modifications.
758 void hdos_leave ( const Uint8 func_no )
759 {
760  hdos.func = func_no;
761  hdos.func_name = hdos_get_func_name(hdos.func);
762  DEBUGHDOS("HDOS: leaving function #$%02X (%s) with carry %s (A=$%02X)" NL, hdos.func, hdos.func_name, cpu65.pf_c ? "SET" : "CLEAR", cpu65.a);
763  // if "func_is_virtualized" flag is set, we don't want to mess things up further, as it was handled before in hdos.c somewhere
764  if (hdos.func_is_virtualized) {
765  DEBUGHDOS("HDOS: VIRT: was marked as virtualized, so end of %s in %s()" NL, hdos.func_name, __func__);
766  hdos.func_is_virtualized = 0; // just to be sure, though it's set to zero on next hdos_enter()
767  return;
768  }
769  if (hdos.func == 0x2E && cpu65.pf_c) { // HDOS setnam function. Also check carry set (which means "ok" by HDOS trap)
770  // We always track this call, because we should know the actual name selected with some other calls then,
771  // since the result can be used _later_ by both of virtualized and non-virtualized functions, possibly.
772  // Let's do a local copy of the successfully selected name via hyppo (we know this by knowing that C flag is set by hyppo)
773  // FIXME: in case of error, is setname buffer modified?
774  // FIXME: is only Y used for _page_ and X ignored, also is tranfer area addr is really modified as a "side effect"?
775  char setnam_current[sizeof hdos.setname_fn];
776  // FIXME: copy routine should not fail ever if hyppo already accepted!
777  if (copy_string_from_user(hdos.setname_fn, sizeof hdos.setname_fn, hdos.in_x + (hdos.in_y << 8)) >= 0)
778  // hdos.transfer_area_address = hdos.in_y << 8; // WTF? setname has X/Y as input!
779  for (char *p = hdos.setname_fn; *p; p++) {
780  if (*p < 0x20 || *p >= 0x7F) {
781  // FIXME: this should not happen if hyppo already accepted!
782  DEBUGHDOS("HDOS: setnam(): invalid character in filename $%02X" NL, *p);
783  hdos.setname_fn[0] = '\0';
784  break;
785  }
786  if (*p >= 'A' && *p <= 'Z')
787  *p = *p - 'A' + 'a';
788  }
789  DEBUGHDOS("HDOS: %s: selected filename is [%s] from $%04X" NL, hdos.func_name, hdos.setname_fn, hdos.in_x + (hdos.in_y << 8));
790  return;
791  }
792  if (hdos.func == 0x3A && cpu65.pf_c) { // HDOS setup transfer area. We don't virtualize this, but maintain a copy of the value set as we need the pointer set
793  hdos.transfer_area_addr = hdos.in_y << 8;
794  DEBUGHDOS("HDOS: transfer area address is set to $%04X" NL, hdos.transfer_area_addr);
795  return;
796  }
797  if (hdos.func == 0x40) { // 0x40: d81attach0 TODO: later I should check if mount was OK and name was MEGA65.D81 to have special external mount then. If HDOS virt is not enabled.
798  DEBUGPRINT("HDOS: %s(\"%s\") = %s" NL, hdos.func_name, hdos.setname_fn, cpu65.pf_c ? "OK" : "FAILED");
799  if (!strcasecmp(hdos.setname_fn, "MEGA65.D81"))
800  OSD(-1, -1, "MEGA65.D81 ;-)");
801  return;
802  }
803 }
804 
805 
806 // Must be called on TRAP RESET, also causes to close all file descriptors (BTW, may be called on exit xemu,
807 // to nicely close open files/directories ...)
808 static void hdos_reset ( void )
809 {
810  DEBUGHDOS("HDOS: reset" NL);
811  hdos.setname_fn[0] = '\0';
812  hdos.func = -1;
813  hdos.error_code = 0;
814  hdos.transfer_area_addr = 0;
816 }
817 
818 
819 // implementation is here, but prototype is in hypervisor.h and not in hdos.h as you would expect!
821 {
822  if (hdos_init_is_done)
823  for (int a = 0; a < HDOS_DESCRIPTORS; a++)
824  (void)close_desc(a);
825 }
826 
827 
828 // implementation is here, but prototype is in hypervisor.h and not in hdos.h as you would expect!
829 int hypervisor_hdos_virtualization_status ( const int set, const char **root_ptr )
830 {
831  if (set >= 0) {
832  if (!!hdos.do_virt != !!set) {
833  hdos.do_virt = set;
834  DEBUGPRINT("HDOS: virtualization is now %s" NL, set ? "ENABLED" : "DISABLED");
835  }
836  }
837  if (root_ptr)
838  *root_ptr = hdos.rootdir;
839  return hdos.do_virt;
840 }
841 
842 
843 // These are called by hypervisor at the beginning and the end of the start machine state (ie, trap reset).
844 // Its intent is to tell hdos.c to do things private to hdos.c but still connected to start machine functionality.
845 
846 
848 {
849  DEBUGHDOS("HDOS: system-start-begin notification received." NL);
850  hdos_reset();
851  sdcard_notify_system_start_begin(); // sd-card/d81 subsystem notification since it will check initial in-sdcard internal d81 mount
852 }
853 
854 
856 {
857  DEBUGHDOS("HDOS: system-start-end notification recevied." NL);
858  sdcard_notify_system_start_end(); // read the comment at the similar line in function hdos_notify_system_start_begin() above
859 }
860 
861 
862 // Must be called, but **ONLY** once in Xemu's running lifetime, before using XEMU HDOS related things here!
863 void hdos_init ( const int do_virt, const char *virtroot )
864 {
865  if (hdos_init_is_done)
866  FATAL("%s() called more than once!", __func__);
867  hdos_init_is_done = 1;
868  DEBUGHDOS("HDOS: initialization with do_virt=%d and virtroot=\"%s\"" NL, do_virt, virtroot ? virtroot : "<NULL>");
869  for (int a = 0; a < HDOS_DESCRIPTORS; a++) {
870  desc_table[a].status = HDOS_DESC_CLOSED;
871  desc_table[a].basedirpath = NULL;
872  }
873  hdos_reset();
874  // HDOS virtualization related stuff
875  // First build the default path for HDOS root, also try to create, maybe hasn't existed yet.
876  // We may not using this one (but virtroot), however the default should exist.
877  char hdosdir[PATH_MAX + 1];
878  sprintf(hdosdir, "%s%s%c", sdl_pref_dir, HDOSROOT_SUBDIR_NAME, DIRSEP_CHR);
879  MKDIR(hdosdir);
880  XDIR *dirp = xemu_os_opendir(hdosdir);
881  if (!dirp)
882  FATAL("Cannot open default HDOS root: %s", hdosdir);
884  if (virtroot && *virtroot) { // now we may want to override the default, if it's given at all
885  XDIR *dirp = xemu_os_opendir(virtroot); // try to open directory just for testing
886  if (dirp) {
887  // seems to work! use the virtroot path but prepare it for having directory separator as the last character
889  strcpy(hdosdir, virtroot);
890  if (hdosdir[strlen(hdosdir) - 1] != DIRSEP_CHR)
891  strcat(hdosdir, DIRSEP_STR);
892  } else
893  ERROR_WINDOW("HDOS: bad HDOS virtual directory given (cannot open as directory): %s\nUsing the default instead: %s", virtroot, hdosdir);
894  }
895  hdos.rootdir = xemu_strdup(hdosdir); // populate the result of HDOS virtualization root directory
896  hdos.cwd = xemu_strdup(hdosdir); // also for cwd (current working directory)
897  hdos.cwd_is_root = 1;
898  hdos.do_virt = do_virt; // though, virtualization itself can be turned on/off by this, still
899  DEBUGPRINT("HDOS: virtualization is %s, root = \"%s\"" NL, hdos.do_virt ? "ENABLED" : "DISABLED", hdos.rootdir);
900 }
DEBUGHDOS
#define DEBUGHDOS(...)
Definition: hdos.c:38
virt_out_x
Uint8 virt_out_x
Definition: hdos.c:46
sdcard_get_mount_info
const char * sdcard_get_mount_info(const int unit, int *is_internal)
Definition: sdcard.c:917
virt_out_a
Uint8 virt_out_a
Definition: hdos.c:46
hdos_init
void hdos_init(const int do_virt, const char *virtroot)
Definition: hdos.c:863
cwd_is_root
int cwd_is_root
Definition: hdos.c:50
in_x
Uint8 in_x
Definition: hdos.c:44
emutools.h
sdcard_force_external_mount
int sdcard_force_external_mount(const int unit, const char *filename, const char *cry)
Definition: sdcard.c:925
HDOSERR_IMAGE_WRONG_LEN
#define HDOSERR_IMAGE_WRONG_LEN
Definition: hdos.c:75
hypervisor_hdos_close_descriptors
void hypervisor_hdos_close_descriptors(void)
Definition: hdos.c:820
virt_out_carry
Uint8 virt_out_carry
Definition: hdos.c:46
sdcard_notify_system_start_begin
void sdcard_notify_system_start_begin(void)
Definition: sdcard.c:778
hdos_enter
void hdos_enter(const Uint8 func_no)
Definition: hdos.c:646
fd
int fd
Definition: hdos.c:62
xemu_os_closedir
#define xemu_os_closedir
Definition: emutools.h:269
HDOS_DESCRIPTORS
#define HDOS_DESCRIPTORS
Definition: hdos.c:58
virt_out_y
Uint8 virt_out_y
Definition: hdos.c:46
dir_entry_no
int dir_entry_no
Definition: hdos.c:67
io_mapper.h
in_z
Uint8 in_z
Definition: hdos.c:44
HDOSERR_TOO_MANY_OPEN
#define HDOSERR_TOO_MANY_OPEN
Definition: hdos.c:76
fn
const char * fn
Definition: roms.c:42
XEMU_BUILDINFO_GIT
const char XEMU_BUILDINFO_GIT[]
Definition: emutools_basicdefs.h:251
HDOSERR_CANNOT_OPEN_DIR
#define HDOSERR_CANNOT_OPEN_DIR
Definition: hdos.c:80
dirp
XDIR * dirp
Definition: hdos.c:63
hdos_notify_system_start_end
void hdos_notify_system_start_end(void)
Definition: hdos.c:855
hdos_notify_system_start_begin
void hdos_notify_system_start_begin(void)
Definition: hdos.c:847
xemu_get_uname_string
const char * xemu_get_uname_string(void)
Definition: emutools.c:468
hypervisor_hdos_virtualization_status
int hypervisor_hdos_virtualization_status(const int set, const char **root_ptr)
Definition: hdos.c:829
Uint8
uint8_t Uint8
Definition: fat32.c:51
HDOSROOT_SUBDIR_NAME
#define HDOSROOT_SUBDIR_NAME
Definition: hdos.h:27
CPU65_PF_C
#define CPU65_PF_C
Definition: cpu65.h:30
HDOSERR_INVALID_DESC
#define HDOSERR_INVALID_DESC
Definition: hdos.c:72
HDOSERR_INVALID_ADDRESS
#define HDOSERR_INVALID_ADDRESS
Definition: hdos.c:70
transfer_area_addr
int transfer_area_addr
Definition: hdos.c:53
HDOSERR_IS_DIRECTORY
#define HDOSERR_IS_DIRECTORY
Definition: hdos.c:73
hyppo_version_string
char hyppo_version_string[64]
Definition: hypervisor.c:41
xemu_os_opendir
#define xemu_os_opendir
Definition: emutools.h:268
xemu_os_close
#define xemu_os_close
Definition: emutools.h:272
DEBUGPRINT
#define DEBUGPRINT(...)
Definition: emutools_basicdefs.h:171
xemu_os_stat
#define xemu_os_stat
Definition: emutools.h:270
hypervisor_leave
void hypervisor_leave(void)
Definition: hypervisor.c:313
hdos_leave
void hdos_leave(const Uint8 func_no)
Definition: hdos.c:758
basedirpath
char * basedirpath
Definition: hdos.c:66
ERROR_WINDOW
#define ERROR_WINDOW(...)
Definition: xep128.h:116
memory_mapper.h
hdos.h
xemucfg_get_cli_info
void xemucfg_get_cli_info(const char **exec_name_ptr, int *argc_ptr, char ***argv_ptr)
error_code
int error_code
Definition: hdos.c:52
NL
#define NL
Definition: fat32.c:37
emutools_config.h
hypervisor.h
in_y
Uint8 in_y
Definition: hdos.c:44
cwd
char * cwd
Definition: hdos.c:49
HDOSERR_FILE_NOT_FOUND
#define HDOSERR_FILE_NOT_FOUND
Definition: hdos.c:71
trap_for_xemu
void trap_for_xemu(const int func_no)
Definition: hdos.c:148
cpu65.h
memory_debug_write_cpu_addr
void memory_debug_write_cpu_addr(Uint16 addr, Uint8 data)
Definition: memory_mapper.c:978
sdcard_notify_system_start_end
void sdcard_notify_system_start_end(void)
Definition: sdcard.c:790
DIRSEP_STR
#define DIRSEP_STR
Definition: emutools_basicdefs.h:141
size
int size
Definition: inject.c:37
XEMU_BUILDINFO_CDATE
const char XEMU_BUILDINFO_CDATE[]
Definition: emutools_basicdefs.h:251
func_name
const char * func_name
Definition: hdos.c:42
D6XX_registers
Uint8 D6XX_registers[0x100]
Definition: io_mapper.c:38
status
enum @26::@29 status
memory_debug_read_cpu_addr
Uint8 memory_debug_read_cpu_addr(Uint16 addr)
Definition: memory_mapper.c:973
MKDIR
#define MKDIR(__n)
Definition: emutools_basicdefs.h:147
sdcard.h
rootdir
const char * rootdir
Definition: hdos.c:48
xemu_strdup
char * xemu_strdup(const char *s)
Definition: emutools.c:278
OSD
#define OSD(...)
Definition: xep128.h:100
setname_fn
char setname_fn[64]
Definition: hdos.c:47
virt_out_z
Uint8 virt_out_z
Definition: hdos.c:46
found
char found[8+3+1]
Definition: cpmfs.c:36
func
Uint8 func
Definition: hdos.c:41
HDOS_VIRT_HYPPO_UNIMPLEMENTED
#define HDOS_VIRT_HYPPO_UNIMPLEMENTED()
Definition: hdos.c:636
do_virt
int do_virt
Definition: hdos.c:51
HDOSERR_END_DIR
#define HDOSERR_END_DIR
Definition: hdos.c:79
HDOSERR_NOT_DIRECTORY
#define HDOSERR_NOT_DIRECTORY
Definition: hdos.c:74
HDOSERR_NO_SUCH_DISK
#define HDOSERR_NO_SUCH_DISK
Definition: hdos.c:77
sdl_pref_dir
char * sdl_pref_dir
Definition: emutools.c:97
FATAL
#define FATAL(...)
Definition: xep128.h:117
func_is_virtualized
int func_is_virtualized
Definition: hdos.c:43
xemu_restrdup
void xemu_restrdup(char **ptr, const char *str)
Definition: emutools.c:286
XDIR
DIR XDIR
Definition: emutools.h:262
xemu_os_readdir
int xemu_os_readdir(XDIR *dirp, char *fn)
Definition: emutools.c:1733
XEMU_UNLIKELY
#define XEMU_UNLIKELY(__x__)
Definition: emutools_basicdefs.h:125
xemu_os_open
#define xemu_os_open
Definition: emutools.h:263
st
struct stat st
Definition: cpmfs.c:43
DIRSEP_CHR
#define DIRSEP_CHR
Definition: emutools_basicdefs.h:142