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.
7 * This code is licensed under the LGPL.
8 * <insert LGPL legal text here>
12 //# include <windows.h>
16 #include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
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>
31 /* DEBUG: Flush out calls that aren't there */
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___
41 struct lib_dos_options lib_dos_option={0};
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;
49 #if TARGET_MSDOS == 32
50 char *freedos_kernel_version_str = NULL;
52 char far *freedos_kernel_version_str = NULL;
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);
62 if (dos_version == 0) {
64 # if TARGET_MSDOS == 32
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 */
69 dos_version_method = "GetVersion";
70 dos_version = (((raw >> 24UL) & 0xFFUL) << 8UL) | (((raw >> 16UL) & 0xFFUL) << 0UL);
72 /* Windows NT/2000/XP NTVDM.EXE lies to us, reporting Windows 95 numbers and MS-DOS 5.0 */
73 if (dos_version == 0x500) {
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 */
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";
92 /* =================== Windows 32-bit ================== */
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 */
101 dos_version_method = "Guessing";
103 /* use the Win32 version of GetVersion() to determine what OS we're under */
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;
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 */
113 else if (major == 4) { /* Windows 95/98/ME */
115 dos_version = 0x800; /* Windows ME (8.00) */
116 else if (minor >= 10)
117 dos_version = 0x70A; /* Windows 98 (7.10) */
119 dos_version = 0x700; /* Windows 95 */
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()) {
126 fptr = GetProcAddress16(win9x_kernel_win16,"GETVERSION");
128 dos_version_method = "Read from Win16 GetVersion() [32->16 QT_Thunk]";
133 mov eax,dword ptr [QT_Thunk]
135 ; QT_Thunk needs 0x40 byte of data storage at [EBP]
136 ; give it some, right here on the stack
141 call eax ; <- QT_Thunk
143 ; release stack storage
147 ; take Win16 response in DX:AX translate to EAX
156 dos_version = (((raw >> 24UL) & 0xFFUL) << 8UL) | (((raw >> 16UL) & 0xFFUL) << 0UL);
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).
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.
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.
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.
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
191 dos_version = 0x532; /* Windows NT v5.50 */
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 */
198 dos_version_method = "GetVersion";
199 dos_version = (((raw >> 24UL) & 0xFFUL) << 8UL) | (((raw >> 16UL) & 0xFFUL) << 0UL);
201 /* Windows NT/2000/XP NTVDM.EXE lies to us, reporting Windows 95 numbers and MS-DOS 5.0 */
202 if (dos_version == 0x500) {
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 */
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";
221 /* TODO: DOS "flavor" detection */
222 /* TODO: If FreeDOS, get the kernel version and allocate a selector to point at FreeDOS's revision string */
226 #elif defined(TARGET_OS2)
227 /* =================== OS/2 ==================== */
228 dos_version = (10 << 8) | 0;
229 dos_version_method = "Blunt guess";
231 # if TARGET_MSDOS == 32
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));
238 dos_version_method = "DosQuerySysInfo (OS/2)";
239 dos_version = (major << 8) | minor;
240 /* TODO: store the revision value too somewhere! */
243 # elif TARGET_MSDOS == 16
248 dos_version_method = "DosGetVersion (OS/2)";
256 /* =================== MS-DOS ================== */
260 # if TARGET_MSDOS == 32
261 int386(0x21,®s,®s);
263 int86(0x21,®s,®s);
265 dos_version = (regs.h.al << 8) | regs.h.ah;
266 dos_version_method = "INT 21h AH=30h";
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);
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? */
277 # if TARGET_MSDOS == 32
278 int386(0x21,®s,®s);
280 int86(0x21,®s,®s);
283 # if TARGET_MSDOS == 32
284 freedos_kernel_version_str = (unsigned char*)(((uint32_t)regs.w.dx << 4UL) + (uint32_t)regs.w.ax);
286 freedos_kernel_version_str = MK_FP(regs.w.dx,regs.w.ax);
289 else if (dos_version >= 0x200 && regs.h.bh == 0xFF)
290 dos_flavor = DOS_FLAVOR_MSDOS;
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,®s,®s);
299 int86(0x21,®s,®s);
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";