    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!
