home *** CD-ROM | disk | FTP | other *** search
/ ProfitPress Mega CDROM2 …eeware (MSDOS)(1992)(Eng) / ProfitPress-MegaCDROM2.B6I / UTILITY / FILE / ATOB11.ZIP / ATOB.ASM next >
Encoding:
Assembly Source File  |  1990-09-07  |  26.1 KB  |  789 lines

  1. ;*************************************************************************
  2. ;
  3. ; atob.asm:  Fast assembly-language version of atob.c
  4. ;            Version 1.1
  5. ;
  6. ; atob converts files from ascii to binary, undoing the encoding
  7. ; of btoa.
  8. ;
  9. ; USAGE:
  10. ;               atob output.fil <input.fil
  11. ;
  12. ; DESCRIPTION:
  13. ;
  14. ; atob reads its stdin and sends its output to the designated output file.
  15. ; If it already exists, the output file is overwritten *without*warning*.
  16. ;
  17. ; The encoding is performed by the program btoa.  The btoa/atob encoding
  18. ; has a 25% expansion rate (as opposed to 33% by uudecode).  Simple error
  19. ; checking is performed at the end to confirm that the entire file was
  20. ; decoded correctly.  No attempt to localize the error is made.  Btoa/atob
  21. ; is suitable only for ASCII transmission and hence is not appropriate for
  22. ; USENET transmission of binaries.
  23. ;
  24. ; I use btoa/atob to transfer binary files from a UNIX host to my IBM PC
  25. ; over a seven-bit channel, using the command line
  26. ;
  27. ;           btoa < desired.fil | kermit -s -
  28. ;
  29. ; receiving the file in MS-Kermit, then decoding it on my IBM PC.  This is
  30. ; faster than sending the binary directly since kermit uses eighth-bit quoting
  31. ; which essentially means that it takes 50% longer to transmit binaries
  32. ; as compared to ASCII files.
  33. ;
  34. ; I have used this program to decode dozens of binaries (including some
  35. ; ridiculously huge ones) and they all decoded fine, so I'm pretty certain
  36. ; that the bugs have been stomped out.  If you find one, contact me at the
  37. ; email address below.
  38. ;
  39. ; SPEED:
  40. ;
  41. ; rjb = Ray Berry's version of atob (1/21/89)
  42. ; v10 = Version 1.0 of my atob
  43. ; v11 = Version 1.1 of my atob
  44. ;
  45. ; Time to decode Ralf Brown's interrupt list (inter490.zoo) on a PC-XT:
  46. ;
  47. ; rjb = 1583 seconds (26 min 23 sec)
  48. ; v10 =  113 seconds ( 1 min 53 sec) Speedup: 14x
  49. ; v11 =   70 seconds ( 1 min 10 sec) Speedup: 22x   [1.6x faster than v10]
  50. ;
  51. ;
  52. ; ENCODING:
  53. ;
  54. ; Four bytes from the input are viewed as a 32-bit integer and converted
  55. ; to base 85.  "!" represents zero, the double-quote represents 1, and
  56. ; so on up to "u" representing 84.  As a special case, the 32-bit number
  57. ; zero is represented by the single character "z".  The file is headed
  58. ; by the string "xbtoa Begin" and is followed by
  59. ;
  60. ;       xbtoa End N Clen Clen E Ceor S Csum R Crot
  61. ;
  62. ; where Clen is the length of the encoded file (first in decimal, then
  63. ; in hex) and Ceor, Csum and Crot are three checksums (encoded in hex).
  64. ; Csum is Clen plus the sum of the characters.  Ceor is the exclusive-or
  65. ; of all the characters, and Crot is a checksum computed via the formula
  66. ;
  67. ;    Crot = (Crot rotated left one bit) + next_character
  68. ;
  69. ; These are crude checksums (not as robust as, say, CRC).
  70. ;
  71. ; IMPLEMENTATION:
  72. ;
  73. ; The program is pretty much a straightforward implementation of the
  74. ; decoding algorithm in assembly.  Of course, it is rather heavily
  75. ; hand-optimized.  In particular, SI and DI are used as global register
  76. ; values and inline macros are used rather frequently.  The only room
  77. ; for improvement I can think of is replacing the multiply-by-85
  78. ; with a shift-and-add algorithm.  (Which might even be slower on
  79. ; the 80386, whose multiplication algorithm has been pretty well-
  80. ; optimized.)
  81. ;
  82. ; HOW TO MAKE IT:
  83. ;
  84. ;       If you have TLINK           If you have MS's LINK
  85. ;       -----------------           ---------------------
  86. ;       masm atob;                  masm atob;
  87. ;       tlink atob;                 link atob;
  88. ;
  89. ; DISCLAIMER/COPYRIGHT:
  90. ;
  91. ; As usual, the author claims no responsibility for the behavior of
  92. ; the program, although he is pretty sure that it works fine.
  93. ;
  94. ; The program remains Copyright 1990 by Raymond Chen, but I make
  95. ; no attempt to restrict distribution in any form.  Just don't try
  96. ; to pass it off as your own.  This copyright is of dubious legal
  97. ; significance since the string "Copyright" appears nowhere in the
  98. ; binary.  I don't care.  If you want to violate my copyright, there
  99. ; isn't too much I can do to stop you.
  100. ;
  101. ; AUTHORSHIP:
  102. ;
  103. ; The program was written by Raymond Chen (raymond@math.berkeley.edu)
  104. ; in January 1990 or thereabouts.
  105. ;
  106. ; And now... The code:
  107.  
  108.         name    atob
  109.  
  110. ;*************************************************************************
  111. ; General equates
  112. ;*************************************************************************
  113.  
  114. DOSvec  equ     21h     ; DOS interrupt service vector
  115.  
  116. stdin   equ     0       ; DOS file handle
  117.  
  118. print   equ     9       ; Print a string to the console
  119. creat   equ     3ch     ; Create a new file
  120. close   equ     3eh     ; Close a file
  121. read    equ     3fh     ; Read from a handle
  122. write   equ     40h     ; Write to a handle
  123. exit    equ     4ch     ; End the process
  124.  
  125. openflg equ     20h     ; read-only, deny write
  126.  
  127. cr      equ     0dh
  128. lf      equ     0ah
  129.  
  130. bufsiz  equ     10240 ; size of I/O buffers.
  131.                       ; five times it must be less than 64K.
  132.  
  133.                       ; 5 = 4 + 1.  1 = a copy of the incoming data.
  134.                       ;             4 = each "z" codes four output bytes.
  135.                       ; worst case expansion is when somebody btoa's a
  136.                       ; file consisting entirely of zeros.
  137.  
  138. ;*************************************************************************
  139. ; Global register assignments:
  140. ;
  141. ;  SI = where to get the next byte from the input buffer
  142. ;  DI = where to send next byte to the output buffer
  143. ;  BP = What byte number within a block of four? (bcount)
  144. ;*************************************************************************
  145.  
  146. ;*************************************************************************
  147. ; Segmentation nonsense.
  148. ;*************************************************************************
  149.  
  150. _TEXT   segment byte public 'CODE'
  151. _TEXT   ends
  152.  
  153. _DATA   segment word public 'DATA'
  154. _DATA   ends
  155.  
  156. _BSS    segment word public 'BSS'
  157. _BSS    ends
  158.  
  159. _BSSEND segment byte public 'STACK'
  160. _BSSEND ends
  161.  
  162. _STACK  segment stack 'STACK'
  163.         dw      64 dup (?)
  164. _STACK  ends
  165.  
  166. DGROUP  GROUP   _DATA, _BSS, _BSSEND
  167.  
  168. ;*************************************************************************
  169. ; Initialized Global Variables
  170. ;*************************************************************************
  171. _DATA   segment word public 'DATA'
  172.  
  173. ClenL   dw      0       ; Length of converted file
  174. ClenH   dw      0
  175.  
  176. Ceor    label   byte    ; Checksum via exclusive or
  177. CeorL   dw      0
  178. CeorH   dw      0
  179.  
  180. Csum    label   dword   ; Checksum via summation
  181. CsumL   dw      0       ;   Low word
  182. CsumH   dw      0       ;   High word
  183.  
  184. Crot    label   dword   ; Checksum via rotation
  185. CrotL   dw      0       ;   Low word
  186. CrotH   dw      0       ;   High word
  187.  
  188. _DATA   ends
  189.  
  190. ;*************************************************************************
  191. ; Uninitialized Global Variables
  192. ;*************************************************************************
  193. _BSS    segment word public 'BSS'
  194.  
  195. fdout   label   word
  196.         dw      1 dup (?)       ; File handle for output
  197.  
  198. numbuf  label   byte
  199.         db      10 dup (?)      ; Numbers go here for decoding
  200.  
  201. inbuf   label   byte
  202.         db      bufsiz dup (?)  ; file input buffer
  203.         db      1 dup (?)       ; the extra byte is for a sentinel
  204.  
  205. outbuf  label   byte
  206.         db      4*bufsiz dup (?); file output buffer
  207.  
  208. _BSS    ends
  209.  
  210. ;*************************************************************************
  211. ; Macros
  212. ;*************************************************************************
  213.  
  214. ;*************************************************************************
  215. ; makestr:  Create a string with the specified label.
  216. ;           The optional third argument receives the length of the string.
  217. ;*************************************************************************
  218. makestr macro   l, s, c
  219. _DATA   segment word public 'DATA'
  220. l       db      s
  221.         ifnb    <c>
  222. c       equ     $-l
  223.         endif
  224. _DATA   ends
  225.         endm
  226.  
  227. ;*************************************************************************
  228. ; die:  Print a message and terminate the program.
  229. ;*************************************************************************
  230. die     macro   msg
  231.         local   l
  232.         makestr l, <msg, '$'>
  233.         mov     dx, offset DGROUP:l
  234.         jmp     error
  235.         endm
  236.  
  237. ;*************************************************************************
  238. ; DOS:  Call DOS with the function code passed as the argument.
  239. ;*************************************************************************
  240. DOS     macro   func
  241.         mov     ah, func
  242.         int     DOSvec
  243.         endm
  244.  
  245. ;*************************************************************************
  246. ; inchar:  Read a character from the input file to al.
  247. ;          If the input buffer is empty, fill it.
  248. ;*************************************************************************
  249. inchar  macro
  250.         local   l
  251.         lodsb
  252.         or      al, al
  253.         jnz     l
  254.             call    flushbuf    ; this also fills the input buffer
  255. l:
  256.         endm
  257.  
  258.  
  259. ;*************************************************************************
  260. ; Code
  261. ;*************************************************************************
  262. _TEXT   segment byte public 'CODE'
  263. DGROUP  group _DATA,_BSS
  264.         assume  cs:_TEXT, ds:DGROUP, es:DGROUP, ss:DGROUP
  265.  
  266. ;*************************************************************************
  267. ; error:  Print the string in DX to the console and die
  268. ;*************************************************************************
  269. error   proc    near
  270.         DOS     print                   ; print the string
  271.         mov     bx, fdout               ; close the file, if it's open
  272.         or      bx, bx
  273.         jz      noclose
  274.         DOS     close
  275. noclose:
  276.         mov     al, 1                   ; return an error
  277.         DOS     exit
  278. error   endp
  279.  
  280. ;*************************************************************************
  281. ; decode:  Decode the buffer until it is empty.
  282. ;          This is the inner loop, so it has been mangled for speed.
  283. ;          The object of the game is to make as few jumps as possible
  284. ;          along the most-frequently-travelled path through the code,
  285. ;          and to combine termination tests.
  286. ;*************************************************************************
  287. decode  proc    near
  288.  
  289. ; Preliminary setup:
  290. ;           Throughout, cx = 85.  We need to keep that number nearby.
  291.         mov cx, 85
  292.         jmp short dloop     ; enter the main loop
  293.  
  294.  
  295. ;*************************************************************************
  296. ; aux:     Auxiliary stuff that has been hoisted from the inner loop.
  297. ;          Since the buffer empties very rarely (once every 16000
  298. ;          times through the loop), we shuffle code around so that
  299. ;          the most common code path doesn't involve any jumps.
  300. ;*************************************************************************
  301. aux1:   call    flushbuf
  302.         jmp     short auxret1
  303. aux2:   call    flushbuf
  304.         jmp     short auxret2
  305. aux3:   call    flushbuf
  306.         jmp     short auxret3
  307. retdc:  ret
  308. ;*************************************************************************
  309. ; zout:  See if we got a 'z'; otherwise, leave.
  310. ;*************************************************************************
  311. zout:   cmp     al, 'z'-'!'
  312.         jnz     retdc
  313.  
  314. ;*************************************************************************
  315. ; zee:     Code four consecutive zeros
  316. ;*************************************************************************
  317. zee:    xor     ax,ax
  318.         stosw
  319.         stosw               ; this codes four zeros
  320.         add     CsumL, 4    ; Update the "Sum" checksum.
  321.         adc     CsumH, 0
  322.  
  323.         ; Updating the "Rotate" checksum is tricky.
  324.         ; The clever way ( rol ax, 4; rol dx, 4; exchanging low nibbles)
  325.         ; takes more clock cycles.  Though this drains the prefetch queue.
  326.  
  327.         mov     dx, CrotH
  328.         mov     ax, CrotL
  329.  
  330.         sal     ax,1        ; This sequence
  331.         rcl     dx,1        ; rotates the 32-bit
  332.         adc     ax,0        ; number by one bit.
  333.  
  334.         sal     ax,1        ; So we do it four times.
  335.         rcl     dx,1        ;
  336.         adc     ax,0        ;
  337.  
  338.         sal     ax,1        ; Third time
  339.         rcl     dx,1        ;
  340.         adc     ax,0        ;
  341.  
  342.         sal     ax,1        ; Fourth time
  343.         rcl     dx,1        ;
  344.         adc     ax,0        ;
  345.  
  346.         mov     CrotH, dx
  347.         mov     CrotL, ax
  348.  
  349. ;*************************************************************************
  350. ; dloop:  The main decoding loop.
  351. ;
  352. ;         The numbers down the center are clock ticks on an 8086.
  353. ;         I optimized for an 8086, so sue me.
  354. ;*************************************************************************
  355. dloop:
  356.  
  357. dloop1: lodsb                   ; Get the first character of a quintet
  358.         or      al, al
  359.         jz      aux1
  360.  
  361. auxret1:
  362.         sub     al,'!'
  363.         jb      dloop1          ; ignore control characters
  364.         cmp     al,cl
  365.         jge     zout            ; either a 'z' or something invalid
  366.         mul     cl
  367.         mov     bx, ax          ; BX = a0
  368.  
  369. dloop2: lodsb                   ; Get the second character of a quintet
  370.         or      al, al
  371.         jz      aux2
  372. auxret2:
  373.         sub     al, '!'
  374.         jb      dloop2          ; ignore control characters
  375.         cmp     al, cl
  376.         jge     retdc
  377.  
  378.         mov     ah, ch          ; zero out the top byte
  379.         add     ax, bx          ; AX = ab
  380.         mov     dx, 85*85
  381.         mul     dx              ; DX:AX = ab00
  382.         mov     bx, ax          ; DX:BX = ab00
  383.  
  384. dloop3: lodsb                   ; Get the third character of a quintet
  385.         or      al, al
  386.         jz      aux3
  387. auxret3:
  388.         sub     al, '!'
  389.         jb      dloop3          ; ignore control characters
  390.         cmp     al, cl
  391.         jge     retdc
  392.  
  393.         mul     cl              ; AX=c0
  394.         add     bx, ax
  395.         adc     dx, 0           ; DX:BX = abc0
  396.  
  397. dloop4: lodsb                   ; Get the fourth character of a quintet
  398.         or      al, al
  399.         jz      aux4
  400. auxret4:
  401.         sub     al, '!'
  402.         jb      dloop4          ; ignore control characters
  403.         cmp     al, cl
  404.         jge     retdc2
  405.  
  406.         mov     ah, ch
  407.         add     bx, ax
  408.         mov     ax, dx
  409.         adc     ax, 0           ; AX:BX = abcd
  410.  
  411.         mul     cx
  412.         mov     bp, ax
  413.         mov     ax, bx
  414.         mul     cx
  415.         add     bp, dx
  416.         mov     bx, ax          ; BP:BX = abcd0
  417.  
  418. dloop5: lodsb                   ; Get the last character of a quintet
  419.         or      al, al
  420.         jz      aux5
  421. auxret5:
  422.         sub     al, '!'
  423.         jb      dloop5          ; ignore control characters
  424.         cmp     al, cl
  425.         jge     retdc2
  426.  
  427.         mov     ah, ch
  428.         add     bx, ax
  429.         mov     dx, bp
  430.         adc     dx, 0           ; DX:BX = abcde
  431.  
  432.         mov     al, dh          ; send out the bytes in [DX:BX]
  433.         call    byteout         ; from top to bottom
  434.         mov     al, dl
  435.         call    byteout
  436.         mov     al, bh
  437.         call    byteout
  438.         mov     al, bl
  439.         call    byteout
  440.  
  441.         jmp     dloop1          ; ready for more!
  442.  
  443. retdc2: ret
  444. aux4:   call    flushbuf
  445.         jmp     short auxret4
  446. aux5:   call    flushbuf
  447.         jmp     short auxret5
  448. decode  endp
  449.  
  450.  
  451. ;*************************************************************************
  452. ; byteout:  Update checksums and output the character in AL.  Preserves AL.
  453. ;*************************************************************************
  454.  
  455. byteout proc    near
  456.         xor     ah,ah           ; Convert byte to word
  457.  
  458.         xor     Ceor, al        ; Update the "exclusive or" checksum.
  459.  
  460.         stc                     ; Update the "Sum" checksum.
  461.         adc     CsumL, ax       ; Add the character, plus one
  462.         adc     CsumH, 0
  463.  
  464.         sal     CrotL,1         ; Update the "Rotate" checksum.
  465.         rcl     CrotH,1         ; Rotate left, then add in the character.
  466.         adc     CrotL,ax        ; Sneaky trick.  Carry gets added for free.
  467.         adc     CrotH,0
  468.  
  469.         stosb                   ; Output the character.  We never overrun
  470.                                 ; the buffer.
  471.         ret
  472. byteout endp
  473.  
  474. ;*************************************************************************
  475. ; flushbuf:  Write out the current output buffer.
  476. ;
  477. ;            The test for buffer overflow is done only when the input
  478. ;            buffer is empty.  This allows us to remove the test for
  479. ;            output buffer overflow from the inner loop.
  480. ;
  481. ; IMPORTANT: This function falls through to fillbuf.
  482. ;
  483. ;*************************************************************************
  484. flushbuf proc   near
  485.          cmp    di, offset DGROUP:outbuf
  486.          jz     noflush
  487.              push   ax
  488.              push   bx
  489.              push   cx
  490.              push   dx
  491.              mov    bx, fdout           ; write to the output file
  492.              mov    dx, offset DGROUP:outbuf   ; buffer location
  493.              mov    cx, di
  494.              sub    cx, dx              ; number of bytes to write
  495.              mov    di, dx
  496.              DOS    write
  497.              pop    dx
  498.              pop    cx
  499.              pop    bx
  500.              pop    ax
  501.              jnc    noflush
  502.                  die "Error writing output file"
  503.  
  504. noflush:     jmp    short fillbuf
  505. flushbuf endp
  506.  
  507. ;*************************************************************************
  508. ; fillbuf: Fill the input buffer with more goodies
  509. ;          We do a block read on stdin, then put a sentinel at the end.
  510. ;          Since we are I/O bound, speed is not an issue.
  511. ;*************************************************************************
  512. fillbuf proc    near
  513.         push    ax
  514.         push    bx
  515.         push    cx
  516.         push    dx
  517.  
  518.         mov     bx, 0               ; read from stdin
  519.         mov     cx, bufsiz          ; number of characters to read
  520.         mov     dx, offset DGROUP:inbuf    ; buffer location
  521.         mov     si, dx
  522.         DOS     read
  523.         jc      fillerr
  524.         or      ax, ax      ; EOF
  525.         jz      fillerr
  526.         mov     bx, ax
  527.         add     bx, offset DGROUP:inbuf
  528.         mov     byte ptr [bx], 0    ; mark the end of the buffer
  529.         pop     dx
  530.         pop     cx
  531.         pop     bx
  532.         pop     ax
  533.         lodsb
  534.         ret
  535.  
  536. fillerr:
  537.         die "Unexpected end of input"
  538.  
  539. fillbuf endp
  540.  
  541. ;*************************************************************************
  542. ; convert:  Convert the number in the input buffer in base BP into [DX:BX]
  543. ;           Returns NZ if [DX:BX] is not equal to dword ptr [DI].
  544. ;           If DI = 0, then returns Z always.
  545. ;*************************************************************************
  546. convert proc    near
  547.         push    di
  548.  
  549.         xor     bx, bx      ; the number is collected in [DI:BX]
  550.         xor     di, di
  551.         xor     cx, cx
  552.  
  553. convloop:
  554.         inchar
  555.         sub     al, '0'
  556.         jb      convbye
  557.         cmp     al, 9
  558.         jbe     notletter
  559.         sub     al, 'a' - ('0' + 10)
  560. notletter:
  561.         mov     cl, al
  562.         cmp     cx, bp
  563.         ja      convbye
  564.  
  565.         mov     ax, di
  566.         mul     bp
  567.         mov     di, ax      ; di now contains itself, times the base
  568.  
  569.         mov     ax, bx
  570.         mul     bp
  571.         add     di, dx
  572.         mov     bx, ax
  573.         add     bx, cx
  574.         adc     di, 0
  575.  
  576.         jmp     convloop
  577.  
  578. convbye:
  579.         mov     dx, di
  580.         pop     di
  581.         or      di, di
  582.         jz      retcv
  583.         cmp     [di], bx
  584.         jnz     retcv
  585.         cmp     [di+2], dx
  586. retcv:  ret
  587. convert endp
  588.  
  589. ;*************************************************************************
  590. ; prefix:  Confirm that the next two characters are AL and a space.
  591. ;*************************************************************************
  592. prefix  proc    near
  593.         inchar
  594.         cmp     ah, al
  595.         jnz     badtrailer
  596.         inchar
  597.         cmp     al, ' '
  598.         jnz     badtrailer
  599.         ret
  600. prefix  endp
  601.  
  602. ;*************************************************************************
  603. ; doprefix:  Calls prefix (qv) with the argument as AL.
  604. ;*************************************************************************
  605. doprefix  macro   c
  606.         mov     ah, c
  607.         call    prefix
  608.         endm
  609.  
  610. ;*************************************************************************
  611. ; confirm:  The prefix should be C, followed by a number in base BASE
  612. ;           which should agree with the variable VAR.  If it doesn't,
  613. ;           then jump to ERR.
  614. ;*************************************************************************
  615.  
  616. confirm macro   c, base, var, err
  617.         ifnb    <c>
  618.             doprefix  c
  619.         endif
  620.         mov     di, offset DGROUP:var&L
  621.         mov     bp, base
  622.         call    convert
  623.         jnz     err
  624.         endm
  625.  
  626. ;*************************************************************************
  627. ; main:  The main program.
  628. ;*************************************************************************
  629.  
  630. main    proc    near
  631.  
  632. ;*************************************************************************
  633. ; Step 1a: Get the filename from the command line and create it for writing.
  634. ;*************************************************************************
  635.         mov     di, 081h            ; command line lives here
  636.         mov     cx, 07fh
  637.         mov     al, ' '
  638.         cld
  639.         repe    scasb               ; look for a non-space
  640.         dec     di
  641.  
  642.         mov     dx, di              ; save filename start
  643.  
  644.         mov     al, cr              ; look for end of command line
  645.         repne   scasb
  646.         mov     byte ptr [di-1], 0  ; terminate the filename
  647.         xor     cx, cx              ; file has no attributes
  648.         DOS     creat
  649.  
  650.         assume  ds:DGROUP, es:DGROUP
  651.         mov     bx, DGROUP
  652.         mov     ds, bx
  653.         mov     es, bx
  654.         jc      outerr
  655.  
  656. ;*************************************************************************
  657. ; Step 1b:  Initalize I/O pointers and preread the input
  658. ;*************************************************************************
  659.         mov     fdout, ax
  660.         call    fillbuf
  661.         dec     si                  ; unread the character fillbuf reads
  662.  
  663. ;*************************************************************************
  664. ; Step 2:   Skip to the start line.
  665. ;           Here, di is used to point into the start line for comparison.
  666. ;*************************************************************************
  667.         makestr header, <"xbtoa Begin">, nheader
  668.  
  669. bol:                                ; at the beginning of a new line
  670.         mov     di, offset DGROUP:header
  671.         mov     cx, nheader
  672.  
  673. compare:                            ; compare current line against start
  674.         inchar
  675.         scasb
  676.         jnz     blip
  677.         loop    compare
  678.         jmp     short found
  679.  
  680. outerr: die "Couldn't open output file"
  681.  
  682. inblip:
  683.         inchar
  684. blip:                               ; skip until you hit a nl
  685.         cmp     al, lf
  686.         jnz     inblip
  687.         jmp     short bol
  688.  
  689. badtrailer:
  690.         die     "Invalid trailer"
  691.  
  692. badchar:
  693.         die     "Invalid character in input"
  694.  
  695. ;*************************************************************************
  696. ; Step 3a:  Prepare to decode the file.
  697. ;*************************************************************************
  698. found:
  699.         mov     di, offset DGROUP:outbuf
  700.  
  701. ;*************************************************************************
  702. ; Step 3b:  Decode the file.
  703. ;*************************************************************************
  704.         call    decode
  705.         cmp     al, 'x'-'!'
  706.         jnz     badchar
  707.  
  708.         push    di              ; free up a register to diddle with
  709.  
  710. ;*************************************************************************
  711. ; Step 4:  Inspect the trailer
  712. ;
  713. ; The trailer looks like this (noting that the x has already been
  714. ;   scanned)
  715. ;
  716. ; xbtoa End N Clen Clen E Ceor S Csum R Crot
  717. ;
  718. ; Where all values are in hex, except for the first Clen.
  719. ; The Clen gives the actual length of the file.  (The encoding
  720. ; pads the file with nulls until the length is 0 mod 4.)
  721. ;*************************************************************************
  722.         makestr trailer, <"btoa End ">, ntrailer
  723.         mov     di, offset DGROUP:trailer
  724.         mov     cx, ntrailer
  725. trail:
  726.         inchar
  727.         scasb
  728.         jnz     badtrailer
  729.         loop    trail
  730.         jmp     short parse
  731.  
  732. badlen:
  733.         die "File lengths disagree"
  734. badcksum:
  735.         die "Checksum mismatch"
  736.  
  737. parse:
  738.         doprefix 'N'
  739.         mov     bp, 10
  740.         xor     di, di          ; don't verify against anything
  741.         call    convert
  742.         mov     ClenL, bx       ; save actual length for checksum
  743.         mov     ClenH, dx
  744.  
  745. ;*************************************************************************
  746. ; Step 4':  Adjust the file length and write out the last little bit
  747. ;*************************************************************************
  748.         pop     di              ; recover output file position
  749. adjustloop:
  750.         and     bx, 3
  751.         jz      noadjust
  752.  
  753.         dec     di
  754.         inc     bx
  755.         jmp     adjustloop
  756.  
  757. noadjust:
  758.         mov     dx, offset DGROUP:outbuf   ; buffer location
  759.         mov     cx, di              ; number of bytes to write
  760.         sub     cx, dx
  761.         jcxz    nowrite
  762.         mov     bx, fdout           ; write to the output file
  763.         DOS     write
  764.         jnc     nowrite
  765.             die "Error writing output file"
  766.  
  767. ;*************************************************************************
  768. ; Step 4'':  Inspect the checksum values.
  769. ;*************************************************************************
  770. nowrite:
  771.         confirm <>,  16, Clen, badlen
  772.         confirm 'E', 16, Ceor, badcksum
  773.         confirm 'S', 16, Csum, badcksum
  774.         confirm 'R', 16, Crot, badcksum
  775.  
  776.         mov     bx, fdout
  777.         DOS     close
  778.         mov     al, 0
  779.         DOS     exit
  780.  
  781. main    endp
  782.  
  783. ;*************************************************************************
  784. ; The end
  785. ;*************************************************************************
  786. _TEXT  ends
  787.  
  788.        end main
  789.