; Saver.ASM
;
; A Simple screen saver. Written by Inbar Raz, released to the public domain.
;

        model   tiny
        .code

; Command Line parameters

        org     00080h

CharacterCount  db      ?
CommandLine     Label   Byte

; Program code

        org     00100h

Start:  jmp     Begin

; Key to toggle screen saver on or off

TOGKEY  EQU     046h                            ; Scroll Lock

; Key to immediately blank screen

BLKKEY  EQU     058h                            ; F12

SPACE   EQU     020h                            ; Space
EOL     EQU     00Dh                            ; Carriage Return

Old09   dd      ?
Old1C   dd      ?
Old2F   dd      ?

Ticks   dw      ?                               ; Tick counts
ScrOff  db      ?                               ; Screen flag. 1 = Screen off
Active  db      ?                               ; Activity flag. 0 = Off.

SetStat db      ?
FlagShow =      000h
FlagHide =      001h

; ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß

SetScr  Proc    Near

        push    ax
        push    bx

        ; Set screen status flag

        mov     al,byte ptr cs:[SetStat]
        mov     byte ptr cs:[ScrOff],al         ; Screen Status

        ; Set screen mode

        mov     ah,012h                         ; Set Screen mode
        mov     bl,036h
        int     10h

        pop     bx
        pop     ax

        ret

SetScr  Endp

; ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß

New1C   Proc    Far

        cli
        pushf

        cmp     byte ptr cs:[Active],001h       ; Are we active?
        jne     End1C                           ; If not, exit

        cmp     byte ptr cs:[ScrOff],001h       ; Is the screen off?
        je      End1C                           ; If yes, no taste to increase

        inc     word ptr cs:[Ticks]

        db      02Eh, 081h, 03Eh                ; CMP WORD PTR CS:
        dw      Ticks                           ; [TICKS],
Delay   dw      ?                               ; [TRIGGER VALUES]

        jne     End1C                           ; If not equal, continue

; Turn off screen

        mov     byte ptr cs:[SetStat],FlagHide
        call    SetScr

End1C:  popf
        sti
        jmp     dword ptr cs:[Old1C]

New1C   Endp

; ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß

New09   Proc    Far

        cli
        pushf

        push    ax

        in      al,060h
        cmp     al,TOGKEY
        je      Toggle

        pop     ax

        cmp     byte ptr cs:[Active],001h
        jne     Done09

        push    ax

        in      al,060h
        cmp     al,BLKKEY or 080h
        pop     ax
        je      Done09

        mov     word ptr cs:[Ticks],00000h      ; Zero counter
        cmp     byte ptr cs:[ScrOff],001h       ; Is the screen off?
        jne     Trigger                         ; If yes, turn it on

        mov     byte ptr cs:[SetStat],FlagShow
        call    SetScr

; Now process the key

Trigger:
        push    ax

        in      al,060h
        cmp     al,TOGKEY
        je      Toggle

        cmp     al,BLKKEY
        pop     ax
        jne     Done09

Blank:  mov     byte ptr cs:[SetStat],FlagHide
        call    SetScr
        jmp     short Done09

Toggle:

; Meantime - reset mouse.

        push    ax
        push    bx

        xor     ax,ax
        int     033h

        pop     bx
        pop     ax

; First, make sure it's a PURE ScrollLock, no Ctrl etc

        push    es

        xor     ax,ax
        mov     es,ax

        mov     al,byte ptr es:[00417h]         ; Keyboard status flag
        and     al,00Fh                         ; Mask SHIFT bits

        pop     es
        pop     ax

        jne     Done09

        xor     byte ptr cs:[Active],001h       ; Toggle flag

Done09: popf
        sti
        jmp     dword ptr cs:[Old09]

New09   Endp

; ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
;
; This is the identification routine. When called, it returns a return code
; in AX that allows the calling program to know Saver is resident, and also
; it allows the calling program to fetch the active flag in BH.
;
; Call with AX,BX,CX = 'SAVER!',
;           DX       = 0 - Installation Check:
;                          BH = Active Flag
;                          ES = PSP of resident part

New2F   Proc    Far

        cli
        pushf

        cmp     ax,'SA'
        jne     Done2F

        cmp     bx,'VE'
        jne     Done2F

        cmp     cx,'R?'
        jne     Done2F

; Yes, this is a Saver query.

        mov     ax,'IR'                 ; Inbar Raz :-)
        mov     bh,byte ptr cs:Active

        push    cs
        pop     es

Done2F: popf
        sti
        jmp     dword ptr cs:[Old2F]

New2F   Endp

; ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
;
; If there is no command line, the defaults are assumed:
;
; Delay time  : 1 minute
; ScreenWrite : Does NOT refresh display

Begin:  mov     ah,009h
        mov     dx,offset msgcw
        int     021h

        cmp     CharacterCount,000h

        je      @@1

        cld
        lea     si,CommandLine

