Category : Pascal Source Code
Archive   : INFOS155.ZIP
Filename : INFOPLUS.ASM

 
Output of file : INFOPLUS.ASM contained in archive : INFOS155.ZIP
;--------------------------------------------------------------------
;
; INFOPLUS.ASM
;
; Version 1.55
;
; Eleven subprograms used by INFOPLUS.PAS:
;
; CPUID - identifies host CPU and NDP (if
; any)
; DISKREAD - reads absolute sectors from disk
; LONGCALL - calls a routine using a CALL FAR
; ATIINFO - for accessing ATI VGAWonder cards
; ALTINTR - calls interrupts with a true INT call
; ALTMSDOS - calls DOS with a true INT call
; CIRRUSCK - Cirrus VGA check
; CTICK - Chips & Technologies VGA check
; TSENGCK - Tseng VGA check
; ZYMOSCK - ZyMOS VGA check
; BUGTST - Test for 386 POPAD bug
;
; Originally by:
; Steve Grant
; Long Beach, CA
; January 13, 1989
;
; mods by Andrew Rossmann (4/21/92)
;--------------------------------------------------------------------

.286P
.8087

public CPUID, DISKREAD, LONGCALL, ATIINFO, ALTINTR, ALTMSDOS
public CTICK, TSENGCK, ZYMOSCK, CIRRUSCK, BUGTST

CODE segment byte

; Conditional jumps are all coded with the SHORT qualifier in
; order to minimize the size of the .OBJ file output of Turbo
; Assembler.

;--------------------------------------------------------------------

CPUID proc far

assume cs:CODE, ds:DATA, es:nothing, ss:nothing

; On entry:
;
; BP
; SP => near return address
; offset of a cpu_info_t record
; segment " " " "
; also, the test type byte should be a 'C' or 'N' to execute the
; CPU or NDP tests.
;
; On exit, the cpu_info_t record has been filled in as follows:
;
; byte = CPU type
; word = Machine Status Word
; 6 bytes = Global Descriptor Table
; 6 bytes = Interrupt Descriptor Table
; boolean = segment register change/interrupt flag
; byte = NDP type
; word = NDP control word
; byte = Weitek presence
; byte = test type (C, N, or W)

cpu_info equ [bp + 6]

mCPU equ byte ptr [bx]
mMSW equ word ptr [bx + 1]
mGDT equ [bx + 3]
mIDT equ [bx + 9]
mchkint equ byte ptr [bx + 15]
mNDP equ byte ptr [bx + 16]
mNDPCW equ word ptr [bx + 17]
mWeitek equ byte ptr [bx + 19]
mtest equ byte ptr [bx + 20]

f8088 equ 0
f8086 equ 1
fV20 equ 2
fV30 equ 3
f80188 equ 4
f80186 equ 5
f80286 equ 6
f80386 equ 7
f80486 equ 8
funk = 0FFH

false equ 0
true equ 1

push bp
mov bp,sp
push ds
lds bx,cpu_info
cmp mtest, 'C'
jnz skipcpu
call cpu
call chkint
skipcpu:
cmp mtest, 'N'
jnz skipndp
call ndp
skipndp:
cmp mtest, 'W'
jnz skipweitek
call weitek
skipweitek:
pop ds
pop bp
ret 4
CPUID endp

;--------------------------------------------------------------------

cpu proc near

; interrupt of multi-prefix string instruction

mov mCPU,funk ;set CPU type to unknown
sti
mov cx,0FFFFH
rep lods byte ptr es:[si]
jcxz short cpu_02
call piq
cmp dx,4
jg short cpu_01
mov mCPU,f8088
jmp cpu_done
cpu_01:
cmp dx,6
jne cpu_01a
mov mCPU,f8086
cpu_01a:
jmp cpu_done
cpu_02:

; number of bits in displacement register used by shift

mov al,0FFH
mov cl,20H
shl al,cl
or al,al
jnz short cpu_04
call piq
cmp dx,4
jg short cpu_03
mov mCPU,fV20
jmp cpu_done
cpu_03:
cmp dx,6
je cpu_03a
jmp cpu_done
cpu_03a:
mov mCPU,fV30
jmp cpu_done
cpu_04:

; order of write/decrement by PUSH SP

