; THE ULTIMATE SCREEN SAVER
;   by Richard Leinecker
; Copyright 1989 by ST-LOG

Kbdvbase        = $22
Supexec         = $26
Ptermres        = $31

GEMDOS          = 1
XBIOS           = 14


**********************************************************************
*************************** start of program *************************
**********************************************************************

        .text

start:
        movea.l a7,a5           ; copy a7 for base page calc
        movea.l #ustk,a7        ; init adr for local stack

        move.l  #setup,-(sp)    ; put address of set up routine on stack
        move.w  #Supexec,-(sp)  ; SUPER mode command
        trap    #XBIOS          ; execute setup routine in SUPERVISOR mode
        addq.l  #6,sp           ; restore the stack pointer


**********************************************************************
*                Insert keyboard patch                               *
**********************************************************************

        move.w  #Kbdvbase,-(sp)         ; XBIOS to get vectors
        trap    #XBIOS                  ; do the trap
        addq.l  #2,sp                   ; clean up stack
        add.l   #32,d0                  ; +32 to get the keyboard vector address
        movea.l d0,a0                   ; put it in a0
        move.l  (a0),keyvector          ; move the vector to keyvector

        movea.l #insert,a0              ; address of the jmp in the patch
        adda.l  #2,a0                   ; adjust it
        move.l  keyvector,(a0)          ; now put the keyboard vector in place

        movea.l d0,a0                   ; get address of patch
        move.l  #trapper,(a0)           ; install it

        move.l  #newmem,d0              ; new screen memory into d0
        andi.l  #$fffffe00,d0           ; assure 512 byte boundary
        addi.l  #512,d0
        move.l  d0,newscreen            ; put this value in newscreen

**********************************************************************
*       calculate amount of memory to keep, then terminate!          *
**********************************************************************

        movea.l 4(a5),a5        ; basepage address
        move.l  $c(a5),d0       ; length of text segment
        add.l   $14(a5),d0      ; length of data segment
        add.l   $1c(a5),d0      ; length of bss segment
        add.l   #$100,d0        ; allow for basepage

        clr.w   -(sp)           ; specify exit code
        move.l  d0,-(sp)        ; length of block to keep
        move.w  #Ptermres,-(sp) ; Terminate and stay resident
        trap    #GEMDOS         ; This doesn't return!

**********************************************************************
*          Install the vertical blank interrupt                      *
**********************************************************************

setup:
        move.w  #0,d1           ; set the counter to zero
        movea.l #$456,a0        ; load a0 with address of the pointer to the vector table
        movea.l (a0),a1         ; load a1 with the address of the vector table

        move.w  #0,d0           ; zero the color
        move.w  d0,vbiflag      ; set the flag to 0

        movea.l #$454,a2        ; nvbls
        move.w  (a2),d3         ; get the number of vector slots

        adda.l  #4,a1
check:
        move.l  (a1),d2         ; put the vector address in d2
        cmpi.l  #0,d2           ; see if it is a null
        beq     install         ; if it is null then go to the install phase
        addq.w  #1,d1           ; add one to the counter
        cmp.w   d3,d1           ; see if all 8 slots have been checked
        beq     skip            ; skip it if there are no slots left
        adda.l  #4,a1           ; increment a1 to the next vector slot
        bra     check           ; go back and check the next vector slot

install:
        move.l  #vbi,(a1)       ; install the vbi address in the vacant slot
        rts                     ; then leave

skip:
        rts                     ; leave


**********************************************************************
*                      Here is the interrupt code                    *
**********************************************************************

