Drip

Well... When I was a younger lad, one of my favorite jokes to play on folks was to install a program called 'Drip'. This was an 'invisible' TSR that would act at random (but decreasing) time intervals, selecting a character from the screen-map and 'dripping' it down the screen until just before it contacted another character. Of course, this didn't hurt anything, but certainly caused some humorous reactions (e.g. "EEEK! My screen is melting!!").

;a86 morasf7.a86 morasf7.com
;---------------------------------------------------------------------;
; COS 230        Final - Problem 7       Sean P. O. MacCath Moran     ;
;                                                                     ;
; Assignment:                                                         ;
;   Weird Screen TSR.  Do something weird with the screen.            ;
;                                                                     ;
; Description:                                                        ;
;   Well... When I was a younger lad, one of my favorite jokes to     ;
;   play on folks was to install a program called 'Drip'.             ;
;   This was an 'invisible' TSR that would act at random (but         ;
;   decreasing) time intervals, selecting a character from the        ;
;   screen-map and 'dripping' it down the screen until just before it ;
;   contacted another character.  Of course, this didn't hurt         ;
;   anything, but certainly caused some humorous reactions            ;
;   (i.e. "EEEK!  My screen is melting!!").                           ;
;                                                                     ;
;   (The following documentation does not apply.  I could not figure  ;
;    out how to get the command line arguments to save with the       ;
;    program when it was moved to memory...)                          ;
;                                                                     ;
;   This prog will accept two command line arguments.  The first one  ;
;   specifies the number of ticks to wait before starting the screen  ;
;   melt.  If ommited, the prog will wait five minutes (5460 ticks).  ;
;   After each drip, the program reduces the number of ticks to wait  ;
;   by 10.                                                            ;
;                                                                     ;
;   The second argument specifies the number of lines on the screen.  ;
;   If ommitted, 25 lines are assumed.                                ;
;                                                                     ;
;   WARNING:  No error checking is performed against the arguments    ;
;---------------------------------------------------------------------;
jmp install
MaxCol      DW  0   ;Maximum number of columns displayed on the screen
MaxColReal  DW  0   ;Maximum number bytes to display row
MaxRow      DW  25d ;Maximum number of rows on the screen
LastRow     DW  0   ;Byte offset position of start of last row
CurRow      DW  0   ;Temp storage of current row
CurCol      DW  0   ;Temp storage of current column
CurChar     DW  0   ;Char offset being manipulated
ScrnOffset  DW  08000h
TICKS       DW  15 ;5460    ; time delay in ticks (1092/min) (5460/5min)
; can set low for debugging and testing
add8        dd  ?   ; old interrupt 8 vector
count       dw  0   ; count to activity in ticks (3276=3 min)
busy        db  0   ; indicates TSR currently active, to 
; prevent re-entrant execution
initDone    db  0   ; indicates if initialization has been called
args        DB  0FFh DUP (?)  ;Provide 255 chars to store all args.
argsLen     DB  0FFh
argsCount   DB  0
argSto      DB  064h DUP (?)   ; storage for numeric results
hexSto      DW  0
argStoLen   DW  064h
;---------------------------------------------------------------------;
; Interrupt handler for INT 8.  With every tick, do the following:    ;
;   -Check kflag to see if key pressed since last tick                ;
;      NO:  decrement the countdown byte.                             ;
;           if 0, dim the VGA palette registers and set pflag to 1    ;
;      YES: Check to see if screen dimmed (pflag=1)                   ;
;           and restore the screen if so.                             ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; Interrupt handler for INT 8.  With every tick, do the following:    ;
;                                                                     ;
; Input Parameters:                                                   ;
;   None                                                              ;
;                                                                     ;
;  Returns:                                                           ;
;   None                                                              ;
;                                                                     ;
;---------------------------------------------------------------------;
vec8:
cmp CS:busy, 0                 ; check the busy flag
je vec81                       ; if not active continue
jmp far CS:add8                ; otherwise quit to old int 8 handler
vec81:
mov CS:busy, 1                 ; indicate TSR active now
pushf                          ; prepare to execute old int 8 
call far CS:add8               ; which expects flags, CS, IP on stack
sti                            ; enable interrupts  
dec CS:count                   ; decrement tick count
jz vec82                       ; if time has lapsed then cause a drip
vec811:
mov CS:busy, 0                 ; otherwise, 
IRET                           ; we're done
vec82:
pusha                          ; save all registers
push es
push ds                        ; 
cli                            ; disable interrupts during drip
mov es, 0B000h                 ; point the extra seg to the screen's text buffer
cmp CS:initDone, 0
jne Drip10
mov CS:initDone, 1             ; indicate TSR active now
call RandomInit
Drip10:
call GetNewRowCol              ; get the next random column/row offset
mov bx, CS:CurChar             ; get the character at the new position
mov ax, es:[bx]                ; grab the character at the current position
cmp al, 020h                   ;  and test to see if a space was selected
je Done                        ; jump out if selected char is a space
mov dx, CS:MaxColReal          ; add the bytes necessary to get to next row
Drip50:
add bx, dx                     ;  to the current position.
mov di, CS:LastRow             ; if we have exceeded the capacity of the screen
cmp bx, di                     ;  then get out
jg Done
mov cx, es:[bx]                ; test character beneath selected one
cmp cl, 020h                   ; if char below is not a space
jne done                       ;  then jump out
mov es:[bx], al                ; swap the two chars so that the target char appears
sub bx, dx
mov es:[bx], cl                ;  to drip down the screen one space
add bx, dx
mov ax, es:[bx]                ; grab the character at the current position
jmp Drip50
Done:
mov ax, TICKS
mov cs:count, ax               ; reset counter 
pop ds                         ; restore save regs
pop es
popa
sti                            ; enable interrupts  
mov CS:busy, 0                 ; and that we're no longer busy
IRET
mov ax, 4C00h                  ; DOS Terminate program function
int 21H
;---------------------------------------------------------------------;
; GetNewRowCol  Call GetNewCol and GetNewRow to get a new character.  ;
;         Make 10 attempts to find a position with a character        ;
;         other than a space and then give up...                      ;
;                                                                     ;
; Input Parameters:                                                   ;
;   None                                                              ;
;                                                                     ;
;  Returns:                                                           ;
;   CurCol and CurRow are populated with new column and row           ;
;                                                                     ;
;---------------------------------------------------------------------;
GetNewRowCol:
push ax
push bx
push cx
push dx
mov cx, 0Ah                    ; make 10 attempts at finding a non-space char.
GetNewRowCol10:
push offset CurCol
call GetCol                    ; get the next random column offset
push offset CurRow
call GetRow                    ; get the next random row offset
mov bx, CS:CurRow              ; point bx to the next random column
add bx, CS:CurCol              ; add the random column value to this
mov al, es:[bx]                ; get the char at the random position
cmp al, 020h                   ; test to see if it is a space
jne GetNewRowCol20             ; exit loop if it is
loop GetNewRowCol10
GetNewRowCol20:
mov CS:CurChar, bx
GetNewRowColTest:
pop dx
pop cx
pop bx
pop ax
ret
;---------------------------------------------------------------------;
;   END GetNewRowCol                                                  ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; GetCol    Use Random function to select next column and store       ;
;       results in CurCol.                                            ;
;                                                                     ;
; Input Parameters:                                                   ;
;   1st: Storage address for the new column value                     ;
;                                                                     ;
;  Returns:                                                           ;
;   Address specified by 1st has new random column value              ;
;                                                                     ;
;---------------------------------------------------------------------;
DestCol   EQU word ptr [bp+04h]
GetCol:
push bp                        ; save callers bp
mov bp, sp                     ; point current bp to the stack frame
push ax
push bx
push cx
push dx
push di
mov di, [DestCol]
push 0                         ; lower range of random number
push CS:MaxCol                 ; upper range of random number
push di                        ; storage for random number
call Random
mov ax, 02h                    ; mult by 2 because each column in memory is two bytes
mov cx, CS:[di]
mul cx
mov CS:[di], ax                ; and store the column value again
GetColTest:
pop di
pop dx
pop cx
pop bx
pop ax
pop bp
ret 2
;---------------------------------------------------------------------;
;   END GetCol                                                        ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; GetRow    Use Random function to select next row and store          ;
;       results in CurRow                                             ;
;                                                                     ;
; Input Parameters:                                                   ;
;   1st: Storage address for the new row value                        ;
;                                                                     ;
;  Returns:                                                           ;
;   Address specified by 1st has new row row value                    ;
;                                                                     ;
;---------------------------------------------------------------------;
DestRow   EQU word ptr [bp+04h]
GetRow:
push bp                        ; save callers bp
mov bp, sp                     ; point current bp to the stack frame
push ax
push bx
push cx
push dx
push di
mov di, [DestRow]
push 0                         ; lower range of random number
push CS:MaxRow                 ; upper range of random number
push di                        ; storage for random number
call Random
mov ax, CS:MaxColReal          ; mult by 160 because each row in memory 
mov cx, CS:[di]                ;  begins in 160 byte increments
mul cx
add ax, CS:ScrnOffset          ; add the beginning address info (B000:8000)
mov CS:[di], ax                ; and store the column value again
pop di
pop dx
pop cx
pop bx
pop ax
pop bp
ret 2
;---------------------------------------------------------------------;
;   END GetRow                                                        ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; Random     Generate a random number within a given range.           ;
;  This code is a modified version of an implementation by Wilson Seto;
;  REF: http://www.cs.nyu.edu/courses/fall98/V22.0201-002/ranasm.html ;
;                                                                     ;
; Input Parameters:                                                   ;
;   1st: The lower bound of the random number range                   ;
;   2nd: The upper bound of the random number range                   ;
;   3rd: The memory address for the memory address to store the word  ;
;                                                                     ;
;  Returns:                                                           ;
;    Carry Flag indicates success or failure                          ;
;    All other regs preserved                                         ;
;                                                                     ;
;---------------------------------------------------------------------;
RandLow   EQU word ptr [bp+08h]
Randhigh  EQU word ptr [bp+06h]
RandSto   EQU word ptr [bp+04h]
constant  dw   8405h           ;multiplier value
seed1   dw     ?
seed2   dw     ?               ;random number seeds
;---------------------------------------------------------------------;
; Randomization and Screen Info Initialization Routine                ;
;---------------------------------------------------------------------;
RandomInit:
push ax
push bx
push cx
push dx
mov    ah,2ch                  ;get current time
int    21h
mov    CS:seed1,cx
mov    CS:seed2,dx             ;save as initial seeds
mov ah, 0Fh                    ; Get the current video mode
int 10h                        ;  ah = chars per line, al = vid mode, bh = page
mov al, ah                     ; convert ah to a word in ax
sub ah, ah
mov CS:MaxCol, ax              ; sto ax (chars per line as a word)
mov cx, 02h                    ; multiply by a factor of two to retrieve the
mul cx                         ;  number of bytes necessary to store a row
mov CS:MaxColReal, ax          ;  and store that value for later use
mov cx, CS:MaxRow              ; multiply the maximum number of rows allowed on the screen
mul cx                         ;  by the bytes per row
add ax, 08000h                 ;  and add the 8000 bytes up to the first row's offset
mov CS:LastRow, ax             ;  to get the starting position of the last row
pop dx
pop cx
pop bx
pop ax
ret
;---------------------------------------------------------------------;
; Randomization Routine                                               ;
;---------------------------------------------------------------------;
Random:
push bp                        ; save callers bp
mov bp, sp                     ; point current bp to the stack frame
push ax
push bx
push cx
push dx
push di
mov ax, [RandHigh]             ; subtract the low value 
sub ax, [RandLow]              ;  from the high value
push   ax                      ;save the range value
mov    ax, CS:seed1
mov    bx, CS:seed2            ;load seeds
mov    cx,ax                   ;save seed
mul    CS:constant             ;(dx,ax) = ax * constant
shl    cx,1
shl    cx,1
shl    cx,1
add    ch,cl
add    dx,cx
add    dx,bx
shl    bx,1                    ;begin scramble algorithm
shl    bx,1
add    dx,bx
add    dh,bl
mov    cl,5
shl    bx,cl
add    ax,1
adc    dx,0
mov    CS:seed1, ax
mov    CS:seed2, dx            ;save results as the new seeds
pop    bx                      ;get back range value
xor    ax,ax                   ;clear register
xchg   ax,dx                   ;adjust ordering
div    bx                      ;ax = trunc((dx,ax) / bx), dx = (r)
xchg   ax, dx                  ;return remainder as the random number
mov di, [RandSto]
mov CS:[di], ax
pop di
pop dx
pop cx
pop bx
pop ax
pop bp                         ; restore callers bp
ret 6                          ; clear stack params
;---------------------------------------------------------------------;
;   END Random                                                        ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; StdOut       Copies the contents of a buffer into another,          ;
;          converting it to all capital letters.                      ;
;                                                                     ;
; Input Parameters:                                                   ;
;   CX       Number of bytes to write                                 ;
;   DS:DX    start of output buffer                                   ;
;                                                                     ;
;  Returns:                                                           ;
;    All regs except flags preserved                                  ;
;                                                                     ;
;---------------------------------------------------------------------;
;StdOut:
;  push ax
;  push bx
;
;  mov bx, 1
;  mov ah, 40h                   ; set up for int 21h
;  int 21h
;
;  pop bx
;  pop ax
;  ret
;---------------------------------------------------------------------;
;   END StdOut                                                        ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; TESTING      Outputs 'TEST' to StdOut for debugging use             ;
;---------------------------------------------------------------------;
TestText     DB 'TEST'
TestTextLen  EQU  $-TestText
TESTING:
push ax
push bx
push cx
push dx
mov cx, CS:TestTextLen         ; setup stdout call
mov dx, offset TestText        ;   to print the word TEST
mov bx, 1
mov ah, 40h                    ; set up for int 21h
int 21h
;  call StdOut                   ; used for testing purposes
pop dx
pop cx
pop bx
pop ax
ret
;---------------------------------------------------------------------;
;   END TESTING                                                       ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; Install   Installs prog as a TSR.  All functions following          ;
;           this routine will not be installed with the program.      ;
;                                                                     ;
; Input Parameters:                                                   ;
;   None                                                              ;
;                                                                     ;
;  Returns:                                                           ;
;   None                                                              ;
;                                                                     ;
;---------------------------------------------------------------------;
Install:
mov bx, offset argsLen         ; args will store the command line arguments
push bx
mov bx, offset argsCount
push bx
call StoreArgs                 ; populate args with the command line values
mov bl, argsCount
cmp bl, 2
jne Arg210
push 02h
push offset argsLen
push offset argSto
push offset argStoLen
call ReturnArg
push offset argSto
push offset hexSto
call DecStr2BinHex
mov MaxRow, hexSto
Arg210:
cmp bl, 1
jne Arg110
push 01h
push offset argsLen
push offset argSto
push offset argStoLen
call ReturnArg
push offset argSto
push offset hexSto
call DecStr2BinHex
mov TICKS, hexSto
Arg110:
mov bx, TICKS                  ; set the count to the default value...
mov count, bx
mov ax, 3508h                  ; get old interrupt 8 address
int 21h
mov word ptr add8, bx          ; save it
mov word ptr add8+2, es
mov ax, 2508h                  ; hook interrupt 8
mov dx, offset vec8
int 21h 
mov es, es:[2ch]               ; release environment block
mov ax, 4900h
int 21h
mov dx, offset install         ; prepare to go resident
shr dx, 4                      ; find paragraphs occupied by resident part
inc dx                         ; for odd bytes left over
mov ax, 3100h       
int 21h
;---------------------------------------------------------------------;
;   END Install                                                       ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; StoreArgs  Retrieve the command line args are store them in a       ;
;            variable length array.                                   ;
;                                                                     ;
; Input Parameters:                                                   ;
;   DS:  Points to the base address of the storage area               ;
;   ES:  Points to the base address of PSP                            ;
;   1st: Points to the start of the word that has the length of       ;
;      the storage area.  The actually storage area precedes this     ;
;      word and is assumed to be stored by byte.                      ;
;   2nd: Points to the byte storage location for the number of args   ;
;      found                                                          ;
;                                                                     ;
;  Returns:                                                           ;
;    Carry Flag indicates success or failure                          ;
;    All other regs preserved                                         ;
;                                                                     ;
;---------------------------------------------------------------------;
StoLoc    EQU word ptr [bp+06h]
ArgsFound EQU word ptr [bp+04h]
ArgLen    EQU word ptr [bp-0Ch]
StoLen    EQU word ptr [bp-0Eh]
TotLen    EQU word ptr [bp-010h]
StoreArgs:
push bp                        ; save callers bp
mov bp, sp                     ; point current bp to the stack frame
push ax
push bx
push cx
push dx
push di
push si
sub sp, 06h                    ; make room for ArgLen, StoLen, and TotLen
sub ax, ax
sub cx, cx
mov bx, [ArgsFound]            ; grab the memory address of the args found counter
mov [bx], al                   ;  and zero it out
mov di, [StoLoc]               ; retrieve the location of the storage area length
mov cx, [di]                   ;  and then the actual storage area length
mov [StoLen], cx               ;  and then store it in StoLen
dec di                         ;  and then bring it back to the last available space
mov cl,ES:[080h]               ; get tail length into cl
jcxz NotFound                  ; if CX=0 there are no cmd tails args
mov si,81h                     ; set si to first byte of command tail ...
add si,cx                      ;  ... then move to the last byte of the command tail
; skip leading blanks before arg
skipBlanks:
call CharScan                  ; scan for the next non space, non 0D char
jnz FoundArg                   ; if non-blank then copy this argument into the destination buffer
jmp GetArgExit                 ; if blank we ran out of tail
; copy found argument into dest buffer
FoundArg:
mov [ArgLen], 0h               ; zero out the argument length accumulator
dec di                         ; adjust di down one to make room for the arg length val later
CopyArg10:
jcxz GetArgDone                ; if out of chars then we're done
mov al, es:[si]                ; copy char from PSP cmd tail...
cmp al, ' '                    ; if this char is a space then
je GetArgDone                  ;  we are at the end of this arg
inc [ArgLen]                   ; increase argument length accumulator
call CopyOne                   ; copy one char over and increment regs
jmp CopyArg10
GetArgDone:
mov ax, [ArgLen]               ; move the arg lenth into ax for temp storage
inc [ArgLen]                   ; increase lengh of arg (used as pointer relocation value)
add di, [ArgLen]               ; move destination pointer one past end of arg's text
mov [di],al                    ; insert the length of string at end of arg text
sub di, [ArgLen]               ; move the destination string pointer back to the start of the arg ...
inc byte ptr [bx]              ; incrememnt the number of args found (bx is set above)
jcxz GetArgExit                ; if out of chars then we're done
jmp skipBlanks                 ; get next argument into the buffer
NotFound:
add sp, 06h                    ; restore local storage
stc                            ; no arg found so set carry flag
jmp RestoreRegs
GetArgExit:
add sp, 06h                    ; restore local storage
clc                            ; clear carry to indicate success
RestoreRegs:
pop si                         ; restore callers regs
pop di
pop dx
pop cx
pop bx
pop ax
pop bp                         ; restore callers bp
ret 4                          ; clear stack params
;---------------------------------------------------------------------;
;   END StoreArgs                             ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; CharScan    Scans ES:SI, going UP the string, stopping at the first ;
;             occurance of a char that is neither a space or an OD.   ;
;             I wrote this because repe scasb is a pain in the        ;
;             rectal cavity.                                          ;
;                                                                     ;
; Input Parameters:                                                   ;
;   SI: Points to the char to start search from                       ;
;   CX: MAX number of characters to search                            ;
;                                                                     ;
;  Returns:                                                           ;
;    si point to the non-space, non-0D character                      ;
;    CX is decremented the number of times characters moved           ;
;    All other regs except flags preserved                            ;
;                                                                     ;
;---------------------------------------------------------------------;
CharScan:
push ax                        ;preserve AX
sub ax, ax                     ; zero out ax
ChSc10:
mov al, es:[si]                ; get the letter at si
cmp al, ' '                    ; if this is a space
je ChSc20                      ;  then keep searching
cmp al, 0dh                    ; if this is an enter
je ChSc20                      ;  then keep searching
jmp ChSc30                     ; otherwise, be done with it
ChSc20:
dec si                         ; point to the next item to search
dec cx                         ; decrement the max char counter
jcxz ChSc30                    ;  and exit if CX reaches zero
jmp ChSc10                     ;
ChSc30:
pop ax
ret
;---------------------------------------------------------------------;
;   END CharScan                                                      ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; CopyOne   Copies on char from source to dest and increments all     ;
;        registers as appropriate.  For use with the StoreArgs        ;
;        process.                                                     ;
;                                                                     ;
;---------------------------------------------------------------------;
CopyOne:
mov [di], al                   ; copy char to buffer at [di]
dec di                         ; adjust pointers
dec si
dec cx                         ; decrement length counter
ret
;---------------------------------------------------------------------;
;   END CopyOne                                                       ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; ReturnArg    Retrieve a command line argument from a string         ;
;          created by StoreArg                                        ;
;                                                                     ;
; Input Parameters:                                                   ;
;   DS:  Points to the base address of the storage area               ;
;   1st (was AL): Parameter number to retrive                         ;
;   2nd: Points to the start of the word that has the length of       ;
;      the storage area.  The actual storage area precedes this       ;
;      word and is assumed to be stored by byte.                      ;
;   3rd (was SI): Start address of the argument string storage area   ;
;   4th (was BX): Max Length of argument string storage area          ;
;                                                                     ;
;  Returns:                                                           ;
;    Carry Flag indicates success or failure                          ;
;    All regs except flags preserved                                  ;
;                                                                     ;
;---------------------------------------------------------------------;
ParamNum  EQU word ptr [bp+0Ah]
SrceSto   EQU word ptr [bp+08h]  ; dest sto and source sto both point to the end
DestSto   EQU word ptr [bp+06h]  ;  of the string where the string len is stored
Alignment EQU word ptr [bp+04h]  ; 0 = left, 1 = right
ArgLenR    EQU word ptr [bp-0Fh]
StoLenR    EQU word ptr [bp-010h]
DestLen   EQU word ptr [bp-012h]
ReturnArg:
push bp                        ; save callers bp
mov bp, sp                     ; point current bp to the stack frame
push ax
push bx
push dx
push si
push di
sub sp, 06h                    ; make room for ArgLenR and StoLenR
sub ax, ax
sub bx, bx
sub cx, cx
sub dx, dx
mov dx, [Alignment]            ; move the value in alignment to dx for later reference
mov si, [SrceSto]              ; retrieve the location of the storage area length
mov cl, [si]                   ;  and then the actual storage area length
mov [StoLenR], cx              ;  and then store it in StoLenR
dec si                         ;  and then bring it back to the last available space
mov di, [DestSto]              ; place the destination address into DI
mov cx, [di]                   ; get the max allowable length of destination
mov [DestLen], cx              ;  and store it for later reference
cmp dx, 00h                    ; if Alignment is not set to 0
jnz Align10                    ;  then setup DI for right align
sub di, [DestLen]              ; otherwise, move di to the begining of the string for left align
jmp Align20
Align10:
dec di                         ; move to the last available storage location of destination
Align20:
sub cx, cx                     ; prep registers for use
sub bx, bx
mov ax, [ParamNum]             ; retrieve the parameter number to be returned
; Move to the start of the arguments available in the string
mov bl,[si]                    ; load message length into bl
cmp bl, 0h                     ; check to see if any arguments are available
jz NotFoundR                   ;  and get out of dodge if not
MoveToFirstArg:
sub si, bx                     ; move si to start of string
dec si                         ; move si to previos arg length
mov bl,[si]                    ; load message length into bl
inc cx                         ; increment the count of num of aguments up
cmp bl, 0h                     ; check to see if we are at end of available args
jnz MoveToFirstArg             ;  ... and continue on up if not
inc si                         ; set si on to first available character
cmp ax, cx                     ; test if greater number of parameters than availabe
jg NotFoundR                   ;  and fail process if so.
mov si, [SrceSto]              ; retrieve end position back into SI
;move to the beginning of the requested argument
sub bx, bx                     ; zero out bx for use
sub cx, ax                     ; subtract the desired position from the number available
inc cx                         ; increment up by one.
FindArg:
sub si, bx                     ; move si start start of string
dec si                         ; point si at the next available message len
mov bl,[si]                    ; load message length into bl
dec cx                         ; decrease argument counter
jnz FindArg                    ; if not at desired arg then move to next one
mov cx, bx                     ; cx is returned with the len of returned arg
cmp dx, 00h                    ; if Alignment is not set to 0
jnz Align30                    ;  then setup SI for right align copying
sub si, bx                     ; otherwise, move si to the begining of the string for left align
jmp Align40
Align30:
dec si                         ; move to the last available storage location of destination
Align40:
CopyArg:
mov al, [si]                   ; copy char from arg source
mov [di], al                   ; to buffer at [si]
cmp dx, 00h                    ; Check alignment
jnz Align50                    ;
inc si                         ; if performing a left aligned copy then
inc di                         ;  adjust pointers down the string
jmp Align60
Align50:
dec si                         ; if performing a right aligned copy then
dec di                         ;  adjust pointers up the string
Align60:
dec bx                         ; decrement the number of positions left
jnz CopyArg                    ;  and continue loop if more are left
mov byte ptr [di],0            ; make copied arg an ASCIIZ string
jmp GetArgExitR                ; ok, all done now, bye bye
NotFoundR:
sub cx, cx                     ; zero out cx - no param so no length
add sp, 06h                    ; restore local storage
stc                            ; no arg found set carry flag
jmp RestoreRegsR
GetArgExitR:
add sp, 06h                    ; restore local storage
clc                            ; clear carry to indicate success
RestoreRegsR:
pop di
pop si                         ; restore regs
pop dx
pop bx
pop ax
pop bp                         ; restore callers bp
ret 8                          ; clear stack params
;---------------------------------------------------------------------;
;   END ReturnArg                                                     ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; DecStr2BinHex     Converts an ASCII string of decimal numbers to    ;
;           a binary byte (DB).                                       ;
;                                                                     ;
; Input Parameters on Stack:                                          ;
;   1st:  Address of ACCIIZ decimal text to convert                   ;
;   2nd:  Address to store byte (DB)                                  ;
;                                                                     ;
;  Returns:                                                           ;
;   All regs including flags preserved                                ;
;   Stack is cleaned up on return                                     ;
;                                                                     ;
;---------------------------------------------------------------------;
D2BDecStr   EQU [bp+6]
D2BOutput   EQU [bp+4]
D2BIsNeg    DB 0h              ; flag tells if dealing with negative #
DecStr2BinHex:
push bp                        ; save caller's bp
mov bp, sp                     ; get frame pointer
push ax                        ; save registers used
push bx
push cx
push dx
push si
push di
pushf                          ; save the flags
mov si, [D2BDecStr]            ; retrieve the address of the hex string
push si                        ; and push it onto the stack as a param for
call StrLen                    ;  call to StrLen.  Len returned in AX.
mov cx, ax
mov di, 0Ah                    ; will divide by 10
mov D2BIsNeg, 0                ; assume we have a positive number
sub ax, ax                     ; beginning result = 0
cmp byte ptr [si], '-'         ; is first char a minus sign?
jne dec2bin10
mov D2BIsNeg, 1                ; set flag to indicate YES
inc si                         ;  and adjust buffer pointer
dec cx                         ;  and char count accordingly
dec2bin10:
mul di                         ; multiply result so far by 10
mov dl, [si]                   ; get next digit
sub dh, dh
sub dl, 30h                    ; convert ASCII to binary
add ax, dx                     ; add to result so far
inc si
loop dec2bin10
cmp D2BIsNeg, 1                ; if the number was negative
jne dec2bin20
neg ax
dec2bin20:
mov bx, [D2BOutput]
mov [bx], al
popf                           ; restore the flags
pop di                         ; restore registers
pop si
pop dx
pop cx
pop bx
pop ax
pop bp
ret 4                          ; return with a clearing of the stack parameter
;---------------------------------------------------------------------;
;   END DecStr2BinHex                                                 ;
;---------------------------------------------------------------------;
;---------------------------------------------------------------------;
; StrLen    returns in AX the length of the ASCIIZ string whose       ;
;       address is at BP + 4.                                         ;
;                                                                     ;
; Input Parameters on Stack:                                          ;
;   1st:  Start of an ASCIIZ string to retrieve length from           ;
;                                                                     ;
;  Returns:                                                           ;
;   AX = length of the string                                         ;
;   All other regs including flags preserved                          ;
;   Stack is cleaned up on return                                     ;
;                                                                     ;
;---------------------------------------------------------------------;
SLStr  EQU [bp+4]
StrLen:
push bp                        ; save caller's bp
mov bp, sp                     ; get frame pointer
push cx                        ; save registers used
push di
pushf                          ; save the flags
mov di, [SLStr]                ; get pointer placed on stack for this function
push di                        ;  and save it for length calculation later
mov cx, 0ffffh                 ; move the max number of allowable chars to cx
sub ax, ax                     ; clear out al so scan will look for 00h
cld                            ; clear direction flag - string searches go up
repnz scasb                    ; scan for value in al until it is found
jnz L10
dec di                         ; point back to 0 byte
L10:
mov ax, di                     ; move the address that scan left us at into ax
pop di                         ; restore di, which points to the start of string
sub ax, di                     ; set ax to the length of the ASCIIZ string
popf                           ; restore the flags
pop di                         ; restore registers
pop cx
pop bp
ret 2                          ; return with a clearing of the stack parameter
;---------------------------------------------------------------------;
;   END StrLen                                                        ;
;---------------------------------------------------------------------;