; SafeFunction.s
;  This program patches the SetFunction() function. This tries to solve problems
;  when two programs try to patch the same function (the system may crash if
;  the programs remove their patch in the wrong order).
; This version is based on the program named SaferPatches which was placed in
; the public domain by the author:
;	Martin Adrian
;	Rullharvsgatan 3A
;	S-431 47 Mölndal
;	SWEDEN
; This version was converted to assembly language for best efficiency, and tries
; to handle better memory failures: SetFunction() is not allowed to fail, but I
; have to make some memory allocations. The problem is solved by throwing an
; "emergency block" into the free memory list on memory shortage. Moreover, all
; allocations are done with pooled allocation functions, in order to reduce
; memory fragmentation. Author:
;	Frédéric Delacroix
;	5 rue d'Artres
;	59269 Quérénaing
;	FRANCE
; This program and the source is fully public domain.

;  version 1.0: Assembly version of Martin Adrian's program.
;  version 1.1: semaphore added to public list for easy rendez-vous.
;		No longer startable from Workbench (the best place is
;		right after SetPatch in the startup-sequence).
;  version 1.2: Enclosed critical parts in Disable()/Enable() (in case
;		an interrupt would try to execute a function whose patch
;		is not yet correctly installed). Now clears the CPU caches.
;  version 1.3: Added a low-memory handler to throw an emergency block of
;		free memory into the system pool on a memory allocation
;		failure during SetFunction(). This reduces the threat of
;		an unexpected NULL return value from SetFunction()...
;  version 1.4: All memory allocations are now done via pools, in order to
;		reduce memory fragmentation.
;  version 1.5: Some minor guide updates, wrote a french documentation.

VERSION	MACRO
	dc.b	'1.5'
	ENDM

	section	Startup,CODE

EMERGENCY_BLOCKSIZE	EQU	32
MEMPUDDLE_SIZE	EQU	32

Start	move.l	4.w,a6
	lea	Variables,a5
	move.l	#20,d7	; return code
	lea	DOS.Name(pc),a1
	moveq	#39,d0	; kickstart >= 3.0 required
	jsr	_LVOOpenLibrary(a6)
	move.l	d0,a4
	move.l	a4,d0
	beq	NoDOS
	move.l	#Bienvenue.MSG,d1
	exg.l	a4,a6
	jsr	_LVOPutStr(a6)
	exg.l	a4,a6
	jsr	_LVOForbid(a6)
	lea	Semaphore.Name,a1
	jsr	_LVOFindSemaphore(a6)
	tst.l	d0
	bne	AlreadyInstalled
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d0
	move.l	#MEMPUDDLE_SIZE,d1
	move.l	d1,d2
	jsr	_LVOCreatePool(a6)
	move.l	a0,var_MemoryPool(a5)
	beq	NoPool
	suba.l	a0,a0
	jsr	MakeHead
	move.l	d0,var_MainHeader(a5)
	beq.s	NoMemory
	move.l	#_LVOSetFunction,a0
	move.l	#NewSetFunction,d0
	move.l	a6,a1
	jsr	_LVOSetFunction(a6)
	move.l	d0,var_OldSetFunc(a5)
	beq.s	NoSetFunction
	lea	LowMemHandler.Interrupt,a1
	jsr	_LVOAddMemHandler(a6)
	move.l	#EMERGENCY_BLOCKSIZE,d0
	move.l	#MEMF_PUBLIC,d1
	jsr	_LVOAllocMem(a6)
	move.l	d0,var_EmergencyBlock(a5)
	beq.s	.NoFlag
	bset	#SFB_BLOCKAVAILABLE,var_Flags(a5)
.NoFlag	lea	var_SetFuncSemaphore(a5),a1
	move.l	#Semaphore.Name,LN_NAME(a1)
	jsr	_LVOAddSemaphore(a6)
	jsr	_LVOPermit(a6)
	move.l	#Installed.MSG,d1
	exg.l	a4,a6
	jsr	_LVOPutStr(a6)
	exg.l	a6,a4
