; FILENAME: AB.ASM
P386
IDEAL
JUMPS
LOCALS @_

INCLUDE "USEFUL.INC"

;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
segment code
        org     100h
        assume cs:code, ds:code, es:nothing, ss:code
start:

        jmp     main


	REM	--  if you've finish coding a program (C/C++ or Pascal)
	REM	    after 3 months(an average span of medium-size
	REM	    software development) which coding frequency is
	REM 	    4 hours every day and saving the program is 5 minutes,
	REM	    this number(5000) in theory could let you retrieve
	REM 	    your initial project code roughly 3 months ago,
	REM	    allowing other programmer, who wants tinker with your
	REM	    code and see how you gradually develop your program
	MAX_FILE_COUNT          = 5000

        label   oldTSRAddr      dword
                oldTSROfs       dw ?
                oldTSRSeg       dw ?


        REM                     -- backup is false tentatively
        canWeBackup                     db 0

        handleOfFileToBackup            dw ?
        handleOfDuplicate               dw ?

        REM             -- length does not include the NULL byte terminator
        filenameLength                  dw ?

	extensionList               	db ' .PAS .INC .C .H .CPP .HPP'
					db ' .ASM .MAC .CXX .HXX .HDR ', 0

        fileNameExtLastCharOffset       dw ?
        fileNameExtLength               dw ?
        nextFnameExtLastCharOffset      dw ?

	fileToBackup                    db DOS_MAXPATHLEN dup(?)
        filenameToAdjust                db DOS_MAXPATHLEN dup(?)

        fnameMostSignificantExtOffset   dw ?

	REM             -- the NULL is copied when you copy the digit
	REM                  extension to the extension fileToBackup.
	REM		   this is the digit filename
	toDigit        db 8 dup(?), NULL


        REM             -- pascal is a special case in our TSR program
        REM                the author found out that Turbo Pascal Editor,
        REM                version 7 in particular don't directly save the
        REM                source code to a .PAS, rather it renames the
        REM                editor temporary file which has an extension
        REM                of .$$$ to a .PAS
	pascalFNameOffset               dw ?

	buffer                          db 1 dup(?)

        mcbAsciiNameLength      dw ?

        struc   langNameList
                len     dw ?
                desc    db 8 dup(NULL)
        ends

	pascalLang	langNameList< 5, 'TURBO' >
			langNameList< 2, 'TP' >
			langNameList< 3, 'TPX' >
			langNameList< 2, 'BP' >
			langNameList< 3, 'BPX' >
			langNameList< 6, 'PASCAL' >
                        db NULL

	ccplusLang      langNameList< 2, 'TC' >
			langNameList< 3, 'TCX' >
			langNameList< 2, 'BC' >
			langNameList< 3, 'BCX' >
			langNameList< 2, 'QC' >
			langNameList< 3, 'QCX' >
			langNameList< 1, 'C' >
                        db NULL


	langToDetect    dw ?


	dta		db 256 dup(?)

        prevDTASeg		dw ?
	prevDTAOffs		dw ?


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    isLanguageInMem?
                push    ax
                push    bx
                push    cx
                push    ds
                push    si
                push    es
                push    di




        @_firstMCB:
                mov     ah, DOS_GET_INITIAL_MCB
                pushf
                call    [cs:oldTSRAddr]
                

                push    cs
                pop     ds

                mov     es, [word es:bx-2]


;............................................................................
        @_traverseMCB:

                mov     bx, [cs:langToDetect]

        @_loopPascalLang:

                mov     di, 0
                mov     cx, 8
        @_findMCBAsciiNameLength:
                inc     di
                cmp     [(mcb es:di).asciiName], NULL
                loopne  @_findMCBAsciiNameLength

                mov     [cs:mcbAsciiNameLength], di

                mov     cx, [(langNameList cs:bx).len]

                REM     -- if not same string length scan the
                REM        next language name
                cmp     cx, [cs:mcbAsciiNameLength]
                        ohNo    @_scanNextLanguageName

                lea     di, [es:mcb.asciiName]
                lea     si, [(langNameList cs:bx).desc]


                cld
                repe    cmpsb
                        ohYes   @_found


        @_scanNextLanguageName:

                add     bx, size langNameList
                cmp     [(langNameList cs:bx).desc], NULL
                        ohYes   @_stopScanningLang

                jmp     @_loopPascalLang

        @_stopScanningLang:

	@_scanTheNextMCB:

                cmp     [es:mcb.linkIndicator], 'Z'
                je      @_notFound

                mov     ax, es
                inc     ax

                add     ax, [es:mcb.paragraphLen]
                mov     es, ax

                jmp     @_traverseMCB

