; 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
		matchfuncs


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