vbi:
        move.w  vbiflag,d0              ; see if the fireworks are going off
        cmpi.w  #1,d0                   ; 1 means yes
        bne     continue                ; if not, keep counting

        ; firework 1

        movea.l #fw1,a0                 ; copy firework data to the
        movea.l #fcolor,a1              ; work area - first assign the
        jsr     assign                  ; pointers then jsr 'assign'
        jsr     realvbi                 ; do the actual  firework stuff
        movea.l #fw1,a1                 ; copy the work area back to
        movea.l #fcolor,a0              ; the firework data
        jsr     assign

        ; firework 2

        movea.l #fw2,a0                 ; copy firework data to the
        movea.l #fcolor,a1              ; work area - first assign the
        jsr     assign                  ; pointers then jsr 'assign'
        jsr     realvbi                 ; do the actual  firework stuff
        movea.l #fw2,a1                 ; copy the work area back to
        movea.l #fcolor,a0              ; the firework data
        jsr     assign

        ; firework 3

        movea.l #fw3,a0                 ; copy firework data to the
        movea.l #fcolor,a1              ; work area - first assign the
        jsr     assign                  ; pointers then jsr 'assign'
        jsr     realvbi                 ; do the actual  firework stuff
        movea.l #fw3,a1                 ; copy the work area back to
        movea.l #fcolor,a0              ; the firework data
        jsr     assign

        ; firework 4

        movea.l #fw4,a0                 ; copy firework data to the
        movea.l #fcolor,a1              ; work area - first assign the
        jsr     assign                  ; pointers then jsr 'assign'
        jsr     realvbi                 ; do the actual  firework stuff
        movea.l #fw4,a1                 ; copy the work area back to
        movea.l #fcolor,a0              ; the firework data
        jsr     assign

        rts                             ; leave, all 4 fireworks done

realvbi:
        move.w  fnum,d0         ; see if there are fireworks going off
        cmpi.w  #0,d0           ; are there any?
        bne     makefw          ; If so, go there

        move.w  fcolor,d0       ; load the color
        move.w  ty,d1           ; load the y coordinate
        move.w  tx,d2           ; load the x coordinate
        jsr     pixel           ; XOR the old pixel

        move.w  ty,d1           ; load the y coordinate
        move.w  tx,d2           ; load the x coordinate
        subq.w  #1,d1           ; decrement by delta y
        move.w  trajectory,d4   ; load the trajectory number
        cmpi.w  #0,d4           ; is it zero?
        bne     notleft
        addq.w  #1,d2           ; is it one?
        bra     notside

notleft:
        cmpi.w  #3,d4
        bne     notside
        subq.w  #1,d2

notside:
        move.w  d2,tx           ; save tx
        move.w  d1,ty           ; save ty

        cmp.w   fheight,d1      ; compare with the intended height
        bgt     makepixel       ; if not there, just make a pixel

        move.w  #0,numcount     ; zero the explosion counter
        move.w  #1,fnum         ; set fnum to the small pattern
        move.w  #0,fwvblanks    ; zero the counter
        jsr     drawfw          ; jump and XOR the fw explosion
        rts                     ; leave

makefw:
        move.w  fwvblanks,d0    ; load the counter
        addq.w  #1,d0           ; increment it
        cmpi.w  #4,d0           ; see if a delay has passed
        beq     delaygone       ; go on if so
        move.w  d0,fwvblanks    ; store the value
        rts                     ; leave

delaygone:
        move.w  #0,fwvblanks    ; zero the counter
        jsr     drawfw          ; jump and XOR the fw explosion
        move.w  fnum,d0         ; load from fnum
        addq.w  #1,d0           ; increment the counter
        move.w  d0,fnum         ; store it
        cmpi.w  #9,d0           ; have there been seven patterns
        beq     is_nine         ; go on if not
        jsr     drawfw          ; jump and XOR the fw explosion
        rts                     ; leave

is_nine:
        move.w  numcount,d0     ; load numcount
        addq.w  #1,d0           ; increment counter
        move.w  d0,numcount     ; store it
        move.w  numexplode,d1   ; load explosion number
        cmp.w   d0,d1           ; compare them
        beq     is_done         ; firework explosions finished!

        move.w  #1,fnum         ; reset fnum to 1

        movea.l #$4bc,a0        ; pointer to system clock

        move.w  (a0),d0         ; get the value
        move.w  colormask,d1
        and.w   d1,d0           ; mask to use maximum colors only
        cmpi.w  #0,d0
        bne     nzero1
        ori.w   #1,d0           ; insure plane one
