*\  :ts=8 bk=0
*
* BPD.asm:	Stands for "Boot PAL, Dammit!"  Written as an alternative to
*		all those other programs that attempt to set PAL mode, and
*		never get it right.
*
*		See, in order for PAL mode to *truly* take effect, you have
*		to convince graphics.library that it's running PAL.  To do
*		that, you have to get in before graphics configures itself
*		and set the PAL bit in the ECS agnus.  And in order to do
*		*that*, you have to be part of the boot sequence.
*
*		So this program does precisely that by creating and
*		installing a resident RomTag into the system that survives
*		reboots.
*
*		Usage:  BPD [ '?' | 'REMOVE' | 'NTSC' ]
*
*		Thanks to Bryce Nesbitt for providing me with an example
*		of how to create resident RomTags.
*
* Leo L. Schwab,  Silent Software, Inc.			9010.20
* NTSC hack added (inspired by Nico Francois)		9011.11
*/
		include	'exec/types.i'
		include	'exec/execbase.i'
		include	'exec/resident.i'
		include	'exec/memory.i'

		xref	_LVOFindResident
		xref	_LVOAllocMem
		xref	_LVOCopyMem
		xref	_LVOSumKickData
		xref	_LVODisable
		xref	_LVOEnable
		xref	_LVOOpenLibrary
		xref	_LVOCloseLibrary

		xref	_LVOOutput
		xref	_LVOWrite

		xref	_vposr		; Custom chip register

TAG_PRI		EQU	100		; Above graphics.library.


****************
* And we're off!
*
		SECTION	BPD,CODE
Hello:
		movea.l	a0,a3		; Save argument pointer
		movea.l	4.w,a6		; ExecBase
		lea	dosname(pc),a1
		moveq	#0,d0
		jsr	_LVOOpenLibrary(a6)
		moveq	#20,d2
		move.l	d0,d6
		beq	openfailed

		lea	Name(pc),a1
		jsr	_LVOFindResident(a6)

* See if ECS Agnus is installed.  We check for ID's 0x20 and 0x30.  Anything
* else is considered something we don't know how to deal with.
*
		move.w	_vposr,d1	; Get ID bits
		andi.w	#$3F00,d1	; Mask off extraneous stuff.
		cmpi.w	#$3000,d1	; Is it NTSC ECS Agnus?
		beq.s	1$
		cmpi.w	#$2000,d1	; Is it PAL ECS Agnus?
		bne	notecs

* Scan command line to see what the user wants to do.
*
1$		move.b	(a3)+,d1
		cmpi.b	#' ',d1
		beq.s	1$		; Skip past leading blanks.
		cmpi.b	#'?',d1		; Usage line
		beq	usage
		cmpi.b	#'r',d1		; Remove
		beq	remove
		cmpi.b	#'R',d1
		beq	remove
		cmpi.b	#'n',d1		; NTSC
		beq.s	2$
		cmpi.b	#'N',d1
		bne.s	install

