ah
=4Ch).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