[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 ;-))
Cheers.
Matt
}
{$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]