'=========================================================================== ' Subject: INTERRUPTS IN QBASIC 4: HOOKSFT Date: 02-07-97 (22:15) ' Author: Rick Elbers Code: QB, QBasic, PDS ' Origin: rick@tip.nl Packet: INTERRPT.ABC '=========================================================================== 'INTERRUPTS IN QBASIC 4:HOOKING SOFTWARE INTERRUPTS '--------------------------------------------------------------- 'RICK ELBERS november 1996 DECLARE FUNCTION add.newint$ (vector&) DECLARE FUNCTION replace.newint$ (vector&) DECLARE SUB dosprnchar () DECLARE SUB dosprstr (string2write$) DECLARE SUB pokeDW (pokeseg%, pokeoff%, dword&) DECLARE SUB nwintcallint (vector&) DECLARE FUNCTION newint2$ (vector&) DECLARE SUB newintcall2 (newint$) DECLARE SUB newintcall (newintr$) DECLARE FUNCTION newint$ (vector&) DECLARE SUB handler1 (vector&, variant%) DECLARE SUB Pokestring (segje%, offje%, main$) DECLARE SUB interruptcall (vector&) DECLARE FUNCTION long2str$ (sdword&) DECLARE FUNCTION int2str$ (sword%) DECLARE FUNCTION oldvec& (nr%) DECLARE FUNCTION newvec& (seghandler%, offhandler%) CLS '------------- 'INTRODUCTION '-------------- 'This is part one concerned with actually intercepting a real interrupt: 'in this case int 21. Let us first start up with doing the interrupt 21 'call a little bit different to provide some insight into real interrupts. '---------------------------------------------- 'CALLING THE INTERRUPT ROUTINE:'INT' SIMULATION '---------------------------------------------- 'Since by now we know that an interrupthandler is just some routine at some 'adress pointed to by the vector at 0:intnr%*4 we might think we could also 'simulate an INT instruction by something like call vector&. And surprisingly, 'this is true. 'However there are two things we have to add before we can do that. Maybe you 'recalled from the first part of this discussion that an int call does also 'push the flags onto the stack before jumping to the code. The meaning of this 'is that our interrupt_call simulation has to pushf before calling the routine 'at vector&. The code in the sub interruptcall is doing that. Let us look if 'it works: OLDINT21VEC& = oldvec&(&H21): PRINT HEX$(OLDINT21VEC&): interruptcall (OLDINT21VEC&) SLEEP 'This is looking like 'a' on my machine..is it with you too? '---------------------------- 'ENABLING OTHER INTERRUPTS '---------------------------- 'With the discussion before we have attended to what an interrupt is normally 'doing except one thing: before jumping to the actual interruptcode there is 'another action taken by INT. Int is enabling further hardware interrupts. 'You can control that again by stepping through some code using debug( take 'for instance the int 9 handler). You simply put int 9 at some place and trace 'the instruction.Immediately you will see the third flag( EI) change to DI( 'disable interrupts). 'Although this is done with every interrupt, it is not necessary for int 21 'since it does not read out itself any necesarry information like ports. 'You should imagine what might have happened when we wanted to read some 'critical information and in between there occurred some interrupt. It might 'be that we read half of it before an interrupt occured ( since most pcs read 'out 32 bits at once it just might be one of the 50% cases where a word 'value is read in two parts.). When this interrupt is storing information say 'in ax and we where just reading a word into ax it is clear we are in trouble. 'so to keep it short a full interrupt masking routine looks like: 'PUSHF 'push flags for matching the int push of flags 'CLI 'disable interrupts 'CALL VECTOR& 'jmps to the vectoradress while pushing the cs:ip return adress 'STI 'enable interrupts again. 'Notewearthy is also that the iret of the procedure we called pop'ed the flags 'before returning. So we have proper access to returned flags like as if we 'used 'INT'. '------------------------------------ 'INTERRUPT HANDLERS: GENERAL FORM '------------------------------------ 'You might have asked yourself, why are we going to such a great length to 'execute a code we could have simply be executing by int 21 itself. The reason 'is that when we are intercepting some real interrupt we might just want to 'actually rewrite only a part of the code. Say for instance we only want to 'intercept int 21, function 2( printchar), but not all other functions. 'In that case after we established the hotfunction is not requested, we could 'call the original routine again. You might wanna ask why not at that point 'execute a simple int instruction ? But there we are asking dos to be 'multitasking..since we are already in interrupt code aren't we ? And well, 'for the moment let us suppose dos does not support multitasking. Indeed it 'does not allow you to call int 21 inside some int 21 code..Before there 'will rise again faulty rumours however, i should state that dos can be made 'multitasking, however this is certainly not simple. To see how it can be done 'you might take a look at R.Hydes art of programming chapter 19. 'Taking into consideration everything we have discussed thus far the general 'outline of an interrupthandler might be clear: 'PUSHF 'push flags for matching the int push of flags 'CLI 'disable interrupts 'CMP AX,HOTFCN 'is there a request for our hot function ? 'JZ NEWINT 'if it is jump to our newint. ' if not: 'CALL OLDINT 'jmps to the vectoradress while pushing the cs:ip return adress 'JMP END 'end we are done 'flags returned are the normal int returns flags. 'newint: '------ 'POPF 'pop original(entrance) flags again. 'PUSHA 'push all registers before... '---------------------- 'actual code to execute '---------------------- 'POPA 'pop all registers afterwards.... '[[PUSHF:CALL OLDINT]]'with the original registers. 'flags returned are the flags returned by our code. 'end: '---- 'STI 'enable interrupts again 'RETF 'back to qbasic 'The part that says [[PUSHF:CALL OLDINT]] is only facultative. You can use 'this when you want to execute and your handler and the original one. Say for 'instance you only want to print another character 'a'before the printed one, 'only when some print 'a' function is executed( or more likely : print 'something like "hello, love feeling good today" when there is a request for 'printing "income"). In that case you have of course to push the flags again, 'since we restored the original ones in our routine. Furthermore especially 'for this addditionhandlers there are the popa and pusha intructions in our 'interrupt. They are necessary since in the actual code, you might change 'reggies in the first part, and when the actual [[PUSHF:CALL OLDINT]]is 'executed then you want the entrance registers restored. Since you just want 'at that point an INT instruction executed.. 'To make the structure pointed out above a bit more visible i wrote the 'routine handler1 that has an interrupt handler for int 21, function 9. 'When you call this routine with the function 9 set( that is option 3) 'it will NOT display the string but instead an 'B'. For other functions, 'for instance function 2( print a character) just the normal function is 'executed. So option 1 will display the character 'A' on screen just like 'the interruptcall routine did. Option 2 is just a control. It will 'display the string via the normal interrupt calling routine. 'For control let us execute handler: CLS FOR i% = 1 TO 3 handler1 oldvec&(&H21), i% LOCATE i%, 40: PRINT "output of variant "; i%: NEXT SLEEP '------------------------------ 'RESUME: A SHORT LONGCUT AGAIN '------------------------------ 'Until now we have already managed a lot, though you might not be aware of it. 'First of all since we have managed to simulate an interruptcall we have now 'the weapons to do multiple "INT" calls in one INTERRUPT ROUTINE. We know what 'to do( CLI and PUSHF before CALLING) to multitask DOS. 'Secondly you might have discovered that our last routine was not as clear as 'normal. A few things are confusing here i think. First of all the calling 'routine and the caller are mixed in this example for instruction purposes. 'To clear things a little bit i separated the newint and the intcaller 'procedures again using the PROC segment handling i described in another 'discussion. In the sub Newintcall our handler is setup like a FAR PROCEDURE, 'and that is exactly what the interrupt routines are themselves. CLS newintcall newint$(oldvec&(&H21)) SLEEP 'The upset in newintcall and newint together could be considered the basic 'upset of a handler. You will notice that when some other function then 'function &h9 is called then there will not be printed anything. 'But when we call newint with intcaller2, that is with registers set up for 'printing a sign,normal functionality is achieved. CLS newintcall2 newint$(oldvec&(&H21)) SLEEP 'Apart from the setting vectors as we did in the first part of this discussion 'our basic handler is allright in NEWINT now. I think we cleared the air a bit 'but there are more sources of confusion when we start to use more advanced 'handlers. The handler so far in newint, does only functions as REPLACE_HANDLER 'as i would like to call it. When we call for the hooked function newint 'REPLACES the oldint. Things are getting a lot more interesting when we are 'going to ADD functionality to some call. In that case we might speak of 'ADD_HANDLERS. In that case we might distinguish a few parts for further 'clarification. But let us first look for the accomplishments: CLS newintcall newint2$(oldvec&(&H21)): SLEEP: CLS : newintcall2 newint2$(oldvec&(&H21)) SLEEP 'Shit on my machine there is an 'i' instead of a 'h' on yours too ? Well 'fix it.... 'When you take a closer look on the newint2 again referred to as the basic 'ADD_handler then you might notice a few parts: '------------------------------ 'HANDLER_ADD: basic structure '------------------------------ '1)Push original registers '------------------------- ' PUSHA 'All the registers changed in this handler '2)condition to be met '---------------------- ' CMP AH, CRITICAL VALUE 'The value to be hooked ' JZ NEWINT 'If so then jump to our newint '3)oldint '--------- ' PUSHF 'If not then simulate INT ' CLI ' CALL VECTOR$ ' JMP END 'and jump to the end '4)newint '4a)newint:part before the original call '--------------------------------------- 'REGISTER UPSET FOR CODE BEFORE '[if there is also a call to vector$ then: ' PUSHF ' CALL VECTOR$] '4b)newint:original call ' POPA 'Restore entrance registers AND ' PUSHA 'Store them again.... ' PUSHF 'Simulate intcall with original registers so: ' CALL VECTOR$ 'old functionality matched!!! '4c)newint:part after the original call 'REGISTER UPSET FOR CODE AFTER '[If there is also a call to vector$ then: ' PUSHF ' CALL VECTOR$] '5)Closing: ' STI 'Enable interrupts again ' POPA 'Restore original registers again ' [note:flags returned are the flags ' returned by the last call vector$] ' RETF 'Return to caller 'Of course it should be mentioned that there can be saved a few bytes by 'making the oldint into a call_able procedure also, but for clarity i thought 'this basic structure was best. Take into consideration however that 'interrupthandlers are usually closely related to TSR programming and it will 'be obvious you should give optimization in this cases your best shot... 'INTERRUPTHANDLERS VIEWED FROM CALLING PROGRAMS '----------------------------------------------- 'So far we have given the interrupthandlers themselves our almost undivided 'attention, but we might turn now for a moment to the calling programs, since 'it seems to be that we 've got our handlers basically working. 'From the point of view from our calling programs callnewint( call the hooked 'function) and callnewint2(call another function) in fact the only important 'thing of newint is in fact its adress.... 'So far we managed to call the newint by adress by just putting the newint$ 'into a PROC segment that could be adressed by the callnewint procedure. It 'was however no coincidence that the PROC segment was an integer array. When 'we are designing things the way we do, as i will advise for keeping things 'evident, then it is obvious we should use an integerarray to store the newint 'instead of an asm$ string since the integerarray will not be walking around 'in memory between CALLs. 'This is the more evident because when it is a real interrupt replacer then 'the function will be called * a lot* of times, and we have to store the 'adress of the newint into the vector table.. 'So far we were passing the newint$ to the newintcaller but it is apparent 'that when the adress of our newint is stored in the vectortable then that 'is no longer necessary. Also the calling codes could be restored to normal '[replace call vector$ by int 21 again]. When we adequately stored the vector 'of our interrupt newint to replace the oldvector of int 21 then everything 'should work nicely. We only should call our setvec once for initializing. 'After we know the newvec of course. There is the option to EITHER put 'a setvec procedure at the beginning of our newintcaller OR put a setvec 'procedure at the beginning of our interrupthandler. The last is certainly 'not advised since we want to keep our interrupthandler as short as possible. 'Also setting vectors at the start of a newintcaller seems to be only making 'sense when you use newint only in that one procedure. In that case you might 'wonder if an interrupthandler is necessary at all, but if so, then it seems 'like the way to go to design newintcaller as first setting vectors and ending 'with resetting vectors. However,,... 'Interrupthandlers are really shining in the area where one handler can be used 'for many other procedures. Or where you want to replace a limiting function 'of DOS for a lot of calls to the same procedure. Therefore in this discussion 'we will go for the general design: '----------- 'QBASIC:MAIN '----------- 'GETVEC 'get oldvector 'INITNEWINT 'Initialize the newintcode- in other words put in into place 'SETVEC 'set newvector 'NEWINTCALLER 'whatever assembly sub that also calls the HOOKED INT 'RESETVEC 'setvec oldvector 'The only remaining thing is the initializing of newint using an adressable 'integerarray for storage. I can promise you that i will develop some method 'of assembly storage that has both the advantages of asm$ and asm% but for 'now i will use a quick patch, since we still use the changing abstraction 'method.....Let us see: newinterrupt$ = newint$(oldvec&(&H21)): DIM nwint%(LEN(newinterrupt$) \ 2 + 1) nwintseg% = VARSEG(nwint%(0)): nwintoff% = VARPTR(nwint%(0)) Pokestring nwintseg%, nwintoff%, newinterrupt$ 'Things are done...so far...a bit of a back and forth translation but allright 'its oftopic and it works.. 'CONTROLE: newintvector& = newvec&(nwintseg%, nwintoff%) CLS : nwintcallint (newintvector&) 'There seems to be no difference to the strings..... But before going to 'the actually putting all of it together a short recall section is in place 'i think '--------------------------------- 'INTERRUPT_HANLERS: SHORT RESUME '---------------------------------- 'We started our discussion concerning interrupthandlers with the examining 'of interrupts as they are. We discovered the vectortable, and managed to 'set up our own interrupt by putting a vector to code of our own at some 'open place. In the process we noticed that QBASIC resets itself a few 'vectors in the vectortable. We developed a few very basicfunctions for 'rapidly setting and resetting vectors, thereby passing the DOS function 'INT 21,25 that is normally doing that, but is to some unknown reason 'disabled in our IDE. Furthermore we started to get further into interrupts 'and there general structure. We discovered PUSHF and the IRET instruction. 'That was pretty much where the first part ended. Promising but nothing more.. 'In the second part presented here we discovered that all the basics were 'already there just waiting for us to put in code..We started with an 'interrupt simulation routine which CALLs the vector instead of INT with the 'same functionality. A little attention was given to enabling interrupts, 'and to DOSs single tasking.. After that we rapidly progressed to some 'basicstructures for HANDLERS. We distinguished between ADD_HANDLERS and the 'more simple REPLACE_HANDLERS. We cleared some confusement considering the 'actual HANDLER and the CALLING ROUTINES by then, since like always, when 'progressing, more structure is needed or at least desirable. We executed 'two calling routines one requesting for the hooked function and the other 'one requesting some other function. 'Towards the end we left the handlers again in favor of first the CALLING 'PROCEDURES and later QBASIC itself as MAIN CALLER. In the process we 'reviewed our initial setvec/getvec procedures. Eventually also we returned 'to integer storing our handler, since it has to be adressable at a fixed 'adress. 'The discussion will be ended here with some almost "nonsense" interrupt 'handler, but i take it that in the process if have outlined the the major 'flexibility of interrupthandlers also. The actual use of handlers is 'a topic far to big for this article, maybe for the place you read this 'from and maybe also for me..I just wanted to present the basic handling 'to you so that new roads are open for you. It might just be that i design 'some handlers and upload them in LINT.bas. We'll see... '------------------------- 'PUTTING IT ALL TOGETHER. '------------------------- 'Now that we know that our handler is allright, we are ready for the big 'surprise. Let us actually do the resetting of vectors...so that our handler 'is executed instead of the original one! 'We have to do minor surgery to our handler and caller however, since the 'handlers will be no longer FAR PROCEDURES but INTERRUPT HANDLERS. The 'only difference is that they return with IRET instead of RETF...meaning 'that they PUSH the flags also. We have been extensivaly into the flags 'already but since our handler is now the interrupt himself he had to return 'with IRET also!! So there are the final subs called ADD_NEWINT and REPLACE_ 'NEWINT. 'The second change is even more little. The caller can does not have to be 'no longer some FAR PROCEDURE CALLER but can be an ordinary interruptcaller. 'For that reason the dosprnstr and dosprnchar are used as calling the hooked 'function and calling another one. 'We know already from the first part of this discussion that the setvec and 'getvec routines are oke. So we do not have to bother about that. CLS COLOR 0, 7: PRINT "Before the INTERRUPT HOOKING a CALL of dosprnstr looks like:": COLOR 7, 0: dosprstr ("wat is dit ? Een interrupt handler !") PRINT : COLOR 0, 7: PRINT "Before the INTERRUPT HOOKING a CALL of dosprnchar looks like:" COLOR 7, 0: dosprnchar PRINT : COLOR 0, 7 LOCATE 12, 1 PRINT "Press a key to see what the same calls looks like after hooking" COLOR 7, 0 SLEEP: CLS 'get the original int 21 vector oldint21& = oldvec&(&H21) 'initializing newinterrupt & nwint_vector for replace.newint '------------------------------------------------------------ newinterrupt$ = replace.newint$(oldint21&): REDIM nwint%(LEN(newinterrupt$) \ 2 + 1) nwintseg% = VARSEG(nwint%(0)): nwintoff% = VARPTR(nwint%(0)) Pokestring nwintseg%, nwintoff%, newinterrupt$ newintvector& = newvec&(nwintseg%, nwintoff%) 'set our vector for int 21 at the vectortable pokeDW 0, &H21 * 4, newintvector& 'execute an ordinary intcaller: [almost forgot how that works...] CLS : COLOR 0, 7: PRINT "When the INTERRUPT is REPLACE_HOOKED a CALL of dosprnstr looks like:": LOCATE 2, 1: COLOR 7, 0: dosprstr ("wat is dit ? Een interrupt handler !") LOCATE 3, 1: COLOR 0, 7: PRINT "When the INTERRUPT is REPLACE_HOOKED a CALL of dosprnchar looks like:" LOCATE 4, 1: COLOR 7, 0: dosprnchar COLOR 0, 7 LOCATE 12, 1: PRINT "Press a key again to see what the ADD_handler output looks like" COLOR 7, 0: SLEEP: CLS 'Initializing newinterrupt & nwint_vector for add.newint '------------------------------------------------------------ newinterrupt$ = add.newint$(oldint21&): REDIM nwint%(LEN(newinterrupt$) \ 2 + 1) nwintseg% = VARSEG(nwint%(0)): nwintoff% = VARPTR(nwint%(0)) Pokestring nwintseg%, nwintoff%, newinterrupt$ newintvector& = newvec&(nwintseg%, nwintoff%) 'set our vector for int 21 at the vectortable pokeDW 0, &H21 * 4, newintvector& 'execute an ordinary intcaller: [almost forgot how that works...] CLS : COLOR 0, 7: PRINT "When the INTERRUPT is ADD_HOOKED a CALL of dosprnstr looks like:": LOCATE 2, 1: COLOR 7, 0: dosprstr ("wat is dit ? Een interrupt handler !") LOCATE 3, 1: COLOR 0, 7: PRINT "When the INTERRUPT is ADD_HOOKED a CALL of dosprnchar looks like:" LOCATE 4, 1: COLOR 7, 0: dosprnchar COLOR 0, 7 LOCATE 12, 1: PRINT "Press a key again to reset everything to normal" COLOR 7, 0: SLEEP: CLS 'reset the vector to oldvec& '----------------------------- pokeDW 0, &H21 * 4, oldint21& 'restore the original vector COLOR 0, 7: PRINT "After the INTERRUPT HOOKING a CALL of dosprnstr looks like:": COLOR 7, 0: dosprstr ("wat is dit ? Een interrupt handler !") PRINT : COLOR 0, 7: PRINT "After the INTERRUPT HOOKING a CALL of dosprnchar looks like:" COLOR 7, 0: dosprnchar PRINT : COLOR 0, 7 END FUNCTION add.newint$ (vector&) '--------------------------- 'EXAMPLE OF AN ADD_HANDLER '--------------------------- 'This handler is an example of hooking an interrupt on 'print string. When another function then function 9 'is requested then the string will be printed,if the 'normal printstring is requested( function 9) then 'this handler will print 'B' AND the string requested AND 'H'. '--------------------------------------------------------- 'DATA 'Make our vector to fit the string way of saving '------- vector$ = long2str$(vector&) '---------------------------------------------------------- 'CODE '---- '0) Preserve the registers on entrance 'push ax 'push dx 'push ds ' '1) Condition to be met 'cmp ah,09 'print string ? 'jz newint 'if zero move to our newint '2) Oldint 'pushf 'imitate the interrupt 'cli 'call vector$ 'if not then execute int 21,9 'jmp end 'and jump to the end '4) Newint '---------- 'part before: '------------- 'mov ah,2 'mov dl,42 'pushf 'simulating an int call again 'cli 'call vector$ 'execute druk tek b 'part original call: 'pop ds 'restore entrance registers 'pop dx 'pop ax 'push ax 'push dx 'push ds 'pushf 'simulate int again 'cli 'call vector$ '------------- 'part after: '------------- 'mov ah,2 'mov dl,49 'pushf 'simulating an int call again 'cli 'call vector$ 'execute druk tek h '5)Closing 'end : 'sti 'pop ds 'pop dx 'pop ax 'iret 'back to calling routine '----------------------------------- 'preserve original registers for later use asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) 'conditional part: asm$ = asm$ + CHR$(&H80) + CHR$(&HFC) + CHR$(&H9)'cmp ah,09 asm$ = asm$ + CHR$(&H74) + CHR$(&H9) 'jz newint 'oldint: asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&HFA) 'cli asm$ = asm$ + CHR$(&H9A) + vector$ 'call vector$ asm$ = asm$ + CHR$(&HEB) + CHR$(&H21) 'jmp end 'newint:before '------------------------------------------------------- 'ax: already preserved but will be changed temporaraly 'dx: already preserved but will be changed temporaraly '----------------------------------------------------- asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) 'mov ah,02 asm$ = asm$ + CHR$(&HB2) + CHR$(&H42) 'mov dl,42 asm$ = asm$ + CHR$(&HFA) 'cli simulate int asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&H9A) + vector$ 'druktek(b) '11 newint:old part asm$ = asm$ + CHR$(&H1F) 'pop original registers back asm$ = asm$ + CHR$(&H5A) asm$ = asm$ + CHR$(&H58) 'push them again for retrieving. asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) asm$ = asm$ + CHR$(&H9C) 'pushf 'simulate an int asm$ = asm$ + CHR$(&H9A) + vector$ 'printstring '24 newint:after part '---------------------------------------- 'Original registers are preserved 'but ax and dx will change '---------------------------------------- asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) 'mov ah,02 asm$ = asm$ + CHR$(&HB2) + CHR$(&H49) 'mov dl,49 asm$ = asm$ + CHR$(&H9C) 'pushf 'simulate int asm$ = asm$ + CHR$(&H9A) + vector$ 'druktek(h) '35 end: asm$ = asm$ + CHR$(&HFB) 'sti 'popa: asm$ = asm$ + CHR$(&H1F) 'pop original registers back asm$ = asm$ + CHR$(&H5A) 'but flags delivered will be asm$ = asm$ + CHR$(&H58) 'last interrupt call flags 'retf: asm$ = asm$ + CHR$(&HCF) 'iret add.newint$ = asm$ END FUNCTION SUB dosprnchar 'This sub is just printing a character using a DOS int 21,2 call '------------------------------------------------------------------------- 'CODE '----- prnchar$ = "" prnchar$ = prnchar$ + CHR$(&H50) 'pusha prnchar$ = prnchar$ + CHR$(&H52) prnchar$ = prnchar$ + CHR$(&HB2) + CHR$(&H45) 'mov dl,&h45 prnchar$ = prnchar$ + CHR$(&HB4) + CHR$(&H2) 'MOV AH,2 prnchar$ = prnchar$ + CHR$(&HCD) + CHR$(&H21) 'INT 21 prnchar$ = prnchar$ + CHR$(&H5A) prnchar$ = prnchar$ + CHR$(&H58) prnchar$ = prnchar$ + CHR$(&HCB) 'retf '________________________________________ Codeoff% = SADD(prnchar$) DEF SEG = VARSEG(prnchar$): LOCATE 5, 1 CALL ABSOLUTE(Codeoff%): DEF SEG '________________________________________ END SUB SUB dosprstr (string2write$) 'This procedure is just printing out the string2write 'using dos int 21,9 function. '------------------------------------------------------------------------- 'DATA a% = LEN(string2write$) + 1: DIM datas%(a% \ 2 + 1) '----------------------------- 'INTRODUCTION ON DATASEGMENTS: '------------------------------ 'DATAS will be our datasegment. Take good care that we declare it as an 'array since arrays start at segment bounderies. That way we will not be 'surprised by segment changes. This might be trivial but when you use 'a simple variable which is number Xth in the row of variables it just might 'be that your starting adress is at seg:&hfffe. Same holds for stringvariables. 'They do not start at segmentbounderies too. Another feature of strings 'is that they walk around in memory due to garbage collection, making a 'string very unsuited for adressability purposes. So: we use DATAS% and poke 'our variables and strings just inside it! 'The length of the datasegment in this example is designed to keep only 'the string. b$ = string2write$ + "$" 'This is the way DOS processes stringprint '-------------------------------- 'MAKE THE DATASEGMENT ADRESSABLE: '-------------------------------- 'Here is the basic upset of an adressable datasegment: dataseg% = VARSEG(datas%(0)): dataoff% = VARPTR(datas%(0)) dataseg$ = int2str$(dataseg%): dataoff$ = int2str$(dataoff%) 'Next we poke our string into the datasegment. Pokestring dataseg%, dataoff%, b$ '----------------------------------- 'CODE 'push ax 'push dx 'push ds 'mov ax,dataseg$ 'mov ds,ax 'mov dx,dataoff$ 'mov ah,09 'printstring with ending $ 'int 21 'pop ds 'pop dx 'pop ax 'retf '----------------------------------- 'pusha asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) asm$ = asm$ + CHR$(&HB8) + dataseg$ 'MOV AX,DATASEG$ asm$ = asm$ + CHR$(&H8E) + CHR$(&HD8) 'MOV DS,AX asm$ = asm$ + CHR$(&HBA) + dataoff$ 'MOV DX,DATAOFF$ asm$ = asm$ + CHR$(&HB4) + CHR$(&H9) 'MOV AH,9 asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'INT 21 'popa asm$ = asm$ + CHR$(&H1F) asm$ = asm$ + CHR$(&H5A) asm$ = asm$ + CHR$(&H58) 'retf asm$ = asm$ + CHR$(&HCB) '________________________________________ Codeoff% = SADD(asm$) DEF SEG = VARSEG(asm$) CALL ABSOLUTE(Codeoff%) '________________________________________ DEF SEG END SUB SUB handler1 (vector&, variant%) 'This handler is an example of hooking an interrupt on 'print string. 'let us setup a variant like or one char print or a string print. 'variant =1 =>print string a$ with our handler 'variant =2 =>print string a$ with normal int 21 'variant =3 =>print character "A" a$ = "this is a teststring$" DIM datas%(2 + LEN(a$)) dataseg% = VARSEG(datas%(0)): dataoff% = VARPTR(datas%(0)) dataseg$ = int2str$(dataseg%): vector$ = long2str$(vector&) stringoff$ = int2str(dataoff% + 4) Pokestring dataseg%, dataoff% + 4, a$ '----------------------------------- 'push ax 'push dx 'push ds 'mov ax,dataseg$ 'mov ds,ax 'mov ds,dataseg 'testroutine: 'if variant%< 3 then 'print a string used for testing 'mov dx,stringoff$ '(option 1 and 2) 'mov ah,9 'else 'mov ah,2 'print character "A" used for testing 'mov dl,41 '(option 3) 'endif 'start of handler: 'pushf 'cli 'if variant%>1 then 'if not an ordinary print string call:(option 1) 'cmp ah,09 'print string ? 'jz newint 'if zero move to our newint 'end if 'call vector$ 'if not then execute int 21,9 'jmp end 'and jump to the end 'newint start: 'popf 'original flags 'mov ah,2 'mov dl,42 'pushf 'simulating an int call again 'call vector$ 'execute druk tek b 'end : 'sti 'pop ds 'pop dx 'pop ax 'retf '----------------------------------- 'pusha asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) 'test: asm$ = asm$ + CHR$(&HB8) + dataseg$ asm$ = asm$ + CHR$(&H8E) + CHR$(&HD8) IF variant% < 3 THEN 'print string asm$ = asm$ + CHR$(&HBA) + stringoff$ 'mov dx,stringoff$ asm$ = asm$ + CHR$(&HB4) + CHR$(&H9) 'mov ah,9 ELSE 'print character 'a' asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) 'mov ah,2 asm$ = asm$ + CHR$(&HB2) + CHR$(&H41) 'mov dl,41 END IF 'handler asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&HFA) 'cli IF variant% > 1 THEN asm$ = asm$ + CHR$(&H80) + CHR$(&HFC) + CHR$(&H9)'cmp ah,09 asm$ = asm$ + CHR$(&H74) + CHR$(&H7) 'jz newint END IF asm$ = asm$ + CHR$(&H9A) + vector$ 'call vector$ asm$ = asm$ + CHR$(&HEB) + CHR$(&HB) 'jmp end 'newint: asm$ = asm$ + CHR$(&H9D) 'popf asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) asm$ = asm$ + CHR$(&HB2) + CHR$(&H42) asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&H9A) + vector$ 'druktek(b) 'end: asm$ = asm$ + CHR$(&HFB) 'sti 'popa: asm$ = asm$ + CHR$(&H1F) asm$ = asm$ + CHR$(&H5A) asm$ = asm$ + CHR$(&H58) 'retf: asm$ = asm$ + CHR$(&HCB) '________________________________________ Codeoff% = SADD(asm$) DEF SEG = VARSEG(asm$) CALL ABSOLUTE(Codeoff%) '________________________________________ DEF SEG END SUB FUNCTION int2str$ (sword%) 'this function is translating sword integers into a string. its only use 'is when you still use asm$ for assembler functions( like i do). in that 'case you can make your integer values usable .. 'this function simply translates the hexa bytes 'into stringbytes as is. '---------------------------------------------------- DEF SEG = VARSEG(sword%) ptr% = VARPTR(sword%) int2str$ = CHR$(PEEK(ptr%)) + CHR$(PEEK(ptr% + 1)) DEF SEG END FUNCTION SUB interruptcall (vector&) DIM datas%(2) dataseg% = VARSEG(datas%(0)): dataoff% = VARPTR(datas%(0)) dataseg$ = int2str$(dataseg%): vector$ = long2str$(vector&) '----------------------------------- 'push ax 'push dx 'push ds 'mov ax,dataseg$ 'mov ds,ax 'mov ds,dataseg 'mov ah,2 'druktek 'mov dl,41 'teken a 'cli 'disable interrupts 'pushf 'simulate interrupt 'call vector$ 'farcall to interrupt vector 'sti 'enable interrupts again 'pop dx 'pop ax ''popf is done by the iret of the routine we called '----------------------------------- 'pusha asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) asm$ = asm$ + CHR$(&HB8) + dataseg$ asm$ = asm$ + CHR$(&H8E) + CHR$(&HD8) asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) asm$ = asm$ + CHR$(&HB2) + CHR$(&H41) asm$ = asm$ + CHR$(&HFA) 'cli asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&H9A) + vector$ asm$ = asm$ + CHR$(&HFB) 'sti 'popa asm$ = asm$ + CHR$(&H1F) asm$ = asm$ + CHR$(&H5A) asm$ = asm$ + CHR$(&H58) 'retf asm$ = asm$ + CHR$(&HCB) '________________________________________ Codeoff% = SADD(asm$) DEF SEG = VARSEG(asm$) CALL ABSOLUTE(Codeoff%) '________________________________________ DEF SEG END SUB FUNCTION long2str$ (sdword&) 'this function simply translates the bytes 'of the sdword to a string as is 'it is only useful when you still use asm$ 'approach (like i do) '------------------------------------------- DEF SEG = VARSEG(sdword&) ptr% = VARPTR(sdword&) long2str$ = CHR$(PEEK(ptr%)) + CHR$(PEEK(ptr% + 1)) + CHR$(PEEK(ptr% + 2)) + CHR$(PEEK(ptr% + 3)) DEF SEG END FUNCTION FUNCTION newint$ (vector&) '----------------------------- 'EXAMPLE OF A REPLACE_HANDLER[far procedure] '------------------------------ 'This handler is an example of hooking an interrupt on 'print string. When another function then function 9 'is requested then the string will be printed,if the 'normal printstring is requested( function 9) then 'this handler will print only 'B'. '--------------------------------------------------------- 'DATA 'Make our vector to fit the string way of saving '------- vector$ = long2str$(vector&) '---------------------------------------------------------- 'CODE '---- '0) Preserve the registers on entrance 'push ax 'push dx 'push ds '1) Imitate an interrupt 'pushf 'cli '2) Condition to be met 'cmp ah,09 'print string ? 'jz newint 'if zero move to our newint '3) Oldint 'call vector$ 'if not then execute int 21,9 'jmp end 'and jump to the end '4) Newint 'newint start: 'popf 'original flags 'mov ah,2 'mov dl,42 'pushf 'simulating an int call again 'call vector$ 'execute druk tek b '5)Closing 'end : 'sti 'pop ds 'pop dx 'pop ax 'retf 'back to calling routine '----------------------------------- 'pusha asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) 'asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) 'mov ah,9 test is oke asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&HFA) 'cli asm$ = asm$ + CHR$(&H80) + CHR$(&HFC) + CHR$(&H9)'cmp ah,09 asm$ = asm$ + CHR$(&H74) + CHR$(&H7) 'jz newint asm$ = asm$ + CHR$(&H9A) + vector$ 'call vector$ asm$ = asm$ + CHR$(&HEB) + CHR$(&HB) 'jmp end 'newint: asm$ = asm$ + CHR$(&H9D) 'popf asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) asm$ = asm$ + CHR$(&HB2) + CHR$(&H42) asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&H9A) + vector$ 'druktek(b) 'end: asm$ = asm$ + CHR$(&HFB) 'sti 'popa: asm$ = asm$ + CHR$(&H1F) asm$ = asm$ + CHR$(&H5A) asm$ = asm$ + CHR$(&H58) 'retf: asm$ = asm$ + CHR$(&HCB) newint$ = asm$ END FUNCTION FUNCTION newint2$ (vector&) '--------------------------- 'EXAMPLE OF AN ADD_HANDLER[far procedure] '--------------------------- 'This handler is an example of hooking an interrupt on 'print string. When another function then function 9 'is requested then the string will be printed,if the 'normal printstring is requested( function 9) then 'this handler will print 'B' AND the string requested AND 'H'. '--------------------------------------------------------- 'DATA 'Make our vector to fit the string way of saving '------- vector$ = long2str$(vector&) '---------------------------------------------------------- 'CODE '---- '0) Preserve the registers on entrance 'push ax 'push dx 'push ds ' '1) Condition to be met 'cmp ah,09 'print string ? 'jz newint 'if zero move to our newint '2) Oldint 'pushf 'imitate the interrupt 'cli 'call vector$ 'if not then execute int 21,9 'jmp end 'and jump to the end '4) Newint '---------- 'part before: '------------- 'mov ah,2 'mov dl,42 'pushf 'simulating an int call again 'cli 'call vector$ 'execute druk tek b 'part original call: 'pop ds 'restore entrance registers 'pop dx 'pop ax 'push ax 'push dx 'push ds 'pushf 'simulate int again 'cli 'call vector$ '------------- 'part after: '------------- 'mov ah,2 'mov dl,49 'pushf 'simulating an int call again 'cli 'call vector$ 'execute druk tek h '5)Closing 'end : 'sti 'pop ds 'pop dx 'pop ax 'retf 'back to calling routine '----------------------------------- 'preserve original registers for later use asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) 'conditional part: asm$ = asm$ + CHR$(&H80) + CHR$(&HFC) + CHR$(&H9)'cmp ah,09 asm$ = asm$ + CHR$(&H74) + CHR$(&H9) 'jz newint 'oldint: asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&HFA) 'cli asm$ = asm$ + CHR$(&H9A) + vector$ 'call vector$ asm$ = asm$ + CHR$(&HEB) + CHR$(&H21) 'jmp end 'newint:before '------------------------------------------------------- 'ax: already preserved but will be changed temporaraly 'dx: already preserved but will be changed temporaraly '----------------------------------------------------- asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) 'mov ah,02 asm$ = asm$ + CHR$(&HB2) + CHR$(&H42) 'mov dl,42 asm$ = asm$ + CHR$(&HFA) 'cli simulate int asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&H9A) + vector$ 'druktek(b) '11 newint:old part asm$ = asm$ + CHR$(&H1F) 'pop original registers back asm$ = asm$ + CHR$(&H5A) asm$ = asm$ + CHR$(&H58) 'push them again for retrieving. asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) asm$ = asm$ + CHR$(&H9C) 'pushf 'simulate an int asm$ = asm$ + CHR$(&H9A) + vector$ 'printstring '24 newint:after part '---------------------------------------- 'Original registers are preserved 'but ax and dx will change '---------------------------------------- asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) 'mov ah,02 asm$ = asm$ + CHR$(&HB2) + CHR$(&H49) 'mov dl,49 asm$ = asm$ + CHR$(&H9C) 'pushf 'simulate int asm$ = asm$ + CHR$(&H9A) + vector$ 'druktek(h) '35 end: asm$ = asm$ + CHR$(&HFB) 'sti 'popa: asm$ = asm$ + CHR$(&H1F) 'pop original registers back asm$ = asm$ + CHR$(&H5A) 'but flags delivered will be asm$ = asm$ + CHR$(&H58) 'last interrupt call flags 'retf: asm$ = asm$ + CHR$(&HCB) newint2$ = asm$ END FUNCTION SUB newintcall (newintr$) 'In this sub the CALLER code is setup. This code will call some interrupt 'handler referred to as newintr$. It is setup for clarity in some other 'function( you can think of that function as a far function) 'This function is requesting a printstring from DOS which IS HOOKED '------------------------------------------------------------------------- 'PROC '----- DIM PROCS%(LEN(newintr$) \ 2 + 1) procseg% = VARSEG(PROCS%(0)): procoff% = VARPTR(PROCS%(0)) procseg$ = int2str$(procseg%): Pokestring procseg%, procoff%, newintr$: newintoff$ = int2str$(procoff%) 'Okay we have a PROC segment now with: 'PROC:0 newintr$ '--------------------------------------------------------------------- 'DATA: '------ b$ = "Hello i have been printed by an interrupthandler$" 'This is the way DOS processes stringprint REDIM datas%(LEN(b$) \ 2 + 1) dataseg% = VARSEG(datas%(0)): dataoff% = VARPTR(datas%(0)) dataseg$ = int2str$(dataseg%): dataoff$ = int2str$(dataoff%) Pokestring dataseg%, dataoff%, b$ '------------------------------------------------------------------------- 'CODE OF NEWINT CALLER:[Note that instead of INT we just call our newint] '----- prnstr$ = "" prnstr$ = prnstr$ + CHR$(&H50) 'pusha prnstr$ = prnstr$ + CHR$(&H52) prnstr$ = prnstr$ + CHR$(&H1E) prnstr$ = prnstr$ + CHR$(&HB8) + dataseg$ 'MOV AX,DATASEG$ prnstr$ = prnstr$ + CHR$(&H8E) + CHR$(&HD8) 'MOV DS,AX prnstr$ = prnstr$ + CHR$(&HBA) + dataoff$ 'MOV DX,DATAOFF$ prnstr$ = prnstr$ + CHR$(&HB4) + CHR$(&H9) 'MOV AH,9 prnstr$ = prnstr$ + CHR$(&H9A) + newintoff$ + procseg$ prnstr$ = prnstr$ + CHR$(&H1F) 'popa prnstr$ = prnstr$ + CHR$(&H5A) prnstr$ = prnstr$ + CHR$(&H58) prnstr$ = prnstr$ + CHR$(&HCB) 'retf '________________________________________ Codeoff% = SADD(prnstr$) DEF SEG = VARSEG(prnstr$): LOCATE 5, 1 CALL ABSOLUTE(Codeoff%): DEF SEG '________________________________________ END SUB SUB newintcall2 (newintr$) 'In this sub the CALLER code is setup. This code will call some interrupt 'handler referred to as newintr$. It is setup for clarity in some other 'function( you can think of that function as a far function) 'This function is requesting a printcharacter to DOS which is NOT HOOKED 'PROC '----- DIM PROCS%(LEN(newintr$) \ 2 + 1) procseg% = VARSEG(PROCS%(0)): procoff% = VARPTR(PROCS%(0)) procseg$ = int2str$(procseg%): Pokestring procseg%, procoff%, newintr$: newintoff$ = int2str$(procoff%) 'Okay we have a PROC segment now with: 'PROC:0 newintr$ '------------------------------------------------------------------------- 'CODE OF NEWINT CALLER:[Note that instead of INT we just call our newint] '----- prnchar$ = "" prnchar$ = prnchar$ + CHR$(&H50) 'pusha prnchar$ = prnchar$ + CHR$(&H52) prnchar$ = prnchar$ + CHR$(&HB2) + CHR$(&H45) 'mov dl,&h45 prnchar$ = prnchar$ + CHR$(&HB4) + CHR$(&H2) 'MOV AH,2 prnchar$ = prnchar$ + CHR$(&H9A) + newintoff$ + procseg$ prnchar$ = prnchar$ + CHR$(&H5A) prnchar$ = prnchar$ + CHR$(&H58) prnchar$ = prnchar$ + CHR$(&HCB) 'retf '________________________________________ Codeoff% = SADD(prnchar$) DEF SEG = VARSEG(prnchar$): LOCATE 5, 1 CALL ABSOLUTE(Codeoff%): DEF SEG '________________________________________ END SUB FUNCTION newvec& (seghandler%, offhandler%) 'While this function is again very evident, it 'clarifys the intel format of storing vectors. '----------------------------------------------- newvec& = seghandler% * 65536 + offhandler% END FUNCTION SUB nwintcallint (vector&) 'In this sub the CALLER code is setup in an integer way. 'This code will call some interrupt handler referred to as newintr. 'It is only referred to here by its vector&(a long integer in the 'form seg:off) 'This function is requesting a printstring from DOS which IS HOOKED '------------------------------------------------------------------------- 'DATA: '------ vector$ = long2str$(vector&) b$ = "Hello i have been printed by an interrupthandler$" 'This is the way DOS processes stringprint DIM datas%(LEN(b$) \ 2 + 1) dataseg% = VARSEG(datas%(0)): dataoff% = VARPTR(datas%(0)) dataseg$ = int2str$(dataseg%): dataoff$ = int2str$(dataoff%) Pokestring dataseg%, dataoff%, b$ '------------------------------------------------------------------------- 'CODE OF NEWINT CALLER:[Note that instead of INT we just call our newint] '----- prnstr$ = "" prnstr$ = prnstr$ + CHR$(&H50) 'pusha prnstr$ = prnstr$ + CHR$(&H52) prnstr$ = prnstr$ + CHR$(&H1E) prnstr$ = prnstr$ + CHR$(&HB8) + dataseg$ 'MOV AX,DATASEG$ prnstr$ = prnstr$ + CHR$(&H8E) + CHR$(&HD8) 'MOV DS,AX prnstr$ = prnstr$ + CHR$(&HBA) + dataoff$ 'MOV DX,DATAOFF$ prnstr$ = prnstr$ + CHR$(&HB4) + CHR$(&H9) 'MOV AH,9 prnstr$ = prnstr$ + CHR$(&H9A) + vector$ prnstr$ = prnstr$ + CHR$(&H1F) 'popa prnstr$ = prnstr$ + CHR$(&H5A) prnstr$ = prnstr$ + CHR$(&H58) prnstr$ = prnstr$ + CHR$(&HCB) 'retf '________________________________________ Codeoff% = SADD(prnstr$) DEF SEG = VARSEG(prnstr$): LOCATE 5, 1 CALL ABSOLUTE(Codeoff%): DEF SEG '________________________________________ END SUB FUNCTION oldvec& (nr%) '-------------------------------------------------------------------' 'this function is a replacement of getvec. it stores the old vector 'in intel format in an long integer. intel format means in the format 'segment:offset here. 'the procedure first stores the vector in es[bx], before we access it 'in: intnr% 'out: oldvec& '-------------------------------------------------------------------' s% = 1: o% = 2'variabele initialisatie dataseg% = VARSEG(s%): offset% = VARPTR(s%) datasg$ = CHR$(dataseg% AND &HFF) + CHR$(dataseg% \ 256) offset1$ = CHR$(VARPTR(s%) AND &HFF) + CHR$(VARPTR(s%) \ 256) offset2$ = CHR$(VARPTR(o%) AND &HFF) + CHR$(VARPTR(o%) \ 256) 'hieronder volgt de code in qbasic '************************************** asm$ = "" asm$ = asm$ + CHR$(&HB4) + CHR$(&H35) 'mov ah,35 asm$ = asm$ + CHR$(&HB0) + CHR$(nr%) 'mov al,intnr asm$ = asm$ + CHR$(&HCD) + CHR$(&H21) 'int 21 asm$ = asm$ + CHR$(&HB8) + dataseg$ 'mov ax,dataseg$ asm$ = asm$ + CHR$(&H8E) + CHR$(&HD8) 'mov ds,ax asm$ = asm$ + CHR$(&H8C) + CHR$(&H6) + offset1$ 'mov ptr[seg],es asm$ = asm$ + CHR$(&H89) + CHR$(&H1E) + offset2$' 'mov ptr[off],bx asm$ = asm$ + CHR$(&HCB) 'retf '____________________________ Codeoff% = SADD(asm$) DEF SEG = VARSEG(asm$) CALL ABSOLUTE(Codeoff%) '____________________________ DEF SEG oldvec& = s% * 65536 + o% END FUNCTION SUB pokeDW (pokeseg%, pokeoff%, dword&) 'This function will just poke a Dword into memory, just like 'the standard function Poke does it, with one enhancement. 'While poke needs a def seg before it we will transfer that to 'the function also! So : 'DW segment to poke word to 'DW offset to poke word to 'DD Dwordvalue to poke '--------------------------------------------------------------- DEF SEG = VARSEG(dword&) ptr% = VARPTR(dword&) LowWlowbyte% = PEEK(ptr%): LowWhighbyte% = PEEK(ptr% + 1) HighWlowbyte% = PEEK(ptr% + 2): HighWhighbyte% = PEEK(ptr% + 3) DEF SEG = pokeseg% POKE pokeoff%, LowWlowbyte% POKE pokeoff% + 1, LowWhighbyte% POKE pokeoff% + 2, HighWlowbyte% POKE pokeoff% + 3, HighWhighbyte% DEF SEG END SUB SUB Pokestring (segje%, offje%, main$) '------------------------------------------------------ 'this function pokes a string (might be asciiz)into 'memory at a given location, making it possible to 'access strings in byte form '------------------------------------------------------ DEF SEG = segje% FOR i% = 0 TO LEN(main$) - 1 POKE offje% + i%, ASC(MID$(main$, i% + 1, 1)) NEXT DEF SEG END SUB FUNCTION replace.newint$ (vector&) '----------------------------- 'EXAMPLE OF A REPLACE_HANDLER '------------------------------ 'This handler is an example of hooking an interrupt on 'print string. When another function then function 9 'is requested then the string will be printed,if the 'normal printstring is requested( function 9) then 'this handler will print only 'B'. '--------------------------------------------------------- 'DATA 'Make our vector to fit the string way of saving '------- vector$ = long2str$(vector&) '---------------------------------------------------------- 'CODE '---- '0) Preserve the registers on entrance 'push ax 'push dx 'push ds '1) Imitate an interrupt 'pushf 'cli '2) Condition to be met 'cmp ah,09 'print string ? 'jz newint 'if zero move to our newint '3) Oldint 'call vector$ 'if not then execute int 21,9 'jmp end 'and jump to the end '4) Newint 'newint start: 'popf 'original flags 'mov ah,2 'mov dl,42 'pushf 'simulating an int call again 'call vector$ 'execute druk tek b '5)Closing 'end : 'sti 'pop ds 'pop dx 'pop ax 'iret 'back to calling routine '----------------------------------- 'pusha asm$ = asm$ + CHR$(&H50) asm$ = asm$ + CHR$(&H52) asm$ = asm$ + CHR$(&H1E) 'asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) 'mov ah,9 test is oke asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&HFA) 'cli asm$ = asm$ + CHR$(&H80) + CHR$(&HFC) + CHR$(&H9)'cmp ah,09 asm$ = asm$ + CHR$(&H74) + CHR$(&H7) 'jz newint asm$ = asm$ + CHR$(&H9A) + vector$ 'call vector$ asm$ = asm$ + CHR$(&HEB) + CHR$(&HB) 'jmp end 'newint: asm$ = asm$ + CHR$(&H9D) 'popf asm$ = asm$ + CHR$(&HB4) + CHR$(&H2) asm$ = asm$ + CHR$(&HB2) + CHR$(&H42) asm$ = asm$ + CHR$(&H9C) 'pushf asm$ = asm$ + CHR$(&H9A) + vector$ 'druktek(b) 'end: asm$ = asm$ + CHR$(&HFB) 'sti 'popa: asm$ = asm$ + CHR$(&H1F) asm$ = asm$ + CHR$(&H5A) asm$ = asm$ + CHR$(&H58) 'retf: asm$ = asm$ + CHR$(&HCF) replace.newint$ = asm$ END FUNCTION