page 60,132 name need title NEED Version 6.6 .286p ; need an smsw to check virtual state ; ideally need 32 bit registers too. ; code runs on more primitive machines. ; E Q U A T E S SNIFF equ 1 AVOID equ 2 NEED equ 3 ; use /DGenerating#sniff ; /DGenerating#avoid ; /DGenerating#need ; on the assembler command line to select which Version ; of the code to assemble. ; Or add code following of the form: GENERATING EQU NEED if Generating eq Sniff %OUT generating Sniff.Com endif if Generating eq Avoid %OUT generating Avoid.Com endif if Generating eq Need %OUT generating Need.Com endif DEBUGGING equ 1 ; if debugging ; inserts some register dumps ;;DEBUGGING EQU 0 ; if not debugging comment | ================= NEED.COM Copyright: (c) 1991-2017 Roedy Green, Canadian Mind Products Greg Chicares (BIX: gchicares) wrote Weird.Com upon which NEED is based. This program is copyrighted, but the executables be freely distributed for non-military use only. You may cannibalize from the source for your own programs. Ensures various environmental conditions are present. This code also generates AVOID.COM which ensures various environmental conditions are absent. SNIFF.COM simply reports on conditions. Many people contributed directly or indirectly. Many more helped by testing. The beta testers from BIX include: aGurski aRog Blaszczak bQuerry bStrauss dSparks Fred.Robinson gChicares hGessau instantel JeBarr lFirrantello Ligo M.Love macbeth Mike123 mReay pRoub rlis sSchneider terjeM tron wlMoore MFG, tenThumbs NEED DESQ LAN RAM:204K DOS3.3+ Need has a great many more possible commands. See the NEED.TXT for a complete list or study the source code where the MCT table is generated. This code also generates AVOID.COM: AVOID DESQ LAN RAM:204K DOS3.3+ And SNIFF.COM SNIFF DESQ LAN RAM DOS Please report bugs and problems to: Roedy Green Canadian Mind Products #101 - 2536 Wark Street Victoria, BC Canada V8T 4G8 tel:(250) 361-9093 mailto:roedyg@mindprod.com http://mindprod.com Version 1.0á 1991 April 17 - released on BIX to beta testers Version 1.1á 1991 April 29 - fix country dependent comma and decimal separators - cpu test should catch everything but the 486 - NPX test should catch everything but the 487 - properly handle Px cpu classes. /P1 /P2 /P3 - add /PCDOS /PCDOS4.0 /MSDOS /MSDOS4.0 - Presence subroutine - support for HELP and ? - put missing ret on Filter - allow commas or decimals in Version numbers - added /LAN - added /NETBIOS - added /SUNDAY .. /SATURDAY - new rules for suffix defaults Version 1.2á 1991 May 1 - correct test for NPX - add code to test for DRDOS - improved prefetch queue determining code - new code to detect the NEC V20/V30 - some poor code to detect SX and 486 - Versionless commands now detected as syntax errors rather than treated as requests for Version 0. Version 1.3á 1991 May 2 - add VCPI:400K test - add QRAM presence test - add /YEAR /MONTH /DAY - more complex default suffix rules to cover /YEAR Version 1.4á 1991 May 7 - fix code to detect numeric co-processors. I was getting confused between the status word and control word. Version 1.5á 1991 May 13 - delete /P1 cpu classes - EXT:400K now includes two kinds of VDISK extended RAM, INT 15 RAM and XMS RAM in its calculations. - PERI or PERISCOPE now detects presence of PERSCOPE DEBUGGER PERI5.1 or PERISCOPE5.0 now detects periscope version. - NEED now does reports on the success or failure of all the tests. You can get rid of this with output redirection. - OR allows you to continue if any one of a group of tests work. - 80386-DX and other parameters with dashes in them were changed since the parser got confused by the dash, thinking it was a - more relaxed rules on which suffixes are allowed. NEED will not let you request things that don't make much sense, but that you possibly could want like NEED C:5MB- meaning you want 5 MB or less free space on C:. - reinstate default suffix specification for more flexibility. - DOS 4.00 and DOS 4.01 now discriminated - modify to make assemble under MASM 5.0. Sneak around bugs. Version 1.6á 1991 May 20 - distributed to all beta testers - add begin/end critical section on CPU timing loop to stop DESQview from meddling during tests. Should more accurately discriminate SX DX 486 cpus. - experimental code in TestDESQview to it if it bypasses DOS bug giving false positive tests. - new ANSI.SYS test that should detect things like DVANSI.SYS and ANSI.SYS in pre-DOS 4.00 that eluded the earlier test. - safer VCPI test, tests for EMS driver first - turn off DMA during queue length and SX timing test. - DRDOS version number test added. - LAN test no longer fooled by CD-ROM Version 1.7á 1991 May 20 - insert manual DMA refresh when turned off - correct a bug in QRAM that caused freezing on machines. - correct a bug in VCPI: test that caused freezing. - try CMOS test without High bit - Guess C: as boot drive when boot drive unknown, in attempt to tell MS DOS and PC DOS apart. Version 1.8á 1991 May 21 - XT/AT test, hours coded in BCD - debug code in XMS Version 1.9á 1991 May 21 - missing Ret, win3e erroneously reported as version 4 - VCPI test now returns 0 if running under WIN3E to avoid crashing. Windows does not like programs asking about VCPI. - new test to discriminate an 80286 from an 80386 that works even under Windows 386 enhanced mode. - NEED no longer turns off DMA refresh. It caused some machines to freeze. - clean up debugging code so it does not interfere with final result of EXT test. Version 2.0á 1991 May 23 - better 80286 test - better SX vs DX test, however we still use the old test under Windows 3.0 extended. - run the SX vs DX test longer under Windows trying to average out the time slicing effects. - IsMamaWatching used to tell when dicey tests are safe. - Faster REAL test that does not do a full CPU determination. - add code for XT BIOSes that have no INT 15 support whatsoever so that EXT:1+ gives 0. Fixes M.Love's bug. - inadvertently cleared up Agurski's DESQview bug Version 2.1á 1991 May 24 distributed to all Beta testers - clean up comments and label names. - detect DRMDOS as DRDOS - new ANSI.SYS detector for Olivetti, fix Agurski's bug. - simpler /WINDOWS test - remove debug code - generating eq NEED - generating eq AVOID - generating eq SNIFF - SNIFF to display without having to specify what you want. - new tests BIOS WEEKDAY for SNIFF - NPX CPU WINDOWS now implemented as synonym for 8087 8088 WIN2 respectively with default+ suffix. - new faster 486 test - more sensitive PC vs MS Dos test - add address and phone to banner in case turkey posts without the manual. Version 2.2á 1991 May 24 - sent to Dave2 - give individual control on a command by command basis for default flags. This is to make AVOID WINDOWS work. - new DESQ test, avoids both false positives and false negatives of previous tests. - mislabeled labels in routine IsMamaWatching. Did no harm, but was confusing to a programmer. Version 2.3á 1991 May 26 - sent to Fred Robinson - yet another new DESQview test Version 2.4á 1991 May 27 - sent to RBabcock, Agurski - safe vector -- to avoid crashing in BIOS deficient machines. Now check than vector are hooked to real code before using them. - fix RBabcock's problem with DOSes that do not provide country comma and decimal separators. - more sensitive BIOS type test to avoid false positives on XT. now tests that month is in range 1..12. Should fix RBabcock's problems with old non-standard machines. - tweaked code so that if PIQ for V30 comes out 5, treat as V30 rather than as V20. Should solve Agurski's problem. Version 2.5á 1991 May 27 - sent to Rlis - better PCDOS discrimination test. Uses COMSPEC to guess the boot drive. Version 3.0 1991 May 27 - released to BIX - new QRAM test - new parameter SLOW to force slow tests - shorten the SX test under windows - Presence routine removed. - DESQ detect bug improperly using Presence fixed. Version 3.1á 1991 May 28 - sent to Agurski, Mkropp, Tfrost - redo ordering of SX 386 and 486 tests because 486 was giving a false positive on the SX test. Reported by Tfrost. - change word "prefix" to "suffix" in the docs. - fix bug in test for ASSIGN installed. Reported by MKropp. - new way of testing BIOS -- using machine model bytes. Should fix problems with exotic clones with strange CMOS. Problems reported by aGurski and tFrost. - Two new BIOS types: EXOTIC and PS2 Version 3.2á 1991 May 28 - added credits for all methods - cleaner BTRIEVE test. - new MSDOS test does not get confused by DR DOS Version 3.3á 1991 May 30 - added DUMPREGS debugging code - added DUMP macro controlled by DEBUGGING Version 3.4á 1991 May 31 - fixed dump of DX in DEBUGGING code - New PCDOSTest not confused by DRDOS - better code to detect non-functioning INT 15 in EXT ram determine. - DEBUG command to turn on debugging Version 3.5á 1991 June 1 -- sent to all beta testers. - code to avoid a dummy INT 15 in EXT ram test. - fix bug in divby10 routine. Was causing divide overflows when some large numbers were displayed. - added HANDLES command - added MTRAILING so that blank padded command lines treated a null. - PIQ tests now done 4 times and longest result used. This should make results more reproduceable. - use of /D command to generate all three programs automatically. Version 3.6á 1991 June 4 - added parameters so you can request or avoid PS2 and EXOTIC. - simplified HANDLES code to use the null device. Bypass DOS bug that requires trailing colon be missing on the NUL: device - added version checks on many interrupt calls to not even try the call unless the DOS version is high enough. - use of documented ASSIGN test rather than undocumented one. - consistent wording -- NOT DETECTED. - made RAWDOSVer preserve registers. - Fixed the test for PCDOS Version - close device drivers whenever opened. Terje Mathisen noticed the problem. - Clear the keystroke buffer before doing the ANSI test. Again, Terje Mathisen noticed the problem. Unfortunately, this still does not bypass the Win /r bug with ANSI.SYS - Rewrote code for ANSI.SYS in a more modular way. - more consistent use of xxxPresent and xxxAbsent. - new test to discriminate Win /r from Win /s - new test to better discriminate AT and PS2 style BIOSes. Version 3.7á 1991 June 6 - fast / slow 486 test - rename TestDOSVer to RAWDOSVer - fix so so DOS version tests work for DR DOS Version 3.8á 1991 June 12 - HANDLES test now DOS 2.1 compatible. Version 3.9 1991 June 13 - released to PC User Soc Version 4.0 1991 June 14 - released to BIX - changes in documentation. Version 4.1 - tFrost discovered bug in QEMM test. - document KEYB - new way of doing KEYB in DOS 5.0 Version 4.2á - bug in ASSIGN fixed. Was typo in Advanced MS DOS. Version 4.3 - fancier QEMM test, not fooled by file of same name. - SMARTDRV test Version 4.4á - added NCACHE Norton Disk cache present test - added PCCACHE PC Tools cache present test. Version 4.5á 1991/11/29 - added 4DOS test Version 4.6á 1991/12/12 - new way around the CD-ROM Graphics.Com bug that plagues the LAN test. - treat machine code FC-03 as an AT rather than PS-2 Version 4.7á 1991/12/18 - BIOS test tries has code to detect machines with non-functioning INT 15 equipment determination interrupt and bypass the bug. Version 4.8 1992/01/09 - detect Phoenix-style ATs with FC:80 ... - display BIOS type first, before model id. Version 4.9 1992/03/10 - detect PCMOS Version 5.0 1992/04/28 - fix bug in LAN detect. CD-ROMS not properly noticed. Version 5.1 1993/06/12 - new address and phone number Version 6.0 1993/10/11 - PENTIUM tests - ISA EISA MCA BUS tests - source released with shareware. Version 6.1 1994/08/13 - /Time:HH:MM /Hour:hh /Min:mm /Minute:mm - AND noise word Version 6.2 1994 August 21 - cli protection on Pentium test, in case interrupt clobbers high parts of 32bit regs. - BABE SmartDrv 4+ test - check for redirection of output before doing ANSI test. Version 6.3 1994 August 24 - corrected ANSI test. - handle 486 CPUs retrofitted with Pentium CPUID instruction. - added HIMEM.SYS detection Version 6.4 1995 August 13 - check for Windows-95 Version 6.5 1996 October 25 - embed POB 707 Quathiaski Cove address Version 6.6 1998 November 8 - embed Barker address to do in future: - tests for chipsets Neptune, Ares, Saturn, Triton, Mercury - PCI bus - MS$MOUSE - tests for more cachers, PCKWIK HYPERDISK - tests for other versions of PCCACHE - check old versions of QEMM - DEBUG to display PIQ and BIOS bytes - video tests - keyboard tests - Babcock - ANSI test failing in Windows /r -- bug in windows ;============================================================== ; R E G I S T E R C O N V E N T I O N S Subroutines may trash any registers they please except CS: DS: ES: SS: and of course the outputs. Often BP or other registers are specially preserved. | ; end of comment ; .XALL turns on macro expansion .SALL turns it off ; .XLIST turns off listing .LIST turns it on ;============================================================== ; E Q U A T E S ; These 3 masks do double duty. ; They control whether suffixes -!+ ok. ; The also control if too small, bang, big ok. Minus equ 1 ; bit mask for allowing - ( too small ) Bang equ 2 ; bit mask for allowing ! ( right on ) Plus equ 4 ; bit mask for allowing + ( too big ) None equ 0 ; no suffixes allowed Any equ Plus or Bang or Minus ; any suffix -!+ is ok ; Where masks TimeStyle equ 8 ; bit mask, WantLevel is of form HH:MM CapacityStyle equ 16 ; bit mask, WantLevel comes from :9999 VersionStyle equ 32 ; bit mask, WantLevel comes from DOS3.3 version suffix ImpliedStyle equ 64 ; bit mask, WantLevel comes from table input ; it is implied by the command used. e.g. CPU ; type names imply a numeric level of CPU power wanted. Aux equ 128 ; bit mask for special aux input to test routine ; comes from MCT table, e.g. C:300K drive number ; A: .. Z: are commands in their own right. ; we have to tell the test routine which drive ; they are supposed to be testing. ;; No spare bits left ;============================================================== ; M A C R O S CPUID macro ; in case no support for CPUID instruction db 0fh,0a2h endm ;====== CR macro ; Carriage return line feed db 0dh,0ah endm ;====== EOS macro ; marks end of display string db 0dh,0ah,'$' endm ;====== Say macro String ;; MACRO To display message on screen ;; on entry String is address of $ delimited message. ;; Preserves all registers push dx lea dx,String call Say$ pop dx endm ;====== saystr macro StringLit ;; MACRO To display message on screen ;; on entry Stringlit is a string in form "abcd", without term $ ;; Preserves all registers String segment Place = $ db StringLit,'$' ;; build $ terminated string literal String ends push dx mov dx,offset COM:Place ;; masm gets confused with lea call Say$ pop dx endm ;====== if DEBUGGING ;; debugging version DUMP macro StringLit local Bypass ;; MACRO To display bebugging message on screen with regisiter dump ;; on entry Stringlit is a string in form "abcd", without term $ ;; Preserves all registers test Debug,-1 jz Bypass saystr StringLit call DumpRegs Bypass: endm else ;; production version DUMP macro StringLit ;; dummy does nothing endm endif ;====== SayWhen macro Value,StringLit local NotThisOne ;; must be VERY first line ;; Compares ax to value and displays string literal if match, ;; and returns, otherwise does nothing. ;; Sort of a poor man's CASE ;; Clobbers DX String segment Place = $ db StringLit,'$' ;; build $ terminated string literal String ends cmp ax,Value jne NotThisOne lea dx,Place call Say$ ret NotThisOne: endm ;====== ACCEPT macro ClassName,ExternalName,Where,AuxInput,Allowables,NeedDefaults,AvoidDefaults,Junk ;; Generates one line in the Master Control Table (MCT) ;; A little too fat to ininitialize with a STRUC. ;; See notes below on the MCT structure we are building. ;; Check that all parms needed are present .errb ;; missing parm .errb if ((Where) and (ImpliedStyle or Aux)) NE 0 .erre Auxinput ;; should not be 0 else .errnz Auxinput ;; must be 0 endif .errb .errb .errb .errnb ;; too much stufff String segment public ;; Build the external name of the command in separate region of memory ;; to avoid messing up the fixed length table items. StringStart = $ org $+1 ;; leave room for count db &ExternalName& ;; build variable length string StringEnd = $ StringLen = (StringEnd - StringStart) - 1 org StringStart ;; overlay count byte db StringLen org StringEnd ;; move to end of string String ends Table segment public dw OffSet COM:StringStart ;; save pointer to counted string ;; e.g. 03 RAM dw &AuxInput& ;; auxilliary input to test routine ;; e.g. 0=A: 1=B: drive number ;; Also used for implied level desired. ;; Build the multipurpose flag word ;; -- suffixes allowed ;; -- where the desired test value comes from implied, capacity etc. db Bang OR (&Allowables&) OR (&Where&) ;; always allow Bang ;; Flags for suffixes allowed ;; Also Where flags to control ;; where data to compute WantLevel ;; comes from. ;; Build default suffixes ;; Default suffixes to assume if ;; user supplies no prefixes. ;; Bang always one ;; of them. if GENERATING eq SNIFF db Bang OR Plus OR Minus ;; everything is ok. ;; we won't even test this anyway. endif if GENERATING eq AVOID db Bang OR AvoidDefaults ;; e.g. AVOID DESQVIEW ;; add Bang meaning want to ;; avoid hitting bang on endif if GENERATING eq NEED db Bang OR NeedDefaults ;; e.g. NEED C:400K+! endif dw offset COM:Test&ClassName& ;; pointer to Test routine ;; determines if feature present ;; and in what degree dw offset COM:Display&ClassName& ;; pointer to display routine ;; displays current state in words Table ends endm ;============================================================= stack segment stack ; keep MS link happy by providing null stack stack ends ;============================================================== CODE segment PARA ; start off in code. ;============================================================== data segment word ; provide a separate DATA segment ; Even though it appears in the source ; before the code, it the COM file it ; will appear at the end. This is as dodge ; to avoid forward references that confuse ; MASM. FirstData label Word data ends ;============================================================== Table segment public ; MCT Table comes next Table ends ;============================================================== String segment public ; Strings come next String ends ;============================================================== MCT struc ;; Master Control Table, 10 bytes per entry ;; structure for table that describes all the parms ;; there is one entry for each possible parameter ;; on the command line. ;; each alias gets its own entry. ;; each DRIVE: letter even gets its own entry. ;; BP and TheBase will point to the current ;; entry in the table. ;; Generated by the ACCEPT macro ExtNameAddr dw ? ;; pointer to counted string ;; command name e.g. RAM EXT EXTENDED AuxInput dw ? ;; e.g. implied power level of the cpu desired ;; only 16 bits. ;; also encodes driver letter A:=1 B:=2 etc. ;; usually just 1 meaning simple presence desired Flags db ? ;; flags telling which suffixes allowed ;; also encodes info about where LevelWanted ;; comes from, i.e. is this an Implied, Capacity or ;; version type parameter. Defaults db ? ;; flags telling which suffixes to assume ;; if none given. Bang allows one of them. ;; generally will be ! or +! TestAddr dw ? ;; Address of routine to test for presence ;; It returns the level of presence in DX:AX ;; e.g. 1=present 0=absent. ;; or amount of free RAM etc. ;; for most parms (non colon type), DX can ;; be ignored. ;; Capacity-type routines must compute DX:AX. ;; Routine can trash any register except ;; SS:SP CS:IP DispAddr dw ? ;; Address of routine to display current state ;; It expects level of presence in DX:AX ;; e.g. 1=present 0=absent. ;; or amount of free RAM etc. ;; for most parms (non colon type), DX can ;; be ignored. ;; Capacity-type routines must computer DX:AX. ;; Routine can trash any register except ;; SS:SP CS:IP MCT ends ;============================================================== ; V A R I A B L E S Data segment WantLevel dd 0 ; level we want for a feature ; for simple cases 0=not present 1=present ; for CPU - is a measure of power. ; for DOS - is a measure of version ; for RAM - is capacity in bytes. ; for TIME - is hours:minutes, mins past midnight GotLevel dd 0 ; Level we HAVE for a feature, according to ; the test ; It is usually carried about in ; in DX:AX. TheCapacity dd 0 ; desired capacity parsed from /RAM:400K TheVersion dw 0 ; desired version parsed from /DOS3.1 = 310 PreviousSuccess db 0 ; -1 means at least one previous group of tests ; separated by OR succeeded ; 0 no previous OR-type group of tests has yet ; succeeded. Success db 0 ; -1 means doing fine in this group of tests ; 0 means a test failed OkMask db 0 ; bit mask that says if level allowed to ; be too small, just right or too big. ; calculated from any explicit suffixes ; or from a computed default. ; Bit on, means allow that "Goldilocks" condition TheBase dw 0 ; Points to entry in current Master Control Table ; From there we can get everything ComAddr dw 0 ; when a single parm is broken in 3 parts ; address of start of first part e.g. ; RAM in RAM:400K+ ; com = command ComLen dw 0 ; length of first part e.g. 3 length of RAM CapAddr dw 0 ; address of second part of parm: e.g. 400K ; cap = capacity CapLen dw 0 ; length of second part of parm e.g. 4 SufAddr dw 0 ; third part of parm e.g. + ; suf = suffix SufLen dw 0 ; length of third part e.g. 1 AlpAddr dw 0 ; in /MSDOS3.1+ address of string MSDOS ; alp = alphabetic AlpLen dw 0 ; in /MSDOS3.1+ len of string MSDOS i.e. 5 VerAddr dw 0 ; in /MSDOS3.1+ address of string 3.1 ; ver = version VerLen dw 0 ; in /MSDOS3.1+ len of string 3.1 i.e. 3 ; of course the puns are intentional. CommaSep db ' ' ; character for thousands separations. ; usually a comma, sometimes decimal point DeciSep db ' ' ; decimal point. Country specific. ; usually a decimal point, sometimes comma. DeciSep2 db ' ' ; alternate decimal point. ; sometimes a comma, when comma treated as decmial. data ends ;============================================================== ; M E S S A G E S String segment if GENERATING eq SNIFF CopyrightMsg label byte db '°±²Û SNIFF 6.6 Û²±°' cr cr db 'Freeware to determine which hardware and software is present.' cr db 'Copyright: (c) 1991-2017 Roedy Green, Canadian Mind Products' cr db '#101 - 2536 Wark Street, Victoria, BC Canada V8T 4G8',13,10 db 'tel:(250) 361-9093 mailto:roedyg@mindprod.com http://mindprod.com',13,10 db 'May be freely distributed and used for any purpose except military.' cr db 'email: roedyg@mindprod.com' eos SyntaxErrMsg label byte cr db '°±²Û Error in Command line Û²±°' db 07d eos SyntaxMsg label byte db 'Try something like this:' cr db 'SNIFF DESQview DOS CPU C: TIME' cr cr db 'DESQview means is DESQview is running?' cr db 'DOS means what version of DOS is running?' cr db 'CPU means what kind of CPU do you have?' cr db 'C: means how much space is left on drive C:?' cr db 'TIME what time is it?' cr db 'There are many other tests supported. See NEED.TXT or NEED.ASM.' eos endif if GENERATING eq AVOID CopyrightMsg label byte db '°±²Û AVOID 6.6 Û²±°' cr cr db 'Freeware to ensure undesirable hardware and software is absent.' cr db 'Copyright: (c) 1991-2017 Roedy Green, Canadian Mind Products' cr db '#101 - 2536 Wark Street, Victoria, BC Canada V8T 4G8',13,10 db 'tel:(250) 361-9093 mailto:roedyg@mindprod.com http://mindprod.com',13,10 db 'May be freely distributed and used for any purpose except military.' cr db 'email: roedyg@mindprod.com' eos SyntaxErrMsg label byte cr db '°±²Û Error in Command line Û²±°' db 07d eos SyntaxMsg label byte cr db 'Try something like this:' cr db 'AVOID DESQview DOS3.3- 80386! C:3MB TIME:23:59+ TIME:00:01-' cr db 'If errorlevel 1 GoTo Trouble' cr cr db 'DESQview means you must NOT be running under DESQview.' cr db 'DOS3.3- means you need something above DOS 3.3.' cr db '80386! means you cannot run on a 386 processor.' cr db 'C:3MB avoid running if there is only 3 MB or less on drive C:' cr db 'TIME:23:59+ means avoid running the program in last minute of the day.' cr db 'TIME:00:10- means avoid running the program in first ten minutes of the day.' cr db 'There are many other tests supported. See NEED.TXT or NEED.ASM.' cr db 'If conditions met, ERRORLEVEL=0, if not ERRORLEVEL=1' eos endif if GENERATING eq NEED CopyrightMsg label byte db '°±²Û NEED 6.6 Û²±°' cr cr db 'Freeware to ensure computer has desired hardware and software present.' cr db 'Copyright: (c) 1991-2017 Roedy Green, Canadian Mind Products' cr db '#101 - 2536 Wark Street, Victoria, BC Canada V8T 4G8',13,10 db 'tel:(250) 361-9093 mailto:roedyg@mindprod.com http://mindprod.com',13,10 db 'May be freely distributed and used for any purpose except military.' cr db 'email: roedyg@mindprod.com' eos SyntaxErrMsg label byte cr db '°±²Û Error in Command line Û²±°' db 07d eos SyntaxMsg label byte cr db ' Try something like this:' cr db 'NEED DESQview DOS3.3- 80386! C:3MB RAM:300K+ TIME:23:59' cr db 'If errorlevel 1 GoTo Trouble' cr cr db 'DESQview means you must be running under DESQview.' cr db 'DOS3.3- means you need DOS 3.3 or lower.' cr db '80386! means you must have an 386 processor, no more no less.' cr db 'C:3MB means you must have at least 3 MB of free disk space on drive C:.' cr db 'RAM:300K+ means you must have at least 300KB or more conventional RAM free.' cr db 'TIME:23:59 means the program can only run during the last minute of the day.' cr db 'There are many other tests supported. See NEED.TXT or NEED.ASM.' cr db 'If conditions met, ERRORLEVEL=0, if not ERRORLEVEL=1' eos endif String ends ;============================================================== com group code,data,Table,String ; force data segments to go at the end! assume CS:com,DS:com,ES:nothing,SS:com ; seg regs cover everything org 100H ; in Code segment ;============================================================== START proc far ; Mainline procedure ;============================= ; P R O C E D U R E S Main proc near call SayCr ; just in case DOS left us on a partial line ; start a fresh one. call CountrySeps ; get country dependent chars to use as ; comma and decimal point mov Success,-1 ; Mark that we are succesful so far in this group mov PreviousSuccess,0 ; mark that no previous group of OR tests ; succeeded. call Parse ; calls tests as it parses each parm mov al,Success ; We succeed if current OR group or ; previous OR group succeeded. or al,PreviousSuccess jnz AllOk OhOh: call SayCr if GENERATING eq SNIFF saystr 'Sniff failed' endif if GENERATING eq AVOID saystr 'Avoid failed' endif if GENERATING eq NEED saystr 'Need failed' endif call SayCr ; Failed at least one test mov ax, 4c01h ; ERRORLEVEL = 1 int 21h ; Die AllOk: ; no need for a SNIFF successful message if GENERATING eq SNIFF call SayCr endif if GENERATING eq AVOID call SayCr saystr 'Avoid successful' call SayCr endif if GENERATING eq NEED call SayCr saystr 'Need successful' call SayCr endif mov ax, 4c00h ; ERRORLEVEL = 0 int 21h ; normal exit to dos, all tests passed abort: ; If user blows it, we still treat him well! ; Must as if he had said NEED HELP say CopyRightMsg ; CopyRight banner say SyntaxErrMsg say SyntaxMsg mov ax, 4c04h ; ERRORLEVEL = 4 int 21h ; Die, with serious errorlevel Main endp ;============================================================== Parse proc near ; Parse the command line for the parameters Data segment ParmIndex dw 0 ; track which parm we are working on Data ends call CommandLine ; string addr ES:di, length cx. mov bx,di call MTrailing ; remove trailing blanks jcxz Abort ; null command line is a syntax error call Filter ; clean out lower case, tabs, slashes mov ParmIndex,1 ; Start parsing with the 1st parm ; Each parm is block separated by spaces. ; Note start with 1 not 0! Parseloop: call CommandLine ; get first parm ; string=ES:di, length=cx mov bx,ParmIndex call NthParm ; work left to right jcxz NoMoreParms ; null param means no more ; e.g. DS:si points to RAM:400K+ ; cx is length of that piece call Handle1Parm ; si points to string, cx is length Next: inc ParmIndex ; bump loop counter jmp Parseloop ; loop till hit null param NoMoreParms: ret ; we are done Parse endp ;============================================================== CommandLine proc near ; Gets command line string into ES:di, len CX ; Command line does not include the program name. ; ES already set since we are a COM file ; Sets cx to length of command line, not counting 0dh at end ; Trashes no other registers ; counted string at HEX 80 contains command line. ; It has no trailing null. ; It has a trailing 0Dh not part of count ; Why does this take so much longer to describe than code? mov di,81h sub CH,CH mov CL,DS:80h ; cx contains length of command ret CommandLine endp ;============================================================== MTrailing proc near ; on entry BX is addr of string, CX its length. ; Trims off any trailing blanks, leaving result in BX CX ; Length may also be 0 or 1, but not -ve ; If the entire string is blank the result is the null string mov di,bx add di,cx ; calc addr last char in string dec di mov al,20H ; AL = blank -- the search char jcxz mtrailing1 ; jump if null string std repe scasb ; scan ES:DI backwards till hit non blank ; DI points just ahead of it (wrap ok) ; CX is one too small, or 0 if none found cld je mtrailing1 ; jump if whole string was blank inc cx mtrailing1: ret MTrailing endp ;============================================================== Filter proc near ; Convert entire command line to upper case ; Filter out tab chars and slashes call CommandLine ; string addr ES:di, length cx. mov si,di FilterLoop: lodsb cmp al,09h ; tab -> space jne NotTab mov al,20h jmp ConvertChar NotTab: cmp al,'/' ; / -> space jne notSlash mov al,20h jmp short ConvertChar NotSlash: cmp al,'a' ; a -> A b -> B etc jb FineAsIs cmp al,'z' ja FineAsIs sub al,20H ConvertChar: mov [si-1],al FineAsIs: loop FilterLoop ret Filter endp ;============================================================== NthParm proc near ; Parses string for Nth Parameter delimited by blanks ; e.g. " RAM:250K DESQ " with bx=1 would give string "RAM:250K" ; By this point slashes have been stripped. ; on entry: ; ES:di - string ; cx - length of string ; bx - which parm wanted 1=parm1 2=parm2 etc. ; NthParm only finds one parm per call. ; On exit ES:si points to string and cx is its length. ; If there is no parm, the length will be 0. ; It also handles multiple leading/trailing blanks on parms. mov al,20h ; al = blank -- the search char Parmloop: ; Remove leading blanks on parm jcxz NullParm ; jump if null string repe scasb ; scan ES:di forwards till hit non blank ; di points just after it ; cx is one too small, or 0 if none found je NullParm ; jump if entire string was blank inc cx ; cx is length of remainder of string dec di ; di points to non-blank mov si,di ; remember start of string ; Search for terminating blank on parm jcxz NullParm ; jump if null string repne scasb ; scan ES:di forwards till hit blank ; di points just after it ; cx is one too small, or 0 if none found jne NoBlank ; jump if entire string was non blank inc cx ; cx is length of remainder of string dec di ; backup di to point to blank at string end NoBlank: ; di=addr tail end of command string, ; cx=len tail end of command string ; si=addr parm just parsed ; Major loop for each parm dec bx jnz Parmloop ; loop once for each parm mov cx,di sub cx,si ; cx is length of parameter. ret NullParm: ; was no nth parameter sub cx,cx ret NthParm endp ;============================================================== Say$ proc near ; Used by Say and saystr ; on entry dx contains addr of $ delimited string ; displays on screen using BIOS push ax mov AH,09h int 21h pop ax ret Say$ endp ;============================================================== SayCr proc near ; Emit a CrLf, to start a new line String segment ACrLf db 13,10,'$' String ends say ACrLf ret SayCr endp ;============================================================== SayHexByte proc NEAR ; call with hex number 00..FF in AL ; converts it to ASCII and displays it on the screen ; If it is 0 shows as 00. Leading 0's are not suppressed. ; Field is always 2 chars wide. ; No leading and no trailing space. DISPLAYED IN HEX ; Can call repeatedly to get a SayHexWord etc. ; Preserves all registers Data segment even hexPAD db '00$' ; where numeric output built by SayHex Data ends push ax ; preserve regs push bx push cx push dx mov dl,al ; save input ; Do first (leftmost digit) mov cl,4 shr al,cl and al,0fh ; get first digit cmp al,9 jg HexChar1 add al,'0' ; convert digit to ASCII jmp StoreChar1 HexChar1: add al,'A'-0AH ; convert to uppercase A..H StoreChar1: mov hexPad,al ; Do second (rightmost digit) mov al,dl and al,0fh ; get last digit cmp al,9 jg HexChar2 add al,'0' ; convert digit to ASCII jmp StoreChar2 HexChar2: add al,'A'-0AH ; convert to uppercase A..H StoreChar2: mov hexPad+1,al ; Number is ready lea dx,Hexpad mov AH,09h ; BIOS put string terminated by $ int 21h pop dx pop cx pop bx pop ax ret SayHexByte endp ;================================= SayLZDec proc NEAR ; on entry DX:AX contains integer, cx is count of digits desired ; uses leading zeros. Displays string on screen. ; e.g. 1 shows as 01 if CX=2 jcxz SayLZDecDone ; nothing to do push ax push cx push dx push di lea di,PadEnd DecLoop: ; loop once for each digit to right of decimal ; number so far is in dx:ax call DivBy10 ; result in is dx:ax and remainder in bx add bl,'0' ; convert digit to ASCII dec di ; work right to left mov [di],bl ; save digit loop DecLoop mov dx,di ; start of ascii string, terminated by $ mov AH,09h ; BIOS put string terminated by $ int 21h pop di pop dx pop cx pop ax SayLZDecDone: ret SayLZDec endp ;================================= SayBigDec proc NEAR ; call with unsigned number in DX:AX ; converts it to ASCII and displays it on the screen ; If it is 0 shows as 0 ; If it is .02 shows as 0.02 ; If it is 1000000 shows as 1,000,000 ; leading 0's are suppressed. ; Field is exactly wide enough to hold the number. ; No leading or trailing spaces. DISPLAYED IN DECIMAL ; Uses global variables DeciSep (decimal point) ; CommaSep (comma) ; DPL (how many decimal places -1=none) ; ; Method is to repeatedly divide by 10. The remainder at ; each stage is one digit of the result, working right to left. ; Preserves all registers Data segment PAD db 15 dup (0) ; where build the output string PADend db '$' ; terminator for Say Data ends push ax push bx push cx push dx push di ; preserve di mov bx,10d lea di,padEnd ; point at $ trailing the PAD ; work right to left building digits at PAD mov cx,DPL ; places to right of decimal jcxz PlopDecimal ; 0 means just decimal point, no digits to right test cx,cx ; -1 means no decimal point. jl DecimalsDone DecimalsLoop: ; loop once for each digit to right of decimal ; number so far is in dx:ax call DivBy10 ; result in is dx:ax and remainder in bx add bl,'0' ; convert digit to ASCII dec di ; work right to left mov [di],bl ; save digit loop DecimalsLoop PlopDecimal: mov bl,Decisep ; plop in a decimal point dec di mov [di],bl DecimalsDone: LeftDigitsLoop: ; loop till all digits to left of decimal done mov cx,3 ; do groups of three digits, then a comma CommaGroupLoop: ; number so far is in dx:ax call DivBy10 ; result in is dx:ax and remainder in bx add bl,'0' ; convert digit to ASCII dec di ; work right to left mov [di],bl ; save digit mov bx,ax or bx,dx jz LeftDigitsDone ; if rest of number is 0, we are done loop CommaGroupLoop PlopComma: mov bl,Commasep ; plop in a comma dec di mov [di],bl jmp LeftDigitsLoop LeftDigitsDone: ; Number is ready mov dx,di ; start of ascii string, terminated by $ mov AH,09h ; BIOS put string terminated by $ int 21h pop di ; restore caller's registers pop dx pop cx pop bx pop ax ret SaybigDec endp ;============================================ SayHexWord proc near ; Displays hex word in ax, followed by space ; Trashes no registers xchg ah,al call SayHexByte ; high byte first xchg ah,al call SayHexByte ; low byte last saystr ' ' ret SayHexWord endp ;============================================ DumpRegs proc near ; dumps contents of all registers. Cr before and after. ; Preserves all registers including the flags. push ax pushf call SayCr ; new line saystr ' ' ; indent a bit saystr 'AX=' call SayHexWord ; ax saystr 'BX=' mov ax,bx call SayHexWord ; bx saystr 'CX=' mov ax,cx call SayHexWord ; cx saystr 'DX=' mov ax,dx call SayHexWord ; dx saystr 'SI=' mov ax,si call SayHexWord ; si saystr 'DI=' mov ax,di call SayHexWord ; di saystr 'SP=' mov ax,sp add ax,10 ; allow for push ax and pushf, ; allow for two call levels, plush pushf on caller. call SayHexWord ; sp saystr 'FL=' pop ax push ax call SayHexWord ; flags call SayCr ; new line saystr ' ' ; indent a bit saystr 'DS=' mov ax,DS call SayHexWord ; DS saystr 'ES=' mov ax,ES call SayHexWord ; ES saystr 'SS=' mov ax,SS call SayHexWord ; SS call SayCr ; new line popf ; restore flags pop ax ; restore ax ret DumpRegs endp ;============================================ SayIfActive proc near ; input 0 or 1 is ax. ; Used to generate messages like 'DESQiew is active' ; or 'DESQview is inactive.' Caller must do the first word himself. test ax,ax jnz IsActive IsInactive: saystr ' is inactive.' ret IsActive: saystr ' is active.' ret SayIfActive endp ;============================================ SayIfDetected proc near ; input is ax, 0 or 1 ; used to display phrases like 'BTRIEVE was detected.' test ax,ax jnz WasDetected saystr ' not detected.' ret WasDetected: saystr ' was detected.' ret SayIfDetected endp ;============================================ SayBytesCapacity proc near ; input is dx:ax capacity. ; used to display phrases like '400,000 bytes free EMS RAM' ; Call must display the terminating phrase. test ax,ax jnz AreFreeBytes test dx,dx jnz AreFreeBytes saystr 'no bytes free ' ret AreFreeBytes: mov dpl,-1 call SayBigDec saystr ' bytes free ' ret SaybytesCapacity endp ;=========================================== SayVersion proc near ; input version in ax. dx=0 ; used to display phrases like 'MSDOS version 4.01 detected.' or ; 'MSDOS not present.' ; Caller must display the first word. test ax,ax jnz SomeVersion saystr ' not detected.' ret SomeVersion: saystr ' version ' mov dpl,2 call SaybigDec saystr ' detected.' ret SayVersion endp ;============================================ ASCIIToBin proc ; Convents ASCII numeric input to binary. ; on entry DS:SI points to start of decimal ASCII string of digits. ; String should contain only the decimal digits 0..9. ; Also allowed are MB KB -- all upper case multipliers ; This routine handles M and K multiplications itself. ; ignores Commas. ; tracks places past decimal in DPL (Forth-like) ; CX contains length of string > 0 ; On exit DX:AX contains binary equivalent of string. ; DPL counts places to right of decimal. ; If there is a decimal, result is integer as if no decimal present. ; ignores embedded colon. ; Works left to right. If invalid chars given, jumps to Abort. Data segment even DPL dw 0 ; DPL is used used to keep track of how many decimal places were ; found when converting a number from ASCII to internal form. Ä1 ; means no decimal places 0 means just a decimal point after the ; number, 2 means two places after the decimal. Data ends mov DPL,-1 ; Mark no decimal point seen yet push bx ; save regs we will trash push cx push si push di sub ax,ax mov dx,ax ; accumulate in DX:AX OneDigitLoop: ; DX:AX contains sum accumulated so far push ax ; save low order part of accumulating sum lodsb ; get next digit in AL call Ignorable test ah,ah jnz IgnoreDigit cmp al,'K' jne NotK ; K means multiply by 1024 = 2^10 pop ax call MultBy1024 jmp NextDigit NotK: cmp al,'M' jne NotM pop ax call MultBy1024 ; M means multiply by 1 megabyte = 2 ^ 20 call MultBy1024 ; i.e. 1024x1204 jmp Short NextDigit NotM: sub al,'0' ; convert 1 digit ASCII to bin jl NumTrouble ; ensure 0..9 cmp al,9 jg NumTrouble sub di,di mov bx,di mov bl,al pop ax call MultBy10 add ax,bx ; add digit onto accumulated product adc dx,di cmp DPL,-1 ; count digits to right of decimal point je NextDigit inc DPL ; this is a digit to rigt of decimal jmp NextDigit IgnoreDigit: pop ax NextDigit: loop OneDigitLoop ; loop once for each digit ; restore regs pop DI pop SI pop CX pop BX ; save regs we will trash ret NumTrouble: ; Some problem, give standard Syntax Error message. jmp Abort ASCIIToBin endp ;============================================ IgnorAble proc near ; this is a proc just to keep loop short enough ; for LOOP instruction to span in ASCIItoBIN ; tests if char in AL can be ignored. AH=1 if yes cmp al,'B' ; ignore 'B' as in 'MB' je IsIgnorable cmp al,':' ; ignore embedded : je IsIgnorable cmp al,Commasep ; ignore ',' je IsIgnorable cmp al,DeciSep ; it is a '.'? je IsDeciSep cmp al,DeciSep2 ; it is a '.'? jne NotDeciSep IsDeciSep: mov DPL,0 ; mark that decimal point seen IsIgnorable: mov ah,1 ret NotDeciSep: mov ah,0 ; ordinary char ret ignorable endp ;============================================ NibblesToBin proc near ; input is AX hex number encoded as 4 nibbles. ; output is AX number as binary integer ; e.g. 0510h -> 510d ; Trashes all non-segment registers mov di,ax ; save input mov bx,10d ; will be multiplying by ten sub dx,dx ; result, accumulates here. mov cx,4 ; process 4 hex nibbles, high to low ; in a loop, 4 bits at a time NibblesLoop: mov ax,dx ; multiply accumulation so far by 10 mul bx mov dx,ax ; save only low order word -> dx ; it cannot overflow. sub ax,ax ; clear high order part ready for shift in rcl di,1 ; slide next high order 4 bits of di into ax rcl ax,1 rcl di,1 rcl ax,1 rcl di,1 rcl ax,1 rcl di,1 rcl ax,1 add dx,ax ; add on sum so far loop Nibblesloop mov ax,dx ; leave result in ax ret NibblesToBin endp ;============================================ Multby10 proc near ; multiply the number in DX:AX by 10 ; Works because 10*X = 2*X + 8*X ; trashes no registers push di push bx shl ax,1 rcl dx,1 ; DX:AX = x*2 mov bx,ax mov di,dx shl bx,1 rcl di,1 shl bx,1 rcl di,1 ; DI:BX = x*8 add ax,bx adc dx,di ; DX:AX = x*2 + x*8 pop bx pop di ret MultBy10 endp ;============================================ DivBy10 proc near ; on entry DX:AX contains an unsigned binary number. ; on exit DX:AX contains that number divided by 10 ; Remainder left in BX, 0 .. 9 ; There are no possibilities of overflow or divide by 0 ; Trashes no other registers mov bx,10 cmp dx,bx jae ToughDiv EasyDiv: ; number is small so answer will fit in AX div bx mov bx,dx ; save remainder in bx sub dx,dx ; replace remainder with 0 high order ret ToughDiv: push cx mov cx,ax ; save low order part away mov ax,dx ; divide just the high order part by 10 sub dx,dx div bx ; dx:ax / 10 -> ax remdr -> dx xchg cx,ax ; save high order quotient ax -> cx ; create dividend of remdr of last ; div as high order and low order from ; original ax ; Next div cannot overflow because dx is ; < 10 because it is a remainder. div bx ; quot -> ax remdr -> dx mov bx,dx ; save remainder mov dx,cx ; restore high order part of quot, calc earlier pop cx ret DivBy10 endp ;============================================ Multby1024 proc near ; multiply the number in DX:AX by 1024 ; Works because 1024=2^10=2^8*2*2 ; trashes no registers ; shove everything left 8 bits mov dh,dl mov dl,ah mov ah,al sub al,al shl ax,1 ; shift left another two bits rcl dx,1 shl ax,1 rcl dx,1 ret MultBy1024 endp ;============================================ Max proc near ; Compares two signed 32 bit numbers in DX:AX and CX:BX ; and leaves the larger in DX:AX mov DI,AX ; calc n2-n1 sub DI,BX mov DI,DX sbb DI,CX jge MAX1 mov AX,BX ; n2=CX:BX was langer mov DX,AX MAX1: ; n1=DX:AX was larger ret Max endp ;============================================ if GENERATING eq SNIFF Handle1Parm proc near ; SNIFF version ignores Version, Capacity and suffixes ; Parses one command on the line, and does all work associated with it. ; On Entry si points to counted string to look for, CX is its length ; not counting the count byte that Findcommand temporarily inserts ; leading / has already be pruned off by Filter call Trisect ; break into 3 pieces C:400K+ ; C 400K + ; Command Capacity Suffix ; ComAddr:ComLen points to C ; CapAddr:CapLen points to 400K ; Sufaddr:SufLen points to + ; for /MSDOS3.1+ ; MSDOS 3.1 + ; ComAddr:ComLen points to MSDOS3.1 ; CapAddr:CapLen is null ; SufAddr:SufLen points to + ; AlpAddr:AlpLen points to MSDOS ; VerAddr:VerLen points to 3.1 ; First try for a matching capacity word ; even if user has not supplied a capacity e.g. /RAM mov al,CapacityStyle or TimeStyle ; SNIFF treats the same mov si,ComAddr mov cx,ComLen call FindCommand ; find command in list ; return with pointer to MCT in BP jnc FoundCommand ; Next try for a matching version word ; even if version left off e.g. /DOS mov al,VersionStyle mov si,AlpAddr mov cx,AlpLen call FindCommand ; find command in list ; return with pointer to MCT in BP jnc FoundCommand ; Next try for a matching implied word e.g. /A20 mov al,ImpliedStyle mov si,ComAddr ; Search for implied first to catch false mov cx,ComLen ; version types e.g. /A20 which are really implied call FindCommand ; find command in list ; return with pointer to MCT in BP jnc FoundCommand NoSuchCommand: jmp Abort ; could not find a matching command FoundCommand: mov TheBase,BP ; found match, either at comaddr or alpaddr ; For SNIFF ; Don't bother to Parse the suffix. We will not use it. ; Don't bother to calculate WantLevel, we will not need it. call DoTheTest ; Test for the feature with one of the Testxxxx ; routines saystr ' ' ; same length as fail msg call DisplayTheTest ; display the result with one of the Displayxxx call SayCr ; routines ret Handle1Parm endp endif ;============================================ if (GENERATING eq NEED) or (GENERATING eq AVOID) Handle1Parm proc near ; Parses one command on the line, and does all work associated with it. ; On Entry si points to counted string to look for, CX is its length ; not counting the count byte that Findcommand temporarily inserts ; leading / has already be pruned off by Filter call Trisect ; break into 3 pieces C:400K+ ; C 400K + ; Command Capacity Suffix ; ComAddr:ComLen points to C ; CapAddr:CapLen points to 400K ; Sufaddr:SufLen points to + ; for /MSDOS3.1+ ; MSDOS 3.1 + ; ComAddr:ComLen points to MSDOS3.1 ; CapAddr:CapLen is null ; SufAddr:SufLen points to + ; AlpAddr:AlpLen points to MSDOS ; VerAddr:VerLen points to 3.1 call ParseCapacity ; get desired Capacity e.g. /RAM:424K ; parse capacity first so we can have both ; /DPMI and /DMPI:400K commands ; also handles /TIME:23:30 call ParseVersion ; get possible version e.g. 310 in /DOS3.1 ; parse early on to allow possibility of both ; a /MSDOS and /MSDOS3.0 command mov cx,CapLen jcxz CantBeCapacity ShouldBeCapacity: ; should be a Capacity ; search only for capacity type keywords ; e.g. /RAM:400K mov al,CapacityStyle or TimeStyle mov si,ComAddr mov cx,ComLen call FindCommand ; find command in list ; return with pointer to MCT in BP jnc FoundCommand jmp Abort CantBeCapacity: ; might be an implied or a version type. ; First search only for Implied type keywords ; e.g. /DPMI /A20 mov al,ImpliedStyle mov si,ComAddr ; Search for implied first to catch false mov cx,ComLen ; version types e.g. /A20 which are really implied call FindCommand ; find command in list ; return with pointer to MCT in BP jnc FoundCommand ; It might have been a version style ; e.g. /MSDOS3.3 ; Don't even look unless there is some ; appended version stuff cmp verlen,0 je NoSuchCommand mov al,VersionStyle mov si,AlpAddr mov cx,AlpLen call FindCommand ; find command in list ; must match whether we have capacity or not ; return with pointer to MCT in BP jnc FoundCommand NoSuchCommand: jmp Abort ; could not find a matching command FoundCommand: mov TheBase,BP ; found match, either at comaddr or alpaddr call ParseSuffix ; Parse suffixes and store results in OkMask ; Can only be done after MCT entry found call CalcWantLevel ; Wantlevel has several possible sources ; usually just 1=present. call DoTheTest ; Test for the feature with one of the Testxxxx ; routines call Goldilocks ; see it level is below or above desired level ; returns AX=1 too low, 2=just right, 4 too high if GENERATING eq AVOID not OkMask ; invert bits in OkMask for AVOID ; so what is acceptable will be the reverse ; of what it would be had we used NEED endif ; For NEED the OkMask is just the way it is. test al,OkMask ; decide what we got is ok. jz FailedTest PassedTest: saystr ' ' ; same length as fail msg call DisplayTheTest ; display the result with one of the Displayxxx call SayCr ; routines ret FailedTest: mov Success,0 ; note that we failed a test saystr 'oops: ' call DisplayTheTest call SayCr ret ; keep going to test other parameters Handle1Parm endp endif ;============================================ DoTheTest proc near ; Do the appropriate test by calling one of the TESTxxxx routines ; i.e. detect the level of presence of that feature. ; on input BP points to the MCT table entry ; on output DX:AX has the level of presence. mov ax,[AuxInput+BP] ; hand off the auxilliary input to the test ; routine. Usually test routine ignores it. ; Only used by Aux-style tests push ds push es push bp ; no other registers need saving call [TestAddr+BP] ; Call routine to do the real work ; of testing presence. ; measured level we have returns in DX:AX ; or AX e.g. amount of RAM pop bp pop es pop ds test [Flags+BP],CapacityStyle jnz DXok sub dx,dx ; fake in test result DX to 32 bits ; non-capacity routines only bother with AX. DXok: mov word ptr GotLevel,ax ; save dx:ax test result mov word ptr GotLevel+2,dx ret DoTheTest endp ;============================================ displayTheTest proc near ; displays the most recent test result ; Calls the approriate displayxxxx routine ; e.g. displays things like 'DESQview detected' ; on entry BP points to the MCT entry ; on entry Gotlevel contains the recent test result push ds push es push bp ; no other registers need saving mov ax,word ptr GotLevel ; put test result in dx:ax ready for display mov dx,word ptr GotLevel+2 call [dispAddr+BP] ; Call routine to display current state ; of of the test since we failed pop bp pop es pop ds ret displayTheTest endp ;============================================ Trisect proc near ; break parm into 3 pieces /RAM:400K+! /TIME:23:30- ; RAM 400K +! ; TIME 23:30 - ; on entry si points to parm, cx is its length, guaranteed >0 ; / has already been pruned off. ; on exit: ; ComAddr:ComLen points to RAM (never allowed to be null) ; CapAddr:CapLen points to 400K (possibly null) ; SufAddr:SufLen points to +! (possibly null) ; AlpAddr:AlpLen points to RAM ; VerAddr:VerLen is null ; ; for /MSDOS3.1+ ; MSDOS 3.1 + ; ComAddr:ComLen points to MSDOS3.1 (never allowed to be null) ; CapAddr:CapLen is null ; SufAddr:SufLen points to + ; AlpAddr:AlpLen points to MSDOS ; VerAddr:VerLen points to 3.1 push cx mov ComLen,0 mov CapLen,0 mov SufLen,0 mov ComAddr,si add si,cx ; scan backwards to split off suffix ; scan back until hit non +-! dec si ; point to last char in string std ScanSuff: lodsb ; scan suffix backwards cmp al,'+' je NextSuff cmp al,'-' je NextSuff cmp al,'!' je NextSuff jmp Short Suffdone NextSuff: inc SufLen loop ScanSuff cld jmp TriSectTrouble ; fell out bottom -- 100% suffix oops! SuffDone: cld inc si ; went two too far inc si mov SufAddr,si ; Now find command by scanning from the ; front end till hit : + - ! mov si,ComAddr pop cx push cx comloop: lodsb cmp al,':' je FoundEndCom cmp al,'+' je FoundEndCom cmp al,'-' je FoundEndCom cmp al,'!' je FoundEndCom inc ComLen loop comloop ; fell out bottom, 100% command. ; this is perfectly ok. FoundEndCom: ; at this point ComAddr:ComLen and SuffAddr:SufLen ; are computed. Whatever lies between must be ; CapAddr:CapLen mov ax,ComAddr add ax,ComLen mov CapAddr,ax pop cx sub cx,ComLen sub cx,SufLen mov CapLen,cx ; at this point CapAddr:Caplen should either ; be null, or should point to colon. ; prune off the colon if present jcxz CapLenOk mov si,CapAddr lodsb cmp al,':' jne TrisectTrouble inc CapAddr ; chop off the colon dec CapLen CapLenOk: ; further split ComAddr:ComLen into ; AlpAddr:AlpLen and VerAddr:VerLen ; Work backwards looking for anything ; not [0 .. 9] [ , . ] mov cx,ComLen mov si,ComAddr mov AlpAddr,si add si,cx dec si std VerLoop: lodsb cmp al,DeciSep ; is it a decimal point? je StillVer cmp al,DeciSep2 ; is it a decimal point? je StillVer cmp al,CommaSep ; is it a comma je StillVer cmp al,'0' jb VerDone cmp al,'9' ja VerDone StillVer: loop VerLoop ; fall out bottom ok -- all numeric command. VerDone: cld ; went two too far inc si inc si ; now point to start of ver mov VerAddr,si ; calc Veraddr:Verlen sub si,AlpAddr ; AlpAddr:AlpLen mov AlpLen,si mov cx,ComLen sub cx,si mov VerLen,cx ret TrisectTrouble: jmp Abort Trisect endp ;============================================ FindCommand proc near ; Scans MCT table for an entry to match the current command. ; on entry, COMMAND to be looked up is all in upper case ; stored pointed to by si:cx ; on entry al contains a bit mask filter. Only entries with match ; on this filter should be considered. ; We look for the command. If we don't find it we abort. ; If we do find it, we return with pointer to the MCT entry in BP. ; On exit cx, si are trashed ; If we are successful we clear the carry, if not we set it Data segment OnlyFilter db 0 ; mask for types of command to look for Data ends mov onlyFilter,al dec si ; si points to count byte mov [si],cl ; store count byte just ahead of command ; to search for ; We need to get rid of it later, because it ; overlays a space the parsing routine ; uses. lea di,MCTStart.ExtNameAddr ; point to first element in MCT ; This is where we start looking Findloop: push si ; save addr string looking for push di ; save addr of current MCT entry mov di,[di] ; get addr of potential match cmpsb ; compare length bytes jne Notthisone sub ch,ch mov cl,[si-1] ; get length of string repe cmpsb ; compare strings jne Notthisone AlmostGotmatch: ; we probably found it ; check the capacity requirements pop bp ; pop the pointer to MCT entry ; was pushed as di push bp mov al,[Flags+BP] ; filter out matches that don't count and al,OnlyFilter ; on this pass. jz NotThisOne ; was not really a match ReallyGotIt: pop bp ; GOT IT! Match on both fields pop si mov byte ptr [si],20h ; restore count byte to a space clc ; indicate success ret NotThisOne: pop di ; get old MCT entry back pop si ; restore addr of string looking for add di,size MCT ; point at next MCT entry cmp di,offset COM:MCTEnd jb FindLoop ; fell out the bottom without finding ; what we are looking for. mov byte ptr [si],20h ; restore count byte to a space stc ; indicate failure ret FindCommand endp ;============================================ ParseCapacity proc near ; Analyses the capacity portion of the current command. ; On Entry CapAddr:CapLen points to string containing the numeric ; capacity e.g. 400K ; On exit, the number be converted to binary and stored in TheCapacity. mov si,CapAddr mov cx,CapLen jcxz NullCapacity call asciitobin ; get the number into DX:AX sub cx,cx ; adjust the number to no decimals call DPLAdjust mov word ptr TheCapacity,ax mov word ptr TheCapacity+2,dx ret NullCapacity: mov word ptr TheCapacity,0 mov word ptr TheCapacity+2,0 ret ParseCapacity endp ;============================================ ParseVersion proc near ; Analyses the Version portion of the current command. ; On Entry VerAddr:VerLen points to string containing the version ; capacity e.g. 3.1 in PCDOS3.1 ; On exit, the number be converted to binary and stored in TheVersion ; 3.1 will be stored as 310. mov si,VerAddr mov cx,VerLen jcxz NullVersion mov CommaSep,0 ; fudge so there is no commasep ; this allows Decisep2, the comma, to ; start acting like a decimal. ; This is mainly of interest to Norwegians ; where the roles of , and . are reversed. call asciitobin ; get the number into DX:AX mov bl,DeciSep2 ; put Commasep back to standard comma mov Commasep,BL mov cx,2 ; adjust to 2 decimal places call DPLAdjust SaveVersion: mov word ptr TheVersion,ax ; ignore high order part ret NullVersion: mov word ptr TheVersion,0 ret ParseVersion endp ;============================================ DPLAdjust proc near ; Adjusts the result of the most recent ASCIITOBIN so that it ; has a standard number of decimal places. ; On entry dx:ax contains an unsigned number ; DPL contains number of decimal places it has ; CX contains number of decimal places you want it to have ; ; On exit DX:AX is adjusted ; DPL is trashed ; treat DPL = -1 as 0 cmp DPL,-1 jne StdDPL mov DPL,0 StdDPL: DPLLoop: ; Decimal points might be off. We want 2 ; places past the decimal. We might have too ; many or two few cmp DPL,CX ; actual = desired decimal places? je DPLAdjusted ja TooManyDecs TooFewDecs: ; too few places past decimal call MultBy10 inc DPL ; add one decimal place jmp Short DPLLoop TooManyDecs: call DivBy10 ; remove one decimal place dec DPL jmp Short DPLLoop DPLAdjusted: ret DPLAdjust endp ;============================================ ParseSuffix proc near ; Parse the suffix part of the command. ; on entry SufAddr:SufLen points to Suffix characters. ; e.g. +!-? ; on exit OkMask is calculated. ; if suffix is null, default OkMask is used. ; If there are invalid characters in the suffix, we abort. ; NOTE WE NEED BP pointing to an MCT enntry mov si,SufAddr mov cx,SufLen jcxz UseDefaultSuffix mov OkMask,0 ; presume nothing allowed to start Suffloop: lodsb ; examine 1 char in suffix cmp al,'+' jne NotPlus test [Flags+BP],Plus ; test if + suffix allowed jz SuffErr or OkMask,Bang+Plus ; record ok to be >= level jmp SuffNext NotPlus: cmp al,'-' jne NotMinus test [Flags+BP],Minus ; test if - suffix allowed jz SuffErr or OkMask,Bang+Minus ; record ok to be <= level jmp Short SuffNext NotMinus: cmp al,'!' jne NotBang test [Flags+BP],Bang ; test if ! suffix allowed jz SuffErr or OkMask,Bang ; record ok to be = level jmp Short SuffNext NotBang: jmp Short SuffErr SuffNext: loop suffloop ret UseDefaultSuffix: mov al,[Defaults+BP] mov Okmask,al ; ! or +! for NEED ret ; ! or -! for AVOID SuffErr: jmp Abort ParseSuffix endp ;============================================ CalcWantLevel proc near ; Compute the desired test result, a function of keyword, capacity and ; version. ; Calculate level of presence desired ; e.g. how many bytes free RAM ; power level of CPU ; 1 = feature present ; on entry BP points to the appropriate MCT table entry sub ax,ax ; presume 0 mov dx,ax test [Flags+BP], TimeStyle ; /TIME:HH:MM style? jz NotTime ; the TheCapacity has HH*100+MM ; we want HH*60+MM mov ax,word ptr TheCapacity mov dx,word ptr TheCapacity+2 push bx mov bx,100d div bx ; ax = HH dx = mm cmp dx,59d jg TimeTrouble cmp ax,23d jg TimeTrouble mov bx,60d mul bl ; ax = HH*60 add ax,dx sub dx,dx ; dx:ax = HH*60+MM pop bx jmp short SaveWantLevel TimeTrouble: jmp Abort NotTime: test [Flags+BP],CapacityStyle or TimeStyle ; /RAM:100K style? jz NotCapacity mov ax,word ptr TheCapacity mov dx,word ptr TheCapacity+2 jmp short SaveWantLevel NotCapacity: test [Flags+BP],VersionStyle ; /DOS3.1 style? jz NotVersion mov ax,TheVersion jmp Short SaveWantLevel NotVersion: test [Flags+BP],ImpliedStyle ; /80286 style? power level jz NotImplied mov ax,[AuxInput+BP] jmp Short SaveWantLevel NotImplied: ; nothing, must be a dummy ; leave as 0 SaveWantLevel: mov word ptr WantLevel,ax mov word ptr WantLevel+2,dx ret CalcWantLevel endp ;============================================ Goldilocks proc near ; Analyse the test result to see if it is too low, too high or just right. ; on entry a tested value present is in DX:AX ; e.g. the actual free ramspace ; We compare it with the Wanted Level ; We then perform the Goldilocks maunoeuvre ; If we have too much, we return +1, just right 0, too little -1 ; We do NOT apply the OkMask cmp DX,word ptr WantLevel+2 jne Diff cmp AX,word ptr WantLevel jne Diff JustRight: mov ax,Bang ret Diff: ; subtract lsw first sub AX,word ptr WantLevel sbb DX,word ptr WantLevel+2 jl Cold Hot: mov ax,Plus ret Cold: mov ax,Minus ret Goldilocks endp ;============================================================== CountrySeps proc near ; Discover the chars used for separators usually comma and decimal ; Store in CommaSep and DeciSep ; see page 396 Ray Duncan's Advanced MS DOS data segment CountryBuf db 34d dup (?) ; stores county dependent info data ends lea dx,CountryBuf ; DS:DX points to country buffer mov ax,3800h ; get country info lea dx,CountryBuf int 21h call RAWDOSVer cmp ax,300 jae Dos3Country Dos2country: mov al,CountryBuf+4 ; comma mov ah,CountryBuf+6 ; decimal jmp Short SaveSeps Dos3Country: mov al,CountryBuf+7 ; comma mov ah,CountryBuf+9 ; decimal SaveSeps: cmp al,20h ja CommasepOk mov al,',' CommaSepOk: cmp ah,20h ja DecisepOk mov ah,'.' DecisepOk: mov CommaSep,al ; usually comma mov DeciSep,ah ; usually point mov DeciSep2,al ; usually comma ret CountrySeps endp ;============================================================== NIY proc near ; Dummy code for Not Implemented Yet tester routines ; just returns 0 in DX:AX Data segment NIYMsg db 'Parameter not implemented yet',13,10,'$' Data ends lea dx,NIYMsg sub ax,ax mov dx,ax ret NIY endp ;============================================================== ; ; M A S T E R C O N T R O L T A B L E ; ; Controls which commands will be accepted and which routines will ; handle them. ; To add new commands to NEED, you simply must add a line to this table ; and write a handler routine. You can share the error messages with another ; class of commands or provide your own. All commands in a class share a ; common pair of error messages and a common testing routine. ; The class name provides the link. ; ; The Classname must be mentioned as the first parameter on each of the ; ACCEPT macros. In addition, your corresponding TEST routine ; must be called 'Testxxxx' where xxxx is the class name. ; You must also write a routine calld 'Displayxxxx' to display the current ; state of the test. ; ; - Aux input is just a number passed to the test routine. It is primarily ; used when many comands all share the same test routine. It lets the routine ; know in a quick way which command was used. It is mainly used to pass the ; drive letter to the free disk space routine. The same slot is also used ; for an unrelated purpose, to encode an implied level wanted -- e.g. ; that an SX is the same as asking for a level 300 CPU. ; ; - Bang,Plus,Minus are bit masks that say whether this command ; accepts each of ! + or - suffixes. The parser checks ; for the presence of switches and validates them. It applies defaults then ; it summarizes their presence in OkMask. ; ; - CapacityStyle says a 32 bit Capacity suffix may follow, separated by a ; colon, and trailed by an M, K or MB e.g. /EXT:2,304K ; Commas ignored (dots ignored in Norway). ; Parser stores this in 32-bit 'TheCapacity' for the handler routine. ; ; - VersionStyle says trailing digits e.g. DOS3.2 should be converted to ; integer with implied two decimal places, e.g. 320. ; Parser stores this in 16-bit 'TheVersion' for the handler routine. ; ; - The ExtNames are stored separately from the main MCT. The ACCEPT macro ; handles this for you automatically. ; ; - the name of the testroutine is generated automatically as TestXXX where ; XXX is the class name. Table segment public MCTStart label word ; start of master control table Table ends ; = = = = = = = = = = = = = = = = = = = = = = SafeVector proc near ; Ensures that a vector exists and his hooked up to something ; before we call the corresponding interrupt. ; Many oddball machines are missing vectors. ; on entry AL has the vector number ; on exit carry flag set if problems, carry clear if ok ; all registers preserved ; see page 393 Ray Duncan's Advanced MS DOS push es push bx push ax mov ah,35h ; get vector int 21h ; ES:BX points to interrupt handler mov ax,ES or ax,BX jz BadVector cmp byte ptr ES:[bx],0cfh ; is it an iret? je BadVector GoodVector: clc jmp VectorDone badVector: stc VectorDone: pop ax pop bx pop es ret SafeVector endp ; = = = = = = = = = = = = = = = = = = = = = = ; <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> 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 ; = = = = = = = = = = = = = = = = = = = = = = TestANDOperator proc near ; This is not really a test, but a logical operator to separate tests: ; E.g. NEED 80387 80386 AND 80486 ; We implement it to look like a test routine. ; It does nothing really, since tests are separated by an IMPLIED AND. if GENERATING eq SNIFF mov ax,1 ; dummy as if present endif if GENERATING eq AVOID mov ax,0 ; dummy as if not present, i.e. good. endif if GENERATING eq NEED mov ax,1 ; dummy as if present, i.e. good endif ret TestAndOperator 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 û 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. ; 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 û ;; 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 ;; û ; ; 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 endm 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 î ; 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 CPUID test eax,eax jz Is486 ; some half baked implementation mov eax,1 CPUID ; 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 î 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 ; 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 ; 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 ; = = = = = = = = = = = = = = = = = = = = = = TestDebugExist proc near ; Force tests to turn on debugging dumps. Data segment Debug dw 0 ; do we want debugging dumps to show? ; initially no. If DEBUG command, then yes. Data ends mov Debug,-1 ; turn on slow testing if GENERATING eq SNIFF mov ax,1 ; dummy as if present endif if GENERATING eq AVOID mov ax,0 ; dummy as if not present endif if GENERATING eq NEED mov ax,1 ; dummy as if present endif ret TestDEBUGExist 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 ; = = = = = = = = = = = = = = = = = = = = = = 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 ; = = = = = = = = = = = = = = = = = = = = = = TestENV 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. if GENERATING eq SNIFF mov ax,1 ; dummy as if present endif if GENERATING eq AVOID mov ax,0 ; dummy as if not present, i.e. good. endif if GENERATING eq NEED mov ax,1 ; dummy as if present, i.e. good endif 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 if GENERATING eq SNIFF mov ax,1 ; dummy as if present endif if GENERATING eq AVOID mov ax,0 ; dummy as if not present endif if GENERATING eq NEED mov ax,1 ; dummy as if present endif 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 û 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 ;============================================ 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 ;============================================ Table segment public MCTEnd label word ; end of MCT Master control table Table ends ;============================================ START endp ;============================================ CODE ends ; end of code segment end Start