nzero1:
        move.w  d0,fcolor       ; store in fcolor

        move.w  (a0),d0         ; get a number
        andi.w  #15,d0          ; and it to assure value in range
        move.w  (a0),d3         ; get another number
        andi.w  #7,d3           ; and it to assure value in range
        move.w  tx,d1           ; load x coordinate
        move.w  ty,d2           ; load y coordinate
        subi.w  #7,d1           ; subtract 4
        subi.w  #4,d2           ; subtract 4
        add.w   d0,d1           ; add the random value
        add.w   d3,d2           ; add the other random value
        move.w  d1,tx           ; store it
        move.w  d2,ty           ; store it

        jsr     drawfw          ; make the pattern
        rts                     ; leave

is_done:
        move.w  #0,fnum         ; zero fnum

        movea.l #$4bc,a0        ; pointer to system clock

        move.w  (a0),d0         ; get the value
        move.w  colormask,d1
        and.w   d1,d0           ; mask to use maximum colors only
        cmpi.w  #0,d0
        bne     nzero2
        ori.w   #1,d0           ; insure plane one
nzero2:
        move.w  d0,fcolor       ; store in fcolor

        move.w  (a0),d4         ; now get another value
        andi.w  #31,d4          ; and it to insure value is in range
        addi.w  #40,d4          ; add 31
        move.w  d4,fheight      ; store in fheight

        move.w  (a0),d4         ; get another random number
        andi.w  #3,d4           ; and it to assure value is in range
        addq.w  #1,d4           ; add one
        move.w  d4,numexplode   ; store the value

        move.w  (a0),d4         ; get yet another value
        andi.w  #3,d4           ; and it to assure value is in range
        move.w  d4,trajectory   ; store in trajectory
        mulu    onethird,d4     ; adjust the starting point
        add.w   fromleft,d4     ; according to resolution

        move.w  #199,ty         ; store the y coordinate starting point
        move.w  #199,d1         ; put 199 in d1
        move.w  d4,tx           ; store the x coordinate starting point
        move.w  d4,d2           ; store x value

makepixel:
        move.w  fcolor,d0       ; move the color into d0
        jsr     pixel           ; goto the pixel routine
        rts                     ; leave

continue:
        move.w  vblanks,d0      ; get the vblanks counter value
        addq.w  #1,d0           ; increment the counter
        move.w  d0,vblanks      ; store the vblanks counter value
        cmpi.w  #32000,d0       ; see if the appropriate time has passed
        bne     gohome          ; otherwise skip it

        move.l  newscreen,d0    ; get the screen address
        movea.l d0,a0           ; move the newscreen address into a0
        move.w  #0,d0           ; set the loop counter to zero

clearit:
        move.l  #0,(a0)+        ; clear the screen
        addq.w  #1,d0           ; increment loop counter
        cmpi    #8000,d0        ; have 8000 longs been cleared?
        bne     clearit         ; otherwise do some more

        movea.l #$44e,a0        ; set to _v_bas_ad - contains the screen address
        move.l  (a0),oldscreen  ; get the old screen location

        movea.l #$ffff8201,a0   ; set to the Physbase location
        movea.l #newscreen,a1   ; move the address of newscreen into a1
        adda.l  #1,a1           ; increment the pointer to get correct byte
        move.b  (a1),(a0)       ; move the byte to the Physbase location
        adda.l  #2,a0           ; increment to the other byte location
        adda.l  #1,a1           ; add 1 to get the correct byte of newscreen
        move.b  (a1),(a0)       ; move the second byte

        movea.l #$ff8260,a0     ; set to ff8260 - contains the resolution
        move.b  (a0),d0         ; put resolution in d0
        andi.w  #$0003,d0       ; mask off any rubish
        move.w  d0,rez          ; store it in 'rez'
        move.w  #0,oldrez       ; store '0' in oldrez

        cmpi.w  #1,d0           ; see if we are in medium resolution
        bne     dontchange      ; if not, don't attempt to change res
        movea.l #$ff8260,a0     ; set to the hardware resolution location
        move.w  (a0),d1         ; get the old resolution value
        move.w  d1,oldrez       ; save it
        move.w  #0,d0           ; move a zero into d0
        movea.l #$44c,a0        ; set to the system variable location
        move.b  d0,(a0)         ; store the new res
        movea.l #$ff8260,a0     ; set to the hardware location
        move.b  d0,(a0)         ; store the new res
        move.w  d0,rez          ; store the new res in 'rez'

