]> 4ch.mooo.com Git - 16.git/blob - src/lib/dl/dos.c
meh
[16.git] / src / lib / dl / dos.c
1 /* dos.c
2  *
3  * Code to detect the surrounding DOS/Windows environment and support routines to work with it
4  * (C) 2009-2012 Jonathan Campbell.
5  * Hackipedia DOS library.
6  *
7  * This code is licensed under the LGPL.
8  * <insert LGPL legal text here>
9  */
10
11 #ifdef TARGET_WINDOWS
12 //# include <windows.h>
13 #endif
14
15 #include <stdio.h>
16 #include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
17 #include <stdlib.h>
18 #include <string.h>
19 #include <stddef.h>
20 #include <unistd.h>
21 #include <malloc.h>
22 #include <assert.h>
23 #include <fcntl.h>
24 #include <dos.h>
25
26 #include "src/lib/doslib/cpu.h"
27 #include "src/lib/doslib/dos.h"
28 //#include <hw/dos/doswin.h>
29 //#include <hw/dos/dosntvdm.h>
30
31 /* DEBUG: Flush out calls that aren't there */
32 #ifdef TARGET_OS2
33 # define int86 ___EVIL___
34 # define int386 ___EVIL___
35 # define ntvdm_RegisterModule ___EVIL___
36 # define ntvdm_UnregisterModule ___EVIL___
37 # define _dos_getvect ___EVIL___
38 # define _dos_setvect ___EVIL___
39 #endif
40
41 struct lib_dos_options lib_dos_option={0};
42
43 /* DOS version info */
44 uint8_t dos_flavor = 0;
45 uint16_t dos_version = 0;
46 uint32_t freedos_kernel_version = 0;
47 const char *dos_version_method = NULL;
48
49 #if TARGET_MSDOS == 32
50 char *freedos_kernel_version_str = NULL;
51 #else
52 char far *freedos_kernel_version_str = NULL;
53 #endif
54
55 void probe_dos() {
56 #if TARGET_MSDOS == 32 && 0
57         assert(sizeof(struct dpmi_realmode_call) == 0x32);
58         assert(offsetof(struct dpmi_realmode_call,ss) == 0x30);
59         assert(offsetof(struct dpmi_realmode_call,cs) == 0x2C);
60 #endif
61
62         if (dos_version == 0) {
63 #ifdef TARGET_WINDOWS
64 # if TARGET_MSDOS == 32
65 #  ifdef WIN386
66 /* =================== Windows 3.0/3.1 Win386 ================= */
67                 DWORD raw = GetVersion(); /* NTS: The Win16 version does not tell us if we're running under Windows NT */
68
69                 dos_version_method = "GetVersion";
70                 dos_version = (((raw >> 24UL) & 0xFFUL) << 8UL) | (((raw >> 16UL) & 0xFFUL) << 0UL);
71
72                 /* Windows NT/2000/XP NTVDM.EXE lies to us, reporting Windows 95 numbers and MS-DOS 5.0 */
73                 if (dos_version == 0x500) {
74                         uint16_t x = 0;
75
76                         /* Sorry Microsoft, but you make it hard for us to detect and we have to break your OS to find the info we need */
77                         __asm {
78                                 mov     ax,0x3306
79                                 mov     bx,0
80                                 int     21h
81                                 jc      err1
82                                 mov     x,bx
83 err1:
84                         }
85
86                         if (x != 0 && x != 0x005) { /* Once pushed to reveal the true DOS version, NTVDM.EXE responds with v5.50 */
87                                 dos_version = (x >> 8) | (x << 8);
88                                 dos_version_method = "INT 21h AX=3306h/NTVDM.EXE";
89                         }
90                 }
91 #  else
92 /* =================== Windows 32-bit ================== */
93                 DWORD raw;
94                 /* GetVersion() 32-bit doesn't return the DOS version at all. The upper WORD has build number instead. */
95                 /* Instead, use GetVersionEx() to detect system. If system is Windows 3.1 or 9x/ME we might be able
96                  * to get away with abusing the DPMI server deep within Windows to get what we want. Else, if it's
97                  * Windows NT, we simply assume v5.50 */
98
99                 /* assume v5.0 */
100                 dos_version = 0x500;
101                 dos_version_method = "Guessing";
102
103                 /* use the Win32 version of GetVersion() to determine what OS we're under */
104                 raw = GetVersion();
105                 if (raw & 0x80000000UL) { /* Windows 9x/ME */
106                         /* Start by guessing the version number based on which version of Windows we're under */
107                         unsigned char major = raw & 0xFF,minor = (raw >> 8) & 0xFF,ok=0;
108
109                         dos_version_method = "Guessing by Windows version";
110                         if (major < 4) { /* Windows 3.1 Win32s */
111                                 dos_version = 0x616;    /* Assume MS-DOS 6.22, though it could be 6.20 or even 6.00 */
112                         }
113                         else if (major == 4) { /* Windows 95/98/ME */
114                                 if (minor >= 90)
115                                         dos_version = 0x800; /* Windows ME (8.00) */
116                                 else if (minor >= 10)
117                                         dos_version = 0x70A; /* Windows 98 (7.10) */
118                                 else
119                                         dos_version = 0x700; /* Windows 95 */
120                         }
121
122                         /* Try: Windows 9x/ME QT_Thunk hack to call down into the Win16 layer's version of GetVersion() */
123                         if (!ok && major == 4 && Win9xQT_ThunkInit()) {
124                                 DWORD fptr,raw=0;
125
126                                 fptr = GetProcAddress16(win9x_kernel_win16,"GETVERSION");
127                                 if (fptr != 0) {
128                                         dos_version_method = "Read from Win16 GetVersion() [32->16 QT_Thunk]";
129
130                                         {
131                                                 __asm {
132                                                         mov     edx,fptr
133                                                         mov     eax,dword ptr [QT_Thunk]
134
135                                                         ; QT_Thunk needs 0x40 byte of data storage at [EBP]
136                                                         ; give it some, right here on the stack
137                                                         push    ebp
138                                                         mov     ebp,esp
139                                                         sub     esp,0x40
140
141                                                         call    eax     ; <- QT_Thunk
142
143                                                         ; release stack storage
144                                                         mov     esp,ebp
145                                                         pop     ebp
146
147                                                         ; take Win16 response in DX:AX translate to EAX
148                                                         shl     edx,16
149                                                         and     eax,0xFFFF
150                                                         or      eax,edx
151                                                         mov     raw,eax
152                                                 }
153                                         }
154
155                                         if (raw != 0) {
156                                                 dos_version = (((raw >> 24UL) & 0xFFUL) << 8UL) | (((raw >> 16UL) & 0xFFUL) << 0UL);
157                                                 ok = 1;
158                                         }
159                                 }
160                         }
161                         /* Tried: Windows 3.1 with Win32s. Microsoft Win32 documentation gleefully calls Dos3Call "obsolete",
162                          *        yet inspection of the Win32s DLLs shows that W32SKRNL.DLL has a _Dos3Call@0 symbol in it
163                          *        that acts just like the Win16 version, calling down into DOS, and most of the Win32s DLLs
164                          *        rely on it quite heavily to implement Win32 functions (the GetSystemTime function for example
165                          *        using it to call INT 21h AH=2Ah).
166                          *
167                          *        Some old MSDN documentation I have has a list of INT 21h calls and corresponding Win32
168                          *        functions to use. Again of course, they skip right over "Get MS-DOS version", no help there.
169                          *
170                          *        Anyway, calling this function with AX=0x3306 or AH=0x30 yields no results. Apparently, Microsoft
171                          *        implemented passing through file I/O, date/time, and code page conversions, yet never considered
172                          *        people might use it for something like... asking DOS it's version number. Attempting to make
173                          *        these calls yields zero in AX and BX, or for AX=3306, a false return number that would imply
174                          *        MS-DOS v1.0 (EAX=1). So, _Dos3Call@0 is not an option.
175                          *
176                          *        But then that means we have absolutely no way to determine the DOS kernel version (short of
177                          *        poking our nose into segments and memory locations we have no business being in!). We can't
178                          *        use _Dos3Call@0, we can't use GetVersion() because the Win32 GetVersion() doesn't return
179                          *        the DOS version, and we can't use Win95 style thunks because Win32s doesn't have a publicly
180                          *        available and documented way to thunk down into Win16. We have absolutely jack shit to go by.
181                          *
182                          *        Hey, Microsoft... When you released Win32s in 1993, did you ever stop to consider someone
183                          *        might want to do something as simple as query the DOS version? Why couldn't you guys have
184                          *        done something straightforward like a "GetDOSVersion()" API function that works under
185                          *        Windows 9x/ME and returns an error under NT? I know it's silly of me to ask this in 2012
186                          *        when Windows 8 is around the corner and Win32s are long dead, but often it seems like you
187                          *        guys really don't stop to think about things like that and you make really stupid mistakes
188                          *        with your APIs. */
189                 }
190                 else {
191                         dos_version = 0x532; /* Windows NT v5.50 */
192                 }
193 #  endif
194 # elif TARGET_MSDOS == 16
195 /* =================== Windows 16-bit ================== */
196                 DWORD raw = GetVersion(); /* NTS: The Win16 version does not tell us if we're running under Windows NT */
197
198                 dos_version_method = "GetVersion";
199                 dos_version = (((raw >> 24UL) & 0xFFUL) << 8UL) | (((raw >> 16UL) & 0xFFUL) << 0UL);
200
201                 /* Windows NT/2000/XP NTVDM.EXE lies to us, reporting Windows 95 numbers and MS-DOS 5.0 */
202                 if (dos_version == 0x500) {
203                         uint16_t x = 0;
204
205                         /* Sorry Microsoft, but you make it hard for us to detect and we have to break your OS to find the info we need */
206                         __asm {
207                                 mov     ax,0x3306
208                                 mov     bx,0
209                                 int     21h
210                                 jc      err1
211                                 mov     x,bx
212 err1:
213                         }
214
215                         if (x != 0 && x != 0x005) { /* Once pushed to reveal the true DOS version, NTVDM.EXE responds with v5.50 */
216                                 dos_version = (x >> 8) | (x << 8);
217                                 dos_version_method = "INT 21h AX=3306h/NTVDM.EXE";
218                         }
219                 }
220
221                 /* TODO: DOS "flavor" detection */
222                 /* TODO: If FreeDOS, get the kernel version and allocate a selector to point at FreeDOS's revision string */
223 # else
224 #  error dunno
225 # endif
226 #elif defined(TARGET_OS2)
227 /* =================== OS/2 ==================== */
228                 dos_version = (10 << 8) | 0;
229                 dos_version_method = "Blunt guess";
230
231 # if TARGET_MSDOS == 32
232                 {
233                         ULONG major=0,minor=0,rev=0;
234                         DosQuerySysInfo(QSV_VERSION_MAJOR,QSV_VERSION_MAJOR,&major,sizeof(major));
235                         DosQuerySysInfo(QSV_VERSION_MINOR,QSV_VERSION_MINOR,&minor,sizeof(minor));
236                         DosQuerySysInfo(QSV_VERSION_REVISION,QSV_VERSION_REVISION,&rev,sizeof(rev));
237                         if (major != 0) {
238                                 dos_version_method = "DosQuerySysInfo (OS/2)";
239                                 dos_version = (major << 8) | minor;
240                                 /* TODO: store the revision value too somewhere! */
241                         }
242                 }
243 # elif TARGET_MSDOS == 16
244                 {
245                         USHORT x=0;
246                         DosGetVersion(&x);
247                         if (x != 0) {
248                                 dos_version_method = "DosGetVersion (OS/2)";
249                                 dos_version = x;
250                         }
251                 }
252 # else
253 #  error dunno
254 # endif
255 #else
256 /* =================== MS-DOS ================== */
257                 union REGS regs;
258
259                 regs.w.ax = 0x3000;
260 # if TARGET_MSDOS == 32
261                 int386(0x21,&regs,&regs);
262 # else
263                 int86(0x21,&regs,&regs);
264 # endif
265                 dos_version = (regs.h.al << 8) | regs.h.ah;
266                 dos_version_method = "INT 21h AH=30h";
267
268                 if (dos_version >= 0x500 && regs.h.bh == 0xFD) {
269                         dos_flavor = DOS_FLAVOR_FREEDOS;
270                         freedos_kernel_version = (((uint32_t)regs.h.ch) << 16UL) |
271                                 (((uint32_t)regs.h.cl) << 8UL) |
272                                 ((uint32_t)regs.h.bl);
273
274                         /* now retrieve the FreeDOS kernel string */
275                         /* FIXME: Does this syscall have a way to return an error or indicate that it didn't return a string? */
276                         regs.w.ax = 0x33FF;
277 # if TARGET_MSDOS == 32
278                         int386(0x21,&regs,&regs);
279 # else
280                         int86(0x21,&regs,&regs);
281 # endif
282
283 # if TARGET_MSDOS == 32
284                         freedos_kernel_version_str = (unsigned char*)(((uint32_t)regs.w.dx << 4UL) + (uint32_t)regs.w.ax);
285 # else
286                         freedos_kernel_version_str = MK_FP(regs.w.dx,regs.w.ax);
287 # endif
288                 }
289                 else if (dos_version >= 0x200 && regs.h.bh == 0xFF)
290                         dos_flavor = DOS_FLAVOR_MSDOS;
291
292                 /* but, SETVER can arrange for DOS to lie to us. so get the real version via
293                  * undocumented subfunctions (DOS 5.0+ or later, apparently) */
294                 regs.w.ax = 0x3306;             /* AH=0x33 AL=0x06 */
295                 regs.w.bx = 0;                  /* in case early DOS versions fail to set CF set BX to zero */
296 # if TARGET_MSDOS == 32
297                 int386(0x21,&regs,&regs);
298 # else
299                 int86(0x21,&regs,&regs);
300 # endif
301                 if ((regs.w.cflag & 1) == 0 && regs.h.bl >= 5 && regs.h.bl <= 8) {
302                         dos_version = (regs.h.bl << 8) | regs.h.bh;
303                         dos_version_method = "INT 21h AX=3306h";
304                 }
305 #endif
306         }
307 }
308