push sp
pop ax
cmp ax,sp
je short cpu_06
call piq
cmp dx,4
jg short cpu_05
mov mCPU,f80188
jmp cpu_done
cpu_05:
cmp dx,6
jne short cpu_done
mov mCPU,f80186
jmp cpu_done

; We most likely have a 286, 386 or 486 CPU by now
;First, grab some tables

cpu_06:
smsw mMSW
sgdt mGDT
sidt mIDT

;!!!!!!!
;!!! Original 286/386 detection code (modified 8/10/90)
;!!! Modified by code supplied by John Levine, apparantly from an Intel
;!!! '486 manual.
;!!!!!!!

pushf ;put flags into CX
pop cx
and cx,0fffh ;mask off upper 4 bits
push cx
popf
pushf
pop ax
and ax,0f000h ;look only at upper 4 bits
cmp ax,0f000h ;88/86 etc.. turn them on
jz badcpu ;not 286/386/486!!!
or cx,0f000h ;force upper 4 bits on
push cx
popf
pushf
pop ax
and ax,0f000h
jz found286 ;bits are zeroed in real mode 286
;
;since we probably have have a 386 or 486 by now, we need to do some 32-bit
;work. Detect the 486 by seeing if the Alignment Check flag is settable. This
;flag only exists on the '486.
;
.386
and esp,0FFFFh ;use only 64K stack
mov edx,esp ;save current stack position
and esp,0FFFCh ;dword align to avoid traps
pushfd ;push 32 bit flag
pop eax
mov ecx,eax ;save current flags
xor eax,40000h ;flip AC (alignment check) flag
push eax
popfd
pushfd
pop eax
xor eax,ecx ;eliminate all but AC bit
push ecx ;restore flags
popfd
mov esp,edx ;restore stack position
test eax,40000h ;is bit set?
.286
jz found386 ;if not, is a 386
mov mCPU,f80486 ;must be a 486!!
jmp short cpu_done
found286:
mov mCPU,f80286
jmp short cpu_done
found386:
mov mCPU,f80386
jmp short cpu_done
badcpu:
mov mCPU,funk ;how'd an 8088 get this far?????
CPU_done:
ret
cpu endp
;--------------------------------------------------------------------

piq proc near

; On exit:
;
; DX = length of prefetch instruction queue
;
; This subroutine uses self-modifying code, but can
; nevertheless be run repeatedly in the course of the calling
; program.

count = 7
opincdx equ 42H ; inc dx opcode
opnop equ 90H ; nop opcode

mov al,opincdx
mov cx,count
push cx
push cs
pop es
mov di,offset piq_01 - 1
push di
std
rep stosb
mov al,opnop
pop di
pop cx
xor dx,dx
cli
rep stosb
rept count
inc dx
endm
piq_01:
sti
ret
piq endp

;--------------------------------------------------------------------

chkint proc near

; save old INT 01H vector

push bx
mov ax,3501H
int 21H
mov old_int01_ofs,bx
mov old_int01_seg,es
pop bx

; redirect INT 01H vector

push ds
mov ax,2501H
mov dx,seg new_int01
mov ds,dx
mov dx,offset new_int01
int 21H
pop ds

; set TF and change SS -- did we trap on following instruction?

pushf
pop ax
or ah,01H ; set TF
push ax
popf
push ss ; CPU may wait one
; instruction before
; recognizing single step
; interrupt
pop ss
chkint_01: ; shouldn't ever trap here

; restore old INT 01H vector

push ds
mov ax,2501H
lds dx,old_int01
int 21H
pop ds
ret
chkint endp
;--------------------------------------------------------------------

new_int01 proc near

; INT 01H handler (single step)
;
; On entry:
;
; SP => IP
; CS
; flags

sti
pop ax ; IP
cmp ax,offset chkint_01
jb short new_int01_03
je short new_int01_01
mov mchkint,false
jmp short new_int01_02
new_int01_01:
mov mchkint,true
new_int01_02:
pop cx ; CS
pop dx ; flags
and dh,0FEH ; turn off TF
push dx ; flags
push cx ; CS
new_int01_03:
push ax ; IP
iret
new_int01 endp
;--------------------------------------------------------------------

ndp proc near

fnone equ 0
f8087 equ 1
f80287 equ 2
f80387 equ 3
funk = 0FFH


