;:: EliCZ's automatic compilation - Rename to BAT and let it compile itself ;@echo OFF ;goto Compile ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; CALC 1.1 (C) Douglas Boling at PC Magazine, 1988 ;; ;; (H)acked by CyberRax the Hacking One , 2000 - 2001 ;; ;; An improved version which uses some 286 instructions, is Alternate ;; ;; Multiplex Interrupt Specification (AMIS, defines Int2Dh as a ;; ;; replacement for standard multiplex interrupt 2Fh) 3.5+ compatible, ;; ;; uses Ctrl+Alt+S as hotkey instead of Alt+S, (optionally) uses XMS to ;; ;; reduce requirements of conventional memory and is able to unload ;; ;; itself ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Calc for the IBM Personal Computer - 1987 by Douglas Boling .286 ;use 286-instructions bios_data segment at 40h ;BIOS data area org 17h bios_kbd_stat db ? ;keyboard status byte org 4ah bios_crt_col dw ? ;number of columns on the screen org 63h addr_6845 dw ? ;6845 Index Register address org 84h bios_crt_row db ? ;number of rows bios_data ends code segment para public 'code' assume cs:code org 100h entry: jmp initialize ;jump to initialization code interrupt_hook_list db 9 ;int hook list, for AMIS jump_1 dw offset isp_9h int_hook_list_2 db 2Dh jump_2 dw offset isp_2Dh adapter db 2 ;0 = CGA, 1 = MDA, 2 = EGA num_vid_col dw ? ;number of columns on screen num_vid_rows db ? ;number of columns on the screen v_segment dw ? ;video segment address v_page db ? ;current video page border_attr db ? ;window border attribute text_attr db ? ;window text attribute header_attr db ? ;window header attribute window_row db 3 ;row of left corner of window window_column db 10 ;column of left corner of window column_adj dw ? ;screen adjust for index registers old_cursor_pos dw ? ;row and column of screen cursor my_cursor_pos dw ? ;cursor position base_flag dw 10 ;numeric base pending_op dw 3dh ;pending operation (3d = nop) fixed_flag db 0 ;indicates fixed math mode decimal_flag db 0 ;flag for decimal point entry entry_pres db 0 ;entry reg nonempty flag entry_reg_high dw 0 ;most significant 16 bits entry_reg_low dw 0 ;least sig. 16 bits for entry num result_reg_high dw 0 ;most sig. 16 bits of result reg result_reg_low dw 0 ;least sig. 16 bits sign db 0 base_ptr: db "Hex Binary Octal Decimal Fixed " number_label db "hbod" op_table: db " and or xor err" old_kbd_status db ? screen_buffer dw offset initialize ;pointer to screen buffer area enabled db 1 ;TSR enabled or disabled active db 0 ;status of interrupt routine xms_handle dw 0 ;handle for allocated XMS block multiplexnumber db 0 ;AMIS-number for communication popup_req db 0 ;popup-reqest marker addition db 6 ;marker for base change xms_offset dw 0 ;offset of XMS driver xms_segment dw 0 ;segment of driver mono_values dw 0b000h ;segment db 70h ;border db 07h ;text db 07h ;header color_values dw 0b800h ;segment db 0fh ;border db 1fh ;text db 1eh ;header enable_values db 2Ch,28h,2Dh,29h ;values to enable CGA display db 2Ah,2Eh,1Eh header_text db "Calculator Base: Esc = Quit" help_text1: db "f1 base f3 and f5 xor f7 sal f9 +/-" help_text2: db "f2 fixed f4 or f6 not f8 sar f10 clr" ;----------------------------------------------------------------------------- ;Front-end routine for the keyboard interrupt handler. Execution is vectored ;here whenever an interrupt 9 is generated by the PC keyboard. ;----------------------------------------------------------------------------- retf_1: db 0CBh ; Interrupt Sharing Protocol for Interrupt 9h ; Required data block to be AMIS compatible isp_9h db 0EBh,10h ;jmp short main old_int_9h label dword ;old interrupt vector old_keyboard_int dw 2 dup (0) dw 424Bh db 80h db 0EBh,0F4h db 7 dup (0) main proc near assume cs:code,ds:nothing,es:nothing,ss:nothing pushf cmp cs:enabled,0 ;We're disabled? je quick_out ;Yes, so exit cmp cs:active,0 ;Calc currently active? jne quick_out ;yes, exit. sti ;no, start initialization, first pusha ; enable interrupts and save push ds ; registers push es in al,60h ;get scan code from keyboard cmp al,31 ;check for 'S' key jne out1 ;no, then exit. mov ah,2 ;check shift keys int 16h and al,00001100b cmp al,00001100b ;check Ctrl and Alt key je main1 ;yap, pop up. out1: pop es ;Restore registers and pass pop ds ; control to original vector popa quick_out: popf jmp cs:old_int_9h ; Start of 'real' code. Clean up interrupt state and check video mode. main1: call kb_reset ;The hot key combination has been push cs ; pressed, spring into action by pop ds ; resetting the keyboard assume ds:code ; interrupt, and setting the mov ax,bios_data ; data segment register. mov es,ax ;Set es for bios segment. Then assume es:bios_data ; grab significant data. mov ax,es:bios_crt_col ;save the number of columns mov num_vid_col,ax mov al,es:bios_crt_row ;save the number of rows mov num_vid_rows,al mov al,es:bios_kbd_stat ;save the state of the keyboard mov old_kbd_status,al or es:bios_kbd_stat,20h ;set num-lock on mov ah,12h ;Check for EGA by testing video mov bl,10h ; function 12h. If bl returns int 10h ; unchanged, then EGA is not cmp bl,10h ; present. jne main110 mov adapter,0 ;not EGA, assume CGA mov num_vid_rows,24 ;No ega must be 25 rows. mov ax,es:addr_6845 test al,40h jnz main110 ;if the bit is 1 then its a MDA inc adapter main110: push cs ;Set es to same segment as code. pop es assume es:code mov ah,15 ;Get current video mode. int 10h cmp al,3 ;is current video mode 0 - 3 ? jle main2 ;yes, then branch cmp al,7 ;is current video mode 7? je main111 ;yes, then branch. else, exit. done: ; Restore the state of the keyboard mov al,old_kbd_status ;get old key status and al,20h ;isolate numlock cmp al,0 ;was numlock on or off? jnz done_1 ;it was on - no change needed mov bx,bios_data mov ds,bx assume ds:bios_data and ds:bios_kbd_stat,not 20h ;write to keyboard flags byte done_1: cmp cs:popup_req,1 ;was this popup requested by Int2D jne really_exit ; handler? ret ;if yes then return to it really_exit: pop es ;else restore registers pop ds popa popf iret ;and exit with an interrupt return assume cs:code, ds:code, es:code main111: mov di,offset mono_values ;set monochrome attributes jmp short main3 main2: mov di,offset color_values ;set color attributes main3: mov ax,[di] mov v_segment,ax ;set video segment and attributes mov ax,2[di] mov border_attr,al mov text_attr,ah mov al,4[di] mov header_attr,al mov ax,num_vid_col ;Compute the column adjustment cmp ax,80 ; value from the number of jl done ; columns on the screen. If the sal ax,1 ; number of columns in the sub al,88 ; screen is < 80, don't pop up. mov column_adj,ax mov v_page,bh ;Save video page. mov ah,3 ;Get cursor position, save it, int 10h ; then position the cursor to mov old_cursor_pos,dx ; one row below the screen so mov dh,num_vid_rows ; that it will be hidden. add dh,2 mov dl,0 mov ah,2 int 10h mov active,1 ;indicate calc is active. call set_cursor mov di,screen_buffer ;Save the screen where the xor ax,ax ; window will be. Then, pop up call screen_ops ; the window. mov bl,0 ;Display the contents of the cmp entry_pres,0 ; of the entry register if je main32 ; something is in it, else inc bl ; display the result register. main32: call display_reg ;The window is now displayed on the screen. Wait for a keypress. main4: mov dx,my_cursor_pos ;Get the position of my 'cursor'. mov al,"<" ; then write it to the screen. mov ah,header_attr call output_char call display_base ;indicate dec, hex, oct, bin, fix main41: mov ah,0 ;get a keypress by first executing int 28h ; a dos idle loop, to let other mov ah,1 ; programs have a chance, then int 16h ; checking for a key before jz main41 ; actually getting the key. If mov ah,0 ; no key, idle again. int 16h main5: cmp al,27 ;ESC key pressed? je end_it ;yes, clean up and exit cmp al,08 ;is it a backspace jne main6 call back_space ;yes, process backspace jmp short main4 main6: cmp ah,93 ;is it shift f10 ? je main7 ;yes, jump to clear routine. cmp ah,59 ;is it f1 ? je main9 ;yes, change base cmp ah,84 ;is it shift+f1? je main9_0 ;set marker and then to changing cmp ah,60 ;is it f2 ? je main9 ;yes, change base cmp ah,64 ;is it between f6 and f10 ? jl main8 ; If so, call unary operator cmp ah,68 ; routine jg main8 main7: call un_proc jmp short main4 main8: call key_proc jmp short main4 main9_0: mov addition,0FEh main9: call base_chang ;f1 or f2, change base jmp short main4 ;goto end ; Escape key has been pressed, clean up and exit. end_it: mov di,screen_buffer ;point DI to holding buffer mov al,1 call screen_ops ;restore video memory contents mov dx,old_cursor_pos ;Restore the cursor to the mov bh,v_page ; position it was before calc mov ah,2 ; was called. int 10h mov active,0 ;reset status flag jmp done ;exit main endp ;----------------------------------------------------------------------------- ; process keys - Process a all keys but esc, f1, f2, and f6 - f10 ;----------------------------------------------------------------------------- key_proc proc near cmp al,0 ;see if extended key jne key0 cmp ah,61 ;if extended, allow only jl key_end ; keys f3 - f10 cmp ah,68 jg key_end jmp short key_oper key0: cmp al,30h ;is it lower than 0 jl key2 ;yes, check for other functions mov bx,base_flag ;get base cmp bl,10h ;see if in hex je key1 ;if so goto hex mode checking or bl,30h ;convert to ascii cmp al,bl ;is number less then the base? jl key_num ;yes, goto number processing jmp short key2 ;no, check for other characters ;Check for a-f if in hex mode key1: cmp al,39h ;see if number less than 10 jle key_num mov bl,al ;copy ascii character and bl,0dfh ;make lower and upper case same cmp bl,41h ;is it lower than a ? jl key2 ;yes, see if other character cmp bl,46h ;no, is it greater then an f ? jg key2 ;yes, check for other characters key_num: call number_key ;no, it must be a number jmp short key_end ;get another keypress ;Check for math functions key2: cmp al,2ah ;if ascii code between 2a and jl key3 ;2f then its a *+,-. or / cmp al,2fh jg key3 cmp al,"," ;see if its a comma that je key_end ; slipped through, if so ret. cmp al,"." ;is it a decimal point? jne key_oper mov decimal_flag,1 ;set flag jmp short key_end key3: cmp al,"%" ;see if other mod operator je key_oper cmp al,13 ;see if enter was pressed je key4 ;yes, process enter cmp al,"=" ;see if = was pressed jne key5 ;no, check for other keys key4: mov al,"=" ;yes, clear enter code call oper_proc ;complete last math operation mov bl,0 ;display result register call display_reg ;call display register jmp short key_end ;get a keypress key5: cmp al,"\" ;see if mod operation jne key_end key_oper: call oper_proc ;call the math procedure key_end: ret ;end key_proc endp ;----------------------------------------------------------------------------- ; process base changes - Change the value in the base flag, then display reg. ;----------------------------------------------------------------------------- base_chang proc near cmp ah,60 ;see if changing to fixed je base_fixed cmp fixed_flag,1 ;if in fixed mode, only change je base_fixed ; to decimal ;Next we get the base flag and use ; following formula to calculate ; new base: ; base + 110b and 1011b, i.e ; add 6 and clear the third bit ; thus getting 2, 8, 10 or 0, ; the last one means 16 so AL ; must be set to it. Next formula ; reverses the conversion: ; base + 100b - 110b and 1011b ; We incorporate both cases into ; one: base + X and 1011b, ; so if X is 6 then the change is ; BIN => OCT => DEC => HEX but ; as 100b - 110b = ca. 11111110b ; then by X = FEh changing goes ; HEX => DEC => OCT => BIN mov ax,base_flag add al,addition mov addition,6 ;BTW, we must restore the marker and al,0bh jnz base_c1 add al,16 base_c1: mov base_flag,ax jmp short base_end base_fixed: mov base_flag,10 ;Make sure base = 10 then cmp fixed_flag,0 ; if already in fixed, make je base_fixed1 ; integer by dividing by 100. mov fixed_flag,0 ; If in integer decimal, make mov decimal_flag,0 ; room for the fraction by mov ch,01 ; multiplying by 100. mov di,offset result_reg_high call fixed_adjust mov di,offset entry_reg_high call fixed_adjust jmp short base_end base_fixed1: inc fixed_flag ;set fixed flag xor cx,cx mov di,offset result_reg_high call fixed_adjust mov di,offset entry_reg_high call fixed_adjust base_end: mov bl,0 ;display result register unless cmp entry_pres,0 ; there is a number in the je base_end1 ; entry register. inc bl ;change register flag for the base_end1: call display_reg ; display routine. ret base_chang endp ;----------------------------------------------------------------------------- ; mulitply and divide registers by 100 ; entry: ch = 1, divide. ch = 0, multiply. ;----------------------------------------------------------------------------- fixed_adjust proc near ;Moving from fixed to decimal push bx ; and back again needs an easy mov bx,100 ; way to multiply and divide by call mul_div_shrt ; 100. pop bx ret ;end fixed_adjust endp ;----------------------------------------------------------------------------- ; mulitply and divide registers by bx ; entry: ch = 1, divide. ch = 0, multiply. ;----------------------------------------------------------------------------- mul_div_shrt proc near ;Moving from fixed to decimal mov cl,sign push cx mov sign,0 ; and back again needs an easy ; way to multiply and divide by cmp base_flag,10 ; 100. The math routines also jne adj3 ; need this routine to correct cmp word ptr [di],0 ; the decimal point on multiplys jge adj3 ; and divides. call negate_reg inc sign ;If the register we are converting adj3: mov ax,[di] ; is negitive, change its sign ; by negating it. cmp ch,0 ;For divide, get the high word, je adj4 ; convert it to a double word, xor dx,dx ; perform the first divide. Save div bx ; the result, and use the mov [di],ax ; remainder as the upper word mov ax,2[di] div bx ; of the lower word divide. jmp short adj5 adj4: mul bx ;For multiply, multiply the high mov [di],ax ; word, then the low, and add the mov ax,2[di] ; upper 16 bits of the second mul bx ; product to the first product. add [di],dx adj5: mov 2[di],ax ;At the end recall if the register cmp sign,0 ; as negitive on entry. If so, je adj6 ; negate the result to return call negate_reg ; the register to its orginal adj6: pop cx ; sign. mov sign,cl ret mul_div_shrt endp ;----------------------------------------------------------------------------- ;process unary operations. ;----------------------------------------------------------------------------- un_proc proc near cmp entry_pres,0 ;set up di to point to the je un1 ; proper register. mov di,offset entry_reg_high jmp short un2 un1: mov di,offset result_reg_high un2: cmp ah,64 ;is it f6 ? jne un3 ;no, check for other keys not word ptr [di] ;not register not word ptr 2[di] jmp short un_end un3: cmp ah,65 ;is it f7, left shift ? jne un4 mov bl,0 jmp short un31 un4: cmp ah,66 ;is it f8, right shift ? jne un5 mov bl,1 un31: call shift_reg jmp short un_end un5: cmp ah,68 ;is it f10 ? jl un6 ;no, check for other keys xor dx,dx mov di,offset entry_reg_high mov [di],dx ;Clear both registers mov 2[di],dx mov 4[di],dx mov 6[di],dx cmp ah,93 ;do we clear the screen? jne un_end mov cx,7 ;yes, do so by scrolling the un50: call scroll_window ; window up 7 lines. loop un50 jmp short un_end un6: cmp ah,67 ;is it f9 ? jne un_end ;no, check for other keys call negate_reg ; negate the result register. un_end: mov bl,entry_pres call display_reg ;display register ret un_proc endp ;----------------------------------------------------------------------------- ;process numbers - insert digits from the keyboard into the entry register. ;----------------------------------------------------------------------------- number_key proc near cmp al,39h ;see if a number not a - f jle num1 ;yes, skip letter conversion add al,9 ;convert letters to hex values num1: and ax,000fh ;strip off ascii code push ax ;save digit mov sign,ah ;ah = 0, clear sign flag mov di,offset entry_reg_high cmp base_flag,10 jne num10 cmp word ptr [di],0 ;Check if the entry register is jge num10 ; negitive, if so, negate it. call negate_reg inc sign num10: pop ax ;get the digit back xor dx,dx cmp fixed_flag,0 ;See if in fixed mode. je num2 mov cl,decimal_flag cmp cl,2 ;If were in the fix point mode, je num11 ; multiply all digits by 100 jg num20 ; until the decimal flag has mov bx,10 ; been set. Then accept two mul bx ; fraction digits shifting only cmp cl,1 ; the tenths digit. je num11 ; entered. mul bx num11: cmp cl,0 je num2 inc decimal_flag mov bx,2[di] jmp short num4 num2: mov bx,ax mov ax,[di] ;get upper 16 bits cmp base_flag,10 je num210 mul base_flag jmp short num211 num210: imul base_flag ;make room for the new digit num211: jc num21 mov [di],ax ;return high word mov ax,2[di] ;get low 16 bits mul base_flag ;repeat shift to make room num4: add ax,bx ;add in new digit adc dx,0 add [di],dx ;add overflow to the high 16 bits num42: mov 2[di],ax ;store lower 16 bits ; if a new number, scroll window before displaying cmp sign,0 ;Before displaying, see if the je num20 ; register was orginally negitive call negate_reg ; if so, negate it. num20: cmp entry_pres,0 ;Check for a number in the entry jne num21 ; register or a pending operation mov bx,pending_op cmp bl,"=" jne num21 call scroll_window num21: mov bl,1 ;display entry register call display_reg inc entry_pres ;entry reg contains a number num3: ret number_key endp ;----------------------------------------------------------------------------- ;write_at_cursor - keeps track of the cursor and calls output character ;Entry: al - ascii value of characher to be displayed ;----------------------------------------------------------------------------- write_at_cur proc near push ax ;save registers ax and dx push dx mov dx,my_cursor_pos ;get cursor position mov ah,text_attr ;use proper display attribute call output_char ;else, display character inc dl ;move cursor over mov my_cursor_pos,dx ;store new cursor position wac1: pop dx pop ax ;same with ax ret ;done. write_at_cur endp ;----------------------------------------------------------------------------- ;oper_proc - processes all operators for the program ;Entry: ax - non-numeric keystroke ;----------------------------------------------------------------------------- oper_proc proc near push ax ;save the key cmp entry_pres,0 ;is there a number in the ent reg? jne oper1 ;yes, perform operation mov pending_op,ax ;no, save operation and exit xor cx,cx jmp short operend oper1: xchg pending_op,ax ;get pending op and save new op call math_proc ;process math function operend: pop dx cmp cx,0 je oper4 mov dx,cx oper4: cmp dl,0 ;see if extended code je operlog mov al," " ;print space to seperate operator call write_at_cur mov ax,dx ;get pending operation call write_at_cur ;display operator oper5: call scroll_window operend1: xor bx,bx ;clear bx mov entry_reg_high,bx ;clear entry register mov entry_reg_low,bx mov decimal_flag,bl mov entry_pres,bl operexit: ret ;done. operlog: mov cx,dx sub ch,61 ;convert keycode into offset sal ch,1 sal ch,1 mov bx,offset op_table add bl,ch ;point to correct label mov cx,4 ;display a space and 3 characters operlog1: mov al,[bx] ;get a character to display call write_at_cur inc bx loop operlog1 jmp short oper5 oper_proc endp ;----------------------------------------------------------------------------- ;math_proc - processes all math for the program ;Entry: al - ascii value of the operation or 0 for result reg = entry reg ;----------------------------------------------------------------------------- math_proc proc near mov bx,ax ;copy key press mov dx,entry_reg_high ;get result register mov ax,entry_reg_low ; mov di,offset result_reg_high xor cx,cx ;clear error flag cmp bx,0 je math02 cmp bl,0 jne math01 jmp short logic_start math01: cmp bl,"+" ;see if addition je math1 cmp bl,"-" ;see if subtraction je math2 cmp bl,"*" ;see if multiplication je mathmd cmp bl,"/" ;see if division je mathmd cmp bl,"%" je mathmd cmp bl,"\" ;see if mod operation je mathmd math02: mov [di],dx ;If operator not recognized, mov 2[di],ax ; copy entry register to jmp short mathend math1: add 2[di],ax ;add adc [di],dx jo matherr math11: jmp short mathend math2: sub 2[di],ax ;subtract sbb [di],dx jo matherr ;jump to error if underflow jmp short mathend mathmd: call mul_div mathend: ret matherr: mov cx,4000h ;point to error tag ret logic_start: cmp bh,61 ;see if and operation jne logic1 ;no, look some more and [di],dx ;perform and and 2[di],ax jmp short logic_end ;print op and end logic1: cmp bh,62 ;see if or operation jne logic2 ;no, look some more or [di],dx ;do or or 2[di],ax jmp short logic_end ;print op and end logic2: cmp bh,63 ;see if xor operation jne logic_end ;give up looking xor [di],dx ;do xor xor 2[di],ax logic_end: xor cx,cx ;clear error code ret math_proc endp ;----------------------------------------------------------------------------- ;Multiply and Divide - process multiply and divide math functions. ;----------------------------------------------------------------------------- mul_div proc near xor cx,cx ;zero cl mov sign,cl ;zero sign flag cmp word ptr [di],cx ;see if result reg is negative jge md1 ;no, skip negation call negate_reg inc sign ;set 1 negative number md1: mov si,offset entry_reg_high cmp word ptr [si],0 ;see if negative jge md2 ;no, skip negation push di mov di,si ;neg entry register call negate_reg pop di inc sign ;set 1 more negative number ; 2 positive numbers, now is mul or div ? md2: push bx ;save the operation cmp bl,"*" ;if not '*' it must be divide jne md6 ; or mod operation. md3: mov ax,[di] ;get result high mul word ptr [si] ;multiply by entry high mov cx,dx add cx,ax mov ax,2[di] ;get result low mul word ptr [si] ;multiply by entry high add cx,dx mov bx,ax ;start high word of product mov ax,[di] ;get result high mul word ptr 2[si] ;multiply by entry low add cx,dx add bx,ax ;continue to build final product mov ax,2[di] ;get result low mul word ptr 2[si] ;multiply by entry low add bx,dx ;complete upper 16 bits cmp cx,0 ;see if any overflow jne mderr cmp base_flag,10 ;If in base 10 protect the sign jne md55 ; bit by declaring an overflow cmp bx,0 ; if it has been changed. jl mderr md55: pop cx ;get the operation back jmp short mdend ;Division part of the routine. md6: cmp word ptr [si],0 jne md61 cmp word ptr 2[si],0 ;see if entry reg = 0 je mderr ; if so, indicate error. md61: cmp fixed_flag,0 ;If in fixed mode multiply the je md7 ; dividend by 100 to make room mov ch,0 ; for the fraction. call fixed_adjust md7: cmp word ptr [si],0 ;Now check to see that the je md8 ; divisor is not too big. mov bl,1 ; If it is, divide both registers push di ; by 2 until the high word of the mov di,si ; divisor is zero. call shift_reg pop di call shift_reg jmp short md7 md8: mov ax,[di] ;perform the divide. First the cwd ; high word, then the low one. idiv word ptr 2[si] mov bx,ax ;Store the high word result in bx mov ax,2[di] div word ptr 2[si] pop cx ;get operation back cmp cl,"/" ;see if division or mod operator je mdend mov ax,dx ;get remainder in in low word and xor bx,bx ; clear the high word. mdend: mov [di],bx mov 2[di],ax cmp fixed_flag,0 ;If in fixed mode, divide je mdend1 ; by 100 to correct fraction cmp cl,"*" jne mdend1 mov ch,1 call fixed_adjust mdend1: cmp sign,1 ;see if odd # of negative numbers jne mdend2 ;even number, multiply done call negate_reg ;di already pointing to result reg mdend2: xor cx,cx ;clear error flag ret mderr: pop bx ;clean off stack mov cx,4000h ret mul_div endp ;----------------------------------------------------------------------------- ;Shift register - shifts the register pointed to by di, 1 place. ;----------------------------------------------------------------------------- shift_reg proc near push ax push dx shift2: mov dx,[di] ;get the register pointed to by bx mov ax,2[di] cmp bl,0 ;see if left or right shift je shift4 ; if bl=0 then left shift shift3: sar dx,1 rcr ax,1 ;Shift 32 bits by shifting the jmp short shift5 ; trailing word first so that its shift4: sal ax,1 ; msb goes into the carry. Then rcl dx,1 ; shift the leading word. shift5: mov [di],dx mov 2[di],ax shift_end: pop dx pop ax ret ;done. shift_reg endp ;----------------------------------------------------------------------------- ;negate register - subtracts a register from zero. ;entry: di points to the register being negated. ;----------------------------------------------------------------------------- negate_reg proc near xor dx,dx ;clear dx negate2: neg word ptr 2[di] ;negate the register sbb dx,[di] mov [di],dx ret negate_reg endp ;----------------------------------------------------------------------------- ;back_space - remove last digit entered in entry reg by dividing by the base ;----------------------------------------------------------------------------- back_space proc near mov di,offset entry_reg_high mov bx,base_flag mov ch,1 cmp fixed_flag,0 je back_sp1 cmp decimal_flag,3 je back_sp1 call fixed_adjust cmp decimal_flag,2 je back_sp2 back_sp1: call mul_div_shrt back_sp2: cmp fixed_flag,0 je back_sp5 mov bx,100 cmp decimal_flag,3 jne back_sp3 mov bx,10 back_sp3: xor cx,cx call mul_div_shrt back_sp4: cmp decimal_flag,0 je back_sp5 dec decimal_flag ; display new entry register back_sp5: mov bl,1 ;display entry register call display_reg ;call display register back_end: ret ;done. back_space endp ;----------------------------------------------------------------------------- ;display_reg - writes a register to the screen at the cursor ;entry: bl = 0, write result reg. bl = 1, write entry register ;----------------------------------------------------------------------------- display_reg proc near mov cx,36 ;First erase what was here before sub my_cursor_pos,cx disp0: mov al," " call write_at_cur loop disp0 cmp bl,0 ;which register to display? jne disp1 ;bl = 0, display result register mov bx,offset result_reg_high jmp short disp2 disp1: mov bx,offset entry_reg_high disp2: mov si,2[bx] ;load register mov di,[bx] xor dx,dx ;clear dx mov ch,1 ;initialize digit counter mov sign,dl ;clear sign flag cmp base_flag,10 ;is this a decimal mode? jne disp_n6 ;no, skip signed display of number cmp di,0 ;see if negative number jge disp_n6 ;no, skip negation neg si ;yes, negate si,di sbb dx,di mov di,dx inc sign jmp short disp_n6 ;Insert digit seperators. disp_s1: inc ch cmp fixed_flag,0 ;see if in fixed mode je disp_n4 cmp ch,3 ;check for place to put decimal pt jne disp_n30 mov al,"." ;write decimal point jmp short disp_n51 disp_n30: mov al,ch ;Since we know were in fixed base, disp_n31: mov cl,3 ; set up for groups of 3. mov bl,"," ;separate decimal digits by a "," jmp short disp_n5 disp_n4: mov al,ch ;Depending on the base were in, dec al ; set up the number of digits to cmp base_flag,10 ; count between the separators. je disp_n31 ; For a base of 8 and 16 separate mov bl," " ; by 4, for binary, separate by 8 mov cl,4 cmp base_flag,2 jne disp_n5 sal cl,1 disp_n5: cbw ;Now that everything is set up, div cl ; check if a separator in needed cmp ah,0 ; by checking for a remainder of jne disp_n6 ; zero when dividing the digit mov al,bl ; count by the group number. disp_n51: call write_back ;Display digit. disp_n6: mov bx,base_flag ;After all this, its time to mov ax,di ; display the digit. Divide the xor dx,dx ; register to display by the base div bx ; for each of the digits. mov di,ax ;save high word quotent mov ax,si div bx mov si,ax ;save low word quotent mov ax,dx ;get remainder of division cmp al,10 ;convert remainder to ascii jl disp_n2 add al,37h jmp short disp_n3 disp_n2: or ax,30h disp_n3: call write_back ;display digit ;check to see if we have completed the display. cmp di,0 ;check for zero number in register jne disp_s1 cmp si,0 ;If the registers are zero, make jne disp_s1 ; sure that at least 1 zero has cmp fixed_flag,0 ; been displayed (3 for fixed.) je disp_end ; If so, exit loop. cmp ch,3 jl disp_s1 disp_end: cmp sign,0 ;If the number displayed was je disp_end1 ; negative, write a "-" before dec my_cursor_pos ; the number. mov al,"-" call write_at_cur disp_end1: call set_cursor ret display_reg endp ;This small routine keeps track of the cursor when writing backwards. write_back proc near dec my_cursor_pos call write_at_cur dec my_cursor_pos ret write_back endp ;----------------------------------------------------------------------------- ;Display Base - Show what base the calculator is in. ;----------------------------------------------------------------------------- display_base proc near mov dh,window_row ;get coordinates of window corner mov dl,window_column inc dh ;move down 1 row add dl,20 ;and over 20 columns mov bx,offset base_ptr ;load address of base label table mov ax,base_flag ;find out what base were in mov cl,al ;copy base sal cl,1 ;shift bits over 1 or al,cl and al,0ch ;look only at the bits we want push ax ;save number for later sal ax,1 cmp fixed_flag,1 ;see if fixed jne display_b1 add al,8 ;point to fixed label display_b1: add bx,ax mov cx,8 mov ah,header_attr display_b2: mov al,[bx] ;Display label by looping 8 times call output_char inc bx inc dl loop display_b2 pop bx ;Now display letter to label sar bl,1 ; the number currently being sar bl,1 ; displayed. mov al,number_label[bx] ;get letter from list mov ah,text_attr mov dx,my_cursor_pos sub dl,37 call output_char ret display_base endp ;----------------------------------------------------------------------------- ;screen ops - saves and restores the contents of the screen beneath the window. ;Entry: ES:DI - buffer address, ;----------------------------------------------------------------------------- screen_ops proc near push ds ;save es and ds. push es push ax ;save entry parameter cmp adapter,0 ;See if CGA, is so disable it jne screen_op1 ; before writing to the screen. call cga_off screen_op1: mov dh,window_row ;row and column of window corner mov dl,window_column mov bl,v_page ;get video page in BX mov si,di ;save buffer address call video_ptr ;get starting address of window mov bx,column_adj ;load index register adjust mov cx,v_segment ;get video segment to load into pop ax ; es or ds later. cmp al,1 ;al=0, screen save. al=1, restore. je screen_op2 ;Since this routine does both the assume ds:nothing ; screen save and the screen assume es:nothing ; restore, es:si and ds:di are xchg si,di ; set to their proper addresses. mov ds,cx ; Because of this, make NO jmp short screen_op3 ; assumptions on es and ds in screen_op2: mov es,cx ; this section of code. screen_op3: mov cx,12 ;12 lines to save cld ;clear DF screen_op4: push cx ;save line counter mov cx,44 ;44 words per line cmp cs:xms_handle,0 ;XMS not in usage? jne xms_move0 ;it is, so use our special code rep movsw ;transfer one line to buffer screen_op99: cmp al,1 je screen_op5 add si,bx ;adjust si for next line jmp short screen_op6 screen_op5: add di,bx ;adjust di for next line screen_op6: pop cx ;get line count loop screen_op4 ;loop until done pop es ;Restore the segment registers assume es:code pop ds assume ds:code cmp al,0 ;If we saved the screen, draw the jne screen_op7 ; calc window into the screen. call open_window screen_op7: cmp adapter,0 ;If CGA, enable it before jne screen_op8 ; returning. call cga_on screen_op8: ret screen_ops endp ;---------------------------------------------------------------------------- ;XMS Move - if we use extended memory then let the XMS driver move the bytes ; This procedure hides it's existance by seting everything like it would be ; without XMS Move. Through this we accomplice that there's no need to modify ; the original code which is used if no XMS is present or P switch was given ; This routine works by converting the parameters given to MOVSW into an EMB ; Move Block which resides in stack. Because of that the block must be created ; in reversed form ;---------------------------------------------------------------------------- xms_move proc near xms_move0: push ds ;Save registers which we push si ; modify ; We actually don't mess with BX, but if some error accoured ; during the XMS operation then the errorcode is returned in ; it. So, to be on the safe side, we back the register up. ; For the sake of codesize we don't check for XMS errors and ; assume that everything's always OK. ; A better behaving version would try to perform the move a ; second time and if it still fails would inform the user. push bx push ax mov bp,sp ;Save SP cmp al,1 je xms_move1 push 0 ;Destination segment jmp short xms_move2 xms_move1: push es xms_move2: push di ;Destination offset cmp al,1 je xms_move3 push word ptr cs:xms_handle ;Destination handle jmp short xms_move4 xms_move3: push 0 push 0 jmp short xms_move5 xms_move4: push ds ;Source segment xms_move5: push si ;Source offset cmp al,1 je xms_move6 push 0 ;Source handle jmp short xms_move7 xms_move6: push word ptr cs:xms_handle xms_move7: push 0 push 88 ;Number of bytes mov si,sp ;Block's address... push ss pop ds ;...into DS:SI mov ah,0bh call dword ptr cs:xms_offset;Call XMS driver mov sp,bp ;Restore stack... pop ax ;...and changed registers pop bx pop si pop ds add si,88 add di,88 jmp short screen_op99 xms_move endp ;----------------------------------------------------------------------------- ;Video ptr calculates the offset into the video memory orrsponding to the row, ; column, and video page passed to it in the registers given below. ;entry: dh,dl - row, column exit: di - offset ; bl - video page ;----------------------------------------------------------------------------- video_ptr proc near mov al,160 ;Compute the displacment by the mul dh ; following equation: sal dl,1 ; (row*160)+(col*2)+(page*1000) xor dh,dh add ax,dx mov di,ax mov ax,1000h xor bh,bh mul bx add di,ax ret video_ptr endp ;----------------------------------------------------------------------------- ;cga off routine to disable the CGA by writing to the mode select register ;----------------------------------------------------------------------------- cga_off proc near mov dx,3DAh ;Wait for the vertical retrace, cga_off1: in al,dx ; then disable the cga by test al,8 ; writing to the mode select je cga_off1 ; register. sub dx,2 mov al,25h out dx,al ret cga_off endp ;----------------------------------------------------------------------------- ;cga on routine to enable the CGA by writing to the mode select register ;----------------------------------------------------------------------------- cga_on proc near mov ah,15 ;Get the current video mode, int 10h ; then, using xlat as a table mov bx,offset enable_values ; lookup get the value needed xlat ; to enable the cga video. mov dx,3D8h ; Finally, write the value to the out dx,al ; MSR. ret cga_on endp ;----------------------------------------------------------------------------- ;KB_RESET resets the keyboard and signals end-of-interrupt to the 8259 ;----------------------------------------------------------------------------- kb_reset proc near in al,61h ;get current control port value mov ah,al ;save it in AH or al,80h ;set bit 7 out 61h,al ;send reset value mov al,ah ;get original value out 61h,al ;send it out to enable keyboard cli ;suspend interrupts mov al,20h ;get EOI value out 20h,al ;send EOI to 8259 sti ;enable interrupts ret kb_reset endp ;----------------------------------------------------------------------------- ;OUTPUT_CHAR writes the designated character directly to video memory. ;Entry: DH,DL - row, column ; AH,AL - attribute, character ;----------------------------------------------------------------------------- output_char proc near push di push dx push es mov es,v_segment assume es:nothing push bx push ax mov bl,v_page ;get page in BX call video_ptr ;calculate address to write to cmp adapter,0 ;is this a CGA? jne output3 ;no, then skip wait loop mov dx,3DAh ;get CGA Status Register address output1: in al,dx ;wait until horiz. retrace done test al,1 jne output1 cli ;suspend interrupts during write output2: in al,dx ;wait for next horizontal retrace test al,1 je output2 output3: pop ax ;get character and attribute stosw ;write them to video memory sti ;enable interrupts pop bx ;Restore registers pop es assume es:code pop dx pop di ret output_char endp ;----------------------------------------------------------------------------- ;OPEN_WINDOW draws the window border onto the screen. Character/attribute ;pairs are sent directly to video memory for fast display speed. ;----------------------------------------------------------------------------- open_window proc near mov dh,window_row ;get coordinates of window corner mov dl,window_column push es mov es,v_segment ;point ES to video buffer assume es:nothing cld ;clear DF for string operations mov bl,v_page ;get video page in BX xor bh,bh call video_ptr ;calculate starting address ;Write the top line of the window border to video. mov al,218 ;al = left end character mov ah,border_attr ;set attribute mov bl,196 ;bl = middle 42 characters mov dl,191 ;dl = right end character mov bh,ah ;copy the border attribute mov dh,ah call write_line ;Do the window header line. mov si,offset header_text ;point SI to text of line call write_header ;Now write the next 18 lines (no text) to the display. mov cx,6 ;6 lines to do mov al,179 ;do leftmost column mov ah,border_attr mov bl,32 ;do next 38 columns (blank) mov bh,text_attr mov dl,179 ;do rightmost column mov dh,ah open2: call write_line loop open2 ;loop until finished ;Write line to seperate the active screen from the help text mov al,195 ;lower left corner mov bl,196 ;line character mov bh,text_attr mov dl,180 call write_line ;Write the help lines. mov si,offset help_text1 ;point SI to text of line call write_header mov si,offset help_text2 ;point SI to text of line call write_header ;Finish things up by writing the last line. mov al,192 ;lower left corner mov bl,196 ;line character mov bh,ah ;copy header attribute mov dl,217 call write_line pop es assume es:code ret open_window endp ; routine to write a line of text to the window write_header proc near mov al,179 ;left window border character stosw ;write it mov cx,42 ;42 characters to write mov ah,header_attr ;use header attribute for these open1: lodsb ;get the text character stosw ;write char/attr pair to video loop open1 ;repeat for all 28 mov ah,border_attr ;do rightmost column mov al,179 stosw add di,column_adj ;adjust DI for next line ret write_header endp ;Write a line of characters to the window write_line proc near push cx stosw ;write left end character mov cx,42 ;next 42 characters mov ax,bx ;get middle characters from bx rep stosw mov ax,dx ;get right end character from dx stosw add di,column_adj ;adjust DI for next line pop cx ret write_line endp ;----------------------------------------------------------------------------- ;Scroll window up - scroll window up 1 line. ;----------------------------------------------------------------------------- scroll_window proc near push ax ;save ax push cx mov al," " ;First remove cursor by writing mov ah,text_attr ; a space over it. call write_at_cur mov ch,window_row ;put upper left corner in CX add ch,2 mov cl,window_column inc cl mov dx,cx ;put lower right corner in DX add dh,5 add dl,41 mov ah,6h ;bios scroll up function mov al,1 ;scroll 1 line mov bh,text_attr ;fill the line with blanks int 10h ;call bios call set_cursor pop cx pop ax ret ;done scroll_window endp set_cursor proc near mov dl,window_column ;Set up my own cursor location mov dh,window_row add dl,38 add dh,7 mov my_cursor_pos,dx ret set_cursor endp signature db 'PC Mag ' ;Signature for compairing db 'CALC ' db 'resident calculator (Ctrl+Alt+S)',0 hotkey_list db 10000000b ;Hotkey list, an AMIS toy db 1 db 31 dw 0000000000001100b,0 db 10000000b retf_2 db 0CBh ;Instruction RETF, for AMIS ; Interrupt Sharing Protocol for Int2Dh isp_2Dh db 0EBh,10h ;JMP SHORT int2Dhandler old_multiplex label dword ;Original vector old_int_2Dh dw 2 dup (0) dw 424Bh db 0 db 0EBh,0F4h db 7 dup (0) int2Dhandler proc near pushf ;save flags cmp ah,cs:multiplexnumber ;were we called? je next_al_0 ;yes, so check the function popf ;else jmp cs:old_multiplex ; jump to original handler next_al_0: cmp al,0 ;if not installation check then jne next_al_2 ; check uninstall-request mov cx,010Ah ;version number into CX push cs pop dx ;signature's address into DX:DI mov di,offset signature jmp short set_ff ;jump to code which puts FFh ; (the "supported" sign) into AL next_al_2: cmp al,2 ;requesting uninstall-info? jne next_al_3 ;if not then to next possibility cmp cs:active,0 ;are we currently active? jne jmp_2_return ; yap, no good to uninstall mov al,4 ;else set 'OK but no uninstaller' mov cs:enabled,0 ;disable ourselves push cs ;and code segment into BX pop bx mov dx,0Ch ;difference of allocated block ; into DX (a CyberRaxTSR thing :) jmp short return ;to handler end jmp_2_return: mov al,5 ;'busy right now, try later' jmp short return next_al_3: cmp al,3 ;want pop-up? jne next_al_4 ;neeeee, let's go on cmp cs:active,0 ;still active? jne return ;if yes then exit with 3, ; 'already popped up', which is in ; AL already mov cs:popup_req,1 ;set 'request by Int2Dhandler' flag pusha ;save all registers push ds push es call main1 ;call main code pop es ;clean stack pop ds popa mov cs:popup_req,0 ;reset flag xor bx,bx ;'return code 0' to BX jmp short set_ff ; and exit next_al_4: cmp al,4 ;want hooked int list? jne next_al_5 ;no, next... ;we should put 4 into AL, but as ; it's already there then we can ; skip this push cs pop dx ;list's address into DX:BX mov bx,offset interrupt_hook_list jmp short return next_al_5: cmp al,5 ;or maybe hotkey-list? jne next_al_20 push cs pop dx ;and address into DX:BX mov bx,offset hotkey_list jmp short set_ff next_al_20: ;functions 20h+ are CALC specific cmp al,20h ;get xms handler jne next_al_30 mov bx,cs:xms_handle ;handle into BX jmp short set_ff next_al_30: cmp al,30h ;get status? jne next_al_31 ;jump if not equal mov bl,cs:enabled ;put status into BL jmp short set_ff next_al_31: cmp al,31h ;set status? jne return_with_0 ;nope... mov cs:enabled,bl ;update our state set_ff: mov al,0ffh ;"function supported" into AL jmp short return ;jump to ending code return_with_0: mov al,0 ;zero register return: ;the "end int" code popf ;restore flags iret ;return from interrupt int2Dhandler endp ;----------------------------------------------------------------------------- ;INITIALIZE checks the commandline, redirects the needed interrupts, ; re-locates the code and reserves enough memory for the program to remain ; resident. ;----------------------------------------------------------------------------- initialize proc near mov dx,offset program ;Display installation message mov ah,9 int 21h ; Begin search for the installed version of us xor ax,ax ;Multiplex number 0, sub func. 0 check_loop: int 2Dh cmp al,0ffh ;anyone there? jne setmltiplex ;if no, go to setmltiplex mov es,dx ;if yes, set ES to code seg. of TSR mov cx,8 ;compare signature (8 words) mov si,offset signature rep cmpsw ;from DS:SI with ES:DI jne rep_loop ;not equal, some other AMIS-TSR jmp short is_insted ;it's us!!! rep_loop: xor al,al ;set AL again to 0 cmp ah,0ffh ;was it the last multiplex number? je all_AHs_done ;mhmhh, jump to all_AHs_done inc ah ;else increase the multiplex number jmp short check_loop ; and repeat the search setmltiplex: cmp found_free,0 ;if we already found some free jne rep_loop ; number then continue the search mov found_free,1 ; else set marker mov multiplexnumber,ah ; and remember the free number jmp short rep_loop ; and go to repeating all_AHs_done: mov allAHs, 1 ;set 'All AH's Were Checked' jmp short check_cmdline ; and begin commandline checking is_insted: ;if we were installed mov alreadyinsted,1 ; then set the marker mov multiplexnumber,ah ; and also the multiplexnumber mov al,30h ;check if 'status' function is int 2Dh ; supported (since 1.1 beta 1) cmp al,0ffh ;well, is it? jne short check_cmdline mov status,bl ;yes, so save the status check_cmdline: ;inspect the commandline mov si,81h ; SI -> command line cld ; ensure proper direction for ; string ops (for any case) cmdline_loop: lodsb ;move byte from DS:SI to AL cmp al,0Dh ;if end of commandline... je check_err ; ...jump cmp al,' ' ; else skip blanks... je cmdline_loop cmp al,'/' ; ...slashes... je cmdline_loop cmp al,'-' ; ...hypens.. je cmdline_loop cmp al,9 ; ...and tabs on commandline je cmdline_loop cmp al,'?' ;was '?' present? je to_helpscreen ;yes, so show helpscreen and al,0DFh ;force character to uppercase cmp al,'E' ;is it 'ENABLE'? jne check_d ;no, so next supported char... mov wantenable,1 ;...else set 'E was there' marker mov changestatus,1 ;...and note that status should be ; changed check_d: cmp al,'D' ;we had 'DISABLE'? jne check_i ;jump if not equal... mov wantdisable,1 ;...else set markers mov changestatus,1 check_i: cmp al,'I' ;repeat by 'INSTALL'... jne check_u mov wantinstall,1 check_u: cmp al,'U' ;...and 'UNINSTALL' jne check_x mov wantuninstall,1 check_x: cmp al,'X' ;Ain't it 'No XMS'? jne check_p mov wantnoxms,1 check_p: cmp al,'P' ;...or 'Pop up!'? jne check_help mov wantpopup,1 check_help: cmp al,'H' ;perhaps it was 'HELP!!!' je to_helpscreen ;yes, so helpscreen jmp cmdline_loop ;repeat the loop to_helpscreen: ;needed because JE/JNE can't jump jmp helpscreen ;more than ca. 122 bytes check_err: cmp word ptr [wantenable],0101h jne changestatus2 ;were both 'E' and 'D' present? jmp en_and_dis ;well, that's not allowed! changestatus2: cmp wantenable,1 ;if 'E'... jne changestatus21 mov enabled,1 ;...then set status to enabled changestatus21: cmp wantdisable,1 ;but if 'D' jne check_rest mov enabled,0 ;...then disable us check_rest: cmp word ptr [wantinstall],0101h jne check_rest2 ;in case of 'I' and 'D'... jmp inst_and_uninst ;...scream a bit check_rest2: cmp wantinstall,1 ;was 'I' present? je installing ;yes, so jump to installing cmp wantuninstall,1 ;by 'U'... jne check_rest3 jmp uninstall ;...we need uninstallation code check_rest3: cmp wantpopup,1 ;by 'P'... jne check_rest4 cmp alreadyinsted,1 ;...check if we're installed je popupping ;if yes then popupping... jmp popup_err ;...else the appropriate insult check_rest4: cmp alreadyinsted,1 ;so, were we actually installed? jne to_helpscreen ;no, so show help... jmp printstatus ;...else print our status popupping: mov ah,multiplexnumber mov al,3 ;use the official AMIS function int 2Dh ;...for this action mov dx,offset message16 ;exit with 'Suc. popped up' msg jmp end_with_0 installing: cmp alreadyinsted,1 ;if we were not installed jne installing1 ; then continue jmp insted_err ; else branch to insted_err installing1: cmp found_free,0 ;If found some free number jne installing2 ; then go on mov dx,offset message2 ; else show 'No free numbers' jmp error_end ; message ; If we got here then we're not resident installing2: cmp wantnoxms,1 ;No XMS? je installing3 ;Yes, so environment releasing mov ax,4300h ;Else check for the presence... int 2fh ;...of an XMS 2.0+ driver cmp al,80h jne installing3 ;Nobody's home :( mov ax,4310h ;Get the address of the driver int 2fh mov xms_offset,bx ;Save it mov xms_segment,es mov ah,0 ;Get XMS'/driver's version number call dword ptr xms_offset cmp ax,0200h ;XMS compared to 2.0? jb installing3 ;Jump if below mov ah,9 ;Allocate memoryblock... mov dx,2 ;...of 2kB call dword ptr xms_offset cmp ax,1 ;Success? jne installing3 ;Jump if not equal mov xms_handle,dx ;Save handle of memoryblock installing3: mov di,2Ch ;free our enviroment mov es,word ptr [di] ; to reduce memory requirements mov ah,49h int 21h push cs ;restore ES pop es call relocate ;call our relocation procedure mov ax,3509h ;get current interrupt 9 vector, int 21h mov old_keyboard_int,bx ;save it mov old_keyboard_int[2],es mov ax,2509h ;and replace with our own mov dx, offset isp_9h int 21h mov ax,352Dh ;do the same with interrupt 2Dh int 21h mov old_int_2Dh,bx mov old_int_2Dh[2],es mov ax,252Dh mov dx,offset isp_2Dh int 21h mov dx,offset message0 ;'Suc. installed' message mov ah,9 int 21h cmp xms_handle,0 ;We did use XMS? je installing4 mov dx,offset message01 ;Yep, so 'using XMS' message mov ah,9 int 21h installing4: mov dx,offset message_crlf ;Print a new line mov ah,9 int 21h ;Exit by int 21h function 31h. ;If no XMS or 'P' switch then keep enough memory to store the screen. cmp xms_handle,0 jne installing5 mov dx,(offset initialize - offset code + 1056 + 15 - 0C0h) shr 4 ;substracting C0h is needed because our relocation ; (actually not needed, but we don't need those bytes ; and if we don't substract then there's no meaning ; to relocate at all) jmp short installing6 installing5: mov screen_buffer,0 mov dx,(offset initialize - offset code + 15 - 0C0h) shr 4 installing6: mov ax,3100h ;Set returncode to 0 int 21h ;Terminate but stay resident uninstall: ;the uninstall routine cmp alreadyinsted,1 ;hopefully installed je uninstall1 ; yes, so continue jmp notinst_err ; else give error uninstall1: mov ah,multiplexnumber ;get status and disable TSR mov al,2 ; with function 2 int 2Dh cmp al,5 ;if 'not safe' jne uninstall2 jmp uninst_notsafe ; give error uninstall2: mov cx,bx ;move code segment of TSR into CX push dx ;save the difference mov ax,3509h ;get current interrupt 9 vector int 21h ; to ES:BX mov dx,es ;Is code segment the same as TSR's cmp dx,cx ; then it's ours je uninstall3 jmp uninst_err uninstall3: cmp bx,offset isp_9h ;what about the offset? je uninstall4 jmp uninst_err ;if different then branch uninstall4: mov ax,352Dh ;do the same with interrupt 2Dh int 21h mov dx,es cmp dx,cx je uninstall5 jmp uninst_err uninstall5: cmp bx,offset isp_2Dh je uninstall6 jmp uninst_err ; if we got here then the code segments/offsets are the same ; which means there aren't any other TSR(s) on these vectors ; so free the XMS (if we're using it) and restore the vectors uninstall6: push es ;save segment of TSR mov ah,multiplexnumber mov al,20h int 2dh ;get EMB handler number from TSR cmp al,0ffh ;func. supported by TSR? jne uninstall7 cmp bx,0 ;We're using XMS? je uninstall7 push bx ;Save handler mov ax,4310h ;Get address of XMS driver int 2fh mov xms_offset,bx ;Save it mov xms_segment,es mov ah,0ah ;'Free XMS block' function pop dx ;handler into DX call dword ptr xms_offset cmp ax,1 ;Success? jne xms_err ;No (don't you just hate that?) uninstall7: pop es ;get back TSR's segment mov dx,es:old_keyboard_int ;restore old values mov ds,es:old_keyboard_int[2] ; in the interrupt list mov ax,2509h int 21h mov dx,es:old_int_2Dh mov ds,es:old_int_2Dh[2] mov ax,252Dh int 21h mov ax,es ;TSR's segment into AX pop dx ;get back the difference add ax,dx mov es,ax ;put their sum into ES mov ah,49h ;release the memory-block int 21h ; allocated by TSR jc release_err ;on error branch to release_err push cs ;set DS to code segment pop ds ; If we got here then we have successfully uninstalled the ; resident part ; Let's say that to the user mov dx,offset message5 end_with_0: mov ah,9 int 21h ;DOS' 'print to screen' funct. mov ax,4C00h ;exit with errorlevel 0 int 21h ; error maintaining :) release_err: push cs ; restore DS pop ds mov dx,offset message7 jmp short error_end insted_err: mov dx,offset message1 jmp short error_end uninst_notsafe: mov dx,offset message4 jmp short error_end xms_err: mov dx,offset message14 jmp short error_end en_and_dis: mov dx,offset message12 jmp short error_end inst_and_uninst: mov dx,offset message13 jmp short error_end notinst_err: mov dx,offset message15 jmp short error_end popup_err: mov dx, offset message17 jmp short error_end uninst_err: mov dx,offset message3 error_end: mov ah,9 ;show message int 21h mov ax,4C01h ;Set returncode to 1 int 21h ; and terminate without going TSR helpscreen: mov dx,offset message6 ;help screen jmp short end_with_0 printstatus: cmp changestatus,0 ;any status change requested? je printstatus1 ;no, so don't change cmp status,2 ;TSR doesn't support change? je printstatus1 ;it doesn't, so no change ; If we're here than 'E' or 'D' was present, we're resident and ; TSR part supports enabling/disabling mov ah,multiplexnumber mov al,31h ;'Set status' function mov bl,enabled int 2Dh ;feed to TSR mov al,enabled mov status,al ;save the new status for ; ourselves printstatus1: mov dx,offset message8 ;"TSR's resident" message mov ah,9 int 21h cmp status,2 ;If TSR doesn't support change je printstatus3 ;...then show "Use CALC..." msg mov ah, multiplexnumber mov al,20h ;Get handler of XMS block int 2Dh cmp al,0ffh ;Func. supported... jne printstatus2 cmp bx,0 ;...and XMS actually in usage? je printstatus2 mov ah,9 mov dx,offset message01 ;So show 'using XMS' msg int 21h printstatus2: cmp status,1 ;Else... jne printstatus3 mov ah,9 mov dx,offset message10 ;...print "Enabled"... int 21h jmp short printstatus4 printstatus3: mov ah,9 mov dx,offset message11 ;...or "Disabled"... int 21h printstatus4: mov dx,offset message9 ;...and "Use CALC..." messages jmp end_with_0 initialize endp ; Text strings program db 13,10,"ΙΝΔ CALC ΝΝΝΝΔ 1.10 ΝΝΝΛΝΔ" db " (C) 1988 Ziff Communications Co. Ν»" db 13,10,"Ί Original by Douglas Boling Ί" db " (H)acked by CyberRax Ί" db 13,10,"Ί at PC Magazine Ί" db " in Estonia '2000 - 2001 Ί" db 13,10,"ΘΝΔ A resident calculator ΝΝΝΚΝΝΝΝΝΝΝΔ " db "Hotkey is Ctrl+Alt+S ΝΝΝΝΝΝΝΌ",13,10,13,10,"$" message_crlf db 13,10,'$' message0 db "Successfully installed$" message01 db ", using XMS$" message1 db "Already installed",13,10,"$" message2 db "No free alternate multiplex number",13,10,"$" message3 db "Can't uninstall: other TSR(s) installed after CALC",13,10,"$" message4 db "Can't uninstall: not safe right now, try later",13,10,"$" message5 db "Successfully uninstalled",13,10,"$" message6 db "Syntax: CALC [switches]",13,10,13,10 db " I - install E - enable",13,10 db " U - uninstall D - disable",13,10 db " X - don't use XMS P - pop up",13,10,"$" message7 db "Can't uninstall: error releasing memory",13,10,"$" message8 db "CALC is resident$" message9 db 13,10,"Type ® CALC ? ― to see supported switches",13,10,"$" message10 db " and enabled$" message11 db " but disabled$" message12 db "Can't use 'enable' and 'disable' together",13,10,"$" message13 db "Can't use 'install' and 'uninstall' together",13,10,"$" message14 db "Error releasing XMS memory",13,10,"$" message15 db "Can't uninstall: not resident",13,10,"$" message16 db "Successfully popped up",13,10,"$" message17 db "Can't pop up: not resident",13,10,"$" ;Used variables/markers found_free db 0 allAHs db 0 alreadyinsted db 0 changestatus db 0 status db 2 wantinstall db 0 wantuninstall db 0 wantenable db 0 wantdisable db 0 wantnoxms db 0 wantpopup db 0 relocate proc near ;the next block moves the whole program from CS:100h to CS:40h ; then substracts 0Ch from CS to get the new segment ; (CS:40h is exactly the same as CS-0Ch:100h) ; and makes a far return to it (it's like a usual RET, just to ; another segment) ;this all is done to make the memory requirements of the ; resident part as small as possible RelocateOurCode: ;next 3 commands: MOV CX,(OFFSET RelocateOurCode - 100h) / 2 + 1) mov cx,offset RelocateOurCode-100h shr cx,1 inc cx mov si,100h ;source - CS:100h mov di,40h ;destination - CS:40h cld ;clear direction flag, so SI and DI will be ;incremented and not decremented rep movsw ;move words mov ax,ds sub ax,0Ch ;substract 0Ch from our data segment mov ds,ax mov es,ax ;set ES also to this new address pop ax ;get the offset address where to return push ds ;push the new segment push ax ; and the offset retf ; and return there relocate endp code ends end entry :Compile ;tasm calc.bat /m ;if exist calc.obj tlink calc.obj /t /x ;if exist calc.obj del calc.obj