COMMENT ~
 PROGRAM: FLASH.ASM
  AUTHOR: Denis Boyles
 RELEASE: Public Domain (Nov 30, 1996)

 COMPILE: Arrowsoft Assembler (MASM v3.0)
      OS: MS-DOS

 PURPOSE: change border colors like my C64 program did
~

;--------------------------------------
;define some constants for us
;--------------------------------------

ID          EQU 0DBF0h                 ;program ID
TIMER       EQU 18                     ;change color every TIMER ticks

;--------------------------------------
;define default code segments
;--------------------------------------

CODE SEGMENT
    ASSUME CS:CODE,DS:CODE,ES:CODE,SS:CODE
    ORG 0100h

;--------------------------------------
;skip TSR code to program init
;--------------------------------------

MAIN:
    jmp     short START

;--------------------------------------
;TSR data goes here that's kept
;--------------------------------------

OldTck      dw ?                       ;and OldTck-2 for old INT 1Ch vector
OldPlx      dd ?                       ;for old INT 2Fh vector

color       db 1                       ;border color
counter     db 0                       ;time to change counter

;--------------------------------------
;NewTck - our new user tick handler
;--------------------------------------

NewTck proc
    push    AX                         ;saved used registers on stack
    push    DX
    push    DS

    push    CS                         ;save CS and bring back as DS
    pop     DS

    cmp     [counter],TIMER            ;has time to change been reached?
    jnz     CntDwn                     ;NO then keep counting and exit

    mov     [counter],000h             ;YES reset counter

    call    SetBorder                  ;change to border to current color

    cmp     [color],00Fh               ;have we done all 16 (0-15) colors?
    jz      ZrClr                      ;YES then reset color back to 0

    inc     [color]                    ;NO then next color=color+1
    jmp     ExTck                      ;jump over to exit interrupt

ZrClr:
    mov     [color],000h               ;reset color back to 0
    jmp     ExTck                      ;jump over to exit interrupt

CntDwn:
    inc     [counter]                  ;increment change timer

ExTck:
    pop     DS                         ;restore saved registers from stack
    pop     DX
    pop     AX
    jmp     dword ptr CS:[OldTck-2]    ;jump to old interrupt to end
NewTck endp

;--------------------------------------
;NewPlx - new multiplex TSR handler
;         for TSR detection
;--------------------------------------

NewPlx proc far
    cmp     AX,ID                      ;did we call ourselves?
    jnz     ExPlx                      ;NO exit INT to continue

    push    CS                         ;YES we did, save so our CS on the
    pop     ES                         ;stack to bring back as ES
    stc                                ;set carry flag to confirm ourselves
    ret     2                          ;return back and keep out flag state

ExPlx:
    jmp     dword ptr CS:[OldPlx]      ;not ourselves, jump to previous INT
NewPlx endp

;--------------------------------------
;SetBorder - used by NewTck to change
;            border colors
;--------------------------------------

SetBorder proc
    mov     DX,03DAh                   ;check CGA status port...

ClearVR:
    in      AL,DX                      ;...for a vertical retrace
    test    AL,008h                    ;are we in retrace?
    jnz     ClearVR                    ;YES keep looping until we aren't

WaitVR:
    in      AL,DX                      ;after flushing previous, check again
    test    AL,008h                    ;are we in retrace?
    jz      WaitVR                     ;NO keep looping until we are

    mov     DX,03C0h                   ;setup EGA ATC for border color change
    mov     AL,011h
    out     DX,AL

    mov     AL,[color]                 ;send current color to EGA ATC port
    out     DX,AL                      ;to change the border color

    mov     AL,020h                    ;reset EGA ATC to turn video back on
    out     DX,AL
    ret                                ;back to caller
SetBorder endp

;--------------------------------------
;program actually starts here
;--------------------------------------

START:
    mov     AX,ID                      ;check if our program is already
    int     02Fh                       ;resident by calling INT 2Fh with ID

    jc      RemoveTSR                  ;carry flag set? YES remove ourselves
                                       ;otherwise install ourselves

    mov     AX,DS:[02Ch]               ;get environment segment of program
    mov     ES,AX                      ;from PSP and store in ES
    mov     AH,049h                    ;DOS free memory block
    int     021h                       ;call DOS to free the environment

    mov     AX,0351Ch                  ;DOS get vector for INT 13h
    int     021h                       ;call DOS to get the vector

    mov     [OldTck-2],BX              ;store offset of vector in memory
    mov     [OldTck],ES                ;store segment of vector in memory

    mov     AX,0352Fh                  ;DOS get vector for INT 2Fh
    int     021h                       ;call DOS to get vector

    mov     word ptr [OldPlx],BX       ;store offset of vector in memory
    mov     word ptr [OldPlx+2],ES     ;store segment of vector in memory

    mov     AX,0251Ch                  ;DOS set vector for new INT 13h
    mov     DX,offset NewTck           ;DS:DX -> new interrupt handler
    int     021h                       ;call DOS to set new handler vector

    mov     AX,0252Fh                  ;DOS set vector for new INT 2fh
    mov     DX,offset NewPlx           ;DS:DX -> new interrupt handler
    int     021h                       ;call DOS to set new handler vector

    mov     AH,009h                    ;DOS print an ASCIID string
    mov     DX,offset msg0             ;DS:DX -> string to print
    int     021h                       ;call DOS to print installed message

    mov     DX,offset START            ;everything upto START is kept
    mov     CL,004h                    ;in memory, so convert bytes to
    shr     DX,CL                      ;paragraphs. 1p = 16 bytes
    inc     DX                         ;plus one more to even it out

    mov     AX,03100h                  ;DOS TSR program with code (0)
    int     021h                       ;call DOS to leave resident

RemoveTSR:                             ;On ID check ES = program's CS
    push    DS                         ;save DS register to stack

    lds     DX,dword ptr ES:[OldTck-2] ;DS:DX -> old vector for INT 1Ch
    mov     AX,0251Ch                  ;DOS set vector for old INT 1Ch
    int     021h                       ;call DOS to restore INT 1Ch vector

    lds     DX,ES:[OldPlx]             ;DS:DX -> old vector for INT 2Fh
    mov     AX,0252Fh                  ;DOS set vector for old INT 2Fh
    int     021h                       ;call DOS to restore INT 2Fh vector

    pop     DS                         ;restore our saved DS register

    mov     [color],000h               ;reset border color back to black
    call    SetBorder

    mov     AH,049h                    ;DOS free memory block @ ES
    int     021h                       ;call DOS to free our TSR memory

    mov     AH,009h                    ;DOS print an ASCIID string
    mov     DX,offset msg1             ;DS:DX -> string to print
    int     021h                       ;call DOS to print our removed message

    mov     AX,04C00h                  ;DOS terminate program with code (0)
    int     021h                       ;call DOS to end our program

;--------------------------------------
;data for main program goes here
;--------------------------------------

msg0        db 'FLASH installed!',00Dh,00Ah,'$'
msg1        db 'FLASH removed!',00Dh,00Ah,'$'

CODE ENDS
END MAIN

