*\  :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