; The next two 80x87 instructions cannot carry the WAIT prefix,
; because there may not be an 80x87 for which to wait. The WAIT is
; therefore emulated with a MOV CX,! LOOP $ combination.

.287
mov word ptr ndp_cw,0000H
cli ;no interrupts during this test

fninit ;initialize NDP
mov cx,2
loop $

fnstcw ndp_cw ;store control word in ndp_cw
mov cx,14h
loop $

sti
mov ax,ndp_cw ;check for valid status word
cmp ah,3 ;is NDP present?
je short ndp_01 ;if 3, must be there
mov mNDP,fnone
jmp short ndp_done

ndp_01:
cmp ax,03FFH ;check if 8087
jne short ndp_02
mov mNDP,f8087
jmp short ndp_04
ndp_02:

.287

cmp ax,037FH ;check if 286/387/486
jne short ndp_05 ;must be garbage

;detect 287 or 387

fld1 ;Load +1.0 onto NDP stack
fldz ;Load +0.0 onto NDP stack
fdiv ;do +1/0
fld1 ;Load +1.0 onto NDP stack
fchs ;Change to -1.0
fldz ;Load +0.0 onto NDP stack
fdiv ;do -1/0
fcom ;compare
fstsw ndp_sw
mov ax,ndp_sw
and ah,41H ; C3, C0
cmp ah,40H ; ST(0) = ST(1)
jne short ndp_03
mov mNDP,f80287
jmp short ndp_04
ndp_03:
cmp ah,01H ; ST(0) < ST(1)
jne short ndp_05
mov mNDP,f80387
ndp_04:

.8087
fstcw mNDPCW ;save status for INFOPLUS
ret
ndp_05:
mov mNDP,funk
ndp_done:
ret
ndp endp

;------------------------------------------------------------------------------
; This checks to see if the BIOS reports a Weitek math coprocessor. This should
; only be called if a 386 or 486 is found.
; NOTE!! This may not work with all computers!!

fnoWeitek equ 0
fWeitek equ 1
fWeitek_real equ 81h

weitek proc near
.386
xor eax,eax ;zero everything
int 11h ;do equipment check
test eax,01000000h ;check bit 24, set if Weitek present
je no_weitek
mov mWeitek,fWeitek
test eax,0800000h ;check bit 23, set if Weitek can be
je weitek_done ; addressed in real mode
mov mWeitek,fWeitek_real
jmp short weitek_done
no_weitek:
mov mWeitek,fnoWeitek
weitek_done:
ret
.286
weitek endp

;--------------------------------------------------------------------

DISKREAD proc far

assume cs:CODE, ds:DATA, es:nothing

; On entry:
;
; BP
; SP => near return address
; offset of disk buffer
; segment " " "
; number of sectors to read
; starting logical sector number
; drive number (0=A, 1=B, etc.)
;
; On exit:
;
; AX = function result
; 00 - function successful
; 01..FF - DOS INT 25H error result

drive equ [bp + 16]
starting_sector equ [bp + 12]
number_of_sectors equ [bp + 10]
buffer equ [bp + 6]

push bp
mov bp,sp
mov ax,3000h ;get DOS version
int 21h
cmp al,4 ;DOS 4?
jge read4 ;We have 4 or newer, so use extended
cmp ax,1d04h ;use old for anything less than 3.30
jle read3
;
;Check bit 1 of the device attributes bit. If it's set, then the driver
;supports use of the extended access method
;
push es ;save regs
push ds
mov dl,drive ;get drive number (0=A,1=B,etc)
inc dl ;func uses 0=dflt, 1=A, etc..
mov ah,32h ;get driver parameter block
int 21h
push ds ;move ds to es
pop es
pop ds ;restore original ds
les bx,[es:bx + 12h] ;point ES:BX to device driver
test word ptr [es:bx + 4],2 ;test device attributes
pop es
jz read3 ;wasn't, so use old method

read4:
mov al,drive
mov bx,starting_sector ;copy info into parameter block
mov extd_starting_sector_lo,bx
mov bx,starting_sector + 2
mov extd_starting_sector_hi,bx
mov bx,number_of_sectors
mov extd_number_of_sectors,bx
les bx,buffer ;get seg:ofs of buffer in ES:BX
mov extd_bufofs,bx ;put into block
mov extd_bufseg,es
mov bx,offset dos4_block ;DS:BX points to block
mov cx,-1 ;-1 means extended read
push ds ;save DS (not really needed, but lets
;me share code with DOS 3 read.)
jmp short readit

