THE "ATARI ST VIRUSKILLER" PROGRAMMING TRICKS (AND TIPS) by Richard Karsmakers Over the last three years, I have been programming my "Atari ST Virus Killer" (previously known under the name "Virus Destruction Utility"). In those years, the program has evolved from a little thing to check disks for the "Signum" virus into a fairly extensive and very effective tool in the battle against viruses. I have learned programming properly in that time, too (well... not all too properly, but who cares about that?), and therefore I would like to share some of the source code of my virus killer with you, hoping to give you some interesting tips & tricks of how to program in GfA Basic. All examples quoted here are from GfA Basic 3, though adaptation to GfA Basic 2 shouldn't prove to be too difficult (but, then again, it may). ----------------- CHECK HOW MUCH MEMORY IS LEFT ----------------- Some of you may think this is done with the help of the FRE(0) command of GfA. Nothing is less true, however! FRE(0) supplies you with the amount of bytes free for programming in GfA Basic itself. Once the program has been compiled and you want to really see what amount of memory is available without actually wanting to get the figure of free Basic work space, you can use the following Gemdos call: a%=(GEMDOS(72,L:-1)) The amount of memory that's truly left will afterwards be in a% (where else had you expected it?!). ------- CHECKING WHETHER OR NOT A DISK IS WRITE-PROTECTED ------- It is very difficult to find out whether a disk in drive A or B is write-protected or not. The Operating System does not offer any functions for this, and therefore people often resort to using 'illegal' (not officially documented) system variables that are likely to change with every TOS version. I used to do exactly the same, and so did Stefan (hence the reason why ST NEWS often didn't work on STE computers - and we're not even talking about the TT!). When I was in Norway last winter, Lord HackBear gave me a better routine. It accessed the FC (Floppy Controller) directly, and seemed to work smoothly. Yet it didn't in special cases. So I had my colleague Michael Bittner at Thalion software look it over. He made it better, but it turned out not to work with two disk drives attached (not properly, at least). So, finally, another colleague by the name of Chris Jungen solved the problem and hence I can now offer you a routine that definitely works on all ST systems - although I have not tested it on the TT and it may not work there... FUNCTION wrpr(crd%) SDPOKE &H43E,-1 !Floppy operations off ~XBIOS(29,NOT (2*(crd%+1))) !Select drive SDPOKE &HFF8606,&H80 !FDC-statusregister select buf%=DPEEK(&HFF8604) !FDC-statusregister read ~XBIOS(30,2*(crd%+1)) !Deselect drive SDPOKE &H43E,0 !Enable floppy operations buf%=(buf% AND 64)/64 !Isolate WP-bit RETURN buf% !Return WP bit as function value ENDFUNC The function can be used as follows, where a% contains the device number (0=A, 1=B): IF @wrpr(a%)=1 ALERT 1,"DISK WRITE PROTECTED!",1,"SHIT",b% ELSE ALERT 1,"EVERYTHING OK!",1,"YEAH",b% ENDIF ---------------------- WAITING FOR A KEY ------------------------ In most programs, it is often required for the program to just wait. Wait until a key is pressed, or until a mouse fire button is pressed, or either. The following routine does this in quite a perfect, fool-proof way: PROCEDURE waitkey LOCAL taste$ LPOKE XBIOS(14,1)+6,0 !Clear keyboard buffer taste$="" DO taste$=INKEY$ EXIT IF MOUSEK OR taste$<>"" LOOP RETURN Since the routine only has to wait, nothing is done with the result. This result, however, is located in the variable 'taste$' so you can do whatever you want with it. If, however, you want to use the result after getting back from this subroutine, you have to get rid of the line that reads 'LOCAL taste$'. --------------------- INITIALISING A DRIVE ---------------------- When scanning through a drive or partition (or, worse, through several different floppy disks), it is possible that one of the following things happens: A) The system still 'thinks' it's got the old disk in the drive. B) A Gemdos 'file not found' or 'out of memory' error occurs. These are both 'bugs' in the Operating System, and I am lead to believe that at least B) has been discarded in TOS 1.6 (possibly even 1.4). The way to solve this is reading the directory off a disk. GEM now recognises that a new disk is there, and all internal buffers are empties like they should. However, you have to initialise the proper drive - and you don't want any stupid filenames all over the screen, do you? So what you have to do is put the drive number in 'devno%' (A=0, etc.), and then use: buf$=CHR$(65+devno%)+":SHIT.QXY" The somewhat unusual search template makes sure that no filenames occur on the screen. ----------------------- FORMATTING A DISK ----------------------- The option to format a disk is very useful to include in a program that writes files to disk; the user must be able to quickly format a disk without having to leave the program if he doesn't have some readily formatted disks handy. The following is the routine that is implemented in the "Atari ST Virus Killer". Is formats a disk single-sided and has been optimised to do just that. Flexibility is gone, and since I was too lazy to make it flexible again (sorry, folks!), I just added some comments here and there. Nothing more. PROCEDURE format LOCAL e%,t%,d% !Something to do ALERT 2,"ARE YOU SURE YOU|WANT TO FORMAT|FLOPPY IN DRIVE A|SINGLE SIDED?!",2,"YES|NO",d% IF d%=1 ;Yeah....format! screen$=SPACE$(10000) !Reserve space for format buffer e%=0 !Error variable t%=0 !First track to format DO SHOWM !See note #1 ' See note #2 e%=XBIOS(10,L:V:screen$,L:0,0,9,t%,0,1,L:&H87654321,0) IF e% !Error occurred! ALERT 1,"FORMAT ERROR!!",1,"AHEM",d% EXIT IF e% ENDIF INC t% !No error....increment track! EXIT IF t%>79 !All tracks formatted LOOP IF e%=0 !DO-LOOP left without error ' This installs the BPB in the bootsector ' Check contents of BPB in any good ST book ("ST Intern") screen$=STRING$(6,0)+MKL$(XBIOS(17))+CHR$(0)+MKI$(2)+CHR$(2) screen$=screen$+MKI$(&H100)+CHR$(2)+CHR$(112)+CHR$(0) screen$=screen$+CHR$(208)+CHR$(2)+CHR$(&HF9) screen$=screen$+MKI$(1280)+MKI$(&H900)+MKI$(256) screen$=screen$+MKI$(0)+STRING$(512,0) SHOWM !See Note #1 ' This writes the bootsector VOID XBIOS(9,L:V:screen$,L:0,0,1,0,0,1) VOID BIOS(7,0) !Get BPB ' Default (empty) FAT contents screen$=MKL$(&HF9FFFF00)+STRING$(508,0) SHOWM !See Note #1 ' Write both FATs VOID BIOS(4,1,L:V:screen$,1,1,0) VOID BIOS(4,1,L:V:screen$,1,6,0) ALERT 1,"THIS DISK NOW HAS|"+STR$(DFREE(1))+" BYTES FREE!",1,"OK",d% ENDIF ENDIF RETURN Note #1 Gemdos has a tendency to disable the mouse whenever doing disk I/O. Should an error occur during the disk I/O that causes a GEM alert box to be displayed, you will then find it very hard to select "OK" or "Cancel". This solution seems to work. Note #2 This is the actual Xbios format command. The parameters are the following: e%=XBIOS(10,L:V:screen$,L:0,0,9,t%,0,1,L:&H87654321,0) | | | | | | | | | | | | | | | | | | | Virgin word | | | | | | | | Magic longword | | | | | | | Interleave | | | | | | Side (here, only side 0) | | | | | Track number (here, 0-79) | | | | Sectors per track | | | Device (here, only A) | | Filler (any value...doesn't matter) | The pointer to the format buffer Xbios function call #10 The 'virgin word' is the word with which the track will be filled after formatting. The 'magic longword' is necessary to make the routine format at all. The 'interleave' is the sequence in which the sectors are written on a track (different interleaves with different numbers of sectors per track result in different data transfer speeds). --------- SCANNING A DRIVE/PARTITION FOR ALL ITS FILES ---------- This routine is the heart of the linkvirus scan routine, and originally written by Stefan. There were a couple of bugs and, let's say, 'unwanted skips', in the routine, so therefore I did some additional coding on it. Except for that bit of 'further coding', it's a straight port of a harddisk backup program that Stefan wrote an article about aeons ago (in ST NEWS, of course). PROCEDURE do_backup buf$=CHR$(65+devno%)+":SHIT.YXQ" !See above FILES buf$ ! " " LOCAL e$,x$,fold%,fspec$,x%,curr_path$,currdrive% ' curr_path$=DIR$(0) !Store current path IF curr_path$="" !If nothing curr_path$="\" !Root ENDIF currdrive%=GEMDOS(&H19)+1 !Current drive fold$(0)=RIGHT$(path$,LEN(path$)-2) !Get first folder name CHDRIVE ASC(LEFT$(path$))-64 !Change to it fold%=0 e$=SPACE$(13) !File name buffer ' CLS PRINT "Now checking drive..." PRINT "Press Escape to abort!" REPEAT CHDIR fold$(fold%) !First folder...and more x%=LEN(DIR$(0))+3 PRINT AT(2,7);DIR$(0);"\" !Print first bit of name ' DEC fold% !Dec folder fspec$=nomo$+"."+ext$ !Search template SHOWM !See above (Note #1) e%=GEMDOS(&H4E,L:V:fspec$,-1) !Gemdos Sfirst WHILE e%=0 !No error... ' The following checks if filename is FOLDER or VOLUME IF (PEEK(GEMDOS(&H2F)+21) AND 16)=0 AND (PEEK(GEMDOS(&H2F)+21) AND 8)=0 x$=SPACE$(13) !Clear name BMOVE GEMDOS(&H2F)+30,V:x$,13 !DTA address PRINT AT(x%,7);e$ !Print name PRINT AT(x%,7);x$;SPACE$(45) !And more... ' Here, you can DO something with the file if you want ' Insert your own routines! ENDIF BMOVE V:e$,GEMDOS(&H2F)+30,13 !Get from DTA SHOWM e%=GEMDOS(&H4F) !Gemdos Snext IF ASC(INKEY$)=27 !Escape pressed? EDIT ;Yes....quit! ENDIF WEND ' fspec$="*.*"+CHR$(0) !Search template SHOWM e%=GEMDOS(&H4E,L:V:fspec$,-1) !Sfirst WHILE e%=0 IF (PEEK(GEMDOS(&H2F)+21) AND 16) x$=SPACE$(13) BMOVE GEMDOS(&H2F)+30,V:x$,13 IF x$<>"."+CHR$(0)+SPACE$(11) AND x$<>".." +CHR$(0)+SPACE$(10) !Special folder files? INC fold% fold$(fold%)=DIR$(0)+"\"+x$ ENDIF ENDIF BMOVE V:e$,GEMDOS(&H2F)+30,13 SHOWM e%=GEMDOS(&H4F) !Snext IF ASC(INKEY$)=27 EDIT ENDIF WEND UNTIL fol<0 CHDRIVE currdrive% !Set back old drive CHDIR curr_path$ !Set back old dir RETURN ---------------------- COOKIE JAR HANDLING ---------------------- On TOS 1.6 and up, Atari implemented something called 'The Cookie Jar'. This is a reserved area in memory where certain values represent certain things. It was included for the STE and TT's sakes, so that program would have ways of getting to know which hardware they were addressing. Of course, Atari totally neglected to document this feature, but after many hours of searching and months of experiencing (and glancing over an issue of German "ST Magazine") I can now offer you some info about it. At address $5A0 (an official system variable that will not be changed any more - but then again you never know with Atari), there will be a zero or a longword pointer. If you have a zero there, this means that: A) Someone shoved a magazine into your system. B) You have a TOS version lower than 1.6, or the pre-version of TOS 1.6. If you find a longword pointer, this will be the address to find the Cookie Jar. The Cookie Jar is, strictly speaking, a collection of 32-bit values, of which two belong together each time. The first one is usually a value representing some kind of ASCII string, and the second is the actual value. Usually, this is another address. ASCII strings starting with an "_" are of Atari and should never be used by other people rather than Atari! The last Cookie is always a longword zero followed by the maximum number of cookies that can be used (only a specific amount of memory is reserved for Cookies, and you should not use more unless you want to install it all yourself again and expand it - but that's too much to tell here). That's basically all there is to know about the Cookie Jar. Its use is simple: Apart from other programs just getting parameters from it, it can also specify addresses of variable locations that can then be used by other programs. Some of the officially documented Atari Cookies are the following. First follows the longword ASCII string, and then the value(s): _CPU This is not hard to imagine. It specifies which processor is present in the system. It can be 0, 10, 20 or 30 for 68000, 68010, 68020 and 68030 respectively. _VDO Version number of the Videohardware. This is actually represented by two words. The first word is the spot in front of the command; the second the one after. 0,0 ST 1,0 STE 2.0 TT _SND A bit table that tells programs which sound possibilities they have. Only two of these 32 bits are used - but let's hope that this shall not last long (idle hopes...). Bit 0 YM soundchip (ST and STE) Bit 1 Stereo DMA sound (STE) _MCH Described the machine you use. Here, also, we're talking about two words. 0,0 260 ST, 520 ST(F)(M), 1040 ST(F)(M) 1,0 MEGA ST (Difference: Real Time Clock) 2,0 STE 3,0 TT _SWI Value of configuration switches, if present. Don't ask me what this is supposed to mean. I don't know (neither do any mortals outside of Atari Corp., I suspect). _FRB Since the TT's "Fast RAM" is not usable for DMA transfers, the BIOS of that computer creates a 64 Kb buffer area in memory of which the address can be found here. Well...that's all there is to say, really. Now, let's head for the source bit. PROCEDURE cookie LOCAL x% CLS IF LPEEK(&H5A0)<>0 !Cookie jar present! x%=LPEEK(&H5A0) !Cookie jar address DO ' This prints the hex cookie address, the cookie identification and the ' cookie value after each other PRINT HEX$(x%);" -- ";MKL$(LPEEK(x%));" -- "; HEX$(LPEEK(x%+4)) ADD x%,8 EXIT IF LPEEK(x%)=0 LOOP ELSE PRINT "No Cookie Jar present (yet)!" ENDIF RETURN ---------------- CREATING A CLEARER FILESELECTOR ---------------- In TOS versions lower than 1.4, the fileselector didn't allow any optional parameters to be supplied (like "SELECT FILE TO LOAD"). In TOS 1.4 and up, using GfA Basic 3, you can specify this using an optional string parameter of the FILESELECT command. FILESELECT "FILE TO LOAD","\*.*","MYFILE.FIL",lo$ If you belong to the enormous groups of people 'blessed' with lower TOS versions, you can use the following routine (medium and high res only). It just puts a bit above the fileselector box before it is called. This may be at the wrong place for certain alternative fileselector boxes! It is being called as follows: @fileselectmooi("FILE TO LOAD") FILESELECT "\*.*","",lo$ And this is the routine: PROCEDURE fileselectmooi(a$) DEFFILL 0,1 IF XBIOS(4)=1 !Medium res PBOX 0,0,319,40 BOX 0,0,320,26 BOX 0,3,319,23 BOX 1,4,318,22 DEFFILL 1,1 PBOX 2,5,317,21 GRAPHMODE 3 DEFTEXT ,,,6 TEXT 25,18,a$ ELSE !Other res (here: High only!) PBOX 157,20,482,60 BOX 157,20,482,54 BOX 160,23,479,51 BOX 161,24,478,50 DEFFILL 1,1 PBOX 162,25,477,49 GRAPHMODE 3 DEFTEXT ,,,13 TEXT 184,43,a$ ENDIF GRAPHMODE 1 RETURN -------------- CHECKING WHICH DRIVES ARE CONNECTED -------------- This routine has obvious uses. The only problem is that I haven't actually found ways of really checking for disk B. The system seems to think it's always present. Tough shit. Another problem is that I don't really grasp my own code any more. All I know is that, in the end, you get a variable called att$ in which you'll get e.g. "A|B|C|D|Cancel". Further, in ndr% you'll find the actual number of drives attached. I know you may find this a bit shit, but I am simply too lazy to find out what I've done again (it may not even work all on its own when not surrounded by the rest of the virus killer program). Just regard it as some kind of extra. It's always better to have something than nothing. PROCEDURE drijfbitz ' * Get drive bits and determine which drives are attached ' ad%=&H4C2 !Drivebits system variable ac$=BIN$(LPEEK(ad%)) !Get active drives buf%=LEN(ac$) IF buf%>2 !RAM-and/or harddisks attached norm!=FALSE ELSE !Only drive A or A+B attached norm!=TRUE ENDIF ' ERASE dr%() DIM dr%(buf%) !Dim an array for that y%=0 x%=0 DO IF MID$(ac$,buf%-x%,1)="1" !Drive present? IF x%>1 !Harddisk only! SHOWM IF BIOS(7,x%)>0 !Get bpb address, if>0, drive is REALLY present dr%(y%)=65+x% !Put letter in array INC y% !Next array element ENDIF ELSE dr%(y%)=65+x% !Put letter in array INC y% !Next array element ENDIF ENDIF INC x% !Next bin$ element EXIT IF x%>buf% LOOP att$="" x%=0 DO EXIT IF dr%(x%)=0 att$=att$+CHR$(dr%(x%))+"|" INC x% LOOP att$=att$+"Cancel" ndr%=x%-1 !Number of drives attached minus one RETURN -------------------- OPENING ALL THE BORDERS -------------------- Well, well. I suppose you're dying to know this, aren't you? OK. It's very simple. You load in an assembler and use the source featured in ST NEWS Volume 5 Issue 1. Hack a bit...assemble... execute...and you're there! Alas, this is slightly difficult to do in GfA Basic. If someone knows how to do it properly (full screen, that is) in 100% GfA Basic (maybe some sync scrolling on top of that), he can call me and get my ST system for free. --------------------- REVERSE TEXT DISPLAY ---------------------- To make a certain word or line of text stand out among the rest, it can be useful to reverse its display mode - i.e. the text becomes white and the block around the characters becomes black. You can use the following routines to do this: PROCEDURE on !Reverse on PRINT CHR$(27);"p"; RETURN PROCEDURE off !Reverse off IF XBIOS(4)=2 PRINT CHR$(27);"q"; ELSE PRINT CHR$(27);"q" ENDIF RETURN ---------- DISPLAYING AN ASCII TEXT FILE ON THE SCREEN ---------- In the special toolkit version of the "Atari ST Virus Killer" that I usually drag around myself, I also implemented a routine that displays an ASCII text file on screen. The skeleton of this routine stems from Stefan - who really is much smarter than me. PROCEDURE text_on_screen CLS LOCAL lo$,a% PAUSE 10 !See note #1 FILESELECT DIR$(0)+"\*.*","",lo$ IF lo$<>"" AND RIGHT$(lo$)<>"\" OPEN "I",#1,lo$ WHILE NOT EOF(#1) LINE INPUT #1,a$ PRINT a$ IF INP?(2) a%=INP(2) IF a%=32 @waitkey !Use the above routine ENDIF IF a%=27 EXIT IF TRUE ENDIF ENDIF WEND IF a%<>27 @waitkey !Use the above routine ENDIF ENDIF CLOSE #1 RETURN Note #1: When using alert boxes or item selectors in a program, it is very likely that the user still keeps the fire button of the mouse pressed when they occur. If the user would have to select to 'load a file' with the mouse and the item selector would appear instantly, the chances are big that a file will be selected in that item selector that the user didn't want. Hence a small pause to make sure that the mouse fire button is released. --------------------------- A SIGNAL ---------------------------- Sometimes, it can be really useful to give the user a signal of some kind. For optimal effect, this needs to be audio-visual, i.e. a bleep and a bit of screen flashing. To most of you, this will seem very simple to do. The screen flashing is a bit more difficult than you may think, though - if you want to do it extremely legally, that is. PROCEDURE scr LOCAL buf% buf%=XBIOS(7,0,-1) !Get value of color #0 buf%=(buf% AND &HFFFF) !Isolate proper word a%=0 !Initiate counter WHILE a%<6 !Not yet six times? IF XBIOS(4)=2 !High res SETCOLOR 0,&H101 !Invert PAUSE 2 !Wait a bit SETCOLOR 0,&H100 !Normal again ELSE !Low res SETCOLOR 0,&H777 !White PAUSE 2 !Wait a bit SETCOLOR 0,0 !Black ENDIF INC a% !Next time PAUSE 2 !Wait a bit PRINT CHR$(7); !Bell sound!! WEND SETCOLOR 0,buf% !Old color #0 back RETURN These were the tips and tricks for this issue. Bye for now!