dontchange:
        cmpi.w  #0,d0           ; assign resolution dependant values
        bne     notlow
        move.w  #8,wordbytes    ; bytes per 16 screen pixels
        move.w  #$000f,colormask; number of colors to be used
        move.w  #80,onethird    ; horizontal distance (c. one third of screen)
        move.w  #40,fromleft    ; value to start from left of screen
        move.w  #1,xadjust      ; x factor
        bra     gotrez

notlow:
        move.w  #2,wordbytes    ; bytes per 16 screen pixels
        move.w  #0,colormask    ; number of colors to be used
        move.w  #160,onethird   ; horizontal distance (c. one third of screen)
        move.w  #40,fromleft    ; value to start from left of screen
        move.w  #2,xadjust      ; x factor

gotrez:
        movea.l #oldpalette,a1  ; set destination of palette copy
        movea.l #$ff8240,a0     ; set source of palette copy
        jsr     getsetpalette   ; jump and do the copy
        movea.l #newpalette,a0  ; set source of palette copy
        movea.l #$ff8240,a1     ; set destination of palette copy
        jsr     getsetpalette   ; jump and do the copy

        move.w  #1,vbiflag

        move.w  #0,countfw              ; zero the counter
        move.w  #0,d7
        move.w  #0,fnum
        move.w  #0,numcount

four_fw:
        movea.l #$4bc,a0        ; pointer to system clock
        move.w  (a0),d0         ; get the value
        move.w  colormask,d1
        and.w   d1,d0           ; mask to use maximum colors only
        cmpi.w  #0,d0
        bne     nzero3
        ori.w   #1,d0           ; insure plane one
nzero3:
        move.w  d0,fcolor       ; store in fcolor

        move.w  d7,d4
        addq.w  #1,d4
        mulu    #10,d4
        addi.w  #40,d4
        move.w  d4,fheight      ; store in fheight

        move.w  (a0),d4         ; get another random number
        andi.w  #3,d4           ; and it to assure value is in range
        addq.w  #1,d4           ; add one
        move.w  d4,numexplode   ; store the value

        move.w  d7,d4
        move.w  d4,trajectory   ; store in trajectory
        mulu    onethird,d4     ; adjust the starting point
        add.w   fromleft,d4     ; according to resolution

        move.w  #199,ty         ; store the y coordinate starting point
        move.w  #199,d1         ; put 199 in d1
        move.w  d4,tx           ; store the x coordinate starting point
        move.w  d4,d2           ; store x value

        move.w  countfw,d7

        addq.w  #1,d7
        move.w  d7,countfw
        cmpi.w  #1,d7
        bne     pastfw1
        movea.l #fcolor,a0
        movea.l #fw1,a1
        jsr     assign
        move.w  fcolor,d0
        move.w  ty,d1
        move.w  tx,d2
        jsr     pixel           ; make the pixel
        bra     four_fw

* Generate four sets of random firework data

pastfw1:
        cmpi.w  #2,d7
        bne     pastfw2
        movea.l #fcolor,a0
        movea.l #fw2,a1
        jsr     assign
        move.w  fcolor,d0
        move.w  ty,d1
        move.w  tx,d2
        jsr     pixel           ; make the pixel
        bra     four_fw
pastfw2:
        cmpi.w  #3,d7
        bne     pastfw3
        movea.l #fcolor,a0
        movea.l #fw3,a1
        jsr     assign
        move.w  fcolor,d0
        move.w  ty,d1
        move.w  tx,d2
        jsr     pixel           ; make the pixel
        bra     four_fw
pastfw3:
        cmpi.w  #4,d7
        bne     gohome
        movea.l #fcolor,a0
        movea.l #fw4,a1
        jsr     assign
        move.w  fcolor,d0
        move.w  ty,d1
        move.w  tx,d2
        jsr     pixel           ; make the pixel

gohome: rts                     ; leave



**********************************************************************
*                      Here is the keyboard patch                    *
**********************************************************************