;............................................................................
        @_found:
                ste
                jmp     @_finish

;............................................................................
        @_notFound:
                cle
                jmp     @_finish

        @_finish:
                
                pop     di
                pop     es
                pop     si
                pop     ds
                pop     cx
                pop     bx
                pop     ax

                ret
        endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-        
        proc    isPascalInMem?
        REM     Input(s):
        REM        langToDetect -- pass here to offset of name list of
        REM                        the language
        REM     Output(s):
        REM        Equality/Zero Flag -- if present in memory
;............................................................................
                mov     [cs:langToDetect], offset pascalLang
                call    isLanguageInMem?
                ret
        endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    isCCPlusplusInMem?
        REM     Input(s):
        REM        langToDetect -- pass here to offset of name list of
        REM                        the language
        REM     Output(s):
        REM        Equality/Zero Flag -- if present in memory
;............................................................................
                mov     [cs:langToDetect], offset ccplusLang
                call    isLanguageInMem?
                ret
        endp



;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
	REM	-- you might think that 10 can be stored in byte variable
	REM	     of course it is, but since we want to obtain a quotient
	REM	     of double word we must divide it by 2 double words.
	REM       EDX(high doubleword:remainder) EAX(low doubleword:quotient)
	by10		dd 	10

	proc    regEAXConvertTo8Digit
		push    ebx
		push    ecx
		push    edx
		push    eax

		mov     cx, 8
		mov	bx, 8
		mov	edx, 0

        @_divideBy10:
		div     [cs:by10]
		dec     bx
		add     dl, '0'
		mov     [cs:toDigit+bx], dl
		mov     edx, 0
                loop    @_divideBy10

		pop     eax
		pop     edx
		pop     ecx
		pop     ebx

                ret
        endp



;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    createNewName
		pusha
		pushf


		push    cs
		pop     ds

		mov	ah, DOS_GET_DTA
		pushf
		call	[cs:oldTSRAddr]

		push	es
		pop	[prevDTASeg]

		push	bx
		pop	[prevDTAOffs]


		mov	ah, DOS_SET_DTA
		mov	dx, offset dta
		pushf
		call	[cs:oldTSRAddr]


                push    cs
                pop     es


		mov     cx, [cs:filenameLength]
                mov     di, offset fileToBackup
		add     di, cx

                mov     al, '.'
                std
		repne   scasb

		REM	-- create a subdirectory out of fileToBackup
		add 	di, 3
		mov	[byte es:di], '@'
		mov	[byte es:di+1], 'B'
		mov	[byte es:di+2], NULL


		REM	-- we have no use for carry
		mov	ah, DOS_CREATE_DIRECTORY
		mov	dx, offset fileToBackup
		pushf
		call	[cs:oldTSRAddr]



		mov	[byte es:di+2], '\'
		mov	[byte es:di+3], NULL

		REM	-- for example a file is named
		REM	   MP1.C this will create a number filename
		REM	   in subdirectory MP1.C@B
		REM     -- skip past the directory's at sign
		add     di, 3
                mov     [cs:fnameMostSignificantExtOffset], di

                jmp     @_startOfFindANewFilename

;............................................................................;
        @_startOfFindANewFilename:
		REM     -- the number filename
		mov     eax, 0

		jmp 	@_findANewFilename