; cut the seglist
	lea	Start-4(pc),a0
	clr.l	(a0)	; tchac
	moveq	#0,d7
CloseDOS
	move.l	a4,a1
	jsr	_LVOCloseLibrary(a6)
NoDOS	move.l	d7,d0
	rts

NoSetFunction
	move.l	var_MainHeader(a5),a1
	move.l	#libh_SIZEOF,d0
	jsr	FreeMemory
NoMemory
	move.l	var_MemoryPool(a5),a0
	jsr	_LVODeletePool(a6)
NoPool	move.l	#Failed.MSG,d1
PrintAndGo
	exg.l	a4,a6
	jsr	_LVOPutStr(a6)
	exg.l	a4,a6
	bra.s	CloseDOS

AlreadyInstalled
	move.l	#AlreadyInstalled.MSG,d1
	moveq	#5,d7
	bra.s	PrintAndGo

DOS.Name	dc.b	'dos.library',0
Bienvenue.MSG	dc.b	'SafeFunction '
	VERSION
	dc.b	' by F.Delacroix, public domain.',10,0
Installed.MSG	dc.b	'  Patch successfully installed',10,0
Failed.MSG	dc.b	'  Failed to install patch ! (not enough memory ?!)',10,0
AlreadyInstalled.MSG	dc.b	'  Patch already active (you cannot remove it).',10,0

	section	ResidentPatch,CODE

	STRUCTURE	LibHeader,0
	 APTR	libh_LibBase	; points to patched library
	 APTR	libh_Patches	; points to list of patches
	 APTR	libh_NextLib	; points to next library header
	LABEL	libh_SIZEOF

	STRUCTURE	LibPatch,0
	 UWORD	libp_JMP	; JMP instruction
	 APTR	libp_OldFunc	; old (before patch) function
	 APTR	libp_NewFunc	; new (after patch) function
	 WORD	libp_Offset	; offset in jump table
	 APTR	libp_NextPatch	; points to next patch in library
	LABEL	libp_SIZEOF

MAGIC_OFFSET	EQU	$2357	; prime numbers :-]


MakeHead	; (D0)LibHeader=MakeHead(Library)(A0)
	movem.l	d1/a0-a2,-(sp)
	move.l	a0,a2
	move.l	#libh_SIZEOF,d0
	bsr	AllocMemory
	move.l	d0,a0
	move.l	a0,d0
	beq.s	.Fail
	move.l	a2,libh_LibBase(a0)
.Fail	movem.l	(sp)+,d1/a0-a2
	rts

MakePatch	; (D0)Patches=MakePatch(Offset,Next,NewFunc)(D0,A0,A1)
	movem.l	d1-d2/a0-a3,-(sp)
	move.l	d0,d2
	move.l	a0,a2
	move.l	a1,a3
	move.l	#libp_SIZEOF,d0
	bsr	AllocMemory
	move.l	d0,a0
	move.l	a0,d0
	beq.s	.Fail
	move.w	#$4EF9,libp_JMP(a0)	; opcode for JMP
	move.l	a3,libp_NewFunc(a0)
	move.w	d2,libp_Offset(a0)
	move.l	a2,libp_NextPatch(a0)
.Fail	movem.l	(sp)+,d1-d2/a0-a3
	rts

SF_OFFSET	EQUR	d7
SF_LIBRARY	EQUR	d6
SF_NEWFUNC	EQUR	d5
SF_OLDFUNC	EQUR	d4

OldSetFunction	; (D0)OldFunc=OldSetFunction(NewFunc)(A0)
	move.w	SF_OFFSET,d0
	ext.l	d0
	exg.l	d0,a0
	move.l	SF_LIBRARY,a1
	move.l	var_OldSetFunc(a5),-(sp)
	rts

