;----------------------------------------------------------------------- ; Alternate Multiplex Interrup Specification Library ; AMIS.ASM Public Domain 1992 Ralf Brown ; You may do with this software whatever you want, but ; common courtesy dictates that you not remove my name ; from it. ; ; Version 0.83 ; LastEdit: 5/2/92 ;----------------------------------------------------------------------- INCLUDE AMIS.MAC TSRcode@ EXTRN HOOKED_INT_LIST:BYTE EXTRN MULTIPLEX_NUMBER:BYTE EXTRN ALTMPX_SIGNATURE:BYTE EXTRN ALTMPX$PSP:WORD TSRcodeEnd@ IFNDEF __TINY__ EXTRN __psp:WORD ENDIF ;----------------------------------------------------------------------- _TEXT SEGMENT PUBLIC BYTE 'CODE' ASSUME CS:_TEXT ;----------------------------------------------------------------------- alloc_strat dw 0 link_state db 0 ; are UMBs part of memory chain? install_flags db 0 ; mpx_number must immediately follow mpx_number db 0 ; multiplex number to install on/when uninstalling ;----------------------------------------------------------------------- ; Terminate back to DOS. Depending on where the resident code was moved, ; the program will be terminated either with a normal terminate call or ; with a terminate-and-stay-resident call. ; public $go_TSR $go_TSR proc DIST db 0BAh ; MOV DX,IMM16 resident_size dw 0 ; number of paras to keep on going TSR db 0B8h ; MOV AX,IMM16 exit_code db 0 ; exit_func must immediately follow exit_func db 4Ch ; will change to 31h if we must go resident int 21h $go_TSR endp ;----------------------------------------------------------------------- ; entry: AX = segment of TSR code within the currently executing transient ; program (AX:ALTMPX_SIGNATURE is used) ; exit: CF clear if not installed ; AL = 00h if free multiplex number exists ; AH = multiplex number to use ; = 01h if all multiplex numbers are already in use ; AH destroyed ; CX destroyed ; CF set if already installed ; AL = FFh ; AH = multiplex number being used ; CX = version number of resident TSR ; public check_if_installed check_if_installed proc DIST ASSUME DS:NOTHING,ES:NOTHING push ds push es push si push di push dx push bx mov ds,ax ASSUME DS:RESIDENT_CODE xor ax,ax ; AH=mpx #00h, AL=func 00h (instchk) mov bx,0001h ; BH=00h, BL=01h: all mpx numbers in use chk_installed_loop: push ax int 2Dh ; check if INT 2D/AH=xx is in use cmp al,0FFh ; multiplex number in use? pop ax je chk_installed_inuse or bl,bl ; if BL=00h, we've already seen a free mpx je chk_installed_next mov bl,0 mov bh,ah jmp short chk_installed_next chk_installed_inuse: mov es,dx ASSUME ES:NOTHING mov si,offset RESIDENT_CODE:ALTMPX_SIGNATURE push cx ; remember version number mov cx,16/2 ; length of signature string cld rep cmpsw pop cx ; retrieve version stc ; assume already installed jz chk_installed_done ; and quit if it is chk_installed_next: inc ah jnz chk_installed_loop ; not yet installed clc mov ah,bh ; AH <- multiplex number to use mov al,bl ; AL <- 'available' flag chk_installed_done: pop bx pop dx pop di pop si pop es pop ds ASSUME DS:NOTHING,ES:NOTHING ret check_if_installed endp ;----------------------------------------------------------------------- ; entry: DS:SI -> hooked interrupt list ; exit: AX, BX, CX, DX destroyed ; CF set if unable to unhook all vectors ; CF clear if successful ; public unhook_interrupts unhook_interrupts proc DIST push es push ds push di push si cld chk_unhook_loop: lodsb mov dx,[si] ; get offset of interrupt handler inc si ; and skip that field in the hook inc si ; list cmp al,2Dh je all_unhookable mov ah,35h int 21h ; get interrupt vector mov ax,es mov cx,ds cmp ax,cx ; check segment agains of vectors jne chk_isp_loop cmp dx,bx ; check offset of vector against ours je chk_unhook_loop ; this int is unhookable if same chk_isp_loop: cmp word ptr es:[bx],10EBh ; handler starts with JMP SHORT $+12 ? jne not_unhookable cmp word ptr es:[bx+6],424Bh ; valid signature? jne not_unhookable cmp byte ptr es:[bx+9],0EBh ; hardware reset must also be JMP SHORT jne not_unhookable cmp cx,word ptr es:[bx+4] ; check segment of next ptr against ours jne chk_next_isp cmp dx,word ptr es:[bx+2] ; check offset of next ptr against ours je chk_unhook_loop ; this int is unhookable if same chk_next_isp: les bx,es:[bx+2] ; advance to next ISP header jmp chk_isp_loop ; and test it not_unhookable: stc unhook_ints_done: pop di pop si pop ds pop es ret all_unhookable: pop si ; get back start of hook list push si ; and preserve SI for return unhook_loop: lodsb mov dx,[si] inc si inc si push ds push ax mov ah,35h int 21h ; get interrupt vector mov ax,es mov cx,ds cmp ax,cx ; check segments of vectors jne isp_loop cmp dx,bx ; check offsets of vectors jne isp_loop lds dx,[bx+2] ; get our old_int?? pointer pop ax push ax mov ah,25h ; set interrupt vector int 21h jmp short unhooked_interrupt isp_next: les bx,es:[bx+2] ; advance to next ISP header isp_loop: ; ; no need to check for a valid ISP header, as we already know all chains reach ; our header before non-ISP code ; cmp cx,es:[bx+4] ; check segment of 'previous' ptr jne isp_next cmp dx,es:[bx+2] ; check offset of 'previous' ptr jne isp_next xchg bx,dx lds bx,[bx+2] xchg bx,dx ; ES:BX -> previous ISP ; DS:DX -> next ISP mov es:[bx+2],dx ; prev->next = curr->next mov es:[bx+4],ds ; thus, we are now unhooked unhooked_interrupt: pop ax pop ds cmp al,2Dh jne unhook_loop clc ; indicate success jmp unhook_ints_done unhook_interrupts endp ;----------------------------------------------------------------------- ; Call the XMS driver to allocate an upper memory block ; ; entry: DX = number of paragraphs needed ; exit: ZF set if successful ; BX = segment address of UMB ; alloc_UMB proc near mov ah,10h ;; fall through to XMS ;; alloc_UMB endp ;----------------------------------------------------------------------- ; Call the XMS driver ; ; entry: all registers as needed for XMS call ; exit: registers as returned by XMS driver ; ZF set if successful, ZF clear if failure ; XMS proc near db 09Ah ; FAR CALL xms_entry dd 0 ; XMS driver's entry point cmp ax,1 ret XMS endp ;----------------------------------------------------------------------- ; Determine entry point of XMS driver and initialize procedure XMS to call ; that entry point ; ; exit: CF set if no XMS driver or other failure ; CF clear if initialization successful ; AX,BX destroyed ; get_XMS_entry proc near push es mov ax,352Fh int 21h ; find out whether INT 2F is valid mov ax,es or ax,bx ; don't try XMS if INT 2F is NULL jz no_XMS_driver ; (could be case under DOS 2.x) mov ax,4300h ; see if XMS is installed int 2Fh cmp al,80h ; did XMS respond? jnz no_XMS_driver mov ax,4310h ; if XMS present, get its entry point int 2Fh mov word ptr xms_entry,bx mov word ptr xms_entry+2,es ; and store entry point for call pop es clc ret no_XMS_driver: pop es stc ret get_XMS_entry endp ;----------------------------------------------------------------------- ; Get an Upper Memory Block from the XMS driver; depending on the ; installation flags, this block will either be the first one available ; or the one closest in size to the requested amount ; ; entry: AX = number of paragraphs needed ; exit: AX = segment of UMB or 0000h if unable to allocate one ; allocate_UMB proc near mov dx,ax ; remember amount of memory to alloc call get_XMS_entry jc no_XMS_avail test install_flags,BEST_FIT jnz alloc_bestfit_UMB ; DX = amount to request alloc_XMS: call alloc_UMB ; ask XMS for the memory mov ax,bx ; (BX -> UMB if successful) je allocate_UMB_done ; if we got the mem, return now no_XMS_avail: xor ax,ax ; return segment 0 if no UMB allocate_UMB_done: ret allocate_UMB endp alloc_bestfit_UMB proc near push si push di @alloc_size = SI @umb_addr = DI mov @alloc_size,dx ; remember how much to request mov dx,0FFFFh ; try 1 meg call alloc_UMB ; ask XMS for the memory je XMM_broken ; if we got it, XMM seriously broken! cmp dx,@alloc_size ; DX = largest available jb UMB_too_small ; not enough high memory left call alloc_UMB ; allocate the largest UMB jne XMM_broken ; if we didn't get it, XMM broken mov @umb_addr,bx ; remember UMB address mov dx,@alloc_size call alloc_bestfit_UMB ; recurse or ax,ax jnz got_block mov ah,11h ; deallocate UMB mov dx,@umb_addr call XMS mov dx,@alloc_size call alloc_UMB ; ask XMS driver for the memory jne UMB_too_small ; did we get it? mov ax,bx ; BX = addr of UMB jmp short alloc_best_done got_block: push ax ; remember address to return mov ah,11h ; deallocate UMB mov dx,@umb_addr call XMS pop ax ; retrieve return value jmp short alloc_best_done XMM_broken: UMB_too_small: xor ax,ax ; didn't get anything alloc_best_done: pop di pop si ret alloc_bestfit_UMB endp ;----------------------------------------------------------------------- ; entry: nothing ; exit: CF set if not available, clear if available ; AX,BX,CX,DX destroyed ; if available, DOS5 UMBs have been linked into the memory chain ; check_if_DOS5_UMBs proc near mov ax,5800h int 21h ; get current allocation strategy mov alloc_strat,ax ; and remember it for later restore mov ax,5802h ; get current state of UMB linkage int 21h mov link_state,al mov ax,3000h ; get DOS version int 21h cmp al,5 ; DOS 5.0 or higher? jb no_DOS5_UMBs cmp al,10 ; but make sure not OS/2 penalty box jae no_DOS5_UMBs mov ax,2B01h mov cx,4445h mov dx,5351h int 21h ; check if DESQview running cmp al,0FFh ; if yes, no UMB's to be allocated jne no_DOS5_UMBs mov ax,5803h mov bx,1 ; try to link in UMBs int 21h mov ax,5802h ; get new link state int 21h cmp al,1 jne no_DOS5_UMBs clc ; yes, we have UMBs ret no_DOS5_UMBs: stc ret check_if_DOS5_UMBs endp ;----------------------------------------------------------------------- ; ; entry: BX = memory allocation strategy ; exit: CF set on error ; CF clear if successful ; AX = segment of memory block ; alloc_DOS_highmem proc near mov ax,5801h ; set allocation strategy int 21h mov ah,48h ; allocate memory mov bx,resident_size ; this is how much we need int 21h ; try to allocate the UMB pushf ; remember whether we succeeded jc no_highmem ; did we succeed? push ax ; yes, so remember where to relocate dec ax ; address the MCB for our new memory mov es,ax ; block inc ax ; back to relocation segment mov word ptr es:[1],ax ; make the memory block own itself mov ah,51h ; get current PSP int 21h dec bx ; back to MCB for main memory block push ds mov ds,bx ; point at MCB ASSUME DS:NOTHING push si push di mov si,8 mov di,si cld movsw ; copy the DOS 4.0+ program name into movsw ; the new memory block's MCB movsw movsw pop di pop si pop ds ASSUME DS:NOTHING pop ax ; retrieve relocation address ;--------------------------- ; Reasons for mucking with the MCB: ; DOS 5 will release any memory blocks owned by the program when ; it exits without going TSR, even if the blocks are in high memory ; and high memory has been disconnected from the memory chain. So, ; we need to change the owner field such that DOS thinks it belongs to ; somebody else and doesn't release it when we exit. ;--------------------------- no_highmem: popf ; get back whether we were successful restore_link_state: pushf ; store flags, especially CF push ax mov ax,5801h mov bx,alloc_strat ; restore allocation strategy int 21h mov ax,5803h ; and restore UMB link status mov bh,0 mov bl,link_state int 21h pop ax popf ; get back flags ret alloc_DOS_highmem endp ;----------------------------------------------------------------------- ; entry: AL = flags ; bit 0 = use first-fit alloc, nonzero = use best-fit alloc ; bit 1 = use UMB only, never conventional memory ; bit 2 = use top of lower memory (at 640K) ; bit 7 = patch resident portion's PSP return value ; AH = multiplex number ; BX = segment of resident code ; CX = size of resident code in paragraphs ; DX = additional paragraphs ; exit: CF clear if successful ; AX = segment at which TSR was installed ; CF set on error ; public $install_TSR $install_TSR proc DIST push es push si push di mov word ptr install_flags,ax ; set both install_flags & mpx_number push bx ; remember segment of resident code push cx ; remember size of resident code mov ax,cx add ax,dx mov resident_size,ax ; ; first, see if we can load into a DOS5 UMB (this is preferred because ; 386MAX will give us an XMS UMB even if DOS5 has grabbed them, but at ; a cost of an extra 80 bytes of overhead). ; test install_flags,LOW_ONLY jnz not_XMS call check_if_DOS5_UMBs ; check if UMBs avail, and link them in jc not_dos5 mov bx,40h ; alloc high memory only, first-fit test install_flags,BEST_FIT jz go_allocate_DOS_highmem inc bx ; BX <- 41h = alloc high only, best-fit go_allocate_DOS_highmem: call alloc_DOS_highmem jnc relocate_TSR_code ; if successful, go install ; ; if not DOS5, see if we can load into an XMS upper memory block ; not_dos5: mov ax,resident_size call allocate_UMB ; try to get AX paragraphs or ax,ax ; did we get a UMB? jnz relocate_TSR_code ; if yes, go install at segment AX ; ; if not XMS, see whether we are allowed to load into conventional memory; ; if yes, check whether we are supposed to load at the high or low end of ; conventional memory ; not_XMS: test install_flags,UMB_ONLY jnz install_failure install_low: test install_flags,USE_TOPMEM jz not_topmem mov bx,2 ; last-fit in low memory call alloc_DOS_highmem ; try to allocate at top of memory jnc relocate_TSR_code ; and go install install_failure: pop cx ; clean up stack pop bx jmp install_failed ; ; as a last resort, use our own PSP to store the code, and go resident ; not_topmem: mov ax,cs add ax,4 ; copy to offset 40h in PSP push ax ; remember where we'll relocate add resident_size,4 mov exit_func,31h ; TSR rather than normal exit xor ax,ax IFDEF __TINY__ xchg ax,cs:[002Ch] ; get and zero environment segment ELSE mov es,__psp xchg ax,es:[002Ch] ; get and zero environment segment ENDIF mov es,ax mov ah,49h ; since we will be going resident, int 21h ; discard our environment pop ax ; get back destination segment ; ; relocate TSR code into the PSP or UMB ; at this point, AX must be the segment at which to relocate ; relocate_TSR_code: pop cx ; get back TSR code size in paragraphs pop bx ; get back TSR code segment push ds mov ds,bx ASSUME DS:NOTHING mov es,ax ; ES -> resident_seg ASSUME ES:NOTHING xor si,si xor di,di mov ax,16 ; bytes per paragraph mul cx ; get size in bytes or dx,dx jnz install_failed_pop ; can only handle 64K at this time mov cx,ax ; number of bytes to copy cld rep movsb ; copy the TSR's code mov al,mpx_number ; patch the multiplex number in the mov es:[multiplex_number],al ; resident code test install_flags,PATCH_RESIDENT jz install_no_patch mov ax,es ; AX <- resident_seg cmp exit_func,4Ch je install_patch IFDEF __TINY__ mov ax,cs ELSE pop ds ; restore DS mov ax,__psp push ds ; need DS on stack ENDIF install_patch: mov word ptr es:[ALTMPX$PSP],ax install_no_patch: push es ; remember resident segment push es pop ds ; DS -> resident_seg mov si,offset RESIDENT_CODE:HOOKED_INT_LIST hook_interrupts: lodsb ; get interrupt number mov ah,35h ; get interrupt vector int 21h mov dx,bx ; ES:DX -> prev handler mov bx,[si] ; get offset of interrupt handler inc si inc si mov [bx+2],dx ; set 'previous' pointer in ISP header mov [bx+4],es mov dx,bx ; DS:DX -> our handler mov ah,25h ; AL still interrupt number int 21h ; hook the interrupt cmp al,2Dh ; INT 2Dh is last in hook list jne hook_interrupts pop ax ; AX <- resident_seg pop ds ; clc ; we were successful ;(CF already clear) install_TSR_done: pop di pop si pop es ret install_failed_pop: pop ds install_failed: stc ; signal installation failure jmp install_TSR_done $install_TSR endp ;----------------------------------------------------------------------- ; entry: AX = segment of TSR code within the calling executable ; exit: CF clear if successful ; CF set on error ; AX,BX,CX,DX destroyed ; public $uninstall_TSR $uninstall_TSR proc DIST push es call check_if_installed jnc not_installed ; ; TSR is installed, AH=multiplex number ; mov mpx_number,ah ; ; first, see whether the TSR can uninstall itself ; mov al,2 mov dx,cs ; load return address for success mov bx,offset _TEXT:uninstall_successful int 2Dh cmp al,0FFh ; successful? je uninstall_successful cmp al,02h ; will uninstall itself je uninstall_successful cmp al,01h ; unable to remove at this time? je uninstall_failed cmp al,05h ; unknown return code? jae uninstall_failed ; ; TSR said it is safe to uninstall, but not able to do so itself, ; so now we find out which interrupts it has hooked ; mov es,bx ; point ES at memory block to be freed ASSUME ES:NOTHING uninst_chk_int_loop: mov ah,mpx_number mov al,4 mov bl,0 ; start with INT 00h int 2Dh cmp al,1 ; function unsupported or can't determine? jbe uninstall_failed cmp al,4 je go_uninstall ; jmp short uninstall_failed ; sorry, can't handle returns 02h/03h yet uninstall_failed: not_installed: pop es ; clean up stack stc ; indicate error ret go_uninstall: push ds push si mov ds,dx ; DS:SI -> hook list mov si,bx call unhook_interrupts pop si pop ds jc uninstall_failed mov ax,es ; get segment of memory block cmp ax,0B000h ; regular DOS memblk if below video jae uninstall_highmem mov ah,49h ; free memory block int 21h uninstall_successful: pop es ; clean up stack clc ; indicate success ret uninstall_highmem: call check_if_DOS5_UMBs ; check if UMBs, and link them in jc uninstall_XMS mov ah,49h ; free the memory block via DOS int 21h ; (ES already points at block) call restore_link_state jmp uninstall_successful uninstall_XMS: call get_XMS_entry jc uninstall_failed ; no XMS driver!?!?! mov ah,11h ; release UMB mov dx,es ; set DX to UMB segment call XMS jne uninstall_failed ; we deallocation successful? jmp uninstall_successful $uninstall_TSR endp ;----------------------------------------------------------------------- _TEXT ENDS END