The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Eighteen (Part 5)

Table of Content

Chapter Nineteen

CHAPTER EIGHTEEN:
RESIDENT PROGRAMS (Part 6)
18.9 - Semiresident Programs
18.9 Semiresident Programs

A semiresident program is one that temporarily loads itself into memory executes another program (a child process) and then removes itself from memory after the child process terminates. Semiresident programs behave like resident programs while the child executes but they do not stay in memory once the child terminates.

The main use for semiresident programs is to extend an existing application or patch an application (the child process). The nice thing about a semiresident program patch is that it does not have to modify the application's ".EXE" file directly on the disk. If for some reason the patch fails you haven't destroyed the '.EXE" file you've only wiped out the object code in memory.

A semiresident application like a TSR has a transient and a resident part. The resident part remains in memory while the child process executes. The transient part initializes the program and then transfers control to the resident part that loads the child application over the resident portion. The transient code patches the interrupt vectors and does all the things a TSR does except it doesn't issue the TSR command. Instead the resident program loads the application into memory and transfers control to that program. When the application returns control to the resident program it exits to DOS using the standard ExitPgm call (ah=4Ch).

While the application is running the resident code behaves like any other TSR. Unless the child process is aware of the semiresident program or the semiresident program patches interrupt vectors the application normally uses the semiresident program will probably be an active resident program patching into one or more of the hardware interrupts. Of course all the rules that apply to active TSRs also apply to active semiresident programs.

The following is a very generic example of s semiresident program. This program "RUN.ASM" runs the application whose name and command line parameters appear as command line parameters to run. In other words:

c:> run pgm.exe parm1 parm2 etc.
is equivalent to
pgm parm1 parm2 etc.

Note that you must supply the ".EXE" or ".COM" extension to the program's filename. This code begins by extracting the program's filename and command line parameters from run's command line. Run builds an exec structure and then calls DOS to execute the program. On return run fixes up the stack and returns to DOS.

; RUN.ASM - The barebones semiresident program.
;
;       Usage:
;               RUN <program.exe> <program's command line>
;        or     RUN <program.com> <program's command line>
;
; RUN executes the specified program with the supplied command line parameters.
; At first
this may seem like a stupid program. After all
why not just run
; the program directly from DOS and skip the RUN altogether? Actually
there
; is a good reason for RUN-- It lets you (by modifying the RUN source file)
; set up some environment prior to running the program and clean up that
; environment after the program terminates ("environment" in this sense does
; not necessarily refer to the MS-DOS ENVIRONMENT area).
;
; For example
I have used this program to switch the mode of a TSR prior to
; executing an EXE file and then I restored the operating mode of that TSR
; after the program terminated.
;
; In general
you should create a new version of RUN.EXE (and
presumbably

; give it a unique name) for each application you want to use this program
; with.
;
;
;----------------------------------------------------------------------------
;
;
; Put these segment definitions 1st because we want the Standard Library
; routines to load last in memory
so they wind up in the transient portion.

CSEG            segment para public 'CODE'
CSEG            ends
SSEG            segment para stack 'stack'
SSEG            ends
ZZZZZZSEG       segment para public 'zzzzzzseg'
ZZZZZZSEG       ends


; Includes for UCR Standard Library macros.

include consts.a
include stdin.a
include stdout.a
include misc.a
include memory.a
include strings.a

includelib stdlib.lib


CSEG            segment para public 'CODE'
assume  cs:cseg
ds:cseg


; Variables used by this program.


; MS-DOS EXEC structure.

ExecStruct      dw      0               ;Use parent's Environment blk.
dd      CmdLine         ;For the cmd ln parms.
dd      DfltFCB
dd      DfltFCB

DfltFCB         db      3
" "
0
0
0
0
0
CmdLine         db      0
0dh
126 dup (" ") ;Cmd line for program.
PgmName         dd      ?               ;Points at pgm name.




Main            proc
mov     ax
cseg        ;Get ptr to vars segment
mov     ds
ax

MemInit                 ;Start the memory mgr.



; If you want to do something before the execution of the command-line
; specified program
here is a good place to do it:



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



; Now let's fetch the program name
etc.
from the command line and execute
; it.

argc                    ;See how many cmd ln parms
or      cx
cx          ; we have.
jz      Quit            ;Just quit if no parameters.