trapper:
        move.w  #0,vblanks      ; reset the vbi counter to zero
        movem.l a0-a1/d0/d7,-(sp)  ; save addresses and loop counter
        move.w  vbiflag,d0      ; get the value in the vbi flag
        cmpi.w  #1,d0           ; see if it is one (enabled)
        bne     getlost         ; if not leave...

        movea.l #$ffff8201,a0
        movea.l #oldscreen,a1
        adda.l  #1,a1
        move.b  (a1),(a0)
        adda.l  #2,a0
        adda.l  #1,a1
        move.b  (a1),(a0)

        move.w  #0,fnum         ; zero fnum in case of fireworks going off

        movea.l #$ff8240,a1     ; set destination of palette copy
        movea.l #oldpalette,a0  ; set source of palette copy
        jsr     getsetpalette   ; jump and do the copy

        move.w  #0, vbiflag     ; set the vbi flag to zero

        move.w  oldrez,d0
        cmpi.w  #0,d0
        beq     getlost
        move.w  #1,d0
        movea.l #$44c,a0
        move.b  d0,(a0)
        movea.l #$ff8260,a0
        move.b  d0,(a0)

getlost:
        movem.l (sp)+,a0-a1/d0/d7  ; restore addresses and loop counter
insert:
        jmp     $11111111       ; this address is changed upon program init


**********************************************************************
*        This routine XORs a pixel on the screen                     *
**********************************************************************

pixel:

* Pixel color in d0
* y coordinate in d1
* x coordinate in d2
* use a3 for screen address
* use a4 for bit mask

        movea.l newscreen,a3    ; put the screen address in a3
        mulu    #160,d1         ; multiply the y coordinate by 16
        andi.l  #$0000ffff,d1
        adda.l  d1,a3           ; add the y value to a3
        andi.l  #$0000ffff,d2
        divu    #16,d2          ; divide the x by 16 to get the number of words
        move.l  d2,d1           ; store the result in order to ascertain the remainder
        andi.l  #$0000ffff,d2
        mulu    wordbytes,d2    ; multiply by wordbytes to find the correct byte offset
        andi.l  #$0000ffff,d2
        adda.l  d2,a3           ; add the byte offset to a3
        lsr.l   #8,d1           ; shift 8
        lsr.l   #7,d1           ; shift 7
        andi.l  #$000000fe,d1   ; offset in d1

        movea.l #pixbits,a4     ; put the pixbit address in a4
        adda.w  d1,a4           ; add the correct offset to a4
        move.w  (a4),d3         ; move the data into d3

* Now the pixel data is in d3 and the screen address is in a3

        btst    #0,d0
        beq     secondplane     ; if not, skip to the second plane

        move.w  (a3),d2         ; get the screen byte
        eor.w   d3,d2           ; XOR it with the data
        move.w  d2,(a3)         ; put it back on the screen

secondplane:
        adda.w  #2,a3           ; add the correct number of bytes
        btst    #1,d0
        beq     thirdplane      ; if not, skip to third plane

        move.w  (a3),d2         ; get the screen byte
        eor.w   d3,d2           ; XOR it with the data
        move.w  d2,(a3)         ; put it back on the screen

thirdplane:
        adda.w  #2,a3           ; add the correct number of bytes
        btst    #2,d0
        beq     fourthplane     ; if not, skip to the fourth plane

        move.w  (a3),d2         ; get the screen byte
        eor.w   d3,d2           ; XOR it with the data
        move.w  d2,(a3)         ; put it back on the screen

fourthplane:
        btst    #3,d0
        beq     lastplane       ; if not then leave

        adda.w  #2,a3           ; add the correct number of bytes
        move.w  (a3),d2         ; get the screen byte
        eor.w   d3,d2           ; XOR it with the data
        move.w  d2,(a3)         ; put it back on the screen

lastplane:
        rts                     ; leave the routine


*************************************************************************
*       Get/Set current pallete  (must be in Supervisor)                *
*************************************************************************

getsetpalette:
        move.w  #0,d7                   ; zero the counter

gsploop:move.w  (a0)+,(a1)+             ; move the data
        addq.w  #1,d7                   ; increment the counter
        cmpi.w  #16,d7                  ; have you copied 16?
        bne     gsploop                 ; if not, do some more

        rts                             ; leave subroutine

************************************************************************
*                     Draw a firework explosion part                   *
************************************************************************

