home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 December / simtel1292_SIMTEL_1292_Walnut_Creek.iso / msdos / filutl / toadxx11.arc / XXD11.ASM < prev    next >
Assembly Source File  |  1989-10-29  |  40KB  |  1,392 lines

  1.     name    XXD
  2.     page    55,132
  3.     title   'XXD.ASM'
  4.  
  5. ; XXDECODE.ASM -- XXDecodes a XXEncoded Binary File
  6.  
  7. ;Author:  David Kirschbaum
  8. ;      Toad Hall
  9. ;      kirsch@arsocomvax.socom.mil
  10.  
  11. ;Released to Public Domain
  12. ;(That means, this is not SHAREWARE; this is not FREEWARE.
  13. ; This belongs to the classic "Public Domain", to everyone!
  14. ; I don't want any "donations", license fees, whatever.
  15. ; That means you can do ANYTHING with this you want to!
  16. ;)
  17.  
  18. ; To assemble and link this program into the executable XXDECODE.COM:
  19. ; (It will NOT run assembled as an .EXE program!)
  20.  
  21. ;        MASM XXD
  22. ;        LINK XXD
  23. ;        (If you just have EXE2BIN:
  24. ;          EXE2BIN XXD
  25. ;          REN XXD.BIN XXD.COM (or whatever)
  26. ;        (If you have Public Domain EXE2COM or equivalent:
  27. ;          EXE2COM XXD
  28. ;          REN XXD.COM XXD.COM (or whatever)
  29. ;        (Delete the .OBJ file)
  30. ;        (If you have TASM, do your TASM thing...)
  31.  
  32. Comment ~
  33.  
  34. v1.1, 29 Oct 89
  35.  - A user in Germany asked for file overwrite protection.
  36.    Adding output file existence checking.
  37.  - While I was at it, made all message output to STDERR (just in case
  38.    the user is confused and is using STDOUT anyway).
  39.  - Adding "-o" switch to command line options (force overwrite).
  40.    Usage now XXD [-o] filename.XXE
  41.    Produces filename.typ, overwriting any such file if it exists.
  42.  - I really should add usage instead of that stupid filename prompt.
  43.    Well, maybe next time.
  44.  
  45.    David Kirschbaum
  46.    Toad Hall
  47.  
  48. v1.0, 29 Jul 89
  49.  
  50. Per request of ..., hacked UUE/UUD family (v1.9)
  51. to handle the XXencode/XXdecode protocol.
  52.  
  53.  David Kirschbaum
  54.  Toad Hall
  55.  kirsch@arsocomvax.socom.mil
  56.  
  57. Comment    ends    ~
  58.  
  59. ;-------------------------------------------
  60. CR        EQU    0DH
  61. LF        EQU    0AH
  62. SPC        EQU    20H
  63. XXBUFFSIZE    equ    64*900            ;likely input buffer size
  64. FALSE        equ    0
  65. TRUE        equ    NOT FALSE
  66.  
  67. ;-------------------------------------------
  68. CSEG    SEGMENT PARA PUBLIC 'CODE'
  69.     ASSUME  CS:CSEG,DS:CSEG, ES:CSEG
  70.  
  71.     org    80H
  72.  
  73. cmd_tail    label    byte
  74.  
  75.     org    100H
  76.  
  77. ;-------------------------------------------
  78. Xxdecode    PROC    near
  79.  
  80.     jmp    Start        ;jump over data
  81.  
  82. ;This data will be used during the xxdecode run.
  83.  
  84. err_inp        DB      'Input file error.',CR,LF
  85. ERR_INP_LEN    EQU    $-err_inp
  86. err_out        DB      'Output file error.',CR,LF
  87. ERR_OUT_LEN    EQU    $-err_out
  88. err_begin    db    'start not found.',CR,LF
  89. ERR_START_LEN    EQU    $-err_begin
  90.  
  91. err_end        DB      'End not found.',CR,LF
  92. ERR_END_LEN    EQU    $-err_end
  93.  
  94. exist$        db    'exists. Aborting!',CR,LF            ;v1.1
  95. EXIST$_LEN    equ    $ - exist$                    ;v1.1
  96.  
  97. inp_handle    DW    0
  98. out_handle    DW    0
  99. xxptr        DW    XXBUFF        ;points at next byte in xxencoded
  100.                     ; input buffer XXBUFF
  101. xxEndptr    DW    XXBUFF        ;points beyond xxencoded data
  102.                     ; (e.g., buffer end)
  103. outptr        DW    OUT_BUF        ;pointer to binary output buffer 
  104.                     ; (where to stuff NEXT xxdecoded byte)
  105. overwrite    db    FALSE        ;is set to true for overwriting    v1.1
  106.  
  107. xx_set db '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  108. XX_LEN    EQU    $ - xx_set        ;nr chars in XX character set
  109.  
  110. ;-------------------------------------------
  111.  
  112.     ASSUME    DS:CSEG,ES:CSEG        ;a reminder
  113.  
  114. Start:
  115.     call    Init            ;cmd line parsing, file opening
  116.  
  117. ;If we make it back, all's ok.
  118. ;We should have input file opened.
  119.  
  120.     call    Read_File        ;do the initial read
  121.  
  122. Get_Out_Fil:
  123.     mov    di,offset OUT_BUF    ;clear DI
  124.     call    Get_Line        ;read a line of input
  125.  
  126. ;Look for xxencoded file's 'begin'
  127. ;(Don't like this very much .. could be stuck here until we've gone
  128. ;through the entire doggone file!  Oh, well ..it works anyway..
  129. ;(Also gobbles up any headers or other garbage at file start.)
  130.  
  131.     lodsw                ;snarf 2 plaintext chars
  132.     cmp    ax,'eb'            ;'be'?
  133.     jne    Get_Out_Fil        ;nope, next line
  134.     lodsw                ;next 2
  135.     cmp    ax,'ig'            ;'gi'?
  136.     jne    Get_Out_Fil        ;nope, next line
  137.     lodsw                ;next 2
  138.     cmp    ax,' n'            ;'n '?
  139.     jne    Get_Out_Fil        ;nope, next line
  140.  
  141.     mov    di,offset OUT_FIL    ;to output buffer
  142.  
  143. ;AH is already a space for fast comparisons
  144. Strip_Spc:
  145. ;Gobble spaces until we hit the number after 'start'
  146.     lodsb
  147.     cmp    al,ah            ;space?
  148.     jbe    Strip_Spc        ;gobble spaces, tabs, ctrl chars 
  149. Strip_Num:
  150. ;Hit the number, now gobble until the space after number
  151.     lodsb
  152.     cmp    al,ah            ;space?
  153.     jne    Strip_Num        ;gobble until space
  154. ;Now gobble any spaces between number and name ... sigh ...
  155. Strip_Spc2:
  156.     lodsb
  157.     cmp    al,ah            ;space?
  158.     jbe    Strip_Spc2        ;gobble until real char
  159.  
  160. ;We should now be at the first char of the original target's filename.
  161. ;Move output filename from LINE_IN buffer to our output filename buffer.
  162. ;Since LINE_IN has been padded with spaces, a space will indicate
  163. ;name end.
  164.  
  165. Get_Out:
  166.     cmp    al,ah            ;space means done
  167.     je    Out_Fin            ;yep, name end
  168.     stosb                ;input byte > OUT_FIL filename buff
  169.     lodsb                ;next filename char
  170.     jmp    Get_Out            ;and keep going
  171.  
  172. ;-------------------------------------------
  173. Out_Fin:
  174.     mov    dx,offset OUT_FIL    ;output filename buffer
  175.     xor    cx,cx            ;normal file attribs (R/W)
  176.     mov    [di],cl            ;AsciiZ output filename
  177.  
  178. ;v1.1    User requested overwrite protection for the output filename.
  179. ;    Ok .. let's check for output filename existence before we
  180. ;    do the create.
  181.  
  182.     cmp    overwrite,TRUE        ;"-o" commandline switch?    v1.1
  183.     jz    Out_Create        ;yep, no checking        v1.1
  184.  
  185.     mov    ah,4EH            ;find first            v1.1
  186.     int    21H
  187.     cmp    al,2            ;file not found?        v1.1
  188.     jz    Out_Create        ;fine, doesn't exist        v1.1
  189.     cmp    al,18            ;no more files to be found?    v1.1
  190.     jz    Out_Create        ;fine                v1.1
  191.  
  192. ;v1.1    File exists.  Error message, die.
  193. ;    DI -> filename AsciiZ 0
  194. ;    DX -> filename 1st char
  195.  
  196.     mov    cx,di            ;last char            v1.1
  197.     sub    cx,dx            ;- first char = chars to write    v1.1
  198.     dec    cx            ;adjust                v1.1
  199.     dec    cx            ;                v1.1
  200.     call    Say_Error        ;display filename        v1.1
  201.  
  202.     mov    dx,OFFSET exist$    ;'File exists'            v1.1
  203.     mov    cx,EXIST$_LEN        ;msg length            v1.1
  204.     mov    al,5            ;fake "Access denied" errorlevel v1.1
  205.     jmp    Fatal_Error        ;display, terminate        v1.1
  206.  
  207.  
  208. Out_Create:                ;v1.1
  209.     mov    ah,3Ch            ;create output file
  210.     int     21h
  211.     jnc    Out_Open        ;went ok
  212.      jmp    Out_Err            ;output file create error
  213.  
  214. ;-------------------------------------------
  215. Out_Open:
  216.     mov    out_handle,ax        ;remember output file handle
  217.     mov    di,offset OUT_BUF    ;prepare to clear outptr
  218.  
  219. New_Line:
  220.     call    Get_Line        ;read in xxencoded line
  221.  
  222. ;First char is nr of binary bytes used to produce this line
  223. ;(usually an 'e' for 45 bytes).
  224. ;If there's an empty last line, there'll just be a '+' (0 length).
  225. ;If just a space, that'll stop us also!
  226. ;There's a terminating 0 beyond the last xxencoded character.
  227. ;(e.g., line is now an AsciiZ string)
  228.  
  229.     mov    al,[si]            ;snarf line length byte
  230.     or    al,al            ;empty line?
  231.     jz    Prog_End        ;yep, must be done
  232.                     ;(or an early abort!)
  233.     cmp    al,'+'            ;XXEncoded 0 length line?
  234.     jz    Prog_End        ;yep, done
  235.     cmp    al,20H            ;just a space?
  236.     jz    Prog_End        ;yep, done
  237.  
  238. ;We first go through the entire line, converting XXencoded characters
  239. ;to their binary equivalent (0..63)
  240.  
  241.     push    di            ;save DI output buff pointer a sec
  242.  
  243.     mov    bx,offset xx_set    ;XX conversion char set
  244.     mov    dx,XX_LEN        ;nr chars in xx_set
  245.                     ;(we'll need these several times)
  246.  
  247.     push    si            ;save first line char ptr
  248.  
  249. Line_Decode_Lup:
  250.     lodsb                ;snarf XXencoded char
  251.     or    al,al            ;terminating 0?
  252.     jz    Line_Decoded        ;yep, done
  253.  
  254.     mov    di,bx    ;offset xx_set    ;XX conversion set
  255.     mov    cx,dx    ;XX_LEN        ;nr chars in set
  256.     repne    scasb            ;find char in set
  257.  
  258. ;If not found, there's a SERIOUS error!
  259.     jnz    Line_Err        ;die
  260.  
  261.     mov    ax,di            ;XX char psn +1
  262.     dec    ax            ;back up from last scasb
  263.     sub    ax,bx            ;char psn - start = binary value
  264.     mov    [si-1],al        ;stuff back in line
  265.     jmp    short Line_Decode_Lup    ;keep going
  266.  
  267. Line_Decoded:
  268.     pop    si            ;first line char ptr
  269.     pop    di            ;output buffer ptr
  270.  
  271.     lodsb                ;binary line count (now binary)
  272.     xor    ah,ah            ;clear msb
  273.     mov    bp,ax            ;BP = line char counter
  274.  
  275. ;Then we go back through the entire line (sigh ...),
  276. ;converting 4 chars to 3 bytes (the final (and original) binary byte).
  277. ;xxencoded stuff (input) is in 'quads' (4-char packets),
  278. ;binary output is in 'hunks' (3 byte packets)
  279. ;4 chars = 3 bytes
  280.  
  281. NN_0:
  282.     mov    cx,0604H        ;handy constant
  283.                     ;CL=4, CH=6
  284.  
  285.     lodsw                ;snarf 2 chars (quad[1],quad[2])
  286.     xchg    al,ah            ;AH=quad[1],AL=quad[2]
  287.     mov    dl,al            ;save quad[2]
  288.  
  289.     shl    ah,1            ;quad[1] SHL 2
  290.     shl    ah,1            ;(faster this way)
  291.     shr    al,cl            ;quad[2] SHR 4
  292.     or    al,ah            ;shifted quad[2] OR shifted quad[1]
  293.     stosb                ;=hunk[1], stuff in OUT_BUF
  294.  
  295.     dec    bp            ;decr binary byte ctr
  296.     jz    New_Line        ;last byte, last line
  297.  
  298.     mov    ah,dl            ;AH=unshifted quad[2]
  299.     lodsb                ;AL=quad[3]
  300.     mov    dl,al            ;save quad[3]
  301.  
  302.     shl    ah,cl            ;quad[2] SHL 4
  303.     shr    al,1            ;quad[2] SHR 2
  304.     shr    al,1            ;(faster this way)
  305.     or    al,ah            ;shifted quad[3] OR shifted quad[2]
  306.     stosb                ;=hunk[2], stuff in OUT_BUF
  307.  
  308.     dec    bp            ;decr binary byte ctr
  309.     jz    New_Line        ;last byte, last line
  310.  
  311.     mov    ah,dl            ;AH=unshifted quad[3]
  312.     lodsb                ;AL=quad[4]
  313.  
  314.     mov    cl,ch            ;6
  315.     shl    ah,cl            ;quad[3] SHL 6
  316.     or    al,ah            ;shifted quad[4] OR shifted quad[3]
  317.     stosb                ;=hunk[3], stuff in OUT_BUF
  318.  
  319.     dec    bp            ;decr binary byte ctr
  320.     jnz    NN_0            ;xxencoded line not done
  321.     jmp    short New_Line        ;line done, get new line
  322.  
  323. Line_Err:
  324.     jmp    Inp_Err            ;'Input file error', die
  325.  
  326. ;-------------------------------------------
  327. ;We hit a null line (e.g., end of XXencoded lines)
  328. ;Now we look for the xxencoded file's 'end'.
  329. ;We'll write out what we've got, even if we don't find the 'end'.
  330.  
  331. Prog_End:
  332.     call    Get_Line        ;should be file's last line
  333.     lodsw                ;load next 2 chars
  334.     cmp    ax,'ne'            ;'en'?
  335.     jne    End_Err_C        ;'No end found'
  336.      lodsb
  337.      cmp    al,'d'
  338.      je    File_End        ;ok, got the 'end'
  339. End_Err_C:
  340.     call    End_Err            ;say we had an end error
  341. File_End:
  342.     call    Write_File        ;do our final output write
  343.  
  344. ;Seems there's no need to close files .. DOS must do it!
  345.  
  346. File_End_X:
  347.     mov    ah,4Ch            ;terminate, ERRORLEVEL ?
  348.     int     21h
  349.  
  350. Xxdecode    endp
  351.  
  352. ;-------------------------------------------
  353. ;Reads in a line of xxencoded text
  354. ;(by working through the input buffer until we hit a CR or LF.
  355. ;Unix text and xxencoded files only have LFs as EOL,
  356. ;Macs only have CR EOLs (I think).
  357. ;Well, we're handling CR/LF and LF EOLs, and that'll do for now.
  358.  
  359. ;The first char of a xxencoded line is usually an 'e'.  This is
  360. ;the nr of binary bytes used to create this xxencoded line (usually 45).
  361.  
  362. Get_Line    PROC    NEAR
  363.     mov    si,xxptr        ;current raw input buffer psn
  364.  
  365. ;This is looped to (from below) if the line was garbage (e.g., some sort
  366. ;of file header.  It refreshes the outbuf pointer to buffer start,
  367. ;and sets up the xxencoded line buffer LINE_IN anew.
  368.  
  369. Flush_Lin:
  370.     mov    outptr,di        ;save outbuf ptr in outptr
  371.                     ;for Write_File test
  372.  
  373. ;BP holds the constant 'maximum line length'.
  374. ;Normal xxencoded line length is 63
  375. ; (60 xxencoded bytes, plus length byte and CR/LF).
  376. ;However, the xxencode protocol permits up to 64 xxencoded chars,
  377. ;so 'maximum length' is 67 chars.
  378.  
  379.     mov    bp,67            ;max allowable xxencoded line len 
  380.  
  381. ;Prepare our xxencoded line buffer LINE_IN for transfer of a line.
  382. ;Remember, this same sequence is used to get the xxencode protocol's
  383. ;first line ('begin 6xx filename.typ'),normal xxencoded lines,
  384. ;and the last xxencode protocol line ('end').
  385. ;We're preparing a 67-char line (maximum allowed).
  386.  
  387.     mov    di,offset LINE_IN    ;xxencoded line buffer start
  388.     mov    byte ptr [di],0        ;clear first byte
  389.  
  390. Next_Chr:
  391.     cmp    si,xxEndptr        ;hit end yet?
  392.     jb    Not_Mt            ;not empty yet
  393.      call    Write_File        ;write our binary output (if any)
  394.      call    Read_File        ;read more xxencoded input
  395. Not_Mt:
  396.     lodsb                ;snarf xxencoded line char
  397.  
  398. ;We check for both CR End-Of_Line (EOL) and LF EOL.
  399. ;(This handles DOS and Unix files.)
  400.  
  401.     cmp    al,CR            ;CR means line end
  402.     je    Eol_CR            ;end of line, got line
  403.     cmp    al,LF            ;How about Unix EOL?
  404.     je    Eol_LF            ;Yep, was Unix EOL, got line
  405.  
  406.     stosb                ;stuff xxencoded line char
  407.     dec    bp            ;count down allowable length
  408.     jnz    Next_Chr        ;Ok, not yet
  409.  
  410. ;This line is longer than 67 chars, so it CAN'T be a xxencoded line.
  411. ;Continue to gobble it up, throwing it away, until EOL,
  412. ;then flush and continue.
  413. ;This only happens for headers.  We never hit trailers at all.
  414.  
  415. Strip_Head:
  416.     cmp    si,xxEndptr        ;beyond xxbuf data?
  417.     jb    Strip_NoRead        ;nope
  418.      call    Read_File        ;refill the xxbuf
  419. Strip_NoRead:
  420.     lodsb                ;look for a LF
  421.     cmp    al,LF            ;LF yet?
  422.     jne    Strip_Head        ;nope, keep going w/this line
  423.  
  424.     mov    di,offset OUT_BUF    ;clear DI to clear outptr
  425.     jmp    Flush_Lin        ;EOL, keep working thru raw
  426.                     ;input buffer
  427.  
  428. ;-------------------------------------------
  429. ;Hit CR, got a xxencoded line (DOS files).
  430. ;SI -> LF just past the CR.
  431.  
  432. Eol_CR:
  433.     inc    si            ;bump raw input buffer ptr past LF
  434.  
  435. ;Hit LF EOL (Unix files).
  436. ;SI -> next raw buff char.
  437.  
  438. Eol_LF:
  439.     mov    byte ptr [di],0        ;terminate xxencoded line with a 0
  440.     mov    xxptr,si        ;remember current raw buff ptr
  441.     mov    di,outptr        ;restore binary output ptr
  442.     mov    si,offset LINE_IN    ;ptr to inbuffer start
  443.     ret                ;done
  444.  
  445. Get_Line    ENDP
  446.  
  447. ;-------------------------------------------
  448. ;Write a chunk of xxdecoded data (if any) to output file.
  449.  
  450. Write_File    PROC    NEAR
  451.  
  452.     mov    dx,offset OUT_BUF    ;output binary buffer start
  453.     mov    cx,dx            ;prepare to reinit outptr
  454.     xchg    cx,outptr        ;outptr=output buffer start,
  455.                     ;CX=outptr
  456.     sub    cx,dx            ;-buffer start=buffer byte count 
  457.  
  458. ;IF CX=0, there's no xxdecoded data to write, just exit.
  459. ;If CX went below 0 (e.g., outptr pointed BELOW OUT_BUF start),
  460. ;we have some sort of error  (my code logic?).
  461. ;Ignore that also.  It'll get cleaned up later.
  462.  
  463.     jbe    Write_Good        ;error
  464.  
  465.      mov    bx,out_handle        ;output file handle
  466.      mov    ah,40h            ;write to file/device
  467.      int     21h
  468.      jb    Out_Err            ;write error
  469. Write_Good:
  470.     ret                ;write done
  471.  
  472. ;Output file write error
  473. Out_Err:
  474.     mov    dx,OFFSET err_out    ;'Output file write error'
  475.     mov    cx,ERR_OUT_LEN        ;msg length
  476.     jmp    short Fatal_Error    ;common code
  477.  
  478. Write_File    ENDP
  479.  
  480. ;-------------------------------------------
  481. ;Read a chunk of input (xxencoded) data into our xxencode buffer.
  482. ;I don't think we'll EVER try to read past EOF (since the 'end' line
  483. ;should've been detected and program terminated).
  484. ;Let's assume that 'read past EOF' is an error of some sort and die.
  485. ;('Coding by trial and error')
  486.  
  487. Read_File    PROC    NEAR
  488.  
  489.     mov    dx,offset XXBUFF    ;read into xxencode input buffer
  490.     mov    cx,XXBUFFSIZE        ;input buffer size
  491.     mov    bx,inp_handle        ;input file handle
  492.     mov    ah,3Fh            ;read from file/device
  493.     int     21h
  494.     jb    Inp_Err            ;failed
  495.     or    ax,ax            ;read anything?
  496.     jz    Read_EOF        ;nope, EOF
  497.  
  498.     mov    si,dx            ;point to input XXBUFF start
  499.     add    ax,si            ;chars read+XXBUFF start
  500.     mov    xxEndptr,ax        ;points beyond last XXBUFF char
  501.     ret                ;read done
  502.  
  503. Read_EOF:
  504.  
  505. ;-------------------------------------------
  506. ;Input file read error.  Error value in AL
  507. Inp_Err:
  508.     mov    dx,OFFSET err_inp    ;'Input file error'
  509.     mov    cx,ERR_INP_LEN        ;msg length
  510.  
  511. Fatal_Error:
  512.     call    Say_Error        ;common code
  513.     jmp    File_End_X        ;terminate, error in AL        v1.1
  514.  
  515. Read_File    ENDP
  516.  
  517. ;-------------------------------------------
  518. ;Unexpected End of File.  (No 'end')
  519. End_Err        PROC    NEAR
  520.  
  521.     mov    dx,OFFSET err_end    ;'End not found'
  522.     mov    cx,ERR_END_LEN        ;msg length
  523. Say_Error:                ;common code
  524.     push    ax            ;save any error value in AL    v1.1
  525.     mov    bx,2            ;Std ErrOut handle
  526.     mov    ah,40h            ;write to file/device
  527.     int     21h
  528.     pop    ax            ;restore errorlevel        v1.1
  529.     ret
  530.  
  531. End_Err        ENDP
  532.  
  533.  
  534. ;using pointers beyond runtime code and/or code end for various buffers.
  535. ;This does NOT take up any space in our program.
  536.  
  537.         EVEN
  538.  
  539. ;LINE_IN is not used until after Init,
  540. ;so it can overwrite this code and data.
  541. LINE_IN    equ    $            ;80 bytes long
  542.  
  543. ;Some start-up data moved down here to minimize memory requirements.
  544. msg_v1        DB      'This program requires DOS Version 2.0 '
  545.         DB      'or higher.',CR,LF,'$'
  546. pr_inp        DB    CR,LF,'Input path/file:  '
  547. PR_INP_LEN    EQU    $-pr_inp
  548. no_action    db    'No action',CR,LF,'$'
  549.  
  550. ;Some start-up code.
  551. Init    proc    near
  552.  
  553. ;First make sure we have DOS 2.0 or higher, or handles won't work.
  554.     mov    ah,30h            ;get DOS version
  555.     int     21h
  556.     cmp    al,2            ;2.0 or above?
  557.     jae    Chk_CmdLine        ;yep, ok
  558.      mov    dx,OFFSET msg_v1    ;'DOS 2.0 or above'
  559. Msg_Die:
  560.      mov    ah,9            ;display string
  561.      int     21h
  562.      mov    ax,4C01H        ;terminate, ERRORLEVEL 1
  563.      int    21H
  564.  
  565. ;Now check cmd line for xxencoded source file.
  566. Chk_CmdLine:
  567.     call    Parse_CmdLine        ;get cmdline target filename
  568.     jnc    Open_Inp_Fil        ;fine, got one
  569.                     ;DI -> AsciiZed filename
  570.                     ;0 terminator.
  571.  
  572. ;Let's be nice and prompt the user for an input filename
  573. ;using normal buffered keyboard input:
  574.  
  575.     mov    dx,OFFSET pr_inp    ;'Input/file name:' prompt
  576.     mov    cx,PR_INP_LEN        ;nr chars to display
  577.     mov    bx,2            ;Std ErrOut handle (always to con)
  578.     mov    ah,40h            ;write to file or device
  579.     int     21h
  580.  
  581. ;Now get the user kbd input:
  582.  
  583.     mov    di,offset cmd_tail-1    ;1 byte before PSP cmdline
  584.     mov    byte ptr [di],80    ;tell DOS max of 80 chars
  585.     mov    dx,di            ;DX -> buffer
  586.     mov    ah,0AH            ;buffered kbd input svc
  587.     int    21H
  588.     call    Parse_CmdLine        ;parse the input
  589.     jnc    Open_Inp_Fil        ;got input (already AsciiZed)
  590.                     ;try to open
  591.                     ;DI -> AsciiZ 0
  592. ;Okay, he doesn't wanna play...
  593.      mov    dx,offset no_action    ;'No action'
  594.      jmp    short Msg_Die        ;display, terminate
  595.  
  596.  
  597. Open_Inp_Fil:
  598.     mov    dx,offset INP_FIL    ;input file name buffer
  599.     mov    ax,3D00h        ;open file
  600.     int     21h
  601.     jb    Open_Die        ;failed, terminate w/error
  602.      mov    inp_handle,ax        ;save input file handle
  603.      ret                ;go xxdecode
  604.  
  605. Open_Die:
  606.      jmp    Inp_Err            ;input file open error, die
  607. Init    endp
  608.  
  609.  
  610. ;Command line processing subroutine
  611.  
  612. Parse_CmdLine    proc    near
  613.  
  614.     mov    si,offset cmd_tail    ;move cmd line parm
  615.     mov    di,offset INP_FIL    ;to our filename buffer
  616.     cld                ;insure fwd
  617.     lodsb                ;cmd line length byte
  618.     or    al,al            ;nothing there?
  619.     jz    PC_NoInput        ;yep, nothing there
  620.  
  621.     mov    ah,20H            ;get a handy space
  622. Strip_Ct:
  623.     lodsb                ;next cmd line char
  624.     cmp    al,ah            ;gobble leading spaces,tabs, etc.
  625.     jbe    Strip_Ct
  626.  
  627. ;v1.1    Check for a "-o" switch on command line
  628.  
  629.     cmp    al,'/'            ;this kind of switch?        v1.1
  630.     jz    GotSwitch        ;yep                v1.1
  631.      cmp    al,'-'            ;switch?            v1.1
  632.      jnz    NotSwitch        ;nope, must be name char    v1.1
  633. GotSwitch:                ;                v1.1
  634.     mov    dx,ax            ;save this char a second    v1.1
  635.     mov    ax,[si]            ;snarf next 2 chars        v1.1
  636.     and    al,5FH            ;mask 1st char to uppercase    v1.1
  637.     cmp    ax,' O'            ;O and space means switch    v1.1
  638.     mov    ax,dx            ;restore in case not        v1.1
  639.     jnz    NotSwitch        ;nope, must be name        v1.1
  640.  
  641. ;v1.1    We have the overwrite switch
  642.  
  643.     not    overwrite        ;toggle flag to TRUE        v1.1
  644.     inc    si            ;bump past 'o'            v1.1
  645.     inc    si            ;and past space            v1.1
  646.     lodsb                ;next char should be name    v1.1
  647.  
  648. Ct_Char:
  649.     cmp    al,ah            ;ctrl char? (e.g., CR)
  650.     jbe    PC_Input        ;yep, done
  651. NotSwitch:                ;                v1.1
  652.     stosb                ;stuff filename byte
  653.     lodsb                ;snarf next cmdline char
  654.     jmp    short Ct_Char        ;and loop
  655.  
  656.     cmp    al,ah            ;ctrl char? (e.g., CR)
  657.     jbe    PC_Input        ;yep, done
  658.  
  659.  
  660. PC_NoInput:
  661.     stc                ;no input, return CF set
  662.     ret
  663.  
  664. PC_Input:
  665.     mov    byte ptr [di],0        ;Asciize filename input
  666.     clc                ;got input, return CF clear
  667.     ret
  668. Parse_CmdLine    endp
  669.  
  670.  
  671.         EVEN
  672.  
  673. ;INP_FIL is used by Init startup, so it must sit below the code.
  674. INP_FIL    equ    $            ;80 bytes long,
  675.                     ; input filename buffer.
  676.                     ; Overwrites OUT_FIL buffer
  677.                     ; and xxencode buffers.
  678.  
  679. ;OUT_FIL, OUT_BUF, and XXBUFF aren't used until AFTER Init completes.
  680. ;They can overwrite Init code, startup data, INP_FIL filename buffer, etc.
  681. ;However, XXBUFF and LINE_IN ARE used to get the output filename OUT_FIL),
  682. ; so XXBUFF and LINE_IN can't overwrite OUT_FIL.
  683.  
  684. ;LINE_IN needs 80 bytes:
  685. OUT_FIL    equ    LINE_IN + 80        ;15 bytes long,
  686.                     ; output filename buffer.
  687.                     ; Sits below LINE_IN,
  688.                     ; overwrites Init code/data.
  689. OUT_BUF    equ    LINE_IN + 80        ;80 bytes long,
  690.                     ; xxdecoded data buffer.
  691.                     ; Sits below LINE_IN,
  692.                     ; overwrites OUT_FIL filename
  693.                     ; and Init code/data.
  694. ;OUT_BUF needs 80 bytes
  695.  
  696. XXBUFF    equ    OUT_BUF + 80        ; xxencoded file read buffer.
  697.                     ; Sits below OUT_BUF,
  698.                     ; uses XXBUFFSIZE bytes
  699.  
  700. logo    db    'XXD v1.1',0
  701.     db    'David P Kirschbaum, Toad Hall',0
  702.  
  703. CSEG    ENDS
  704.     END    Xxdecode
  705.  
  706.     name    XXENCODE
  707.     page    55,132
  708.     title   'XXENCODE.ASM'
  709. ;
  710. ; XXENCODE.ASM -- XXEncodes a Binary File
  711. ;
  712. ; Cmd line processing, general layout from UU.ASM by Theodore A. Kaldis
  713. ;
  714. ;Author:  David Kirschbaum
  715. ;      Toad Hall
  716. ;      kirsch@arsocomvax.socom.mil
  717. ;
  718. ;Released to Public Domain
  719. ;(That means, this is not SHAREWARE; this is not FREEWARE.
  720. ; This belongs to the classic "Public Domain", to everyone!
  721. ; I don't want any "donations", license fees, whatever.
  722. ; That means you can do ANYTHING with this you want to!
  723. ;)
  724. ;
  725. ; To assemble and link this program into the executable XXENCODE.COM:
  726. ; (It will NOT run compiled as an .EXE program!)
  727.  
  728. ;        MASM XXE;
  729. ;        LINK XXE;
  730. ;        (If you just have EXE2BIN:
  731. ;          EXE2BIN XXE
  732. ;          REN XXE.BIN XXENCODE.COM (or whatever)
  733. ;        (If you have Public Domain EXE2COM or equivalent:
  734. ;          EXE2COM XXE
  735. ;          REN XXE.COM XXENCODE.COM (or whatever)
  736. ;        (Delete the bogus .OBJ file.)
  737. ;        (If you have TASM, do your TASM thing...)
  738.  
  739. Comment ~
  740. v1.1, 29 Oct 89
  741.  - A user in Germany asked for file overwrite protection.
  742.    Adding output file existence checking.
  743.    HOWEVER!  If you compile this sucker with the STDOUT switch enabled
  744.    (so output file goes to STDOUT, the file/device of YOUR choice),
  745.    you're on your own!
  746.    Distributed version is NOT STDOUT-enabled.  It produces its own
  747.    output file name from the input file name.
  748.  - While I was at it, made all message output to STDERR (just in case
  749.    the user is confused and is using STDOUT anyway).
  750.  - Adding "-o" switch to command line options (force overwrite).
  751.    Usage now XXE [-o] filename.typ
  752.    Produces filename.XXE, overwriting any such file if it exists.
  753.  
  754.    David Kirschbaum
  755.    Toad Hall
  756.  
  757. v1.0, 29 Jul 89
  758.  
  759. - Per request of Erich ...., hacking the TOADUU family to handle the newer
  760.   XXENCODE/XXDECODE protocol.
  761.   Same general idea, but only uses the ASCII character set:
  762.  
  763. +-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
  764.  
  765. - Making an assumption:  The last line (before the "end" line)
  766.   will be a null line (e.g., just CR/LF).
  767.  
  768. - Sep 89:  Finally got the public domain XXENCODE/XXDECODE (C version)
  769.   for comparison and tests:
  770.   - XXE is leaving a "blank" line as the last line (with a single space).
  771.     XXENCODE leaves a "+" as that last line (signifying 0 length).
  772.     My "null line" assumption was correct (almost).
  773.   - When there's an "odd" number of bytes in the target file (e.g.,
  774.     not MOD 3), XXENCODE produces "trailing" characters differently
  775.     from XXE. I think there's a problem in XXENCODE's logic there,
  776.     and my process (nulling out the last characters) is correct.
  777.     It doesn't really matter since those xxencoded characters are
  778.     NOT used as part of the decoding process.
  779.  
  780. - I'm forcing the internal target file name to its actual file name
  781.   (lower case).  XXENCODE and UUENCODE command line protocols force
  782.   the user to use three (3) command line parameters:
  783.     target file name
  784.     "internal" target file name
  785.     output redirection
  786.   Ugh!  We're producing a .XXE type file, period!
  787.  
  788.   You can recompile this code to enable "redirection" (via the DOS
  789.   '>' business) if you wish .. turn on the STDOUT flag below.
  790.  
  791.  
  792.  David Kirschbaum
  793.  Toad Hall
  794.  kirsch@arsocomvax.socom.mil
  795.  
  796. Comment    ends    ~
  797.  
  798. ;-------------------------------------------
  799. LF        EQU    10
  800. CR        EQU    13
  801. FALSE        equ    0
  802. TRUE        equ    NOT FALSE
  803.  
  804. READSIZE    EQU    45000        ;max bytes for a binary file read.
  805.                     ;Small enough to fit within our
  806.                     ; 64Kb code/data space.
  807.                     ;Size must be an even divisor of 3.
  808.  
  809. LINELEN        EQU    60        ;nr chars in a xxencoded line
  810.  
  811.  
  812. STDOUT        EQU    FALSE        ;change to TRUE to enable redirection
  813.  
  814. CSEG    SEGMENT PARA PUBLIC 'CODE'
  815.     ASSUME  CS:CSEG,DS:CSEG, ES:CSEG
  816.  
  817.     org    80H            ;PSP cmdline start
  818. cmd_tail    label    byte
  819.  
  820.     org    100H
  821.  
  822. XxEncode    PROC    near
  823.     jmp    Start            ;jump over data
  824.  
  825.     IF    STDOUT
  826. usage    db    'XXE [d:][\path\]binary.fil [>output] <RETURN>'
  827.     db    CR,LF,'(Uses redirection)'
  828.     ELSE
  829. usage    db    'XXE [-o] [d:][\path\]binary.fil <RETURN>',CR,LF
  830.     db    'produces binary.XXE on current drive\path',CR,LF
  831.     db    '(providing binary.XXE doesn''t already exist).',CR,LF    ;v1.1
  832.     db    '-o switch forces overwrite of existing binary.XXE'    ;v1.1
  833.     ENDIF
  834.         db    CR,LF                    ;v1.1
  835. USAGE_LEN    equ    $-usage                    ;v1.1
  836.  
  837. msg_v1        DB      'This program requires DOS V2.0 or higher.',CR,LF ;v1.1
  838. MSG_V1_LEN    equ    $ - msg_v1                ;v1.1
  839.  
  840. pr_inp        DB    CR,LF,'Input path/file:  '
  841. PR_INP_LEN    EQU    $-pr_inp
  842.  
  843. err_inp        DB      'Input file error.',CR,LF
  844. ERR_INP_LEN    EQU    $-err_inp
  845.  
  846. err_out        DB      'Output file error.',CR,LF
  847. ERR_OUT_LEN    EQU    $-err_out
  848.  
  849. ;UUENCODE just had a blank line before the end (actually a single space).
  850. ;Turns out XXENCODE makes this a "null" xxencoded line
  851. ;(with a "+" (0) length byte.  Not EXACTLY like UUENCODE, but ...
  852.  
  853. end_msg        DB      '+',CR,LF,'end',CR,LF
  854. END_MSG_LEN    EQU    $-end_msg
  855.  
  856. ;no_action    db    'No action',CR,LF,'$'    v1.1
  857.  
  858. exist$        db    'exists!  Aborting!',CR,LF            ;v1.1
  859. EXIST$_LEN    equ    $-exist$                    ;v1.1
  860.  
  861. inp_handle    DW    0        ;input file handle
  862. out_handle    DW    1        ;output file handle (default StdOut)
  863. read_count    DW    data_buf     ;nr binary bytes read
  864. last_flag    db    FALSE        ;set true when partial read
  865. overwrite    db    FALSE        ;set true if "-o" cmdline switch v1.1
  866.  
  867. xxe_set    db '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  868.  
  869. ;-------------------------------------------
  870.  
  871. Start:
  872. ; Insure we're DOS 2.0 or above (or handles won't work!)
  873.     mov    ah,30h            ;get DOS version
  874.     int    21h
  875.     cmp    al,2            ;2.0 or above?
  876.     jae    Chk_Cmd            ;yep, ok
  877.  
  878.      mov    dx,OFFSET msg_v1    ;'DOS 2.0 or above'
  879.      mov    cx,MSG_V1_LEN        ;msg length            v1.1
  880.                     ;AL has errorlevel 1        v1.1
  881. Msg_Die:
  882. ;v1.1    mov    ah,9            ;display string
  883. ;v1.1    mov    ax,4C01H        ;terminate, ERRORLEVEL 1
  884. ;v1.1    int    21H
  885.     call    Say_Error        ;write to StdErr, AL unchanged    v1.1
  886.     jmp    Terminate        ;terminate            v1.1
  887.  
  888. ;-------------------------------------------
  889. ;Check our PSP command line for a target filename.
  890. Chk_Cmd:
  891.  
  892. ;    Moved command line parsing to a separate subroutine
  893. ;    (since we may have to do it twice)
  894.  
  895.     call    Parse_CmdLine        ;parse PSP cmdline
  896.     jnc    Open_Inp_Fil        ;we have input
  897.                     ;(already AsciiZed)
  898.                     ;DI -> AsciiZ 0
  899.  
  900. ;    No PSP cmdline input.
  901. ;    Let's prompt our user:
  902.  
  903.     mov    dx,OFFSET pr_inp    ;'Input/file name:' prompt
  904.     mov    cx,PR_INP_LEN        ;nr chars to display
  905.     call    Say_Error        ;display to STDERR        v1.1
  906.  
  907. ;Get user's kbd input
  908. ;    via regular DOS buffered keyboard input call
  909.  
  910.     mov    di,offset cmd_tail-1    ;1 byte before PSP cmdline
  911.     mov    byte ptr [di],80    ;tell DOS max of 80 chars
  912.     mov    dx,di            ;DX -> buffer
  913.     mov    ah,0AH            ;buffered kbd input svc
  914.     int    21H
  915.     call    Parse_CmdLine        ;parse the input
  916.     jnc    Open_Inp_Fil        ;got input (already AsciiZed)
  917.                     ;try to open
  918.                     ;DI -> AsciiZ 0
  919.  
  920. ;No command line.  Show usage, exit.
  921.  
  922.     mov    dx,offset usage        ;'Usage..'
  923. ;v1.1    mov    ah,9            ;display str
  924. ;v1.1    int    21H
  925. ;v1.1    mov    dx,offset no_action    ;'No action'
  926.     mov    cx,USAGE_LEN        ;msg length            v1.1
  927.     jmp    short Msg_Die        ;display, terminate
  928.  
  929. ;We have an input filename.  Open it.
  930.  
  931. Open_Inp_Fil:
  932.     mov    dx,offset inp_fil    ;input filename buffer
  933.     mov    si,dx            ;remember input filename
  934.                     ;buff start
  935.     mov    ax,3D00h        ;open file
  936.     int    21h
  937.     jnb    Inp_Open        ;went ok
  938.      jmp    Inp_Err            ;input file open error, die
  939.  
  940. ;-------------------------------------------
  941. Inp_Open:
  942.     mov    inp_handle,ax        ;save input file handle
  943.  
  944. ;Take input file name (up to file separator) (no paths)
  945. ;move "filename.typ" into our xxencoded buffer and write to file.
  946. ;First scan for any paths, drives, etc.
  947. ;SI -> input filename buffer start
  948. ;DI -> the last filename char+1, so we can compute length.
  949.  
  950.     mov    cx,di            ;last char+1
  951.     sub    cx,si            ;-start = nr chars+1
  952.     dec    cx            ;adjust
  953.  
  954. ;We'll start at the end and scan back toward the front.
  955. ;Remember, scasb decrements DI even if it finds the scan char,
  956. ;so we'll have to adjust after the find.
  957.  
  958.     mov    al,'\'            ;first scan for paths
  959.     std                ;going backwards
  960.     repne    scasb
  961.     cld                ;set back forward again
  962.     jz    Found_Path        ;DI points to char before '\'
  963.     mov    di,si            ;back to start
  964.     cmp    byte ptr [di+1],':'    ;how about a drive?
  965.     jne    Name_Start        ;nope, use buffer start
  966.                     ;else fall thru and bump di past 'd:'
  967.  
  968. ;DI's now pointing at the REAL target file name starting char
  969. ;(past the drive, paths, etc.)
  970. ;We first move the original target file name into our xxe buffer
  971. ;(which is initialized with the "start 644 " characters).
  972. ;This xxe buffer will be written as the first line of our xxencoded file.
  973.  
  974. Found_Path:
  975.     inc    di            ;adjust for scasb or 'd:'
  976.     inc    di
  977. Name_Start:
  978.     mov    si,di            ;move from input name start
  979.     mov    dx,si            ;save starting point a sec
  980.     mov    di,offset xxe_filename    ;move to within xxe buffer
  981. OutName_Loop:
  982.     lodsb                ;snarf each char
  983.     or    al,al            ;0 means filename end
  984.     jz    OutName_Done        ;done
  985.      stosb                ;stuff filename char
  986.      jmp    OutName_Loop        ;keep going
  987.  
  988. OutName_Done:
  989.     mov    ax,0A0DH        ;get CR/LF
  990.     stosw                ;stuff it in xxencode buffer
  991.  
  992. ;target file name has now been moved into a starting xxencoded file
  993. ;text line (to include CR/LF).
  994.  
  995. IF    STDOUT            ;use StdOut redirection
  996.     mov    cx,di            ;ptr to last filename char +1
  997. ELSE                ;create 'filename.xxe'
  998.  
  999.     push    di            ;remember that ending psn
  1000.  
  1001. ;Now to create our output file name: filename.xxe
  1002.  
  1003.     mov    si,dx            ;SI is PSP target filename start
  1004.     mov    di,offset out_fil    ;move to output file name buffer
  1005.     mov    dx,di            ;we'll need it here also
  1006. xxe_Name_Loop:
  1007.     lodsb                ;snarf each char
  1008.     or    al,al            ;0 means filename end
  1009.     jne    Check_Dot        ;nope
  1010.      mov    al,'.'            ;no file type, so fake it
  1011. Check_Dot:
  1012.     stosb                ;stuff name char into output name
  1013.     cmp    al,'.'            ;go up to separator
  1014.     jne    xxe_Name_Loop        ;not yet
  1015. ;We've now moved the file name (plus the '.') into our output buffer.
  1016. ;Time for the type
  1017.     mov    ax,'xx'            ;'xxe'
  1018.     stosw                ;stuff
  1019.     mov    ax,'e'            ;'e', DOS AsciiZ terminator
  1020.     mov    [di],ax            ;stuff
  1021.  
  1022. ;DX has output filename starting ofs.
  1023. ;ptr to last byte in xxe buffer is on the stack.
  1024.  
  1025.     xor    cx,cx            ;normal file attrib (R/W)
  1026.  
  1027. ;v1.1    User asked for output file overwrite protection.
  1028. ;    Ok .. let's check for output filename existence
  1029. ;    before we create it.
  1030. ;    DX -> AsciiZ filename
  1031.  
  1032.     cmp    overwrite,TRUE        ;'-o' cmdline switch?        v1.1
  1033.     jz    Out_Create        ;that's right, overwrite if exists v1.1
  1034.  
  1035.     mov    ah,4EH            ;find first            v1.1
  1036.     int    21H            ;                v1.1
  1037.     cmp    al,2            ;file not found?        v1.1
  1038.     jz    Out_Create        ;fine, create it        v1.1
  1039.     cmp    al,18            ;no more files to be found?    v1.1
  1040.     jz    Out_Create        ;fine, create it        v1.1
  1041.  
  1042. ;v1.1    File exists, so we'll message and abort.
  1043.  
  1044.     pop    cx            ;clear stack            v1.1
  1045.     mov    cx,di            ;last char in output name
  1046.     sub    cx,dx            ;last -first = length        v1.1
  1047.     mov    bx,2            ;STDERR                v1.1
  1048.     add    cx,bx            ;adjust count            v1.1
  1049.     mov    ah,40H            ;write to file/device        v1.1
  1050.     int    21H
  1051.     mov    dx,offset exist$    ;' exists!  Aborting!'        v1.1
  1052.     mov    cx,EXIST$_LEN        ;msg length            v1.1
  1053.     mov    al,5            ;fake "Access denied" errorlevel v1.1
  1054.     jmp    Msg_Die            ;display, terminate        v1.1
  1055.  
  1056. Out_Create:
  1057.     mov    ah,3CH            ;create file
  1058.     int    21H
  1059.     pop    cx            ;restore xxe ptr into CX
  1060.     jnb    Create_Ok        ;ok
  1061.      jmp    Out_Err            ;'Output file error', die
  1062.  
  1063. Create_Ok:
  1064.     mov    out_handle,ax        ;save output handle
  1065.  
  1066. ENDIF                ;StdOut or filename.xxe
  1067.  
  1068.     mov    dx,offset xxe_out    ;'start 644 filename.typ', CR/LF
  1069.     sub    cx,dx            ;last char-buff start = bytes to write
  1070.     call    Write_File        ;write that record
  1071. ;Write_File set DI to offset xxe_out+1,BP=0
  1072.  
  1073. Read_Loop:
  1074.     call    Read_File        ;do the initial binary read
  1075.     jz    Write_Xxe_End        ;nothing read, done with input
  1076.  
  1077. ;Read_File set SI to offset data_buf, didn't touch DI output buffer ptr,
  1078. ;or BP binary byte counter.
  1079.  
  1080. Xxe_Loop:
  1081. ;SI and BP are incrementing as we xxencode 45 bytes of binary data
  1082. ;into 60 bytes of Ascii data.
  1083.  
  1084.     mov    cx,0604H        ;handy constant
  1085.                     ;CL=4,CH=6
  1086.  
  1087.     lodsb                ;hunk[1]
  1088.     mov    ah,al            ;AH, AL=hunk[1]
  1089.     shr    al,1            ;quad[1] = hunk[1] SHR 2
  1090.     shr    al,1            ;(faster this way)
  1091.     stosb                ;= quad[1], stuff
  1092.  
  1093.     lodsb                ;AL=hunk[2]
  1094.     mov    dl,al            ;save hunk[2] a sec
  1095.     shl    ah,cl            ;hunk[1] SHL 4
  1096.     shr    al,cl            ;hunk[2] SHR 4
  1097.     add    al,ah            ;shifted hunk[1]+shifted hunk[2]
  1098.     stosb                ;= quad[2], stuff
  1099.  
  1100.     mov    ah,dl            ;AH=orig hunk[2]
  1101.     lodsb                ;AL=hunk[3]
  1102.     mov    dl,al            ;save hunk[3] in DL a sec
  1103.     shl    ah,1            ;hunk[2] SHL 2
  1104.     shl    ah,1            ;(faster this way)
  1105.     mov    cl,ch            ;CL now 6
  1106.     shr    al,cl            ;hunk[3] SHR 6
  1107.     add    al,ah            ;shifted hunk[2]+shifted hunk[3]
  1108.     stosb                ;= quad[3], stuff
  1109.  
  1110.     mov    al,dl            ;AL=orig hunk[3]
  1111.     stosb                ;= quad[4], stuff
  1112.  
  1113. ;That 3-byte hunk is done.  See if our binary buffer's empty.
  1114. ;Notice we ALWAYS assume we did all 3 binary bytes.
  1115. ;If we didn't (e.g., had a non-MOD 3 nr of binary bytes in our file),
  1116. ;we'll correct that later with an adjustment to the binary counter
  1117. ;character in the xxencoded line.
  1118.  
  1119.     add    bp,3            ;+3
  1120.  
  1121. ;    Trying to work around that "extra 4 chars"
  1122. ;    written to output at the 45000-byte buffer end.
  1123. ;    To do this, we first check to see if our xxencoded output line
  1124. ;    is full yet (e.g., 45 binary bytes read, 64 xxencoded chars
  1125. ;    ready to be output):
  1126.  
  1127.     cmp    bp,45            ;done a line of binary data yet? 
  1128.     jnz    Chk_BuffEnd        ;nope
  1129.      call    Write_Xxe        ;stuff binary count in record,
  1130.                     ;Asciify entire record,
  1131.                     ;append CR/LF, write to file
  1132.                     ;Reset BP binary counter=0,
  1133.                     ;DI back to output buffer start +1
  1134.  
  1135. Chk_BuffEnd:
  1136.     cmp    si,read_count        ;hit data end yet?
  1137.     jb    Xxe_Loop        ;nope,not yet
  1138.                     ;keep xxencoding input buffer
  1139.  
  1140.     cmp    last_flag,1        ;Was last read the binary file EOF?
  1141.     jne    Read_Loop        ;nope, do another read, maybe end.
  1142.  
  1143. ;-------------------------------------------
  1144. Write_Xxe_End:
  1145.  
  1146.     or    bp,bp            ;any bytes xxencoded?
  1147.     jz    No_Partial_Write    ;none, so no trailing to write
  1148.  
  1149. ;In converting 3 binary bytes to 4 quad chars, we may have bumped SI
  1150. ;beyond the actual number of binary bytes read.
  1151. ;By subtracting the original count of bytes read from SI,
  1152. ;we'll get the number of 'bogus' binary bytes in that last quad.
  1153. ;Subtract that from the BP binary counter, and we'll get the TRUE
  1154. ;number of binary bytes in that xxencoded line.
  1155. ;It's up to the xxdecoding program to catch that difference
  1156. ;and ignore the extra quads.
  1157.  
  1158.      sub    si,read_count        ;check for overrun
  1159.      sub    bp,si            ;subtract any bogus bytes
  1160.      call    Write_Xxe        ;write partial line
  1161.  
  1162. No_Partial_Write:
  1163.     mov    dx,offset end_msg    ;'end',CR/LF
  1164.     mov    cx,END_MSG_LEN        ;nr bytes to move
  1165.     call    Write_File        ;do the last write
  1166.  
  1167. ;DOS normally closes all files at termination.
  1168. ;Still, just to be neat...
  1169.  
  1170. IF    NOT STDOUT            ;no StdOut
  1171.     mov    bx,out_handle        ;output file handle
  1172.     mov    ah,3EH            ;close the file
  1173.     int    21H
  1174.     xor    al,al            ;return ERRORLEVEL 0        v1.1
  1175. ENDIF
  1176.  
  1177. Terminate:
  1178.     mov    ah,4Ch            ;terminate, ERRORLEVEL ?
  1179.     int    21h
  1180.  
  1181. XxEncode    endp
  1182.  
  1183. ;-------------------------------------------
  1184.  
  1185. Write_Xxe    PROC    NEAR
  1186. ;Enter with:
  1187. ;  DI pointing to char beyond last xxencoded char.
  1188. ;  BP = binary bytes in this line
  1189. ;
  1190. ;Stuff CR/LF, compute line length, write to file.
  1191.  
  1192.     mov    dx,offset xxe_out    ;output line start (length byte)
  1193.  
  1194.     mov    cx,di            ;current output pointer
  1195.     sub    cx,dx            ;- buffer start = nr bytes in line
  1196.                     ;+1, but that's ok since we're adding
  1197.                     ;the line_len byte
  1198.     mov    di,dx            ;point to line start
  1199.  
  1200. ;Do the last masking of the line of quads
  1201.  
  1202.     mov    ax,bp            ;binary bytes in this line
  1203.     mov    bp,cx            ;save full line length for later
  1204.     mov    [di],al            ;stuff binary length byte
  1205.                     ; (xxencode later)
  1206.     mov    bx,offset xxe_set    ;set of XX characters
  1207.     mov    ah,3FH            ;handy masking constant
  1208.  
  1209. ;Gotta process every byte, masking, converting to XXE character.
  1210. ;This includes that length byte.
  1211.  
  1212. Mask_Loop:
  1213.     mov    al,[di]            ;snarf binary char
  1214.     and    al,ah    ;3FH        ;six-bit mask
  1215.     xlat                ;AL now has XX[al]
  1216.     stosb                ;stuff it back in line buffer
  1217.     loop    Mask_Loop        ;do them all
  1218.  
  1219.     mov    cx,bp            ;restore char count for bytes to write
  1220.  
  1221. ;DI now points at char just beyond xxencoded char line
  1222.  
  1223.     mov    word ptr [di],0A0DH    ;stuff CR/LF in buffer
  1224.     inc    cx            ;add in CR/LF to length
  1225.     inc    cx
  1226.  
  1227. Write_File:
  1228.     mov    bx,out_handle        ;output file handle
  1229.     mov    ah,40h            ;write to file/device
  1230.     int    21h
  1231.     jb    Out_Err            ;write error
  1232.      mov    di,dx            ;point DI back to xxe_out start
  1233.      inc    di            ;bump past length byte
  1234.      xor    bp,bp            ;reset byte ctr
  1235.      ret                ;write done
  1236.  
  1237. ;Output file write error
  1238. Out_Err:
  1239.     mov    dx,OFFSET err_out    ;'Output file error'
  1240.     mov    cx,ERR_OUT_LEN        ;msg length
  1241.     jmp    short Fatal_Error    ;common code
  1242.  
  1243. Write_Xxe    ENDP
  1244.  
  1245. ;-------------------------------------------
  1246. ;Read a chunk of raw binary data
  1247. Read_File    PROC    NEAR
  1248.  
  1249.     mov    dx,offset data_buf    ;into binary input buffer
  1250.     mov    cx,READSIZE        ;nr bytes to read
  1251.     mov    bx,inp_handle        ;input file handle
  1252.     mov    ah,3Fh            ;read from file/device
  1253.     int    21h
  1254.     jb    Inp_Err            ;failed
  1255.  
  1256. ;AX has nr of bytes read
  1257.     mov    si,dx            ;SI needs offset data_buf
  1258.     mov    bx,dx            ;buffer start
  1259.     add    bx,ax            ;+bytes read = data end
  1260.  
  1261. ;BX points to the next 'empty' byte (data_buf start + bytes read)
  1262.  
  1263.     cmp    ax,cx            ;read a full buffer?
  1264.     je    Read_Full        ;yep, no fiddling required
  1265.  
  1266. ;We've read less than a buffer full.  Let's make sure the last bytes
  1267. ;are nulls if bytes read are not MOD 3.
  1268.  
  1269.     mov    word ptr [bx],0        ;stuff 2 nulls there
  1270.     mov    last_flag,1        ;turn EOF flag on
  1271.  
  1272. Read_Full:
  1273.     mov    read_count,bx        ;point to data end
  1274.     or    ax,ax            ;set flags for return
  1275.     ret                ;read done
  1276.  
  1277. ;-------------------------------------------
  1278. ;Input file read error.  Error value in AL
  1279. Inp_Err:
  1280.  
  1281.     mov    dx,OFFSET err_inp    ;'Input file error'
  1282.     mov    cx,ERR_INP_LEN        ;msg length
  1283.  
  1284. ;Common code added here
  1285. Fatal_Error:
  1286.     call    Say_Error        ;display error msg, AL unchanged v1.1
  1287.     jmp    Terminate        ;terminate
  1288.  
  1289. Read_File    ENDP
  1290.  
  1291. ;-------------------------------------------
  1292. Say_Error    proc    near
  1293. ;DX -> error msg
  1294. ;CX = msg len
  1295.  
  1296.     push    ax            ;save AX            v1.1
  1297.     mov    bx,2            ;Std ErrOut handle
  1298.     mov    ah,40h            ;write to file/device
  1299.     int    21h
  1300.     pop    ax            ;restore            v1.1
  1301.     ret
  1302.  
  1303. Say_Error    ENDP
  1304.  
  1305. ; Command line processing subroutine
  1306.  
  1307. Parse_CmdLine    proc    near
  1308.  
  1309.     mov    si,offset cmd_tail    ;move cmd line parm
  1310.     mov    di,offset inp_fil    ;to our filename buffer
  1311.     cld                ;insure fwd
  1312.     lodsb                ;cmd line length byte
  1313.     or    al,al            ;nothing there?
  1314.     jz    PC_NoInput        ;yep, nothing there
  1315.  
  1316.     mov    ah,20H            ;get a handy space
  1317.  
  1318. Strip_Ct:
  1319.     lodsb                ;next cmd line char
  1320.     cmp    al,ah            ;gobble leading spaces,tabs, etc.
  1321.     jbe    Strip_Ct
  1322.  
  1323. ;v1.1    Check for a "-o" switch on command line
  1324.  
  1325.     cmp    al,'/'            ;this kind of switch?        v1.1
  1326.     jz    GotSwitch        ;yep                v1.1
  1327.      cmp    al,'-'            ;switch?            v1.1
  1328.      jnz    NotSwitch        ;nope, must be name char    v1.1
  1329. GotSwitch:                ;                v1.1
  1330.     mov    dx,ax            ;save this char a second    v1.1
  1331.     mov    ax,[si]            ;snarf next 2 chars        v1.1
  1332.     and    al,5FH            ;mask 1st char to uppercase    v1.1
  1333.     cmp    ax,' O'            ;O and space means switch    v1.1
  1334.     mov    ax,dx            ;restore in case not        v1.1
  1335.     jnz    NotSwitch        ;nope, must be name        v1.1
  1336.  
  1337. ;v1.1    We have the overwrite switch
  1338.  
  1339.     not    overwrite        ;toggle flag to TRUE        v1.1
  1340.     inc    si            ;bump past 'o'            v1.1
  1341.     inc    si            ;and past space            v1.1
  1342.     lodsb                ;next char should be name    v1.1
  1343.  
  1344. Ct_Char:
  1345.     cmp    al,ah            ;ctrl char? (e.g., CR)
  1346.     jbe    PC_Input        ;yep, done
  1347.  
  1348. NotSwitch:
  1349.      stosb                ;stuff filename byte
  1350.      lodsb                ;snarf next cmdline char
  1351.      jmp    Ct_Char            ;and loop
  1352.  
  1353. PC_NoInput:
  1354.     stc                ;no input, return CF set
  1355.     ret
  1356.  
  1357. PC_Input:
  1358.     mov    byte ptr [di],0        ;Asciize filename input
  1359.     clc                ;got input, return CF clear
  1360.     ret
  1361.  
  1362. Parse_CmdLine    endp
  1363.  
  1364.  
  1365. ;using pointers here at code end for various buffers.
  1366. ;the xxe_out buffer is normally 60 xxencoded chars, plus CR/LF.
  1367. ;It's initialized with the default xxencode file header.
  1368. ;The magic number '644' has something to do with file attributes
  1369. ;on a Unix system.
  1370.  
  1371.         EVEN
  1372.  
  1373. xxe_out        db    'begin 644 '    ;first write contains this + name
  1374.  
  1375. ;The rest of these buffers don't take any code space.
  1376.  
  1377. xxe_filename    equ    $        ;where we move filename.xxe
  1378. ;Leave room for LINELEN+2 chars for xxe_out buffer.
  1379. inp_fil        equ    xxe_out  + LINELEN +2    ;80 chars long
  1380.  
  1381. ;Leave room for 80 chars for inp_fil filename buffer.
  1382. out_fil        equ    inp_fil + 80    ;15 bytes long
  1383.  
  1384. data_buf    equ    out_fil        ;leave room for xxe_out
  1385.  
  1386. logo    db    'XXENCODE v1.1',0
  1387.     db    'David P Kirschbaum, Toad Hall, '
  1388.     db    'Given to the public domain',0
  1389.  
  1390. CSEG    ENDS
  1391.     END    XxEncode
  1392.