* Exceedingly cheap hack to support PAL machines that want to boot into
* NTSC mode (inspired by Nico Francois' NTSC-PAL programs).  This will
* cause the bootnode to stuff a 0 into the magic ECS Agnus register,
* which should make the system come up in NTSC mode.
*
2$		clr.w	movearg


* No meaningful command line arguments supplied, so we'll assume s/he wants
* to install it.
*
install		tst.l	d0
		bne	installed	; Already installed

* Allocate memory to survive reboot.
*
		move.l	#RESIDENT_SIZ,d0
		move.l	#MEMF_CHIP!MEMF_PUBLIC,d1
		jsr	_LVOAllocMem(a6)
		tst.l	d0
		beq	nomem
		move.l	d0,a2		; Save pointer

* Relocate pointers to destination.
*
		lea	RelTable(pc),a0
		lea	ResStart(pc),a1
		sub.l	a1,d0		; Compute relocation difference
		move.l	d0,d7		; Save it for later

2$		move.l	(a0)+,d0	; Fetch address of thing to relocate
		beq.s	3$		; NULL-terminated array
		move.l	d0,a1
		add.l	d7,(a1)		; Add offset to thing
		bra.s	2$
3$:

* Set a few more things atomically.
*
		jsr	_LVODisable(a6)

* Insert MemList into system KickMem list.  (I would have used AddHead(),
* but SysBase.KickMemPtr is not a struct List.  Oh well...
*
		lea	MyMemList(pc),a0	; Address of our memlist
		move.l	KickMemPtr(a6),d0
		move.l	d0,(a0)		; Append current system entry to us
		add.l	d7,a0		; Add relocation offset
		move.l	a0,KickMemPtr(a6)	; Add us to system

* Insert into Resident module list.  This is done so that running BPD twice
* before rebooting will not cause it to be installed twice (that would be
* bad, you see...).
*
		lea	MyResModule(pc),a0	; Our ResModule array
		move.l	ResModules(a6),d0	; Already ResModules here?
		beq.s	4$
		bset.l	#31,d0		; Yes, set special indirection flag
		move.l	d0,4(a0)	;  and store pointer.
4$:
		add.l	d7,a0		; Add relocation offset
		move.l	a0,ResModules(a6)	; Insert us into Exec list

* Insert into KickTagPtr list.
*
		lea	MyKickTagPtr(pc),a0	; Our KickTagPtr array
		move.l	KickTagPtr(a6),d0	; Already KickTagPtrs here?
		beq.s	5$
		bset.l	#31,d0		; Yep...
		move.l	d0,4(a0)
5$:
		add.l	d7,a0		; Relocate...
		move.l	a0,KickTagPtr(a6)	; Insert us into Exec list

* Copy resident code and data into allocated memory.
*
		lea	ResStart(pc),a0	; src
		movea.l	a2,a1		; dest
		move.l	#RESIDENT_SIZ,d0
		jsr	_LVOCopyMem(a6)

* Recompute KickCheckSum.
*
		jsr	_LVOSumKickData(a6)
		move.l	d0,KickCheckSum(a6)

* Return to reality.
*
		jsr	_LVOEnable(a6)
		lea	BPD.msg(pc),a2
		bsr	climsg
		lea	installed.msg(pc),a2
		bsr	climsg

exit		lea	change.msg(pc),a2
		bsr	climsg
eexit		movea.l	d6,a1
		jsr	_LVOCloseLibrary(a6)
openfailed	move.l	d2,d0		; D2 usually set to 0 by climsg
		rts

errexit		moveq	#20,d2
		bra.s	eexit


****************
* User wants to remove the goodie.
*
remove		tst.l	d0
		beq.s	nothere		; Nothing to remove

* Remove ourselves from the system lists.  First the KickMemPtr list.
*
		lea	KickMemPtr(a6),a1
1$		movea.l	a1,a0
		move.l	(a0),d1
		beq.s	nothere		; Couldn't find MemList entry.

		movea.l	d1,a1
		cmp.l	ML_ME+ME_ADDR(a1),d0	; See if it's us.
		bne.s	1$

		move.l	(a1),(a0)	; Pull us from the list.

* Now try to find us in the KickTagPtr list.
*
		lea	KickTagPtr(a6),a0
tablehop	move.l	(a0),d1
		beq.s	nothere		; End of tables;  Couldn't find us.
		bclr.l	#31,d1
		movea.l	d1,a1

tableloop	move.l	(a1)+,d1
		bgt.s	2$
		lea	-4(a1),a0	; Indirection, get pointer to it
		bra.s	tablehop	;  and loop to next table

2$		cmp.l	d1,d0		; Is it us?
		bne.s	tableloop

* We found ourselves.  Now perform remove.  (Since I know the format of
* the table I installed, I can make assumptions about the data I'm zapping.)
*
		move.l	(a1),d1		; Get pointer to next table.
		btst.b	#7,(a0)		; Did we get here by indirection?
		bne.s	3$
		bclr.l	#31,d1		; No, clear indirection bit.
3$		move.l	d1,(a0)		; Patch to jumper over us.

* Reset KickCheckSum.
*
		jsr	_LVOSumKickData(a6)
		move.l	d0,KickCheckSum(a6)

		lea	BPD.msg(pc),a2
		bsr.s	climsg
		lea	removed.msg(pc),a2
		bsr.s	climsg
		bra.s	exit


****************
* Diagnostic messages.
*
installed	lea	BPD.msg(pc),a2
		bsr.s	climsg
		lea	already.msg(pc),a2
		bsr.s	climsg
		bra.s	errexit

nomem		lea	nomem.msg(pc),a2
		bsr.s	climsg
		bra.s	errexit

notecs		lea	notecs.msg(pc),a2
		bsr.s	climsg
		bra.s	errexit

nothere		lea	BPD.msg(pc),a2
		bsr.s	climsg
		lea	not.msg(pc),a2
		bsr.s	climsg
		lea	installed.msg(pc),a2
		bsr.s	climsg
		bra	errexit

usage		lea	ID(pc),a2
		bsr.s	climsg
		lea	usage.msg(pc),a2
		bsr.s	climsg
		bra	exit

****************
* Routine to print to CLI.
*
climsg		exg	d6,a6		; Error string pointer in A2
		jsr	_LVOOutput(a6)
		move.l	d0,d1
		beq.s	noout		; No output channel

		move.l	a2,d2		; Compute string length
1$		tst.b	(a2)+
		bne.s	1$
		suba.l	d2,a2
		move.l	a2,d3
		subq.l	#1,d3
		jsr	_LVOWrite(a6)	; Write string
noout		exg	d6,a6
		moveq	#0,d2
		rts


****************
* Relocation table.  Contains pointers to addresses to be moved.
*
RelTable	dc.l	RL0,RL1,RL2,RL3,RL4,RL5,RL6,RL7,RL8,0

* Diagnostic strings.
*
dosname		dc.b	'dos.library',0
BPD.msg		dc.b	'BPD ',0
already.msg	dc.b	'already '
installed.msg	dc.b	'installed.',10,0
not.msg		dc.b	'not ',0
notecs.msg	dc.b	'ECS Agnus not present.',10,0
nomem.msg	dc.b	'No memory for resident RomTag.',10,0
removed.msg	dc.b	'removed.',10,0
usage.msg	dc.b	'This program may be freely distributed.',10,10
		dc.b	'Usage: BPD [REMOVE | NTSC]',10,0
change.msg	dc.b	'Change will take effect on next reboot.',10,0


************************************************************************
* The resident stuff intended to survive reboot.
*
ResStart:	cnop	0,2

* The Resident structure.
*
RomTag:					;STRUCTURE RT,0
		dc.w	RTC_MATCHWORD	;   UWORD RT_MATCHWORD
RL0:		dc.l	RomTag		;   APTR  RT_MATCHTAG
RL1:		dc.l	ResEnd		;   APTR  RT_ENDSKIP
		dc.b	RTF_COLDSTART	;   UBYTE RT_FLAGS
		dc.b	1		;   UBYTE RT_VERSION
		dc.b	NT_BOOTNODE	;   UBYTE RT_TYPE
		dc.b	TAG_PRI		;   BYTE  RT_PRI
RL2:		dc.l	Name		;   APTR  RT_NAME
RL3:		dc.l	ID		;   APTR  RT_IDSTRING
RL4:		dc.l	PALTrick	;   APTR  RT_INIT


* The MemList.
*
MyMemList:				; STRUCTURE ML,LN_SIZE
					;	STRUCTURE  LN,0
		dc.l	0		;	   APTR    LN_SUCC
		dc.l	0		;	   APTR    LN_PRED
		dc.b	NT_MEMORY	;	   UBYTE   LN_TYPE
		dc.b	0		;	   BYTE    LN_PRI
RL5:		dc.l	Name		;	   APTR    LN_NAME
		dc.w	1		;    UWORD   ML_NUMENTRIES
					; STRUCTURE ME,0
RL6:		dc.l	ResStart	;    APTR    ME_ADDR
		dc.l	RESIDENT_SIZ	;    ULONG   ME_LENGTH


* Array of pointers to RomTag structures describing things to be made
* resident.  If the entry in the array has bit 31 set (0x80000000), then
* the pointer (sans high bit) is in fact a pointer to *another* array of
* RomTag pointers.  A NULL terminates the array.
* (These also need relocation.)
*
RL7:
MyKickTagPtr	dc.l	RomTag,0
RL8:
MyResModule	dc.l	RomTag,0


* Name and ID strings.  (I wonder if Xoper will show these?)
*
Name		dc.b	'BPD.resident',0
ID		dc.b	'BPD (Boot PAL, Dammit!) v1.1 by Leo L. Schwab  '
		dc.b	'(11 Nov 1990)',13,10,0

****************
* Why we went to all this trouble.  Kind of anti-climactic, isn't it?
* (PAL Now Brown Cow)
*
PALTrick:	btst.b	#6,$BFE001	; Check mouse button
		beq.s	palexit		; If pressed, let user boot normally

movearg		EQU	*+2		; The immediate argument to the MOVE
		move.w	#$0020,$DFF1DC	; Turn on PAL bit in ECS Agnus
palexit:	moveq	#0,d0
		rts

ResEnd		EQU	*
RESIDENT_SIZ	EQU	ResEnd-ResStart	; Size of preserved block.

		END