mov     ax
1           ;Get the first parm (pgm name)
argv
mov     word ptr PgmName
di    ;Save ptr to name
mov     word ptr PgmName+2
es


; Okay
for each word on the command line after the filename
copy
; that word to CmdLine buffer and separate each word with a space

; just like COMMAND.COM does with command line parameters it processes.

lea     si
CmdLine+1   ;Index into cmdline.
ParmLoop:       dec     cx
jz      ExecutePgm

inc     ax              ;Point at next parm.
argv                    ;Get the next parm.

push    ax
mov     byte ptr [si]
' ' ;1st item and separator on ln.
inc     CmdLine
inc     si
CpyLp:          mov     al
es:[di]
cmp     al
0
je      StrDone
inc     CmdLine         ;Increment byte cnt
mov     ds:[si]
al
inc     si
inc     di
jmp     CpyLp

StrDone:        mov     byte ptr ds:[si]
cr ;In case this is the end.
pop     ax                   ;Get current parm #
jmp     ParmLoop


; Okay
we've built the MS-DOS execute structure and the necessary
; command line
now let's see about running the program.
; The first step is to free up all the memory that this program
; isn't using. That would be everything from zzzzzzseg on.

ExecutePgm:     mov     ah
62h         ;Get our PSP value
int     21h
mov     es
bx
mov     ax
zzzzzzseg   ;Compute size of
sub     ax
bx          ; resident run code.
mov     bx
ax
mov     ah
4ah         ;Release unused memory.
int     21h

; Warning! No Standard Library calls after this point. We've just
; released the memory that they're sitting in. So the program load
; we're about to do will wipe out the Standard Library code.

mov     bx
seg ExecStruct
mov     es
bx
mov     bx
offset ExecStruct ;Ptr to program record.
lds     dx
PgmName
mov     ax
4b00h       ;Exec pgm
int     21h

; When we get back
we can't count on *anything* being correct. First
fix
; the stack pointer and then we can finish up anything else that needs to
; be done.

mov     ax
sseg
mov     ss
ax
mov     sp
offset EndStk
mov     ax
seg cseg
mov     ds
ax

; Okay
if you have any great deeds to do after the program
this is a
; good place to put such stuff.

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

; Return control to MS-DOS

Quit:           ExitPgm
Main            endp
cseg            ends

sseg            segment para stack 'stack'
dw      128 dup (0)
endstk          dw      ?
sseg            ends

; Set aside some room for the heap.

zzzzzzseg       segment para public 'zzzzzzseg'
Heap            db      200h dup (?)
zzzzzzseg       ends

end     Main

Since RUN.ASM is rather simple perhaps a more complex example is in order. The following is a fully functional patch for the Lucasart's game XWING'. The motivation for this patch can about because of the annoyance of having to look up a password everytime you play the game. This little patch searches for the code that calls the password routine and stores NOPs over that code in memory.

The operation of this code is a little different than that of RUN.ASM. The RUN program sends an execute command to DOS that runs the desired program. All system changes RUN needs to make must be made before or after the application executes. XWPATCH operates a little differently. It loads the XWING.EXE program into memory and searches for some specific code (the call to the password routine). Once it finds this code it stores NOP instructions over the top of the call.

Unfortunately life isn't quite that simple. When XWING.EXE loads the password code isn't yet present in memory. XWING loads that code as an overlay later on. So the XWPATCH program finds something that XWING.EXE does load into memory right away - the joystick code. XWPATCH patches the joystick code so that any call to the joystick routine (when detecting or calibrating the joystick) produces a call to XWPATCH's code that searches for the password code. Once XWPATCH locates and NOPs out the call to the password routine it restores the code in the joystick routine. From that point forward XWPATCH is simply taking up memory space; XWING will never call it again until XWING terminates.

; XWPATCH.ASM
;
;       Usage:
;               XWPATCH - must be in same directory as XWING.EXE
;
; This program executes the XWING.EXE program and patches it to avoid
; having to enter the password every time you run it.
;
; This program is intended for educational purposes only.
; It is a demonstration of how to write a semiresident program.
; It is not intended as a device to allow the piracy of commercial software.
; Such use is illegal and is punishable by law.
;
; This software is offered without warranty or any expectation of
; correctness. Due to the dynamic nature of software design
programs
; that patch other programs may not work with slight changes in the
; patched program (XWING.EXE). USE THIS CODE AT YOUR OWN RISK.
;
;----------------------------------------------------------------------------


byp             textequ <byte ptr>
wp              textequ <word ptr>

; Put these segment definitions here so the UCR Standard Library will
; load after zzzzzzseg (in the transient section).

cseg            segment para public 'CODE'
cseg            ends

sseg            segment para stack 'STACK'
sseg            ends

zzzzzzseg       segment para public 'zzzzzzseg'
zzzzzzseg       ends

.286
include         stdlib.a
includelib      stdlib.lib


CSEG            segment para public 'CODE'
assume  cs:cseg
ds:nothing


; CountJSCalls- Number of times xwing calls the Joystick code before
; we patch out the password call.

CountJSCalls    dw      250


; PSP-  Program Segment Prefix. Needed to free up memory before running
;       the real application program.

PSP             dw      0



; Program Loading data structures (for DOS).

ExecStruct      dw      0                       ;Use parent's Environment blk.
dd      CmdLine                 ;For the cmd ln parms.
dd      DfltFCB
dd      DfltFCB
LoadSSSP                dd      ?
LoadCSIP                dd      ?
PgmName         dd      Pgm

DfltFCB         db      3
" "
0
0
0
0
0
CmdLine         db      2
" "
0dh
16 dup (" ") ;Cmd line for program
Pgm             db      "XWING.EXE"
0


;****************************************************************************
; XWPATCH begins here. This is the memory resident part. Only put code
; which which has to be present at run-time or needs to be resident after
; freeing up memory.
;****************************************************************************

Main            proc
mov     cs:PSP
ds
mov     ax
cseg                ;Get ptr to vars segment
mov     ds
ax

mov     ax
zzzzzzseg
mov     es
ax
mov     cx
1024/16
meminit2


