[Back to TSR SWAG index]  [Back to Main SWAG index]  [Original]

{
I love it when people actually post there hard slaved code. I've
seen a number of people wanting to write tsr's, and all the examples
in BP that I've ever come across are pathetic and above 6K of mem!
So for those serious here is some flexible and easy to use code.
Drowning in comments ;-) It's extremely efficient on memory (1040)
bytes! I  can write it in pascal using 550 bytes put then I decided
to make it user friendly to a degree ;-) Shout if you need to know
more info on tsr writing eg. making it be polite to dos etc ;-))
}

{$A-}{$R-}{$S-}{$D-}{$F-}{$L-}{$Q-}{$T-}{$V-}{$X-}{$Y-}{$G+}
{$B-}{$N-}{$P-}
{$M 1024,0,0}
(* Ulti-Tsr-Demo-Proggie Coded: Matthew Tagg Jan '95                    *)

(* The beauty of this is that your init code, eg paramter parsing etc.  *)
(* does NOT effect the size in memory! Unlike crappy BP's attemp at a   *)
(* tsr! Another beautiful thing is that for you who hate the technical  *)
(* stuff can let the program work out what to keep in memory!           *)
(* USES *)                          (* There is no USES, so don't       *)
                                    (* use units, they suck. Use include*)
                                    (* files if you want but units      *)
                                    (* create new segments AARG         *)
                                    (* NOT for tsr's!                   *)
Const
   CharSEG  = $B800;                (* $B800 = COLOUR or $B000 for Mono *)
   DSegSize = 128;                  (* Data Segment Size                *)
   SSegSize = 256;                  (* Stack Segmetn Size               *)
   SSize    = SSegSize-16;          (* This is the value of the Stack   *)
                                    (* pointer (sp) and bp              *)
   DSegOff  = 2;                    (* This is added to the value copied*)
                                    (* from cs and written to ds ie DS  *)
                                    (* points to 32 bytes ahead of CS   *)
                                    (* I do this so that that pointers  *)
                                    (* Are not overriden                *)
   DatOffs  = DSegOff*16;           (* Offset relocation number         *)
                                    (* Use this to reference your data  *)
                                    (* ie. say you want to move the var *)
                                    (* NUM int the ax >                 *)
                                    (*   mov ax, CS:datoffs+NUM         *)
                                    (*           ^^^ Must use segment   *)
                                    (* override! OR easier to set       *)
                                    (*  DS = CS+DSegoff   default=2     *)
                                    (* When in a pascal routine you can *)
                                    (* reference it normally            *)

   CodeOffs = 16;                   (* Were OUR Code/Data starts, first *)
                                    (* 16 bytes of the int09 proc are   *)
                                    (* used for pushing stuff but we    *)
                                    (* don't need that crap!            *)

                                    (* PS Typed Constants use up memory *)
                                    (* They just another name for       *)
                                    (* initialised data.                *)
                                    (* Untyped are the same as EQU in   *)
                                    (* asm.  Untyped = No memory        *)

   PSize = 8;                       (* The amount of bytes used by the  *)
                                    (* Pointers                         *)
   NewSSegLoc = (CodeOffs+PSize+DsegSize+16) div 16;
                                    (* This says were the stack must    *)
                                    (* start (+16 so no code is over-   *)
                                    (* ridden)                          *)
Var
(* VARIABLES TO BE USED IN THE ACTUAL TSR                               *)
   Bob            : Byte;           (* Just test variables              *)
   Obo            : Word;           (* Used by the i.s.r (you can use   *)
                                    (* your own)      `                 *)

   EndData        : Word;           (* Insert all data to be saved,     *)
                                    (* ie data use in the i.s.r,        *)
                                    (* BEFORE this statement all init   *)
                                    (* data can come after  Saves Mem!  *)

(* VARIABLES TO BE USED IN THE INIT PART                                *)
   ProgSize       : Word;           (* Memory required in 16 byte       *)
                                    (* Paragraphs                       *)

   ThisDoes       : Byte;           (* Eg....                           *)
   NotGetSaved    : Char;
   ButCanBe       : Pointer;
   UsedInThe      : Longint;
   InitCode       : PChar;

   LastVar        : Word;           (* WARNING DO NOT CHANGE THIS       *)
                                    (* Do Not Insert Any Variable After *)
{$F+}
Procedure Int09; Interrupt; Assembler;
Asm
   Dw 0,0                              (* Pointers 4*2 bytes = 8     *)
   Dw 0,0
(* Data Seg (8*16=128bytes Not Bad) *) (* Bp7 Chews up the first 80  *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  (* bytes for whatever AAARG   *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  (* Change the size according  *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  (* to the amount of data your *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  (* prog uses                  *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
(* Stack Seg (8*16=128*)            (* Temp Stack                 *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (* You can make the stack size    *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (* whatever you want, but remember*)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (* to change the const SSize,     *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (* Default=256                    *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
(* Stack Seg (8*16+8*16=256) *)     (* Temp Stack                       *)
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
   Db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

   Nop                              (* Used for to ensure safety        *)
   Sti                              (* Interupts allowed                *)

   Pushf                            (* Pushf simulates and interrupt    *)
   Call Dword Ptr CS:[CodeOffs]     (* Calls the saved INT8             *)

   Inc   CS:DatOffs+Bob             (* Has a second passed? If not exit *)
   Cmp   CS:DatOffs+Bob,18          (* else call our interrupt WRT      *)
   Jne @Finnish

   Mov   Cs:DatOffs+Bob,0           (* Rests it for the next time       *)
   Inc   Cs:DatOffs+Obo             (* Obo is the number we write to the*)
                                    (* screen.                          *)
   Pushf
   Call Dword Ptr Far [CS:Codeoffs+4]; (* Call our routine              *)
   Sti
@Finnish:
   iret
End;

(* You can delete this procedure (for demo purposes only)               *)
Procedure PWord(Num,Pos,Base:Word); (* Proc. to write a number in any   *)
                                    (* base                             *)
Const
   AlphaNum : Array[0..35] of Byte =

(48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,
81,82,83,84,85,86,87,88,89,90);Begin   Asm
      Push  CharSEG                 (* Set ES to our text segment addr  *)
      Pop   Es                      (* Text Graphics segment            *)
      Mov   Ax, Num                 (* Ax to be divided                 *)
   @l1:
      Xor   Dx, Dx                  (* Clear                            *)
      Div   Base                    (* Dx = Remainder, Ax = Quotient    *)
      Cmp   Dx,0                    (* Remainder                        *)
      Jnz   @l2                     (* Finnished?                       *)
      Cmp   Ax,0                    (* Quotient                         *)
      Jz    @Fin
   @l2:
      Mov   Si, Dx
      Mov   Di, Pos                 (* Load offs                        *)
      Mov   Dl, Byte Ptr [AlphaNum+Si]
      Mov   [Es:Di], Dl
      Sub   Pos,2                   (* Inc bx by 2                      *)
      Jmp   @l1
@Fin:
      Pop   Pos                     (* Maintain stack                   *)
   End;
End;

Procedure Wrt;Interrupt; (* This Is Our Interrupt Service Routine       *)
Begin
   Asm
      Mov   Ax, Cs                  (* ALWAYS include these three       *)
      Add   Ax, DSegOff             (* instructions if you want to use  *)
      Mov   Ds, Ax                  (* your pascal declared variables.  *)
   End;
   (* DO WHAT YOU WANT HERE                                             *)
   PWord(Obo,158,10);               (* Write a number in the top left   *)
                                    (* hand column, demo purposes only! *)
End;
{$F-}{ <-----------------------------
                                    |                                   }
(* Init Procedures follow, note the {$f-} means these are now local     *)
(* BTW if you make your own init procedures make sure you place them    *)
(* after the Getvec or you can place them before the getvec but then    *)
(* change the line further down that needs the name of the first init   *)
(* procedure to your own name (currently the first init proc is GetVec  *)

Procedure GetVec(VecNo :Word; Var SavPoint :Pointer); (* DOS Sucks      *)
Var
   SavSeg, SavOff :Word;            (* Temp variables for pointer       *)
Begin
   Asm
      Push  Es                      (* Save Es                          *)
      Shl   VecNo, 2                (* Multiply num by 4 to get address *)
      Mov   Es, Word Ptr 0h         (* Zero Es. Vect Int's start at 0:0 *)
      Mov   Di, VecNo               (* Di = Num * 4                     *)
      Mov   Ax, Word Ptr [Es:Di]    (* Copy offset word of int,         *)
      Mov   SavOff, Ax              (* and save it                      *)
      Add   Di, 2                   (* Point to next word (segment)     *)
      Mov   Ax, Word Ptr [Es:Di]    (* Ax = Offset                      *)
      Mov   SavSeg, Ax              (* Save in temporary variable       *)
      Pop   Es                      (* Retrieved stored value           *)
   End;
   SavPoint := Ptr(SavSeg, SavOff); (* Convert Seg:Offset to pointer    *)
End;

Procedure SetVec(VecNo :Word; NewPoint :Pointer);  (* Don't use units   *)
Type                                (* Revectors the interrupts         *)
   PType          = Array[0..1] of Word;
Var
   NtPoint        : ^PType;
Begin
   Asm Cli End;                     (* No interrupts can be generated   *)
   NtPoint := @NewPoint;            (* during the process               *)
   MemW[0:VecNo*4] := NtPoint^[0];
   MemW[0:VecNo*4+2] := NtPoint^[1];
   Asm Sti End;                     (* Enable Interrupts                *)
End;

Begin
   If (Ofs(Int09) <> 0) Then Halt;  (* Used to ensure the proc is       *)
                                    (* included                         *)
                                    (* If pascal doesn't see a reference*)
                                    (* to a proc. or var. it excludes   *)
                                    (* it!                              *)

   EndData := Ofs(EndData);         (* Basicly finds the size of the    *)
                                    (* DS to be kept in memory          *)
   GetVec($8, Pointer(MemL[Cseg:CodeOffs]));
                                    (* Vector 8 is the timer interrupt  *)
                                    (* generated every 18.2 times a sec *)
   SetVec($8, Ptr(Cseg, CodeOffs+PSize+DSegSize+SSegSize));
   (*                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^            *)
                                    (* This is where the CODE actually  *)
                                    (* starts in the code segment       *)
   MemW[Cseg:Codeoffs+4] := Ofs( WRT );
   MemW[Cseg:Codeoffs+6] := Seg( WRt );
   (*           -----------------^^^                                    *)
   (* Substitute your own i.s.r routine name over here, if you want     *)

   (* This next stuff copies the data we defined and places it in       *)
   (* our new data segment                                              *)
   Asm
      Mov   Ax, Cs                  (* Seeing as you can't say          *)
      Add   Ax, DSegOff             (* Mov Es, Cs ..this is a way round *)
      Mov   Es, Ax                  (* Inc Ax, so no code overriden ;)  *)
      Xor   Di, Di                  (* Di = 0                           *)
      Xor   Si, Si                  (* Si = 0                           *)
      Mov   Cx, EndData             (* # of bytes to copy               *)
      Rep   Movsb                   (* ... and copy it                  *)
   End;

   (* What I am actually doing is having all three segments in the code *)
   (* segment, risky if you don't be careful but worth the space (g)    *)
   Asm
      Mov   Ax, Cs                  (* Get the current Code Seg         *)
      Add   Ax, NewSSegLoc          (* Add the New Stack Segment Locat  *)
      Mov   Ss, Ax                  (* Adjust SS                        *)
      Mov   Sp, SSize               (* The stack pointer, is SSegSize-16*)
      Mov   Bp, SSize               (*   (-16) to allow for segment     *)
                                    (* alignment.                       *)
      Push  Bp
   End;

   (* This allocates the amount of memory to be used in 16 byte         *)
   (* paragraphs. The variable progsize is the memory used by our prog. *)
   (* @GetVec is the FIRST of the init procdures so it gets that address*)
   (* and uses it as the size of our code, it adds 256 because of the   *)
   (* PSP (Program Segment Prefix) and then divides by 16 to get        *)
   (* paragraphs and adds on 1 more to to be safe.                      *)
   ProgSize := (Ofs(GetVec)+256) div 16 + 1;
   Asm                              (* Uses dos int 31.. *KEEP*         *)
      Mov   Dx, ProgSize
      Mov   Al, 0                   (* Return code ei ERRORLEVEL        *)
      Mov   Ah, 31h                 (* Dos Func 31h                     *)
      Int   21h
   End;
End.


[Back to TSR SWAG index]  [Back to Main SWAG index]  [Original]