;............................................................................
	@_findANewFileName:

		call    regEAXConvertTo8Digit

                REM     -- if we exhaust filenames number list adjust ...
		cmp     eax, MAX_FILE_COUNT + 1
                        ohYes   @_adjustListOfFileHistory

		jmp     @_createAUniqueFilename


        @_createAUniqueFilename:
		inc     eax

                REM     -- preserved the file counter
		push    eax


		mov     di, [cs:fnameMostSignificantExtOffset]
                REM     -- 8 digit number filename plus NULL character
		mov     cx, 9
                mov     si, offset toDigit
                cld
                rep     movsb

                mov     ah, DOS_FILE_FIND_FIRST
                mov     cx, DOS_NORMAL_FILE
                mov     dx, offset fileToBackup
                pushf
		call    [cs:oldTSRAddr]

		REM     -- restore the file counter
		pop     eax

                REM     -- file is existing
                jnc     @_findANewFileName


		REM     -- the above loop is terminated  because
                REM        we found a file name that does not already
                REM        exist

		jmp	@_finally

;............................................................................
        @_adjustListOfFileHistory:
		pusha


                REM     -- we will remove the first file from the list
		mov     eax, 0
		call    regEAXConvertTo8Digit

                mov     di, [cs:fnameMostSignificantExtOffset]
		REM     -- 8 digit number filename plus NULL character
		mov     cx, 9
                mov     si, offset toDigit
                cld
                rep     movsb

                mov     ah, DOS_FILE_DELETE
                mov     dx, offset fileToBackup
                pushf
                call    [cs:oldTSRAddr]


		REM     -- for ax := 0 to MAX_FILE_COUNT - 1 do
		REM   		adjust filenames
		mov     eax, 0
        @_adjustThis:


                mov     si, offset fileToBackup
                mov     di, offset filenameToAdjust
                mov     cx, DOS_MAXPATHLEN
                cld
                rep     movsb


                REM     -- construct the name of file to be rename
                call    regEAXConvertTo8Digit
		mov     di, [cs:fnameMostSignificantExtOffset]
                REM     -- 8 digit number filename plus NULL character
		mov     cx, 9
                mov     si, offset toDigit
                cld
                rep     movsb

                REM     -- construct the new name for file to renamed
		inc     eax
                call    regEAXConvertTo8Digit
                mov     di, [cs:fnameMostSignificantExtOffset]
                REM     -- filenameToBackup and filenameToAdjust is
                REM        adjacent in memory, so it is just
                REM        okay to do the following light trick:
                REM        di += DOS_MAXPATHLEN; which essentially
                REM        will point DI to the new name or the
                REM        filenameToAdjust's dot character
                add     di, DOS_MAXPATHLEN
		mov     si, offset toDigit
                REM     -- 8 digit number filename plus NULL character
		mov     cx, 9
                cld
                rep     movsb

                REM     -- preserve the file counter
		push    eax

                REM     -- now we can rename the file
                mov     ah, DOS_FILE_RENAME
                mov     dx, offset filenameToAdjust
                mov     di, offset fileToBackup
                pushf
                call    [cs:oldTSRAddr]

                REM     -- restore the file counter
		pop     eax

		cmp     eax, MAX_FILE_COUNT
                        ohYes   @_stopRenaming
                jmp     @_adjustThis

;............................................................................
        @_stopRenaming:


                REM     -- from MAX_FILE_NUMBER to MAX_FILE_NUMBER - 1
                REM        so when create a unique filename is performed
		REM        it has no knowing that MAX_FILE_NUMBER
		REM        adjustments were made, then after inc eax
		REM 	   is performed, register EAX holds
		REM        the number MAX_FILE_NUMBER again
                popa
		mov     eax, MAX_FILE_COUNT - 1
                jmp     @_createAUniqueFilename


;............................................................................
	@_finally:

		mov	ah, DOS_SET_DTA

		push	[cs:prevDTAOffs]
		pop	dx

		push	[cs:prevDTASeg]
		pop	ds

		pushf
		call	[cs:oldTSRAddr]

		popf
		popa
                ret
        endp



;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    copyByteByByte

		pusha

		push    cs
		pop     ds


                REM     -- go the beginning of file, from there we will
                REM        do a byte by byte read of the file to backup
                mov     bx, [cs:handleOfFileToBackup]
                mov     ah, DOS_FILE_MOVE_PTR
                mov     al, DOS_FILE_BEGIN
                REM     -- longword offset is in CX:DX pair,
                REM        conventionally longword is put in DX:AX pair,
                REM        but since AX register is use in function
                REM        the designer of DOS opted to put the
                REM        longword file offset in CX:DX
                mov     cx, 0
                mov     dx, 0
                pushf
                call    [cs:oldTSRAddr]


                mov     ah, DOS_FILE_CREATE
                mov     dx, offset fileToBackup
                mov     cx, 00
                pushf
                call    [cs:oldTSRAddr]
		mov     [cs:handleOfDuplicate], ax

		jmp	@_doTheCopying

;............................................................................
	@_doTheCopying:
		mov     dx, offset buffer
        @_byteRead:
                mov     bx, [cs:handleOfFileToBackup]
                mov     ah, DOS_FILE_READ
                mov     cx, size buffer
                pushf
                call    [cs:oldTSRAddr]

		REM     -- if read size is 0 then no more to read
                REM        so just end the loop
		cmp     ax, 0
                        ohYes   @_finally

        @_byteWrite:
                mov     cx, ax
                mov     bx, [cs:handleOfDuplicate]
                mov     ah, DOS_FILE_WRITE
                pushf
                call    [cs:oldTSRAddr]

		jmp     @_byteRead

;............................................................................
        @_finally:

                mov     bx, [cs:handleOfDuplicate]
                mov     ah, DOS_FILE_CLOSE
                pushf
                call    [cs:oldTSRAddr]

                popa

                ret
        endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    extensionMatch?

        REM     Input  Register(s):
        REM         DS:DX = offset of filename

        REM     Output Register(s):
        REM         DS:DX = offset of filename

        REM         Zero/Equality Flag:
        REM             Set when TRUE    -- z / e
        REM             Clear when FALSE -- nz / ne
;............................................................................

                REM     -- for an unknown reason Turbo Pascal
                REM        editor seems to be lacking in stack space
                REM        whenever we push all the registers(using PUSHA)
                REM        the rename function can't continue, so the
                REM        author opt to preserve only those registers that
                REM        are used in this function


                push    ax
                push    cx
                push    dx
                push    es
                push    ds
                push    si
                push    di


                push    ds
                pop     es
        
                REM     -- find the offset of NULL character
                REM        dx holds the offset of the filename
                REM        of the interrupted program
                mov     di, dx
                REM     -- scan the AsciiZ NULL character
                mov     al, NULL
                mov     cx, DOS_MAXPATHLEN
                cld
                repne   scasb
        
                REM     -- inc cx instr. excludes the null byte from 
                REM        string length
                inc     cx
                mov     [cs:filenameLength], DOS_MAXPATHLEN
                sub     [cs:filenameLength], cx


                REM     -- 2 is the distance of the DI register termination
                REM        back to the character before the NULL character
                sub     di, 2
        
                REM     -- from the NULL character offset - 2 is the last
                REM        character
                mov     [cs:fileNameExtLastCharOffset], di

                REM     -- the following instr. is use for the 
                REM        comparison(cmpsb) of the asciiz filename
                REM        against the extension list
                push    cs
                pop     es
        
                mov     [cs:nextFnameExtLastCharOffset], offset extensionList
        
                jmp     @_tryExtIfMatchExtList

