; This is a simple (and messy but that can be cleaned up later)
; Protected mode Source code for a PCX file selction and viewer program
; Place this program (the exe) in a directory with .pcx's
; Thanx to TRAN for releasing his Protected mode Handler
; This is using TRAN's "pm21232.zip" protected mode blah blah...

display "PCX Decoder Coded by Martial Artist"

        ideal
        p386n
        jumps

segment code16 para public use16
        assume cs:code16, ds:code16

;------------------------------------------------------------------------------

; 16-bit code goes here
ends    code16

segment code32 para public use32
        assume cs:code32, ds:code32

include 'pmodei.inc'    ; Ideal mode Include file
include 'file.inc'      ; include file stuff..
public  _main

;------------------------------------------------------------------------------
; Code starts here

_main:
        sti

        mov  [v86r_ax],3h
        mov  al,10h
        int  33h           ; reset to video mode 3 (easy clear screen)

        call getfilename      ;procedure to get file names from disk
        call showfiles        ;procedure to show files on screen
        call selectfiles      ;procedure to let user select file

        lea  edx,[filename]  ; open the pcx file chosen
        call _openfile       ;
        jc   @stop           ;
        mov  ax,[v86r_ax]    ;
        mov  [handle],ax     ; save the file handle

        mov  [v86r_bx],ax        ; read in the whole file
        lea  edx,[manufacturer]  ; starting at manufacturer in memory
        mov  ecx,65535-128-786   ; (assuming that the file size is not greater
        call _readfile           ; than 65536 bytes long)
        jc   @stop               ;

        call checkifpcx        ;procedure to check the pcx header
        call dobitsperpixel    ;procedure to figure out bits per pixel
        call readpalette       ;procedure to read in palette

        mov  ax,[handle]     ;
        mov  [v86r_bx],ax    ;
        call _closefile      ;close the pcx file

        mov  [v86r_ax],13h     ;
        mov  al,10h            ;
        int  33h               ;change screen to mode 13h

        call setpalette      ;procedure to convert and set palette


        call unpackbits      ;decode the pcx onto the screen

        call _waitforkey    ; wait for a key to be pressed

        mov  [v86r_ax],3   ;
        mov  al,10h        ;
        int  33h           ;go back to mode 3 (text)

@stop:


        lea  edx,[sig]      ;
        add  edx,[_code32a] ;
        mov  ax,dx          ;
        shr  edx,4          ;
        and  ax,0fh         ;
        mov  [v86r_dx],ax   ;
        mov  [v86r_ds],dx   ;
        mov  [v86r_ah],9    ;
        mov  al,21h         ;
        int  33h            ;


        jmp _exit          ; duuhh!

;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
; Procedure UNPACKBITS
; This procedure takes the pcx packed picture starting at relative memory
; REST and unpacks it to the screen (0a0000h)
;------------------------------------------------------------------------------

proc    unpackbits
        @rlp edi,0a0000h          ; point EDI to the screen
        lea  esi,[rest]           ; point ESI to REST


        mov  ax,[bytes_per_line]
        mov  [image_wide],ax      ; save number of bytes per line

        sub  ax,ax                ; clear ax

        mov  al,[colour_planes]   ;
        mov  [image_planes],ax    ; save the number of planes

        mov  ax,[ymax]            ;
        mov  [image_deep],ax      ; save the image depth

        sub  bx,bx                ; clear bx

        mul  [image_planes]       ;
        mov  cx,ax                ; get the number of lines to unpack

@@10:   push cx                   ; save the number of lines to unpack
        mov  ax,[image_wide]       ;
        mov  [image_count],ax      ; reset the image width
        inc  bx                   ; increment the running line counter
        