NewSetFunction	; (D0)OldFunc=NewSetFunction(Offset,Library,NewFunc)(A0,A1,D0)
	movem.l	a0-a3/a5/d1/d4-d7,-(sp)
	lea	Variables,a5
	moveq	#0,SF_OLDFUNC
	move.w	a0,SF_OFFSET
	move.l	a1,SF_LIBRARY
	move.l	d0,SF_NEWFUNC
	lea	var_SetFuncSemaphore(a5),a0
	jsr	_LVOObtainSemaphore(a6)
	move.l	var_MainHeader(a5),a0

.NextLib
	move.l	libh_NextLib(a0),d0
	beq	.NoMoreLib
	move.l	d0,a1
	cmp.l	libh_LibBase(a1),SF_LIBRARY
	beq.s	.LibFound
	move.l	a1,a0
	bra	.NextLib
.LibFound
	move.l	a0,var_HeadPtr(a5)
	move.l	a1,var_HeadPtr1(a5)

	move.l	libh_Patches(a1),var_Patch(a5)
.NextPatch
	move.l	var_Patch(a5),a0
	move.l	libp_NextPatch(a0),a1
	move.l	a1,var_Patch1(a5)
	beq.s	.MakeANewPatch
	cmp.w	libp_Offset(a1),SF_OFFSET
	ble.s	.SearchPatchOn

