Source file: /~heha/hs/dos/dosmisc.zip/SRC/FPATCH.ASM

;FilePatcher by haftmann#software

;Limitations:
;Max. search pattern length: 255 bytes (by design)
;Max. replace pattern length: same as search pattern length (by design)
;Max. replace locations per file: 32 (by MaxPos constant)
;Max. file size: 2G-1 bytes (by system: DOS)

;Characteristics:
;file scan buffer: 4KB+4KB double buffer (by BufSize constant)
;resolve of wrap-around: by copying few bytes from upper to lower buffer
;program parses the command line once for each file (poor, saves memory)

;Change log:
;11/06: - fixed bug with uninitialized data near PosMin2
;	- handling negative positions introduced by -p-xxx
;	* errorlevel == 1 when nothing found
;
;09/01:	+ unicode (wide char) support (no OEM translation), option u
;	+ redirection of stdin for search pattern
;	* shortened hex address output for short files
;	* code optimized: "bset" (=SETFLAG) instead of "or" and such
;
;10/94: + p option for offsetting found position(s)
;
;1994:	initial version as cracking aid (DiskEdit sucks)

;To do:
;+ Replace pattern from file
;* Structuring of source (proc/endp)
;* far scan buffer 64KB with automatic wrap-around (great idea)
;+ true inserting and deleting, moving rest of file
;* Enlargement of limits: search and replace pattern length, no replace limit
;+ searching and replacing with "wild" bytes or bits inside pattern
;+++ graphical hex/ascii viewer/editor, based on ANSI control sequences?