@@20:   lodsb                     ; get a byte to unpack

        mov  ah,al                ;
        and  ah,0c0h              ;
        cmp  ah,0c0h              ; check if it's a pattern call
        jne  @@30                 ; no, then go and print it out

        and  al,3fh               ; yes,
        sub  cx,cx                ; get the pattern count
        mov  cl,al                ; into cx
        sub  [image_count],cx     ; subtract it from the width counter

        lodsb                     ; get the pattern byte

        cmp  bx,[image_planes]    ; check the line counter against the plane...
        jl   @@40                 ; counter.

        cld
        repne stosb               ; write the pattern

        jmp  @@40                 ; go check if we are done the line yet

@@30:   mov  cx,1                 ; not a pattern call soo...
        sub  [image_count],cx     ; decrement our counter
        cmp  bx,[image_planes]    ;
        jl   @@40                 ; go check if we are done the line yet

        cld                       ;
        repne stosb               ; write the byte to the screen

@@40:   cmp  [image_count],0      ; are we done yet?
        jg   @@20                 ; no go back up to get another byte

        cmp  bx,[image_planes]    ; make sure the line counter doesnt wrap
        jl   @@50
        mov  bx,0

@@50:   pop  cx                   ; loop until all lines are unpacked
        loop @@10

        ret
endp    unpackbits

;------------------------------------------------------------------------------
; Procedure SETPALETTE
; This procedure takes the pcx palette and converts it from 8 bits to 6 bits
; then goes straight to the DAC registers to engage it
; Uses palette at memory PALETTE
;------------------------------------------------------------------------------

proc    setpalette
        lea  esi,[palette]    ; point ESI and EDI to the palette
        mov  edi,esi          ;
        mov  cx,768           ; 3*256(colors) is 768 bytes
@@10:
        lodsb                 ; convert the palette from
        shr  al,1             ; 8 bits
        shr  al,1             ; to 6 bits
        stosb                 ; and save it
        loop @@10             ; cauze the VGA only uses 6 bits for 256 colors

        mov  dx,03c8h         ;
        mov  al,0             ; Tell the vga we are going to start writing
        out  dx,al            ; to palette register 0
        inc  dx               ;
        lea  esi,[palette]    ;
        mov  cx,768           ;
        rep  outsb            ; Tell the DACS what the palette is

        ret
endp    setpalette

