/*---------------------------------------------------------------------------- UNIFIED SOFTWARE INTERRUPT FUNCTION FOR DOS COMPILERS Chris Giese http://SisAndHappy.com/ChrisGiese/ I, the copyright holder of this work, hereby release it into the public domain. This applies worldwide. If this is not legally possible: I grant any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law. 18 July 2023: - trap16() redone yet again; no longer uses self-modifying code 15 June 2017: - trap32() extensively redone: - renamed to trap16() - saves and loads only 16-bit registers (AX; not EAX) - saves and restores caller FLAGS so callee doesn't leave interrupts disabled - no longer "sanitizing" FLAGS value - no longer using [esp + NNN] addressing mode 21 May 2015: - Redid machine language in g_trap32[] - trap() renamed to trap32(); rm_int_...() renamed to trap32_...() - Order of arguments to trap()/trap32() and trap32_esdi_dsdx() changed - rm_int_esdi() deleted; use trap32_esdi_dsdx() instead 30 Dec 2014: - Replaced trap() with my newest 32-bit trap32() routine. It uses INT again to call the interrupt; instead of IRET (once again, it it self-modifying code). - Renamed the 16-bit registers field in regs_t from "w" to "x"; for compatability with DJGPP. 29 Sep 2013: initial release EXPORTS: regs_t int trap16(regs_t *regs, unsigned int_num); int trap_esdi_dsdx(regs_t *regs, unsigned int_num, void FAR *esdi_buf, unsigned esdi_buf_size, void FAR *dsdx_buf, unsigned dsdx_buf_size); For 32-bit Watcom C, this code also exports these: __dpmi_regs int __dpmi_int(unsigned int_num, __dpmi_regs *rm_regs); int __dpmi_simulate_real_mode_interrupt(unsigned int_num, __dpmi_regs *rm_regs); int __dpmi_allocate_dos_memory(int paragraphs, int *sel_or_max); int __dpmi_free_dos_memory(int sel); ----------------------------------------------------------------------------*/ #if 0 #include #else typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned long uint32_t; #endif #pragma pack(1) typedef struct { /* 0 */uint8_t res0[4]; /* (E)DI */ /* 4 */uint8_t res1[4]; /* (E)SI */ /* 8 */uint8_t res2[4]; /* (E)BP */ /* 12 */uint8_t res3[4]; /* 16 */uint8_t bl, bh, res5[2]; /* 20 */uint8_t dl, dh, res7[2]; /* 24 */uint8_t cl, ch, res6[2]; /* 28 */uint8_t al, ah, res4[2]; /* 32 */} regs8_t; #pragma pack(1) typedef struct { /* 0 */uint16_t di, res0; /* 4 */uint16_t si, res1; /* 8 */uint16_t bp, res2; /* 12 */uint16_t res3[2]; /* 16 */uint16_t bx, res4; /* 20 */uint16_t dx, res5; /* 24 */uint16_t cx, res6; /* 28 */uint16_t ax, res7; /* 32 */uint16_t flags; /* 34 */uint16_t es; /* 36 */uint16_t ds; /* 38 */uint16_t fs; /* 40 */uint16_t gs; /* 42 */uint16_t ip; /* 44 */uint16_t cs; /* 46 */uint16_t sp; /* 48 */uint16_t ss; /* 50 */} regs16_t; #pragma pack(1) typedef struct { /* 0 */uint32_t edi; /* 4 */uint32_t esi; /* 8 */uint32_t ebp; /* 12 */uint32_t res3; /* 16 */uint32_t ebx; /* 20 */uint32_t edx; /* 24 */uint32_t ecx; /* 28 */uint32_t eax; /* 32 */} regs32_t; /****************************************************************************/ #if defined(__TURBOC__) || (defined(__WATCOMC__)&&!defined(__386__)) #if defined(__WATCOMC__) #include #define inportb(P) inp(P) #define outportb(P,V) outp(P,V) #endif #define LINEAR2FAR(L) MK_FP((unsigned)(L >> 4), (unsigned)(L & 15)) #define dosmemget(off,n,buf) _fmemcpy(buf, LINEAR2FAR(off), n) #define dosmemput(buf,n,off) _fmemcpy(LINEAR2FAR(off), buf, n) #define peekw(S,O) *(uint16_t far *)MK_FP(S,O) /* The layout of this structure is the same as that used by DPMI function INT 31h AX=0300h: SIMULATE REAL MODE INTERRUPT */ typedef union { regs8_t h; regs16_t x; regs32_t d; } regs_t; /* NOTE 1: INT 25h and INT 26h need special handling (not implemented here) because they leave crap on the stack -- see Microsoft article 48744 regarding int86x() NOTE 2: PUSHA, POPA, and PUSH imm16 instructions require 186+ CPU NOTE 3: There are at least three ways to implement this function: - Push a four-byte stub on the stack (INT nn, RETF, NOP) and far-call it. This approach is used by this code and by Turbo C. - Have 3-byte stubs (INT nn, RET) for all possible INT nn instructions; called via a jump table. This approach is used by Watcom C and requires at least 768 bytes. - INT instruction embedded in this code. Requires self-modifying code so it's not suitable for e.g. embedded systems with code in ROM. */ static const unsigned char g_trap16[] = { /* BITS 16 */ 0x60, /* pusha */ 0x1E, /* push ds */ 0x06, /* push es */ /* save FLAGS so the called interrupt doesn't e.g. leave interrupts disabled */ 0x9C, /* pushf */ 0x89, 0xE5, /* mov bp,sp */ /* stack contents at this point: [sp + 28] = [bp + 28] = unsigned int_num (2nd arg) [sp + 26] = [bp + 26] = regs_t *regs (1st arg) [sp + 24] = [bp + 24] = caller value of CS (=far return address) [sp + 22] = [bp + 22] = caller value of IP (=far return address) [sp + 6] = [bp + 6] = caller values of general-purpose registers (PUSHA) [sp + 0] = [bp + 0] = caller values of FLAGS, ES, DS */ 0x8D, 0x5E, 0xFC, /* lea bx,[bp-4] address of stub */ 0xB0, 0xCD, /* mov al,0xcd */ 0x8A, 0x66, 0x1C, /* mov ah,[bp+28] int_num */ 0x68, 0xCB, 0x90, /* push word 0x90cb stub: RETF, NOP */ 0x50, /* push ax stub: INT nn */ 0x0E, /* push cs far ret addr from stub */ 0xE8, 0x00, 0x00, /* call where_am_i */ /* where_am_i: */ 0x58, /* pop ax */ 0x05, 0x27, 0x00, /* add ax,(stub_retfs_to_here - where_am_i) */ 0x50, /* push ax far ret addr from stub */ 0x16, /* push ss far addr of stub */ 0x53, /* push bx far addr of stub */ /* stack contents at this point: [sp + 40] = [bp + 28] = unsigned int_num (2nd arg) [sp + 38] = [bp + 26] = regs_t *regs (1st arg) [sp + 36] = [bp + 24] = caller value of CS (=far return address) [sp + 34] = [bp + 22] = caller value of IP (=far return address) [sp + 18] = [bp + 6] = caller values of general-purpose registers (PUSHA) [sp + 12] = [bp + 0] = caller values of FLAGS, ES, DS [sp + 8] = [bp - 4] = stub code: INT nn, RETF, NOP [sp + 4] = [bp - 8] = far address of stub [sp + 0] = [bp - 12] = far return address from stub */ 0x8B, 0x5E, 0x1A, /* mov bx,[bp+26] regs */ 0x8B, 0x3F, /* mov di,[bx] regs->w.di */ 0x8B, 0x77, 0x04, /* mov si,[bx+4] regs->w.si */ 0x8B, 0x6F, 0x08, /* mov bp,[bx+8] regs->w.bp */ 0x8B, 0x57, 0x14, /* mov dx,[bx+20] regs->w.dx */ 0x8B, 0x4F, 0x18, /* mov cx,[bx+24] regs->w.cx */ 0x8B, 0x47, 0x1C, /* mov ax,[bx+28] regs->w.ax */ 0x8E, 0x47, 0x22, /* mov es,[bx+34] regs->w.es */ 0xFF, 0x77, 0x10, /* push word [bx+16] regs->w.bx */ 0xFF, 0x77, 0x24, /* push word [bx+36] regs->w.ds */ 0x1F, /* pop ds */ 0x5B, /* pop bx */ /* far "call" the four-byte stub (INT nn/RETF/NOP) on the stack */ 0xCB, /* retf */ /* stub_retfs_to_here: remove stub code from the stack */ 0x81, 0xC4, 0x04, 0x00, /* add sp,4 */ 0x1E, /* push ds */ 0x53, /* push bx */ 0x9C, /* pushf */ 0x89, 0xE3, /* mov bx,sp */ /* stack contents at this point: [sp + 34] = [bx + 34] = unsigned int_num (2nd arg) [sp + 32] = [bx + 32] = regs_t *regs (1st arg) [sp + 30] = [bx + 30] = caller value of CS (=far return address) [sp + 28] = [bx + 28] = caller value of IP (=far return address) [sp + 12] = [bx + 12] = caller values of general-purpose registers (PUSHA) [sp + 6] = [bx + 6] = caller values of FLAGS, ES, DS [sp + 0] = [bx + 0] = callee FLAGS, BX, DS */ 0x36, 0x8E, 0x5F, 0x0A, /* mov ds,[ss:bx+10] caller DS */ 0x36, 0x8B, 0x5F, 0x20, /* mov bx,[ss:bx+32] regs */ 0x8F, 0x47, 0x20, /* pop word [bx+32] regs->w.flags */ 0x8F, 0x47, 0x10, /* pop word [bx+16] regs->w.bx */ 0x8F, 0x47, 0x24, /* pop word [bx+36] regs->w.ds */ 0x8C, 0x47, 0x22, /* mov [bx+34],es regs->w.es */ 0x89, 0x47, 0x1C, /* mov [bx+28],ax regs->w.ax */ 0x89, 0x4F, 0x18, /* mov [bx+24],cx regs->w.cx */ 0x89, 0x57, 0x14, /* mov [bx+20],dx regs->w.dx */ 0x89, 0x6F, 0x08, /* mov [bx+8],bp regs->w.bp */ 0x89, 0x77, 0x04, /* mov [bx+4],si regs->w.si */ 0x89, 0x3F, /* mov [bx],di regs->w.di */ 0x9D, /* popf */ 0x07, /* pop es */ 0x1F, /* pop ds */ 0x61, /* popa */ 0x31, 0xC0, /* xor ax,ax return 0 always */ 0xCB /* retf */ }; /* g_trap16[] is CODE but is stored in the DATA segment. trap16() is a pointer to it, and must be declared 'far'. For 16-bit Watcom C, use 'cdecl' to force usage of normal, stack calling convention instead of Watcom register calling convention. */ int far cdecl (*trap16)(regs_t *regs, unsigned int_num) = (int far cdecl (*)(regs_t *, unsigned))g_trap16; /****************************************************************************/ #elif defined(__WATCOMC__)&&defined(__386__) #include /* memcpy() */ #include #include /* struct SREGS, union REGS, int386x() */ #define inportb(P) inp(P) #define outportb(P,V) outp(P,V) /* These work with CauseWay DOS extender only (segment base address = 0) */ #define dosmemget(off,n,buf) memcpy(buf, (void *)(off), n) #define dosmemput(buf,n,off) memcpy((void *)(off), buf, n) #define peekw(S,O) *(uint16_t *)((S) * 16uL + (O)) #define trap16(R,N) __dpmi_int(N,R) typedef union { regs8_t h; regs16_t x; regs32_t d; } regs_t; typedef regs_t __dpmi_regs; /****************************************************************************/ int __dpmi_int(unsigned int_num, __dpmi_regs *rm_regs) { struct SREGS sregs; union REGS regs; /* "DPMI will provide a small (30 words) real mode stack if SS:SP is zero" */ rm_regs->x.ss = rm_regs->x.sp = 0; memset(&sregs, 0, sizeof(sregs)); memset(®s, 0, sizeof(regs)); sregs.es = FP_SEG(rm_regs); regs.x.edi = FP_OFF(rm_regs); regs.w.ax = 0x0300; /* simulate real-mode interrupt */ regs.h.bl = int_num; regs.h.bh = 0; /* flags */ regs.w.cx = 0; /* number of words to copy between stacks */ int386x(0x31, ®s, ®s, &sregs); return regs.w.cflag ? -1 : 0; } /****************************************************************************/ int __dpmi_simulate_real_mode_interrupt(unsigned int_num, __dpmi_regs *rm_regs) { struct SREGS sregs; union REGS regs; memset(&sregs, 0, sizeof(sregs)); memset(®s, 0, sizeof(regs)); sregs.es = FP_SEG(rm_regs); regs.x.edi = FP_OFF(rm_regs); regs.x.eax = 0x0300; /* simulate real-mode interrupt */ regs.h.bl = int_num; regs.h.bh = 0; /* flags */ regs.w.cx = 0; /* number of words to copy between stacks */ int386x(0x31, ®s, ®s, &sregs); return regs.w.cflag ? -1 : 0; } /****************************************************************************/ int __dpmi_allocate_dos_memory(int paragraphs, int *sel_or_max) { union REGS regs; memset(®s, 0, sizeof(regs)); regs.w.ax = 0x0100; regs.w.bx = paragraphs; int386(0x31, ®s, ®s); if(regs.w.cflag) /* error */ { *sel_or_max = regs.w.bx; /* max paragraphs available */ return -1; } *sel_or_max = regs.w.dx; /* selector */ return regs.w.ax; /* segment */ } /****************************************************************************/ int __dpmi_free_dos_memory(int sel) { union REGS regs; memset(®s, 0, sizeof(regs)); regs.w.ax = 0x0101; regs.w.dx = sel; int386(0x31, ®s, ®s); return regs.w.cflag ? -1 : regs.w.ax; } /****************************************************************************/ #elif defined(__DJGPP__) #include /* dosmemput(), dosmemget() */ #include /* _farpeekw() */ #include /* _dos_ds */ #include #define trap16(R,N) __dpmi_int(N,R) #define peekw(S,O) _farpeekw(_dos_ds, (S) * 16uL + (O)) typedef __dpmi_regs regs_t; #else #error Sorry, unsupported compiler #endif /***************************************************************************** YES, this modifies ES, DI, DS, DX in the regs_t object. Maybe you want to pull it apart into separate trap_esdi() and trap32_dsdx() functions after all. *****************************************************************************/ #if defined(__TURBOC__) || (defined(__WATCOMC__) && !defined(__386__)) #include /* FP_SEG(), FP_OFF() */ int trap_esdi_dsdx(regs_t *regs, unsigned int_num, void far *esdi_buf, unsigned esdi_buf_size_UNUSED, void far *dsdx_buf, unsigned dsdx_buf_size_UNUSED) { regs->x.es = FP_SEG(esdi_buf); regs->x.di = FP_OFF(esdi_buf); regs->x.ds = FP_SEG(dsdx_buf); regs->x.dx = FP_OFF(dsdx_buf); return trap16(regs, int_num); } /****************************************************************************/ #elif defined(__DJGPP__) || (defined(__WATCOMC__) && defined(__386__)) #include /* printf() */ int trap_esdi_dsdx(regs_t *regs, unsigned int_num, void *esdi_buf, unsigned esdi_buf_size, void *dsdx_buf, unsigned dsdx_buf_size) { int es = 0, ds = 0, es_sel = 0, ds_sel = 0; /* allocate conventional memory block (CMB)... */ if(esdi_buf != NULL && esdi_buf_size != 0) { if((es = __dpmi_allocate_dos_memory( (esdi_buf_size + 15) / 16, &es_sel)) == -1) ERR: { printf("Error: can't allocate conventional memory\n"); return -1; } /* ...copy buffer to CMB... */ dosmemput(esdi_buf, esdi_buf_size, es * 16); /* ...and point real-mode register pair to CMB */ regs->x.es = es; regs->x.di = 0; } if(dsdx_buf != NULL && dsdx_buf_size != 0) { if((ds = __dpmi_allocate_dos_memory( (dsdx_buf_size + 15) / 16, &ds_sel)) == -1) { __dpmi_free_dos_memory(es_sel); goto ERR; } dosmemput(dsdx_buf, dsdx_buf_size, ds * 16); regs->x.ds = ds; regs->x.dx = 0; } /* do interrupt */ trap16(regs, int_num); /* copy CMB back to buffer & free CMB */ if(esdi_buf != NULL) { if(esdi_buf_size != 0) dosmemget(es * 16, esdi_buf_size, esdi_buf); __dpmi_free_dos_memory(es_sel); } if(dsdx_buf != NULL) { if(dsdx_buf_size != 0) dosmemget(ds * 16, dsdx_buf_size, dsdx_buf); __dpmi_free_dos_memory(ds_sel); } return 0; } #endif /* defined(__DJGPP__) || (defined(__WATCOMC__) && defined(__386__)) */ /*---------------------------------------------------------------------------- LFN DIRECTORY FUNCTIONS FOR DOS Chris Giese http://SisAndHappy.com/ChrisGiese/ I, the copyright holder of this work, hereby release it into the public domain. This applies worldwide. If this is not legally possible: I grant any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law. 29 Sep 2013: initial release (There were earlier versions but I didn't keep track of the file history.) EXPORTS: struct my_stat dirent_t dir_t dir_t *my_opendir(const char *dir_name); dirent_t *readdir_with_stat(dir_t *dir, struct my_stat *statbuf); int my_closedir(dir_t *dir); ----------------------------------------------------------------------------*/ /* memset(), strncpy(), strlen(), strcpy(), strcat(), strchr(), strcrmp() */ #include #include /* malloc(), free() */ #include /* ENOMEM, EBADF, errno */ #include /* printf(), putchar() */ #include /* time_t, struct tm, mktime(), localtime() */ /* FP_SEG(), FP_OFF(), struct SREGS, union REGS, int86(), int86x() */ #include #define DIRMAGIC 0xD17F #define S_IFDIR 0x4000 /* directory */ #define S_IREAD 0x0100 /* readable */ #define S_IWRITE 0x0080 /* writable */ #define S_IEXEC 0x0040 /* executable; or searchable dir */ #define S_IFMT 0xF000 /* file type mask */ struct my_stat { short st_dev; short st_mode;/*, st_nlink, st_ino; int st_uid, st_gid; short st_rdev; */ uint32_t st_size; time_t st_atime, st_mtime, st_ctime; }; typedef struct { char d_name[260]; } dirent_t; typedef struct { unsigned magic; /* magic value to validate this object */ unsigned handle; /* handle from LFN Find First */ /* filled in by LFN Find First; read by first call to readdir_with_stat(): */ dirent_t dirent; struct my_stat statbuf; char first; char sfn[13]; /* short filename */ } dir_t; #pragma pack(1) typedef struct { /* 0 */uint32_t attrib; /* 4 */uint16_t ctime, cdate; /* 8 */uint32_t ctime64_msd; /* 0C */uint16_t atime, adate; /* 10 */uint32_t atime64_msd; /* 14 */uint16_t mtime, mdate; /* 18 */uint32_t mtime_msd; /* 1C */uint32_t size_msd, size_lsd; /* 24 */uint8_t reserved[8]; /* 2C */char long_name[260]; /*130 */char short_name[14]; /*13E */ } lfn_find_t; #pragma pack(1) typedef struct { /* 0 */uint8_t drive_letter; /* 1 */char want_name[11]; /* 0C */uint8_t want_attrib; /* 0D */uint16_t ent_in_dir; /* 0F */uint16_t parent_cluster; /* 11 */char res[4]; /* 15 */uint8_t got_attrib; /* 16 */uint16_t time; /* 18 */uint16_t date; /* 1A */uint32_t size; /* 1E */char got_name[13]; /* 2B */ } sfn_find_t; static char g_lfn_supported; /***************************************************************************** Bitfields for file time: Bit(s) Description (Table 01665) 15-11 hours (0-23) 10-5 minutes 4-0 seconds/2 Bitfields for file date: Bit(s) Description (Table 01666) 15-9 year - 1980 8-5 month 4-0 day *****************************************************************************/ static unsigned long dos_time_date_to_time_t(unsigned dos_time, unsigned dos_date) { struct tm time; memset(&time, 0, sizeof(time)); time.tm_mday = dos_date & 0x1F; /* 1-31 */ dos_date >>= 5; time.tm_mon = (dos_date & 0x0F) - 1; /* 1-12 -> 0-11 */ dos_date >>= 4; time.tm_year = dos_date + 80; /* 0=1900 */ time.tm_sec = (dos_time & 0x1F) * 2; /* 0-58 */ dos_time >>= 5; time.tm_min = dos_time & 0x3F; /* 0-59 */ dos_time >>= 6; time.tm_hour = dos_time; /* 0-23 */ return mktime(&time); } /****************************************************************************/ void check_lfn(void) { static const char *root = "\\*.*"; /**/ lfn_find_t find; regs_t regs; int i; /* try LFN FindFirst to see if LFN supported */ regs.x.ax = 0x714E; regs.x.cx = 0x0037; regs.x.si = 0x0001; regs.x.flags = 0x0001; /* ES:DI -> FindData record, DS:DX -> filename */ i = trap_esdi_dsdx(®s, 0x21, &find, sizeof(find), root, strlen(root) + 1); if(i != 0 || regs.x.flags & 0x01) g_lfn_supported = 0; else { /* FindClose */ regs.x.bx = regs.x.ax; /* handle */ regs.x.ax = 0x71A1; trap16(®s, 0x21); g_lfn_supported = 1; } } /****************************************************************************/ static void lfn_nmst(dir_t *dir, lfn_find_t *find) { /* n=Name */ if(find->long_name[0] != '\0') { strncpy(dir->dirent.d_name, find->long_name, 259); dir->dirent.d_name[259] = '\0'; } else { strncpy(dir->dirent.d_name, find->short_name, 12); dir->dirent.d_name[12] = '\0'; } if(find->short_name[0] != '\0') strncpy(dir->sfn, find->short_name, 12); else strncpy(dir->sfn, find->long_name, 12); dir->sfn[12] = '\0'; /* m=Mode */ if(find->attrib & 0x10) dir->statbuf.st_mode = (S_IFDIR | S_IREAD | S_IEXEC); else { dir->statbuf.st_mode = S_IREAD; if((find->attrib & 0x01) == 0) dir->statbuf.st_mode |= S_IWRITE; } /* s=Size */ dir->statbuf.st_size = find->size_lsd; /* t=Times */ dir->statbuf.st_ctime = dos_time_date_to_time_t(find->ctime, find->cdate); dir->statbuf.st_atime = dos_time_date_to_time_t(find->atime, find->adate); dir->statbuf.st_mtime = dos_time_date_to_time_t(find->mtime, find->mdate); } /****************************************************************************/ static void sfn_nmst(dir_t *dir) { static unsigned long dta_linear; /**/ sfn_find_t find; regs_t regs; /* sfn_find_t stored in the Disk Transfer Area (DTA) */ if(dta_linear == 0) { regs.h.ah = 0x2F; trap16(®s, 0x21); dta_linear = regs.x.es * 16uL + regs.x.bx; } dosmemget(dta_linear, sizeof(sfn_find_t), &find); /* n=Name */ strncpy(dir->sfn, find.got_name, 12); dir->sfn[12] = '\0'; strncpy(dir->dirent.d_name, find.got_name, 12); dir->dirent.d_name[12] = '\0'; /* m=Mode */ if(find.got_attrib & 0x10) dir->statbuf.st_mode = (S_IFDIR | S_IREAD | S_IEXEC); else { dir->statbuf.st_mode = S_IREAD; if((find.got_attrib & 0x01) == 0) dir->statbuf.st_mode |= S_IWRITE; } /* s=Size */ dir->statbuf.st_size = find.size; /* t=Times */ dir->statbuf.st_ctime = dir->statbuf.st_atime = dir->statbuf.st_mtime = dos_time_date_to_time_t (find.time, find.date); } /****************************************************************************/ dir_t *my_opendir(const char *dir_name) { lfn_find_t find; unsigned len; dir_t *dir; regs_t regs; char *name; int i; /* allocate memory for DIR object */ if((dir = malloc(sizeof(dir_t))) == NULL) { errno = ENOMEM; return NULL; } /* copy dir_name, append "/" if necessary, then append "*" */ len = strlen(dir_name); /* +5 for "/*.*" and NUL byte */ if((name = malloc(len + 5)) == NULL) { free(dir); errno = ENOMEM; return NULL; } strcpy(name, dir_name); if(name[len - 1] != '/' && name[len - 1] != '\\') strcat(name, "/*.*"); else strcat(name, "*.*"); if(g_lfn_supported) { /* LFN FindFirst */ regs.x.ax = 0x714E; /* CH=0x00="must be" attributes: (nothing) CL=0x17="can be" attributes: b5=archive, b4=dir, b3=volume label, b2=system, b1=hidden, b0=read-only */ regs.x.cx = 0x0037; regs.x.si = 0x0001; /* use MS-DOS-format time/date */ /* carry should be set before calling LFN functions -- see news:<50AFFDB9.7060108@gmx.de> */ regs.x.flags = 0x0001; /* ES:DI -> FindData record, DS:DX -> filename */ i = trap_esdi_dsdx(®s, 0x21, &find, sizeof(find), name, len + 5); free(name); if(i != 0 || (regs.x.flags & 0x0001)) { free(dir); errno = ENOENT; return NULL; } /* get info about first entry in dir */ lfn_nmst(dir, &find); } else { /* SFN FindFirst */ regs.x.ax = 0x4E00; /* find files, dirs (b4), system (b2), hidden (b1), read-only (b0) */ regs.x.cx = 0x17; /* i = trap32_dsdx(... */ i = trap_esdi_dsdx(®s, 0x21, NULL, 0, name, len + 5); free(name); if(i != 0 || (regs.x.flags & 0x0001)) { free(dir); errno = ENOENT; return NULL; } /* get info about first entry in dir */ sfn_nmst(dir); } /* success; update fields in DIR object */ dir->magic = DIRMAGIC; dir->handle = regs.x.ax; dir->first = 1; return dir; } /****************************************************************************/ dirent_t *readdir_with_stat(dir_t *dir, struct my_stat *statbuf) { lfn_find_t find; regs_t regs; if(dir->magic != DIRMAGIC) { errno = EBADF; return NULL; } if(!dir->first) { if(g_lfn_supported) { /* LFN FindNext */ regs.x.ax = 0x714F; regs.x.bx = dir->handle; regs.x.si = 0x0001; /* use MS-DOS-format time/date */ regs.x.flags = 0x0001; /* ES:DI -> FindData record */ if(trap_esdi_dsdx(®s, 0x21, &find, sizeof(find), NULL, 0) || (regs.x.flags & 0x0001)) return NULL; /* get info */ lfn_nmst(dir, &find); } else { /* SFN FindNext */ regs.h.ah = 0x4F; trap16(®s, 0x21); if(regs.x.flags & 0x0001) return NULL; sfn_nmst(dir); } } dir->first = 0; *statbuf = dir->statbuf; /* structure copy */ return &dir->dirent; } /****************************************************************************/ int my_closedir(dir_t *dir) { regs_t regs; if(dir == NULL || dir->magic != DIRMAGIC) { errno = EBADF; return -1; } if(g_lfn_supported) { /* LFN FindClose */ regs.x.ax = 0x71A1; regs.x.bx = dir->handle; regs.x.flags = 0x0001; /* set CY */ trap16(®s, 0x21); } /* there is no SFN FindClose else { } */ dir->magic = 0; free(dir); return 0; } /*---------------------------------------------------------------------------- DOOM launcher program Chris Giese http://SisAndHappy.com/ChrisGiese/ I, the copyright holder of this work, hereby release it into the public domain. This applies worldwide. If this is not legally possible: I grant any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law. 18 July 2023: - Path handling code fixed so e.g. "rundoom c:foo.wad" works - Default is to now to start Windows ZDOOM instead of DOS DOOM 24 November 2018: High-speed timer is back; XORing it with output of rand(). 5 June 2018: Calling srand() only once now, so result is more random if you use both -r and -e options. 15 Oct 2017: Seeding srand() from high-speed timer doesn't make it more random; going back to using time() as seed 15 June 2017: srand() now seeded from high-speed timer; not from time() 24 Jul 2015: Deleted the logic to prevent "doom2 -file doom2.wad ..." and "doom -file doom.wad ..." because it prevents me from running AMFDOOM.WAD and I don't know how to fix it at the moment. 23 June 2015: String comparision in episode_cmp() now 5 bytes long instead of 4 (4 bytes didn't work with DOOM 2) 11 Jun 2015: Added code to prevent "doom2 -file doom2.wad ..." and "doom -file doom.wad ..." 25 May 2015: Added support for DOOM 2 29 Sep 2013: Initial release (There were earlier versions but I didn't keep track of the file history.) To do: - Pass unrecognized options to DOOM.EXE (-warp X Y, -loadgame N, -respawn, etc.) - Get directory containing .WAD file(s) from DOOMWADDIR environment variable - Multiple "-d DIR" options on command line - restore code that prevents "rundoom doom.wad" ----------------------------------------------------------------------------*/ //#include /* struct stat, stat() */ //#include /* DIR, struct dirent, opendir(), closedir() */ #if !defined(__WATCOMC__) #include /* MAXPATH, chdir(), getcwd() */ #else #include /* getcwd() */ #define MAXPATH 260 #endif /* IMPORTS from LFN code struct my_stat dirent_t dir_t */ extern char g_lfn_supported; void check_lfn(void); dir_t *my_opendir(const char *dir_name); dirent_t *readdir_with_stat(dir_t *dir, struct my_stat *statbuf); int my_closedir(dir_t *dir); #include /* qsort(), realloc(), free() */ #include #include #include /* isdigit() */ #include /* time() */ #if defined(unix) #include /* chdir() */ #endif #if defined(__GNUC__) #define strcmpi(L,R) strcasecmp(L,R) #endif typedef struct { char name[13]; /* store only short file name */ time_t atime; /* store only atime; not entire stat struct */ } wad_t; /****************************************************************************/ static unsigned long read_le32(void *buf0) { unsigned char *buf = buf0; unsigned long rv; rv = buf[3]; /* MSB */ rv <<= 8; rv |= buf[2]; rv <<= 8; rv |= buf[1]; rv <<= 8; rv |= buf[0]; /* LSB */ return rv; } /***************************************************************************** find *.wad files in directory given by 'dir_name'; get names of these files and stat() them *****************************************************************************/ static int find_wads(wad_t **wad_ptr, unsigned *num_wads_ptr, char *dir_name) { unsigned num_wads = 0, num_alloc = 0; char curr_dir[MAXPATH]; wad_t *wad = NULL; dir_t *dir; /* save current directory (CD) */ getcwd(curr_dir, MAXPATH); /* make 'dir_name' the new CD */ if(chdir(dir_name)) ERR: { printf("Error: can't enter directory '%s'\n", dir_name); return -1; } if((dir = my_opendir(".")) == NULL) /* can't happen! */ { chdir(curr_dir); goto ERR; } printf("Scanning directory '%s' for .WAD files...\n", dir_name); /* read all entries in directory */ while(1) { struct my_stat st; dirent_t *e; wad_t *wad0; char *s; /* for FAT filesystem, combined readdir()/stat() is faster than doing them separately */ if((e = readdir_with_stat(dir, &st)) == NULL) break; /* ignore files that do not end with .WAD */ if((s = strchr(e->d_name, '.')) == NULL) continue; /* no extension */ if(strcmpi(s, ".wad")) continue; /* enlarge array */ if(num_wads >= num_alloc) { /* start with 16 entries and double the size when it overflows */ num_alloc = (num_alloc == 0) ? 16 : (num_alloc * 2); wad0 = realloc(wad, num_alloc * sizeof(wad_t)); if(wad0 == NULL) { printf("Error: out of memory\n"); my_closedir(dir); chdir(curr_dir); if(wad != NULL) free(wad); return -1; } wad = wad0; } wad0 = &wad[num_wads]; num_wads++; /* store dirent (8.3 filename) */ strncpy(wad0->name, e->d_name, 12); wad0->name[12] = '\0'; /* store atime */ wad0->atime = st.st_atime; } my_closedir(dir); chdir(curr_dir); *wad_ptr = wad; *num_wads_ptr = num_wads; printf("Found %u .WAD files\n", num_wads); return 0; } /****************************************************************************/ #define BPERL 16 /* byte/line for dump */ static void dump(const void *data0, unsigned count) { const unsigned char *data = data0; unsigned j, k; while(count != 0) { for(j = 0; j < BPERL; j++) { if(count == 0) break; printf("%02X ", data[j]); count--; } printf("\t"); for(k = 0; k < j; k++) { if(data[k] < ' ') printf("."); else printf("%c", data[k]); } printf("\n"); data += BPERL; } } /***************************************************************************** opens file 'wad_name', verifies it's a DOOM PWAD, loads episodes *****************************************************************************/ static int load_episodes(char **eps_ptr, unsigned *num_eps_ptr, const char *wad_name) { char buf[16], *eps = NULL; unsigned num_eps = 0; unsigned long pos; int rv = 0; /* return value -- 0=DOOM 1, 1=DOOM2, -1=error */ FILE *f; *eps_ptr = NULL; *num_eps_ptr = 0; /* open file */ if((f = fopen(wad_name, "rb")) == NULL) { printf("Error: can't open file '%s'\n", wad_name); return -1; } /* read header; verify it's a PWAD */ if(fread(buf, 1, 12, f) != 12) NOT: { printf("Error: file '%s' is not a DOOM PWAD/IWAD file\n", wad_name); fclose(f); return -1; } if(memcmp(buf, "PWAD", 4) && memcmp(buf, "IWAD", 4)) goto NOT; /* seek to directory */ pos = read_le32(&buf[8]); fseek(f, pos, SEEK_SET); /* read directory entries */ num_eps = 0; eps = NULL; while(fread(buf, 1, 16, f) == 16) { char *new_eps; //dump(buf, 16); /* bytes 8-15 are object name, which is "ExMx" for a DOOM 1 level (map)... */ if(buf[8] == 'E' && isdigit(buf[9]) && buf[10] == 'M' && isdigit(buf[11])) /* OK */; /* ..or "MAPxx" for a DOOM 2 level */ else if(buf[8] == 'M' && buf[9] == 'A' && buf[10] == 'P' && isdigit(buf[11]) && isdigit(buf[12])) rv = +1; else continue; /* Remember _all_ episode and mission numbers. Most PWADs have only a single episode in them so realloc() gets called once here. In the worst case (a total conversion like INVASION.WAD), it will get called 36 times. */ if((new_eps = realloc(eps, (num_eps + 1) * 8)) == NULL) { fclose(f); printf("Error: out of memory\n"); if(eps != NULL) free(eps); return -1; } eps = new_eps; new_eps = &eps[num_eps * 8]; num_eps++; memcpy(new_eps, &buf[8], 8); } fclose(f); *eps_ptr = eps; *num_eps_ptr = num_eps; return rv; } /***************************************************************************** each pointer points to a string of the form "EnMnXXXX" (DOOM 1) or "MAPnnXXX" (DOOM 2) *****************************************************************************/ static int episode_cmp(const void *left_ptr, const void *right_ptr) { const char *left, *right; left = (const char *)left_ptr; right = (const char *)right_ptr; /* DOOM 1: strlen("ExMx")==4 DOOM 2: strlen("MAPxx")==5 */ return (*left == 'E') ? strncmp(left, right, 4) : strncmp(left, right, 5); }; /***************************************************************************** returns number of 838-ns "jiffies" since midnight *****************************************************************************/ static uint32_t read_timer(void) { static char init; /**/ unsigned char lsb, msb; unsigned msw, lsw; unsigned long rv; if(!init) { /* wait for next tick; meaning a timer interrupt just ocurred, and we have some time before the next one */ msw = peekw(0x40, 0x6C); while(msw == peekw(0x40, 0x6C)) /* nothing */; /* make sure timer is in mode #2 */ outportb(0x43, 0x34); outportb(0x40, 0); outportb(0x40, 0); /* wait for next tick for timer to settle down */ msw = peekw(0x40, 0x6C); while(msw == peekw(0x40, 0x6C)) /* nothing */; init = 1; } /* MSW of count comes from the BIOS (18.2 Hz "timer ticks since midnight") */ msw = peekw(0x40, 0x6C); /* latch timer value */ outportb(0x43, 0); lsb = inportb(0x40); msb = inportb(0x40); /* trying to avoid Turbo C shift bug... */ lsw = msb; lsw <<= 8; lsw |= lsb; /* counter counts down -- invert to get count that goes up. DJGPP needs the "& 0xFFFF" */ lsw = (~lsw) & 0xFFFF; rv = msw; rv <<= 16; rv |= lsw; return rv; } /****************************************************************************/ static unsigned my_rand(void) { return rand() ^ (unsigned)read_timer(); } /****************************************************************************/ /* default skill level: ultraviolence */ #define SKILL 4 int main(int arg_c, char *arg_v[]) { static char doom_cmd_line[1024]; /**/ unsigned j, k, num_wads, num_eps = 0, episode, mission, skill = SKILL; char path[MAXPATH] = "", *eps = NULL; wad_t *wads = NULL, *the_wad; int doom2; enum { WW_CMD_LINE = 1, WW_RANDOM, WW_OLDEST } which_wad = 0; enum { WE_FIRST, WE_RANDOM } which_episode = WE_FIRST; srand((unsigned)time(NULL)); check_lfn(); if(arg_c < 2) { printf("Runs DOOM with PWAD file. Usage:\n" "Play PWAD 'file.wad':\t\t\t" "RUNDOOM [-s N][-d DIR][-e] file.wad\n" "Play random PWAD:\t\t\t" "RUNDOOM [-s N][-d DIR][-e] -r\n"); if(g_lfn_supported) printf("Play least-recently-accessed PWAD:\t" "RUNDOOM [-s N][-d DIR][-e] -o\n"); printf( "Look for PWAD files in directory DIR (else current dir):\t" "-d DIR\n" "Play random episode in PWAD (else first episode):\t\t" "-e\n" "Skill level N (0=nomonsters,5=nightmare,default=4)\t\t" "-s N\n"); return 1; } /* process command line */ for(j = 1; j < arg_c; j++) { /* PWAD file name */ if(arg_v[j][0] != '-') { if(which_wad != 0) ERR: { printf("Error: PWAD name, -r option, and -o" " option are mutually exclusive\n"); return 1; } strcat(path, arg_v[j]); which_wad = WW_CMD_LINE; } /* play least-recently-accessed PWAD */ else if(tolower(arg_v[j][1]) == 'o') { if(which_wad != 0) goto ERR; which_wad = WW_OLDEST; if(!g_lfn_supported) { printf("Error: file access time (atime) not" " supported by this version of DOS\n"); return 2; } } /* random PWAD */ else if(tolower(arg_v[j][1]) == 'r') { if(which_wad != 0) goto ERR; which_wad = WW_RANDOM; } /* directory in which to look for PWAD files */ else if(tolower(arg_v[j][1]) == 'd') { if(which_wad == WW_CMD_LINE) { printf("Error: -d option must come " "before PWAD file\n"); return 1; } if(j + 1 == arg_c) goto ARG; j++; strcpy(path, arg_v[j]); /* append '/' if necessary */ k = strlen(path); if(path[k - 1] != '/' && path[k - 1] != '\\') strcpy(&path[k], "/"); } /* skill level */ else if(tolower(arg_v[j][1]) == 's') { if(j + 1 == arg_c) ARG: { printf("Error: %s option requires argument\n", arg_v[j]); return 1; } j++; skill = atoi(arg_v[j]); } /* random episode */ else if(tolower(arg_v[j][1]) == 'e') which_episode = WE_RANDOM; else { printf("Error: invalid command-line option %s\n", arg_v[j]); return 1; } } /* find PWAD */ if(which_wad != WW_CMD_LINE) { if(find_wads(&wads, &num_wads, path)) return 3; if(num_wads == 0) { printf("Error: no .WAD files found\n"); return 5; } /* Find .WAD file with oldest atime (time of last access). This requires the enhanced filesystem functionality of DOS 7.0+ (i.e. VFAT). */ if(which_wad == WW_OLDEST) { j = 0; for(k = 0; k < num_wads; k++) { if(wads[j].atime > wads[k].atime) j = k; } the_wad = &wads[j]; } /* choose a file at random */ else /*if(which_wad == WW_RANDOM)*/ { j = my_rand(); printf("rand()=%u, num_wads=%u, ", j, num_wads); j %= num_wads; printf("random file=%u\n", j); the_wad = &wads[j]; } /* prepend '/' to filename if necessary */ j = strlen(path); if(path[j - 1] != '/' && path[j - 1] != '\\') strcat(path, "/"); strcat(path, the_wad->name); } /* verify it's a PWAD and load episodes */ if((doom2 = load_episodes(&eps, &num_eps, path)) < 0) { if(wads != NULL) free(wads); return 4; } /* did we find anything? */ if(num_eps == 0) { printf("Error: no map (level) in WAD file '%s'\n" "(sound or graphics only?)\n", path); if(wads != NULL) free(wads); if(eps != NULL) free(eps); return 5; } printf("%u episode(s) in WAD file '%s'\n", num_eps, path); /* play first episode in PWAD file */ if(which_episode == WE_FIRST) { qsort(eps, num_eps, 8, episode_cmp); j = 0; } /* choose an episode at random */ else /*if(which_episode == WE_RANDOM)*/ { j = my_rand(); printf("rand()=%u, num_eps=%u, ", j, num_eps); j %= num_eps; printf("random episode=%u\n", j); } if(doom2) episode = atoi(&eps[j * 8 + 3]); else { episode = eps[j * 8 + 1] - '0'; mission = eps[j * 8 + 3] - '0'; } /* cleanup */ if(wads != NULL) free(wads); wads = NULL; num_wads = 0; if(eps != NULL) free(eps); eps = NULL; num_eps = 0; /* run DOOM! */ #if 0 /* DOS; iD versions of DOOM */ #define RUNDOOM1 "doom.exe" #define RUNDOOM2 "doom2.exe" #else /* Windows */ #define RUNDOOM1 "start zdoom.exe" #define RUNDOOM2 "start zdoom.exe" #endif if(doom2) { sprintf(doom_cmd_line, "%s -warp %u -skill %u -file %s", RUNDOOM1, episode, skill, path); printf("%s\n", doom_cmd_line); if(system(doom_cmd_line) == -1) { perror("Can't run DOOM executable"); return 6; } printf("That was level %u from .WAD file '%s'\n", episode, path); } else { sprintf(doom_cmd_line, "%s -warp %u %u -skill %u -file %s", RUNDOOM2, episode, mission, skill, path); printf("%s\n", doom_cmd_line); if(system(doom_cmd_line) == -1) { perror("Can't run DOOM executable"); return 6; } printf("That was E%uM%u from .WAD file '%s'\n", episode, mission, path); } return 0; }