;; Error messages
ERR_OK          EQU     0
msgErr0         db      'Later!',13,10,'$'
ERR_MEM         EQU     1
msgErr1         db      'Error 001:  Out of memory?',13,10,'$'
ERR_CPU         EQU     2
msgErr2         db      'Error 002:  CPU must be at least an 80386.',13,10,'$'
ERR_FILE        EQU     3
msgErr3         db      'Error 003:  File error.',13,10,'$'
ERR_FILENOTFOUND EQU    4
msgErr4         db      'Error 004:  File not found.',13,10,'$'
msgtblError     dw      offset msgErr0, offset msgErr1, offset msgErr2,
                        offset msgErr3, offset msgErr4
nError          db      0

;; CPU name strings
CPUName86       DB      "8088/8086$"
CPUName286      DB      "80286DX/SX$"
CPUName386      DB      "80386DX/SX$"
CPUName486      DB      "80486DX/SX or better$"
CPUNameTable    DW      CPUName86,CPUName286,CPUName386,CPUName486

EVEN
msgCPUTypeIs    DB      "Your CPU type:  $"
EVEN
msgCPUTypeIsEnd DB      13,10,'$'
nCPU            DB      0

EVEN
msgPages        DB      'Pages displayed:  '
strNumPages     DB      6 dup (?),13,10,'$'

EVEN
bufText         DW      80*50 DUP (?)   ; Needs this much to hold
                                        ; a 50-line screen...
wCPos           DW      0
nDisplay        DB      0

EVEN
fnMap1          db      'DIAGONAL.MAP',0
fnTiles1        db      'DIAGONAL.TIL',0
fnPalette       db      'DIAGONAL.PAL',0        ; only one allowed, for now
fnMap2          db      'SCROLL.MAP',0
fnTiles2        db      'SCROLL.TIL',0

fntblMap        dw      offset fnMap1,offset fnMap2
fntblTiles      dw      offset fnTiles1,offset fnTiles2
nMap            dw      0

;; CPUType routine snatched from Ray Duncan's _Power Programming MASM_
;; chapter 14.  Reformatted to my style, but I left the code alone
;; except that it used to push a bunch of stuff, but doesn't any more
;; because I don't care what gets destroyed.
CPUType         PROC    near
                pushf                   ; now try to clear bits 12-15
                pop     ax              ; of CPU flags
                and     ax,0fffh
                push    ax              ; set modified CPU flags
                popf
                pushf
                pop     ax              ; get flags again
                and     ax,0f000h       ; if bits 12-15 are still
                cmp     ax,0f000h       ; set, this is 8086/88
                jne     cpu1            ; jump, not 8086/88
                mov     nCPU,CPU8086    ; set nCPU = 86/88 CPU type
                jmp     cpux            ; and exit

cpu1:           or      ax,0f000h       ; must be 286 or later,
                push    ax              ; now try to set bits 12-15
                popf                    ; of CPU flags
                pushf
                pop     ax              ; if bits 12-15 can't be
                and     ax,0f000h       ; set, this is a 286
                jnz     cpu2            ; jump, not 80286
                mov     nCPU,CPU80286   ; set nCPU = 286 CPU type
                jmp     cpux            ; and exit

cpu2:           mov     bx,sp           ; 386 or later, save SP
                and     sp,not 3        ; avoid stack alignment fault
                pushfd                  ; get value of EFLAGS
                pop     eax
                mov     ecx,eax         ; save copy of EFLAGS
                xor     eax,40000h      ; flip AC bit in EFLAGS
                push    eax             ; try and force EFLAGS
                popfd
                pushfd                  ; get back EFLAGS value
                pop     eax
                mov     sp,bx           ; restore old stack pointer
                xor     eax,ecx         ; can AC bit be changed?
                jnz     cpu3            ; no, jump, not a 386
                mov     nCPU,CPU80386   ; set nCPU = 386 CPU type
                jmp     cpux            ; and exit

cpu3:           mov     nCPU,CPU80486   ; set nCPU = 486 CPU type

cpux:           mov     bl,nCPU
                xor     bh,bh
                shl     bx,1
                DOSPRINT <offset msgCPUTypeIs>
                DOSPRINT CPUNameTable[bx]
                DOSPRINT <offset msgCPUTypeIsEnd>
                ret                     ; return with nCPU = CPU type
CPUType         ENDP