read3: mov al,drive
mov dx,starting_sector
mov cx,number_of_sectors
push ds
lds bx,buffer ;get seg:ofs of buffer in DS:BX
readit: int 25H
inc sp ; fix broken stack
inc sp
pop ds
jc short diskread_01
xor ax,ax
diskread_01:

pop bp
ret 10

DISKREAD endp

;
;LONGCALL will call a routine using a CALL FAR.
;
;Pascal format: procedure longcall(addr: longint; var regs: registers); external;
;

longcall proc far
assume cs:CODE, ds:DATA, es:nothing

regaddr equ [bp + 6]
addr equ [bp + 10]

push bp
mov bp,sp
push ds
mov ax,addr ;copy calling address for later use
mov word ptr cs:address,ax
mov ax,addr+2
mov word ptr cs:address+2,ax
lds si,regaddr ;get pointer to regs
mov cs:ds_save,ds ;save needed ones
mov cs:si_save,si
cld ;go forward
lodsw ;load AX and hold it
push ax
lodsw ;load BX
mov bx,ax
lodsw ;load CX
mov cx,ax

lodsw ;load DX
mov dx,ax
lodsw ;load BP
mov bp,ax
lodsw ;load SI and hold it
push ax
lodsw ;load DI
mov di,ax
lodsw ;load DS and hold it
push ax
lodsw ;load ES
mov es,ax
lodsw ;load Flags
and ax,008D5h ;mask out non-standard bits
push bx ;I need a register!
mov bx,ax
pushf ;get current flags in AX
pop ax
and ax,0F72Ah ;mask out normal bits
or ax,bx ;set needed flags
push ax
popf
pop bx
pop ds ;get rest of regs
pop si
pop ax
call dword ptr cs:address ;make far call
pushf ;save flags and modified regs
push es
push di
mov es,cs:ds_save ;get regs pointer into ES:DI
mov di,cs:si_save
cld ;go forward
stosw ;save AX
mov ax,bx
stosw ;save BX
mov ax,cx
stosw ;save CX
mov ax,dx
stosw ;save DX
mov ax,bp
stosw ;save BP
mov ax,si
stosw ;save SI
pop ax
stosw ;save DI
mov ax,ds
stosw ;save DS
pop ax
stosw ;save ES
pop ax
stosw ;save Flags
pop ds ;restore regs
pop bp
ret 8

address dd ?
ds_save dw ?
si_save dw ?

longcall endp

;
; ATIINFO is used in the Video identification routine to get special
; information from ATI VGA Wonder cards.
;
; Pascal format: function ATIinfo(data_in: byte; register: word): byte;
;
ATIinfo proc far
assume cs:CODE, ds:DATA, es:NOTHING

data_in equ [bp+8]
register equ [bp+6]

push bp
mov bp,sp
mov dx,register ;get register
mov ax,data_in ;get command word (actually byte)
cli ;no interrupts
out dx,al
inc dx ;next port
in al,dx ;get result
sti ;restore interrupts
mov sp,bp
pop bp
ret 4

ATIinfo endp

; AltIntr is an alternative to the Intr function. The standard Intr function
; does not do a true Interrupt!! Instead, it gets the address of the interrupt
; from the interrupt table, loads all the registers, and then does a RETF!!!
; The address of a return routine has been pushed on the stack so that it
; returns to TP and unloads the registers. This was probably done because
; Intel saw to it that all interrupt numbers must be immediate, and Borland
; didn't want to use self-modifying code.
; NOTE: The MsDos routine is ALSO affected by this problem. It just stuffs
; a 21h into the stack, and calls Intr!!! So you can use ALTMSDOS instead!
; Now, normally, the above procedure works perfectly fine, except under 1
; condition. When the CPU is under protected or Virtual 86 mode. When in those
; modes, a program with higher privileges can trap an interrupt and act on it.
; I found this out the hard way by going nuts wondering why I couldn't detect
; DPMI drivers or Windows!! My alternative Interrupt functions identically to
; Borlands, but uses self-modifying code to generate a true interrupt. To
; prevent possible problems with CPU pipelining, the entry point is near the
; end of the code, and then jumps back to continue.
;
; Pascal format: procedure AltIntr(intno: byte; regs: registers); external;

