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 >
Wrap
Assembly Source File
|
1989-10-29
|
40KB
|
1,392 lines
name XXD
page 55,132
title 'XXD.ASM'
; XXDECODE.ASM -- XXDecodes a XXEncoded Binary File
;Author: David Kirschbaum
; Toad Hall
; kirsch@arsocomvax.socom.mil
;Released to Public Domain
;(That means, this is not SHAREWARE; this is not FREEWARE.
; This belongs to the classic "Public Domain", to everyone!
; I don't want any "donations", license fees, whatever.
; That means you can do ANYTHING with this you want to!
;)
; To assemble and link this program into the executable XXDECODE.COM:
; (It will NOT run assembled as an .EXE program!)
; MASM XXD
; LINK XXD
; (If you just have EXE2BIN:
; EXE2BIN XXD
; REN XXD.BIN XXD.COM (or whatever)
; (If you have Public Domain EXE2COM or equivalent:
; EXE2COM XXD
; REN XXD.COM XXD.COM (or whatever)
; (Delete the .OBJ file)
; (If you have TASM, do your TASM thing...)
Comment ~
v1.1, 29 Oct 89
- A user in Germany asked for file overwrite protection.
Adding output file existence checking.
- While I was at it, made all message output to STDERR (just in case
the user is confused and is using STDOUT anyway).
- Adding "-o" switch to command line options (force overwrite).
Usage now XXD [-o] filename.XXE
Produces filename.typ, overwriting any such file if it exists.
- I really should add usage instead of that stupid filename prompt.
Well, maybe next time.
David Kirschbaum
Toad Hall
v1.0, 29 Jul 89
Per request of ..., hacked UUE/UUD family (v1.9)
to handle the XXencode/XXdecode protocol.
David Kirschbaum
Toad Hall
kirsch@arsocomvax.socom.mil
Comment ends ~
;-------------------------------------------
CR EQU 0DH
LF EQU 0AH
SPC EQU 20H
XXBUFFSIZE equ 64*900 ;likely input buffer size
FALSE equ 0
TRUE equ NOT FALSE
;-------------------------------------------
CSEG SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CSEG,DS:CSEG, ES:CSEG
org 80H
cmd_tail label byte
org 100H
;-------------------------------------------
Xxdecode PROC near
jmp Start ;jump over data
;This data will be used during the xxdecode run.
err_inp DB 'Input file error.',CR,LF
ERR_INP_LEN EQU $-err_inp
err_out DB 'Output file error.',CR,LF
ERR_OUT_LEN EQU $-err_out
err_begin db 'start not found.',CR,LF
ERR_START_LEN EQU $-err_begin
err_end DB 'End not found.',CR,LF
ERR_END_LEN EQU $-err_end
exist$ db 'exists. Aborting!',CR,LF ;v1.1
EXIST$_LEN equ $ - exist$ ;v1.1
inp_handle DW 0
out_handle DW 0
xxptr DW XXBUFF ;points at next byte in xxencoded
; input buffer XXBUFF
xxEndptr DW XXBUFF ;points beyond xxencoded data
; (e.g., buffer end)
outptr DW OUT_BUF ;pointer to binary output buffer
; (where to stuff NEXT xxdecoded byte)
overwrite db FALSE ;is set to true for overwriting v1.1
xx_set db '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
XX_LEN EQU $ - xx_set ;nr chars in XX character set
;-------------------------------------------
ASSUME DS:CSEG,ES:CSEG ;a reminder
Start:
call Init ;cmd line parsing, file opening
;If we make it back, all's ok.
;We should have input file opened.
call Read_File ;do the initial read
Get_Out_Fil:
mov di,offset OUT_BUF ;clear DI
call Get_Line ;read a line of input
;Look for xxencoded file's 'begin'
;(Don't like this very much .. could be stuck here until we've gone
;through the entire doggone file! Oh, well ..it works anyway..
;(Also gobbles up any headers or other garbage at file start.)
lodsw ;snarf 2 plaintext chars
cmp ax,'eb' ;'be'?
jne Get_Out_Fil ;nope, next line
lodsw ;next 2
cmp ax,'ig' ;'gi'?
jne Get_Out_Fil ;nope, next line
lodsw ;next 2
cmp ax,' n' ;'n '?
jne Get_Out_Fil ;nope, next line
mov di,offset OUT_FIL ;to output buffer
;AH is already a space for fast comparisons
Strip_Spc:
;Gobble spaces until we hit the number after 'start'
lodsb
cmp al,ah ;space?
jbe Strip_Spc ;gobble spaces, tabs, ctrl chars
Strip_Num:
;Hit the number, now gobble until the space after number
lodsb
cmp al,ah ;space?
jne Strip_Num ;gobble until space
;Now gobble any spaces between number and name ... sigh ...
Strip_Spc2:
lodsb
cmp al,ah ;space?
jbe Strip_Spc2 ;gobble until real char
;We should now be at the first char of the original target's filename.
;Move output filename from LINE_IN buffer to our output filename buffer.
;Since LINE_IN has been padded with spaces, a space will indicate
;name end.
Get_Out:
cmp al,ah ;space means done
je Out_Fin ;yep, name end
stosb ;input byte > OUT_FIL filename buff
lodsb ;next filename char
jmp Get_Out ;and keep going
;-------------------------------------------
Out_Fin:
mov dx,offset OUT_FIL ;output filename buffer
xor cx,cx ;normal file attribs (R/W)
mov [di],cl ;AsciiZ output filename
;v1.1 User requested overwrite protection for the output filename.
; Ok .. let's check for output filename existence before we
; do the create.
cmp overwrite,TRUE ;"-o" commandline switch? v1.1
jz Out_Create ;yep, no checking v1.1
mov ah,4EH ;find first v1.1
int 21H
cmp al,2 ;file not found? v1.1
jz Out_Create ;fine, doesn't exist v1.1
cmp al,18 ;no more files to be found? v1.1
jz Out_Create ;fine v1.1
;v1.1 File exists. Error message, die.
; DI -> filename AsciiZ 0
; DX -> filename 1st char
mov cx,di ;last char v1.1
sub cx,dx ;- first char = chars to write v1.1
dec cx ;adjust v1.1
dec cx ; v1.1
call Say_Error ;display filename v1.1
mov dx,OFFSET exist$ ;'File exists' v1.1
mov cx,EXIST$_LEN ;msg length v1.1
mov al,5 ;fake "Access denied" errorlevel v1.1
jmp Fatal_Error ;display, terminate v1.1
Out_Create: ;v1.1
mov ah,3Ch ;create output file
int 21h
jnc Out_Open ;went ok
jmp Out_Err ;output file create error
;-------------------------------------------
Out_Open:
mov out_handle,ax ;remember output file handle
mov di,offset OUT_BUF ;prepare to clear outptr
New_Line:
call Get_Line ;read in xxencoded line
;First char is nr of binary bytes used to produce this line
;(usually an 'e' for 45 bytes).
;If there's an empty last line, there'll just be a '+' (0 length).
;If just a space, that'll stop us also!
;There's a terminating 0 beyond the last xxencoded character.
;(e.g., line is now an AsciiZ string)
mov al,[si] ;snarf line length byte
or al,al ;empty line?
jz Prog_End ;yep, must be done
;(or an early abort!)
cmp al,'+' ;XXEncoded 0 length line?
jz Prog_End ;yep, done
cmp al,20H ;just a space?
jz Prog_End ;yep, done
;We first go through the entire line, converting XXencoded characters
;to their binary equivalent (0..63)
push di ;save DI output buff pointer a sec
mov bx,offset xx_set ;XX conversion char set
mov dx,XX_LEN ;nr chars in xx_set
;(we'll need these several times)
push si ;save first line char ptr
Line_Decode_Lup:
lodsb ;snarf XXencoded char
or al,al ;terminating 0?
jz Line_Decoded ;yep, done
mov di,bx ;offset xx_set ;XX conversion set
mov cx,dx ;XX_LEN ;nr chars in set
repne scasb ;find char in set
;If not found, there's a SERIOUS error!
jnz Line_Err ;die
mov ax,di ;XX char psn +1
dec ax ;back up from last scasb
sub ax,bx ;char psn - start = binary value
mov [si-1],al ;stuff back in line
jmp short Line_Decode_Lup ;keep going
Line_Decoded:
pop si ;first line char ptr
pop di ;output buffer ptr
lodsb ;binary line count (now binary)
xor ah,ah ;clear msb
mov bp,ax ;BP = line char counter
;Then we go back through the entire line (sigh ...),
;converting 4 chars to 3 bytes (the final (and original) binary byte).
;xxencoded stuff (input) is in 'quads' (4-char packets),
;binary output is in 'hunks' (3 byte packets)
;4 chars = 3 bytes
NN_0:
mov cx,0604H ;handy constant
;CL=4, CH=6
lodsw ;snarf 2 chars (quad[1],quad[2])
xchg al,ah ;AH=quad[1],AL=quad[2]
mov dl,al ;save quad[2]
shl ah,1 ;quad[1] SHL 2
shl ah,1 ;(faster this way)
shr al,cl ;quad[2] SHR 4
or al,ah ;shifted quad[2] OR shifted quad[1]
stosb ;=hunk[1], stuff in OUT_BUF
dec bp ;decr binary byte ctr
jz New_Line ;last byte, last line
mov ah,dl ;AH=unshifted quad[2]
lodsb ;AL=quad[3]
mov dl,al ;save quad[3]
shl ah,cl ;quad[2] SHL 4
shr al,1 ;quad[2] SHR 2
shr al,1 ;(faster this way)
or al,ah ;shifted quad[3] OR shifted quad[2]
stosb ;=hunk[2], stuff in OUT_BUF
dec bp ;decr binary byte ctr
jz New_Line ;last byte, last line
mov ah,dl ;AH=unshifted quad[3]
lodsb ;AL=quad[4]
mov cl,ch ;6
shl ah,cl ;quad[3] SHL 6
or al,ah ;shifted quad[4] OR shifted quad[3]
stosb ;=hunk[3], stuff in OUT_BUF
dec bp ;decr binary byte ctr
jnz NN_0 ;xxencoded line not done
jmp short New_Line ;line done, get new line
Line_Err:
jmp Inp_Err ;'Input file error', die
;-------------------------------------------
;We hit a null line (e.g., end of XXencoded lines)
;Now we look for the xxencoded file's 'end'.
;We'll write out what we've got, even if we don't find the 'end'.
Prog_End:
call Get_Line ;should be file's last line
lodsw ;load next 2 chars
cmp ax,'ne' ;'en'?
jne End_Err_C ;'No end found'
lodsb
cmp al,'d'
je File_End ;ok, got the 'end'
End_Err_C:
call End_Err ;say we had an end error
File_End:
call Write_File ;do our final output write
;Seems there's no need to close files .. DOS must do it!
File_End_X:
mov ah,4Ch ;terminate, ERRORLEVEL ?
int 21h
Xxdecode endp
;-------------------------------------------
;Reads in a line of xxencoded text
;(by working through the input buffer until we hit a CR or LF.
;Unix text and xxencoded files only have LFs as EOL,
;Macs only have CR EOLs (I think).
;Well, we're handling CR/LF and LF EOLs, and that'll do for now.
;The first char of a xxencoded line is usually an 'e'. This is
;the nr of binary bytes used to create this xxencoded line (usually 45).
Get_Line PROC NEAR
mov si,xxptr ;current raw input buffer psn
;This is looped to (from below) if the line was garbage (e.g., some sort
;of file header. It refreshes the outbuf pointer to buffer start,
;and sets up the xxencoded line buffer LINE_IN anew.
Flush_Lin:
mov outptr,di ;save outbuf ptr in outptr
;for Write_File test
;BP holds the constant 'maximum line length'.
;Normal xxencoded line length is 63
; (60 xxencoded bytes, plus length byte and CR/LF).
;However, the xxencode protocol permits up to 64 xxencoded chars,
;so 'maximum length' is 67 chars.
mov bp,67 ;max allowable xxencoded line len
;Prepare our xxencoded line buffer LINE_IN for transfer of a line.
;Remember, this same sequence is used to get the xxencode protocol's
;first line ('begin 6xx filename.typ'),normal xxencoded lines,
;and the last xxencode protocol line ('end').
;We're preparing a 67-char line (maximum allowed).
mov di,offset LINE_IN ;xxencoded line buffer start
mov byte ptr [di],0 ;clear first byte
Next_Chr:
cmp si,xxEndptr ;hit end yet?
jb Not_Mt ;not empty yet
call Write_File ;write our binary output (if any)
call Read_File ;read more xxencoded input
Not_Mt:
lodsb ;snarf xxencoded line char
;We check for both CR End-Of_Line (EOL) and LF EOL.
;(This handles DOS and Unix files.)
cmp al,CR ;CR means line end
je Eol_CR ;end of line, got line
cmp al,LF ;How about Unix EOL?
je Eol_LF ;Yep, was Unix EOL, got line
stosb ;stuff xxencoded line char
dec bp ;count down allowable length
jnz Next_Chr ;Ok, not yet
;This line is longer than 67 chars, so it CAN'T be a xxencoded line.
;Continue to gobble it up, throwing it away, until EOL,
;then flush and continue.
;This only happens for headers. We never hit trailers at all.
Strip_Head:
cmp si,xxEndptr ;beyond xxbuf data?
jb Strip_NoRead ;nope
call Read_File ;refill the xxbuf
Strip_NoRead:
lodsb ;look for a LF
cmp al,LF ;LF yet?
jne Strip_Head ;nope, keep going w/this line
mov di,offset OUT_BUF ;clear DI to clear outptr
jmp Flush_Lin ;EOL, keep working thru raw
;input buffer
;-------------------------------------------
;Hit CR, got a xxencoded line (DOS files).
;SI -> LF just past the CR.
Eol_CR:
inc si ;bump raw input buffer ptr past LF
;Hit LF EOL (Unix files).
;SI -> next raw buff char.
Eol_LF:
mov byte ptr [di],0 ;terminate xxencoded line with a 0
mov xxptr,si ;remember current raw buff ptr
mov di,outptr ;restore binary output ptr
mov si,offset LINE_IN ;ptr to inbuffer start
ret ;done
Get_Line ENDP
;-------------------------------------------
;Write a chunk of xxdecoded data (if any) to output file.
Write_File PROC NEAR
mov dx,offset OUT_BUF ;output binary buffer start
mov cx,dx ;prepare to reinit outptr
xchg cx,outptr ;outptr=output buffer start,
;CX=outptr
sub cx,dx ;-buffer start=buffer byte count
;IF CX=0, there's no xxdecoded data to write, just exit.
;If CX went below 0 (e.g., outptr pointed BELOW OUT_BUF start),
;we have some sort of error (my code logic?).
;Ignore that also. It'll get cleaned up later.
jbe Write_Good ;error
mov bx,out_handle ;output file handle
mov ah,40h ;write to file/device
int 21h
jb Out_Err ;write error
Write_Good:
ret ;write done
;Output file write error
Out_Err:
mov dx,OFFSET err_out ;'Output file write error'
mov cx,ERR_OUT_LEN ;msg length
jmp short Fatal_Error ;common code
Write_File ENDP
;-------------------------------------------
;Read a chunk of input (xxencoded) data into our xxencode buffer.
;I don't think we'll EVER try to read past EOF (since the 'end' line
;should've been detected and program terminated).
;Let's assume that 'read past EOF' is an error of some sort and die.
;('Coding by trial and error')
Read_File PROC NEAR
mov dx,offset XXBUFF ;read into xxencode input buffer
mov cx,XXBUFFSIZE ;input buffer size
mov bx,inp_handle ;input file handle
mov ah,3Fh ;read from file/device
int 21h
jb Inp_Err ;failed
or ax,ax ;read anything?
jz Read_EOF ;nope, EOF
mov si,dx ;point to input XXBUFF start
add ax,si ;chars read+XXBUFF start
mov xxEndptr,ax ;points beyond last XXBUFF char
ret ;read done
Read_EOF:
;-------------------------------------------
;Input file read error. Error value in AL
Inp_Err:
mov dx,OFFSET err_inp ;'Input file error'
mov cx,ERR_INP_LEN ;msg length
Fatal_Error:
call Say_Error ;common code
jmp File_End_X ;terminate, error in AL v1.1
Read_File ENDP
;-------------------------------------------
;Unexpected End of File. (No 'end')
End_Err PROC NEAR
mov dx,OFFSET err_end ;'End not found'
mov cx,ERR_END_LEN ;msg length
Say_Error: ;common code
push ax ;save any error value in AL v1.1
mov bx,2 ;Std ErrOut handle
mov ah,40h ;write to file/device
int 21h
pop ax ;restore errorlevel v1.1
ret
End_Err ENDP
;using pointers beyond runtime code and/or code end for various buffers.
;This does NOT take up any space in our program.
EVEN
;LINE_IN is not used until after Init,
;so it can overwrite this code and data.
LINE_IN equ $ ;80 bytes long
;Some start-up data moved down here to minimize memory requirements.
msg_v1 DB 'This program requires DOS Version 2.0 '
DB 'or higher.',CR,LF,'$'
pr_inp DB CR,LF,'Input path/file: '
PR_INP_LEN EQU $-pr_inp
no_action db 'No action',CR,LF,'$'
;Some start-up code.
Init proc near
;First make sure we have DOS 2.0 or higher, or handles won't work.
mov ah,30h ;get DOS version
int 21h
cmp al,2 ;2.0 or above?
jae Chk_CmdLine ;yep, ok
mov dx,OFFSET msg_v1 ;'DOS 2.0 or above'
Msg_Die:
mov ah,9 ;display string
int 21h
mov ax,4C01H ;terminate, ERRORLEVEL 1
int 21H
;Now check cmd line for xxencoded source file.
Chk_CmdLine:
call Parse_CmdLine ;get cmdline target filename
jnc Open_Inp_Fil ;fine, got one
;DI -> AsciiZed filename
;0 terminator.
;Let's be nice and prompt the user for an input filename
;using normal buffered keyboard input:
mov dx,OFFSET pr_inp ;'Input/file name:' prompt
mov cx,PR_INP_LEN ;nr chars to display
mov bx,2 ;Std ErrOut handle (always to con)
mov ah,40h ;write to file or device
int 21h
;Now get the user kbd input:
mov di,offset cmd_tail-1 ;1 byte before PSP cmdline
mov byte ptr [di],80 ;tell DOS max of 80 chars
mov dx,di ;DX -> buffer
mov ah,0AH ;buffered kbd input svc
int 21H
call Parse_CmdLine ;parse the input
jnc Open_Inp_Fil ;got input (already AsciiZed)
;try to open
;DI -> AsciiZ 0
;Okay, he doesn't wanna play...
mov dx,offset no_action ;'No action'
jmp short Msg_Die ;display, terminate
Open_Inp_Fil:
mov dx,offset INP_FIL ;input file name buffer
mov ax,3D00h ;open file
int 21h
jb Open_Die ;failed, terminate w/error
mov inp_handle,ax ;save input file handle
ret ;go xxdecode
Open_Die:
jmp Inp_Err ;input file open error, die
Init endp
;Command line processing subroutine
Parse_CmdLine proc near
mov si,offset cmd_tail ;move cmd line parm
mov di,offset INP_FIL ;to our filename buffer
cld ;insure fwd
lodsb ;cmd line length byte
or al,al ;nothing there?
jz PC_NoInput ;yep, nothing there
mov ah,20H ;get a handy space
Strip_Ct:
lodsb ;next cmd line char
cmp al,ah ;gobble leading spaces,tabs, etc.
jbe Strip_Ct
;v1.1 Check for a "-o" switch on command line
cmp al,'/' ;this kind of switch? v1.1
jz GotSwitch ;yep v1.1
cmp al,'-' ;switch? v1.1
jnz NotSwitch ;nope, must be name char v1.1
GotSwitch: ; v1.1
mov dx,ax ;save this char a second v1.1
mov ax,[si] ;snarf next 2 chars v1.1
and al,5FH ;mask 1st char to uppercase v1.1
cmp ax,' O' ;O and space means switch v1.1
mov ax,dx ;restore in case not v1.1
jnz NotSwitch ;nope, must be name v1.1
;v1.1 We have the overwrite switch
not overwrite ;toggle flag to TRUE v1.1
inc si ;bump past 'o' v1.1
inc si ;and past space v1.1
lodsb ;next char should be name v1.1
Ct_Char:
cmp al,ah ;ctrl char? (e.g., CR)
jbe PC_Input ;yep, done
NotSwitch: ; v1.1
stosb ;stuff filename byte
lodsb ;snarf next cmdline char
jmp short Ct_Char ;and loop
cmp al,ah ;ctrl char? (e.g., CR)
jbe PC_Input ;yep, done
PC_NoInput:
stc ;no input, return CF set
ret
PC_Input:
mov byte ptr [di],0 ;Asciize filename input
clc ;got input, return CF clear
ret
Parse_CmdLine endp
EVEN
;INP_FIL is used by Init startup, so it must sit below the code.
INP_FIL equ $ ;80 bytes long,
; input filename buffer.
; Overwrites OUT_FIL buffer
; and xxencode buffers.
;OUT_FIL, OUT_BUF, and XXBUFF aren't used until AFTER Init completes.
;They can overwrite Init code, startup data, INP_FIL filename buffer, etc.
;However, XXBUFF and LINE_IN ARE used to get the output filename OUT_FIL),
; so XXBUFF and LINE_IN can't overwrite OUT_FIL.
;LINE_IN needs 80 bytes:
OUT_FIL equ LINE_IN + 80 ;15 bytes long,
; output filename buffer.
; Sits below LINE_IN,
; overwrites Init code/data.
OUT_BUF equ LINE_IN + 80 ;80 bytes long,
; xxdecoded data buffer.
; Sits below LINE_IN,
; overwrites OUT_FIL filename
; and Init code/data.
;OUT_BUF needs 80 bytes
XXBUFF equ OUT_BUF + 80 ; xxencoded file read buffer.
; Sits below OUT_BUF,
; uses XXBUFFSIZE bytes
logo db 'XXD v1.1',0
db 'David P Kirschbaum, Toad Hall',0
CSEG ENDS
END Xxdecode
name XXENCODE
page 55,132
title 'XXENCODE.ASM'
;
; XXENCODE.ASM -- XXEncodes a Binary File
;
; Cmd line processing, general layout from UU.ASM by Theodore A. Kaldis
;
;Author: David Kirschbaum
; Toad Hall
; kirsch@arsocomvax.socom.mil
;
;Released to Public Domain
;(That means, this is not SHAREWARE; this is not FREEWARE.
; This belongs to the classic "Public Domain", to everyone!
; I don't want any "donations", license fees, whatever.
; That means you can do ANYTHING with this you want to!
;)
;
; To assemble and link this program into the executable XXENCODE.COM:
; (It will NOT run compiled as an .EXE program!)
; MASM XXE;
; LINK XXE;
; (If you just have EXE2BIN:
; EXE2BIN XXE
; REN XXE.BIN XXENCODE.COM (or whatever)
; (If you have Public Domain EXE2COM or equivalent:
; EXE2COM XXE
; REN XXE.COM XXENCODE.COM (or whatever)
; (Delete the bogus .OBJ file.)
; (If you have TASM, do your TASM thing...)
Comment ~
v1.1, 29 Oct 89
- A user in Germany asked for file overwrite protection.
Adding output file existence checking.
HOWEVER! If you compile this sucker with the STDOUT switch enabled
(so output file goes to STDOUT, the file/device of YOUR choice),
you're on your own!
Distributed version is NOT STDOUT-enabled. It produces its own
output file name from the input file name.
- While I was at it, made all message output to STDERR (just in case
the user is confused and is using STDOUT anyway).
- Adding "-o" switch to command line options (force overwrite).
Usage now XXE [-o] filename.typ
Produces filename.XXE, overwriting any such file if it exists.
David Kirschbaum
Toad Hall
v1.0, 29 Jul 89
- Per request of Erich ...., hacking the TOADUU family to handle the newer
XXENCODE/XXDECODE protocol.
Same general idea, but only uses the ASCII character set:
+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
- Making an assumption: The last line (before the "end" line)
will be a null line (e.g., just CR/LF).
- Sep 89: Finally got the public domain XXENCODE/XXDECODE (C version)
for comparison and tests:
- XXE is leaving a "blank" line as the last line (with a single space).
XXENCODE leaves a "+" as that last line (signifying 0 length).
My "null line" assumption was correct (almost).
- When there's an "odd" number of bytes in the target file (e.g.,
not MOD 3), XXENCODE produces "trailing" characters differently
from XXE. I think there's a problem in XXENCODE's logic there,
and my process (nulling out the last characters) is correct.
It doesn't really matter since those xxencoded characters are
NOT used as part of the decoding process.
- I'm forcing the internal target file name to its actual file name
(lower case). XXENCODE and UUENCODE command line protocols force
the user to use three (3) command line parameters:
target file name
"internal" target file name
output redirection
Ugh! We're producing a .XXE type file, period!
You can recompile this code to enable "redirection" (via the DOS
'>' business) if you wish .. turn on the STDOUT flag below.
David Kirschbaum
Toad Hall
kirsch@arsocomvax.socom.mil
Comment ends ~
;-------------------------------------------
LF EQU 10
CR EQU 13
FALSE equ 0
TRUE equ NOT FALSE
READSIZE EQU 45000 ;max bytes for a binary file read.
;Small enough to fit within our
; 64Kb code/data space.
;Size must be an even divisor of 3.
LINELEN EQU 60 ;nr chars in a xxencoded line
STDOUT EQU FALSE ;change to TRUE to enable redirection
CSEG SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CSEG,DS:CSEG, ES:CSEG
org 80H ;PSP cmdline start
cmd_tail label byte
org 100H
XxEncode PROC near
jmp Start ;jump over data
IF STDOUT
usage db 'XXE [d:][\path\]binary.fil [>output] <RETURN>'
db CR,LF,'(Uses redirection)'
ELSE
usage db 'XXE [-o] [d:][\path\]binary.fil <RETURN>',CR,LF
db 'produces binary.XXE on current drive\path',CR,LF
db '(providing binary.XXE doesn''t already exist).',CR,LF ;v1.1
db '-o switch forces overwrite of existing binary.XXE' ;v1.1
ENDIF
db CR,LF ;v1.1
USAGE_LEN equ $-usage ;v1.1
msg_v1 DB 'This program requires DOS V2.0 or higher.',CR,LF ;v1.1
MSG_V1_LEN equ $ - msg_v1 ;v1.1
pr_inp DB CR,LF,'Input path/file: '
PR_INP_LEN EQU $-pr_inp
err_inp DB 'Input file error.',CR,LF
ERR_INP_LEN EQU $-err_inp
err_out DB 'Output file error.',CR,LF
ERR_OUT_LEN EQU $-err_out
;UUENCODE just had a blank line before the end (actually a single space).
;Turns out XXENCODE makes this a "null" xxencoded line
;(with a "+" (0) length byte. Not EXACTLY like UUENCODE, but ...
end_msg DB '+',CR,LF,'end',CR,LF
END_MSG_LEN EQU $-end_msg
;no_action db 'No action',CR,LF,'$' v1.1
exist$ db 'exists! Aborting!',CR,LF ;v1.1
EXIST$_LEN equ $-exist$ ;v1.1
inp_handle DW 0 ;input file handle
out_handle DW 1 ;output file handle (default StdOut)
read_count DW data_buf ;nr binary bytes read
last_flag db FALSE ;set true when partial read
overwrite db FALSE ;set true if "-o" cmdline switch v1.1
xxe_set db '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
;-------------------------------------------
Start:
; Insure we're DOS 2.0 or above (or handles won't work!)
mov ah,30h ;get DOS version
int 21h
cmp al,2 ;2.0 or above?
jae Chk_Cmd ;yep, ok
mov dx,OFFSET msg_v1 ;'DOS 2.0 or above'
mov cx,MSG_V1_LEN ;msg length v1.1
;AL has errorlevel 1 v1.1
Msg_Die:
;v1.1 mov ah,9 ;display string
;v1.1 mov ax,4C01H ;terminate, ERRORLEVEL 1
;v1.1 int 21H
call Say_Error ;write to StdErr, AL unchanged v1.1
jmp Terminate ;terminate v1.1
;-------------------------------------------
;Check our PSP command line for a target filename.
Chk_Cmd:
; Moved command line parsing to a separate subroutine
; (since we may have to do it twice)
call Parse_CmdLine ;parse PSP cmdline
jnc Open_Inp_Fil ;we have input
;(already AsciiZed)
;DI -> AsciiZ 0
; No PSP cmdline input.
; Let's prompt our user:
mov dx,OFFSET pr_inp ;'Input/file name:' prompt
mov cx,PR_INP_LEN ;nr chars to display
call Say_Error ;display to STDERR v1.1
;Get user's kbd input
; via regular DOS buffered keyboard input call
mov di,offset cmd_tail-1 ;1 byte before PSP cmdline
mov byte ptr [di],80 ;tell DOS max of 80 chars
mov dx,di ;DX -> buffer
mov ah,0AH ;buffered kbd input svc
int 21H
call Parse_CmdLine ;parse the input
jnc Open_Inp_Fil ;got input (already AsciiZed)
;try to open
;DI -> AsciiZ 0
;No command line. Show usage, exit.
mov dx,offset usage ;'Usage..'
;v1.1 mov ah,9 ;display str
;v1.1 int 21H
;v1.1 mov dx,offset no_action ;'No action'
mov cx,USAGE_LEN ;msg length v1.1
jmp short Msg_Die ;display, terminate
;We have an input filename. Open it.
Open_Inp_Fil:
mov dx,offset inp_fil ;input filename buffer
mov si,dx ;remember input filename
;buff start
mov ax,3D00h ;open file
int 21h
jnb Inp_Open ;went ok
jmp Inp_Err ;input file open error, die
;-------------------------------------------
Inp_Open:
mov inp_handle,ax ;save input file handle
;Take input file name (up to file separator) (no paths)
;move "filename.typ" into our xxencoded buffer and write to file.
;First scan for any paths, drives, etc.
;SI -> input filename buffer start
;DI -> the last filename char+1, so we can compute length.
mov cx,di ;last char+1
sub cx,si ;-start = nr chars+1
dec cx ;adjust
;We'll start at the end and scan back toward the front.
;Remember, scasb decrements DI even if it finds the scan char,
;so we'll have to adjust after the find.
mov al,'\' ;first scan for paths
std ;going backwards
repne scasb
cld ;set back forward again
jz Found_Path ;DI points to char before '\'
mov di,si ;back to start
cmp byte ptr [di+1],':' ;how about a drive?
jne Name_Start ;nope, use buffer start
;else fall thru and bump di past 'd:'
;DI's now pointing at the REAL target file name starting char
;(past the drive, paths, etc.)
;We first move the original target file name into our xxe buffer
;(which is initialized with the "start 644 " characters).
;This xxe buffer will be written as the first line of our xxencoded file.
Found_Path:
inc di ;adjust for scasb or 'd:'
inc di
Name_Start:
mov si,di ;move from input name start
mov dx,si ;save starting point a sec
mov di,offset xxe_filename ;move to within xxe buffer
OutName_Loop:
lodsb ;snarf each char
or al,al ;0 means filename end
jz OutName_Done ;done
stosb ;stuff filename char
jmp OutName_Loop ;keep going
OutName_Done:
mov ax,0A0DH ;get CR/LF
stosw ;stuff it in xxencode buffer
;target file name has now been moved into a starting xxencoded file
;text line (to include CR/LF).
IF STDOUT ;use StdOut redirection
mov cx,di ;ptr to last filename char +1
ELSE ;create 'filename.xxe'
push di ;remember that ending psn
;Now to create our output file name: filename.xxe
mov si,dx ;SI is PSP target filename start
mov di,offset out_fil ;move to output file name buffer
mov dx,di ;we'll need it here also
xxe_Name_Loop:
lodsb ;snarf each char
or al,al ;0 means filename end
jne Check_Dot ;nope
mov al,'.' ;no file type, so fake it
Check_Dot:
stosb ;stuff name char into output name
cmp al,'.' ;go up to separator
jne xxe_Name_Loop ;not yet
;We've now moved the file name (plus the '.') into our output buffer.
;Time for the type
mov ax,'xx' ;'xxe'
stosw ;stuff
mov ax,'e' ;'e', DOS AsciiZ terminator
mov [di],ax ;stuff
;DX has output filename starting ofs.
;ptr to last byte in xxe buffer is on the stack.
xor cx,cx ;normal file attrib (R/W)
;v1.1 User asked for output file overwrite protection.
; Ok .. let's check for output filename existence
; before we create it.
; DX -> AsciiZ filename
cmp overwrite,TRUE ;'-o' cmdline switch? v1.1
jz Out_Create ;that's right, overwrite if exists v1.1
mov ah,4EH ;find first v1.1
int 21H ; v1.1
cmp al,2 ;file not found? v1.1
jz Out_Create ;fine, create it v1.1
cmp al,18 ;no more files to be found? v1.1
jz Out_Create ;fine, create it v1.1
;v1.1 File exists, so we'll message and abort.
pop cx ;clear stack v1.1
mov cx,di ;last char in output name
sub cx,dx ;last -first = length v1.1
mov bx,2 ;STDERR v1.1
add cx,bx ;adjust count v1.1
mov ah,40H ;write to file/device v1.1
int 21H
mov dx,offset exist$ ;' exists! Aborting!' v1.1
mov cx,EXIST$_LEN ;msg length v1.1
mov al,5 ;fake "Access denied" errorlevel v1.1
jmp Msg_Die ;display, terminate v1.1
Out_Create:
mov ah,3CH ;create file
int 21H
pop cx ;restore xxe ptr into CX
jnb Create_Ok ;ok
jmp Out_Err ;'Output file error', die
Create_Ok:
mov out_handle,ax ;save output handle
ENDIF ;StdOut or filename.xxe
mov dx,offset xxe_out ;'start 644 filename.typ', CR/LF
sub cx,dx ;last char-buff start = bytes to write
call Write_File ;write that record
;Write_File set DI to offset xxe_out+1,BP=0
Read_Loop:
call Read_File ;do the initial binary read
jz Write_Xxe_End ;nothing read, done with input
;Read_File set SI to offset data_buf, didn't touch DI output buffer ptr,
;or BP binary byte counter.
Xxe_Loop:
;SI and BP are incrementing as we xxencode 45 bytes of binary data
;into 60 bytes of Ascii data.
mov cx,0604H ;handy constant
;CL=4,CH=6
lodsb ;hunk[1]
mov ah,al ;AH, AL=hunk[1]
shr al,1 ;quad[1] = hunk[1] SHR 2
shr al,1 ;(faster this way)
stosb ;= quad[1], stuff
lodsb ;AL=hunk[2]
mov dl,al ;save hunk[2] a sec
shl ah,cl ;hunk[1] SHL 4
shr al,cl ;hunk[2] SHR 4
add al,ah ;shifted hunk[1]+shifted hunk[2]
stosb ;= quad[2], stuff
mov ah,dl ;AH=orig hunk[2]
lodsb ;AL=hunk[3]
mov dl,al ;save hunk[3] in DL a sec
shl ah,1 ;hunk[2] SHL 2
shl ah,1 ;(faster this way)
mov cl,ch ;CL now 6
shr al,cl ;hunk[3] SHR 6
add al,ah ;shifted hunk[2]+shifted hunk[3]
stosb ;= quad[3], stuff
mov al,dl ;AL=orig hunk[3]
stosb ;= quad[4], stuff
;That 3-byte hunk is done. See if our binary buffer's empty.
;Notice we ALWAYS assume we did all 3 binary bytes.
;If we didn't (e.g., had a non-MOD 3 nr of binary bytes in our file),
;we'll correct that later with an adjustment to the binary counter
;character in the xxencoded line.
add bp,3 ;+3
; Trying to work around that "extra 4 chars"
; written to output at the 45000-byte buffer end.
; To do this, we first check to see if our xxencoded output line
; is full yet (e.g., 45 binary bytes read, 64 xxencoded chars
; ready to be output):
cmp bp,45 ;done a line of binary data yet?
jnz Chk_BuffEnd ;nope
call Write_Xxe ;stuff binary count in record,
;Asciify entire record,
;append CR/LF, write to file
;Reset BP binary counter=0,
;DI back to output buffer start +1
Chk_BuffEnd:
cmp si,read_count ;hit data end yet?
jb Xxe_Loop ;nope,not yet
;keep xxencoding input buffer
cmp last_flag,1 ;Was last read the binary file EOF?
jne Read_Loop ;nope, do another read, maybe end.
;-------------------------------------------
Write_Xxe_End:
or bp,bp ;any bytes xxencoded?
jz No_Partial_Write ;none, so no trailing to write
;In converting 3 binary bytes to 4 quad chars, we may have bumped SI
;beyond the actual number of binary bytes read.
;By subtracting the original count of bytes read from SI,
;we'll get the number of 'bogus' binary bytes in that last quad.
;Subtract that from the BP binary counter, and we'll get the TRUE
;number of binary bytes in that xxencoded line.
;It's up to the xxdecoding program to catch that difference
;and ignore the extra quads.
sub si,read_count ;check for overrun
sub bp,si ;subtract any bogus bytes
call Write_Xxe ;write partial line
No_Partial_Write:
mov dx,offset end_msg ;'end',CR/LF
mov cx,END_MSG_LEN ;nr bytes to move
call Write_File ;do the last write
;DOS normally closes all files at termination.
;Still, just to be neat...
IF NOT STDOUT ;no StdOut
mov bx,out_handle ;output file handle
mov ah,3EH ;close the file
int 21H
xor al,al ;return ERRORLEVEL 0 v1.1
ENDIF
Terminate:
mov ah,4Ch ;terminate, ERRORLEVEL ?
int 21h
XxEncode endp
;-------------------------------------------
Write_Xxe PROC NEAR
;Enter with:
; DI pointing to char beyond last xxencoded char.
; BP = binary bytes in this line
;
;Stuff CR/LF, compute line length, write to file.
mov dx,offset xxe_out ;output line start (length byte)
mov cx,di ;current output pointer
sub cx,dx ;- buffer start = nr bytes in line
;+1, but that's ok since we're adding
;the line_len byte
mov di,dx ;point to line start
;Do the last masking of the line of quads
mov ax,bp ;binary bytes in this line
mov bp,cx ;save full line length for later
mov [di],al ;stuff binary length byte
; (xxencode later)
mov bx,offset xxe_set ;set of XX characters
mov ah,3FH ;handy masking constant
;Gotta process every byte, masking, converting to XXE character.
;This includes that length byte.
Mask_Loop:
mov al,[di] ;snarf binary char
and al,ah ;3FH ;six-bit mask
xlat ;AL now has XX[al]
stosb ;stuff it back in line buffer
loop Mask_Loop ;do them all
mov cx,bp ;restore char count for bytes to write
;DI now points at char just beyond xxencoded char line
mov word ptr [di],0A0DH ;stuff CR/LF in buffer
inc cx ;add in CR/LF to length
inc cx
Write_File:
mov bx,out_handle ;output file handle
mov ah,40h ;write to file/device
int 21h
jb Out_Err ;write error
mov di,dx ;point DI back to xxe_out start
inc di ;bump past length byte
xor bp,bp ;reset byte ctr
ret ;write done
;Output file write error
Out_Err:
mov dx,OFFSET err_out ;'Output file error'
mov cx,ERR_OUT_LEN ;msg length
jmp short Fatal_Error ;common code
Write_Xxe ENDP
;-------------------------------------------
;Read a chunk of raw binary data
Read_File PROC NEAR
mov dx,offset data_buf ;into binary input buffer
mov cx,READSIZE ;nr bytes to read
mov bx,inp_handle ;input file handle
mov ah,3Fh ;read from file/device
int 21h
jb Inp_Err ;failed
;AX has nr of bytes read
mov si,dx ;SI needs offset data_buf
mov bx,dx ;buffer start
add bx,ax ;+bytes read = data end
;BX points to the next 'empty' byte (data_buf start + bytes read)
cmp ax,cx ;read a full buffer?
je Read_Full ;yep, no fiddling required
;We've read less than a buffer full. Let's make sure the last bytes
;are nulls if bytes read are not MOD 3.
mov word ptr [bx],0 ;stuff 2 nulls there
mov last_flag,1 ;turn EOF flag on
Read_Full:
mov read_count,bx ;point to data end
or ax,ax ;set flags for return
ret ;read done
;-------------------------------------------
;Input file read error. Error value in AL
Inp_Err:
mov dx,OFFSET err_inp ;'Input file error'
mov cx,ERR_INP_LEN ;msg length
;Common code added here
Fatal_Error:
call Say_Error ;display error msg, AL unchanged v1.1
jmp Terminate ;terminate
Read_File ENDP
;-------------------------------------------
Say_Error proc near
;DX -> error msg
;CX = msg len
push ax ;save AX v1.1
mov bx,2 ;Std ErrOut handle
mov ah,40h ;write to file/device
int 21h
pop ax ;restore v1.1
ret
Say_Error ENDP
; Command line processing subroutine
Parse_CmdLine proc near
mov si,offset cmd_tail ;move cmd line parm
mov di,offset inp_fil ;to our filename buffer
cld ;insure fwd
lodsb ;cmd line length byte
or al,al ;nothing there?
jz PC_NoInput ;yep, nothing there
mov ah,20H ;get a handy space
Strip_Ct:
lodsb ;next cmd line char
cmp al,ah ;gobble leading spaces,tabs, etc.
jbe Strip_Ct
;v1.1 Check for a "-o" switch on command line
cmp al,'/' ;this kind of switch? v1.1
jz GotSwitch ;yep v1.1
cmp al,'-' ;switch? v1.1
jnz NotSwitch ;nope, must be name char v1.1
GotSwitch: ; v1.1
mov dx,ax ;save this char a second v1.1
mov ax,[si] ;snarf next 2 chars v1.1
and al,5FH ;mask 1st char to uppercase v1.1
cmp ax,' O' ;O and space means switch v1.1
mov ax,dx ;restore in case not v1.1
jnz NotSwitch ;nope, must be name v1.1
;v1.1 We have the overwrite switch
not overwrite ;toggle flag to TRUE v1.1
inc si ;bump past 'o' v1.1
inc si ;and past space v1.1
lodsb ;next char should be name v1.1
Ct_Char:
cmp al,ah ;ctrl char? (e.g., CR)
jbe PC_Input ;yep, done
NotSwitch:
stosb ;stuff filename byte
lodsb ;snarf next cmdline char
jmp Ct_Char ;and loop
PC_NoInput:
stc ;no input, return CF set
ret
PC_Input:
mov byte ptr [di],0 ;Asciize filename input
clc ;got input, return CF clear
ret
Parse_CmdLine endp
;using pointers here at code end for various buffers.
;the xxe_out buffer is normally 60 xxencoded chars, plus CR/LF.
;It's initialized with the default xxencode file header.
;The magic number '644' has something to do with file attributes
;on a Unix system.
EVEN
xxe_out db 'begin 644 ' ;first write contains this + name
;The rest of these buffers don't take any code space.
xxe_filename equ $ ;where we move filename.xxe
;Leave room for LINELEN+2 chars for xxe_out buffer.
inp_fil equ xxe_out + LINELEN +2 ;80 chars long
;Leave room for 80 chars for inp_fil filename buffer.
out_fil equ inp_fil + 80 ;15 bytes long
data_buf equ out_fil ;leave room for xxe_out
logo db 'XXENCODE v1.1',0
db 'David P Kirschbaum, Toad Hall, '
db 'Given to the public domain',0
CSEG ENDS
END XxEncode