; various tests we do on the environment. ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept 386MAXExist, '386MAX', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = Display386MaxExist proc near ; Display state of whether 386MAX is present ; input ax not zero of 386MAX present saystr '386Max' call SayIfDetected ret Display386MaxExist endp ; = = = = = = = = = = = = = = = = = = = = = = Test386MAXExist proc near ; Test for presence of 386MAX, leave 1 in AX if found ; Test works by testing for device (not file) calledh 386MAX$$ ; This test could be fooled by a file of the same name. data segment MAXsig db '386MAX$$',0 data ends mov ax,03d00h ; Open file, read only lea dx,MAXsig ; called 386MAX$$ int 021h jc MaxAbsent ; If CF=1, return errcode MaxPresent: mov bx,ax mov ah,3Eh ; close the driver int 21h mov ax,1 ret MaxAbsent: mov ax,0 ; presume not present. ret Test386MAXExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept 4DOSExist, '4DOS', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = Display4DOSExist proc near ; Display state of whether 4DOS is present ; input ax not zero of 4DOS present saystr '4DOS' call SayIfDetected ret Display4DOSExist endp ; = = = = = = = = = = = = = = = = = = = = = = Test4DOSExist proc near ; Test for presence of 4DOS, leave 1 in AX if found ; Method from 4DOS manual ; To detect 4DOS, call INT 2Fh with: ; AX = D44Dh ; BX = 0 ; ; If 4DOS is not loaded, AX should be returned unchanged. If ; 4DOS is loaded, it will return the following (no other ; registers are modified): ; AX = 44DDh ; BX = Version number (BL = major version, BH = minor ; version) ; CX = 4DOS PSP segment address ; DL = 4DOS shell number ; ; 4DOS will be detected, even if COMMAND.COM is used under 4DOS. mov al,02fh call SafeVector ; see if 2fh is hooked up to anything jc FDOSAbsent mov ax,0D44Dh mov bx,0 int 2Fh cmp ax,0D44Dh ; see if AX changed je FDOSAbsent FDOSPresent: mov ax,1 ret FDOSAbsent: mov ax,0 ; presume not present. ret Test4DOSExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept A20, 'A20', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayA20 proc near ; Displays current state of A20 line. ; Presumes Call TestA20 called just prior, so AX has state saystr 'A20' call SayIfActive ret DisplayA20 endp ; = = = = = = = = = = = = = = = = = = = = = = TestA20 proc near ; Returns the state of the A20 line ; Returns AX = 1 if A20 line enabled, 0 otherwise ; Make compare of low and high RAM TWICE. ; In between the tests, change low RAM so that false match ; will be unmasked. ; Based on code inside Microsoft's HIMEM.SYS ; auxilliary data to help determine A20 status data segment even LowMemory label dword ; Set equal to 0000:0080 dw 00080h dw 00000h HighMemory label dword dw 00090h ; Set equal to FFFF:0090 dw 0FFFFh data ends push ds push es lds si,cs:LowMemory ; Compare byte at 0000:0080 les di,cs:HighMemory ; with the byte at FFFF:0090 cli ; freeze interrupts in case ; someone else accesses low RAM mov bl,DS:[si] mov bh,ES:[di] cmp bl,bh jne A20On ; jump if bytes differ ; rep do test again, this time with low ram incremented inc byte ptr ds:[si] ; change low ram mov bl,DS:[si] mov bh,ES:[di] dec byte ptr ds:[si] ; put low ram back the way it was sti ; interrupts back on. cmp bl,bh je A20Off ; jump if areas same A20On: mov ax,1 ; no wrap, were different jmp Short goHome A20Off: mov ax,0 ; wrap, were equal ; Yes, return A20 Disabled GoHome: sti ; interrupts back on. pop es pop ds ret TestA20 endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept ANSIExist, 'ANSI', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayANSIExist proc near ; Displays current state of ANSI.SYS driver. ; Presumes Call TestANSIExist called just prior, so AX has state saystr 'ANSI screen driver' call SayIfDetected ret DisplayANSIExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestANSIExist proc near ; returns 1 in ax if ANSI.SYS loaded, 0 otherwise. ; Uses two of the possible three methods. mov al,02fh call SafeVector ; see if 2fh is hooked up to anything jc UseANSITest2 call RAWDOSVer cmp ax,400 ; fast method only works on DOS 4.00+ jb UseANSITest2 call TestANSIExist1 ; use fast test test ax,ax jz UseANSITest2 ; Second test might see something test 1 missed. ret UseANSITest2: call TestANSIExist2 ; use the cursor position report test. ret TestANSIExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestANSIExist1 proc near ; returns 1 in ax if ANSI.SYS loaded, 0 otherwise. ; Fast method from Undocumented DOS page 657. Only works in DOS 4+. ; Cannot see DVANSI.SYS mov ax,1a00h ; SafeVector on 2F already done. int 2fh cmp al,0FFh ; if FF, means it is installed jne ANSIAbsent1 ANSIPresent1: mov ax,1 ret ANSIAbsent1: mov ax,0 ret TestANSIExist1 endp ; = = = = = = = = = = = = = = = = = = = = = = TestANSIExist2 proc near ; Test for presence of ANSI.SYS. Leave 1 in ax if found 0 otherwise. ; The second method fails when using redirection e.g. SNIFF ANSI > TEMP.TXT ; See Ray Duncan's Advanced MS DOS page 92. ; Windows /r has a bug. You need to hit a key to retrieve the cursor ; positioning response. ; ; Alternate method works by sending a device status request ; To the screen esc [ 6 n. ; If nothing happens, we have no ANSI.SYS, and we erase the gibberish ; with backspaces, spaces and more backspaces. ; If we get a report coming in as if from the keyboard Esc [99;99R ; that is ANSI reporting the cursor position. There is nothing to erase. ; The complication, discovered by aGurski, is that not all ANSI.SYS ; drivers deliver dual-digit row-col numbers. We must be prepared for ; 1 to 3 digits. String segment ANSIRequest db 27,'[6n','$' ANSIErase db 8,8,8,8,32,32,32,32,8,8,8,8,'$' String ends ; This test won't work if redirection is invoked. ; Test first if output is redirected. See Microsoft Systems Journal ; 1993 January Q&A column. call IsStdOutRedirected test ax,ax jz CanDoANSI2Test mov ax,0 ; can't do test, return not found ; nearly always we have already done an ; ANSI1 test already. ret CanDoANSI2Test: ; clear keystroke buffer ClearKeyBuffer: ; user keystrokes could confuse the test mov ah,0bH ; check if char input waiting int 21h test al,al jz KeyBuffClear ; no more waiting, we are ready to go mov ah,08H ; read the char int 21h jmp ClearKeyBuffer KeyBuffClear: ; send request for device status ; esc [ 6 n say ANSIRequest mov ah,0bH ; check if char input waiting int 21h test al,al jz ANSIAbsent2 ; can't be ANSI if no response ; expect string of form esc [99;99R mov ah,08H ; read the ESC int 21h ; windows in REAL mode hangs till you cmp al,27 ; hit keyboard to prod it along jne ANSIAbsent2 mov ah,0bH ; check if char waiting int 21h test al,al jz ANSIAbsent2 ; can't be ANSI if no response mov ah,08H ; read the [ int 21h cmp al,'[' jne ANSIAbsent2 mov cx,8 ; bypass up to the next 8 chars 999;999R IgnoreANSILoop: mov ah,0bH ; check if char waiting int 21h test al,al jz ANSIAbsent2 ; give up if no char waiting mov ah,08H ; read the char int 21h cmp al,'R' ; R marks the end je ANSIPresent2 loop IgnoreANSIloop jz ANSIAbsent2 ANSIPresent2: mov ax,1 ret ANSIAbsent2: say ANSIErase ; erase junk we put on screen mov ax,0 ret TestANSIExist2 endp ; = = = = = = = = = = = = = = = = = = = = = = IsStdOutRedirected proc near ; Test if output is redirected. See Microsoft Systems Journal ; 1993 January Q&A column. Return 1= true in AX. push bx push dx mov ax,4400h mov bx,1 ; stdout handle int 21h and dl, 82h ; plain = bit 7 char dev, bit 1 = stdout cmp dl, 82h pop dx pop bx jne IsRedir NotRedir: mov ax,0 ret IsRedir: mov ax,1 ret IsStdOutRedirected endp ; = = = = = = = = = = = = = = = = = = = = = = comment î ; T H I S C O D E I S N O T U S E D TestANSIExist3 proc near ; Test for presence of ANSI.SYS. Leave 1 in ax if found 0 otherwise. ; This method is the poorest of the three. I have never seen it work. ; Perhaps I have misunderstood it, or perhaps it only works on some ; few versions of ANSI.SYS. I have included it here just for interest. ; The code is commented out. String segment ANSISig db 'CON' String ends mov ax,3529h ; Get undocumented INT 29 vector. ; see Undocumented DOS page 576 ; Method inspired by Michael Mefford's ; ANSI.COM, sent by jsinstadt. int 21h ; works in DOS 2+ lea si,ANSISig ; Does it point to ANSI.SYS? mov di,10 ; ES:10 points to possible signature mov cx,3 ; as device name. repe cmpsb jne ANSIAbsent3 ANSIPresent3: mov ax,1 ret ANSIAbsent3: mov ax,0 ret TestANSIExist3 endp î ; end of comment ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept ANDOperator, 'AND', ImpliedStyle , 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayAndOperator proc near ; Dummy test display routine to display the AND operator saystr '-- AND --' ret DisplayAndOperator endp ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept APPENDExist, 'APPEND', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayAPPENDExist proc near ; Displays current state of APPEND driver. ; Presumes Call TestAPPENDExist called just prior, so AX has state saystr 'Append' call SayIfDetected ret DisplayAPPENDExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestAPPENDExist proc near ; Tests if APPEND loaded. Leaves 1 in AX if yes ; See Undocumented DOS page 668. ; See Advanced DOS page 490. mov al,2fh call SafeVector ; see if vector is hooked up to anything jc APPENDAbsent ; if not, we can't detect APPEND call RAWDOSVer cmp ax,330 ; only works in DOS 3.3+ jb AppendAbsent mov ax,0B700h ; function B7 00 of multiplex interrupt ; get installed state int 02Fh cmp al,0FFh ; if FF, means it is installed jne APPENDAbsent APPENDPresent: mov ax,1 ret APPENDAbsent: mov ax,0 ret TestAPPENDExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept ASSIGNExist, 'ASSIGN', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayASSIGNExist proc near ; Displays current state of ASSIGN driver. ; Presumes Call TestASSIGNExist called just prior, so AX has state saystr 'Assign' call SayIfDetected ret DisplayASSIGNExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestASSIGNExist proc near ; Tests if ASSIGN loaded. Leaves 1 in AX if yes, 0 otherwise ; See Advanced MS DOS page 489 for function 0600 method. ; Not the typo: 02 should read 06. ; See Undocumented DOS, page 590 for more 0600 function test. mov al,2fh call SafeVector ; see if vector is hooked up to anything jc ASSIGNAbsent ; cannot detect ASSIGN call RAWDOSVer cmp ax,320 ; only works in DOS 3.2+ jb AssignAbsent mov ax,0600h ; function 06 00 of multiplex interrupt ; get installed state int 02Fh cmp al,0FFh ; if FF, means it is installed jne ASSIGNAbsent ASSIGNPresent: mov ax,1 ret ASSIGNAbsent: mov ax,0 ret TestASSIGNExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept BUS, 'BUS', ImpliedStyle, 1, Any, Plus, Plus accept BUS, 'ISA', ImpliedStyle, 1, Any, Bang, Bang accept BUS, 'MCA', ImpliedStyle, 2, Any, Bang, Bang accept BUS, 'EISA', ImpliedStyle, 3, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayBUS proc near ; on entry ax=1 for ISA, 2 for MCA, 3 for EISA ; display which kind we have saywhen 1,'ISA BUS type' saywhen 2,'MCA BUS type' saywhen 3,'EISA BUS type' ret DisplayBUS endp ; = = = = = = = = = = = = = = = = = = = = = = TestBUS proc near ; ; returns BUS type in AX. 3=EISA 2=MCA 1=ISA ; ; Determines what kind of BUS you have. ; MCA by looking at bit 1 in the config byte after bios revision. ; EISA by looking for signature at FFD9 ROMBIOS segment AT 0f000h ; dummy segment inside ROM BIOS org 0ffd9h EISAsig dw ? ; letters EISA org 0fffeh BIOSMachineId db ? ; BIOS machine type code BIOSSubId db ? ; BIOS sub model id code ROMBIOS ends push ES mov ah,0ch int 15h ; get config via INT 15 test ah,ah jnz IsISA ; old machines don't support INT 15 ; ES:bx points to config table test byte ptr ES:[bx+4],1 ; config byte, SJGrant suggested ; this method. jnz IsMCA ; might be ISA or EISA mov ax,ROMBIOS mov ES,ax cmp byte ptr ES:[EISAsig],'E' ; S Costa suggested this method jne IsISA cmp byte ptr ES:[EISAsig+1],'I' jne IsISA cmp byte ptr ES:[EISAsig+2],'S' jne IsISA cmp byte ptr ES:[EISAsig+3],'A' jne IsISA jmp IsEISA IsISA: pop ES mov ax,1 ret IsMCA: pop ES mov ax,2 ret IsEISA: pop ES mov ax,3 ret TestBus endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept BIOS, 'BIOS', ImpliedStyle, 1, Any, Plus, Plus accept BIOS, 'EXOTIC', ImpliedStyle, 1, Any, Bang, Bang accept BIOS, 'XT', ImpliedStyle, 2, Any, Bang, Bang accept BIOS, 'AT', ImpliedStyle, 3, Any, Bang, Bang accept BIOS, 'PS2', ImpliedStyle, 4, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayBios proc near ; on entry ax=2 for XT, 3 for AT, 4 for PS2 ; display which kind we have call DisplayBIOSName saystr " -- " saystr 'machine model ID code ' call GetModId call SayHexByte saystr ":" call GetSubModId call SayHexByte ret DisplayBios endp ; = = = = = = = = = = = = = = = = = = = = = = DisplayBiosName proc near ; on entry ax=2 for XT, 3 for AT, 4 for PS2 ; display which kind we have saywhen 2,'XT BIOS type' saywhen 3,'AT BIOS type' saywhen 4,'PS2 BIOS type' saystr 'Unknown EXOTIC BIOS type' ret DisplayBiosName endp ; = = = = = = = = = = = = = = = = = = = = = = TestBIOS proc near ; ; returns BIOS type in AX. 4=PS2 3=AT 2=XT 1=Unknown ; ; Determines what kind of BIOS you have by looking at the model ID ; byte in ROM location F000:FFFE. ; Information on what model bytes go with which clones comes ; from BIX (mfsargent, rbabcock, WardC), Peter Norton Guides, ; and Mark A Sehorne. This table is not complete. There are other ; PS/2 machines not included. comment û Date Model Submod Rev Descr 03/30/87 F8 00 00 PS/2 8580-041 (16mhz) (-071?) 10/07/87 F8 01 00 PS/2 8580-111/311 (20mhz) 04/11/88 F8 04 02 PS/2 8570-121 04/11/88 F8 09 02 PS/2 8570-E61 09/09/88 F8 0B 01 PS/2 8573-??? ? F8 0C 00 PS/2 8555-031/061 06/22/88 F8 0D 00 PS/2 8570-A21 09/13/85 F9 00 00 AT (PC CONVERTIBLE) 09/02/86 FA 00 00 PS/2 8530 06/26/87 FA 01 00 PS/2 8525 01/10/86 FB 00 01 XT 05/09/86 FB 00 02 XT 01/10/84 FC -- -- AT 06/10/85 FC 00 01 AT 5170-239 11/15/85 FC 01 00 AT 5170-339 (8Mhz) 04/21/86 FC 02 00 AT XT-286 02/13/87 FC 04 00 PS/2 8550-021 04/18/88 FC 04 03 PS/2 8550-031/061 02/13/87 FC 05 00 PS/2 8560 08/25/88 FC 09 00 PS/2 8530-Exx (286) FC 81 Phoenix clone 06/01/83 FD -- -- XT PCJr 11/08/82 FE -- -- XT & Portable 04/24/81 FF -- -- XT Original PC 10/19/81 FF -- -- XT Later PC 10/27/82 FF -- -- XT PC w/hard disk & 640K support û; end of comment call GetModID ; get the model ID in ax cmp al,0ffh ; FF -> XT je IsXT cmp al,0feh ; FE -> XT je IsXT cmp al,0fdh ; FD -> XT je IsXT cmp al,0fbh ; FB -> XT je IsXT cmp al,0fch ; FC -> AT or PS2 je IsATorPS2 cmp al,0f9h ; F9 -> AT je IsAt cmp al,0fah ; FA -> PS2 je IsPS2 cmp al,0f8h ; F8 -> PS2 je IsPS2 IsUnknown: mov ax,1 ; 1 means unknown ret IsAtOrPS2: ; cannot tell just by model byte. call GetSubModId ; must look at submodel byte. cmp al,03h ; 00 .. 03 -> AT jbe IsAt cmp al,80h ; 80 .. -> AT Phoenix jae IsAt jmp IsPS2 ; anything else must be PS/2 -- there ; are no NEW AT codes. IsXT: mov ax,2 ret IsAT: mov ax,3 ret IsPS2: mov ax,4 ret TestBIOS endp ; = = = = = = = = = = = = = = = = = = = = = = GetModId proc near ; Returns models ID byte from F000:FFFE in AX ; See IBM Tech Ref Bios listing push ES mov ax,ROMBIOS ; we DON'T use int 15h, since ; old machines don't support it. mov ES,ax mov al,ES:[BiosMachineId] sub ah,ah pop ES ret GetModId endp ; = = = = = = = = = = = = = = = = = = = = = = GetSubModId proc near ; Int 15 not supported on older machines, to we have to test ; that INT 15 is really working. ; Returns sub model ID byte from in ax. ; See IBM Tech Ref BIOS listing page 5-164 ; Preserves registers. push ES push bx mov ah,0c0h int 15h ; on return ES:BX points to block ; 2-byte size ; 1-byte model id offset 2 ; 1-byte submodel id offset 3 ; 1-byte BIOS level etc jc NoSubmodel call GetModId ; get model Id via old method cmp al,ES:[bx+2] ; compare with model id by new method jne NoSubModel ; if differ, int 15 is broken mov al,ES:[bx+3] ; offset 3 contains the byte want sub ah,ah ; the submodel pop bx pop ES ret NoSubmodel: mov ax,0 pop bx pop ES ret GetSubModId endp ; = = = = = = = = = = = = = = = = = = = = = = comment û ; T H I S C O D E I S N O T U S E D ;This older version trashed to many non-standard machines. I have ;left the code for reference. TestBIOS proc near ; Returns 1 for XT BIOS, 2 for AT BIOS (does not support EXOTIC and PS2) ; Read CMOS month. If cmos not present, will get ff ; IF CMOS present will get 01..12 BCD. ; WARNING -- this code does not work if you single step with Periscope. ; It will also trash incompatible machines like the M24 and NEC machines. cli ; lock out other cmos sniffers ; we will be changing the CMOS port mov al,08h ; offset in CMOS where month byte lives ; high bit on turns off NMI in some bioses. out 70h,al ; select offset in CMOS nop nop in al,71h ; get byte from CMOS mov ah,al ; save month byte mov al,0dh ; point to battery status register out 70h,al ; leave pointing at a safe r/o register sti ; put interrupts back on cmp ah,12h ; month coded in BCD!! jg WasXT ; XT will probably give 0 or FF cmp ah,01h jb WasXT WasAT: mov ax,2 ret WasXT: mov ax,1 ret TestBIOS endp û ; end of comment ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept BtrieveExist, 'BTRIEVE', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayBTRIEVEExist proc near ; Displays current state of BTRIEVE driver. ; Presumes Call TestBTRIEVEExist called just prior, so AX has state saystr 'BTrieve' call SayIfDetected ret DisplayBTRIEVEExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestBTRIEVEExist proc near ; Return level discovered in AX, 0=not found 1=found ; See page 4-34 Btrieve operations manual. ; We do this using INT 35 rather than cheating as Novell suggests. mov ax,357bh ; get vector 7bh into ES:BX int 21h cmp bx,33h ; offset part of vector is 33h jne BtrieveAbsent ; when present. BtrievePresent: mov ax,1 ret BtrieveAbsent: mov ax,0 ; presume not present ret TestBTRIEVEExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; CPUS accept CPUs, 'CPU', ImpliedStyle, 10, Any, Plus, Plus accept CPUs, '8088', ImpliedStyle, 10, Any, Bang, Bang accept CPUs, '80188', ImpliedStyle, 15, Any, Bang, Bang accept CPUs, 'V20', ImpliedStyle, 20, Any, Bang, Bang accept CPUs, '8086', ImpliedStyle, 30, Any, Bang, Bang accept CPUs, '80186', ImpliedStyle, 40, Any, Bang, Bang accept CPUs, 'V30', ImpliedStyle, 50, Any, Bang, Bang accept CPUs, '80286', ImpliedStyle, 200, Any, Bang, Bang accept CPUs, '80386SX', ImpliedStyle, 300, Any, Bang, Bang accept CPUs, 'SX', ImpliedStyle, 300, Any, Bang, Bang accept CPUs, '80386DX', ImpliedStyle, 350, Any, Bang, Bang accept CPUs, '80386', ImpliedStyle, 350, Any, Bang, Bang accept CPUs, '386', ImpliedStyle, 350, Any, Bang, Bang accept CPUs, 'DX', ImpliedStyle, 350, Any, Bang, Bang accept CPUs, '80486DX', ImpliedStyle, 400, Any, Bang, Bang accept CPUs, '80486', ImpliedStyle, 400, Any, Bang, Bang accept CPUs, '486', ImpliedStyle, 400, Any, Bang, Bang accept CPUs, '586', ImpliedStyle, 500, Any, Bang, Bang accept CPUs, 'PENTIUM', ImpliedStyle, 500, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayCPUS proc near ; Displays type of CPU ; Presumes Call TestCPUS called just prior, so AX has state saystr 'CPU type is ' saywhen 10,'8088.' saywhen 15,'80188.' saywhen 20,'V20.' saywhen 30,'8086.' saywhen 40,'80186.' saywhen 50,'V30.' saywhen 200,'80286.' saywhen 300,'80386SX.' saywhen 350,'80386DX.' saywhen 400,'80486DX.' saywhen 500,'PENTIUM.' saystr 'unknown.' ret DisplayCPUS endp ; = = = = = = = = = = = = = = = = = = = = = = TestCPUs proc near ; Function to determine CPU type. ; This routine is a simplification of one written by Mike Blaszcak. ; ; It turn it was strongly based on the routine presented in 'TECH ; Notebook 82', on page 51 of the November 1987 issue of PC Tech Journal. ; ; It also makes use of the code presented in 'Chips In Transition', ; page 56, in the April 1986 issue of PC Tech Journal. ; ; The routine returns the following values in AX for each processor: ; ; 8088 10 ; 80188 15 ; V-20 20 ; 8086 30 ; 80186 40 ; V-30 50 ; 80286 200 ; SX 300 ; 80386 350 ; 80486 400 ; PENTIUM 500 if DEBUGGING test debug,-1 ; display PIQ length if DEBUG command jz HidePIQ saystr ' CPU PIQ length is ' call PIQTest sub dx,dx mov dpl,-1 call SayBigDec call SayCr HidePIQ: endif ; Differentiate between [88, 188, V20, 86, 186, V30] and [286, SX, 386, 486] push sp ; older processors will push pop ax ; SP-2 instead of SP cmp ax,sp jz Is_286Plus ; if equal, SP was pushed ; Differentiate between [188, 186] [88, V20, 86, V30] mov ax,0fffh mov cl,32 ; [188 186] uses count mod 32 = 0 shl ax,cl ; [others] shift 32 times so ax = 0 jnz Is_186or188 ; non-zero: no shift, so 186 ; Differentiate between [V20 V30] and [8088, 8086] comment û There are four methods I could have used to detect the V20/V30. Method 1. This method of detecting NEC chips came compliments of ddunfield. The BCD multiply adjust method only detects V20s, not the V30s. The op code for AAM is D40A. In Intel chips their is an undocumented op code D40B which divides by 11 (divisor is embedded in the op code.) In the NEC V20 D4xx is treatad as divide by 10. The code would have looked like this: ;; SUB bx,bx ;; MOV ax,100d ;; DB 0D4h,0Bh ; encode an undocumented AAM ;; ; BCD mulitiply adjust. A usual one ;; ; is D40A to divide by 10. ;; ; D40B divides by 11 on Intel ;; ; but divides by 10 on NEC. ;; TEST al,al ; if 0, must have divided by 10 ;; JZ Is_V20orV30 Method 2 The most common method used is to exploit the fact that interrupts do not properly restore dual override prefixes in the Intel chips, but they do on the NEC chips. The idea is to just move lot of data for a long time and see if any errors occurred from timer interrupts. There are two modes of failure. See Bob Smith's article in PCTech April 1986 page 56 and July 1986 page 15. This method just smells too imprecise for my tastes. Method 3 The D6 instruction is undefined on the V20/V30 but performs a predictable action on all Intel micros. The method comes from Rick Naro of NEC. I did not use this method simply because I wanted to avoid using illegal instructions in NEED avoid. Some future emulator might trip over them. ;; stc ; Set the carry ;; sub al, al ; Clear AL ;; db d6h ; 'magic' instruction ;; cmp al, 0ffh ; Test the result ;; jne Is_V20orV30 ; Branch accordingly Method 4 The method I chose to use exploits the way the Intel chips scramble the Z flag on multiply. The NEC chips leave it untouched. û; end comment sub al,al ; Set ZF on mov al,40h ; 64 decimal mul al ; 64**2 > 255 jz Is_V20orV30 ; V20/V30 MUL leaves ZF untouched ; 8088/8086 sets ZF off. ; Differentiate between [8088] [8086] Is_88or86: call PIQTest ; 8086 has 6-long instruction queue cmp ax,5 ; 8088 has 4-long jge Is_8086 jmp Is_8088 Is_V20orV30: ; Differentiate between [V20] and [V30] call PIQTest ; V30 has 6-long instruction queue cmp ax,5 ; V20 has 4-long jge Is_V30 jmp Is_V20 ; Differentiate between [188] [186] Is_186or188: call PIQTest ; 186 has 6-long instruction queue cmp ax,5 ; 188 has 4-long jge Is_80186 jmp Is_80188 ; Differentiate between [286] [SX, 386, 486]. ; protected mode now safe to use, though we will never leave real mode. ; We may be in 80286 real mode or 80386 real mode or 80386 virtual mode. Is_286Plus: ; In 80286 real mode, we cannot set the high order flag bits to ; anything but 0. ; In 80386 real mode, we can set them, but they are ignored. ; In 80386 virtual mode, almost for certain the IOPL bits will be 11 ; to stop us from doing i/o. ; Trying to set the bits will not generate an exception, (Ahhhh!) ; At worst we will just be ignored. ; So our strategy is to try to set the bits, and see if there are ; any ones. If there are we have an 80386+, if not an 80286. ; Bit 15 is reserved. ; Bit 14 is the nested task flag (used to delay saving numeric processor ; registers. ; bits 12 and 13 are the i/o priviledge level of tasks allowed to do i/o. ; 11 = everyone 00=only Mama V86 manager. ; This method might fail on some very old CPUS that had bugs in pushf/popf. pushf ; get copy of current flags pop ax or ax,0f000h ; turn on 4 high order bits push ax popf ; attempt to change 4 high bits pushf pop ax ; how did we do? test ax,0f000h ; Don't worry, we did not hurt the flags, ; so there is no need to restore them. jz Is_80286 ; still 0s, must be 80286 ; some 1s, must be 80386+ comment û ; T H I S C O D E I S N O T U S E D ; The following method does NOT work under WINDOWS enhanced mode ; because Windows traps our innocent sgdt, and EMULATES it as ; if it were an 80286. This is the method we used before. ; to discriminate the [286] from [SX 386 + ]. sub sp,6 ; [286] GDT is different mov bp,sp ; allocate stack space for GDT ptr ; OPTASM and MASM 5.0 Style sgdt qword ptr [bp] ; store a copy of GDT 6 bytes ; into the stack add sp,4 ; in 80286 high order byte is 1's pop ax ; but in thu 80386 it is 0's test ah,ah jnz Is_80286 û ; end comment ; Differentiate between [SX 386] [486] ; with /SLOW use Slow486Test but ; even if Mama is watching use FAST486Test test SlowTests,-1 jz UseFast486Test call Slow486Test ; returns 1 for 486, 0 otherwise jmp Analyze486Test UseFast486Test: call fast486Test ; returns 1 for 486, 0 otherwise Analyze486Test: test ax,ax jnz Is_80486orBetter ; Differentiate between [SX] [386] call IsMamaWatching test ax,ax jz UseFastSXTest ; Windows 3/4 enhanced is here ; meddling as usual. We have to use ; innacurate slow tests. call SlowSXTest ; returns 1 for SX, 0 otherwise jmp AnalyzeSXTest UseFastSXTest: call FastSXTest ; also returns 1 for SX 0 otherwise AnalyzeSXTest: test ax,ax jnz Is_SX jmp Is_80386 Is_80486orBetter: call PentiumTest test ax,ax jnz Is_Pentium jmp Is_80486 Is_8088: mov ax,10 ret Is_80188: mov ax,15 ret Is_V20: mov ax,20 ret Is_8086: mov ax,30 ret Is_80186: mov ax,40 ret Is_V30: mov ax,50 ret Is_80286: mov ax,200 ret Is_SX: mov ax,300 ret Is_80386: mov ax,350 ret Is_80486: mov ax,400 ret Is_Pentium: mov ax,500 ret TestCPUs endp ; = = = = = = = = = = = = = = = = = = = = = = PIQTest proc near ; Calculates the length of the processor prefetch queue in bytes. ; Leaves the length in AX ; Used by TestCPUs ; It works by repetitively calling PIQ and taking the longest ; measurement. DMA and other unknown factors interfere with the ; test. push bx ; save bx call PIQ push ax call PIQ push ax call PIQ push ax call PIQ pop bx cmp ax,bx ; pick largest of 4 tests jge piq2 mov ax,bx piq2: pop bx cmp ax,bx jge piq3 mov ax,bx piq3: pop bx cmp ax,bx jge piq4 mov ax,bx piq4: pop bx ; restore bx ret PIQTest endp ; = = = = = = = = = = = = = = = = = = = = = = PIQ proc near ; Calculates the length of the processor prefetch queue in bytes. ; Leaves the length in AX ; Used by TestCPUs ; ; The instruction prefetch queue logic came compliments ; of Steve Grant, with refinements from Terje Mathisen. ; This procedure uses self-modifying code but can nevertheless be run ; repeatedly in the course of the calling program. ; The idea is to dynamically clobber some code then immediately ; execute it. The code in the prefetch queue will not be ; clobbered since it was read into the CPU just prior to the ; change. So the old code will execute. The RAM of course IS ; clobbered for all later executions. It is subtle. Try reading ; the detailed notes to understand it. ; ; Terje Mathisen and others on BIX suggested refinements such ; as paragraph aligning and using MUL/DIV pairs to give the prefetch ; queue time to fill. ; ; 8088 4 bytes Intel iAPX 88 page 2-18 ; 80188 4 bytes ; 8086 6 bytes Intel MCS-86 Product Description, Feb. 79, pp. 4 ; 80186 6 bytes ; V-20 4 bytes NEC Microcomputer Products 1987 Data Book, Vol. 2, pp. 3-52 ; V-30 6 bytes ibid., pp. 3-84 ; V-40 4 bytes ibid., pp. 3-151 ; V-50 6 bytes ibid., pp. 3-217 ; ; SX ? ; 12-14 bytes measured ; ; 80386 16 bytes Intel 386 Microprocessor Hardware Reference Man., pp. 2-3 ; 8-12 bytes measured ; ; 80486 32 bytes Intel i486 Microprocessor Hardware Reference Man., pp. 2-10 ; 32 bytes measured, can be lower if not aligned optimally. MaxQueue equ 50 ; longest a prefetch queue could be op_inc_cx equ 41h ; INC CX opcode op_nop equ 90h ; NOP opcode mov al, op_inc_cx ; prepare to initialize code to ; a string of inc cx mov cx, MaxQueue ; init loop counter push cs pop es ; set up ES: to clobber code lea di,Clobber ; point to last of the inc cx instructs ; OPTASM will convert to MOV offset push cx push di std ; work backwards rep stosb ; reset all inc cx's ; prepare to clobber the same instructions mov ax, op_nop ; prepare to clobber them to nops. pop di pop cx cli ; inhibit interrupts from interfering ; will also inhibit DESQview ; timing. ; make sure queue is full align 16 ; standard measure of queue size ; presumes first instruction in ; queue (16 bytes further from here) ; starts on a paragraph boundary. nop ; pad following section to 16 bytes mov bx,1234h ; dummy value for MUL/DIV jmp short $+2 ; Flush prefetch queue ; to give repeatability ; now interrupts are off mul bx div bx ; MUL/DIV pairs take a long mul bx ; time to execute. This gives div bx ; the BIU plenty of time to fill ; the prefetch queue ; ax shaken, but back where it started rep stosb ; start clobbering the following ; inc dxs. The whole idea is, if ; we have prefetched them, they ; will still get executed as ; inc dx's instead of nops ; By working backwards we avoid ; chasing a receeding rainbow. ; at this point cx is 0. align 16 ; Previous block of code was rigged ; to be 16 bytes long, so this ; align 16 should be nugatory. ; The VERY optimal situation is to have ; one of the instructions just ; ahead of the paragraph boundary, ; since you can have in the 486 ; 32 bytes in the queue and one ; inthe instruction decode pipeline. ; However that would give 33 as the count. ; We don't want to count that extra byte. rept MaxQueue-1 inc cx Clobber: inc cx ; start clobbering the inc cx with ; a nop, work backwards clobbering ; all of them sti ; now safe to turn interrupts back on. cld ; set string direction back to normal mov ax,cx ; each inc cx that survived by ; sneaking into the prefetch queue ; managed to increment cx by 1. ; ax is the prefetch queue length ret PIQ endp ; = = = = = = = = = = = = = = = = = = = = = = Slow486Test proc near ; returns ax=1 if 486, ax=0 if 386 or SX ; This test is not normally needed, since the fast test seems to work ; fine even under Windows 3.0 enhanced. ; It will only be used if /SLOW requests it to avoid a meddling supervisor call PIQTest ; 80386 has an 12-long instruction queue ; SX has a 14-long instruction queue cmp ax,16 ; 80486 has a 32-long queue ; it fetches 16 bytes at at time, paragraph ; aligned. jg Slow486 mov ax,0 ret Slow486: mov ax,1 ret Slow486Test endp ; = = = = = = = = = = = = = = = = = = = = = = Fast486Test proc near ; This test will discriminate a 386 from a 486 ; it returns 1 in ax for 486, 0 for 386 or SX. ; This test can also be used under Windows 3 in enhanced mode. ; This code uses 32 bit registers so I had to hand assemble it. ; IanL on bix helped out my doing the assembly of this fragment ; with his assembler than understands 32 bit codes. ; The 486 has a bit in the Eflags register called the alignment ; check bit. You can use it along with a bit in CR0 to force ; a trap on fetches not optimally aligned. The 80386 has no such ; bit. The AC bit is in bit 18 of the Eflags. ; According to Terje Mathisen, on at least one PC/BIOS combo, ; you cannot change the AC bit in the flags, without FIRST ; enabling the AM bit in CR0! On such a machine our test would ; fail. Changing CR0 is bound to panic VM monitors when some ; freak out if you even LOOK at CR0. db 66h,08Bh,0D4h ; mov edx,esp ; Save current stack ptr db 66h,083h,0E4h,0FCh ; and esp, not 3 ; Align stack db 66h,09Ch ; pushfd ; Push EFlags. db 66h,058h ; pop eax ; Get EFlags value. db 66h,08Bh,0C8h ; mov ecx,eax ; Save original EFlags db 66h,035h,00,00,04,00 ; xor eax,40000h ; Flip AC bit in Eflags db 66h,050h ; push eax ; Copy to EFLAGS db 66h,09Dh ; popfd ; ... db 66h,09Ch ; pushfd ; Get new EFLAGS value. db 66h,058h ; pop eax ; Put into eax. db 66h,033h,0C1h ; xor eax,ecx ; See if AC bit changed db 66h,0C1h,0E8h,012h ; shr eax,18 ; Set EAX=1 if 386 db 66h,051h ; push ecx ; Original EFLAGS value db 66h,09Dh ; popfd ; restored. db 66h,08Bh,0E2h ; mov esp,edx ; Restore original db 66h,025h,01,0,0,0 ; and eax,1 ; Mask out all other bits ret Fast486Test endp ; = = = = = = = = = = = = = = = = = = = = = = PentiumTest proc near ; This test will discriminate a 586 Pentium from a 486 ; it returns 1 in ax for 586, 0 for 486. ; This code uses 32 bit registers so I had to hand assemble it. ; We try to set the CPUID bit in the EFLAGS. If we can ; we can use the CPUID instruction to discriminate. If not, it ; must be a 486. ; This test is fairly innocuous. It does not upset ; Windows in either standard or extended modes. DESQview and ; QEMM are not freaked either. ; hand assembled version cli ; Some programs may fail to save/restore ; full 32 bit regs on interrupt db 66h,08Bh,0D4h ; mov edx,esp ; Save current stack ptr db 66h,083h,0E4h,0FCh ; and esp, not 3 ; Align stack db 66h,09Ch ; pushfd ; Push EFlags. db 66h,058h ; pop eax ; Get EFlags value. db 66h,08Bh,0C8h ; mov ecx,eax ; Save original EFlags db 66h,035h,00,00,20h,00 ; xor eax,200000h ; Flip cpuid bit in Eflags db 66h,050h ; push eax ; Copy to EFLAGS db 66h,09Dh ; popfd ; ... db 66h,09Ch ; pushfd ; Get new EFLAGS value. db 66h,058h ; pop eax ; Put into eax. db 66h,033h,0C1h ; xor eax,ecx ; See if AC bit changed db 66h,0C1h,0E8h,015h ; shr eax,21 ; Set EAX=1 if pentium db 66h,051h ; push ecx ; Original EFLAGS value db 66h,09Dh ; popfd ; restored. db 66h,08Bh,0E2h ; mov esp,edx ; Restore original db 66h,025h,01,0,0,0 ; and eax,1 ; Mask out all other bits ; at this point we know ; the CPU ID instruction ; is supported jz Is486 ; at this point we know ; the CPUID instruction ; is supported db 2Bh, 0C0h ; sub eax,eax ; check that level 1 CPUID is supported db 0fh, 0a2h ; CPUID ; find out family db 85h, 0C0h ; test eax,eax jz Is486 ; some half baked implementation db 0B8h,01,0,0,0 ; mov eax,1 db 0fh, 0a2h ; CPUID ; find out family mov al,ah ; family in EAX:11:8 and al,15 ; al = 4 for 486, 5 for Pentium cmp al,5 ; 0..4 -> 486, 5+ -> Pentium jb Is486 ispentium: sti ; could get cpu serno with EAX=3, CPUID ; result eax:ebx junk ecx:edx serno. mov ax,1 ret is486: sti mov ax,0 ret ; end hand assembled version comment î ; T H I S C O D E I S N O T U S E D ; version for 32 bit assembler cli ; Some programs may fail to save/restore ; full 32 bit regs on interrupt mov edx,esp ; Save current stack ptr and esp, not 3 ; Align stack pushfd ; Push EFlags. pop eax ; Get EFlags value. mov ecx,eax ; Save original EFlags xor eax,200000h ; Flip cpuid bit in Eflags push eax ; Copy to EFLAGS popfd ; ... pushfd ; Get new EFLAGS value. pop eax ; Put into eax. xor eax,ecx ; See if AC bit changed shr eax,21 ; Set EAX=1 if pentium push ecx ; Original EFLAGS value popfd ; restored. mov esp,edx ; Restore original and eax,1 ; Mask out all other bits jz Is486 ; at this point we know ; the CPUID instruction ; is supported sub eax,eax ; check that level 1 CPUID is supported cpuidx test eax,eax jz Is486 ; some half baked implementation mov eax,1 cpuidx ; find out family mov al,ah ; family in EAX:11:8 and al,15 ; al = 4 for 486, 5 for Pentium cmp al,4 jle Is486 IsPentium: sti mov ax,1 ret Is486: sti mov ax,0 ret î ; end version for 32 bit assembler PentiumTest endp ; = = = = = = = = = = = = = = = = = = = = = = comment î ; T H I S C O D E I S N O T U S E D ; DMAREFRESH is not currently used. Some machines are ; too sensitive to use it safely. DMARefreshOff proc near ; Turns of DMA refresh logic that might disturb a timing test. ; Preserves registers. comment û The following information compliments of rspade on BIX. According to 'Firmware Furnace' by Ed Nisley in issue 19 of 'Circuit Cellar INK' and 'Zen of Assembly Language, Volume I' by Michael Abrash: Timer 1 of the 8253 timer chips generates a refresh signal every 15.08us. The 8237 DMA controller then steals the bus from the CPU and performs a read access to 1 of 256 possible memory addresses All addresses that are congruent modulo 256 are refreshed at the same time, so 256 of these refreshes are required to refresh every location. Thus, each location is refreshed once every 3.86ms. Typical DRAM specs require that each location be refreshed once every 4ms to guarantee integrity of data. ûTimercntl equ 43H TRefresh equ 01010100B push ax call ManualDMARefresh ; get RAM pumped up with ; air, ready to hold breath mov al,TRefresh out Timercntl,al pop ax ret DMARefreshOff endp ; = = = = = = = = = = = = = = = = = = = = = = DMARefreshOn proc near ; Turn normal DMARefresh back on ; Preserves registers Normref equ 18 ; normal rate (fast would be 4) Timer1 equ 41H push ax mov al,NormRef ; normal counter out Timer1,al call ManualDMARefresh ; hyperventilate after holding ; breath pop ax ret DMARefreshOn endp ; = = = = = = = = = = = = = = = = = = = = = = ManualDMARefresh proc near ; Manually refresh the DMA, run through all 0..255 mod 256 addresses push ax push cx push si mov si,0 mov cx,256*2d rep lodsb ; manually refresh all ; modulo 256 addresses twice ; to catch up for lost time pop si pop cx pop ax ret ManualDMARefresh endp î ; end comment. ; = = = = = = = = = = = = = = = = = = = = = = FastSXTest proc near ; returns ax=1 if we have SX, ax=0 80386 ; ; Warning: if you were to call this test with a 486, it would look ; like an SX. ; ; This more accurate method uses the ; mov EAX,CR0 instruction. However this freaks out ; big Mama (otherwise know an Windows 3.0 in extended mode). ; There may be other virtual monitors that get upset we may ; discover later that also cannot use this test. ; ; This method was suggested by John 'Bitman' Kennedy (aka DOAD on BIX). ; In the SX you cannot toggle the ET bit in CR0 because it just isn't ; there. In the DX it controls whether you have an 80287 or 80387 style ; numeric coprocessor, and you can toggle it. cli db 0fh,020h,0c0h ; hand assembled 'mov eax,cr0' no need for 66h ; In virtual mode this would generate a trap ; so we only use it in real mode. mov bx,ax ; save old lsw of cr0 xor al,10h ; attempt to toggle the ET bit 4 in CR0 db 0fh,022h,0c0h ; hand assembled 'mov cr0,eax' no need for 66h ; put cr0 back db 0fh,020h,0c0h ; hand assembled 'mov eax,cr0' no need for 66h ; see if it took xor al,bl ; compare -- leave result in flag test al,10h ; non-zero if different in bit 4 mov ax,bx db 0fh,022h,0c0h ; hand assembled 'mov cr0,eax' no need for 66h ; put back cr0 the way it used to be sti jz FastFoundSX ; SX will not let you change that bit FastNOTSX: mov ax,0 ret FastFoundSX: mov ax,1 ret FastSXTest endp ; = = = = = = = = = = = = = = = = = = = = = = SlowSXTest proc near ; Determines if we have an SX ; returns ax=1 if we have SX, ax=0 80386 DX. Unknown for other cpus. ; ; This method is timing dependent, and does not always give ; accurate results: we time how many REP LODSW laps ; we can complete in 50 ticks and compare that with how ; many REP LODSD laps we can complete in the same time. A DX ; should be twice as fast doing REP LODSD, and an SX should be about ; 4/3 as fast. I made up this clutz method. ; ; The SX does dword (4 byte) operations, 2 bytes at time. ; The 80386 does them 4 bytes at a time. We measure the ; relative speed of DWord vs Word operations. SXTicks equ 50 ; how many ticks to run the SX test for ; 50 ticks = 3 seconds, done twice. data segment even StopTime dd 0 ; time test started LapsByWord dw 0 ; how many laps made in 5 ticks ; when done in 2-byte chunks LapsByDword dw 0 ; how many laps made in 5 ticks ; when done in 4-byte chunks data ends call FreshTick ; Wait for a fresh tick to start call BeginCrit ; Tell DESQview not to interrupt ; we leave interrupts on -- especially ; the timer tick ; Windows will interrupt us many times ; and take our time slice away. This ; will destroy the results. So all we ; can do is run the test for a long time hoping ; all will average out. add dx,SXTicks ; We will test for SXTicks ticks adc cx,0 mov word ptr StopTime,dx mov word Ptr StopTime+2,cx mov LapsByWord,0 AnotherWordLap: mov cx,sp ; we use the entire program, data areas, ; unused area, and stack as our arena. shr cx,1 ; divide by 2 since going by words sub si,si rep lodsw ; does nothing, but scan RAM by words inc LapsByWord call GetTicks sub dx,Word Ptr Stoptime sbb cx,word ptr Stoptime+2 jl AnotherWordLap call EndCrit ; give DESQview the all clear ; Time how many REP LODSD laps we can complete in 5 ticks call FreshTick ; Wait for a fresh tick to start call BeginCrit ; Tell DESQview not to interrupt add dx,SXTicks ; We will test for SXTicks Ticks adc cx,0 mov word ptr StopTime,dx mov word Ptr StopTime+2,cx mov LapsByDWord,0 AnotherDWordLap: mov cx,sp ; we use the entire program, data areas, ; unused area, and stack as our arena. shr cx,2 ; divide by 4 since going by Dwords sub si,si db 66h ; flip to 32 bit temporarily ; my assembler cannot do LODSD rep lodsw ; does nothing, but scan RAM by Dwords ; cx and di will both be 16 bit ; EAX will be 32 bit. inc LapsByDWord call GetTicks sub dx,Word Ptr Stoptime sbb cx,word ptr Stoptime+2 jl AnotherDWordLap call EndCrit ; give DESQview the all clear ; ratio will be 5:3 for SX or 6:3 for DX mov ax,LapsByDword mov bx,30d ; compute ratio*30 mul bx div LapsByWord ; result will be 46..53 for SX, 54..60 for DX cmp ax,54d ; split at 54 jl Found_SX Not_An_SX: sub ax,ax ; was something faster ret Found_SX: mov ax,1 ret SlowSXTest endp ; = = = = = = = = = = = = = = = = = = = = = = FreshTick proc near ; Get time of day in 1/18.2 second clock ticks since midnight. ; leaves tick count in CX:DX ; Stalls until you have just started a new tick data segment OldTick dd 0 ; time in ticks data ends call GetTicks mov word ptr OldTick,dx mov word ptr OldTick+2,cx StallTillFresh: call GetTicks cmp cx,word ptr OldTick+2 jne GotFresh cmp dx,word ptr OldTick je StallTillFresh Gotfresh: ret FreshTick endp ; = = = = = = = = = = = = = = = = = = = = = = GetTicks proc near ; Get time of day in 1/18.2 second clock ticks since midnight. ; leaves tick count in CX:DX ; For accurate results, don't call this too often. ; In some systems the clock gets behind if you call ; GetTicks in a tight loop. mov ah,0 int 1aH ; BIOS ticks since midnight ; CX:DX is count ; AL is non zero of went over midnight ret GetTicks endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DAY, 'DAY', CapacityStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDay proc near ; Displays day of the month. ; day of month given in ax as input saystr 'today is day ' mov dpl,-1 call sayBigDec saystr ' of the month.' ret DisplayDay endp ; = = = = = = = = = = = = = = = = = = = = = = TestDay proc near ; returns the current Day in DX:AX e.g. 31 ; ; This parameter was requested by Aron Gurski of Norway ; also known as AGurski on BIX. ; See Ray Duncan's Advanced MS DOS page 384 mov ah,02ah ; get date DOS function int 21h ; Day is returned in DL mov al,dl sub ah,ah sub dx,dx ret TestDay endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DAYOFWEEK, 'WEEKDAY', ImpliedStyle, 1, Any, Plus, Plus accept DAYOFWEEK, 'SUNDAY', ImpliedStyle, 1, Any, Bang, Bang accept DAYOFWEEK, 'MONDAY', ImpliedStyle, 2, Any, Bang, Bang accept DAYOFWEEK, 'TUESDAY', ImpliedStyle, 3, Any, Bang, Bang accept DAYOFWEEK, 'WEDNESDAY', ImpliedStyle, 4, Any, Bang, Bang accept DAYOFWEEK, 'THURSDAY', ImpliedStyle, 5, Any, Bang, Bang accept DAYOFWEEK, 'FRIDAY', ImpliedStyle, 6, Any, Bang, Bang accept DAYOFWEEK, 'SATURDAY', ImpliedStyle, 7, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDayOfWeek proc near ; Displays day of the Week ; day of week given in ax as input, 1 = Sunday 7=Saturday saystr 'today is ' saywhen 1,'Sunday.' saywhen 2,'Monday.' saywhen 3,'Tuesday.' saywhen 4,'Wednesday.' saywhen 5,'Thursday.' saywhen 6,'Friday.' saywhen 7,'Saturday.' saystr 'unknown.' ret DisplayDayOfWeek endp ; = = = = = = = = = = = = = = = = = = = = = = TestDayOfWeek proc near ; Returns current day of week in ax ; 1 = Sunday 2 = Monday ... 7 = Saturday. ; See Ray Duncan's Advanced MS DOS page 384 mov ah,02ah ; get date DOS function int 21h ; dos returns al= day of week ; 0=Sunday inc al ; convert to 1=Sunday form mov ah,0 ret TestDayOfWeek endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DESQExist, 'DESQVIEW', ImpliedStyle, 1, None, Bang, Bang accept DESQExist, 'DESQ', ImpliedStyle, 1, None, Bang, Bang accept DESQExist, 'DV', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDESQExist proc near ; Displays current state of DESQVIEW ; Presumes Call TestDESQviewExist called just prior, so AX has state saystr 'DESQview' call SayIfDetected ret DisplayDESQExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestDESQExist proc near ; Is DesqView is present? ; Returns version 1 AX if present, 0 otherwise. call TestDESQVer test ax,ax jz DESQAbsent DESQPresent: mov ax,1 ret DESQAbsent: mov ax,0 ret TestDESQExist endp ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DESQVer, 'DESQVIEW', VersionStyle, 0, Any, Bang, Bang accept DESQVer, 'DESQ', VersionStyle, 0, Any, Bang, Bang accept DESQVer, 'DV', VersionStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDESQVer proc near ; Displays current version DESQVIEW verio ; Presumes Call TestDESQviewVer called just prior, so AX has version*100 saystr 'DESQview' call SayVersion ret DisplayDESQVer endp ; = = = = = = = = = = = = = = = = = = = = = = TestDESQVer proc near ; What version of DesqView is present? ; Returns version *100 in AX if present, 0 otherwise. ; Method from Tech support on BIX ; This about the fifth version of this code. The following techniques ; do NOT work on all machines. The problems are DOS and BIOS bugs. ; The approach we have here depends more actively on DESQview itself ; so it should be safer. ; Technique from DV manual, appendix J. ; @CALL DVPRESENT from API manual page 29 ; Trashes CX DX AX mov cx,'DE' ; deliberately invalid set date mov dx,'SQ' sub bx,bx ; in case DOS just ignores us mov ax,2b01h int 21h mov ax,bx ; version # to AX cmp ax,2 ; early DV 2.00 ? jne NotOldDESQ ; jump if not xchg ah,al ; swap bytes if old version NotOldDESQ: test ax,ax ; did we get back 0 we sent in bx? jz NoDESQ ; ah=major version al=minor version mov bl,100d ; convert 2.31 -> 231 mov cl,al sub ch,ch mov al,ah ; mult major version by 100 mul bl add ax,cx ; add on the minor version ret NODESQ: mov ax,0 ret TestDESQVer endp ; = = = = = = = = = = = = = = = = = = = = = = BEGINCrit proc near ; Begin DESQview critical section. Does nothing if DESQview not active ; Disturbs no registers ; See DESQview API manual push ax push bx push cx push dx call TestDESQExist ; if DESQ exists we will have to ; persuade it to let us turn off ; interrupts, and stop slicing ; which disturbs our test. test ax,ax jz NoBeginCrit mov ax,0101Bh ; @CALL BEGINC int 15h ; start DESQ critical section ; no need for SafeVector, because ; we have already tested that DV exists. NoBeginCrit: pop dx pop cx pop bx pop ax ret BeginCrit endp ; = = = = = = = = = = = = = = = = = = = = = = ENDCrit proc near ; End DESQview critical section. Does nothing if DESQview not active ; Disturbs no registers ; See DESQview API manual push ax push bx push cx push dx call TestDESQExist ; if DESQ exists we will have to ; give it the all clear. The special ; uninterruptible section is done. test ax,ax jz NoEndCrit mov ax,0101Ch ; @CALL ENDC int 15h ; end DESQ critical section ; no need for SafeVector, because ; we have already tested that DV exists. NoEndCrit: pop dx pop cx pop bx pop ax ret EndCrit endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DOSver, 'DOS', VersionStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDOSVer proc near ; displays DOS version ; on entry ax contains version multiplied by 100. push ax push dx mov ax,DOSType call DisplayDOSType pop dx pop ax call SayVersion ret DisplayDOSVer endp ; = = = = = = = = = = = = = = = = = = = = = = DisplayDOSType proc near ; on extry ax encodes dos type ; clobbers DX saywhen 3,'Digital Research DR DOS' saywhen 2,'IBM PC DOS' saywhen 1,'Microsoft MS DOS' saystr 'Unknown DOS' ret DisplayDOSType endp ; = = = = = = = = = = = = = = = = = = = = = = TestDOSVer proc near data segment DOSType dw 0 ; DOS type 0=unknown 1=MS 2=PC 3=DR data ends ; Calculates DOS Version ; future: OS/2. function 30 returns AL=20 AH>20 ; major is ah/10 minor is ah%10 ; future NT detect (but not version) ; mov edx,0 ; mov eax,0d3h ; NT native yield ; int 2eh ; NT changes EDX, other os's do not. ; future Win98 detect ; Win98 reports 4.10.build. Win95 reports 4.00.build. ; Try DR DOS call TestDRDOSExist test ax,ax jz NoDRDOS call TestDRDOSVer mov DOSType,3 ret NoDRDOS: ; Try PC DOS call TestPCDOSExist test ax,ax jz NoPCDOS call TestPCDOSVer mov DOSType,2 ret NoPCDOS: ; Try MS DOS call TestMSDOSExist test ax,ax jz NoMSDOS call TestMSDOSVer mov DOSType,1 ret NoMSDOS: ; Must be something else mov ax,0 ; unknown DOS mov DOSType,0 ret TestDOSVer endp ; = = = = = = = = = = = = = = = = = = = = = = RAWDOSVer proc near ; Returns DOS version e.g. 4.01 * 100 = 401 in AX. ; See Ray Duncan's Advanced MS DOS page 389 ; Preserves all registers. ; DR DOS will report 3.31 ; OS/2 will report 10.00 ; Windows-95 will report 7.00 ; NT will report 5.00 push bx push cx mov ax,3000h ; using function 30h int 21h ; get version number ; AL=major AH=minor ; we want major*100 + minor ; BH = OEM id, FF for MS, 0 for IBM test al,al ; DOS 1.0 returns 0, should be 1 ; We are in trouble since all code ; presumes 2.0 or later. jnz DOSMajorOk pop cx pop bx mov ax,100d ; DOS 1.0 ret DOSMajorOk: mov bl,100d ; convert 3.1 -> 301 mov cl,ah sub ch,ch mul bl add ax,cx cmp ax,400d ; DOS 4.01 is not properly reported pop cx pop bx je DiscrimDos4 ret DiscrimDos4: call TestDos4 ; Will return 400 or 401 in ax ret RAWDOSVer endp ; = = = = = = = = = = = = = = = = = = = = = = TestDos4 proc near ; Discriminating DOS 4.00 from 4.01. ; puts 400 in ax for DOS 4.00 and 401 for for 4.01 ; Preserves all registers. comment û This method was suggested by WL Moore. He wrote a C program to test the method. Function 30 fails to discriminate between dos 4 and DOS 4.01. It calls them both DOS 4.01. We have to resort to extraordinary means to tell them apart. This information was gleaned from pg. 532 of Undocumented DOS. It uses a byte in the Disk Buffer Info structure that contains a 01h in versions after the UR 25066 Corrective Services release and either 00h or FFh in v4.00 ( pre UR 25066 ). Int 21h function 52h is used to obtain the List of Lists. At offset 12h from ES:BX is a DWord pointer for the Disk Buffer Info structure. At offset 0Ch in that is the discriminating byte. û; end of comment push ES push bx mov ah,52h int 21h ; get list of lists in ES:BX ; see undocumented DOS page 518 les bx,ES:[BX+12h] ; ES:BX+12h dword points to disk buffer info ; see undocumented DOS page 519 cmp byte ptr ES:[bx+0Ch],01 ; at offset 0Ch from that is the ; discriminating byte 0 or FF DOS 4.0 ; 01 for DOS 4.01 pop bx pop ES je FoundDos401 mov ax,400d ; found DOS 4.00 ret FoundDos401: ; found DOS 4.01 mov ax,401d ret TestDos4 endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DPMIExist, 'DPMI', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDPMIExist proc near ; on entry ax is non zero if DMPI detected saystr 'DPMI (DOS Protected Mode Interface) memory manager' call SayIfDetected ret DisplayDPMIExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestDPMIExist proc near ; Test whether a DPMI memory manager is present ; DPMI stands for Dos Protected Mode Interface ; returns 1 in AX if present, otherwise 0. ; information for this routine compliments of Ergo2 on BIX. ; It is documented on page 402 of Extending DOS by Ray Duncan. ; Also want a free RAM detector /DPMI:300K ; This would have to use function 500H. However I believe you ; must go into protected mode before using 500H. This would preclude ; using NEED under mothering systems such as DESQview and Windows. ; Further I have no documentation on how function 500H works. mov al,2fh call SafeVector ; see if 2fh is hooked up to anything jc DPMIAbsent ; DPMI could not be present mov ax,1687h ; check via multiplex interrupt push ES ; preserve ES int 02fh pop ES ; AX=0 means present ; version is in DX DH:DL major:minor ; BX is flags 1=32 bit support ; CL = proc code 2=286 3=386 4=486 ; ES:DI entry point ; SI = overhead paragraphs needed by DPMI test ax,ax jnz DPMIAbsent DPMIPresent: mov ax,1 ret DPMIAbsent: mov ax,0 ret TestDPMIExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DRDOSExist, 'DRDOS', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; ; DRDOS looks like DOS 3.31 to the ordinary INT 21 h function 30h ; version check. ; This is because it uses Compaq style partitions. ; To find out the true version, we need to examine the environment for ; SET OS=DRDOS or SET OS=DRMDOS and VER=5.0 ; Information on how do do this check compliments of Arog on BIX. ; ; Later this could be expanded to test for DRDOS and DRMDOS separately. ; Note the test for version below. ; ; = = = = = = = = = = = = = = = = = = = = = = DisplayDRDOSExist proc near saystr 'DRDOS' call SayIfDetected ret DisplayDRDOSExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestDRDOSExist proc near ; Returns ax=1 if we are running under DR DOS, ax=0 otherwise ; Method suggested by arog -- the moderator of the CPM conference ; on BIX. String segment ; look for ; SET OS=DRDOS or ; SET OS=DRMDOS SETOS db 'OS' WANTOS1 db 'DRDOS' WANTOS2 db 'DRMDOS' String ends mov ax,3000h int 21h ; get version number ; AL=major AH=minor ; BH is OEM number cmp ax,1f03h ; is it version 3.31? jne DRDOSAbsent lea si,SETOS ; check that SET OS=DRDOS is present mov cx,2 call GetSet cmp cx,5 lea si,WANTOS1 repe cmpsb je DRDOSPresent ; no OS=DRDOS, oh well, try for OS=DRMDOS lea si,SETOS ; check that SET OS=DRMDOS is present mov cx,2 call GetSet cmp cx,6 lea si,WANTOS2 repe cmpsb jne DRDOSAbsent DRDOSPresent: mov ax,1 ret DRDOSAbsent: mov ax,0 ret TestDRDOSExist endp ; = = = = = = = = = = = = = = = = = = = = = = Getset proc near ; Needed by TestDRDOS, but make have other uses ; Finds values of variables in the SET environment. ; ; Let us presume you had put the command: ; SET MYVAR=XXXX ; Into your bat file. You want to find the current value ; of MYVAR ; ; On entry DS:SI CX=len points to a string MYVAR all upper case ; GetSet will search the environment for that string. ; On Exit ES:DI CX=len points to the value string XXXX ; If there is so such string CX will be 0. ; ; How it works: ; The segment to the environment is at offset 2Ch in the PSP ; See Ray Duncan's Advanced MS DOS page 23. ; The environment has strings of the form YYY=XXXX terminated ; by a null. The last string is terminated by two nulls. ; ; Trashes ax and dx, but leaves other register intact. ; push si ; save start of var wanted mov dx,cx ; save length of variable wanted jcxz GetSetFail mov ax,ds:word ptr[02Ch] mov ES,ax ; ES:di points to environment sub di,di GetSetLookAgain: ; DS:si len dx points to var ; we are looking for ; ES:di points to char after null i.e. ; the start of new variable, ; or to first parm in env, ; or to double null in empty env, ; or to null, second in terminating pair cmp byte ptr ES:[di],0 je GetSetFail ; we hit double null without finding ; the variable we wanted. mov bx,di ; save start of string mov cx,32768 ; largest possible environment mov al,'=' ; scan for end of variable repne scasb ; terminated by = jne GetSetFail ; have found = ; di points just past it mov cx,di sub cx,bx dec cx ; cx = len of variable cmp cx,dx ; length candidiate variable same ; as one wanted? jne GetSetBypass ; no, was not this one pop si push si ; DS:si pointer to start of var wanted xchg bx,di ; ES:di points to candidate var ; cx is length, some for both repe cmpsb ; has candidate same name? mov di,bx ; di points just past equal jne GetSetBypass GetSetSucceed: ; we have found the parameter ; di points just past = ; at start of value mov bx,di ; save start of parm string mov cx,32768 ; string could be very long mov al,0 ; null terminates string repne scasb jne GetSetFail ; di points 1 past null ; ES:bx points to start of value mov cx,di sub cx,bx dec cx ; cx = len value mov di,bx ; customers want to see start ; of value pop si ; balance the stack ret ; we are done ; GetSetBypass: ; Variable was not the one ; wanted, scan over the ; following value ready to ; test the nex variable. ; ES:di point just past the = at end ; of the candidate variable mov cx,32768 ; max env len mov al,0 ; search for null repne scasb jnz GetSetFail ; ES:di now points just past the ; null at the end of the value jmp GetSetLookAgain GetSetFail: sub di,di ; return dummy ES:0 mov cx,di ; cx=0 pop si ; balance the stack ret Getset endp ;============================================ ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DRDOSVer, 'DRDOS', VersionStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDRDOSVer proc near saystr 'DRDOS' call SayVersion ret DisplayDRDOSVer endp ; = = = = = = = = = = = = = = = = = = = = = = TestDRDOSVer proc near ; Check DRDOS version. look for string VER=5.0 in environment ; Method suggested by arog -- the moderator of the CPM conference ; on BIX. String segment ; look for ; SET VER=5.0 SETVER db 'VER' DRDOSVerNum db '00000000' String ends call TestDRDOSExist test ax,ax jz NoDRDOSVer lea si,SETVER ; check that SET VER=5.0 is present mov cx,3 call GetSet ; On Exit ES:DI CX=len points to the value string 5.0 jcxz NoDRDOSVer mov bx,cx lea si,DRDOSVernum CopyDrDOS: ; copy 5.0 to DRDOSVernum mov al,ES:[di] cmp al,'.' je DRDOSverOk cmp al,'0' jl NoDRDOSVer cmp al,'9' ja NoDrDosVer DRDOSVerOk: mov DS:[si],al inc si inc di loop CopyDRDOS mov cx,bx lea si,DRDosVerNum ; DS:SI,cx points to ASCII string call ASCIIToBin ; DX:AX has binary result mov cx,2 ; adjust to two decimal places call DPLAdjust ; version *100 is in DX:AX ret NoDRDOSVer: sub ax,ax ret TestDRDOSVer endp ;============================================ ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; Space on drives accept Drives, 'A', , 1, Any, Plus, Minus accept Drives, 'B', , 2, Any, Plus, Minus accept Drives, 'C', , 3, Any, Plus, Minus accept Drives, 'D', , 4, Any, Plus, Minus accept Drives, 'E', , 5, Any, Plus, Minus accept Drives, 'F', , 6, Any, Plus, Minus accept Drives, 'G', , 7, Any, Plus, Minus accept Drives, 'H', , 8, Any, Plus, Minus accept Drives, 'I', , 9, Any, Plus, Minus accept Drives, 'J', , 10, Any, Plus, Minus accept Drives, 'K', , 11, Any, Plus, Minus accept Drives, 'L', , 12, Any, Plus, Minus accept Drives, 'M', , 13, Any, Plus, Minus accept Drives, 'N', , 14, Any, Plus, Minus accept Drives, 'O', , 15, Any, Plus, Minus accept Drives, 'P', , 16, Any, Plus, Minus accept Drives, 'Q', , 17, Any, Plus, Minus accept Drives, 'R', , 18, Any, Plus, Minus accept Drives, 'S', , 19, Any, Plus, Minus accept Drives, 'T', , 20, Any, Plus, Minus accept Drives, 'U', , 21, Any, Plus, Minus accept Drives, 'V', , 22, Any, Plus, Minus accept Drives, 'W', , 23, Any, Plus, Minus accept Drives, 'X', , 24, Any, Plus, Minus accept Drives, 'Y', , 25, Any, Plus, Minus accept Drives, 'Z', , 26, Any, Plus, Minus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDRIVEs proc near ; input DX:AX capacity. ; Display a message of the form: ; '4,000 free bytes on drive C:' data segment DriveNameDis db 'A:$' data ends dump "DisplayDrives" call SaybytesCapacity saystr 'on drive ' mov ax,[auxinput+BP] ; a:= 1 add al,64d ; -> A mov DriveNameDis,al say DriveNameDis ret DisplayDRIVEs endp ; = = = = = = = = = = = = = = = = = = = = = = TestDRIVEs proc near ; entry with drive number in ax, 1=A: 2=B: etc ; exit with free space on drive in DX:AX ; Method suggested by Terje Mathisen of Norway. ; See Ray Duncan's Advanced MS DOS page 394. mov dl,al ; get drive number mov ah,036h ; get free space function ; works in DOS 2.0+ ; we don't work about DOS 1.x int 21h ; ax=Sectors per cluster ; FFFFh if invalid drive specified ; BX = Number of available clusters ; CX = Bytes per sector ; DX = Total number of clusters per drive cmp ax,0ffffh je badDrive mul cx ; bytes/cluster -> DX:AX ; presume DX =0 mul bx ; bytes avail -> DX:AX ret BadDrive: sub dx,dx ; treat bad drive as zero capacity mov ax,dx ret TestDRIVEs endp if DEBUGGING ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept DEBUGExist, 'DEBUG', ImpliedStyle, 1, any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayDebugExist proc near saystr 'Turning on debugging' ret DisplayDebugExist endp endif ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept ENV, 'ENV', CapacityStyle, 0, Any, Plus, Minus accept ENV, 'ENVIRONMENT', CapacityStyle, 0, Any, Plus, Minus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayENV proc near call SaybytesCapacity saystr 'in the Environment.' ret DisplayENV endp ; = = = = = = = = = = = = = = = = = = = = = = TestENV proc near ; This code was provide compliments of Jay Vanderbilt. ; It this same code he uses in ROOM! and ASK! ; Return free space in the current master environment in DX:AX ; The current master environment is the one a SET command would ; affect. It can be several levels back if for example a program ; spawned NEED directly, rather than through COMMAND.COM or 4DOS. ; The current master environment is NOT necessarily the great mother ; of environments -- the original autoexec environment. ; ; TestEnv considers all the space from the first double null ; to the end of the reserved environment region to be free. ; Some of it may contain the name of the program currently ; executing, so the free space calculated is a tad optimistic. ; Hovever, it is the size that SET is concerned with when it ; decides if the environment is overflowing. ; ; Finds current master environment two ways. By going ; child to parent to current Command and either getting ; the address of the environment from the PSP of Command ; or searching the MCB chain for a block that belongs to ; Command and matches the program's env. ; ; Returns the amount of free space in DX:AX. Note that DX ; is always zeroed because 32k is the max possible. ; ; Assumes that DS=ES=PSP_segment on entry ; ; If you use 4DOS it inserts a SET command called CMDLINE that ; places the entire command line into the SET environment, including ; the program name. If this routine does not give you the answers ; you expect, chances are you forgot to account for this parameter ; inserted near the head of the environment string. ; ; NT seems to use autoexpanding environments, but this code does ; not yet handle that. However, I think only the official SET ; knows how to trigger the expansion, so returning 20,000 all the ; time would be misleading. ; push cx push di push es EnvParentLoop: mov es,ES:[016h] ; segment of parent program ; see Undocumented DOS p 108 mov di,es mov ax,ES:[016h] ; parent's parent cmp di,ax ; if same - COMMAND jne EnvParentLoop ; try again mov ax,ES:[02Ch] ; master env segment ; see MS DOS Encyclopedia p109 cmp ax,0 ; if 0 need to get env jne EnvFound call EnvWalk ; returns env_seg in ax jc EnvFail ; not found EnvFound: dec ax ; point to mcb mov es,ax mov cx,ES:[03h] ; size of env region in paragraphs shl cx,1 ; multiply by 16 shl cx,1 shl cx,1 shl cx,1 ; size in bytes inc ax mov es,ax ; point to start of env ; ES:0 points to THE environment ; CX is the size of the whole region ; in bytes. mov ax,0 ; looking for double null at end of env mov di,1 ; allow for first dec di EnvLookForNulls: dec cx ; size of env dec di ; step aheadcccc by bytes ; scasw goes by twos. ; NOTE: this is just scasw not REPNE SCASW scasw ; look for double null at end of env ; two steps forward, then one step back jne EnvLookForNulls dec cx ; allow for double null at end sub dx,dx mov ax,cx ; and return in dx:ax EnvDone: pop es pop di pop cx ret EnvFail: sub dx,dx mov ax,dx ; indicate failure as 0 bytes free jmp Short EnvDone TestENV endp ; = = = = = = = = = = = = = = = = = = = = = = ; nested in TestEnv EnvWalk proc near ; Finds master environment by walking up mcb chain looking for block ; owned by COMMAND. If not found, returns carry flag set. ; Used by TESTEnv. ; Should only be needed for DOS 2.x. ; ; called with es=command_seg ; returns env seg in ax push bp push es mov ax,es mov bp,es ; save for checking owner dec ax mov es,ax ; point to mcb EnvNextMCB: add ax,ES:[03h] ; size of block inc ax ; point to next mcb mov es,ax cmp bp,ES:[01] ; owned by command.com? je EnvFoundShell cmp ES:byte ptr[0],05Ah ; last block? je EnvWalkFailed jmp Short EnvNextMCB EnvFoundShell: push ds push es inc ax ; move on from mcb mov es,ax mov di,0 ; es:di now points to possible env mov ds,DS:[02ch] ; point to our copy of env mov si,0 EnvMatchLoop: cmpsb ; do Environments match? ; note, cmpsb not REPE cmpsb jne EnvTryNextBlock ; they don't so try next block cmp DS:word ptr[si],0 ; look for 0 0 at end of env jne EnvMatchLoop pop es pop ds EnvWalkDone: pop es pop bp ret EnvTryNextBlock: pop es pop ds cmp byte ptr ES:[0],05Ah ; last block? je EnvWalkFailed ; already checked jmp Short EnvNextMCB ; go get next blocke EnvWalkFailed: stc ; not found jmp short EnvWalkDone EnvWalk endp ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept EXPExist, 'EXP', ImpliedStyle ,1, None, Bang, Bang accept EXPExist, 'EMM', ImpliedStyle ,1, None, Bang, Bang accept EXPExist, 'EMS', ImpliedStyle ,1, None, Bang, Bang accept EXPExist, 'LIM', ImpliedStyle ,1, None, Bang, Bang accept EXPExist, 'EXPANDED', ImpliedStyle ,1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayEXPExist proc near ; Displays current state of EXPANDED RAM driver. ; Presumes Call TestEXPExist called just prior, so AX has state saystr 'Expanded RAM driver' call SayIfDetected ret DisplayEXPExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestEXPExist proc near ; Test for presence of expanded EMM manager, leave 1 in AX if found, ; 0 otherwise. ; See page 2-26 Lotus Intel Microsoft Expanded Memory Specification. ; Preserves all registers. data segment EMMsig db 'EMMXXXX0' data ends push ES push si push di push bx push cx mov ax,3567h ; get vector 67 int 21h mov ax,ES ; vector in ES:BX test ax,ax jz ExpAbsent mov di,0ah ; ES:DI points to device name lea si,EMMSig mov cx,4 ; 8 bytes = 4 words long repe cmpsw jne EXPAbsent ExpPresent: pop cx pop bx pop di pop si pop ES mov ax,1 ret ExpAbsent: pop cx pop bx pop di pop si pop ES mov ax,0 ret TestEXPExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept EXP, 'EXP', CapacityStyle, 0, Any, Plus, Minus accept EXP, 'EMM', CapacityStyle, 0, Any, Plus, Minus accept EXP, 'EMS', CapacityStyle, 0, Any, Plus, Minus accept EXP, 'LIM', CapacityStyle, 0, Any, Plus, Minus accept EXP, 'EXPANDED', CapacityStyle, 0, Any, Plus, Minus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; Not both /EXP and /EXP:400K are supported ; = = = = = = = = = = = = = = = = = = = = = = DisplayEXP proc near dump "Display EXP" call SaybytesCapacity saystr 'Expanded RAM.' ret DisplayEXP endp ; = = = = = = = = = = = = = = = = = = = = = = TestEXP proc near ; Test for free bytes of expanded ram. Leave amount found in DX:AX ; If no EMM manager, return 0 bytes. ; trashes BX ; See page 3-7 Lotus Intel Microsoft Expanded Memory Specification. dump "Before EXP exist" call TestExpExist ; ax = 1 if manager present dump "After EXP exist" test ax,ax jz NoExp ; ax = 0 means no manager mov ah,42h ; get unallocated pages ; see Advanced MS DOS page 617 int 67h ; BX contains free pages, DX=total pages ; no need for SafeVector dump "After int 67 funct 42h" test ah,ah jnz NoExp ; 1 page = 16K mov ax,16d*1024d mul bx ; convert to bytes in DX:AX ret NoExp: sub dx,dx mov ax,dx ret TestEXP endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept EXT, 'EXT', CapacityStyle, 0, Any, Plus, Minus accept EXT, 'EXTENDED', CapacityStyle, 0, Any, Plus, Minus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayEXT proc near call SaybytesCapacity saystr 'Extended RAM.' ret DisplayEXT endp ; = = = = = = = = = = = = = = = = = = = = = = TestEXT proc near ; This routine totals the extended RAM available, in DX:AX ; There are are four methods to consider: ; 1. XMS RAM ; 2. int 15 RAM growing down ; 3. int 15 RAM already allocated via the VDISK int 19 method, ; growing up. ; 4. int 15 RAM already allocated via the VDISK boot track method ; growing up. (Should be same as 3. We take the larger.) ; Total RAM is (1) + (2) - [Max (3) (4)] call TestVDisk19Use ; ram in USE via INT 19 -> dx:ax dump "V19 RAM" push ax push dx call TestVDiskBootUse ; ram in USE via VDISK boot -> dx:ax dump "VBoot RAM" pop cx pop bx call Max ; take the larger of the two push ax ; They SHOULD agree. Result dx:ax push dx call TestInt15RAM ; get low INT 15 free -> dx:ax dump "INT 15 RAM" pop cx pop bx sub ax,bx ; subtract off RAM used by sbb dx,cx ; vdisk push ax push dx call TestXMSCap ; get free XMS ram -> dx:ax dump "XMS RAM" pop cx pop bx add ax,bx ; add on any unallocated int 15 RAM adc dx,cx ret TestEXT endp ; = = = = = = = = = = = = = = = = = = = = = = TestVDISK19Use proc near ; returns bytes of RAM in USE by VDISK INT 19 method in DX:AX ; See Extending DOS by Ray Duncan page 91 ; Linear address of free RAM is in a control block pointed at ; by INT 19 in conventional RAM. data segment VDISKSig db 'VDISK' ; signature for VDISK driver data ends mov ax,3519h ; get vector 19h that VDISK uses int 21h ; result in ES:BX. Ignore BX. lea si,VDISKsig ; This is NOT a vector to code. ; Having it point to what looks like IRET is ok. mov di,12h mov cx,5 repe cmpsb ; see if VDISK signature present at ES:12h jne NoVDISK19 mov ax,ES:[2Ch] ; get linear 3 byte address of free ext RAM mov dl,ES:[2Ch+2] ; from the control block sub dh,dh ; extended ram starts at 1 MB or ; 100000h in linear hex ; we subtract to find out how much ; is in use sub dx,10h ; dx:ax contains bytes in use ret NOVDISK19: sub dx,dx ; no bytes in use mov ax,dx ret TestVDISK19Use endp ; = = = = = = = = = = = = = = = = = = = = = = TestVDISKBootUse proc near ; Returns bytes of RAM in USE by VDISK boot track method in DX:AX ; ; See Extending DOS by Ray Duncan page 92 ; VDISK builds a fake boot block at the 1 MB mark. In it is ; a 2-byte pointer to the next free space, measured in K. ; the pointer lives at linear address 10001E. ; A VDISK OEMID must be present at 100003. data segment even GDT db 16d dup (0) ; reserved dw 64d ; source segment length, bytes LinSource db 0,0,0 ; linear source addr 10001E db 93h ; access dw 0 ; reserved dw 64d ; target segment length, bytes linTarget db 0,0,0 ; linear target addr db 93h ; access db 17d dup (0) ; reserved OEMID db 6 dup (0) ; might be VDISK FreeK dw 9999h ; will hold addr in K of free RAM data ends mov ax,3519h ; get vector 19h that VDISK uses int 21h ; result in ES:BX. Ignore BX. lea si,VDISKsig mov di,12h mov cx,5 repe cmpsb ; see if VDISK signature present at ES:12h jne NoVDISKBoot mov al,15h call SafeVector ; see if 15h is hooked up to anything jc NOVDISKboot ; if not, could be no VDISKboot lea bx,OEMID ; set up pointer to Target DS:bx cx:bx mov cx,DS ; must be in 24 bit linear form. call ToLinear mov word ptr LinTarget,BX mov byte ptr LinTarget+2,CL mov word ptr LinSource,0003h mov byte ptr LinSource+2,10h ; OEMID is an linear 100003 mov cx,3 ; number of words we want from ext ; ram transferred down. mov ax,DS ; point at the GDT we have built mov ES,ax lea si,GDT mov ah,87h ; all set to leap into protected mode ; to grab five OEM ID bytes from extended RAM. ; What a production! ; AH =87h CX=words ES:SI points to GDT int 15h ; we do the SafeVector at the top ; Compare OEMID with expected signature lea si,VDISKsig lea di,OEMid mov cx,5 repe cmpsb ; see if VDISK signature present in boot jne NoVDISKBoot lea bx,FreeK ; set up pointer to Target DS:bx cx:bx mov cx,DS ; must be in 24 bit linear form. call ToLinear mov word ptr LinTarget,BX mov byte ptr LinTarget+2,CL mov word ptr LinSource,001Eh mov byte ptr LinSource+2,10h ; FreeK is at 10001E mov cx,1 ; number of words we want from ext ; ram transferred down. mov ax,DS ; point at the GDT we have built mov ES,ax lea si,GDT mov ah,87h ; all set to leap into protected mode ; to grab two freeK bytes from extended RAM. ; What a production! ; AH =87h CX=words ES:SI points to GDT int 15h ; We do the SafeVector at the top mov ax,FreeK sub ax,1024d ; we want the K amount IN use, so sub dx,dx ; subtract off low RAM call Multby1024 ; convery to bytes ret NOVDISKBoot: sub dx,dx ; no VDISK boot-style RAM in use. mov ax,dx ret TestVDISKBootUse endp ; = = = = = = = = = = = = = = = = = = = = = = ToLinear proc near ; Converts absolute seg:offset to absolute linear 24-bit address. ; ( CX=seg BX=offset --- CX=msw BX=lsw of 24-bit addr ) ; CX is absolute seg, BX may be larger than 15 ; Even handles crazy addresses like FFFF:FFFF by wrap around. ; The largest real address is FFFF:000F. mov ax,cx ; AX:BX now has seg:off sub cx,cx shl ax,1 ; shift seg AX left 4 bits rcl cx,1 ; must do multi-register shifts a bit at a time shl ax,1 ; even on NEC chips rcl cx,1 shl ax,1 rcl cx,1 shl ax,1 rcl cx,1 add bx,ax ; add shifted seg to offset adc cx,0 ; add carry and 4 high order bits ret ToLinear endp ; = = = = = = = = = = = = = = = = = = = = = = TestINT15RAM proc near ; Returns bytes of free INT 15 extended RAM in DX:AX ; As RAM is used from the top down, this number is reduced. ; See IBM AT Tech Ref BIOS listing. ; This interrupt may or may not be supported. ; In an XT commands are rejected with AH=80 and carry set. ; Some machines ignore the interrupt altogether. ; ATs may or may not set the carry on function 88, and 80 is ; a valid response for AH. ARRRGGH! How do you tell if INT 15/88 ; is supported? Our trick is to test if INT 15/86 is supported. ; Filter out codes with dummy INT 15 by asking size twice. ; If you set the registers to different values beforehand, but they ; converge on the same value, we are ok. mov al,15h call SafeVector ; see if vector is hooked up to anything jc NoInt15RAM ; Could not be any INT 15 RAM clc mov ah,86h ; dummy Wait 1 microseconds mov cx,1 mov bx,cx int 15h jc NOInt15RAM ; XT would set carry cmp ah,80h je NoInt15RAM ; XT would set to 80h mov ax,8800h int 15h push ax ; save result of first try mov ax,8801h ; int 15 function 88, yet again int 15h ; get extended RAM size dump "Raw int15/88 results" pop bx cmp bx,ax jne NoInt15RAM ; not consistent implies we have a dummy ; interrupt just leaving regs as is. ; Users reduce apparent amount left ; by intercepting int 15. ; Result in ax in KB ; If XMS loaded, likely will be 0 sub dx,dx call MultBy1024 ; convert to bytes ret NoInt15Ram: sub dx,dx mov ax,dx ret TestInt15RAM endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept HANDLES, 'HANDLES', CapacityStyle, 0, Any, Plus, Minus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayHandles proc near test ax,ax jz DisplayNoHandles mov dpl,-1 call SayBigDec saystr ' free file handles.' ret DisplayNoHandles: saystr 'no free file handles.' ret DisplayHandles endp ; = = = = = = = = = = = = = = = = = = = = = = TestHandles proc near ; Returns number of free handles in DX:AX. ; Normally there are 20 per task total. 5 are takes by standard handles. ; 3 of those share a handle. If you close some standard handles you can ; get 17 free handles, but usually you just have 15. ; If the global limit is less than 20, (from CONFIG.SYS FILES=20) ; or if TSRs are using handles, we will see a smaller number. ; ; This does NOT include potential handles that could be allocated ; by using int 21h function 67 to boost the per-task limit. ; Even if you use the boost, you still cannot go above the ; global limit. ; ; Our method for detecting is crude but robust. ; Then we open null files over and over till our opens ; fail. Then we close all the handles. ; We use the stack to hold all the handles. String segment NullFile db 'NUL',0 ; note we leave OFF the colon. DOS is illogical. ; it will not work if the colon is included! String ends sub cx,cx ; clear counter for number of files we ; were able to open OpenLoop: mov ax,03d00h ; AL=Open mode, read anly ; for DOS 2+ use 0 - read only, compat mode ; read access, deny none would be DOS 3+ method ; 0-2 = 000 read access ; 3 = 0 reserved ; 4-6 = 100 deny none ; 7 = 0 child inherits lea dx,NullFile ; DS:DX Pointer to ASCII filename int 21h jc NoMoreOpens inc cx ; got one more opened push ax ; save handle on stack. ; stack is huge, so not to worry about overflow jmp OpenLoop NoMoreOpens: ; open failed. We must have run out of handles. mov di,cx ; save count of how many handles we were able ; to open. jcxz NoHandles CloseLoop: pop bx ; get handle mov ah,3Eh ; close each handle int 21h loop CloseLoop FoundHandles: mov ax,di mov dx,0 ret NoHandles: mov ax,0 mov dx,ax ret TestHandles endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept Help, 'HELP', ImpliedStyle, 1, None, Bang, Bang accept Help, '?', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayHELP proc near ; dummy does nothing, testroutine does the work. ret DisplayHELP endp ; = = = = = = = = = = = = = = = = = = = = = = TestHELP proc near ; Display something similar to what we would show on a syntax error say CopyRightMsg ; CopyRight banner say SyntaxMsg ; sample syntax mov ax, 4c04h ; ERRORLEVEL = 4 int 21h ; Die ; Don't continue, or might generate ret ; second similar message. ; Treat as error, just in case. TestHELP endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept HIMEMExist, 'HIMEM', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayHIMEMExist proc near ; on entry ax = 0 if not present, 1 if present ; display state saystr 'HIMEM' call SayIfDetected ret DisplayHIMEMExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestHIMEMExist proc near ; Test for presence of HIMEM.SYS, leave 1 in AX if found, 0 ; otherwise. This is the memory manager that comes with DOS. ; code found by experiment. ; HIMEM.SYS is device driver XMSXXXX0. ; It hooks int 2F ax=4308h ; returns mysterious numbers in bh and bl ; and sets AL = 43h to indicate its presence. ; Trashes all registers except the segment registers. ; This code has not been extensively tested. Some other ; memory managers may mimic HIMEM. HIMEM might not advertise ; itself in all versions of DOS. I tested 5.0 and 6.2. mov al,2fh call SafeVector ; see if vector is hooked up to anything jc HIMEMAbsent ; Could not be any HIMEM mov ax,04308h ; test for presence of memory driver int 2fh cmp al,043h jne HIMEMAbsent HIMEMPresent: mov ax,1 ret HIMEMAbsent: mov ax,0 ret TestHIMEMExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept HOUR, 'HOUR', CapacityStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayHour proc near ; on entry DX:AX is time in minutes past midnight call TestTime call DisplayTime ret DisplayHour endp ; = = = = = = = = = = = = = = = = = = = = = = TestHour proc near ; Return hour 0..23 in DX:AX ; returns the current time in DX:AX in minutes past midnight 0..1439. push bx call TestTime mov bx,60d div bx sub dx,dx pop bx ret TestHour endp ;============================================ ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept KEYB, 'KEYB', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayKEYB proc near saystr 'KEYB' call SayIfDetected ret DisplayKEYB endp ; = = = = = = = = = = = = = = = = = = = = = = TestKEYB proc near ; returns 1 in AX if DOS utility KEYB detected, 0 otherwise ; Method from Undocumented DOS page 663 mov al,2fh call SafeVector ; see if vector is hooked up to anything jc KEYBAbsent ; cannot test for it. call RAWDOSVer cmp ax,330 ; only works in DOS 3.3-DOS4.01 jb KEYBAbsent cmp ax,500 jae KEYB5 ; in DOS 5.00 now is documented way mov ax,0ad80h ; use undocumented method int 2fh cmp al,0FFh ; if FF, means it is installed jne KEYBAbsent ; ES: trashed at this point jmp KEYBPresent KEYB5: ; Use official DOS 5.0 test mov ax,0ad80h int 2fh test bx,bx ; 0 = not installed jz KEYBAbsent ; BH = major BL= minor ; see BIX IBM.DOS/SECRETS.3;1929 ; by Andrew Schulman KEYBPresent: mov ax,1 ret KEYBAbsent: mov ax,0 ret TestKEYB endp ;============================================ ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept LAN, 'LAN', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayLAN proc near saystr 'LAN' call SayIfDetected ret DisplayLAN endp ; = = = = = = = = = = = = = = = = = = = = = = TestLAN proc near ; Is a network present? Check drives C:..Z: to see if any are remote. ; Return ax=1 if ANY drive is a LAN, 0 if all are not LAN. ; See Byte 1989/September page 227 LAN-Aware DOS Programs by Barry Nance ; WCowley discovered a bug, since fixed. I had left in a bit of code from ; an earlier version that tested only the default drive. ; Code for detecting CD-ROMs suggested by GChicares. ; CD-ROMS otherwise show up as Remote LAN drives. ; We still have a problem. LASER brand CD-ROMS are still being ; detected as LANS. data segment ; Drives to avoid that are not really LANS FirstCDRom dw 0 ; A:=1 B:=2 LastCDRom dw 0 data ends call RAWDOSVer cmp ax,310 ; Method only works on 3.1+ jb LANAbsent mov al,2fh call SafeVector ; see if 2fh is hooked up to anything jc NoCDROMS ; cannot test for CDROMs, so continue ; LAN test without worrying about them. mov ax,1500h ; check for Microsoft CD-ROM mov bx,0000h ; see Undocumented DOS page 654 mov di,-1 ; indicator that GRAPHICS will change int 2fh ; bx=#of letters used for CD-ROM ; cx=starting letter A=0 cmp di,-1 ; if ax=FFFF chances are this was jne NoCDROMS ; interpreted as Graphics.Com ; install check. MS goofed and ; assigned interrupt to two separate ; functions. However, sometimes ; the CD-ROM call leaves AX=FFFF ; so we cannot trust that. ; Graphics sets DI, to an addess ; so we detect a change in DI to ; indicate Graphics grabbed the ; interrupt. test bx,bx jz NoCDROMs ; even if code run twice will ; still be 0,0 inc cx mov FirstCDRom,cx ; first CD ROM -- not LAN A:=1 add cx,bx dec cx mov LastCDRom,cx ; last CD ROM -- not LAN A:=1 NoCDROMS: mov cx,24 ; test 24 drives A..Z mov bx,3 ; start with C: LoopLANDrives: cmp cx,FirstCDROM jl PossLanDrive cmp cx,LastCDROM ja PossLanDrive jmp NotLanDrive PossLanDrive: mov ax,04409h ; IOCTL, check if block device remote int 21h ; only works on DOS 3.1+ ; Version test done at the top. jc NotLANDrive ; not even a drive, much less LAN test dx,01000h ; test bit 12, 1=remote jz NotLanDrive ; might be LAN or CD-ROM jmp LANPresent ; found one drive on LAN. ; No need to keep looking NotLanDrive: inc bx ; try next drive loop LoopLanDrives LANAbsent: ; no LAN drives mov ax,0 ret LANPresent: ; found a LAN drive mov ax,1 ret TestLAN endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept MINUTE, 'MINUTE', CapacityStyle, 0, Any, Bang, Bang accept MINUTE, 'MIN', CapacityStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayMinute proc near ; on entry DX:AX is time in minutes past midnight call TestTime call DisplayTime ret DisplayMinute endp ; = = = = = = = = = = = = = = = = = = = = = = TestMinute proc near ; Return Minute 0..59 in DX:AX push bx call TestTime ; returns the current time in DX:AX in minutes past midnight 0..1439. mov bx,60d div bx mov ax,dx sub dx,dx pop bx ret TestMinute endp ;============================================ ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept MONTH, 'MONTH', CapacityStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayMonth proc near ; ax contains month 1 .. 12 saystr 'month is ' mov dpl,-1 call SayBigDec ret DisplayMonth endp ; = = = = = = = = = = = = = = = = = = = = = = TestMonth proc near ; returns the current Month in DX:AX e.g. 12 ; ; This parameter was requested by Aron Gurski of Norway ; also known as AGurski on BIX. ; See page 384 of Ray Duncan's Advanced MS DOS mov ah,02ah ; get date DOS function int 21h ; month is returned in DH mov al,dh sub ah,ah sub dx,dx ret TestMonth endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept MOUSEExist, 'MOUSE', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayMouseExist proc near saystr 'Mouse' call SayIfDetected ret DisplayMouseExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestMouseExist proc near ; Is a MOUSE present? ; See Logitech Technical Reference page 6-4 mov al,33h call SafeVector ; see if vector is hooked up to anything jc MOUSEAbsent ; Could not be a mouse IsMouseVec: sub ax,ax ; test for presence of mouse driver int 33h test ax,ax ; ax = mouse status; bx = number of buttons jz MouseAbsent MousePresent: mov ax,1 ret MouseAbsent: mov ax,0 ret TestMouseExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept MSDOSExist, 'MSDOS', ImpliedStyle , 1, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayMSDOSExist proc near ; Displays current state of whether using MS DOS any version ; Presumes Call TestMSDOSExist called just prior, so AX has state saystr 'MS DOS' call SayIfDetected ret DisplayMSDOSExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestMSDOSExist proc near ; returns ax=0 if not running under MSDOS, ax=1 if we are. ; any version call TestIBMBIOExist ; if PC DOS IBMBIO present, can't be MSDOS test ax,ax jnz MSDOSAbsent call TestDRDOSExist ; if DR DOS, can't be MS DOS test ax,ax jnz MSDOSAbsent ; Treat OS/2 compat box as either ; MS OR PC DOS. ; There are no other choices, so it must ; be MS DOS. MSDOSPresent: mov ax,1 ret MSDOSAbsent: mov ax,0 ret TestMSDOSExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept MSDOSVer, 'MSDOS', VersionStyle , 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayMSDOSVer proc near ; on extry ax contains version * 100 saystr 'MSDOS' call SayVersion ret DisplayMSDOSVer endp ; = = = = = = = = = = = = = = = = = = = = = = TestMSDOSVer proc near ; return MSDOS version number *100 in AX, 0 if not PC DOS call TestMSDOSExist test ax,ax jz NotMSDOSVer call RAWDOSVer ; will return with ax=version ; handles special DOS 4.01 problem ret NotMSDOSVer: mov ax,0 ret TestMSDOSVer endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept NCacheExist, 'NCACHE', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayNCACHEExist proc near ; Displays current state of Norton disk caching. ; Presumes Call TestNCACHEExist called just prior, so AX has state saystr 'NCache' call SayIfDetected ret DisplayNCACHEExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestNCACHEExist proc near ; Is the Norton Cache program running -- either as device driver or TSR? ; This information was gleaned by watching the NCACHE utility ; communicate with the NCACHE TSR using periscope. ; Detect if Norton Cache 6.01 is present mov ax,0FE00h ; set up mux interrupt signature ; function 00 --is NCACHE present? mov di,04E55h ; "NU" mov si,04346h ; "CF" stc ; set carry to indicate failure push ES ; wrecks AX,BX,ES,CX. Only ES important int 02fh ; multiplex interrupt pop ES jc NCACHEAbsent ; carry set means is not there. NCACHEPresent: mov ax,1 ret NCACHEAbsent: mov ax,0 ret TestNCACHEExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept NETBIOS, 'NETBIOS', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayNETBIOS proc near saystr 'NetBIOS' call SayIfDetected ret DisplayNETBIOS endp ; = = = = = = = = = = = = = = = = = = = = = = TestNETBIOS proc near ; Is a NETBIOS present? ax=1 if yes, 0 if no ; See Byte 1989/September page 227 NETBIOS-Aware DOS Programs by Barry Nance mov al,2ah call SafeVector ; see if vector is hooked up to anything jc NoNETBIOS ; Could not be NETBIOS mov ah,0 int 2ah ; trigger NETBIOS test ah,ah ; if ah is still 0, NETBIOS wasn't there jnz IsNetBios NoNETBIOS: mov ax,0 ret IsNETBIOS: mov ax,1 ret TestNETBIOS endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; Numeric coprocessors (including the 80486) accept NPXes, 'NPX', ImpliedStyle, 10, Any, Plus, Plus accept NPXes, '8087', ImpliedStyle, 10, Any, Bang, Bang accept NPXes, '80287', ImpliedStyle, 200, Any, Bang, Bang accept NPXes, '80387', ImpliedStyle, 300, Any, Bang, Bang accept NPXes, '80487', ImpliedStyle, 400, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayNPXes proc near saystr 'Numerical co-processor is ' saywhen 10,'8087.' saywhen 200,'80287.' saywhen 300,'80387.' saywhen 400,'80487.' saystr 'not present.' ret DisplayNPXes endp ; = = = = = = = = = = = = = = = = = = = = = = TestNPXes proc near ; Detects the presence of various numeric coprocessors. ; AX returns 10 for 8087, 200 for 80287, 300 for 80387, 400 for 80487 ; Currently cannot discriminate 80387 and 80487. ; As soon as we can tell 80386 from 80486, this code will start to work. ; ; TestNPXes pays no attention to the BIOS equip flag. ; It goes straight for the chip. ; Method from the Intel 80387 tech ref manual page 6-4. ; Also borrows from Intel 80386/80287 tech ref manual page 3-3. ; Based on code provided by Jfleming. ; I stole the fdisi method of discriminating the 8087 and 80287 ; from a disassembly of CPU-NDP.COM by Baron L Roberts of Ziff-Davis. data segment even NPXControl dw 0 ; NPX control word storage area NPXStatus dw 0 ; NPX status word storage area data ends fninit ; initialize NPX, using non-wait form (wait ; form will hang an 8086 or 8088 if no ; NPX is present) mov word ptr NPXcontrol,5A5Ah ; prime with invalid value fnstsw NPXStatus ; save status word. No WAIT. ; ignored if no NPX present cmp byte ptr NPXStatus,0 jne No_Npx ; double check that control word is as expected fnstcw NPXcontrol ; attempt to store control word mov ax,NPXControl and ax,103Fh ; mask out infinity, rounding, precision cmp ax,03Fh jne No_Npx ; We are now sure we have some sort of NPX ; now differentiate between 8087/80287 and 80387 fld1 ; must use default control word from FNINIT fldz ; form infinity as 1 divided by zero. fdiv ; 8087/80287 says +infinity = -infinity fld st ; form negative infinity (DUP NEGATE) fchs ; 80387 says +infinity <> -infinity fcompp ; see if they are the same and remove them fstsw NPXStatus ; look at status mov ax,NPXStatus sahf ; see if infinities matched je not_80387 ;here, we have detected an 80387 or 80487 call TestCpus ; if have 80486, must be 80487 cmp ax,400 jb Is_80387 jmp Is_80487 ;here, we have detected an 8087 or 80287 Not_80387: ; The old 8087 had a bit in the ; control register to control ; interrupts. The fdisi instruction ; disables interrupts in the 8087, ; but is ignored in the 80287. and NPXControl,0FF7Fh ; mask off the interrupts disabled ; bit, i.e. enable interrupts fldcw NPXControl fdisi ; disable interrupts on 8087 ; set the interrupt disable bit. ; ignored by the 80287 fstcw NPXControl test NPXControl,080h ; test the interrupt disable bit jnz Is_8087 ; if bit changed, was 8087 jmp Is_80287 Is_80487: mov ax,400d ; use code 400 for 80486/7 ret Is_80387: mov ax,300d ; use code 300 for 80387 ret Is_80287: mov ax,200d ; use code 200 for 80287 ret Is_8087: mov ax,10d ; use code 10 for 8087 ret ;here, no NPX is present No_npx: mov ax,0 ; no NPX detected, code 0 ret TestNPXes endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept OROperator, 'OR', ImpliedStyle , 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayOrOperator proc near ; Dummy test display routine to display the OR operator saystr '-- OR --' ret DisplayOrOperator endp ; = = = = = = = = = = = = = = = = = = = = = = TestOrOperator proc near ; This is not really a test, but a logical operator to separate tests: ; E.g. NEED 80387 80386 OR 80486 ; We implement it to look like a test routine. mov ax,0 ; dummy as if not present, i.e. good. test Success,-1 jnz AGroupSucceeded mov Success,-1 ; start a new group, forgive failure ret AgroupSucceeded: mov PreviousSuccess,-1 ; record group's success, in case ; future groups fail. ret TestOrOperator endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept PCDOSExist, 'PCDOS', ImpliedStyle , 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayPCDOSExist proc near ; Displays whether using IBM DOS or not. ; Presumes Call TestPCDOSExist called just prior, so AX has state saystr 'IBM PCDOS' call SayIfDetected ret DisplayPCDOSExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestPCDOSExist proc near ; returns ax=0 if not running under PCDOS, ax=1 if we are. ; any version. call TestIBMBIOExist test ax,ax jz PCDOSAbsent ; if no IBMBIO, not PCDOS call TestDRDOSExist ; if DR DOS present, can't be PCDOS test ax,ax jnz PCDOSAbsent ; There are no other choices, so it must ; be PC DOS. PCDOSPresent: mov ax,1 ret PCDOSAbsent: mov ax,0 ret TestPCDOSExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestIBMBIOExist proc near ; returns ax=1 if IBMBIO.COM on boot drive detected String segment IBMBIO db 'C:\IBMBIO.COM',0 SETCOMSPEC db 'COMSPEC' String ends data segment DTA db 128 dup (?) ; disk transfer area data ends push bx mov ax,3000h int 21h ; get version number ; AL=major AH=minor dump "Version/OEM test" test bh,bh ; we want major*100 + minor pop bx jnz IBMBIOabsent ; BH = OEM id, FF for MS, 0 for IBM ; Some companies pretend to be IBM. ; However MS DOSer's that tell the truth ; we can find right away. push cx push dx sub dx,dx mov ax,3305h ; get boot drive, only works in DOS 4.0+ ; see Advanced MS DOS page 392 int 21h ; DL=boot drive 1=A: 2=B: etc ; if comes back 0, did not work, try C: test dl,dl ; We have no way of knowing the true boot drive ; Try guessing that it is the same as COMSPEC jz GuessBoot add dl,'A'-1 ; convert to letter mov IBMBIO,dl ; plop letter into filename string jmp FindIBMBIO GuessBoot: ; guess that bootdrive is same as COMSPEC push ES lea si,SETCOMSPEC ; find SET COMSPEC=C:\DOS\COMMAND.COM mov cx,7 call GetSet ; ES:DI points to C:\DOS\COMMAND.COM mov al,ES:[di] ; get drive letter from comspec mov IBMBIO,al ; plop drive letter on top pop ES FindIBMBIO: mov ah,1ah ; set DTA address lea dx,DTA ; DS:DX Address of DTA int 21h mov ah,4Eh ; Find First by handle mov cx,07h ; hidden system r/o File attribute lea dx,IBMBIO ; DS:DX Pointer to filespec (ASCIIZ) int 21h ; first first match ; results to DTA -- we ignore them pop dx pop cx ; carry flag means did not find it jc IBMBIOAbsent IBMBIOPresent: mov ax,1 ret IBMBIOAbsent: mov ax,0 ret TestIBMBIOExist endp ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept PCDOSVer, 'PCDOS', VersionStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayPCDOSVer proc near saystr 'IBM PCDOS' call SayVersion ret DisplayPCDOSVer endp ; = = = = = = = = = = = = = = = = = = = = = = TestPCDOSVer proc near ; return with PC DOS version * 100 in ax, 0 if not PC DOS call TestPCDOSExist test ax,ax jz NotPCDOS call RAWDOSVer ; Do have PC DOS, now which version ret NotPCDOS: sub ax,ax ret TestPCDOSVer endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept PCCacheExist, 'PCCACHE', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayPCCacheExist proc near ; Displays current state of Central Point PCTools PCACHE disk caching. ; Presumes Call TestPCCacheExist called just prior, so AX has state saystr 'PCCache' call SayIfDetected ret DisplayPCCacheExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestPCCacheExist proc near ; Is the Central Point PCTools PCACHE disk cache running? ; This information was gleaned from a telephone conversation ; with a Central Point programmer. ; This code works on version 4.4 and 6.0. ; I suspect it might NOT work on earlier and later versions. ; Detect if Central Point Cache is present mov ax,0FFA5h ; signature, NOT INT 2f!! ; is PCTOOLS present? mov cx,01111h ; int 016h ; KEYBOARD interrupt of all things! test ch,ch jnz PCCacheAbsent ; non zero means not present PCCachePresent: mov ax,1 ret PCCacheAbsent: mov ax,0 ret TestPCCacheExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept PCMOSExist, 'PCMOS', ImpliedStyle , 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayPCMOSExist proc near ; Displays whether using PCMOS or not. ; on entry ax has 1 if present, 0 if not. saystr 'PCMOS' call SayIfDetected ret DisplayPCMOSExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestPCMOSExist proc near ; returns ax=0 if not running under PCMOS, ax=1 if we are. ; any version. ; Code compliments of Dave Williams of The Software Link mov ax,3000h mov bx,ax ; set ax == bx == cx == dx mov cx,ax ; to read the MOS version # mov dx,ax int 21h push ax mov ax,3099h ; now insure ax is different int 21h ; to read the DOS version # pop bx cmp bx,ax ; if bx <> ax then return jne PCMOSPresent PCMOSAbsent: mov ax,0 ret PCMOSPresent: mov ax,1 ret TestPCMOSExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept PERIExist, 'PERI', ImpliedStyle, 1, None, Bang, Bang accept PERIExist, 'PERISCOPE', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayPERIExist proc near saystr 'Periscope' call SayIfDetected ret DisplayPERIExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestPERIExist proc near ; Test for presence of PERI, leave 1 in AX if found, 0 otherwise. ; Works by tracing int 3 which periscope tracks. ; I think I'm the one who made this up. data segment PERIsig db 'SEGDEBUG',0 data ends mov ah,35 call SafeVector jc PeriAbsent mov ax,3503h ; PS uses int 03h int 21h ; get current int 03h vector in ES:BX ; offset 0100h contains signature ; offset 010Ah contains 0500h or 0510h version mov di,0100h lea si,PeriSig mov cx,9 rep cmpsb jne PeriAbsent PeriPresent: mov ax,1 ret PeriAbsent: mov ax,0 ret TestPERIExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept PERIVer, 'PERI', VersionStyle, 0, Any, Bang, Bang accept PERIVer, 'PERISCOPE', VersionStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayPERIVer proc near saystr 'Periscope' call SayVersion ret DisplayPERIVer endp ; = = = = = = = = = = = = = = = = = = = = = = TestPERIVer proc near ; Test for version of PERI, leave version*100 AX if found, 0 otherwise. ; From a message posted in the Periscope conference on BIX. call TestPeriExist test ax,ax jz NotPeriVer mov ax,3503h ; peri uses int 03h int 21h ; get current int 03h vector in ES:BX ; offset 0100h contains signature ; offset 010Ah contains 0500h or 0510h version ; This is a different scheme from the one ; DOS uses. 0510h -> 510d meaning ver 5.1 IsPERIVer: mov ax,ES:[010Ah] call NibblesToBin ret NotPERIVer: sub ax,ax ret TestPERIVer endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept QEMMExist, 'QEMM', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayQEMMExist proc near saystr 'QEMM' call SayIfDetected ret DisplayQEMMExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestQEMMExist proc near ; Test for presence of QEMM, leave 1 in AX if found, 0 otherwise. ; Test works by testing for device (not file) called QEMM386$ ; Inspired by WLMoore. data segment QEMMsig db 'QEMM386$',0 data ends mov ax,03d00h ; Open file, read only lea dx,QEMMsig ; called QEMM386$ int 21h jc NotQEMM ; If CF=1, return errcode ; double check that is it a device, not a file mov bx,ax ; save handle mov ax,04400h int 21h ; IOCTL get device data ; DX has status mov ah,3Eh ; close the driver int 21h test dx,080h ; is it a device? 1=device 0=file jz NotQEMM ; if a file, was not QEMM IsQEMM: mov ax,1 ret NotQEMM: mov ax,0 ret TestQEMMExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept QRAMExist, 'QRAM', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayQRAMExist proc near ; on entry ax = 0 if not present, 1 if present ; display state saystr 'QRAM' call SayIfDetected ret DisplayQRAMExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestQRAMExist proc near ; Test for presence of QRAM, leave 1 in AX if found, 0 ; otherwise. QRAM is Quarterdeck's memory manager for the 80286. ; ; QRAM.SYS is NOT an EMM driver. It acts like an XMS driver ; with no RAM. Only when QEXT.SYS is also loaded does the ; XMS RAM appear. Only if you already have EMS RAM ; hardware does EMS RAM appear. QRAM's only function is ; to create RAM in the 640 to 1 MB region for LOADHI. ; ; This code is based on WLMoore's C program. This is simply an ; assembler translation. I don't have have never seen the docs ; on the two crucial interrupts. WLMoore must take full credit for this. ; ; We use int 2f function D200 to find out if a memory driver ; has been installed. (This unusual interrupt is documented ; in Ralph Brown's Interrupt list release 25 (V91.2).) ; ; Then we use int2f function D201 to get a pointer to the HIGH ; memory (MCB) chain in CX. (There are TWO chains when you have programs ; like QRAM loading high.) ; ; What we are looking at is at offset 8 of the MCB segment -- ; the magic letters QRAM. ; ; Trashes all registers except the segment registers. data segment QRAMsig db 'QRAM' data ends mov al,2fh call SafeVector ; see if vector is hooked up to anything jc QRAMAbsent ; Could not be any QRAM mov ax,0D200h ; test for presence of memory driver mov bx,'QD' ; Quarterdeck signature QD MEM 0 mov cx,'ME' mov dx,'M0' int 2fh ; see Ralf Brown ; AL=ff means all is groovy ; BX,CX,DX='MEMDVR', but testing is overkill cmp al,0ffh jne QRAMAbsent ; AL=else means no QRAM mov ax,0D201h ; Get start of High Ram driver chain mov bx,'HI' ; signature HI RAM 0 mov cx,'RA' mov dx,'M0' int 2fh ; see Ralf Brown again ; BX='OK' means all is still ok cmp bx,'OK' jne QRAMAbsent ; BX=else means no QRAM ; cx is seg pointer to MCB chain ; dx is pointer to QRAM code segment ; We don't use dx. ; If you get this far you must be either QRAM or QEMM ; QRAM loads itself high, and puts itself first on the high ; MCB chain. push ES mov ES,cx mov di,8 ; ES:DI likely points to the letters 'QRAM' lea si,QRAMsig ; DS:si QRAM signature we are looking for mov cx,4 ; length = 4 bytes repe cmpsb pop ES jne QRAMAbsent ; on match we have QRAM QRAMPresent: mov ax,1 ret QRAMAbsent: mov ax,0 ret TestQRAMExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept RAM, 'RAM', CapacityStyle, 0, Any, Plus, Minus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayRAM proc near ; on entry bytes free conventional ram is in DX:AX call SaybytesCapacity saystr 'conventional RAM.' ret DisplayRAM endp ; = = = = = = = = = = = = = = = = = = = = = = TestRAM proc near ; on exit DX:AX contains largest chunk of free RAM in bytes ; See Ray Duncan's Advanced MS DOS page 440. mov ah,04Ah ; resize RAM mov bx,0ffffh ; ask for all the ram ; We have everything already int 21h ; Will always fail, so carry is ok. ; no need to give back. mov ax,16 ; bx = paras allocated mul bx ; dx:ax = bytes allocated ret TestRAM endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept REAL, 'REAL', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayREAL proc near test ax,ax jnz DisInReal saystr 'cpu in virtual, not real (8086) mode.' ret DisInReal: saystr 'cpu in real (8086) mode.' ret DisplayREAL endp ; = = = = = = = = = = = = = = = = = = = = = = TestREAL proc near ; returns 1 in AX in REAL mode, else 0. Other modes include virtual ; and protected. smsw technique compliments of Steve Grant. ; This might get us in trouble with meddling supervisors. push sp ; older processors will push pop ax ; SP-2 instead of SP cmp ax,sp jne IsReal ; was older, must be real. ; We don't dare try the smsw on it smsw cx ; store Machine Status Word to CX ror cx, 1 ; rotate protected mode flag into carry bit jc IsVirtual IsReal: mov ax,1 ret IsVirtual: mov ax,0 ret TestREAL endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept SLOWExist, 'SLOW', ImpliedStyle, 1, any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplaySlowExist proc near saystr 'Using Slow tests' ret DisplaySlowExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestSlowExist proc near ; Force tests to use a slow method to avoid upsetting a mother ; supervisor. data segment Slowtests dw 0 ; do we need to use slow tests? ; initially no. If SLOW command, then yes. ; used my IsMamaWatching. data ends mov SlowTests,-1 ; turn on slow testing mov ax,0 ; dummy as if not present ret TestSlowExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept ShareExist, 'SHARE', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayShareExist proc near saystr 'Share' call SayIfDetected ret DisplayShareExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestShareExist proc near ; Tests if SHARE loaded. Leaves 1 in AX if yes, 0 otherwise mov al,2fh call SafeVector ; see if vector is hooked up to anything jc ShareAbsent ; We cannot detect it. call RAWDOSVer ; check DOS version ; Share test only works 3.2+ ; see Ray Duncan's Advanced MS DOS page 490 cmp ax,320d jb ShareAbsent ; early DOS did not have Share mov ax,01000h ; function 10 00 of multiplex interrupt ; get installed state int 02Fh cmp al,0FFh ; if FF, means it is installed jne ShareAbsent SharePresent: mov ax,1 ret ShareAbsent: mov ax,0 ret TestShareExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept SMARTDrvExist, 'SMARTDRV', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplaySMARTDrvExist proc near saystr 'SMARTDrv' call SayIfDetected ret DisplaySMARTDrvExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestSMARTDrvExist proc near ; Test for presence of SMARTDrv caching, ; leave 1 in AX if found, 0 otherwise. ; For version 4+ use int 2f BABE method, for earlier versions, ; test for device (not file) called SMARTAAR. ; Techniques compliments of TenThumbs and Dave Rifkind on BIX. ; Technique similar to one used to detect QEMM. mov al,02fh call SafeVector ; see if 2fh is hooked up to anything jc NotSmart4 push bx push cx push dx push si push di push bp mov ax,4A10h ; test for version 4+ sub bx,bx mov cx,0EBABh ; 'BABE' signature int 2fh ; uses ax,bx,cx,dx,di,si,bp pop bp pop di pop si pop dx pop cx pop bx cmp ax,0BABEh jnz NotSmart4 mov ax,1 ; was version 4+ ret data segment SMARTDrvsig db 'SMARTAAR',0 data ends NotSmart4: ; might be earlier version. mov ax,03d00h ; Open file, read only lea dx,SMARTDrvsig ; called SMARTARR int 21h jc NotSMARTDrv ; If CF=1, return errcode ; double check that is it a device, not a file mov bx,ax ; save handle mov ax,04400h int 21h ; IOCTL get device data ; DX has status mov ah,3Eh ; close the driver int 21h test dx,080h ; is it a device? 1=device 0=file jz NotSMARTDrv ; if a file, was not SMARTDrv IsSMARTDrv: call testNcacheExist ; might be false positive on Norton NCACHE test ax,1 jnz NotSmartDrv call TestPCCACHEExist ; might be false positive on PCTools PCCACHE test ax,1 jnz NotSmartDrv mov ax,1 ret NotSMARTDrv: mov ax,0 ret TestSMARTDrvExist endp ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept TIME, 'TIME', TimeStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayTime proc near ; Display as HH:MM ; on entry DX:AX is time in minutes past midnight mov bx,60d div bx push dx ; dx = MM ax= HH sub dx,dx saystr 'time is ' mov cx,2 ; display two digits call SayLZDec ; do HH saystr ':' pop ax sub dx,dx call SayLZDec ; do MM ret DisplayTime endp ; = = = = = = = = = = = = = = = = = = = = = = TestTime proc near ; returns the current time in DX:AX in minutes past midnight 0..1439. ; push cx mov ah,02ch ; get time DOS function int 21h ; ch=HH cl=mm dh=sec dl=centisec mov al,60d mul ch ; ax = hh*60 sub ch,ch add ax,cx ; ax = hh*60 + mm sub dx,dx pop cx ret TestTime endp ;============================================ comment û ; T H I S C O D E I S N O T U S E D ; This whole section is just beginning. It is nowhere near ; completion. ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept VIDEO, 'CGA', ImpliedStyle, 10, any, Bang, Bang accept VIDEO, 'MDA', ImpliedStyle 20, any, Bang, Bang accept VIDEO, 'HERC', ImpliedStyle, 30, any, Bang, Bang accept VIDEO, 'HERCPLUS', ImpliedStyle, 40, any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayVideo proc near saystr 'Video card type is ' saywhen 10,'CGA Colour Graphics Adapter.' saywhen 20,'MDA Plain Monchrome Data Adapter.' saywhen 30,'Hercules GB102 graphics.' saywhen 40,'Hercules Plus GB112 graphics.' saywhen 50,'EGA Enhanced Graphic Adapter.' saywhen 60,'Hercules InColour.' saywhen 70,'VGA Video Graphics Array Grayscale' saywhen 80,'VGA Video Graphics Array Colour.' saywhen 90,'Super VGA Video Graphics Array Grayscale.' saywhen 100,'Super VGA Video Graphics Array Colour.' saystr 'unknown.' ret DisplayVideo endp ; = = = = = = = = = = = = = = = = = = = = = = TestVideo proc near ; Detects type of primary video card present ; and returns a numeric code in ax for the type ; 0 unknown ; 10 CGA Colour Graphics Adapter ; 20 MDA Plain Monchrome Data Adapter ; 30 Hercules GB102 graphics ; 40 Hercules Plus GB112 graphics ; 50 EGA Enhanced Graphic Adapter ; 60 Hercules InColour ; 70 VGA Video Graphics Array Grayscale ; 80 VGA Video Graphics Array Colour ; 90 Super VGA Video Graphics Array Grayscale ; 100 Super VGA Video Graphics Array Colour mov ax,0 ret TestVideo endp ; = = = = = = = = = = = = = = = = = = = = = = TestCGA proc near ; Method from Richard Wilton's Programmer's Guide to ; PC & PS/2 Video Systems pafe 517 ; If you have dual monitors, it will find either one ; returns 1 if CGA or CGA+, 0 otherwise in AX. CGA_status_port equ 3d4h ; Display Status Port mov ax,CGA_Status_Port call Test6845 ; check if CRT controller at that addr ; returs ax=0 for no, ax=1 for yes ret TestCGA endp ; = = = = = = = = = = = = = = = = = = = = = = Test6845 proc near ; on extry ax has port address ; on exit, ax has 1 if 6845 CRT controller found, 0 otherwise mov dx,ax ; set up port address mov al,0fh ; Select cursor low register out dx,al inc dx ; dx now points to data register in al,dx ; read current cursor low row mov ah,al ; save mov al,66h ; poke silly value for cursor low out dx,al mov cx,100h ; delay a little wait6845: loop Wait6845 in ax,dx ; read it back xchg ah,al ; ah = returned value ; al = original value out dx,al ; restore cursor low cmp ah,66h ; did the chip respond? je Is6845 No6845: mov ax,0 ret Is6845: mov ax,1 ret Test6845 endp ;============================================ ; = = = = = = = = = = = = = = = = = = = = = = DisplayHERC proc near saystr 'Hercules Graphics card GB102 or GB112' call SayIfDetected ret DisplayHERC endp ; = = = = = = = = = = = = = = = = = = = = = = TestHERC proc near ; Method from LOAD48.ASM, a file that Tech support at Hercules sent me. ; If you have dual monitors, it only tests the active one. ; returns 1 if HERC or HERC+, 0 otherwise in AX. Herc_status_port equ 3BAh ; Display Status Port mov ah,15 ; check current video state int 10h cmp al,7 ; is it 80 x 25 'B&W' Card jne NoHerc ; no give up mov dx,Herc_status_port ; record state in al,dx and al,80h ; save bit 7 for test mov ah,al mov cx,8000h Herc_examine_Loop: in al,dx ; take another reading and al,80h ; again save bit seven cmp al,ah jne HaveHerc ; if bit 7 changes, then it loop Herc_examine_Loop ; is a Hercules Graphics Card NoHERC: mov ax,0 ret HaveHERC: mov ax,1 ret TestHERC endp ;============================================ DisplayHERCPLUS proc near saystr 'Hercules Plus GB112 Graphics card' call SayIfDetected ret DisplayHERCPLUS endp ; = = = = = = = = = = = = = = = = = = = = = = TestHERCPLUS proc near ; Method from LOAD48.ASM, a file that Tech support at HERCPLUSules sent me. ; If you have dual monitors, it only tests the active one. ; returns 1 if HERCPLUS+, 0 otherwise in AX. HercPlus_id_mask equ 00110000b HercPlus_id_code equ 00010000b call TestHerc ; HercPlus card must past Herc test test ax,ax jz NoHercPlus mov dx,Herc_status_port in al,dx ; test for GB112 and al,HercPlus_id_mask ; clear all but bits 4 and 5 cmp al,HercPlus_id_code ; test ID bits jne NoHercPlus ; failed - not a GB112 HaveHercPlus: mov ax,1 ret NoHercPlus: mov ax,0 ret TestHERCPLUS endp ;============================================ û ; end comment ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept VCPIExist, 'VCPI', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayVCPIExist proc near saystr 'VCPI (Virtual Control Program Interface) memory manager' call SayIfDetected ret DisplayVCPIExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestVCPIExist proc near ; returns 1 in AX if VCPI memory manager present, 0 otherwise ; VCPI stands for Virtual Control Program Interface. ; information for this routine compliments of Ergo2 on BIX. ; There is a small amount of information on VCPI in Extending DOS ; by Ray Duncan, but not quite enough detail to use the functions. ; See also TestVCPI which tests amount of VCPI RAM call IsMamaWatching ; Test for Windows 3E 4E. ; She gets very pissed you test ; for VCPI. test ax,ax ; Under WIN3E treat VPCI as absent jnz VCPIAbsent call TestExpExist ; must be EMS driver active test ax,ax ; i.e. int 67h jz VCPIAbsent mov ax,0de00h ; check VCPI presence via int 67 int 67h ; no need for SafeVector ; AX=0 means VCPI present ; version is in BX BH:BL major:minor ; in hex test ax,ax jnz VCPIAbsent VCPIPresent: mov ax,1 ret VCPIAbsent: mov ax,0 ret TestVCPIExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept VCPICap, 'VCPI', CapacityStyle, 0, Any, Plus, Plus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayVCPICap proc near call SaybytesCapacity saystr 'VCPI (Virtual Control Program Interface) RAM.' ret DisplayVCPICap endp ; = = = = = = = = = = = = = = = = = = = = = = TestVCPICap proc near ; returns amount of free ram in bytes in DX:AX ; VCPI stands for Virtual Control Program Interface. ; information for this routine compliments of Ergo2 on BIX. ; See page 391 in Extending DOS by Ray Duncan. ; call TestVCPIExist test ax,ax jz NoVCPI mov ax,0DE03h int 67h ; returns number of 4K pages in EDX ; ah=0 if all ok ; no need for SafeVector test ah,ah jnz NoVCPI mov ax,dx ; ignore high order part of EDX ; low order part covers up to 256 MB ; enough for the nonce. mov bx,4096d ; convert from 4K blocks to bytes mul bx ; leaves result in DX:AX ret NoVCPI: sub ax,ax ; not present, treat as 0 bytes mov dx,ax ret TestVCPICap endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept WINs, 'WINDOWS', ImpliedStyle, 01, Any, Plus, Plus accept WINs, 'WIN2', ImpliedStyle, 01, Any, Bang, Bang accept WINs, 'WIN3R', ImpliedStyle, 300, Any, Bang, Bang accept WINs, 'WIN3S', ImpliedStyle, 301, Any, Bang, Bang accept WINs, 'WIN3E', ImpliedStyle, 302, Any, Bang, Bang accept WINs, 'WFWG', ImpliedStyle, 311, Any, Bang, Bang accept WINs, 'WIN95', ImpliedStyle, 400, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayWINs proc near saystr 'Microsoft Windows ' saywhen 1,'old version 2.' saywhen 300,'version 3.x real mode.' saywhen 301,'version 3.x standard mode.' saywhen 302,'version 3.x extended mode.' saywhen 311,'For WorkGroups.' saywhen 400,'95.' saystr 'not present.' ; WINOS2/DOS and NT/WINDOWS/DOS emulation shows up as not present. ret DisplayWINs endp ; = = = = = = = = = = = = = = = = = = = = = = TestWINs proc near ; Get windows version number in into ax. See table above. ; Technique from Ralf Brown's intrlist. ; also Microsoft Journal March 1991 ; I have discovered the following about RAM availability in the DOS BOX in ; Windows using HIMEM.SYS. ; ; /r /s /3 ; expanded RAM no no yes ; extended RAM yes no yes ; XMS yes no yes ; VCPI no no no ; DPMI no no yes ; REAL mode yes yes no ; ; With QEMM intead of HIMEM.SYS, here is how it looks: ; ; /r /s /3 ; expanded RAM yes no yes ; extended RAM yes no yes ; XMS yes no yes ; VCPI yes yes no ; DPMI no no yes ; REAL mode no no no ; We can thus use the presence of XMS to discriminate /r from /s. ; The official test does not work. ; ; Can you detect my hatred for Windows? ; This great bloated creature soaks up machine resources and ; sucks up endless hours placating it. It needlessly interferes ; with programs running under it. It has whole conferences devoted ; to bypassing its bugs. It has set programming BACK 20 years with ; a bizarrely complex programmers' interface. I wish it would die. ; Installing it is a nightmare -- endless meaningless parameters ; like something out of Alice in Wonderland on drugs. It won't ; work with all kinds of hardware -- never giving you the tiniest clue ; what the problem is. To top matters off it won't even properly ; identify itself! You cannot tell real and standard mode apart. ; running in a DOS box under WinOS2 does not count as running under ; Windows. It will be treated as no windows since I think it is really ; running under OS/2 DOS emulation. mov al,2fh call SafeVector ; see if vector is hooked up to anything jc IsWin0 ; cannot test for it. mov ax,1600h int 2fh and ax,07fh ; AL = 00 if nothing, WIN3r, or WIN3s ; 01 if WIN 2.x ; 03 if WIN3e, WFWG ; 04 if WIN95 ; 7F if WIN 2.x ; else treat as nothing jz IsWIN3r3s0 cmp al,01 je IsWIN2 cmp al,03 je IsWIN3eOrWFWG cmp al,04 je IsWIN95 cmp al,07fh je IsWIN2 jmp IsWIN0 IsWIN3r3s0: ; discriminate between [WIN3R, WIN3S] and [0] mov ax,4680h int 2fh ; AL = 00 if WIN3r or WIN3s ; 80 if nothing test al,al jnz IsWIN0 ; discriminate between [WIN3R] and [WIN3S] call TestXMSCap ; get free XMS ram -> dx:ax ; Note we test for free RAM, not driver presence. or dx,ax ; STANDARD mode gobbles up all the XMS. jz IsWin3S ; In REAL mode, there should be some some left. jmp IsWin3R comment î ; T H I S C O D E D O E S N O T W O R K ; discriminate between [WIN3R] and [WIN3S] mov ax,1605h ; simulate initialization of standard mode xor bx,bx ; from Microsoft Journal mov es,bx ; only trouble is, this code does not work. xor si,si mov ds,si xor cx,cx mov dx,1 int 2fh or cx,cx ; did it work? jnz IsWin3R ; no, must have been REAL mode mov ax,1606h ; simulate exit from standard mode int 2fh jmp IsWIN3S î ; end of comment IsWIN3eOrWFWG: ; we already know multiplex 2f works. mov ax,160Ah ; technique from Mike Blaszczak of Microsoft int 2fh ; BH = major BL = minor, e.g. 3.1 = 030A ; we know this is supported. cmp bl,11 ; version 3.11? je IsWFWG ; Windows For Workgroups is Windows 3.11 jmp IsWin3E ; Windows 3E is 3.00 or 3.10. IsWIN0: mov ax,0 ; no windows ret IsWIN2: mov ax,1 ; /WIN2 ret IsWIN3r: mov ax,300 ; /WIN3R ret IsWIN3s: mov ax,301 ; /WIN3S ret IsWIN3e: mov ax,302 ; /WIN3E ret IsWFWG: mov ax,311 ; /WFWG ret IsWIN95: mov ax,400 ; /WIN95 ret TestWINs endp ; = = = = = = = = = = = = = = = = = = = = = = IsMamaWatching proc near ; Windows 3.0 in enhanced mode interferes with various tests. ; When we detect it, we have to use more plebeian, less accurate tests. ; returns 1 in ax if WIN3E detected, 0 otherwise. ; see Microsoft Journal March 1991 ; Note, IsMamaWatching does not SET Slowtests if Mama is watching. test Slowtests,-1 jnz MamaIsPresent ; act as if Windows were here mov al,2fh call SafeVector ; see if vector is hooked up to anything jc MamaIsAbsent ; cannot test for Windows. mov ax,1600h ; Test for Windows 3E 4E. int 2fh and ax,07fh ; AL = 00 if nothing, WIN3r, or WIN3s ; 01 if WIN 2.x ; 03 if WIN3e ; 04 if WIN4e ; 7F if WIN 2.x ; else treat as nothing cmp al,3 je MamaIsPresent cmp al,4 je MamaIsPresent MamaIsAbsent: mov ax,0 ret MamaIsPresent: mov ax,1 ret IsMamaWatching endp ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept XMSExist, 'XMS', ImpliedStyle, 1, None, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayXMSExist proc near saystr 'XMS RAM driver' call SayIfDetected ret DisplayXMSExist endp ; = = = = = = = = = = = = = = = = = = = = = = TestXMSExist proc near ; returns 1 in ax if XMS driver detected, 0 otherwise. ; see Ray Duncan's Extending DOS page 95 mov al,2fh call SafeVector ; see if vector is hooked up to anything jc XMSAbsent ; XMS could not be present mov ax,4300h ; test for presence of XMS driver int 2fh cmp al,80h jne XMSAbsent ; jmp if no XMS driver present XMSPresent: mov ax,1 ret XMSAbsent: mov ax,0 ret TestXMSExist endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept XMSCap, 'XMS', CapacityStyle, 0, Any, Plus, Minus ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> DisplayXMSCap proc near call SaybytesCapacity saystr 'XMS RAM.' ret DisplayXMSCap endp ; = = = = = = = = = = = = = = = = = = = = = = TestXMSCap proc near ; Returns amount of free XMS ram in bytes in DX:AX ; Method via Microsoft document XMS.TXT ; also Ray Duncan's Extending DOS page 103 data segment even XMSEntry dd 0 ; entry point to XMS handler data ends mov al,2fh call SafeVector ; see if vector is hooked up to anything jc NoXMS ; cannot test for it. mov ax,4300h ; test for presence of XMS driver int 2fh ; see page 95 Extending DOS cmp al,80h jne NoXMS ; jmp if no XMS driver present mov ax,4310h ; get driver entry point int 2fh ; ES:assumed nothing otherwise override will not stick ; ES:BX contains entry point mov word ptr XMSEntry+2,ES mov word ptr XMSEntry,BX mov ah,8h call XMSEntry ; query free space via far call ; ax has largest block ; dx has total free space in K test ax,ax ; ax = 0 signifies error jnz haveSomeXMS NoXMS: sub dx,dx mov ax,dx ; no ram free at all ret HaveSomeXMS: mov ax,dx ; free ram in K sub dx,dx call MultBy1024 ; free ram in bytes ret TestXMSCap endp ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> accept YEAR, 'YEAR', CapacityStyle, 0, Any, Bang, Bang ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> ; = = = = = = = = = = = = = = = = = = = = = = DisplayYear proc near saystr "year is " mov cx,4 call SayLZDec ; YYYY ret DisplayYear endp ; = = = = = = = = = = = = = = = = = = = = = = TestYear proc near ; returns the current year in DX:AX e.g. 1991 ; ; This parameter was requested by Aron Gurski of Norway ; also known as AGurski on BIX. ; see page 384 Ray Duncan's Advanced MS DOS mov ah,02ah ; get date DOS function int 21h ; year is returned in CX mov ax,cx sub dx,dx ret TestYear endp ;============================================