;
;       RBCONIO.ASM
;
;       Turbo C++ 1.0 conio.h routines for the Rainbow.
;       1992-03-06 by M. Warner Losh
;       Donated to the Public Domain.  No claim of Copyright made by the
;       author.
;
;
;       gotoxy is derived, in part, from the EWF.ASM program.
;
;       The call_c macro is pure sleeze, or a work of art, depending on
;       who you ask.  I'd love to know how other people deal with calling
;       functions written in 'C' or with a 'C' interface from MASM.
;
;       WARING:  Yes, you don't get off quite that easily.  These routines are
;       the bare essentials that I needed to get moria working on my Rainbow
;       at reasonable speeds.  I pay, at best, lip service to the window()
;       call.  There are a couple of hooks in the code to deal with windows,
;       but they are just hooks, with no flesh hanging from them.  There is
;       no translation of 8 bit characters from the IBM world to the Rainbow
;       world, since I'm basically lazy and the only thing it effects is the
;       map the entire level function.  Since that is real high on the list
;       of command that I rarely use, I haven't put the effort into making it
;       work.
;
;       The gettext() and puttext() calls are probably not compatable with
;       thier PC conterparts in terms of the data stored in the buffers.
;       However I could find no good definition of what they should contain,
;       so I'm not too worried.
;

       .MODEL  LARGE,C
       .CODE

;
;       push_args       A helper macro for the call_c and call_pascal
;                       functions.
;
push_args       macro args
       irp     x,<&args>               ;; Foreach of the args that were passed.
       push    x                       ;; Push the arg.
       endm
       endm

;
;       call_c          A macro that makes it painless to call 'C' functions
;                       with parameters from assembler.
;
call_c  MACRO   who,args
       nargs   =       0                       ;; We have no args to start with
       IFNB    <&args>                         ;; If we have args
               IRP     x,<&args>               ;; For each of the args that we have
               IF      NARGS EQ 0              ;; For the first one we set newargs
                       newargs equ     <&x>    ;;   equal to the arg
               ELSE                            ;; otherwise we prepend this arg to
                       newargs catstr <&x,>,newargs ;; the string we're building
               ENDIF
               nargs   =       nargs + 1       ;; bump total count
               ENDM

               push_args       %newargs        ;; push the args.
       ENDIF

       call    &who                            ;; make the call
       IFNB    <&args>                         ;; If we have args,
               ADD     SP,nargs*2              ;;    fix the stack
       ENDIF                                   ;; That's all folks...
       ENDM

RB_ATTR_OFFSET  EQU     1000H           ; offset of char and attributes
RB_LINE_TABLE   EQU     0EF4H           ; Table of line pointers
RB_CUR_COLUMN   EQU     0F41H           ; offset of cursor column pos
RB_CUR_LINE     EQU     0F42H           ; offset of cursor line pos
RB_MAX_ROW      EQU     24              ; Biggest row
RB_MAX_COLUMN   EQU     80              ; Biggest column
RB_ROM          EQU     18H             ; Interrupt to use.
RB_PC_MAX_ROW   EQU     25              ; Biggest row
RB_VIDEO_SEGMENT EQU    0EE00H          ; Video offset.

_rb_cursor_off  PROC    Near
       mov     DI, 08H                 ; Cursor off function
       INT     RB_ROM
       ret
_rb_cursor_off  ENDP

_rb_cursor_on   PROC    Near
       mov     DI, 0AH                 ; Cursor on function
       INT     RB_ROM
       ret
_rb_cursor_on   ENDP

ABS_VALIDATE_COLUMN     MACRO   col, lbl
       LOCAL   lab1,lab2
       mov     ax, col                 ; get the column
       or      ax,ax                   ; is it 0?
       jnz     lab1                    ; yes -> bale
       mov     ax,1
       jmp     lbl                     ; exit
lab1:
       cmp     ax, RB_MAX_COLUMN       ; validate
       jbe     lab2                    ; too big, bail
       mov     ax,1
       jmp     lbl                     ; exit
lab2:
       ENDM

ABS_VALIDATE_ROW        MACRO   row, lbl
       LOCAL   lab1,lab2,lab3
       mov     ax, row                 ; get the column
       or      ax,ax                   ; is it 0?
       jnz     lab1                    ; yes -> bale
       mov     ax,1
       jmp     lbl                     ; exit
lab1:
       cmp     ax, RB_MAX_ROW          ; validate
       jbe     lab2                    ; too big, bail
       cmp     ax, RB_PC_MAX_ROW       ; Special case for PC's 25th line
       je      lab3
       mov     ax,1
       jmp     lbl                     ; exit
lab3:
       dec     ax                      ; 25->24
       mov     row, ax                 ; save it
lab2:
       ENDM

XLATE_COORDS    MACRO   col, row, lbl
       ABS_VALIDATE_ROW        row, lbl
       ABS_VALIDATE_COLUMN     col, lbl
       ENDM
;
;       void absgotoxy(int x,int y)
;               Move the cursor to x, y (x == column, y == row)
;
gotoxy  PROC    C       uses ES DS SI DI, locx:word, locy:word

       XLATE_COORDS    locx, locy, punt

       call    _rb_cursor_off          ; Turn off the cursor.
       mov     ax, RB_VIDEO_SEGMENT
       mov     ds, ax                  ; DS: points at the video segment.

       mov     ax, locy                ; Row
       mov     DS:BYTE PTR RB_CUR_LINE, AL ; set the row

       mov     ax, locx                ; Column into AX
       mov     DS:BYTE PTR RB_CUR_COLUMN, AL ; set the column

       call    _rb_cursor_on           ; Turn off the cursor.
punt:
       ret
gotoxy  ENDP

;
;       int putch(int c)
;               Puts c to console.  Returns c.
;
putch   PROC    C USES es ds si di, c:word
       mov     ax,c            ; Character to put
       xor     di,di           ; 0 == PUTCH
       int     RB_ROM          ; do it
       mov     ax,c            ; compatability return value
       ret                     ; done
putch   ENDP

;
;       int cputs(char *str)
;
cputs   PROC    C USES es ds si di, str:dword
       LOCAL   retval:word

;       DB      0CCH            ; INT 3
       cld
       lds     si, str
cputs1:
       lodsb
       or      al,al
       jz      done
;       push    ax
;       call    putch
;       add     sp,2
       call_c  putch,<ax>
       mov     retval,ax
       jmp     cputs1
done:
       mov     ax,retval
       ret
cputs   ENDP

;
;       int getch()
;               gets a character from the colsole.
;
getch   PROC    C       USES es ds si di
getch1:
       mov     di,2            ; getch
       int     RB_ROM          ; do it
       or      cl,cl           ; CL == 0 when no char yet.
       jz      getch1          ; loop if cl == 0
       xor     ah,ah           ; fix return value
       ret                     ; done
getch   ENDP

;
;       int kbhit()
;               returns 0 if no char, else non-zero (0xff in our case)
;
kbhit   PROC    C       USES es ds si di
       mov     di,4            ; Console status
       int     RB_ROM          ; do it
       xor     ax,ax           ; Make sure ah has no trash in it
       mov     al,cl           ; cl has status
       ret
kbhit   ENDP

;
;       int gettext(int left, int top, int right, int bottom, void *bfr)
;               returns 0 if OK, 1 if not
;               The manual doesn't describe the format of bfr, so I'm
;               storing the bytes in a stripes.  That is I store each
;               row's chars, then its attributes.  Should make puttext
;               easier to write as well.
;
gettext PROC    C       uses es ds si di, left:word, top:word, right:word,\
                                       bottom:word, bfr:dword
       call    _rb_cursor_off
       mov     ax, RB_VIDEO_SEGMENT
       mov     ds, ax                  ; DS is video segment now

       ; make sure the coords are OK
       ABS_VALIDATE_COLUMN     left, done
       ABS_VALIDATE_ROW        top, done
       ABS_VALIDATE_COLUMN     right, done
       ABS_VALIDATE_ROW        bottom, done

       ; OK, they are good, or we wouldn't be here
       ; bx == current row number
       ; cx == number of columns to xfer
       mov     bx, top
       mov     ax,left                 ; Adjust left to make life
       dec     ax                      ;   easier later.
       mov     left,ax
       les     di, bfr                 ; where to store the data
       mov     dx, right               ; cx = right - left + 1
       sub     dx, left
       cld                             ; Make sure we go forward..
outer_loop:
       mov     si,bx                   ; do the indexing
       dec     si                      ; 0, not 1, relative
       shl     si,1                    ; words, not bytes
       mov     ax, RB_LINE_TABLE[si]   ; put the two together
       mov     si,ax
       add     si, left                ; offset
       mov     cx,dx                   ; Move in the count
       push    si                      ; Save the source
       rep     movsb                   ; Move this row's characters
       pop     si                      ; restore the source
       add     si, RB_ATTR_OFFSET      ; Make it point to the attributes
       mov     cx,dx                   ; move in the count
       rep     movsb                   ; Move the attributes in
       inc     bx                      ; bump the row number
       mov     ax, bottom              ; load in the limit
       cmp     bx,ax
       jbe     outer_loop              ; loop if we aren't there yet.

       xor     AX,AX                   ; Yup, life is good
done:
       call    _rb_cursor_on
       ret
gettext ENDP

