Subscribe to 10 GOTO 10        RSS Feed
-----

QBasic Extension TSR1

Icon 2 Comments
Purpose Statement
This blog is about sharing with the world my "secret to success" about 14-15 years ago. My QBasic extension has been a closely guarded secret for years because I have never really gotten over my childhood ego. The TSR1 program was a major breakthrough for me as a young programmer, something I worked very hard at for a long time and I have always been reluctant to share this piece of hard work. Times have changed. I routinely work with Open Source software, I add snippets and the occasional tutorial here on DIC. My whole outlook on programming these days is very much in line with the Open Source initiative and yet I still guard the "secrets" of TSR1 as though anyone really cared. So this blog is about letting go and sharing my best kept secret of QBasic Programming.

Back story -- My first real programming breakthroughs were done using Microsoft QBasic which used to be routinely available on all PCs running DOS 5 or above. QBasic had a nice editor that had one irritating feature -- it was compatible with a mouse, BUT the language had no integration with the computer mouse. You could use a mouse while editing, but not within your programs and this double standard drove me crazy!!! Now this was long before the days of the internet (at least for me, there was compuserve and BBS but I didn't have access to either at the time) so I could not "Google" a solution. Without google I was forced to the library to look though computer books and read BYTE magazine. I found some Debug/HEX routines that allowed some basic mouse functionality and eventually found a reference on the mouse interrupt 33h.

For a long time I did what most programmers did -- I used the DOS debug utility to assemble short routines which I would save to disk and then use a HEX editor to extract the byte data as hexadecimal values which I would insert into my program (I think I even made a utility to do this for me). Eventually I got my first compiler, Turbo C++ 3.1 and it came with TASM! I was like a kid who get a birthday present and then plays with the batteries -- I quickly ditched C/C++ and decided to learn assembly. Why? Because it was going to help me extend QBasic and do all the things I really wanted to do!

TSR1 was my first (and actually only) major success -- but it was such a success!

TSR1 QBasic Extender V1.00 -- for the QBasic interpreter (not the QuickBasic compiler[1]).

Now TSR1 is not an example of super programming. It was written by me in when I was in high school in 1994 and I had been working in nearly complete vacuum -- I did not have other people to talk to about what I was doing, I did not have the internet etc. Just me, the public library and my trusty 286. The purpose of the program was to extend the capabilities of the QBasic interpreter to allow more advanced features that were readily available on the PC platform at the time. The program was a TSR that left a number of functions in memory. Most of the functions were just wrappers for standard interrupts (Mouse 33h/DOS 21h/BIOS video services 10h) -- so the TSR1 program was coupled with a library of QBasic functions (sort of a basic shell to start a TSR1 compatible program from)

The TSR1 Calling Convention:

TSR1 installed an ISR for interrupt 60h. This interrupt was called with:
....di:dx pointing to a function table that the ISR would fill with vectors to the TSR1 functions.
....si:bx pointing to a structure of register values to be used as arguments to the functions.


So the basic process of using TSR1 was:
  • Ensure that TSR1 was loaded by looking at the interrupt service table and then looking for TSR1's ID attached to int 60h
  • Establish a shared structure for the registers and an array for the table of functions.
  • Load and call a small assembly routine that would pass pointers to the register structure and function table to TSR1 though int 60h
  • Fill out the registers structure, and call the routines!
TSR1.ASM: written for TASM
	MODEL TINY ;;Ensure that this is a .com file

	PROG SEGMENT PARA PUBLIC
	ASSUME CS:PROG, DS:PROG, SS:PROG, ES:NOTHING

	ORG 2Ch
ENV_PTR LABEL WORD
	ORG 100H;;Entry point for .com programs

start: 
	JMP STARTCODE

ID_NAME LABEL BYTE
	DB 'TSR1 V1.00'
PRO_NAME LABEL BYTE	
	DB 'PROGRAM BY: NICKDMAX 1994',13,10,'$'
	
   ;; Wrapper for the mouse interrupt. 
MOUSE_CALL PROC FAR
	PUSH DS
	 PUSH ES
	  PUSH SI
	   PUSH DI
		PUSH BP
		PUSH CS
		POP DS
	   MOV SI,WORD PTR ARGMT
	   MOV AX,WORD PTR ARGMT+2
		PUSH SI
		MOV DS,AX
		MOV AX,[SI+12]
		MOV ES,AX
		MOV AX,[SI]
		MOV BX,[SI+2]
		MOV CX,[SI+4]
		MOV DX,[SI+6]
		MOV DI,[SI+10]
		MOV BP,[SI+16] 
		 PUSH DS
		  PUSH AX
		  MOV AX,[SI+14]
		  MOV SI,[SI+8]
		  MOV DS,AX
		  POP AX
		 INT 33H
		 POP DS
		POP SI
	   MOV [SI],AX
	   MOV [SI+2],BX
	   MOV [SI+4],CX
	   MOV [SI+6],DX
	   MOV [SI+12],ES
	   MOV [SI+16],BP
	   POP BP
	   POP DI
	  POP SI
	 POP ES
	POP DS
	RETF
MOUSE_CALL ENDP

;;Wrapper for the DOS interrupt 21h
DOS_CALL PROC FAR
	PUSH DS
	 PUSH ES
	  PUSH SI
	   PUSH DI
	   PUSH BP 
		PUSH CS
		POP DS
	   MOV SI,WORD PTR ARGMT
	   MOV AX,WORD PTR ARGMT+2
		PUSH SI
		MOV DS,AX
		MOV AX,[SI+12]
		MOV ES,AX
		MOV AX,[SI]
		MOV BX,[SI+2]
		MOV CX,[SI+4]
		MOV DX,[SI+6]
		MOV DI,[SI+10]
		MOV BP,[SI+16] 
		 PUSH DS
		  PUSH AX
		  MOV AX,[SI+14]
		  MOV SI,[SI+8]
		  MOV DS,AX
		  POP AX
		 INT 21H
		 POP DS
		POP SI
	   MOV [SI],AX
	   MOV [SI+2],BX
	   MOV [SI+4],CX
	   MOV [SI+6],DX
	   MOV [SI+12],ES
	   MOV [SI+16],BP 
	   POP BP
	   POP DI
	  POP SI
	 POP ES
	POP DS
	RETF
DOS_CALL ENDP

;;Wrapper for the BIOS interrupt 10h
DISPLAY_CALL PROC FAR
	PUSH DS
	 PUSH ES
	  PUSH SI
	   PUSH DI
	   PUSH BP 
		PUSH CS
		POP DS
	   MOV SI,WORD PTR ARGMT
	   MOV AX,WORD PTR ARGMT+2
		PUSH SI
		MOV DS,AX
		MOV AX,[SI+12]
		MOV ES,AX
		MOV AX,[SI]
		MOV BX,[SI+2]
		MOV CX,[SI+4]
		MOV DX,[SI+6]
		MOV DI,[SI+10]
		MOV BP,[SI+16] 
		 PUSH DS
		  PUSH AX
		  MOV AX,[SI+14]
		  MOV SI,[SI+8]
		  MOV DS,AX
		  POP AX
		 INT 10H
		 POP DS
		POP SI
	   MOV [SI],AX
	   MOV [SI+2],BX
	   MOV [SI+4],CX
	   MOV [SI+6],DX
	   MOV [SI+12],ES
	   MOV [SI+16],BP
	   POP BP
	   POP DI
	  POP SI
	 POP ES
	POP DS
	RETF
DISPLAY_CALL ENDP

;; block memory move -- have to love my spelling! Whew -- thank goodness for the advent of spell check!
MEMORIE_MOVE PROC FAR
	PUSH DS
	 PUSH ES
	  PUSH SI
	   PUSH DI
		PUSH CS
		POP DS
	   MOV SI,WORD PTR ARGMT
	   MOV AX,WORD PTR ARGMT+2
		PUSH SI
		MOV DS,AX
		MOV AX,[SI+12]
		MOV ES,AX
		MOV AX,[SI]
		MOV BX,[SI+2]
		MOV CX,[SI+4]
		MOV DX,[SI+6]
		MOV DI,[SI+10]
		 PUSH DS
		  PUSH AX
		  MOV AX,[SI+14]
		  MOV SI,[SI+8]
		  MOV DS,AX
		  POP AX
		 CLD
		 REP MOVSB
		 POP DS
		POP SI
	   MOV [SI],AX
	   MOV [SI+2],BX
	   MOV [SI+4],CX
	   MOV [SI+6],DX
	   MOV [SI+12],ES
	   POP DI
	  POP SI
	 POP ES
	POP DS
	RETF
MEMORIE_MOVE ENDP 

;;Initialize for XMS calls
XMS_INST PROC FAR
	PUSH DS
	 PUSH ES
	  PUSH SI
	   PUSH DI
		PUSH CS
		POP DS
	   MOV SI,WORD PTR ARGMT
	   MOV AX,WORD PTR ARGMT+2
		PUSH SI
		MOV DS,AX
		MOV AX,4300H 
		INT 2FH
		POP SI
	   MOV [SI],AX
	   POP DI
	  POP SI
	 POP ES
	POP DS
	RETF
XMS_INST ENDP

;;Wrapper to Call the XMS function
XMS_CALL PROC FAR
	PUSH DS
	 PUSH ES
	  PUSH SI
	   PUSH DI
		PUSH BP
		PUSH CS
		POP DS
	   MOV AX,4310H
	   INT 2FH
	   MOV WORD PTR XMS_CTRL,BX
	   MOV WORD PTR XMS_CTRL+2,ES
	   MOV SI,WORD PTR ARGMT
	   MOV AX,WORD PTR ARGMT+2
		PUSH SI
		MOV DS,AX
		MOV AX,[SI+12]
		MOV ES,AX
		MOV AX,[SI]
		MOV BX,[SI+2]
		MOV CX,[SI+4]
		MOV DX,[SI+6]
		MOV DI,[SI+10]
		MOV BP,[SI+16] 
		 PUSH DS
		  PUSH AX
		  MOV AX,[SI+14]
		  MOV SI,[SI+8]
		  MOV DS,AX
		  POP AX
		 CALL CS:[XMS_CTRL]
		 POP DS
		POP SI
	   MOV [SI],AX
	   MOV [SI+2],BX
	   MOV [SI+4],CX
	   MOV [SI+6],DX
	   MOV [SI+12],ES
	   MOV [SI+16],BP
	   POP BP
	   POP DI
	  POP SI
	 POP ES
	POP DS
	RETF
XMS_CALL ENDP

;; New interrupt 60h -- this interrupt will expects SI:ES to point to a table that will get filled with vectors to the various functions. 
NEWINT PROC FAR
	PUSH DS
	 PUSH CS
	 POP DS
	MOV WORD PTR ARGMT, SI	
	MOV WORD PTR ARGMT+2, BX
	MOV DS,DX
	MOV [DI+0],CS
	MOV [DI+2],OFFSET MOUSE_CALL
	MOV [DI+4],OFFSET DOS_CALL
	MOV [DI+6],OFFSET DISPLAY_CALL
	MOV [DI+8],OFFSET MEMORIE_MOVE
	MOV [DI+10],OFFSET XMS_INST
	MOV [DI+12],OFFSET XMS_CALL
	POP DS
	IRET
NEWINT ENDP	
   ;----------------------------------------------------------
   ;------ DATA ---- DATA ---- DATA ---- DATA ---- DATA ------
   ;----------------------------------------------------------
EVEN
ORIGINT LABEL DWORD
	DW ?	   ;OFFSET
	DW ?	   ;SEGMENT
ARGMT LABEL DWORD
	DW ?	   ;OFFSET
	DW ?	   ;SEGMENT
XMS_CTRL LABEL DWORD
	DW 0H
	DW 0H
SAVESIZE EQU (($-PROG)/16)+1
START_UP_MSG LABEL BYTE
	DB '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -',13,10
	DB '	TSR1 QBasic Extender V1.00	 By: NickDMax',13,10
	DB '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -',13,10,'$'
ERR_TSR1 LABEL BYTE
	DB '	ERROR: TSR1 Not Loaded!!!',13,10,'$'
BYE_TSR1 LABEL BYTE
	DB '	TSR1 Unloaded',13,10,'$'
TSR_OK LABEL BYTE
	DB '	TSR1 Loaded',13,10,'$'

STARTCODE:
	MOV AX,0900H
	MOV DX,OFFSET START_UP_MSG
	INT 21H
	
	MOV AX,3560H
	INT 21H
	MOV WORD PTR ORIGINT,BX
	MOV WORD PTR ORIGINT+2,ES
	
	MOV DI, OFFSET ID_NAME;ES:DI -> ID_NAME THAT PROG
	MOV SI,OFFSET ID_NAME ;DS:SI -> ID_NAME THIS PROG
	MOV CX,10
	CLD
	REPZ CMPSB
	JZ THE_EXIT1		   ;IF INSTALED
	
	MOV AX,2560H
	MOV DX,OFFSET NEWINT
	INT 21H
	MOV ES,ENV_PTR
	MOV AH,49H
	INT 21H
	MOV AX,0900H
	MOV DX,OFFSET TSR_OK
	INT 21H
	MOV AX,3100H
	MOV DX,SAVESIZE
	INT 21H

THE_EXIT1:
	PUSH DS
	 MOV DX,ES:WORD PTR ORIGINT
	 MOV AX,ES:WORD PTR ORIGINT+2
	 MOV DS,AX
	 MOV AX,2560H
	 INT 21H
	 POP DS
	MOV AX,4900H ;ES ->OLD PROGRAM
	INT 21H
	JNC TSR1_UNLOADED
	
	MOV AH,09H
	MOV DX,OFFSET ERR_TSR1
	INT 21H
	MOV AX,4C01H	
	INT 21H
	INT 20

TSR1_UNLOADED:
	MOV AH,09H
	MOV DX,OFFSET BYE_TSR1
	INT 21H
	MOV AX,4C01H	
	INT 21H
	INT 20
PROG ENDS
END start



TSR1SUBS.BAS: Basic shell for using TSR1
DECLARE FUNCTION bios.getmode% ()
DECLARE FUNCTION bios.setvesamode% (mode%)
DECLARE FUNCTION bios.testmode% (mode%)
DECLARE FUNCTION mouse.init% ()
DECLARE FUNCTION istsr1% ()
DECLARE SUB clregs ()
DECLARE FUNCTION xms.alloc% (handel%, kbytes%)
DECLARE FUNCTION xms.free% (handel%)
DECLARE FUNCTION xms.get% (handel%, xoffset&, leng&, buffer%())
DECLARE FUNCTION xms.init% ()
DECLARE FUNCTION xms.put% (handel%, xoffset&, leng&, buffer%())
TYPE register
ax AS INTEGER
bx AS INTEGER
cx AS INTEGER
dx AS INTEGER
si AS INTEGER
di AS INTEGER
es AS INTEGER
ds AS INTEGER
bp AS INTEGER
END TYPE
DEFINT A-Z
DIM SHARED regs AS register
DIM SHARED table(6) AS INTEGER








loadtable.asm:
DATA &h55, &h8B, &hEC, &h56, &h57, &h8B, &h6E
DATA &h06, &h8B, &h76, &h00, &h8B, &h5E, &h02
DATA &h8B, &h7E, &h04, &h8B, &h56, &h06, &hCD
DATA &h60, &h5F, &h5E, &h5D, &hCA, &h08, &h00

FUNCTION bios.getmode
clregs
regs.ax = &HF00
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
bios.getmode = regs.ax AND &HFF
END FUNCTION

SUB bios.getpos (row, col, page)
clregs
regs.ax = &H3 * 256
regs.bx = page * 256
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
col = (regs.dx AND 255) + 1
row = ((regs.dx AND 65280) / 256) + 1
END SUB

SUB bios.goto (row, col, page)
row = row - 1: col = col - 1
clregs
regs.ax = &H2 * 256
regs.bx = page * 256
regs.dx = 256 * row + col
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
END SUB

SUB bios.putchara (char, attrib, count, page)
clregs
regs.ax = &H9 * 256 + char
regs.bx = page * 256 + attrib
regs.cx = count
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
END SUB

SUB bios.scroll (dir, row1, col1, row2, col2, num, attrib)
row1 = row1 - 1: col1 = col1 - 1
row2 = row2 - 1: col2 = col2 - 1
clregs
IF dir = 0 THEN regs.ax = num + 256 * &H6
IF dir = 1 THEN regs.ax = num + 256 * &H7
regs.bx = 256 * attrib
regs.cx = 256 * row1 + col1
regs.dx = 256 * row2 + col2
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
END SUB

SUB bios.setmode (mode)
clregs
regs.ax = mode AND &HFF
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
END SUB

SUB bios.setpage (page)
clregs
regs.ax = page + 256 * &H5
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
END SUB

SUB bios.setpix (x, y, c)
clregs
regs.ax = &HC00 + c
regs.cx = x
regs.dx = y
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
END SUB

FUNCTION bios.setvesamode (mode)
clregs
regs.ax = &H4F02
regs.bx = mode
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
IF regs.ax = &H4F THEN bios.setvesamode = -1
END FUNCTION

FUNCTION bios.testmode (mode)
clregs
regs.ax = mode
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
regs.ax = 256 * &HF
DEF SEG = table(0)
CALL absolute(table(3))
DEF SEG
a = regs.ax AND &HFF
IF a = mode THEN bios.testmode = -1 ELSE bois.testmode = 0
END FUNCTION

SUB clregs
regs.ax = 0
regs.bx = 0
regs.cx = 0
regs.dx = 0
regs.si = 0
regs.di = 0
regs.es = 0
regs.ds = 0
END SUB

FUNCTION istsr1
pt = &H60 * 4
DEF SEG = 0
a1 = PEEK(pt) + 256 * PEEK(pt + 1)
a2 = PEEK(pt + 2) + 256 * PEEK(pt + 3)
DEF SEG
IF a1 + a2 = 0 THEN GOTO notsr1
DEF SEG = a2
ch$ = ""
FOR x = 259 TO 268
ch$ = ch$ + CHR$(PEEK(x))
NEXT x
IF ch$ <> "TSR1 V1.00" THEN GOTO notsr1
DEF SEG
istsr1 = -1
EXIT FUNCTION
notsr1:
DEF SEG
istsr1 = 0
END FUNCTION

SUB load.table
DIM temp(14) AS INTEGER
RESTORE loadtable.asm
pointer = VARPTR(temp(0))
DEF SEG = VARSEG(temp(0))
FOR x = 1 TO 28
READ k
POKE pointer + (x - 1), k
NEXT x
DEF SEG
a1 = VARSEG(table(0))
a2 = VARPTR(table(0))
a3 = VARSEG(regs)
a4 = VARPTR(regs)
pointer = VARPTR(temp(0))
DEF SEG = VARSEG(temp(0))
CALL absolute(a1, a2, a3, a4, pointer)
DEF SEG
END SUB

SUB loadblock (filename$, array())
pointer = VARPTR(array(0))
DEF SEG = VARSEG(array(0))
BLOAD filename$, pointer
DEF SEG
END SUB

SUB mouse.getbut (btin, btout, count, mxl, myl)
clregs
regs.ax = 5
regs.bx = bt
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
btout = regs.ax
count = regs.bx
mxl = regs.cx
myl = regs.dx
END SUB

SUB mouse.getbutr (btin, btout, count, mxl, myl)
clregs
regs.ax = 6
regs.bx = btin
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
btout = regs.ax
count = regs.bx
mxl = regs.cx
myl = regs.dx
END SUB

SUB mouse.getpos (mx, my, bt)
clregs
regs.ax = 3
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
mx = regs.cx: my = regs.dx: bt = regs.bx
END SUB

SUB mouse.hide
clregs
regs.ax = 2
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
END SUB

FUNCTION mouse.init
clregs
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
mouse.init = regs.ax
END FUNCTION

SUB mouse.setcusg (hsx, hsy, tseg, toff)
clregs
regs.ax = 9
regs.bx = hsx: regs.cx = hsy
regs.es = tseg: regs.dx = toff
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
END SUB

SUB mouse.setexc (X1, y1, x2, y2)
clregs
regs.ax = 16
regs.cx = X1: regs.dx = y1
regs.si = x2: regs.di = y2
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
END SUB

SUB mouse.setlim (X1, y1, x2, y2)
clregs
regs.ax = 7
regs.cx = X1: regs.dx = x2
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
regs.ax = 8
regs.cx = y1: regs.dx = y2
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
END SUB

SUB mouse.setpos (mx, my)
clregs
regs.ax = 4
regs.cx = mx: regs.dx = my
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
END SUB

SUB mouse.show
clregs
regs.ax = 1
DEF SEG = table(0)
CALL absolute(table(1))
DEF SEG
END SUB

FUNCTION xms.alloc (handel, kbytes)
clregs
regs.ax = &H900
regs.dx = kbytes
DEF SEG = table(0)
CALL absolute(table(6))
DEF SEG
handel = regs.dx
IF regs.ax = 0 THEN xms.alloc = 0 ELSE xms.alloc = -1
END FUNCTION

FUNCTION xms.free (handel)
clregs
regs.ax = &HA00
regs.dx = handel
DEF SEG = table(0)
CALL absolute(table(6))
DEF SEG
IF regs.ax = 0 THEN xms.free = 0 ELSE xms.free = -1
info.xms = 0
END FUNCTION

FUNCTION xms.get (handel, xoffset&, leng&, buffer())
clregs
xmsmem.bytes = leng&
xmsmem.sorhan = handel
xmsmem.soroff = xoffset&
xmsmem.dsthan = 0
xmsmem.dstoff = 65536 * VARSEG(buffer(0)) + VARPTR(buffer(0))
regs.ax = &HB00
regs.ds = VARSEG(xmsmem)
regs.si = VARPTR(xmsmem)
DEF SEG = table(0)
CALL absolute(table(6))
DEF SEG
IF regs.ax = 0 THEN xms.get = 0 ELSE xms.get = -1
END FUNCTION

FUNCTION xms.init
clregs
DEF SEG = table(0)
CALL absolute(table(5))
DEF SEG
IF regs.ax = &H4380 THEN xms.init = -1 ELSE xms.init = 0
END FUNCTION

FUNCTION xms.put (handel, xoffset&, leng&, buffer())
clregs
xmsmem.bytes = leng&
xmsmem.dsthan = handel
xmsmem.dstoff = xoffset&
xmsmem.sorhan = 0
xmsmem.soroff = 65536 * VARSEG(buffer(0)) + VARPTR(buffer(0))
regs.ax = &HB00
regs.ds = VARSEG(xmsmem)
regs.si = VARPTR(xmsmem)
DEF SEG = table(0)
CALL absolute(table(6))
DEF SEG
IF regs.ax = 0 THEN xms.put = 0 ELSE xms.put = -1
END FUNCTION




[1]QuickBasic 4.0 and above had the INTERRUPT and INTERRUPTX functions which essentially did everything that TSR1 did plus it integrated much better with libraries written in other languages. So TSR1 is not needed on these platforms. The interpreter on the other hand only had CALL absolute.

2 Comments On This Entry

Page 1 of 1

Munawwar 

17 September 2010 - 01:59 PM
I was about to ask you "You wrote all this for a mouse?". But I just remembered I too did something like this with turbo 3.1!
Man, programmers are crazy!
//19 December 2007
enum {NOT_MOVED=1};
enum {NONE,LEFT_CLICK,RIGHT_CLICK,BOTH,DOUBLE_CLICK};
enum {TEXT=0,GRAPHMODE=3};
enum {MOUSEDOWN=1,MOUSEUP=2};

void store_registers(int &tax,int &tbx,int &tcx,int &tdx)
{
  auto int tempax,tempbx,tempcx,tempdx;
		asm{
		mov tempax,ax;
		mov tempax,bx;
		mov tempax,cx;
		mov tempax,dx;
		}
  tax=tempax;
  tbx=tempbx;
  tcx=tempcx;
  tdx=tempdx;
}

void restore_registers(int tax,int tbx,int tcx,int tdx)
{
		asm {
		mov ax,tax;
		mov bx,tbx;
		mov cx,tcx;
		mov dx,tdx;
		}
}

class _mouse
{
  private:
  int visibility;
  int prev_button;
  int x,y;
  int temp_x,temp_y;
  int prev_x,prev_y;
  int button,status,movement;

  public:
  int initialize(int mode=GRAPHMODE,int minx=DEF,int maxx=DEF,int miny=DEF,int maxy=DEF);
  int getvis();
  int hide();
  int unhide();

  int getprevx(int cordtype=GRAPHIC);
  int getprevy(int cordtype=GRAPHIC);
  int getx(int cordtype=GRAPHIC);
  int gety(int cordtype=GRAPHIC);
  int calcx(int cordtype=GRAPHIC);
  int calcy(int cordtype=GRAPHIC);

  int getprevbutton();
  int getbutton();
  int getstat();
  int getact();
  int getevent();

  _mouse()
  {
	 visibility=FALSE;
	 prev_button=0,button=0;
	 x=0,y=0,temp_x=0,temp_y=0;
	 prev_x=0,prev_y=0;
	 status=NOT_CLICKED,movement=NOT_MOVED;
  }
};

int _mouse::initialize(int mode,int minx,int maxx,int miny,int maxy)
{
	 int tax,tbx,tcx,tdx;

	 if(mode==TEXT64 && minx==DEF && maxx==DEF && miny==DEF && maxy==DEF)
	 minx=0,maxx=639,miny=0,maxy=399;
	 else
	 if(minx==DEF && maxx==DEF && miny==DEF && maxy==DEF)
	 minx=0,maxx=getmaxx()-1,miny=0,maxy=getmaxy()-1;


	 if(mode!=TEXT64 && mode!=GRAPHMODE)
	 return FAILED;

	 store_registers(tax,tbx,tcx,tdx);
	 asm{
		mov ax,0x0007  //setting the width..
		mov cx,minx       //..min x
		mov dx,maxx     //..max x
		int 0x33       //..all set

		mov ax,0x0008  //setting the height..
		mov cx,miny       //..min y
		mov dx,maxy     //..max y
		int 0x33       //..now set
	 }
	 restore_registers(tax,tbx,tcx,tdx);
	 return SUCCESS;
}
int _mouse::getvis()
{
  return visibility;
}

int _mouse::hide()
{
  if(visibility==TRUE)
  {
	 visibility=FALSE;
	 int tax,tbx,tcx,tdx;
	 store_registers(tax,tbx,tcx,tdx);

	 asm mov ax,0x0002  //hide mouse pointer
	 asm int 0x33       //...

	 restore_registers(tax,tbx,tcx,tdx);
	 return SUCCESS;
  }
  return FAILED;
}

int _mouse::unhide()
{
  if(visibility==FALSE)
  {
	 visibility=TRUE;

	 int tax,tbx,tcx,tdx;
	 store_registers(tax,tbx,tcx,tdx);
	 asm{
		mov ax,0x0001   //unhide the mouse pointer
		int 0x33        //...done
	 }
	 restore_registers(tax,tbx,tcx,tdx);
	 return SUCCESS;
  }
  return FAILED;
}

int _mouse::getprevx(int cordtype)
{
  if(cordtype==GRAPHIC)
  return prev_x;
  if(cordtype==TEXT)
  return ((prev_x/8)+1);
  return -1;
}

int _mouse::getprevy(int cordtype)
{
  if(cordtype==GRAPHIC)
  return prev_y;
  if(cordtype==TEXT)
  return ((prev_y/8)+1);
  return -1;
}

int _mouse::getx(int cordtype)
{
  if(cordtype==GRAPHIC)
  return x;
  if(cordtype==TEXT)
  return ((x/8)+1);
  return -1;
}

int _mouse::gety(int cordtype)
{
  if(cordtype==GRAPHIC)
  return y;
  if(cordtype==TEXT)
  return ((y/8)+1);
  return -1;
}

int _mouse::calcx(int cordtype)
{
  int tax,tbx,tcx,tdx,mxco;
  store_registers(tax,tbx,tcx,tdx);
  asm{
	mov ax,0x0003
	int 0x33

	mov mxco,cx
  }
  restore_registers(tax,tbx,tcx,tdx);

  x=mxco;
  if(cordtype==GRAPHIC)
  return x;
  if(cordtype==TEXT)
  return (x/8)+1;
  return -1;
}

int _mouse::calcy(int cordtype)
{
  int tax,tbx,tcx,tdx,myco;
  store_registers(tax,tbx,tcx,tdx);
  asm{
	mov ax,0x0003
	int 0x33

	mov myco,dx  //gets mouse y coordinate
  }
  restore_registers(tax,tbx,tcx,tdx);

  y=myco;
  if(cordtype==GRAPHIC)
  return y;
  if(cordtype==TEXT)
  return (y/8)+1;
  return -1;
}

int _mouse::getprevbutton()
{
  return prev_button;
}
int _mouse::getbutton()
{
  return button;
}

int _mouse::getstat()
{
  int tax,tbx,tcx,tdx,mbutton;
  store_registers(tax,tbx,tcx,tdx);
  asm{
	mov ax,0x0003
	int 0x33

	mov mbutton,bx
	//mbutton=1 when left click
	//mbutton=2 when right click
	//mbutton=3 when both are clicked togeather
	//mbutton=0 when not clicked
  }
  restore_registers(tax,tbx,tcx,tdx);

  static int msec,prevmsec=0,tempmsec,temps,dbbt;
  static struct time t;

  gettime(&t);
  temps=msec;
  prevmsec=msec;
  msec=t.ti_hund;
  tempmsec=msec-prevmsec;
  if(tempmsec<0) tempmsec+=1000;

	prev_button=button;
	button=mbutton;
	if(button!=0 && prev_button!=button)
	{
	  dbbt=button;
	  if(tempmsec<=500 && dbbt==button && button==LEFT_CLICK)
	  status=DOUBLE_CLICK,msec=-600;
	  else
	  status=CLICKED;
	}
	else
	{
	  if(tempmsec>500)
	  msec=-600;
	  else
	  msec=temps;
	  status=NOT_CLICKED;
	}

  return status;
}

int _mouse::getact()
{
  x=calcx(),y=calcy();
  if(x!=temp_x || y!=temp_y)
  {
	 prev_x=temp_x;
	 prev_y=temp_y;

	 temp_x=x,temp_y=y;
	 return MOVED;
  }
  else
  return NOT_MOVED;
}

int _mouse::getevent()
{
  if( (prev_button==NONE && button>0) || (prev_button==LEFT_CLICK && button==3) || (prev_button==RIGHT_CLICK && button==3) )
  return MOUSEDOWN;
  else
  if(prev_button>0 && button==NONE)
  return MOUSEUP;
  else
  return NONE;
}

class mouse
{
  private:
  int mode;    //TEXT64 or GRAPHMODE

  public:
  int cordinates_type;  //TEXT or GRAPHIC
  int visibility;
  int prev_x,prev_y;
  int x,y;
  int prev_button;
  int button,status,movement,event;

  int initialize(int modeno,int cordtype,int minx=DEF,int maxx=DEF,int miny=DEF,int maxy=DEF);
  int mousearea(int minx=DEF,int maxx=DEF,int miny=DEF,int maxy=DEF);
  int mousemode();
  int getallprop();
  int hide();
  int unhide();
  int gotoxy(int x,int y);

  mouse(int modeno=GRAPHMODE,int cordtype=GRAPHIC,int minx=DEF,int maxx=DEF,int miny=DEF,int maxy=DEF)
  {
	 initialize(modeno,cordtype,minx,maxx,miny,maxy);
  }
  ~mouse()
  {
	 mousearea(0,639,0,478);
  }
};

_mouse __mouse;
int mouse::mousemode()
{
  return mode;
}
int mouse::initialize(int modeno,int cordtype,int minx,int maxx,int miny,int maxy)
{
  event=NONE;
  status=NONE;
  movement=NOTMOVED;
  if(cordtype!=GRAPHIC && cordtype!=TEXT)
  cordtype=GRAPHIC;

  cordinates_type=cordtype;
  if(modeno==GRAPHMODE || modeno==TEXT64)
  mode=modeno;
  else
  modeno=GRAPHMODE;

  return mousearea(minx,maxx,miny,maxy);
}

int mouse::mousearea(int minx,int maxx,int miny,int maxy)
{
  return __mouse.initialize(mode,minx,maxx,miny,maxy);
}

int mouse::getallprop()
{
  if(mode==-1)
  return FAILED;

  visibility=__mouse.getvis();

  status=__mouse.getstat();
  event=__mouse.getevent();
  if(__mouse.getprevbutton()!=__mouse.getbutton())
  prev_button=__mouse.getprevbutton();
  button=__mouse.getbutton();

  prev_x=__mouse.getprevx(cordinates_type);
  prev_y=__mouse.getprevy(cordinates_type);

  movement=__mouse.getact();
  x=__mouse.getx(cordinates_type),y=__mouse.gety(cordinates_type);
  return SUCCESS;
}

int mouse::hide()
{
  if(mode==-1)
  return FAILED;

  visibility=__mouse.hide();
  return SUCCESS;
}

int mouse::unhide()
{
  if(mode==-1)
  return FAILED;

  visibility=__mouse.unhide();
  return SUCCESS;
}
int mouse::gotoxy(int x,int y)
{
  if( (cordinates_type==GRAPHIC && (x>=640 || x<0 || y>479 || y<0)) || (cordinates_type==TEXT64 && (x>80 || x<1 || y>50 || y<1)) || mode==-1 )
  return FAILED;
  else
  if(mode==TEXT64)
  x*=8,y*=8;

  int tax,tbx,tcx,tdx;
  store_registers(tax,tbx,tcx,tdx);
  asm{
	 mov  ax,0x0004
	 mov  cx,x
	 mov  dx,y
	 int  0x33
  }
  restore_registers(tax,tbx,tcx,tdx);
  return SUCCESS;
}

0

NickDMax 

22 September 2010 - 12:15 PM
Wow... someone read this? :) thanks.

Actually using the mouse may have been the motivation but TSR1 actually does much more such as giving direct access to BIOS/VSA graphics routines allowing QBasic programs to use SVGA high-color, high resolution screen modes. It gave old DOS programs access to EMS/XMS memory.

Basically it was to be my base graphics platform! Thus the dedicated "MEMORIE_MOVE" (wow my spelling was even worse back then) which would be vital for sprite manipulation. One drawback was the lack of access to VGA/SVGA registers though which would have made sprite handling much faster... but this would have been a big step in the right direction.

Like most kid programmers -- I did all of this for the graphics! The mouse was just an important part of a graphic interface.
0
Page 1 of 1

April 2019

S M T W T F S
 123456
78910111213
14151617181920
212223 24 252627
282930    

Recent Entries

Search My Blog

0 user(s) viewing

0 Guests
0 member(s)
0 anonymous member(s)