drawfw:
        move.w  fcolor,d0       ; get the firework color
        move.w  #0,d7           ; zero the counter
        movea.l #fpattern,a5    ; get the address of fpattern
        move.w  fnum,d6         ; load the firework number
        move.w  d6,d5           ; save the firework number in d5 for future use
        mulu    #3,d6           ; figure out the radius

do_dots:
        move.w  tx,d2           ; get the x coordinate
        sub.w   d6,d2           ; adjust to top left of firework pattern
        move.w  xadjust,d4      ; load xadjust
        cmpi.w  #2,d4           ; see if it is 2
        bne     xa_not_2        ; go on otherwise
        sub.w   d6,d2           ; adjust additional amount
xa_not_2:
        move.w  ty,d1           ; get the y coordinate
        sub.w   d6,d1           ; adjust to top left of firework pattern
        move.w  (a5)+,d4        ; get the x pattern element
        mulu    d5,d4           ; multiply according to the size of the fw
        mulu    xadjust,d4      ; resolution factor
        add.w   d4,d2           ; add the x position to the top left of pattern
        move.w  (a5)+,d4        ; get the y pattern element
        mulu    d5,d4           ; multiply according to the size of the fw
        add.w   d4,d1           ; add the y position to the top left of pattern
        addq.w  #1,d7           ; increment the counter
        jsr     pixel           ; now, make the pixel
        cmpi.w  #8,d7           ; see if you have done the 8 dots
        bne     do_dots         ; do some more if not

        rts                     ; leave the routine

************************************************************************
*           Move the specified firework data to the work area          *
************************************************************************

assign:
        move.w  #0,d0

do_more:
        move.w  (a0)+,(a1)+
        addq.w  #1,d0
        cmpi.w  #10,d0
        bne     do_more

        rts


**********************************************************************
*                       Data Storage Segment                         *
**********************************************************************

                .even
                .data

pixbits:        .dc.w   $8000,$4000,$2000,$1000,$0800,$0400,$0200,$0100
                .dc.w   $0080,$0040,$0020,$0010,$0008,$0004,$0002,$0001

newpalette:     .dc.w   $0000,$0070,$0700,$0700,$0770,$0707,$0077,$0777
                .dc.w   $0770,$0704,$0074,$0740,$0704,$0740,$0400,$0700

fpattern:       .dc.w   $0000,$0003,$0001,$0001,$0003,$0000,$0005,$0001
                .dc.w   $0006,$0003,$0005,$0005,$0003,$0006,$0001,$0005

fw1:            .dc.w   $0000,$0000,$0000,$0000,$0000,$0000
                .dc.w   $0000,$0000,$0000,$0000
fw2:            .dc.w   $0000,$0000,$0000,$0000,$0000,$0000
                .dc.w   $0000,$0000,$0000,$0000
fw3:            .dc.w   $0000,$0000,$0000,$0000,$0000,$0000
                .dc.w   $0000,$0000,$0000,$0000
fw4:            .dc.w   $0000,$0000,$0000,$0000,$0000,$0000
                .dc.w   $0000,$0000,$0000,$0000


**************************************************************************
* Block Storage Segment

                .bss

rez:            .ds.w   1       ; current resolution
oldrez:         .ds.w   1
oldscreen:      .ds.l   1       ; old screen pointer
keyvector:      .ds.l   1       ; keyboard vector
oldpalette:     .ds.w   16
wordbytes:      .ds.w   1       ; bytes per screen word
colormask:      .ds.w   1
onethird:       .ds.w   1
fromleft:       .ds.w   1
xadjust:        .ds.w   1
newmem:         .ds.l   8200
newscreen:      .ds.l   1
vblanks:        .ds.w   1       ; vblank counter
countfw:        .ds.w   1
vbiflag:        .ds.w   1       ; status of the screen saver
                                ; 0=not enabled
                                ; 1=enabled

fcolor:         .ds.w   1
numexplode:     .ds.w   1
trajectory:     .ds.w   1
fheight:        .ds.w   1
ty:             .ds.w   1
tx:             .ds.w   1
fnum:           .ds.w   1
numcount:       .ds.w   1
fwvblanks:      .ds.w   1


                .ds.l    255    ; the stack can grow down 1K before
ustk:           .ds.l    1      ; it starts to eat the program!!
                .end