;Sucht Hexzeichenfolge in Datei, gibt Position (hex) aus
;und patcht das File.
;fpatch -scd2103 -p+1 -p-1 -rcd-21-04 anders.exe
;oder (Trennzeichen '-,/' erlaubt)
;fpatch /p204f /r#103,#2,##5 \dos/attrib.exe
;oder (# markiert Dezimal-Bytes, ## Dezimal-Wörter (Präfix!))
;fpatch -s'All Rights Reserved'0 -r"All Rights Reversed"0 -m term.com
;-p Positioniere!
;-s Suche!
;-r Ersetze! (Warnung bei fehlendem -m, wenn Suchkette zum Ersetzen
;   mehrfach auftritt)
;-m Ersetze auch mehrfach!
;-i Suche vorgegebene Kleinbuchstaben case-insensitiv!
;-d Belasse Filedatum
;-# Dezimale Positionsausgabe

;Später noch:
;-dn,n,n,n,n,n Setze Filedatum auf Wert

MaxPos		equ	32		;Such/Ersetzpositionen pro File
BufSize		equ	1000h		;Diskpuffergröße 4 KiloByte
	;ein Maß für den transienten Bedarf des Programms

		INCLUDE prolog.asm
;Start
		mov	bx,offset StckEnd
		cmp	sp,bx
		mov	al,8		;Fehlercode
		jn	c,errm		;Insufficient Memory
		mov	sp,bx
		;Dieses Programmstück sollte unnötig sein...
		mov	bx,(StckEnd-PSPOrg+0fh)/16 ;Paragrafengleichung
		DOS	4ah		;Speicherblockgröße verändern
		jn	c,errm
		xor	bx,bx		;stdin
		DOS	4400h		;sollte ohne Fehler arbeiten
		btst	dx,bit 7
		jnz	is_device
		bset	[Switches],(bit 11) or (bit 2)
is_device:	call	ParseO
		jc	errP		;Parameterfehler!
		jz	help		;Segítség!
loop1:	;Äußere Schleife!!
		call	FindFirst
		jc	errF
		mov	[parseptr],si
loop2:	;Innere Schleife!!
		mov	ax,3d02h
		and	[byte switches],37h ;Bits 3, 6 und 7 löschen!
		and	al,[byte switches]
		int	21h		;Öffnen R/W oder R/O
		jc	errF		;DX=nullterminierter Filename
		mov	[handle],ax
		xchg	bx,ax		;wie üblich
		call	FetchFileLastByte
		DOS	5700h		;Datum lesen
		jc	errF
		mov	[DateStamp],dx
		mov	[TimeStamp],cx
		bset	[switches],bit 7;scharf!
		call	ParseO
		pushf
		call	SetStamp
		mov	bx,[handle]
		DOS	3eh		;Schließen
		popf
		jc	errP
		call	FindNext
		jc	errm		;nur DOS-ERROR
		jnz	loop2
		mov	si,[parseptr]
loop3:		lodsb
		cmp	al,' '
		jz	loop3
		dec	si
		jnc	loop1		;Nächste Gruppe von Dateinamen!
		mov	al,[byte switches+1]
		and	al,1
		xor	al,1
		jr	exial		;Errorlevel 01 bei Nothing found

help:	;Hilfetext
		mov	dx,offset msgH
		xor	bl,bl		;Errorlevel 0
		jr	errp1

errP:	;Parameterfehler
		mov	dx,offset msgP
		mov	bl,0ffh		;Errorlevel 255
		jr	errp1

errF:	;Dateifehler
		xchg	bx,ax
		push	dx
		PRINT	msgF
		pop	dx
		call	OSTR
		jr	errp11

errm:	;Fehlermeldung DOS
		xchg	bx,ax
		PRINT	msgE
		mov	al,bl
		call	AHEX
errp11:		mov	dx,offset NL$
errp1:		mov	ah,9
		int	21h
exi:		xchg	ax,bx
exial:		DOS	4Ch		;Fehlercode durchpipen
		;(wie sich das für ein ordentliches Programm gehört!)

ParseO:	;Optionen abgrasen, CY=1: Fehler, Z=1: Hilfe angefordert
	;sonst: SI zeigt auf (vermeintlichen) Dateinamen
		cld
		mov	si,81h
		xor	ax,ax
		mov	[FndCntGes],ax
		mov	[PosCnt],ax
		btst	[Switches],bit 11
		jz	Parse2
		xor	bx,bx		;stdin
		call	Seek0		;seek, AL ist schon 0
		mov	cx,255
		mov	dx,offset PString+1
		DOS	3Fh		;read
		mov	[PString],al
		mov	ax,offset Parse
		push	ax
		 jmp	opFindFromStdin	;Lossuchen!
Parse:
		jc	ParsErr
		bset	[switches],bit 3	;mindestens 1 Kommando ok.
Parse2:		lodsb
		cmp	al,' '
		jz	Parse2
		call	IsOption
		jz	Option		;Option prüfen!
		dec	si		;auf Dateiname, Z=0
		btst	[switches],bit 3
		jz	opErr		;Fehler wenn keine Option
ParsErr:	ret

Option:		;Zuerst nur Syntaxprüfung von Optionen!
		;Scharf mit gesetztem Bit 7(Switches)!
		mov	ax,offset Parse
		push	ax		;Rückkehradresse
		 lodsb			;Buchstabe
		 call	UpCase
		 cmp	al,'P'
		 je	opPos
		 cmp	al,'S'
		 jn	z,opFind
		 cmp	al,'R'
		 jn	z,opRepl
		 cmp	al,'M'
		 je	opMore
		 cmp	al,'I'
		 je	opCase
		 cmp	al,'H'
		 je	opHelp
		 cmp	al,'?'
		 je	opHelp
		 cmp	al,'D'
		 je	opDate
		 cmp	al,'#'
		 je	opDecOutput
		 cmp	al,'U'
		 je	opUnicode
		 cmp	al,'!'
		 je	opName2StdOut
opErr:		 stc			;Syntaxfehler
		 ret
opHelp:		pop	ax		;1 Stackebene herauf!
opRet:		ret

opPos:	;Positioniere!
		lodsb
		cmp	al,'+'
		jz	PosPlus
		cmp	al,'-'
		jz	PosMinus
		dec	si
		call	InPos
		jc	opErr		;Zahl zu groß
		jmp	AppendPos

PosPlus:
		call	InPos
		jc	opErr
PosMin2:	btst	[switches],bit 7
		jz	opRet
		mov	cx,[FndCnt]	;Nur die zuletzt gefundenen!
		mov	bx,[PosCnt]
		sub	bx,cx
AddLoop:	jcxz	opRet
		call	GetPosAdr	;setzt DI je nach bl
		add	[di],ax
		adc	[di+2],dx
		js	al_neg
al_cont:	dec	cx
		inc	bl
		clc
		jr	AddLoop

PosMinus:
		call	InPos
opErr0:		jc	opErr
		neg	ax
		adc	dx,0
		neg	dx
		jr	PosMin2

opDecOutput:	;setzt dezimalen Output
		bset	[switches],bit 4
		ret
opDate:	;setzt "Keine Datumsverändering"
		bset	[switches],bit 5
		ret

opCase:	;setze Casesensitivität
		bset	[switches],bit 0
		ret

opMore:	;setze Mehrfachersetzung
		bset	[switches],bit 2 ;wird eh rückgesetzt pro File-Lauf
opRet1:		ret

opUnicode:	;setzt Unicode-Eingabestrings (gilt nicht für Hex-Bytes)
		bset	[switches],(bit 12) or (bit 13)
		lodsb			;folgt "M" für Motorola?
		call	UpCase
		cmp	al,'M'
		je	to_ret
		bres	[switches],bit 13
		dec	si
to_ret:		ret

opName2StdOut:
		cmp	[byte si],'-'
		mov	bx,200h
		jne	n2o
		mov	bh,4
		inc	si
n2o:		or	[switches],bx
opRet01:	ret

al_neg: 	;negative Ersetzungsposition
		btst	[switches],bit 2	;Doppelfunktion von -m
		jnz	al_cont		;wird von BlockWrite ignoriert
		PRINT	neg$
		mov	bl,-2
		jmp	errp11

opRepl:	;Ersetzung
		bset	[switches],bit 1;wird NICHT MEHR rückgesetzt!
		call	ReadStringW
		jc	opErr0
		btst	[switches],bit 7
		jz	opRet1
		mov	cx,[PosCnt]
		jcxz	nrpl
		btst	[switches],bit 2;Mehrfachersetzung erlaubt?
		jnz	rpl1
		cmp	[FndCntGes],2	;????(Nicht ganz klar)
		jc	rpl1
		PRINT	rplrq
		call	YesNo
		jnz	rpl3
rpl1:
		PRINT	rpl$
		mov	bl,0
rpl2:		call	GetPos
		inc	bl
		call	BlockWrite
		loop	rpl2
		PRINT	done$
rpl3:		mov	[PosCnt],0	;ClearPos
		mov	[FndCntGes],0	;Fürs nächste Mal
		jr	NLRET01

nrpl:		PRINT	nrpl$
NLRET01:	jmp	NLRET
opFind:	;Suchen
		btst	[switches],bit 11;	Standardeingabe umgelenkt?
		jn	nz,opErr	;ja: Fehler!
		call	ReadString
opFindFromStdin:
		jc	opRet2		;Fehler
		btst	[switches],bit 7
		jz	opRet2		;Operation noch kalt
		mov	[FndCnt],0
		push	si
		call	qsrc		;Suche Stringvorkommen
		pop	si
		jc	opRet2
		mov	ax,[FndCnt]
		add	[FndCntGes],ax	;Gesamtfindungen summieren
		or	ax,ax
		jz	nfnd
		bset	[switches],bit 8;Es wurde überhaupt etwas gefunden!
		btst	[switches],bit 10
		jnz	opRet2
		push	ax
		mov	dx,offset FileName ;Stattdessen Dateiname ausgeben
		call	OSTR
		pop	ax
		btst	[switches],(bit 9) or (bit 10)	;ein Quiet-Modus?
		jnz	NLRET
		mov	cx,ax
		mov	bx,[PosCnt]
		sub	bx,cx
		PRINT	fnd$		;'Found at:'
HexLoop:	mov	al,' '
		call	OCHR
		call	GetPos
		inc	bl
		push	cx
		call	PosOut		;ausgeben
		pop	cx
		loop	HexLoop
		jr	NLRET

nfnd:		btst	[switches],bit 9
		jnz	opRet2
		mov	dx,offset filename
		call	OSTR
		btst	[switches],(bit 9) or (bit 10)
		jnz	NLRET
		PRINT	nfnd$		;'Not found'
NLRET:		PRINT	NL$
		clc
opRet2:		ret

ReadStringW:	mov	cl,0
		jr	rds1
ReadString:	;Nachfolgenden String in Standardpuffer einlesen
		mov	cl,1
rds1:		mov	di,offset PString ;zunächst nur "void buffer"
		xor	ax,ax
		push	di cx
		 stosb			;Längenbyte
		 call	ReadBytes
		 mov	ax,di		;das Ende +1
		pop	cx di
		jc	RdsErr
		sub	ax,di		;sollte CY=0 sein!
		dec	al
		cmp	al,cl		;Länge Null?
		stosb			;korrekte Länge
RdsErr:		ret

proc Seek0
;Datei-Handle BX zur Position 0L seeken, AL=Bezug (0, 1 oder 2)
;PE: BX=Handle, AL=Ursprung
;PA: DX:AX=Position
;VR: AX,CX,DX
	xor	cx,cx
	xor	dx,dx
	DOS	42h
	ret
endp

proc FetchFileLastByte
;Dateigröße-1 bestimmen
;PE: BX=Handle
;PA: -
;VR: AX,CX,DX,DI
	mov	al,2
	call	Seek0
	mov	di,offset FileLastByte
	sub	ax,1
	sbb	dx,0
	stosw
	xchg	dx,ax
	stosw			;6 Bytes statt sonst 7
	mov	al,0
	call	Seek0
	ret
endp

qsrc:	;Schnellsuche im Pufferbereich, Problem: Wrap Around!
	;Lösung: Puffer doppeln, ab&zu Hinterteil nach vorn schieben
		;(di)PString
		;si: lfd. Suchposition
		;cx: Puffer-Restlänge
		xor	cx,cx		;auf Null
		xor	dx,dx		;auch Null
		mov	ax,4200h	;Position Null!
		mov	bx,[handle]
		int	21h
		call	BlockRead
		jc	SuchEnd
		mov	cx,ax		;Gelesene Länge
		call	VCopy
		call	BlockRead
		jc	SuchEnd		;Lesefehler
		add	cx,ax		;verlängern
		mov	[word FilePos],0
		mov	[word FilePos+2],0 ;nullsetzen

Schleife:	jcxz	SuchEnd1
		mov	ah,[PString+1]	;1. Suchzeichen
		lodsb
		dec	cx
		call	CHRComp		;ah=al?
		jnz	Ungl
		push	cx si		;Position halten!
		mov	di,offset PString
		mov	bl,[di]
		inc	di
Innen:		dec	bl
		jz	FndEntry	;Position verewigen
		inc	di
		mov	ah,[di]
		lodsb			;nächstes Byte
		call	CHRComp		;vergleichen
		jnz	Ungl1
		jcxz	Ungl1
		dec	cx
		jr	Innen
FndEntry:
		pop	ax
		push	ax
		sub	ax,offset Buffer1+1
		add	ax,[word FilePos]
		mov	dx,[word FilePos+2]
		adc	dx,0		;Übertrag
		call	AppendPos	;dx-ax eintragen
		jc	SuchEnd2	;Tabelle randvoll, sei kein Fehler
		inc	[FndCnt]

Ungl1:		pop	si cx
Ungl:
		cmp	si,offset Buffer2
		jc	Schleife
		;Vorkopieren und Nachladen
		call	VCopy
		call	BlockRead
		jc	SuchEnd		;Lesefehler
		add	cx,ax		;verlängern
		jr	Schleife

SuchEnd2:	pop	si cx
SuchEnd1:	clc
SuchEnd:	ret

VCopy:	;Hinteren Bereich nach vorn kopieren
		push	cx di
		 mov	si,offset Buffer2
		 mov	di,offset Buffer1
		 shr	cx,1		;Boost
		 inc	cx
		 rep	movsw
		 mov	si,offset Buffer1
		pop	di cx
		ret

SetStamp:	;Stempel setzen wenn alles dazu ok
		mov	al,[byte switches]
		not	al
		and	al,22h
		jnz	SStE
		mov	bx,[handle]
		mov	cx,[TimeStamp]
		mov	dx,[DateStamp]
		mov	ax,5701h
		int	21h
SStE:		ret

BlockRead:	;Block-Lesen nach Buffer2
		;PE: -
		;PA: AX: Anzahl Zeichen
		;    CY: Lesefehler
		;VR: AX
		push	bx cx dx
		mov	cx,BufSize	;Maximale Länge versuchen
		add	[word filepos],cx  ;mit dem neuen CX
		adc	[word filepos+2],0
		mov	dx,offset Buffer2
		mov	bx,[handle]
		DOS	3fh		;lesen
BW1:		pop	dx cx bx
		ret

BlockWrite:	;Block-Schreiben aus Stringpuffer (max. 255 Bytes)
		;PE: DX-AX: Dateiposition (nicht negativ!)
		;PA: CY: Schreibfehler
		;VR: AX
		push	bx cx dx
		test	dx,dx
		js	BW1		;raus mit CY=0 (ignorieren)
		mov	bx,[handle]
		mov	cx,dx
		mov	dx,ax
		mov	ax,4200h
		int	21h		;positionieren
		mov	cl,[PString]
		xor	ch,ch
		mov	dx,offset PString+1
		DOS	40h		;schreiben
		jc	BW1
		cmp	ax,cx		;Disk full, ax<cx?
		jr	BW1

UpCase:	 ;al:= UpCase(al) ohne hohe Zeichen
		cmp	al,'a'
		jb	UpCas1
		cmp	al,'z'
		ja	UpCas1
		and	al,not 20h
UpCas1:		ret

CHRComp: ;Gegenteil von UpCase mit Testung des Flags
		test	ah,20h		;Großbuchstabe gesucht?
		jz	LowCas1		;bleibt al!
		btst	[switches],bit 0;Nie LowCase?
		jz	LowCas1		;al bleibt
		cmp	al,'A'
		jb	LowCas1
		cmp	al,'Z'
		ja	LowCas1
		or	al,20h
LowCas1:	cmp	ah,al		;Z ist Ergebnis
		ret

YesNo:	;liefert Z=1 beim Drücken auf 'Y'
		PRINT	yesno$
		DOS	1
		call	UpCase
		push	ax
		PRINT	NL$
		pop	ax
		cmp	al,'Y'
		ret

AppendPos:	;Füge künftige Ersetzposition hinzu!
		;CY=1 Tabelle voll
		mov	bx,[PosCnt]
		cmp	bx,MaxPos
		cmc
		jc	APErr		;Zu viele Positionen
		inc	[PosCnt]
		call	GetPosAdr
		mov	[di],ax
		mov	[di+2],dx	;Zeiger eintragen
APErr:		ret

GetPosAdr:	;Erhalte in DI den Zeiger auf das Dateipositionsarray
		push	bx
		 shl	bx,1
		 shl	bx,1
		 mov	di,offset Positions
		 add	di,bx	;auf 'nem 386er ein "lea di,[Positions+bx*4]"
		pop	bx
		ret

GetPos: ;Erhalte in DX-AX Ersetzposition Nr. bl
		call	GetPosAdr
		mov	ax,[di]
		mov	dx,[di+2]
		ret

	;Testet ob Zeichen eine Ziffer, al:= al-'0'!
IsDigit:	sub	al,'0'
		jc	ID1
		cmp	al,10
		cmc
ID1:		ret

	_ANUM
	;AL:= Hexäquivalent zum ASCII-Zeichen AL, CY=1: Keine HexZahl!
HexNibble:	call	ANum
		cmp	al,10h
		cmc
hn1:		ret

	;Zweistelliges Hex-Byte einlesen nach [di]
InHexByte:	push	cx
		mov	al,[si]
		call	HexNibble
		jc	IHB1		;Fehler 1.Zeichen
		inc	si
		mov	cl,4
		mov	ch,al
		shl	ch,cl
		mov	cl,al		;aufheben für 1-Zeichen-Hexzahlen!
		mov	al,[si]
		call	HexNibble
		jc	IHB2
		or	al,ch
		inc	si
		jr	IHB3
IHB2:		mov	al,cl		;alte Tetrade!
IHB3:		stosb
IHD1:		clc
IHB1:		pop	cx
		ret

	;8stellige Hexzahl (Dateiposition) einlesen nach DX-BX (und AX)
InHexDD:	push	cx
		mov	al,[si]
		call	HexNibble
		jc	IHB1		;Schon 1. Zeichen fehlerhaft!
		xor	bx,bx
		xor	dx,dx
IHD2:		mov	cx,4
IHD3:		shl	bx,1
		rcl	dx,1
		jc	IHB1		;Zahl zu groß
		loop	IHD3
		or	bl,al
		inc	si
		mov	al,[si]
		call	HexNibble
		jnc	IHD2
		mov	ax,bx
		jr	IHD1		;clc, pop cx, ret


InPos:	;wertet Dezpräfix '#' aus
		cmp	[byte si],'#'
		jne	InHexDD
		inc	si
	;Dezimalzahl nach DX-AX einlesen, Quelle=[si]
InDez:		push	bx
		mov	al,[si]
		call	IsDigit
		jc	IND1
		xor	bx,bx
		xor	dx,dx
InD2:		 push	si di cx
		 xchg	si,bx
		 xchg	di,dx
		 mov	bl,al
		 xor	bh,bh
		 xor	dx,dx
		 mov	cx,10
InD3:		  add	bx,si
		  adc	dx,di
		  loop	InD3
		 pop	cx di si
		jc	IND1		;Fehler: Überlauf!
		inc	si
		mov	al,[si]
		call	IsDigit
		jnc	InD2
		clc
		xchg	ax,bx
IND1:		pop	bx
		ret

	;Testet, ob AL ein Opionspräfix ist
IsOption:	cmp	al,'/'
		jz	IO1
		cmp	al,'-'
IO1:		ret

	;Allgemeine Zeichenkette bis zum Leerzeichen einlesen
ReadBytes:	;(Zeichenkette darf nicht leer sein!)
		;(ZK darf kein Zeichen <20h enthalten!)
rdLoop:		lodsb
		call	IsOption
		jz	rdLoop
		cmp	al,','
		jz	rdLoop		;Komma als Trennzeichen zulassen
		cmp	al,'#'
		je	rdDez		;Dezimalbyte
		cmp	al,''''
		jz	rdString
		cmp	al,'"'
		jz	rdString
		dec	si		;Zeiger zurück!
		cmp	al,21h
		cmc
		jnc	rdDone		;Ende Zeichenkette
		call	InHexByte
		jc	RB1		;Fehlerhaft!
		jr	rdLoop
		;
rdDez:		cmp	[si],al		;Doppel-Doppelkreuz?
		je	rdDezW
		call	InDez
		jc	RB1		;Überlauf!
		or	dx,dx
		jnz	RB1		;zu groß
		or	ah,ah
		jnz	RB1		;noch zu groß
		stosb
		jr	rdLoop
		;
rdDezW:		inc	si
		call	InDez
		jc	RB1
		or	dx,dx
		jnz	RB1
		stosw
		jr	rdLoop
		;
rdString:	mov	cl,al		;Delimitor merken!
rdStr1:		lodsb
		cmp	al,cl		;Stringende?
		jz	rdStr2
		cmp	al,13		;Steuerzeichen CR?
		je	RB1		;Fehler!
		cmp	al,10		;LF?
		je	RB1
		or	al,al		;NUL?
		jz	RB1
rdStr3:		btst	[switches],bit 12
		jz	rdStr_byte
		mov	ah,0		;eigentlich: Tabellenzugriff!
		btst	[switches],bit 13
		jz	rdStr_intel
		xchg	ah,al
rdstr_intel:	stosw
		jr	rdStr1
rdstr_byte:
		stosb
		jr	rdStr1
rdStr2:
		cmp	[si],al		;Noch ein Delimitor?
		jne	rdLoop		;nein, echtes String-Ende
		inc	si
		jr	rdStr3		;als 1 Trennzeichen übersetzen
		;
RB1:		stc
rdDone:		ret

;========= AUSGABEN ========================= DXAX AXHX AHEX DXAXDEZ ====
;######TEST#####
;		db	6 dup (90h)	;paar Nops

PosOut:	;Positionsausgabe
		btst	[switches],bit 4;Bit 4=dezimal?
		jnz	DXAXDez
dxax:	;Hexausgabe DX-AX mit Bindestrich, aber DX nur, wenn Dateilänge>64KB
		cmp	[word HIGH FileLastByte],0
		jz	axhx		;weil High-Teil uninteressant
		call	axhx0
		xchg	cx,ax
		mov	al,'-'
		call	OCHR
		xchg	ax,cx
axhx0:		xchg	dx,ax
axhx:	;Hexausgabe AX
		call	ahex0
ahex0:		xchg	ah,al
ahex:	;Hexausgabe AL
		push	ax		;Werte erhalten
		mov	cl,4		;oberes Nibble auf Bit 3...0
		shr	al,cl		; schieben
		call	ahex1
		pop	ax
ahex1:		and	al,0fh
		add	al,90h		;Hex -> ASCII
		daa
		adc	al,40h
		daa
OCHR:	;Zeichenausgabe ohne DX-Zerstörung (natürlich CHR in AL!)
		push	ax dx
		mov	dl,al
		mov	ah,2
		int	21h
		pop	dx ax
		ret

DXAXDez:;DX-AX dezimal vzl ausgeben mit Vornullenunterdrückung
		;(unsigned long)
		push	bx
		mov	cl,11		;maximale Ziffern(?)
AXD1:		call	ODigit
		push	cx
		inc	sp		;Nur H-Teil pushen!
		dec	cl
		jnz	AXD1
		;
		mov	cx,11		;LIFO dreht Ziffern herum!
AXD2:		dec	sp
		pop	ax		;AH=Zahl aus LIFO
		or	ch,ah		;Führende Null ausblenden
		jnz	AXD3
		cmp	cl,1
		jne	AXD4		;bis auf die letzte, bitte!
AXD3:		mov	al,ah
		add	al,'0'
		call	OCHR
AXD4:		dec	cl
		jnz	AXD2
		pop	bx
		ret

ODigit:		;Ziffernberechnung (ch), leider von hinten nach vorne!
		;dx-ax durch 10 teilen, ohne INT0 zu erzeugen!
		push	ax
		 xor	ax,ax
		 xchg	ax,dx
		 mov	bx,10		;Divisor
		 div	bx		;AX:= AX/10, Rest DX
		 mov	bx,sp
		 xchg	[bx],ax		;Sheisz Processor!
		 mov	bx,10		;Divisor
		 div	bx		;AX:= DX-AX/10, Rest DX
		 mov	ch,dl		;die Dezziffer!
		pop	dx
		ret

FindFirst:	;was der Name schon sagt...
		;PE: si: Dateiname mit Wildcards, abgeschlossen mit <=20h
		;PA: dx: Dateiname als ASCIIZ
		;    si: Zeigt auf 1. Zeichen nach Dateiname
		;    CY: Fehler aufgetreten, AX=Code
		;    Z : Keine (weiteren) Dateien
		push	cx di
		mov	dx,offset DTA
		DOS	1ah
		mov	di,offset FileName
		call	strcopyx
		cmp	[byte filename],'!' ;List-File?
		jz	OpenList
ff4:		cmp	di,offset filename
		jz	ff3
		mov	al,[di-1]
		cmp	al,'/'
		jz	ff3
		cmp	al,'\'
		jz	ff3
		cmp	al,':'
		jz	ff3
		dec	di
		jr	ff4
ff3:		mov	[BehindPath],di		;merken für FindNext
		mov	cx,7
		mov	dx,offset filename
		DOS	4eh		;FindFirst
		jc	ff5
ff6:		push	si
		mov	si,offset DTA+1eh
		mov	di,[BehindPath]
		call	strcopyx
		pop	si
ff7:		mov	dx,offset FileName
		or	dx,dx			;Z=0, CY=0
ff5:		pop	di cx
		ret

FindNext:	;
		;PA: dx: ASCIIZ Dateiname
		;    CY: Fehler
		;    Z:  Keine weiteren Dateien
		push	cx di
		cmp	[BehindPath],100
		jc	FindNextFile
		DOS	4fh
		jnc	ff6
		cmp	al,12h
		stc
		jnz	ff5
		clc
		jr	ff5

OpenList:	;Dateiliste öffnen
		mov	dx,offset filename+1
		mov	ax,3d00h
		int	21h
		jc	ff5		;Listendatei nicht gefunden
		mov	[BehindPath],ax	;Handle retten
ffl1:		call	input		;CY=1: Fehler oder Dateiende!
		jc	ff5		;Nicht EIN Dateiname erzeuge ERROR!
		cmp	al,21h
		jc	ffl1
fnf2:		mov	di,offset filename
ffl3:		stosb
		call	input
		jc	ffl2		;Ende der Liste
		cmp	al,21h
		jnc	ffl3
ffl2:		xor	al,al
		stosb			;Endezeichen Null
		jr	ff7		;Z=0


FindNextFile:
		call	input
		jc	fnf1		;Keine gültigen Zeichen gelesen
		cmp	al,21h
		jc	findnextfile
		jr	fnf2
fnf1:		xor	al,al		;CY=0, Z=1: Listenende
		jr	ff5

input:	;liest 1 Byte von der Listendatei, schließt ggf. die Datei
		push	bx cx dx
		mov	bx,[BehindPath]
		mov	dx,offset a_byte
		mov	cx,1
		DOS	3fh
		jc	inp1
		cmp	ax,1		;Null Bytes gelesen?
		jc	inp1
		mov	al,[a_byte]
inp1:		pop	dx cx bx
		ret

strcopyx:	;StringCopy mit Abschluß->=20h
		cld
ff2:		cmp	[byte si],21h
		jc	ff1
		movsb
		jr	ff2
ff1:		mov	[byte di],0		;Abschluß-Null
		ret

OSTR:	;Ausgabe nullterminierter String ab dx auf StdOut
		push	si dx
		pop	si
		cld
OSTRloop:	lodsb
		or	al,al
		jz	OSTRexi
		call	OCHR
		jr	OSTRloop
OSTRexi:	pop	si
		ret

msgE:		db	'DOS error $'
msgP:		db	'Command line error. For help type -h'
NL$:		db	nl,'$'
msgF:		db	'Can''t find or open file $'
fnd$:		db	': Found at:$'
nfnd$:		db	': Not found$'
done$:		db	'done$'
nrpl$:		db	'replace address buffer empty$'
rpl$:		db	'replace.. $'
rplrq:		db	'multiple locations found, replace all?$'
yesno$:		db	' (Y/N)$'
neg$:		db	'negative position - aborting (use -m before -p to ignore)$'
msgH:
    db 'FilePatch 1.14 (haftmann#software)		++ Public Domain ++',nl
    db 'Usage:	fpatch {/|-}options filename [filename..] [<pattern-file]',nl
    db 'Options are:',nl
    db ' -sSTRING  search string and store positions in address buffer (max. 32)',nl
    db ' -p{hexnumber|#decnumber}  store absolute position in replace address buffer',nl
    db ' -p{+|-}{hexnum|#decnum}  offset those buffer positions found by last search',nl
    db ' -rSTRING  replace STRING into positions stored and clear buffer for next op.',nl
    db ' -m  do not ask when replacing at multiply found positions (replace all)',nl
    db ' -i  search lower case letters in STRING case insensitively (strict for upper)',nl
    db ' -u  strings are Unicode (little endian), -um  (big endian), no OEM conversion',nl
    db ' -d  keep file date and time',nl
    db ' -#  show positions found by option "-s" in decimal',nl
    db ' -![-]  write list file with [without] matches to stdout',nl,10
    db 'STRING is:	universal, spaceless chain of the following parts:',nl
    db '  1- or 2-digit hex byte with or without delimiters ,-/',nl
    db '  #decimal-byte,  ##decimal-word, "string" or ''string''',nl,10
    db 'filename: Wildcards allowed, List-File with prefix ''!''!',nl,10
    db 'Examples:  fpatch -sCD213 -p+1 -p-1 -rcd-21-04 example.exe',nl
    db '	   fpatch /p204F /r#103,#2,##5 \path\*.exe',nl
    db '	   fpatch -s''All Rights Reserved''0 -r"All Rights Reversed"0 win.com',nl
    db '$'
PosCnt		dw	0		;Anzahl Ersetzpositionen
Switches	dw	0		;Bit-Switches
;13: Big-Endian-Unicodes (Motorola-Notation)
;12: Unicode-String-Interpretation
;11: Standardeingabe ist umgeleitet, -s unmöglich, -m automatisch
;10: Ausgabe wenn String NICHT gefunden (statt "not found")
;9: Ausgabe des Dateinamens auf StdOut, wenn String gefunden (statt "found at")
;8: Es wurde (heute) schon mal was gefunden (für Errorlevel)
;7: "Scharfe" Aktionen an Datei (sonst nur Kommandozeilen-Syntaxprüfung)
;5: Keine Veränderung des Zeitstempels des Files
;4: Dezimale Positionsangaben
;3: Mindestens eine Option wurde angegeben
;2: Ignoriere Warnungen wenn mehrere Dateipositionen passen (-m)
;1: Ersetzen in Kommandozeile vorhanden: Datei im Modus 2 öffnen!
;0: Beachte Groß/Kleinschreibung beim Suchen nicht!
FndCntGes	dw	0
FndCnt		dw	?
handle		dw	?
TimeStamp	dw	?
DateStamp	dw	?
FileLastByte	dd	?		;nur für Breite der Hex-Ausgabe
ParsePtr	dw	?
FilePos		dd	?		;Dateiposition des Buffer1-Anfangs
DTA		db	2bh dup (?)	;Unser DTA für Wilde Karten
A_Byte		db	?		;einzelnes Lese-Byte (f. List-File)
FileName	db	80 dup (?)	;Merkstelle für Dateiname und Pfad
BehindPath	dw	?
		alignv	16
Positions	dd	MaxPos dup (?)	;32 Merkpositionen pro File
PString		db	100h dup (?)	;256 Byte Pascal-String
Buffer1		db	BufSize dup (?)	;4 KB Lesepuffer
Buffer2		db	BufSize dup (?)	;Noch ein Lesepuffer
Stck		db	100h dup (?)	;Kellerspeicher
StckEnd:
		ENDC
Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded