home *** CD-ROM | disk | FTP | other *** search
/ Fujiology Archive / fujiology_archive_v1_0.iso / !MAGS / INSIDENF / IINFO54.ZIP / IINFO54.MSA / TEXT_CMDMENTS.TXT < prev    next >
Text File  |  1991-08-29  |  26KB  |  585 lines

  1.  
  2.   A Programmer's Eleven Commandments for Coexistent Vector Stealing
  3.   -----------------------------------------------------------------
  4.  
  5.    Or, Tried and True Techniques Used by the CodeHeads for Successfully
  6.    Intercepting Vectors in the Midst of Numerous ST Vector Thieves.
  7.  
  8.    Copyright 1990 John Eidsvoog and Charles F. Johnson
  9.                   (CodeHead Software)
  10.  
  11.    Last revised: Wednesday, February 14, 1990  5:47:44 pm
  12.  
  13.  
  14.   We have prepared this document in the interest of attaining and
  15. furthering compatibility between resident programs and accessories for
  16. the Atari ST.  Since the TOS operating system has no provisions for
  17. managing its interrupt and trap vectors, ST developers who need to
  18. intercept these vectors are forced to use the "trial and error" system
  19. to determine what works.
  20.  
  21.   This is a very dangerous situation.  More and more programs are
  22. appearing which enhance the ST's GEM operating system by patching into
  23. the vectors which handle system calls.  Many of these programs work
  24. perfectly as long as no other resident programs are used, or as long as
  25. certain combinations of programs are used.  But when these programs are
  26. released into the "real world," the conflicts quickly start showing up.
  27.  
  28.   At CodeHead Software we've encountered more than our share of these
  29. types of problems, since almost all of our commercial products intercept
  30. one or more of the ST's system vector(s).  From this boiling witches'
  31. brew of potential pitfalls, we've managed to distill some pragmatic
  32. methods that can alleviate most, if not all of the conflicts.
  33.  
  34.   If you follow these guidelines when programming Atari ST applications
  35. which require the interception of system vectors, you will be compatible
  36. with _most_ of the programs currently in use.  At the very least, your
  37. code will be compatible with all of the CodeHead Software products.  If
  38. any program has general compatibility problems with other resident
  39. programs or accessories, it's very likely that the offending program is
  40. breaking one of the following Eleven Commandments:
  41.  
  42.  
  43. --------
  44.   I.
  45. --------
  46.  
  47. Always fall through to the previous address when your routine has
  48. completed its function.  (The only exception to this rule is if your
  49. code replaces an entire system call; in this case, you'll probably want
  50. to terminate your routine with an RTE.  Be aware that if you do this,
  51. any program which was previously installed in that vector will not "see"
  52. this call come through.)  The "fall through" can be accomplished by
  53. storing the previous vector address two bytes past a JMP instruction;
  54. this approach solves any possible problems with pushing the return
  55. address on the stack (see Commandment V below), or destroying an address
  56. register to do an indirect JMP. 
  57.  
  58. There are some cases where it doesn't make sense to fall through to a
  59. previous routine, such as when you replace the Alt-Help vector which
  60. performs a screen dump.  Even here, however, it's a good idea to make
  61. allowances for other programs which may use the Alt-Help vector for
  62. purposes other than a screen dump...such as the Templemon and AMON
  63. debuggers.  AMON avoids conflicts with other programs in the Alt-Help
  64. vector by requiring the user to press the left shift key in addition to
  65. Alternate and Help.
  66.  
  67. Another special case where falling through makes no sense is the ST's
  68. vertical blank queue list, which allows you to install a routine to be
  69. executed as a subroutine from the main system VBI.  There are eight
  70. entries in the default queue list, and the correct way to install a
  71. routine in one is to search the list for a zero longword.  When your VBI
  72. queue routine is finished, it may remove itself by clearing its entry in
  73. the list.  (This is why it makes no sense to fall through to a previous
  74. queue entry -- that entry should have been zero when you grabbed it.)
  75. Even this mechanism is subject to abuse, however; an unfortunate number
  76. of programs simply stuff an address into one of the queue slots, without
  77. checking first to see if that slot has been taken.  (A good example of
  78. this kind of vector abuse is the first version of STARTGEM.PRG.)
  79. Remember: when using the vertical blank queue list, always search the
  80. list for a zero entry in which to install your routine. 
  81.  
  82. With more and more programs appearing that replace entire operating
  83. system functions, compatibility is going to become even more
  84. problematic.  For example, clashes will occur because Program A needs to
  85. "see" a certain call being made, but Program B is intercepting the call,
  86. handling it, and returning to the caller.  In this scenario, Program A
  87. will just stop doing anything since it will never see the call for
  88. which it's watching.  Keep this in mind when you're writing code
  89. intended to replace an entire system call; and be sure to test your code
  90. with as many other resident vector-grabbers as possible.
  91.  
  92.  
  93. --------
  94.   II.
  95. --------
  96.  
  97. Never replace a vector after grabbing it, unless you're in a controlled
  98. situation where there is no chance that another program could intercept
  99. the same vector and fall through to your code.  Here's an example of
  100. what can go haywire if you do replace a vector at the wrong time:
  101.  
  102. An early public domain ST program had a feature to select DESKTOP.INF
  103. files for different resolutions.  The program grabbed the trap #1 vector
  104. (GEMDOS) and then used the Ptermres() call to make itself resident. 
  105. Then, as a resident program, it monitored all GEMDOS calls, looking for
  106. the Fopen() call for the filename DESKTOP.INF.  When that call was
  107. detected, the program replaced the system's filename ("DESKTOP.INF")
  108. with either LOW.INF, MEDIUM.INF, or HIGH.INF depending on the current
  109. resolution.  Then it made the big mistake -- to remove itself, our
  110. example program took the address that it originally found in the trap #1
  111. vector (when it first ran) and stored it back into the vector. 
  112.  
  113. Why is this such a big mistake?  Because other programs that can run
  114. AFTER our example program may also need to grab the trap #1 vector.  If
  115. this happens, the next program to install itself in trap #1 will be CUT
  116. OUT of the chain of fall-throughs when our example program replaces the
  117. vector.  If you're lucky, the only ill effect will be that one of your
  118. TSR's will suddenly stop working.  If you're unlucky, the system will
  119. crash or hang.  (It all depends on what the program that got cut out of
  120. the chain was doing with that vector.)
  121.  
  122. Oh, and by the way, our unnamed example program has since been updated
  123. to fix this thorny problem.  The fix was simple; the program now remains
  124. in the trap #1 vector after replacing the system's DESKTOP.INF filename.
  125. After doing its job, the code does nothing but fall through to the
  126. previous vector. 
  127.  
  128. If you are a resident program and you want to remove yourself, do it by
  129. setting a flag to bypass your code and fall through (see Commandment I.)
  130. Remember that some other program may run after yours and grab the same
  131. vector; in this case, the other program will be falling through to your
  132. code.  If you remove yourself by replacing the original vector address,
  133. you'll also be removing everything else that ran after you.
  134.  
  135.  
  136. --------
  137.   III.
  138. --------
  139.  
  140. Don't use a "magic cookie" (the infamous Diablo emulator mistake).  That
  141. is, if you are trying to find another program (or yourself), don't look
  142. for a "magic" word near the address in the vector that the program
  143. steals.  This technique will fail as soon as some other program grabs
  144. the same vector; and this is exactly how the Diablo emulator (for the
  145. SLM804 laser printer) breaks.  The Diablo emulator consists of two
  146. separate programs -- one that goes in an AUTO folder (the emulator code
  147. itself), and a configuration program that installs as a desk accessory. 
  148. The AUTO program grabs the BIOS vector, so that it can redirect printer
  149. output to the laser via the DMA port.  The desk accessory configuration
  150. program tries to find the AUTO program (every time it's activated) by
  151. looking for a "magic cookie" stored by the AUTO program in the location
  152. immediately before its BIOS interception code.  Problem: if another
  153. program intercepts the BIOS vector AFTER the Diablo emulator AUTO
  154. program, the configuration accessory is unable to find the AUTO program
  155. (because the "magic cookie" is not where the accessory thinks it should
  156. be). 
  157.  
  158. There are a number of ways to reliably find another program.  One of the
  159. easiest is to make a "fake" call to one of the trap routines with an
  160. undefined function code.  The ST's BIOS and XBIOS will ignore calls with
  161. undefined function codes, and simply return with no ill effects if the
  162. program you're searching for is not present.  We suggest using unusual
  163. function codes, such as $4857 (for example), so that your code will not
  164. conflict with future additions to the BIOS or XBIOS functions.  The
  165. receiving program can then return whatever kind of information you need
  166. from it (you've got lots of registers to use). 
  167.  
  168. Here's an example (in assembly language) of some code that uses an
  169. undefined BIOS call to detect the presence of another program:
  170.  
  171. *-------------------------------------------------------------------
  172. *
  173. * The "target" program (the program being searched for) must intercept
  174. * the trap #13 vector and examine the stack after each trap #13 call
  175. * to see if the magic word function number is present.  If it is, the
  176. * target program should load the return value into d5 and perform an RTE.
  177. *
  178.         moveq   #0,d5           ; Clear d5 in preparation
  179.         move    #$4857,-(sp)    ; Magic word - undefined BIOS call
  180.         trap    #13             ; Call BIOS
  181.         addq    #2,sp           ; Correct the stack
  182.  
  183.         tst.l   d5              ; If d5 is still zero, we didn't find anyone
  184.         beq.s   notfound        ; If non-zero, it's a returned value
  185.  
  186.         move.l  d5,returned     ; Save the returned value somewhere
  187.  
  188. *-------------------------------------------------------------------
  189.  
  190. (NOTE: The version of TOS (1.6) that will be supplied with Atari's STE
  191. and TT machines has a new feature called the "Cookie Jar," which does
  192. not suffer from the problems described here.  It provides a documented
  193. address where programs can search for "magic cookies"; it's a nice
  194. solution.  Our only complaint with the "Cookie Jar" is that we wish it
  195. had been implemented three years ago.)
  196.  
  197.  
  198. --------
  199.   IV.
  200. --------
  201.  
  202. Do not try to monitor and maintain a vector from a vertical blank or
  203. other timed interrupt (in other words, don't keep watching it and
  204. replacing it if it changes).  Think for a moment about what happens
  205. if two programs do this at the same time.  (Ouch.)  This extremely bad
  206. practice may seem to work when no other programs are using the same
  207. vector, but you will definitely have coexistence problems down the road. 
  208. Don't do it.
  209.  
  210.  
  211. --------
  212.   V.
  213. --------
  214.  
  215. Do not use the (system) stack from an interrupt or trap vector.  There
  216. is _very_ little stack headroom available in the location used by the
  217. operating system.  A system stack overflow will cause crashes that can
  218. be extremely difficult to diagnose. 
  219.  
  220. If you need to save registers during some vector-handling code, it's
  221. best to save them in a location in your own program, instead of on the
  222. system stack.  For example:
  223.  
  224. *-------------------------------------------------------------------
  225.  
  226.         movem.l d0-a6,-(sp)    ; Don't do this!
  227.  
  228. *-------------------------------------------------------------------
  229.  
  230.         movem.l d0-a6,regsave  ; Do this instead.
  231.  
  232. *-------------------------------------------------------------------
  233.  
  234.  
  235. --------
  236.   VI.
  237. --------
  238.  
  239. Always restore all registers and the status register when your routine
  240. is finished.  Don't even assume that you can destroy D0 or A0 because
  241. some programs (believe it or not) actually rely on them to return from a
  242. trap unchanged.  (The exceptions to this rule are the BIOS and XBIOS
  243. vectors; the dispatching routines for these vectors always trash
  244. register A0, so it's safe to use A0 in a BIOS or XBIOS routine without
  245. saving it.)
  246.  
  247.  
  248. --------
  249.   VII.
  250. --------
  251.  
  252. Don't alter the processor state.  That is, don't 'rte' into your own
  253. code in order to be in USER mode because other programs down the line
  254. may expect the machine to be in SUPERVISOR mode.
  255.  
  256.  
  257. --------
  258.   VIII.
  259. --------
  260.  
  261. When intercepting frequently called traps (such as trap #2), always use
  262. optimized assembly language routines to eliminate a slowdown in system
  263. operation.  Don't make the "GDOS mistake". 
  264.  
  265.  
  266. --------
  267.   IX.
  268. --------
  269.  
  270. Never assume something simply because it always "seems to be."  This
  271. includes using "hard" addresses specific to a particular ROM, assuming
  272. that certain vectors will be pointing to ROM routines, assuming that 8
  273. bytes into the GEM base page is pointing into the OS, or making _any_
  274. decision based on an empirical condition. 
  275.  
  276.  
  277. --------
  278.   X.
  279. --------
  280.  
  281. Use the source code provided below for maintaining the trap #2 vector
  282. from a resident program.  This somewhat oblique method is required
  283. because the operating system stuffs its own address into the trap #2
  284. vector (with no regard for what is there) after running a TOS program,
  285. and possibly at other times as well.  (Yes, we are aware that this
  286. routine breaks Commandment IX.)  The routine which handles trap #13 in
  287. this code also demonstrates a method to remain compatible with
  288. 68010/68020/68030 processors, by checking a new BIOS variable Atari has
  289. documented. 
  290.  
  291.  
  292. --------
  293.   XI.
  294. --------
  295.  
  296. Commandment XI may be the most difficult one to follow.  Have the
  297. wisdom to know when it's necessary to break any of the other
  298. commandments, and the responsibility to think through the
  299. consequences if you do.  Some of these rules should _never_ be
  300. broken; others can be bent once in a while, as long as you
  301. carefully consider all the ramifications.  Above all, just as in
  302. any other endeavor, you have to learn the rules and understand the
  303. reasons for their existence before you can get away with breaking
  304. them.
  305.  
  306.  
  307.  
  308. *****************************************
  309. *                                       *
  310. *  Intercept the trap #2 vector         *
  311. *                                       *
  312. *  Code by Charles F. Johnson           *
  313. *                                       *
  314. *  Includes ideas, techniques and       *
  315. *  refinements by Bob Breum,            *
  316. *  Chris Latham, and John Eidsvoog      *
  317. *                                       *
  318. *  Last revision: 06/26/88  12:13:32    *
  319. *                                       *
  320. *****************************************
  321.  
  322.         .TEXT
  323.  
  324. * ------------------------
  325. *  Program initialization
  326. * ------------------------
  327.  
  328.         move.l  #prog_end,d6    ; Get address of end of this program
  329.         sub.l   4(sp),d6        ; Subtract start of basepage - save in d6
  330.  
  331.         move.l  #not_auto,addrin ; Try to do an alert box
  332.         move    #1,intin
  333.         move.l  #f_alrt,aespb
  334.         move.l  #aespb,d1
  335.         move    #$C8,d0
  336.         trap    #2
  337.         tst     intout          ; If intout is zero, we're in \AUTO
  338.         beq.s   .start1
  339.  
  340.         cmp     #1,intout       ; Install?
  341.         beq.s   .0              ; Yes, continue
  342.  
  343.         clr     -(sp)           ; Pterm0
  344.         trap    #1              ; outta here
  345.  
  346. .0:     pea     prg_start(pc)   ; Steal trap #2 right away if run from desktop
  347.         move    #38,-(sp)       ; Supexec
  348.         trap    #14
  349.         addq    #6,sp
  350.  
  351.         move    #1,prgflg       ; Set flag indicating desktop load
  352.         bra.s   .start2
  353.  
  354. .start1:
  355.         pea     title           ; Print title message
  356.         move    #9,-(sp)
  357.         trap    #1
  358.         addq    #6,sp
  359.  
  360. .start2:
  361.         dc.w    $A000           ; Don't you just love Line A?
  362.         move.l  a0,line_a       ; Save the address of the Line A variables
  363.  
  364.         pea     set_bios(pc)    ; Appropriate the Trap #13 vector
  365.         move    #38,-(sp)
  366.         trap    #14
  367.         addq.l  #6,sp
  368.  
  369.         clr.w   -(sp)           ; Terminate and Stay Resident
  370.         move.l  d6,-(sp)        ; Number of bytes to keep
  371.         move    #$31,-(sp)      ; That's all folks!
  372.         trap    #1              ; We are now happily resident in RAM
  373.  
  374. * -------------------------------
  375. *  Desktop vector initialization
  376. * -------------------------------
  377.  
  378. prg_start:
  379.         move.l  $88,t2_vec      ; Set my fall throughs
  380.         move.l  $88,aesvec
  381.         move.l  #my_trap2,$88   ; Steal trap #2 (GEM)
  382.         rts
  383.  
  384. * -----------------------
  385. *  Steal the BIOS vector
  386. * -----------------------
  387.  
  388. set_bios:
  389.         move.l  $B4,t13adr      ; Set Bios fall through
  390.         move.l  #my_t13,$B4     ; Steal trap #13 (BIOS)
  391.         rts
  392.  
  393. * ------------------------
  394. *  Trap #13 wedge routine
  395. * ------------------------
  396.  
  397. my_t13:
  398.         btst    #5,(sp)         ; Was the trap called from super or user mode?
  399.         beq.s   t13_ex          ; If from user mode, bail out
  400.  
  401.         lea     6(sp),a0        ; Pointer to function code on stack
  402.  
  403.         tst     $59E            ; See what _longframe has to tell us
  404.         beq.s   notlng          ; If _longframe is zero, it's a 68000
  405.  
  406.         lea     8(sp),a0        ; Advance past the vector offset word
  407.  
  408. ***  This section is based on the assumption that the OS always calls
  409. ***  BIOS setexec() immediately after obnoxiously grabbing back the trap
  410. ***  #2 vector with no warning whatsoever.  Yes, this is an empirical
  411. ***  condition, which violates Commandment IX.  (But there's no other
  412. ***  way to prevent that no-good, thieving TOS from ripping off the
  413. ***  vector while you aren't looking.)
  414.  
  415. notlng: cmp.l   #$050101,(a0)   ; Setexec call for critical error vector?
  416.         bne.s   t13_ex          ; Nope, exit
  417.  
  418.         tst     prgflg          ; On the desktop? Or are vectors already set?
  419.         beq.s   first_time      ; No, skip ahead
  420.  
  421. do_crit:
  422.         move.l  #my_trap2,$88   ; Pilfer trap #2
  423.         move.l  $404,d0         ; Get current crit vector
  424.         move.l  4(a0),d1        ; Get address we're setting it to
  425.         bmi.s   t13_x1          ; If minus, return old vector in d0
  426.         move.l  d1,$404         ; Set that vector
  427. t13_x1: rte                     ; We only get here if we're last in the chain
  428.  
  429. first_time:
  430.         tst.l   4(a0)           ; Reading the vector?
  431.         bmi.s   t13_ex          ; Yes, let the system take care of it
  432.  
  433.         move.l  $4F2,a1         ; Get address of OS header (could be in RAM)
  434.         move.l  8(a1),a1        ; Get pointer to base of OS from header
  435.         cmp.l   4(a0),a1        ; Is the crit error routine below the OS?
  436.         bhi.s   t13_ex          ; Yes, bail out
  437.         move.l  $14(a1),a1      ; Get address of end of OS (GEMDOS parm block)
  438.         cmp.l   4(a0),a1        ; Is it above the OS?
  439.         blo.s   t13_ex          ; Yes, exit stage left
  440.  
  441. ***  This is a very important part of the code.  In order to maintain the
  442. ***  correct vector chaining order when running at \AUTO time, it's necessary
  443. ***  that each program first fall through to the BIOS and RETURN TO ITS OWN
  444. ***  CODE, grabbing the trap #2 vector on the way back.  This way, the order
  445. ***  that each program intercepts trap #2 is the same as the order in which
  446. ***  they run from the AUTO folder.
  447.  
  448.         move    #1,prgflg       ; Set the 'first-time'/'desktop' flag
  449.         move.l  2(sp),retsav    ; Save return address
  450.         move.l  #t13_2,2(sp)    ; Replace it with my own
  451. t13_ex: jmp     $DEADBEEF       ; Go to the Bios and come back,
  452. t13adr  =       t13_ex+2        ;   maintaining the correct chaining order
  453.  
  454. t13_2:  bsr     prg_start       ; Grab the trap #2 vector on the way back
  455.         move.l  retsav(pc),-(sp) ; And return to the caller
  456.         rts
  457.  
  458. retsav: dc.l    0
  459.  
  460. *--------------------------------------------------------------------------
  461.  
  462. The techniques described here have worked successfully for us, both in
  463. our CodeHead Software products and our individual projects.  However, we
  464. do not wish to appear as the final and absolute authorities on this
  465. subject.  If you can find any flaws in our scheme, or perhaps enlighten
  466. us with a more efficient trick, we can be easily reached.  The quickest
  467. way to get a reply is to leave a message in the CodeHead Category (#32)
  468. on GEnie or leave GEnie mail to C.F.JOHNSON or J.EIDSVOOG1.  You may
  469. also call CodeHead Software at (213) 386-5735.
  470.  
  471.  
  472. <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
  473.  
  474. NOTES ON THE GERMAN "XBRA" PROTOCOL
  475.  
  476. For quite some time we've been hearing rumors about a new "standard"
  477. protocol devised in Germany, which supposedly can prevent some of the
  478. problems with conflicting vector-grabbers.  It's called the "XBRA"
  479. protocol -- here's how it works:
  480.  
  481. When a program needs to intercept a trap or interrupt vector, it should
  482. put the previous vector address four bytes before the beginning of its
  483. routine, preceded by two longwords.  The first longword before the
  484. address should be a unique identification code for your application. 
  485. The second longword before the previous vector address should be the
  486. magic longword "XBRA" ($58425241).  So, in assembly language, the code
  487. would look something like:
  488.  
  489. *----------------------------------------------------------------
  490.  
  491.         dc.l    'XBRA'  ; Magic longword signifying XBRA protocol
  492.         dc.l    'BRAT'  ; Unique (hopefully) 4-byte ID
  493. oldvec: dc.l    0       ; Put the previous vector address here
  494.  
  495. my_vector_routine:      ; Your vector-handling code starts here
  496.  
  497. *----------------------------------------------------------------
  498.  
  499. In order for this protocol to really work, the vector interception code
  500. should also use the previous vector address stored in the XBRA structure
  501. to fall through to the previous routine.  This way, if it's necessary to
  502. restructure the fall-through chain, any vector interception code will
  503. automatically start falling through to the new address.
  504.  
  505. *----------------------------------------------------------------
  506.  
  507.         move.l  oldvec(pc),-(sp)  ; One way to fall through to the
  508.         rts                       ; address in an XBRA structure
  509.  
  510. *----------------------------------------------------------------
  511.  
  512.         move.l  oldvec(pc),jump+2 ; Another way to fall through:
  513. jump:   jmp     $ADEADBEE         ; by modifying a JMP instruction.
  514.                                   ; This uses more memory, and may
  515.                                   ; not work on a 68030 (without
  516.                                   ; tweaking), but it doesn't use
  517.                                   ; the system stack.
  518. *----------------------------------------------------------------
  519.  
  520. The main use of XBRA seems to be to allow programs to unhook themselves
  521. from a vector chain; it provides a method whereby programs can walk
  522. through the chain of vectors, unhook themselves (or unhook other
  523. programs!) if necessary, and even restructure the whole chain.  Again,
  524. it would have been nice if the XBRA protocol were proposed three years
  525. ago; if even one program in the chain is not following XBRA, the whole
  526. scheme is useless.  And since there are _many_ programs that don't use
  527. XBRA, the scheme is of little use in the real ST world at the present. 
  528.  
  529. Still, it doesn't take much effort to implement the XBRA protocol, so it
  530. may be a good idea to use it in any future vector-grabbing programs.  If
  531. all programs used XBRA, _some_ of the problems with conflicting vector
  532. thieves could be eased.  (Why does XBRA remind us of Esperanto, the
  533. United Nations-sponsored "international language" that was going to make
  534. it possible for all mankind to live in peace?)
  535.  
  536. (NOTE: In our opinion the XBRA protocol could be improved, by adding a
  537. JMP instruction to the XBRA structure immediately before the previous
  538. vector address.  If the structure looked like this:
  539.  
  540. *----------------------------------------------------------------
  541.  
  542.         dc.l    'XBRA'
  543.         dc.l    'BRAT'
  544. jump:   dc.w    $4EF9    ; 680x0 absolute JMP instruction
  545. oldvec: dc.l    0        ; Put the previous vector address here
  546.  
  547. my_vector_routine:       ; Your vector-handling code starts here
  548.  
  549. *----------------------------------------------------------------
  550.  
  551. then a program could simply branch to the label "jump" to fall through
  552. to the previous vector-handling routine.
  553.  
  554. We must _emphasize_, however, that this is merely an observation on our
  555. part.  Don't use this suggested extension to XBRA in your code, since
  556. the XBRA protocol does NOT support it as of this date.)
  557.  
  558. It should be pointed out that XBRA is not a panacea; the "Eleven
  559. Commandments" we've outlined here are still valid, even if you do employ
  560. the XBRA protocol in your code.  In fact, since so many programs already
  561. exist that do not use XBRA, it's even more important not to rely on the
  562. XBRA protocol to solve your problems for you.
  563.  
  564. <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
  565.  
  566.  
  567.       ***********************************************************
  568.       *                                                         *
  569.       *  This document is Copyright 1990 CodeHead Software.     *
  570.       *  All Rights Reserved.                                   *
  571.       *                                                         *
  572.       *  May be freely distributed as long as this ASCII text   *
  573.       *  file is complete and unaltered in any way.  This       *
  574.       *  document MAY NOT be reprinted or used for commercial   *
  575.       *  purposes without express written permission from       *
  576.       *  CodeHead Software.                                     *
  577.       *                                                         *
  578.       *  If you wish to reprint this document, contact us at    *
  579.       *  the phone number given above for permission.           *
  580.       *                                                         *
  581.       ***********************************************************
  582.  
  583.                     <<<<*>>>><<<<*>>>><<<<*>>>>
  584.  
  585.