;............................................................................
        @_tryExtIfMatchExtList:
        
                mov     di, [cs:nextFnameExtLastCharOffset]
        
        @_tryFindTheDotCharacter:
                inc     di
        
                cmp     [byte cs:di], '.' 
                        ohYes   @_countTheCharacter
        
                cmp     [byte cs:di], NULL
                        ohYes   @_noExtMatch
        
		jmp     @_tryFindTheDotCharacter
        
        @_countTheCharacter:
                mov     cx, 0
        
        @_thenCount:
                inc     di
                inc     cx
                cmp     [byte cs:di], ' '
                        ohNo    @_thenCount

                REM     -- exclude the space character                
                dec     cx
                mov     [cs:fileNameExtLength], cx 
                dec     di
                mov     [cs:nextFnameExtLastCharOffset], di
                
                mov     cx, [cs:fileNameExtLength]
                mov     si, [cs:fileNameExtLastCharOffset]
                mov     di, [cs:nextFnameExtLastCharOffset]
                std
                repe    cmpsb
                        ohYes   @_extMatch
        
                mov     di, [cs:nextFnameExtLastCharOffset]
                
                jmp     @_tryFindTheDotCharacter

        @_extMatch:
                
                pop     di
                pop     si
                pop     ds
                pop     es
                pop     dx
                pop     cx
                pop     ax
                

                ste
                ret

	@_noExtMatch:

                pop     di
                pop     si
                pop     ds
                pop     es
                pop     dx
                pop     cx
                pop     ax
                
                cle
                ret                
        endp

;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    dosIntercept
                jmp     @_beginning

				tsrMarker db 'AB-MLTH'

        @_beginning:
                pushf


                REM     -- Pascal 7 Editor is a special case, it doesn't
                REM        directly saves the source file to .PAS, instead
                REM        it save it first to temporary file then save
                REM        rename it to .PAS
                cmp     ah, DOS_FILE_RENAME
                        ohYes   @_tryRename

                cmp     ah, DOS_FILE_CREATE
                        ohYes   @_tryExtension

                cmp     ah, DOS_FILE_CREATE_SAFE
                        ohYes   @_tryExtension

                cmp     ah, DOS_FILE_CLOSE
                        ohYes   @_tryBackup


                jmp     @_toOldRoutine


        
;............................................................................
                REM     -- this is a special case for Turbo Pascal 7 Editor
                REM        which does not directly save source code to its
                REM        Pascal filename, rather, it renames the editor
                REM        swap file which has an extension of .$$$ to .PAS
        @_tryRename:
                call    isCCPlusplusInMem?
                        ohNo    @_tryIfPascalRename

                jmp     @_continueInterceptingRename

        @_tryIfPascalRename:
                call    isPascalInMem?
                        ohNo    @_toOldRoutine

                jmp     @_continueInterceptingRename

        @_continueInterceptingRename:



                mov     [cs:pascalFNameOffset], di

                push    ds
                push    dx

                REM     -- from the calling rename:
                REM           DS:DX = ES:DI
                REM        where ES:DI is the new name of the rename
                REM           and DS:DX is the filename to test its
                REM           extension
                push    es
                pop     ds
                push    di
                pop     dx
                call    extensionMatch?
                        ohNo    @_pascalNoMatch

                jmp     @_backupPascalFilenameAndHandle

;............................................................................
        @_pascalNoMatch:

                REM     -- no match so do the ff:
                REM          restore the previous registers,
                REM          then the flag of the interrupted program
                pop     dx
                pop     ds
                popf

                pushf
                call    [cs:oldTSRAddr]
                        jc      @_contNoMatchFinal
                
              @_contNoMatchFinal:

                retf    2