;; Initialize:  So far, all it does is make sure you have a 386 +
;; (because that's what I assembled the code for).
Initialize      PROC    near
        ; Set DS = CS in this program, since data is local
                mov     ax,cs
                mov     segCode,ax      ; Store the Code Segment
                mov     bx,ds
                mov     segPSP,bx       ; Store the PSP Segment
                mov     ds,ax           ; Set DS = CS

        ; Resize code to 64K
        CODE_SIZE EQU 64                ; <- this is arbitrary.
                ; ES already -> allocated segment
                mov     ah,4ah
                mov     bx,64*CODE_SIZE
                int     21h
                mov     nError,ERR_MEM
                jc      TerminateError

;; I've chosen not to implement sprites yet so that I can get this out
;; the door...
;; ; 320x200 buffer for sprite drawing.  To draw sprites, first draw them
;; ; into this buffer, adding rectangles to the current rectangle list.
;; ; Then, use BUFFER_COPY to put out the buffers with the current
;; ; rectangle list to the screen.  BUFFER_COPY will ensure minimal VGA
;; ; writing.
;;         ; Create a buffer segment
;;                 mov     bx,(320 * 200) / 16
;;                 mov     ah,48h
;;                 int     21h
;;                 mov     nError,ERR_MEM
;;                 jc      TerminateError
;;                 mov     segBuffer,ax

                call    CPUType
                mov     nError,ERR_CPU
                cmp     nCPU,2
                jl      TerminateError

                mov     ds,segCode
                mov     dx,offset fnPalette
                call    LoadPaletteFile
                jc      TerminateError

                call    LoadIndex
                jc      TerminateError

                KEYB_START

                call    Beginning       ; Can display an entry screen here

        ; This is linked in from Michael Abrash's zen timer code.
        ; (But I wrote the Click myself)
        call    Click
        call    ZTimerOn

                call    MainLoop

        call    ZTimerOff
        call    Click

                call    Ending          ; Can display an exit screen here

                KEYB_END

Terminate:      mov     nError,ERR_OK
TerminateError:
                mov     ax,cs   ;DOS functions require that DS point
                mov     ds,ax   ; to text to be displayed on the screen
                mov     bh,0
                mov     bl,nError
                shl     bx,1
                DOSPRINT msgtblError[bx]

                mov     ax,pages
                mov     ds,segCode
                mov     si,offset strNumPages
                call    Int2Ascii
                DOSPRINT <offset msgPages>

        call    ZTimerReport

                mov     al,nError
                mov     ah,4ch        ; DOS Terminate
                int     21h
                ; Don't need to RET!  We're outta here
Initialize      ENDP

;; Clicks the internal speaker.  I use this to indicate that page timing
;; has started.
Click           PROC
                in      al,61h
                mov     ah,al
                or      al,3
                out     61h,al

                mov     cx,5000         ; (this is an arbitrary delay!)
spkr_on:        loop    spkr_on
                mov     al,ah
                out     61h,al
                ret
Click           ENDP

;; Copied from an old 8088 "Learn Assembly" book and changed a bit
Int2Ascii       PROC
                mov     cx,6
                mov     byte ptr cs:[si],' '
                mov     byte ptr cs:[si+1],'0'
                mov     byte ptr cs:[si+2],'0'
                mov     byte ptr cs:[si+3],'0'
                mov     byte ptr cs:[si+4],'0'
                mov     byte ptr cs:[si+5],'0'
                add     si,6
                mov     cx,10
                or      ax,ax
                jns     clear_divide
                neg     ax
                mov     byte ptr cs:[si-6],'-'
clear_divide:   mov     dx,0
                div     cx
                add     dx,'0'
                dec     si
                mov     cs:[si],dl
                or      ax,ax
                jnz     clear_divide
                ret
Int2Ascii       ENDP

;; Given a filename at DS:DX, reads the file into memory and returns
;; a pointer to it as DX:0000.
;; Note that this routine obviously will only work correctly for
;; a file < 640k in size, but you can bring in files bigger than 64k.
;; This code comes from Future Crew's STMIK sampler "Mental Surgery"
;; and I commented it up to make it fit in with my stuff a little better.
;; Thank you, FC, for releasing that code!  Several of the routines
;; in this program were inspired or helped along by having that source...
;; Most recently, added in error codes.
EVEN
LoadFile        PROC    NEAR
                ;set: DX=offset to filename
                ;return: DX=segment of file

        ; Open the datafile at DS:DX.
                mov     ax,3D00h        ; 3D,00 -> Open file, read only
                                        ; DS:DX already points at filename
                int     21h             ;  returns AX=file handle
                mov     cl,ERR_FILENOTFOUND
                jc      ferror
                mov     bx,ax           ; Store file handle in BX
                mov     si,bx           ; and also in a variable

        ; Get the length of the file so we know how much to allocate
                mov     ax,4202h        ; 42,02 -> Seek, signed from end
                mov     cx,0            ; CX:DX is a long file offset,
                                        ; BX is already set as file handle
                mov     dx,0            ;  zero in this case = end of file
                int     21h             ; (returns long offset in DX:AX)
                mov     cl,ERR_FILE
                jc      ferror

;;;             shr     dx,1            ; This is original FC code,
;;;             rcr     ax,1            ; which I removed because the
;;;             shr     dx,1            ; 386 has a nice instruction
;;;             rcr     ax,1            ; to do this all!
;;;             shr     dx,1            ; But 286 users will want to
;;;             rcr     ax,1            ; return to this code, instead
;;;             shr     dx,1            ; of SHRD dx,ax,4
;;;             rcr     ax,1            ;

        ; Now turn that long DX:AX into a number of paragraphs to allocate
        ; for when we read the file.
                shrd    ax,dx,4         ; Divides long DX:AX by 4,
                mov     bx,ax           ;  and stores this in BX
                inc     bx      ; HHMMMM?  One more needed for small #'s
                mov     ah,48h          ; 48 -> Allocate memory
                                        ; BX already = # of paragraphs
                int     21h
                mov     cl,ERR_MEM
                jc      ferror
                mov     di,ax           ; store this in a variable

        ; Seek the file back to the beginning in order to read it into
        ; the memory we just allocated.
                mov     ax,4200h        ; 42,00 -> Seek, absolute offset
                mov     bx,si           ; BX is the file handle.
                mov     cx,0            ; CX:DX is a long offset
                mov     dx,0
                int     21h
                jc      ferror

        ; Now read the file into memory
                mov     ds,di           ; DS points at alloc'd memory
ReadBlock:      mov     ah,3fh          ; 3F -> Read file
                mov     cx,32768        ; read 32768 bytes at a time
                mov     dx,0            ; DS:DX points at beginning of
                int     21h             ;  this block of memory.
                mov     cl,ERR_FILE
                jc      ferror
                mov     dx,ds           ; Offset DS by (32768/16), which
                add     dx,800h         ;  is the number of paragraphs in
                mov     ds,dx           ;  each block of 32768 bytes.
                cmp     ax,32768        ; Did we actually read 32768 bytes?
                je      ReadBlock       ; If so, there's more to read...
                                        ; Otherwise, we've read all the
                                        ;  data in the file.

        ; So now, close the file handle.
                mov     ah,3Eh          ; 3E -> Close file
                                        ; BX still is the file handle
                int     21h

        ; Everything went ok.  Return the segment in DX.
                mov     dx,di
                mov     nError,ERR_OK
                ret
ferror:         mov     nError,cl
                ret
LoadFile        ENDP

;; Eventually, this should load in an index of all data files to
;; allow for filenames to be specified outside of the program.  The
;; goal is to make the program have no hardcoded filenames...
;; Of course, the structure of this index and its entries will be
;; hardcoded, as will the structures of all of the files it includes.
LoadIndex       PROC    near
                ret
LoadIndex       ENDP

;; Save the current video mode and cursor position with standard
;; BIOS calls
SaveVideo       PROC    near
                mov     ah,0Fh
                int     10h             ; Get current display Mode
                mov     nDisplay,al
                mov     ah,03h
                mov     bh,0
                int     10h
                mov     wCPos,dx

                mov     ds,segText
                mov     si,0
                mov     es,segCode
                mov     di,offset bufText
                mov     cx,80*50
            rep movsw
                ret
SaveVideo       ENDP

;; Restore the current video mode and cursor position with standard
;; BIOS calls
RestoreVideo    PROC    near
                mov     ah,00h
                mov     al,nDisplay
                int     10h             ; Get current display Mode
                mov     ah,02h
                mov     bh,0
                mov     dx,wCPos
                int     10h

                PAL_UPDATE      ; When flipping into text mode, re-do the
                                ; palette because the BIOS changes it.

                mov     es,segText
                mov     di,0
                mov     ds,segCode
                mov     si,offset bufText
                mov     cx,80*50
            rep movsw
                ret
RestoreVideo    ENDP