; make a new patch (none installed here)
.MakeANewPatch
	move.w	SF_OFFSET,d0
	move.l	var_Patch1(a5),a0
	move.l	SF_NEWFUNC,a1
	bsr	MakePatch
	move.l	d0,var_Patch1(a5)
	beq	.Done
	move.l	SF_NEWFUNC,a0
	jsr	_LVODisable(a6)
	bsr	OldSetFunction
	move.l	var_Patch1(a5),a0
	move.l	d0,libp_OldFunc(a0)
	jsr	_LVOEnable(a6)
	tst.l	d0
	beq.s	.OldSetFuncFailed	; (should not happen, but let's be careful)
	move.l	var_Patch(a5),a0
	move.l	var_Patch1(a5),libp_NextPatch(a0)
	move.l	var_Patch1(a5),SF_OLDFUNC
	bra	.Done
.OldSetFuncFailed
	move.l	var_Patch1(a5),a1
	move.l	#libp_SIZEOF,d0
	bsr	FreeMemory
	bra	.Done

.SearchPatchOn
	move.l	var_Patch1(a5),a0
	cmp.w	libp_Offset(a0),SF_OFFSET
	bne	.NotThisPatch
	cmp.l	a0,SF_NEWFUNC
	beq.s	.RemoveLast
	cmp.l	libp_OldFunc(a0),SF_NEWFUNC
	bne.s	.NotRemoveLast
; user wants to remove the last patch
.RemoveLast
	move.l	libp_OldFunc(a0),a0
	bsr	OldSetFunction	; no need for Disable() here: let SetFunction() do it
	move.l	d0,SF_OLDFUNC
	move.l	var_Patch1(a5),a0
	cmp.l	libp_NewFunc(a0),SF_OLDFUNC
	bne.s	.BigTrouble
	move.l	var_Patch1(a5),a1
	move.l	var_Patch(a5),a0
	move.l	libp_NextPatch(a1),libp_NextPatch(a0)
	move.l	#libp_SIZEOF,d0
	bsr	FreeMemory
	bra.s	.LastRemoved
.BigTrouble	; if execution arrives here, than someone has changed
	move.l	SF_OLDFUNC,a0	; the vector from the outside...
	move.l	a0,SF_OLDFUNC	; we are in BIG TROUBLE, we just restore
	beq	.Done		; the original vector and pray.
	bsr	OldSetFunction
	moveq	#0,SF_OLDFUNC
	bra	.Done
.LastRemoved
	move.l	var_Patch(a5),a0
	cmp.w	#MAGIC_OFFSET,libp_Offset(a0)
	bne	.Done
	tst.l	libp_NextPatch(a0)
	bne	.Done
; remove unused library header
	move.l	var_HeadPtr(a5),a0
	move.l	var_HeadPtr1(a5),a1
	move.l	libh_NextLib(a1),libh_NextLib(a0)
	move.l	#libh_SIZEOF,d0
	bsr	FreeMemory
	move.l	var_Patch(a5),a1
	move.l	#libp_SIZEOF,d0
	bsr	FreeMemory
	bra	.Done

.NotRemoveLast
	move.l	var_Patch1(a5),a0
.RemoveLoop
	move.l	libp_NextPatch(a0),var_Patch2(a5)
	beq.s	.AddPatch
	move.l	var_Patch2(a5),a0
	cmp.w	libp_Offset(a0),SF_OFFSET
	bne.s	.AddPatch
	cmp.l	a0,SF_NEWFUNC
	beq.s	.RemoveThisOne
	cmp.l	libp_OldFunc(a0),SF_NEWFUNC
	beq.s	.RemoveThisOne
	move.l	a0,var_Patch1(a5)
	bra.s	.RemoveLoop

.RemoveThisOne
	move.l	var_Patch1(a5),a0
	move.l	var_Patch2(a5),a1
	move.l	libp_NextPatch(a1),libp_NextPatch(a0)
; no need for Disable() for a single instruction
	move.l	libp_OldFunc(a1),libp_OldFunc(a0)
	move.l	libp_NewFunc(a1),SF_OLDFUNC
	move.l	#libp_SIZEOF,d0
	bsr	FreeMemory
	bra	.Done

.AddPatch
; let's make a new patch on this function
	move.w	SF_OFFSET,d0
	move.l	var_Patch(a5),a0
	move.l	libp_NextPatch(a0),a0
	move.l	SF_NEWFUNC,a1
	bsr	MakePatch
	move.l	d0,var_Patch1(a5)
	beq	.Done
	move.l	SF_NEWFUNC,a0
	jsr	_LVODisable(a6)
	bsr	OldSetFunction
	move.l	var_Patch1(a5),a0
	move.l	d0,libp_OldFunc(a0)
	jsr	_LVOEnable(a6)
	tst.l	d0
	beq.s	.OldSetFuncFailed2
	move.l	var_Patch(a5),a0
	move.l	var_Patch1(a5),libp_NextPatch(a0)
	move.l	var_Patch1(a5),SF_OLDFUNC
	bra	.Done
.OldSetFuncFailed2
	move.l	var_Patch1(a5),a1
	move.l	#libp_SIZEOF,d0
	bsr	FreeMemory
	bra	.Done

.NotThisPatch
	move.l	var_Patch1(a5),var_Patch(a5)
	bra	.NextPatch

.NoMoreLib
; create a new library header
	move.l	a0,var_HeadPtr(a5)
	move.l	SF_LIBRARY,a0
	bsr	MakeHead
	move.l	d0,var_HeadPtr1(a5)
	beq.s	.Done
	move.l	var_HeadPtr(a5),a0
	move.l	d0,libh_NextLib(a0)
	move.w	SF_OFFSET,d0
	suba.l	a0,a0
	move.l	SF_NEWFUNC,a1
	bsr	MakePatch
	move.l	d0,var_Patch(a5)
	beq.s	.CantMakePatch
	move.w	#MAGIC_OFFSET,d0
	move.l	var_Patch(a5),a0
	sub.l	a1,a1
	bsr	MakePatch
	move.l	var_HeadPtr1(a5),a0
	move.l	d0,libh_Patches(a0)
	beq.s	.NoMagic
	move.l	SF_NEWFUNC,a0
	jsr	_LVODisable(a6)
	bsr	OldSetFunction
	move.l	var_Patch(a5),a0
	move.l	d0,libp_OldFunc(a0)
	jsr	_LVOEnable(a6)
	tst.l	d0
	beq.s	.OldSetFuncFailed3
	move.l	var_HeadPtr(a5),a0
	move.l	var_HeadPtr1(a5),libh_NextLib(a0)
	move.l	var_Patch(a5),SF_OLDFUNC
	bra.s	.Done
.OldSetFuncFailed3
	move.l	var_HeadPtr1(a5),a1
	move.l	libh_Patches(a1),a1
	move.l	#libp_SIZEOF,d0
	bsr.s	FreeMemory
.NoMagic
	move.l	var_Patch(a5),a1
	move.l	#libp_SIZEOF,d0
	bsr.s	FreeMemory
.CantMakePatch
	move.l	var_HeadPtr1(a5),a1
	move.l	#libh_SIZEOF,d0
	bsr.s	FreeMemory
.Done	lea	var_SetFuncSemaphore(a5),a0
	jsr	_LVOReleaseSemaphore(a6)
	jsr	_LVOCacheClearU(a6)
	move.l	SF_OLDFUNC,d0
	movem.l	(sp)+,a0-a3/a5/d1/d4-d7
	rts

; flag definitions for var_Flags
	BITDEF	SF,ALLOCATING,0
	BITDEF	SF,BLOCKAVAILABLE,1

AllocMemory	; (D0)Memory=AllocMemory(Size)(D0)
	move.l	var_MemoryPool(a5),a0
	jsr	_LVOForbid(a6)
	bset	#SFB_ALLOCATING,var_Flags(a5)
	jsr	_LVOAllocPooled(a6)
	bclr	#SFB_ALLOCATING,var_Flags(a5)
	jsr	_LVOPermit(a6)
	btst	#SFB_BLOCKAVAILABLE,var_Flags(a5)
	bne.s	.NoNeed
	move.l	d0,-(sp)
	beq.s	.Nope
	move.l	#EMERGENCY_BLOCKSIZE,d0
	move.l	#MEMF_PUBLIC,d1
	jsr	_LVOAllocMem(a6)
	move.l	d0,var_EmergencyBlock(a5)
	beq.s	.Nope
	bset	#SFB_BLOCKAVAILABLE,var_Flags(a5)
.Nope	move.l	(sp)+,d0
.NoNeed	rts

FreeMemory	; FreeMemory(Memory,Size)(A1,D0)
	move.l	var_MemoryPool(a5),a0
	jmp	_LVOFreePooled(a6)

LowMemHandler.Entry
	move.b	var_Flags(a1),d0
	and.b	#SFF_ALLOCATING!SFF_BLOCKAVAILABLE,d0
	beq.s	.DoNothing
	cmp.w	#EMERGENCY_BLOCKSIZE,memh_RequestSize(a0)
	bgt.s	.DoNothing
	bclr	#SFB_BLOCKAVAILABLE,var_Flags(a1)
	move.l	var_EmergencyBlock(a1),a1
	move.l	#EMERGENCY_BLOCKSIZE,d0
	jsr	_LVOFreeMem(a6)
	move.l	#MEM_ALL_DONE,d0
	rts
.DoNothing
	move.l	#MEM_DID_NOTHING,d0
	rts

LowMemHandler.Interrupt
	dc.l	0,0
	dc.b	NT_INTERRUPT,-120
	dc.l	LowMemHandler.Name
	dc.l	Variables	; IS_DATA field
	dc.l	LowMemHandler.Entry

Semaphore.Name	dc.b	'SafeFunction.Semaphore',0
LowMemHandler.Name	dc.b	'SafeFunction.EmergencyHandler',0

	section	Variables,BSS

	rsreset	; these variables are protected by the semaphore
var_HeadPtr	rs.l	1
var_HeadPtr1	rs.l	1
var_Patch	rs.l	1
var_Patch1	rs.l	1
var_Patch2	rs.l	1
var_MainHeader	rs.l	1	; first LibHeader
var_OldSetFunc	rs.l	1	; points to real SetFunction()
var_SetFuncSemaphore	rs.b	SS_SIZE
var_EmergencyBlock	rs.l	1
var_MemoryPool	rs.l	1
var_Flags	rs.b	1
var_Flags1	rs.b	1	; word-alignment, for now...
var_SIZEOF	rs.w	0

Variables	ds.b	var_SIZEOF