;............................................................................          
        @_backupPascalFilenameAndHandle:

                REM     -- no match so do the ff:
                REM          restore the previous registers,
                pop     dx
                pop     ds


                push    ds
                push    si
                push    es
                push    di

                REM     -- then from the rename do the copy string of
                REM           of the renamed's new name to the
                REM           fileToBackup 
                REM        assign: ES:DI = DS:SI
                REM           destination:
                REM              ES = CS
                REM              DI = offset fileToBackup
                REM          source:(the new name of to be renamed temp file)
                REM              DS = ES
                REM              SI = [cs:pascalFNameOffset] --> previously
                REM                   this is the DI, but we save
                REM                   first DI to the variable
                REM                   pascal filename offset to avoid
                REM                   confusion in variable assignments
                push    es
                pop     ds
                mov     si, [cs:pascalFNameOffset]                
                push    cs
                pop     es
                mov     di, offset fileToBackup                                
                mov     cx, [cs:filenameLength]
                inc     cx
                cld
                rep     movsb

                pop     di
                pop     es
                pop     si
                pop     ds


                REM     -- restore the flag of the interrupted program
                popf   
                REM     -- the rename
                pushf
                call    [cs:oldTSRAddr]

                jnc     @_cont

                push    ax
                push    dx
                mov     ah, 02
                mov     dl, 176
                pushf
                call    [cs:oldTSRAddr]
                pop     dx
                pop     ax



                retf    2

        @_cont:                

                REM     -- preserve rename's flag and registers
                pushf
                push    ax
                push    ds
                push    dx


                push    cs
                pop     ds
                mov     ah, DOS_FILE_OPEN
                mov     al, 0
                mov     dx, offset fileToBackup
                pushf
                call    [cs:oldTSRAddr]
                        jnc     @_continueBackuppingPascalHandle
                        
                jmp     @_doNotBackupPascalHandleAndDoNotCloseHandle


;............................................................................
        @_continueBackuppingPascalHandle:
                mov     [cs:handleOfFileToBackup], ax
                setnc   [cs:canWeBackup]                

                REM     -- restore rename's registers and flag
                pop     dx
                pop     ds
                pop     ax
                popf

                REM     -- preserve it we are doing the file close
                pushf
                push    ax
                push    bx

                REM     -- when the file is close it will be
                REM        be backupped
                mov     ah, DOS_FILE_CLOSE
                mov     bx, [cs:handleOfFileToBackup]
                int     21h

                REM     -- restore the rename's previous registers
                REM        and flag, not from the file close
                pop     bx
                pop     ax
                popf

                retf    2

;............................................................................
        @_doNotBackupPascalHandleAndDoNotCloseHandle:

                REM     -- cannot open a file so just do the ff:
                REM          restore previous registers and,
                REM          restore flag of the interrupted
                REM          program
                pop     dx
                pop     ds
                pop     ax
                popf

                REM     -- return to interrupted program
                retf    2

;............................................................................
        @_tryExtension:
        REM     -- before we invoke the hooked FILE CREATE/FILE CREATE SAFE
        REM        we test first if the filename's extension if it
        REM        match our filename extension list

                call    isCCPlusplusInMem?
                        ohNo    @_tryIfPascalExtension

                jmp     @_continueInterceptingExtension

        @_tryIfPascalExtension:
                call    isPascalInMem?
                        ohNo    @_toOldRoutine

                jmp     @_continueInterceptingExtension

        @_continueInterceptingExtension:
                        


                call    extensionMatch?
                        ohYes   @_backupFilenameAndHandle

                jmp     @_noExtensionMatch
                                      

;............................................................................
        @_backupFilenameAndHandle:

                pusha
                push    cs
                pop     es
                mov     di, offset fileToBackup
                mov     si, dx
		mov     cx, [cs:filenameLength]

		BUG	-- this just one line that is missing from the
		BUG	   distributed diskette cause a very erroneous
		BUG        critical error when the file is saved

		inc	cx
                cld
                rep     movsb
                popa


                REM     -- restore previous state of flag
                REM           this  flag comes from the interrupted program
                REM           not from this routine
                REM        then invoke the hooked routine which is
                REM           FILE CREATE/FILE CREATE SAFE
                popf
                pushf
                call    [cs:oldTSRAddr]

                setnc   [cs:canWeBackup]
                mov     [cs:handleOfFileToBackup], ax

                REM     -- return to the interrupted program,
                REM        but do not load the flags from the stack
                REM        instead it leave it as is
                retf    2



;............................................................................
        @_noExtensionMatch:
        REM     -- filename's extension does not match our filenames
        REM          extension list, so just invoke the hooked routine
        REM          which is the FILE CREATE/FILE CREATE SAFE
                jmp     @_toOldRoutine


;............................................................................
        @_tryBackup:
                cmp     bx, [cs:handleOfFileToBackup]
                        ohNo    @_toOldRoutine

                cmp     [cs:canWeBackup], 1
                        ohYes   @_sound

                mov     [cs:canWeBackup], 0

                jmp     @_toOldRoutine

;............................................................................
        @_sound:
              
                call    createNewName
                call    copyByteByByte

                mov     [cs:canWeBackup], 0

                jmp     @_toOldRoutine


;............................................................................
        @_toOldRoutine:

                popf
                jmp     [cs:oldTSRAddr]
        endp

        tsrSize = ( ( $ - start ) + 256 + 16 ) / 16
        

        hookedIntr = 21h




;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    freeEnvironment

        REM     Output Register(s):
        REM             ES - segment address of the Environment Offset

;............................................................................

                push    ax

                mov     ah, DOS_RELEASE_MEM
		mov     es, [ds:ENVIRONMENT_SEGMENT]
                int     21h

                pop     ax
                ret
        endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    isTSRInMem?

        REM     Output Register(s):
        REM         ES - segment address of the TSR

        REM         Zero/Equality Flag:
        REM             Set when TRUE    -- z / e
        REM             Clear when FALSE -- nz / ne
        
;............................................................................ 

                mov     ah, DOS_INTR_GET_VECT
                mov     al, 21h
                int     21h

                REM     -- 8 is an arbitrary value, it can be 
                REM        any number greater than one, but for 
                REM        the program to surely detect if it really match
                REM        we choose more or less 8  
                mov     cx, 8
                mov     si, offset tsrMarker
                mov     di, bx
                REM     -- we add 3 to DI because the tsrmarker offset
                REM        from the beginning of jmp instruction is 3
                add     di, 3
                cld
                repe    cmpsb
                        ohYes   @_alreadyInMem
                
                jmp     @_notInMem
;............................................................................
        @_notInMem:
                REM     -- the ff. will force clear the equality
                cle
                jmp     @_finally

;............................................................................
        @_alreadyInMem:
                ste
                jmp     @_finally


;............................................................................
        @_finally:
                ret                        
        endp
        
;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    removeTSRInMem

        REM     Input Register(s):
        REM             ES - segment address of the TSR

        REM     Output Register(s):
        REM         Zero/Equality Flag:
        REM             Set when SUCCESS   -- z / e
        REM             Clear when FAILED  -- nz / ne
        REM         Carry Flag:
        REM             Set when FAILED    -- c
        REM             Clear when SUCCESS -- nc


;............................................................................
                push    ax
;............................................................................
        @_tryRelease:
                mov     ah, DOS_RELEASE_MEM
                int     21h
                jc      @_clearEqual

                REM     -- success removing
                ste
                jmp     @_finally

;............................................................................
        @_clearEqual:
                REM     -- no success in removing
                cle                
                jmp     @_finally

;............................................................................
        @_finally:
                pop     ax
                ret                                        
        endp
        
        
;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    putCodeInMem

        REM     -- returns nothing.
        REM        this function does not return to the calling function
        REM        instead it directly returns to DOS
        REM        so it is okay not to preserve used registers

;............................................................................
                REM     -- we have no use for environment strings
                call    freeEnvironment
        
                mov     ah, DOS_INTR_GET_VECT
                mov     al, hookedIntr
                int     21h

                mov     [oldTSROfs], bx
                mov     [oldTSRSeg], es

                mov     ah, DOS_INTR_SET_VECT
                mov     al, hookedIntr
                lea     dx, [dosIntercept]
                int     21h

                mov     ah, DOS_KEEP_TSR
                mov     dx, tsrSize
                int     21h

        endp
        

;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    isCommandLineEmpty?

        REM     -- returns equal(e/z) when true otherwise not equal(ne/nz)

;............................................................................
		REM     -- do we have a command line? test the length
		pusha
		cmp     [byte es:COMMAND_LINE_LEN_OFFSET], 00h
			jz	@_noCommandLine

		mov	di, 81h
		mov	ch, 0
		mov	cl, [byte es:80h]
		mov	al, ' '
		cld
		repe	scasb
			je	@_noCommandLine


		clz
		jmp	@_finally

	@_noCommandLine:
		ste
		jmp	@_finally

	@_finally:
		popa
                ret
        endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    isCommandLineSlashU?

        REM     Output Register(s):
        REM         Zero/Equality Flag:
        REM             Set when TRUE    -- z / e
        REM             Clear when FALSE -- nz / ne


;............................................................................        
                push    ax
                push    cx
                push    dx
                push    di
                push    si

                REM    -- scan for slash character
                mov     al, '/'
                mov     ch, 0
                mov     cl, [byte ds:COMMAND_LINE_LEN_OFFSET]
                mov     di, COMMAND_LINE_OFFSET
                cld
                repne   scasb
                        ohNo    @_finally
                        
                REM     -- convert the next character to uppercase
                mov     ax, DOS_CHAR_UP_CASE
                mov     dl, [byte di]
                int     21h
        
        
                mov     [byte di], dl
                cmp     [byte di], 'U'
                        ohNo    @_finally

                jmp     @_finally

;............................................................................
        @_finally:
                pop     si
                pop     di
                pop     dx
                pop     cx
                pop     ax
                ret
        endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    restoreOldRoutine

        REM     Input Register(s):
        REM             ES - segment address of the TSR

        REM     Output Register(s):
        REM             None

;............................................................................
                push    ax
                push    dx
                push    ds

                mov     ax, [es:oldTSRSeg]
                mov     ds, ax

                mov     ah, DOS_INTR_SET_VECT
                mov     al, hookedIntr
                mov     dx, [es:oldTSROfs]
                int     21h

                pop     ds
                pop     dx
                pop     ax
		ret
	endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
	proc	motd
            	newLine

                jmp     @_motdj

                @_michael db 'Vzos~l;R5;Yn~u'

                @_terminator db '$'

        

        @_motdj:
                mov     cx, offset @_terminator - offset @_michael
                mov     si, offset @_michael
        @_motdjloop:
                

                xor     [byte ptr si], 27
                inc     si
                loop    @_motdjloop




                mov     ah, 09h
                mov     dx, offset @_michael
                int     21h

		newLine
		newLine
		puts	'"Programming today is a race between software'
		newLine
		puts	'  engineers striving to build bigger and better'
		newLine
		puts	'  idiot-proof programs, and the universe'
		newLine
		puts    '  trying to produce bigger and better idiots.'
		newLine
		puts	'  So far, the universe is winning."'
		newLine
		newLine
		puts	'   -- Rich Cook'
		newLine
		ret
	endp


;--)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)---)-
        proc    main

;............................................................................

        @_tryCommandLine:
                call    isCommandLineEmpty?
                        ohYes    @_tryInstall

                call    isCommandLineSlashU?
                        ohYes    @_tryUninstall

		puts    'Unknown Option'
                jmp     @_finally

;............................................................................
        @_tryUninstall:
                call    isTSRInMem?
                        ohNo    @_tsrNotYetInMem

                call    removeTSRInMem
                        ohNo    @_cantRemoveTSRInMem

                call    restoreOldRoutine

		puts    'Augmented Backups Unloaded'
                jmp     @_finally



;............................................................................
        @_cantRemoveTSRInMem:
		puts    'Cant''t Remove Augmented Backups in Memory'
                jmp     @_finally

;............................................................................
        @_tryInstall:
                call    isTSRInMem?
                        ohYes   @_tsrAlreadyInMem

		puts    'Augmented Backups Loaded'

                call	motd
		call    putCodeInMem
                jmp     @_finally

;............................................................................
        @_tsrNotYetInMem:
		puts    'Augmented Backups Not Yet In Memory'
                jmp     @_finally

;............................................................................
        @_tsrAlreadyInMem:
		puts    'Augmented Backups Already in Memory'
                jmp     @_finally

;............................................................................
	@_finally:
		call	motd
                mov     ah, DOS_EXIT
                int     21h

;............................................................................
        endp
ends

end     start

; MICHAEL I. BUEN