Next:   lodsb                                   ; Skip trailing spaces
        cmp     al,SPACE
        je      Next

        cmp     al,EOL                          ; Is it end of line?

        je      @@1

@5:     cmp     al,'/'

        jne     @@6

Tmp:    lodsb
        or      al,020h                         ; De-Capitalize
        cmp     al,'o'
        jne     SwError

        lodsb
        or      al,020h
        cmp     al,'n'
        je      SwitchOn
        cmp     al,'f'
        jnz     SwError

SwitchOff:

        call    CheckInst
        jcxz    @Off                            ; CXZ = Installed

        jmp     InstError

@@1:    jmp     @1                              ; Relative Jump fix
@@6:    jmp     short @6

@Off:   lodsb                                   ; the 'f' of the /Off
        mov     byte ptr es:[Active],000h       ; ES = PSP of Resident part

        call    Print
        db      'Saver turned off.$'

SwitchOn:

        call    CheckInst
        jcxz    @On                             ; CXZ = Installed

        jmp     short InstError

@On:    mov     byte ptr es:[Active],001h

        call    Print
        db      'Saver turned on.$'

SwError:
        call    Print
        db      'Error: Invalid switch. Use /ON or /OFF only.$'

@6:     cmp     al,'1'
        jb      MinErr
        cmp     al,'9'
        ja      MinErr

; No error detected. Translate MINUTES into ticks

        sub     al,'0'                          ; Translate into a number
        cbw                                     ; Xor AH
        shl     ax,1
        mov     bx,offset TimeTable
        add     bx,ax
        mov     ax,word ptr [bx]
        mov     word ptr [Delay],ax             ; And re-set delay

        jmp     @1

InstError:

        call    Print
        db      'Error: Not installed.$'

MinErr: call    Print
        db      'Error: Invalid time parameter. Use 1-9 only.$'

; Remember - CheckInst jumps here if already installed, therefore ES:BX is
; the vector to our INT 9, therefore ES is the segment of our resident code.

ResErr: cmp     byte ptr es:[Active],001h       ; Are we ON?
        jne     @State

        mov     word ptr cs:[ResStat],')n'
        mov     byte ptr cs:[ResStat+2],' '

@State: call    Print
        db      'Error: Already resident. (O'
ResStat db      'ff)$'

; Assume the defaults

@1:

        call    CheckInst
        jcxz    ResErr                          ; CXZ - Resident

; We're not installed, continue

        mov     ax,03509h                       ; Get 009h vector
        int     21h

        mov     word ptr [Old09],bx
        mov     word ptr [Old09+00002h],es

        mov     ax,0351Ch                       ; Get 01Ch vector
        int     21h

        mov     word ptr [Old1C],bx
        mov     word ptr [Old1C+00002h],es

        mov     ax,0352Fh
        int     21h

        mov     word ptr [Old2F],bx
        mov     word ptr [Old2F+00002h],es

        mov     ax,02509h                       ; Set new 09h
        mov     dx,offset New09
        int     21h

        mov     ax,0251Ch                       ; Set new 1Ch
        mov     dx,offset New1C
        int     21h

        mov     ax,0252Fh
        mov     dx,offset New2F
        int     21h

        mov     byte ptr cs:[Active],001h          ; Set Active flag to ON

        mov     dx,offset Begin
        int     27h                             ; Stay resident

; Data Space

; We put the variable here, so it doesn't get included in the part of the
; program that stays resident

msgcw   db      'Saver - Written by Inbar Raz, released to the public domain.'
        db      00Dh, 00Ah, 00Dh, 00Ah, 024h

NewSeg  dw      ?

TimeTable       EQU     $ - 2                   ; To cover for ax=1 (bx+2)

        dw      (1*60) * 182 / 10
        dw      (2*60) * 182 / 10
        dw      (3*60) * 182 / 10
        dw      (4*60) * 182 / 10
        dw      (5*60) * 182 / 10
        dw      (6*60) * 182 / 10
        dw      (7*60) * 182 / 10
        dw      (8*60) * 182 / 10
        dw      (9*60) * 182 / 10

; ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
; Installation check routine

; Check wether we are already installed
; ES:BX points to the vector of Interrupt 9.
; On return, if CX=0, program already installed

CheckInst:

        mov     ax,'SA'
        mov     bx,'VE'
        mov     cx,'R?'
        int     2Fh

        xor     cx,cx                           ; Already installed

        cmp     ax,'IR'
        je      DoneChk

        inc     cx                              ; Not installed

DoneChk:

        ret

; ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
; Common error routine.
; Call with the error string immediately following the call instruction.
; That way all we have to do is POP the string's address into DX!

Print:  push    cs
        pop     ds

        pop     dx                      ; String is just beyond call
        mov     ah,009h                 ; Display string
        int     21h

        mov     ah,04Ch                 ; Terminate process
        int     21h

        end     Start