;------------------------------------------------------------------------------
; Procedure READPALETTE
; This procedure goes to the end of the pcx file, then goes back 769 bytes.
; Checks if we are at the palette (cuz pcx's have the palette at the end of file)
; and then reads it into memory at PALETTE
;------------------------------------------------------------------------------

proc    readpalette
        mov  ax,[handle]     ;
        mov  [v86r_bx],ax    ;
        mov  bl,2            ; Move to the end of the pcx file
        mov  eax,0           ;
        call _lseekfile      ; (to find out how big it is)
        jc   notpall

        sub  eax,769         ; subtract 769 bytes from the end of the pcx
        mov  bl,0            ; to figure out where the palette starts
        call _lseekfile      ; in the file
        jc   notpall

        lea  edx,[temp]      ; Read in the pcx palette sig byte
        mov  ecx,1           ;
        call _readfile       ;
        jc   notpall

        cmp  [temp],0ch      ; check if we are at the palette
        jne  notpall         ; (pcx uses 0ch to tell you the palette starts)

        mov  ecx,768         ;
        lea  edx,[palette]   ;
        mov  bl,1            ; Read in the palette
        call _readfile       ;

        ret
notpall:
        mov  [error],3       ; there was an error.. report it and end
        call errors          ;
endp    readpalette

;------------------------------------------------------------------------------
; Procedure DOBITSPERPIXEL
; figures out how many bits per pixel the PCX is
; also checks if the number of bits per pixel is 8
; cauze we are only doing a 256 color viewer here
;------------------------------------------------------------------------------

proc    dobitsperpixel
        xor  eax,eax              ; zero eax
        mov  al,[bits_per_pixel]
        cmp  al,1
        jne  @@10

        mov  al,[colour_planes]
        mov  [bits],ax
        jmp  @@20
@@10:
        mov  [bits],ax             ; make sure it is 8 bits per pixel
        cmp  ax,8                  ;
        je   @@20                  ;

        mov  [error],2
        call errors
@@20:
        ret
endp    dobitsperpixel

;------------------------------------------------------------------------------
; Procedure CHECKIFPCX
; Checks the header of the pcx to make sure that it IS a pcx
;------------------------------------------------------------------------------

proc    checkifpcx
        cmp  [manufacturer],0ah   ; check the manufacturer byte
        jne  notpcx               ;

        cmp  [version],5          ; check the version
        jne  notpcx               ;

        ret

notpcx: mov  [error],1
        call errors
endp    checkifpcx

;------------------------------------------------------------------------------
; Procedure ERRORS
; This procedure displays an error
; at the moment it is not conditional, but can be made to display
; certain messages with certain errors, depending on the [error] flag
;------------------------------------------------------------------------------

proc    errors
        lea  edx,[error1]     ; make edx point to the error message
        add  edx,[_code32a]    ; convert the edx address so that the v86 int
        mov  ax,dx             ; can determine the segment and offset
        shr  edx,4             ;
        and  ax,0fh            ;
        mov  [v86r_dx],ax     ;
        mov  [v86r_ds],dx     ;
        mov  [v86r_ah],9      ;
        mov  al,21h           ;
        int  33h              ; print message using dos 21 function 9

        jmp  _exit            ; end the program
endp    errors

;------------------------------------------------------------------------------
; Procedure SELECTFILES
; This procedure lets the user cursor up and down to select a pcx file
; on the screen, then places the file in memory location FILENAME when done
;------------------------------------------------------------------------------

proc    selectfiles
        mov  bp,0                 ; use bp to hold last cursor location
        mov  si,0                 ; use si to hold current cursor location
@sel4:
        call  putcurs            ; place the cursor on the screen

        mov  [v86r_ah],0          ;
        mov  al,16h               ;
        int  33h                  ; get a key

        cmp  [v86r_ax],4800h     ; is the key the up arrow?
        je   @sel2               ; yes goto @sel2

        cmp  [v86r_ax],5000h      ; is the key the down arrow?
        je   @sel1                ; yes goto @sel1

        cmp  [v86r_al],0dh       ; is the key a carriage return
        je   @sel3               ; yes, got @sel3

        jmp  @sel4                ; illegal key, get another

@sel1:  cmp  si,[numfiles]       ; cursor down. are we going down further than
        je   @sel4               ; the number of files? yes, then no down
        mov  bp,si                ; no, save our cursor position
        inc  si                   ; increment the cursor down
        jmp  @sel4                ; go back to get another key

@sel2:  cmp  si,0                ; cursor up. are we going past the top of the
        je   @sel4               ; screen? yes, then no up
        mov  bp,si                ; no then save the cursor position
        dec  si                   ; decrement the cursor position
        jmp  @sel4                ; go back to get another key

@sel3:  lea  edi,[filename]      ; We are done.. so point EDI to FILENAME
        xor  eax,eax              ; zero eax
        mov  ax,si               ; Remember SI held the Y coord of the cursor
        lea  esi,[files]          ; Point ESI to FILES
        mov  bx,14               ; File number multiplied by 14 chars
        mul  bx                  ; cauze each filename buffer is 14 chars
        add  esi,eax              ; point ESI to the filename in our file buffer
        mov  cx,13                ;
        rep  movsb                ; and move the file name to FILENAME

        ret
endp    selectfiles


;------------------------------------------------------------------------------
; Procedure PUTCURS  (for the selectfiles procedure)
; places a 'cursor' on the screen give by the y coordinate in SI
; erases old cursor, pointed to by BP
;------------------------------------------------------------------------------

proc    putcurs
        xor  eax,eax               ; kill eax
        mov  ax,bp                  ; get the old cursor Y position
        mov  bx,160                ; multiply the Y by 160 (cuz 2 bytes per char
        mul  bx                    ;                        remember attributes)
        @rlp edi,0b8000h           ; point EDI to screen memory
        add  edi,eax               ; add our calculated y offset to EDI
        mov  [byte ptr edi],32    ; place a SPACE at the old cursor position

        xor  eax,eax               ; same as above, but with new Y position
        mov  ax,si                 ; pointed to in SI and place character
        mov  bx,160                ; 16 in that location
        mul  bx                    ;
        @rlp edi,0b8000h           ;
        add  edi,eax               ;
        mov  [byte ptr edi],16     ;
        ret
endp    putcurs

;------------------------------------------------------------------------------
; Procedure SHOWFILES
; This procedure takes the files from disk (that were given in FILES)
; and places them onto the screen
;------------------------------------------------------------------------------

proc    showfiles
        lea  esi,[files]    ; point ESI to FILES buffer
@@40:
        lodsb               ; get the first char of the filename
        cmp  al,0ffh         ; is it an FF? (end of files)
        je   @@10            ; yes, goto @@10
        cmp  al,0           ; is the character a 0 (end of filename)
        je   @@30           ; yes, goto @@30
        jmp  @@40
@@30:
        mov  edi,esi        ; end of file name..
        mov  al,'$'         ; store a $ for use with int 21 function 9
        stosb               ;
        jmp  @@40           ; keep doing this till we get an ff
@@10:
        lea  esi,[files]     ; point esi to files
@@20:
        mov  [v86r_ah],2    ;
        mov  [v86r_dl],32   ;
        mov  al,21h         ;
        int  33h            ; output a space char on screen

        mov  edx,esi        ;
        add  edx,[_code32a] ;
        mov  ax,dx          ;
        shr  edx,4          ;
        and  ax,0fh         ;
        mov  [v86r_dx],ax   ;
        mov  [v86r_ds],dx   ;
        mov  [v86r_ah],9    ;
        mov  al,21h         ;
        int  33h            ; use int 21 function 9 to display file

        mov  [v86r_ah],2    ;
        mov  [v86r_dl],0ah  ;
        mov  al,21h         ;
        int  33h            ; output a line feed to the screen
        mov  [v86r_ah],2    ;
        mov  [v86r_dl],0dh  ;
        mov  al,21h         ;
        int  33h            ; output a carriage return to the screen

        add  esi,14         ; go to next file name location
        lodsb                ; get the first character
        dec  esi            ; point to that same character again
        inc  [numfiles]      ; increment the number of files counter
        cmp  al,0ffh        ; is the character the end of files marker?
        jne  @@20           ; no, print the file out
        dec  [numfiles]     ; yes, then end
        ret
endp    showfiles

;------------------------------------------------------------------------------
; Procedure GETFILENAME
; This procedure uses the file mask *.pcx and gets all the files in the
; current directory which comply with the mask, then saves them into the
; buffer FILES.
; It looks like a useless byte at the end (where it stores a $ at position 14
; of everyfile, but this is used in case the file is actually 13 chars long.)
;------------------------------------------------------------------------------

proc    getfilename

        lea  edi,[files]        ; point EDI to FILES buffer
        mov  esi,[_pspa]        ; get the PSP address
        add  esi,09eh            ; make ESI point to PSP + 09eh bytes cuz
        mov  [psp],esi           ; that is where the find file function places
                                 ; the file found.

        lea  edx,[filemask]     ; Point EDX to the FILEMASK (*.pcx)
        add  edx,[_code32a]      ; convert edx to the virtual DS:DX
        mov  ax,dx               ; pointer that the dos function can associate
        shr  edx,4               ; with
        and  ax,0fh              ;
        mov  [v86r_dx],ax        ;
        mov  [v86r_ds],dx        ;
        mov  [v86r_ax],4e00h     ;
        mov  [v86r_cx],0         ;
        mov  al,21h              ;
        int  33h                 ; Call find first file function (21h,4eh)

        mov  ecx,13              ; 13 chars in a filename
@@1:
        mov  al,[gs:esi]         ; get first byte of filename
        inc  esi                  ; (gotta do it this way because the psp is
        stosb                     ; lower than the code start address)
        loop @@1                 ; store the filename in the FILES buffer

        mov  al,'$'               ; place a $ at position 14 after the file
        stosb                     ; in case it is 13 bytes long..
@getit:
        mov  esi,[psp]           ;
        mov  [v86r_ax],4f00h     ;
        mov  al,21h              ;
        int  33h                 ; call get next file function (21h,4fh)
        jc   @getdon             ; are we done getting files?

        mov  ecx,13              ; no, 13 chars in a filename
@@2:
        mov  al,[gs:esi]         ; get and store the filename in the FILES buff
        inc  esi                 ;
        stosb                    ;
        loop @@2                 ;
        mov  al,'$'              ;
        stosb                    ;
        jmp  @getit              ;

@getdon:
        mov  al,0ffh             ; Place a 0ffh at the end of the buffer
        stosb                    ; so we can figure out later on that the
        ret                      ; number of files is done
endp    getfilename



;°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
; Just wait for a keypress (and nullify it)
;°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
_waitforkey:
        push ax
waitforkeyl0:
        mov ax,[gs:41ah]
        cmp ax,[gs:41ch]
        je waitforkeyl0
        mov [gs:41ch],ax
        pop ax
        ret


;------------------------------------------------------------------------------
; DATA starts here.
;------------------------------------------------------------------------------

filename       db 14 dup(0)   ; buffer to hold the pcx filename
handle         dw 0           ; buffer to hold the file handle
psp            dd 0           ; buffer to hold the psp + 9eh
filemask       db '*.pcx',0   ; file mask for find file function
numfiles       dw 0           ; number of pcx's found in the directory

;------------------------------------------------------------------------------

;pcx header
manufacturer   db 0              ; manufacturer byte (always 0A0h)
version        db 0              ; pcx Version
encoding       db 0              ; (always 1)
bits_per_pixel db 0              ; color bits per pixel
xmin           dw 0              ; image origin x
ymin           dw 0              ; image origin y
xmax           dw 0              ; image end x
ymax           dw 0              ; image end y
hres           dw 0              ; horizontal resolution
vres           dw 0              ; vertical resolution
palette1       db 48 dup(0)      ; (color palette, for older not 256 col vers.)
reserved       db 0              ; This is the winning lottery number in BCD
colour_planes  db 0              ; number of color planes
bytes_per_line dw 0              ; line buffer size
palette_type   dw 0              ; grey or color palette indicator
filler         db 58 dup(0)      ; These bytes hold the address
                                 ; to solomon rushdie


rest           db 65000 dup(0)   ; a pre-defined buffer to hold the rest of
                                 ; the pcx file (the picture)
                                 ; could have just allocated memory
                                 ; at the end of the program and used that
                                 ; cauz we're in protected mode.. but for
                                 ; some reason I always liked doing this
                                 ; (just lazy I guess) so it adds 65000
                                 ; bytes to your file.. you use a compactor
                                 ; like pklite or pp dont you?

palette        db 768 dup(0)     ; this will hold the palette (256*3 bytes each)

temp           db 0              ; just a temporary buffer

error          db 0              ; error flag

error1         db 0dh,0ah,'Not a PCX file',0dh,0ah,'$' ; error message

; variables used by the unpackbits procedure

width1         dw 0              ; width of the pic
depth1         dw 0              ; height of the pic
bytes          dw 0              ;
bits           dw 0              ;

image_wide     dw 0              ;  duuhhhh
image_planes   dw 0              ;         hh
image_deep     dw 0              ;           h
image_count    dw 0              ;            h

files          db 1000h dup(0)   ; buffer to hold files
                                 ; change this to accomodate your needs
                                 ; 1000h should be enuff (1000h/14)=292 pcx's
sig db 'Small PCX viewer by Martial Artist',0dh,0ah,'$'
ends    code32
        end

; that's it