ALTINTRP proc far
assume cs:CODE, ds:DATA, es:NOTHING

regaddr equ [bp + 6]
intno equ [bp + 10]


altcont:
lds si,regaddr ;point DS:SI to regs
mov cs:save_ds,ds ;save pointer for return
mov cs:save_si,si
cld ;go forward
lodsw ;load AX and hold it
push ax
lodsw ;load BX
mov bx,ax
lodsw ;load CX
mov cx,ax
lodsw ;load DX
mov dx,ax
lodsw ;load BP
mov bp,ax
lodsw ;load SI and hold it
push ax
lodsw ;load DI
mov di,ax
lodsw ;load DS and hold it
push ax
lodsw ;load ES
mov es,ax
lodsw ;load Flags
and ax,008D5h ;mask out non-standard bits
push bx ;I need a register!
mov bx,ax
pushf ;get current flags in AX
pop ax
and ax,0F72Ah ;mask out normal bits
or ax,bx ;set needed flags
push ax
popf
pop bx
pop ds ;get rest of regs
pop si
pop ax
db 0cdh ;Int opcode
intrpt db ? ;loaded with real interrupt
pushf ;save flags and modified regs
push es
push di
mov es,cs:save_ds ;get regs pointer into ES:DI
mov di,cs:save_si
cld ;go forward
stosw ;save AX
mov ax,bx
stosw ;save BX
mov ax,cx
stosw ;save CX
mov ax,dx
stosw ;save DX
mov ax,bp
stosw ;save BP
mov ax,si
stosw ;save SI
pop ax
stosw ;save DI
mov ax,ds
stosw ;save DS
pop ax
stosw ;save ES
pop ax
stosw ;save Flags
pop ds ;restore regs
pop bp
ret 6

altintr:
push bp
mov bp,sp
push ds ;save DS, because we screw it up
mov al,intno ;get interrupt number to use
mov cs:intrpt,al ;and modify our code
jmp altcont ;continue with rest of code

;local storage

save_ds dw ?
save_si dw ?

ALTINTRP endp
;
; Pascal format: procedure AltMsDos(var regs: registers); external;
;
ALTMSDOS proc far
assume cs:CODE, ds:DATA, es:NOTHING

pop si ;back track a bit so we can stuff
pop dx ;interrupt number in
pop cx
pop bx
mov al,21h ;push interrupt number
push ax
push bx
push cx ;restore other info
push dx
push si
jmp ALTINTR ;do interrupt call

ALTMSDOS endp

CIRRUSCK proc far
assume cs:CODE, ds:DATA, es:nothing;

;Cirrus VGA detection from 'Advanced Programmer's Guide to Super VGAs'

; Fetch address of CRT controller
mov ax,40h ;BIOS segment
mov es,ax
mov dx,es:[63h] ;get CRTC address
; clear Start Address register in CRTC (index 0Ch)
mov al,0ch ;index of Start Address reg
out dx,al ;select
inc dx
mov ah,al
in al,dx ;get current value
xchg ah,al ;save
push ax
push dx
xor al,al
out dx,al ;clear start address reg
dec dx
; fetch unlock password
mov al,1fh
out dx,al ;select id reg
inc dx
in al,dx ;read unlock password
mov ah,al ;save
mov bh,al ;save again
mov ch,cl
; enable extended regs

mov cl,4 ;nibble swap rotate count
mov dx,3c4h ;address of sequencer
mov bl,6 ;get extension control value
ror bh,cl ;compute extensions disable value
mov ax,bx ;extensions disable
out dx,al ;disable extensions
inc dx
in al,dx
or al,al ;disabled?
jnz exit_cirrus

mov bh,al ;save current setting
dec dx
mov al,6
out dx,al ;select extension control reg
inc dx
mov al,ah ;get unlock password
out dx,al ;enable extended regs
in al,dx ;read back extension reg
cmp al,1
jne exit_cirrus ;wasn't a cirrus
ror bh,cl ;compute extensions enable value
dec dx
mov ax,bx ;extensions enable