; Now
free up memory from ZZZZZZSEG on to make room for XWING.
; Note: Absolutely no calls to UCR Standard Library routines from
; this point forward! (ExitPgm is okay
it's just a macro which calls DOS.)
; Note that after the execution of this code
none of the code & data
; from zzzzzzseg on is valid.

mov     bx
zzzzzzseg
sub     bx
PSP
inc     bx
mov     es
PSP
mov     ah
4ah
int     21h
jnc     GoodRealloc

; Okay
I lied. Here's a StdLib call
but it's okay because we failed
; to load the application over the top of the standard library code.
; But from this point on
absolutely no more calls!

print
byte    "Memory allocation error."
byte    cr
lf
0
jmp     Quit

GoodRealloc:

; Now load the XWING program into memory:

mov     bx
seg ExecStruct
mov     es
bx
mov     bx
offset ExecStruct ;Ptr to program record.
lds     dx
PgmName
mov     ax
4b01h             ;Load
do not exec
pgm
int     21h
jc      Quit                    ;If error loading file.

; Unfortunately
the password code gets loaded dynamically later on.
; So it's not anywhere in memory where we can search for it. But we
; do know that the joystick code is in memory
so we'll search for
; that code. Once we find it
we'll patch it so it calls our SearchPW
; routine. Note that you must use a joystick (and have one installed)
; for this patch to work properly.

mov     si
zzzzzzseg
mov     ds
si
xor     si
si

mov     di
cs
mov     es
di
mov     di
offset JoyStickCode
mov     cx
JoyLength
call    FindCode
jc      Quit                    ;If didn't find joystick code.


; Patch the XWING joystick code here

mov     byp ds:[si]
09ah       ;Far call
mov     wp ds:[si+1]
offset SearchPW
mov     wp ds:[si+3]
cs

; Okay
start the XWING.EXE program running

mov     ah
62h                 ;Get PSP
int     21h
mov     ds
bx
mov     es
bx
mov     wp ds:[10]
offset Quit
mov     wp ds:[12]
cs
mov     ss
wp cseg:LoadSSSP+2
mov     sp
wp cseg:LoadSSSP
jmp     dword ptr cseg:LoadCSIP


Quit:           ExitPgm
Main            endp


; SearchPW gets call from XWING when it attempts to calibrate the joystick.
; We'll let XWING call the joystick several hundred times before we
; actually search for the password code. The reason we do this is because
; XWING calls the joystick code early on to test for the presence of a
; joystick. Once we get into the calibration code
however
it calls
; the joystick code repetitively
so a few hundred calls doesn't take
; very long to expire. Once we're in the calibration code
the password
; code has been loaded into memory
so we can search for it then.

SearchPW        proc    far
cmp     cs:CountJSCalls
0
je      DoSearch
dec     cs:CountJSCalls
sti                             ;Code we stole from xwing for
neg     bx                      ; the patch.
neg     di
ret

; Okay
search for the password code.

DoSearch:       push    bp
mov     bp
sp
push    ds
push    es
pusha

; Search for the password code in memory:

mov     si
zzzzzzseg
mov     ds
si
xor     si
si

mov     di
cs
mov     es
di
mov     di
offset PasswordCode
mov     cx
PWLength
call    FindCode
jc      NotThere                ;If didn't find pw code.


; Patch the XWING password code here. Just store NOPs over the five
; bytes of the far call to the password routine.

mov     byp ds:[si+11]
090h    ;NOP out a far call
mov     byp ds:[si+12]
090h
mov     byp ds:[si+13]
090h
mov     byp ds:[si+14]
090h
mov     byp ds:[si+15]
090h

; Adjust the return address and restore the patched joystick code so
; that it doesn't bother jumping to us anymore.

NotThere:       sub     word ptr [bp+2]
5      ;Back up return address.
les     bx
[bp+2]              ;Fetch return address.

; Store the original joystick code over the call we patched to this
; routine.

mov     ax
word ptr JoyStickCode
mov     es:[bx]
ax
mov     ax
word ptr JoyStickCode+2
mov     es:[bx+2]
ax
mov     al
byte ptr JoyStickCode+4
mov     es:[bx+4]
al

popa
pop     es
pop     ds
pop     bp
ret
SearchPW        endp

;****************************************************************************
;
; FindCode: On entry
ES:DI points at some code in *this* program which
;        appears in the XWING game. DS:SI points at a block of memory
;        in the XWING game. FindCode searches through memory to find the
;        suspect piece of code and returns DS:SI pointing at the start of
;        that code. This code assumes that it *will* find the code!
;        It returns the carry clear if it finds it
set if it doesn't.

FindCode        proc    near
push    ax
push    bx
push    dx

DoCmp:          mov     dx
1000h       ;Search in 4K blocks.
CmpLoop:        push    di              ;Save ptr to compare code.
push    si              ;Save ptr to start of string.
push    cx              ;Save count.
repe    cmpsb
pop     cx
pop     si
pop     di
je      FoundCode
inc     si
dec     dx
jne     CmpLoop
sub     si
1000h
mov     ax
ds
inc     ah
mov     ds
ax
cmp     ax
9000h       ;Stop at address 9000:0
jb      DoCmp           ; and fail if not found.

pop     dx
pop     bx
pop     ax
stc
ret

FoundCode:      pop     dx
pop     bx
pop     ax
clc
ret
FindCode        endp


;****************************************************************************
;
; Call to password code that appears in the XWING game. This is actually
; data that we're going to search for in the XWING object code.

PasswordCode    proc    near
call    $+47h
mov     [bp-4]
ax
mov     [bp-2]
dx
push    dx
push    ax
byte    9ah
04h
00
PasswordCode    endp
EndPW:

PWLength        =       EndPW-PasswordCode


; The following is the joystick code we're going to search for.

JoyStickCode    proc    near
sti
neg     bx
neg     di
pop     bp
pop     dx
pop     cx
ret
mov     bp
bx
in      al
dx
mov     bl
al
not     al
and     al
ah
jnz     $+11h
in      al
dx
JoyStickCode    endp
EndJSC:

JoyLength       =       EndJSC-JoyStickCode
cseg            ends

sseg            segment para stack 'STACK'
dw      256 dup (0)
endstk          dw      ?
sseg            ends

zzzzzzseg       segment para public 'zzzzzzseg'
Heap            db      1024 dup (0)
zzzzzzseg       ends
end     Main

Chapter Eighteen (Part 5)

Table of Content

Chapter Nineteen

Chapter Eighteen: Resident Programs (Part 6)
29 SEP 1996