;
;       void put_put_line(type, row, col, count, seg, chars, attr)
;               type    type of xfer. 0 both, 1 attr, 2 chars
;               row     row to put this into
;               col     col to put this into
;               count   number of chars to stuff
;               seg     segment of characters
;               chars   offset within seg of chars to use
;               attr    offset within seg of attributes to use
;
;               NB chars and attr must be relative to the same segment.
;
_rb_put_line    PROC    C       USES es ds di si, xfer_type:word, row:word,\
                       col:word, count:word, data_seg:word, chars:word,\
                       attr:word
       mov     ax,row
       mov     bl,al
       mov     ax,col
       mov     bh,al
       mov     cx,count
       mov     dx,attr                 ; Please read the manual.  It tells you
       mov     si,chars                ; that attr is in dx and chars in si.
       mov     ax,xfer_type
       mov     bp,data_seg             ; MUST BE DONE LAST, since bp appears
                                       ; in the above, you just can't see it
       mov     di, 14H                 ; Now then, don't we need this, lest
                                       ; the INT 18H become effectively a nop?
       INT     18H
       ret
_rb_put_line    ENDP

;
;       puttext(left, top, right, bottom, bfr)
;
puttext PROC    C       USES es ds di si, left:word, top:word, right:word,\
                                       bottom:word, bfr:dword
       LOCAL   wid,row
;
;               wid == width of the row
;               row == current row
;
       ; make sure the coords are OK
       call    _rb_cursor_off
       ABS_VALIDATE_COLUMN     left, done
       ABS_VALIDATE_ROW        top, done
       ABS_VALIDATE_COLUMN     right, done
       ABS_VALIDATE_ROW        bottom, done

       mov     ax, right               ; ax = right - left + 1
       sub     ax, left
       inc     ax
       mov     wid,ax                  ; save the width

       les     di,bfr                  ; es:di == buffer.

       mov     ax,top                  ; row = top
outer_loop:
       mov     row,ax                  ; Inside loop so end of loop stores OK.
       mov     si,di
       add     si,wid                  ; move poitner to next set of chars
       xor     ax,ax                   ; attributes and characters
;       push    si                      ; attributes
;       push    di                      ; chars
;       push    es                      ; segment
;       push    wid                     ; width
;       push    left                    ; col
;       push    row                     ; row
;       push    ax                      ; Push type == BOTH.
;       call    _rb_put_line            ; do it
;       add     sp,14                   ; clean that stack!
       call_c  _rb_put_line,<ax,row,left,wid,es,di,si>
       add     di,wid                  ; move poitner to attributes
       add     di,wid                  ; move poitner to attributes
       mov     ax,row
       inc     ax
       cmp     ax,bottom
       jbe     outer_loop              ; note this includes bottom.
done:
       call    _rb_cursor_on
       ret
puttext ENDP

clreol  PROC    C       uses ds
       .data
clreol_str      db      27,'[K',0
       .code
       mov     ax, @data
       mov     ds, ax
       mov     ax, offset byte ptr clreol_str
;       push    ds
;       push    ax
;       call    cputs
;       add     sp,4
       call_c  cputs,<ax,ds>           ; Ugg, I just found a hole in the
                                       ; macros I've written.  This really
                                       ; should be call_c cputs,<ds:ax>.
       ret
clreol  ENDP

window  PROC    C       uses ds, left:word, top:word, right:word, bottom:word
       .data
win_left        dw      1
win_top         dw      1
win_right       dw      80
win_bottom      dw      24
       .code
       mov     ax, @data
       mov     ds,ax

       ABS_VALIDATE_COLUMN     left, done
       ABS_VALIDATE_ROW        top, done
       ABS_VALIDATE_COLUMN     right, done
       ABS_VALIDATE_ROW        bottom, done

       mov     ax,left
       mov     win_left,ax
       mov     ax,top
       mov     win_top,ax
       mov     ax,right
       mov     win_right,ax
       mov     ax,bottom
       mov     win_bottom,ax
done:
       ret
window  ENDP

clrscr  PROC    C       Uses DS
       mov     ax, @data
       mov     ds, ax
;       push    win_top
;       push    win_left
;       call    gotoxy
;       add     sp,4
       call_c  gotoxy,<win_left,win_top>
       .data
clrscr_str      db      27,'[J',0
       .code
       mov     ax, offset byte ptr clrscr_str
;       push    ds
;       push    ax
;       call    cputs
;       add     sp,4
       call_c  cputs,<ax,ds>           ; see above.  should be <ds:ax>
       ret
clrscr  ENDP

wherex  PROC C uses ds
       mov     ax, RB_VIDEO_SEGMENT
       mov     ds, ax                  ; DS: points at the video segment.
       xor     ax, ax
       mov     al, DS:[RB_CUR_COLUMN]
       ret
wherex  ENDP
wherey  PROC C
       mov     ax, RB_VIDEO_SEGMENT
       mov     ds, ax                  ; DS: points at the video segment.
       xor     ax, ax
       mov     al, DS:[RB_CUR_LINE]
       ret
wherey  ENDP
       END