out dx,ax ;enable extensions
inc dx
in al,dx ;read extended control reg
cmp al,1 ;enabled
jne exit_cirrus ;wasn't cirrus
pop dx
dec dx
pop ax
out dx,ax
mov ah,0
mov al,ch
jmp short end_cirrusck
exit_cirrus:
pop dx ;restore CRTC addr
dec dx ;point to index
pop ax ;restore CRC value
out dx,ax
mov ax,0
end_cirrusck:
ret

CIRRUSCK endp

CTICK proc far
assume cs:CODE, ds:DATA, es:nothing;

;CTI VGA detection from 'Advanced Programmer's Guide to Super VGAs'

;place VGA in setup mode
cli
mov dx,46e8h ;address of setup control reg
in al,dx
or al,10h ;turn on setup bit
out dx,al ;go to setup mode
;enable extended register bank
mov dx,103h ;extended reg address
in al,dx
or al,80h ;turn enable bit on
out dx,al
;read global ID
mov dx,104h ;global ID reg
in al,dx
mov ah,al ;save
; place vga in normal mode
mov dx,46e8h
in al,dx
and al,03fh
out dx,al
sti
; read version extended register
mov dx,3d6h
mov al,0
out dx,al ;select version register
inc dx
in al,dx
cmp ah,5ah ;check for CTI ID
jne notcti
and al,0f0h ;adjust chip id
shr al,1
shr al,1
shr al,1
shr al,1
cmp al,2 ;only 0, 1 and 3 are good
je notcti
cmp al,4
jge notcti
cmp al,3
je end_ctick
inc al ;adjust to match chip number
jmp short end_ctick
notcti:
xor ax,ax
end_ctick:
ret

CTICK endp

TSENGCK proc far
assume cs:CODE, ds:DATA, es:nothing;

;Tseng VGA detection from 'Advanced Programmer's Guide to Super VGAs'

mov dx,3cdh ;page select reg
in al,dx
mov ah,al ;save
and al,0c0h ;save some bits
or al,55h ;test value one
out dx,al ;write it
in al,dx
cmp al,55h ;same?
jne nottseng
mov al,0aah ;test value two
out dx,al
in al,dx
cmp al,0aah ;same
jne nottseng
mov al,ah ;restore original settings
out dx,al
mov al,1
jmp short end_tsengck
nottseng:
mov al,0
end_tsengck:
ret

TSENGCK endp

ZYMOSCK proc far
assume cs:CODE, ds:DATA, es:nothing;

;ZyMOS VGA detection from 'Advanced Programmer's Guide to Super VGAs'

mov dx,3c4h ;extended reg bank
mov al,0bh ;version reg
out dx,al
inc dx
in al,dx ;get version
and al,0fh
cmp al,2
je end_zymosck
mov al,0
end_zymosck:
ret
ZYMOSCK endp

BUGTST proc far
;
; BUGTST.ASM - By: John Lauro
; Based on bug found by Jeff Prothero
; Adapted for Infoplus by Andrew Rossmann, 7/20/91.

ASSUME CS:CODE,DS:DATA,ES:nothing
.386
mov eax,12345678
mov edx, 0
mov edi, 0
pushad
popad

; The instruction immediately following popad is the critical
; instruction. Simple fix, insert a NOP after popad.

mov ecx, [edx+edi]

cmp eax, 12345678
.286
mov al,0
je end_bugtst
mov al,1
end_bugtst:
ret
BUGTST endp

code ends

;--------------------------------------------------------------------

DATA segment byte

; storage for CPUID

; redirected INT 01H vector

old_int01 label dword
old_int01_ofs dw ?
old_int01_seg dw ?

; storage for NDPID

; 80x87 control word after initialization, status word after divide by zero

ndp_cw dw ?
ndp_sw dw ?

; storage for DISKREAD

; DOS 4.0 extended read parameter block
dos4_block label byte
extd_starting_sector_lo dw ?
extd_starting_sector_hi dw ?
extd_number_of_sectors dw ?
extd_bufofs dw ?
extd_bufseg dw ?


DATA ends

end


  3 Responses to “Category : Pascal Source Code
Archive   : INFOS155.ZIP
Filename : INFOPLUS.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/