;---------------------------------------------------------------------------- ; UUENCODE program, version 1.1 ; ; Copyright 1995, David S. Peterson ; ; This program may be used and distributed freely as long as this copyright ; notice is not removed. Feel free to use sections of code from this program ; as part of your own programs, but please leave this file intact. I am ; reasonably certain that this program doesn't have any major bugs, but its ; use is at your own risk. If you find any bugs in this program, or have any ; questions or comments, please feel free to send me some email or write me a ; letter. My email address is: ; ; dspeter1@eos.ncsu.edu ; ; and my mailing address is: ; ; David S. Peterson ; 3717 Williamsborough Ct. ; Raleigh, NC 27609 ; USA ; ; If you like this program, feel free to send me a postcard from your home ; town. ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; Changes in Version 1.1: ; ; 1. Program modified so that encoded output file no longer contains any ; space characters. All characters which were formerly written as ; spaces are now written as ` characters (backwards single quote, ; ASCII value 96). With this modification, mail programs which strip ; trailing spaces from files will no longer cause problems. ; ; 2. The character at the beginning of each line specifying the number of ; encoded characters in the line will always represent a nonnegative ; number when decoded. This change is probably not very significant, ; but it could prevent one potential problem. If someone wrote a ; uudecode program that treats the decoded value of the first ; character in each line as an unsigned value, errors could result ; from attempting to decode a file encoded using version 1.0 of this ; program. ; ; 3. Copyright message changed. ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; Program should be linked to produce a COM file. ; ; Usage: uuencode INFILE OUTFILE ; ; INFILE and OUTFILE can be full pathnames such as C:\bin\prog.exe ;---------------------------------------------------------------------------- TAB EQU 09h SPACE EQU 20h CR EQU 0Dh LF EQU 0Ah IBUF_SIZE EQU 30720 ;file input buffer size (30 k) OBUF_SIZE EQU 30720 ;file output buffer size (30 k) uuencode SEGMENT PARA 'CODE' ORG 80h args DB 128 DUP (?) ;command line args in PSP ORG 100h ASSUME CS: uuencode, DS: uuencode ASSUME ES: uuencode, SS: uuencode start: CALL parse_args ;parse command line args CMP BYTE PTR argc, 2 JE do_encode ;disp usage msg if not 2 args MOV AH, 09h LEA DX, usage INT 21h MOV AL, 1 ;exit code JMP SHORT exit do_encode: CALL encode ;returns with exit code in AL exit: MOV AH, 4Ch INT 21h ;terminate program ;---------------------------------------------------------------------------- ; PROC encode: ; ; encode file ; ; NOTE: No registers need to be saved and restored, since this subroutine is ; called immediately before the program exits. Also, since this ; subroutine has no local variables or parameters on the stack, a stack ; frame doesn't need to be set up, and BP can be used as a general ; purpose register. ; ; The begin line of the output file will have the following format: ; ; begin 600 [filename] ; ; The number 600 specifies the permissions the file will have if it is ; decoded on a UNIX system. The number gets treated as an octal value ; and corresponds to the following permissions: ; ; user : read, write, (no execute) ; group: none ; other: none ; ; These permissions are hardcoded into the program because MS-DOS does ; not specify permissions for files. If the above permissions are ; unsatisfactory, they can be changed with chmod once the encoded file ; is transferred to the UNIX system and decoded. An alternative would ; be to modify the permissions in this source code and reassemble it. ; Of course, if the encoded file is to be decoded on a PC or some ; other non-UNIX system, the permissions are ignored during decoding. ; ; global variables used: ; ; if_handle : stores handle for input file ; of_handle : stores handle for output file ; obuf : output file buffer ; ibuf_n : # of chars in ibuf (file input buffer) ; ibuf_p : ptr to next char in ibuf (file input buffer) ; errno : contains error codes ; ; return value: in AL: 1 if error occurred or 0 if no error ;---------------------------------------------------------------------------- encode PROC NEAR LOCALS @@ MOV AX, 3D00h MOV DX, argv ;ptr to input filename in DX INT 21h ;open input file for reading JNC @@cr_outfile MOV SI, argv ;filename for error msg XOR CX, CX ;no files to close JMP @@rtn_error ;return with error msg @@cr_outfile: MOV if_handle, AX ;save file handle MOV AH, 5Bh MOV CX, 0020h ;mode = archive MOV DX, argv[2] ;ptr to output file in DX INT 21h ;create output file (new file) JNC @@cr_success MOV SI, argv[2] ;filename for error msg MOV CX, 0100h JMP @@rtn_error ;return with error msg @@cr_success: MOV of_handle, AX ;save file handle LEA SI, beg LEA DI, obuf ;[DI] = next position in obuf MOV CX, 5 MOV DX, CX SHL DX, 1 ;DX will indicate # chars in obuf CLD REP MOVSW ;put 'begin 600 ' in obuf MOV SI, argv ;[SI] = inp. filename (with path) CALL excl_path ;separate filename from pathname ADD DX, CX ;CX chars will be added to obuf CLD ;clear DF for rest of subroutine JCXZ @@wrt_CR_LF @@get_char: LODSB ;get char from filename CMP AL, 'A' JB @@put_char CMP AL, 'Z' JA @@put_char ADD AL, 20h ;convert to lowercase @@put_char: STOSB ;put char in obuf LOOP @@get_char ;loop puts input filename in obuf @@wrt_CR_LF: MOV AX, 0A0Dh ;AL = CR, AH = LF STOSW ADD DX, 2 ;... in obuf XOR CX, CX ;CX = # chars in ibuf @@read_data: CMP CX, 45 ;start of outer loop JAE @@read_ibuf CALL fill_ibuf CMP AL, 0 JE @@test_eof MOV CH, 03h ;close both files JMP @@cl_files ;return on error (ret val in AL) @@long_jmp_1: JMP @@do_end ;JMP is > 127 bytes forward @@long_jmp_2: JMP @@flush_err ; " " " @@test_eof: JCXZ @@long_jmp_1 ;exit outer loop if ibuf empty MOV BX, CX ;set BX to # of chars to encode CMP BX, 45 JB @@here @@read_ibuf: MOV BX, 45 ;encode next 45 chars @@here: MOV SI, BP ;[SI] = 1st char to encode ADD BP, BX ;[BP] = next unread char in ibuf SUB CX, BX ;CX = # chars left in ibuf CMP DX, OBUF_SIZE - 63 JBE @@do_encode CALL flush_obuf ;flush output buffer CMP BH, 0 JNE @@long_jmp_2 ;jump on flush error @@do_encode: MOV AL, BL ;AL = # chars to encode (45 max.) CMP AL, 0 JG @@wrt_num XOR AL, AL ;make sure AL is nonnegative @@wrt_num: AND AL, 77o JNZ @@1 ADD AL, 64 ;encode as ` char @@1: ADD AL, SPACE STOSB ;store AL ... INC DX ;... in obuf CMP BL, 0 JLE @@add_CR_LF ;skip inner loop if BL <= 0 EVEN NOP @@inner_loop: LODSB MOV AH, AL SHR AL, 1 SHR AL, 1 AND AL, 77o JNZ @@2 ADD AL, 64 ;encode as ` char @@2: ADD AL, SPACE STOSB ;store AL ... INC DX ;... in obuf MOV AL, AH SHL AL, 1 SHL AL, 1 SHL AL, 1 SHL AL, 1 AND AL, 60o MOV BH, AL LODSB EVEN MOV AH, AL SHR AL, 1 SHR AL, 1 SHR AL, 1 SHR AL, 1 AND AL, 17o OR AL, BH AND AL, 77o JNZ @@3 ADD AL, 64 ;encode as ` char @@3: ADD AL, SPACE STOSB ;store AL ... INC DX ;... in obuf MOV AL, AH SHL AL, 1 SHL AL, 1 AND AL, 74o MOV BH, AL LODSB EVEN MOV AH, AL SHR AL, 1 SHR AL, 1 SHR AL, 1 SHR AL, 1 SHR AL, 1 SHR AL, 1 AND AL, 3o OR AL, BH AND AL, 77o JNZ @@4 ADD AL, 64 ;encode as ` char @@4: ADD AL, SPACE STOSB ;store AL ... INC DX ;... in obuf MOV AL, AH AND AL, 77o JNZ @@5 ADD AL, 64 ;encode as ` char @@5: ADD AL, SPACE STOSB ;store AL ... INC DX ;... in obuf SUB BL, 3 JG @@inner_loop ;loop again if BL > 0 @@add_CR_LF: MOV AX, 0A0Dh ;AL = CR, AH = LF STOSW ADD DX, 2 JMP @@read_data ;repeat outer loop @@do_end: CMP DX, OBUF_SIZE - 8 JBE @@wrt_end CALL flush_obuf ;flush output buffer CMP BH, 0 JNE @@flush_err @@wrt_end: LEA SI, endline MOV CX, 8 ADD DX, CX SHR CX, 1 REP MOVSW ;copy end line into obuf CALL flush_obuf ;write any remaining chars CMP BH, 0 JNE @@flush_err MOV CX, 0301h XOR DH, DH ;return 0 (success) JMP SHORT @@cl_files @@flush_err: MOV CX, 0300h JMP SHORT @@set_DH @@rtn_error: MOV BYTE PTR errno, AL PUSH SI CALL disp_error @@set_DH: MOV DH, 1 ;return 1 (error) @@cl_files: MOV DL, CH AND DL, 01h JZ @@test_outf MOV AH, 3Eh MOV BX, if_handle INT 21h @@test_outf: AND CH, 02h JZ @@rtn MOV AH, 3Eh MOV BX, of_handle INT 21h JNC @@rtn CMP CL, 01h JNE @@rtn ;skip if msg already displayed MOV BYTE PTR errno, AL MOV BX, argv[2] PUSH BX CALL disp_error @@rtn: MOV AL, DH ;return value in AL RET encode ENDP ;---------------------------------------------------------------------------- ; end PROC encode ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; PROC excl_path: ; ; find start of actual filename in pathname pointed to by SI; on exit, SI ; will point to first char in filename, and CX will contain length of ; filename ; ; example: pathname = 'c:\bin\abc.exe' ; ^ ; | ; +--- SI will point here on exit ; ; CX will equal 7 (length of 'abc.exe') ; ; parameters: ; ; SI: ptr to pathname; will point to actual filename within pathname on ; exit ; ; return value: in CX: length of actual filename ;---------------------------------------------------------------------------- excl_path PROC NEAR LOCALS @@ PUSH AX PUSH DX PUSH DI XOR AL, AL ;NULL char in AL MOV DI, SI MOV CX, 128 CLD REPNE SCASB ;look for terminating null char SUB DI, 2 ;[DI] = last char in dir path MOV SI, DI SUB CX, 128 NEG CX ;CX = length of pathstring + 1 MOV DI, CX DEC DI ;DI = length of pathstring STD ;scan backwards from end @@find_char: LODSB ;loop searches for ':' or '\', CMP AL, '\' ;which will be last char in dir LAHF ;path; when loop exits, CX will MOV DH, AH ;equal the # of chars in dir path CMP AL, ':' LAHF OR DH, AH AND DH, 40h LOOPZ @@find_char ADD SI, 2 ;[SI] = 1st char in filename SUB DI, CX MOV CX, DI ;CX = # of chars in filename POP DI POP DX POP AX RET excl_path ENDP ;---------------------------------------------------------------------------- ; end PROC excl_path ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; PROC fill_ibuf: ; ; attempt to fill file input buffer; called from subroutine 'encode' when ; buffer is empty or almost empty ; ; parameters: ; ; CX: # of chars in input buffer; gets updated on return ; BP: ptr to next char in input buffer; gets updated on return ; ; global variables used: ; ; argv : stores input filename ; if_handle: file handle of input file ; ibuf : input file buffer ; errno : stores error codes ; ; return value: in AL: 0 on success, 1 on error ;---------------------------------------------------------------------------- fill_ibuf PROC NEAR LOCALS @@ PUSH BX PUSH DX PUSH SI PUSH DI PUSH AX MOV AX, CX ;save value in CX MOV SI, BP LEA DI, ibuf REP MOVSB ;move chars to start of ibuf MOV CX, IBUF_SIZE SUB CX, AX ;# chars to read from file in CX LEA DX, ibuf ADD DX, AX ;[DX] = 1st empty place in ibuf MOV SI, AX ;save value in AX MOV AH, 3Fh MOV BX, if_handle INT 21h ;attempt to fill ibuf JNC @@set_vars MOV BYTE PTR errno, AL ;error reading data from disk MOV BX, argv ;ptr to input filename in BX PUSH BX CALL disp_error ;display error msg MOV BL, 1 ;return 1 (error) JMP SHORT @@rtn @@set_vars: MOV CX, AX ADD CX, SI ;CX = # chars in input buffer LEA BP, ibuf ;[BP] = next unread char in ibuf XOR BL, BL ;return 0 (success) @@rtn: POP AX MOV AL, BL POP DI POP SI POP DX POP BX RET fill_ibuf ENDP ;---------------------------------------------------------------------------- ; end PROC fill_ibuf ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; PROC flush_obuf: ; ; flush file output buffer; called from subroutine 'encode' ; ; parameters: ; ; DX: # of chars in output buffer; gets updated on return ; DI: points to next available position in obuf; gets updated on return ; ; global variables used: ; ; argv[2] : stores ptr to output filename ; of_handle: file handle of output file ; obuf : output file buffer ; errno : stores error codes ; ; return value: in BH: 0 on success, 1 on error ;---------------------------------------------------------------------------- flush_obuf PROC NEAR LOCALS @@ PUSH AX PUSH CX PUSH BX MOV AH, 40h MOV BX, of_handle MOV CX, DX LEA DX, obuf INT 21h ;write buffer to disk JNC @@test_full MOV BYTE PTR errno, AL ;error writing data to disk @@disp_msg: MOV BX, argv[2] ;ptr to output filename in BX PUSH BX CALL disp_error ;display error msg MOV CL, 1 ;return 1 (error) JMP SHORT @@rtn @@test_full: CMP AX, CX JE @@set_vars MOV BYTE PTR errno, 27h ;disk full JMP SHORT @@disp_msg @@set_vars: XOR DX, DX ;0 chars in obuf LEA DI, obuf ;[DI] = start of obuf MOV CL, DL ;return 0 (success) @@rtn: POP BX MOV BH, CL POP CX POP AX RET flush_obuf ENDP ;---------------------------------------------------------------------------- ; end PROC flush_obuf ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; PROC parse_args: ; ; parse command line argument string in PSP, first converting any tabs to ; spaces and any lowercase letters to uppercase; add NULL char (ASCII value ; 0) to end of each arg; store pointers to args in array argv and store # of ; args in argc ; ; NOTE: there is no need to save and restore registers, since this will ; always be the first subroutine that gets called ; ; parameters: none ; ; global variables used: ; ; args: section of PSP containing command line argument string ; argc: on exit, will equal # of args found ; argv: on exit, will contain array of pointers to args in PSP ;---------------------------------------------------------------------------- parse_args PROC NEAR LOCALS @@ CLD ;clear DF for entire subroutine LEA DI, args ;[DI] = length byte for args str. XOR CH, CH MOV CL, [DI] ;CX = # chars in args string JCXZ @@clear_BX INC DI ;[DI] = first char in args string MOV SI, DI MOV BX, CX ;save CX MOV DX, DI ;save DI @@convert: LODSB CMP AL, 'a' JB @@do_tabs CMP AL, 'z' JA @@do_tabs SUB AL, 20h ;convert to uppercase @@do_tabs: CMP AL, TAB JNE @@put_char MOV AL, SPACE ;convert TAB to SPACE @@put_char: STOSB LOOP @@convert MOV DI, DX ;restore DI MOV AL, TAB MOV DL, SPACE XCHG AX, DX ;SPACE in AL MOV CX, BX ;restore CX INC CX @@clear_BX: XOR BX, BX ;use BX to index argv array MOV DL, BL ;NULL char in DL @@lp: REPE SCASB ;look for next nonspace char JCXZ @@lp_done ;jump if no more nonspace chars DEC DI MOV argv[BX], DI ;ptr to arg in argv INC DI ADD BX, 2 REPNE SCASB ;look for next SPACE char DEC DI XCHG AX, DX ;NULL char in AL STOSB ;add treminating NULL char XCHG AX, DX ;SPACE in AL JCXZ @@lp_done ;jump if no more space chars JMP SHORT @@lp ;continue parsing args @@lp_done: SHR BL, 1 MOV argc, BL ;# args found in argc RET parse_args ENDP ;---------------------------------------------------------------------------- ; end PROC parse_args ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; PROC disp_error: ; ; display error message for file find ; ; NOTE: Since the path name which just failed is an ASCIIZ (NULL terminated) ; string, we can't print it with function 09h of INT 21h. This is ; because the function expects a $ terminated string and the pathname ; might have a $ in it. Therefore, the string is printed by repeatedly ; calling function 02h of INT 21h (display char). This isn't very ; efficient, but who cares, since all we are printing is one string. ; ; parameters on stack: ; ; pathname: pointer to ASCIIZ string containing pathname ; ; global variables used: ; ; errno: error code passed in this variable ;---------------------------------------------------------------------------- disp_error PROC NEAR ARG pathname: WORD = ARG_N LOCALS @@ PUSH BP MOV BP, SP PUSH AX PUSH BX PUSH DX MOV AH, 09h LEA DX, uu INT 21h ;display 'uudecode: ' MOV BX, pathname ;[BX] = path string @@do_char: MOV DL, [BX] ;get char from path string CMP DL, 0 JE @@disp_err ;exit loop if NULL char MOV AH, 02h INT 21h ;display char INC BX ;[BX] = next char in path string JMP SHORT @@do_char @@disp_err: MOV AH, 09h XOR BH, BH MOV BL, errno SHL BL, 1 MOV DX, errors[BX] INT 21h ;disp error msg string POP DX POP BX POP AX POP BP RET ARG_N disp_error ENDP ;---------------------------------------------------------------------------- ; end PROC disp_error ;---------------------------------------------------------------------------- usage DB 'Usage: uuencode INFILE OUTFILE$' uu DB 'uuencode: $' EVENDATA beg DB 'begin 600 ' endline DB 60h, CR, LF, 'end', CR, LF error_00h DB '$' error_01h DB ': Invalid function code', CR, LF, '$' error_02h DB ': File not found', CR, LF, '$' error_03h DB ': Path not found', CR, LF, '$' error_04h DB ': Too many open files', CR, LF, '$' error_05h DB ': Access denied', CR, LF, '$' error_08h DB ': Out of memory', CR, LF, '$' error_0Fh DB ': Invalid drive', CR, LF, '$' error_13h DB ': Disk is write protected', CR, LF, '$' error_14h DB ': Bad disk unit', CR, LF, '$' error_15h DB ': Drive not ready', CR, LF, '$' error_17h DB ': CRC error', CR, LF, '$' error_19h DB ': Seek error', CR, LF, '$' error_1Ah DB ': Not a DOS disk', CR, LF, '$' error_1Bh DB ': Sector not found', CR, LF, '$' error_1Fh DB ': General failure', CR, LF, '$' error_27h DB ': Disk is full', CR, LF, '$' error_50h DB ': File already exists', CR, LF, '$' error_52h DB ': Cannot make directory entry', CR, LF, '$' error_54h DB ': Out of structures', CR, LF, '$' errors DW error_00h, error_01h, error_02h, error_03h, error_04h DW error_05h, error_00h, error_00h, error_08h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_0Fh, error_00h, error_00h, error_00h, error_13h DW error_14h, error_15h, error_00h, error_17h, error_00h DW error_19h, error_1Ah, error_1Bh, error_00h, error_00h DW error_00h, error_1Fh, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_27h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_00h, error_00h, error_00h, error_00h, error_00h DW error_50h, error_00h, error_52h, error_00h, error_54h version DB ' uuencode version 1.1 ', CR, LF copyright DB ' Copyright 1995, David S. Peterson' EVENDATA errno DB ? ;keeps track of error codes EVENDATA argc DB ? ;# of command line args EVENDATA argv DW 64 DUP (?) ;array of pointers to args in PSP if_handle DW ? ;input file handle of_handle DW ? ;output file handle EVENDATA ibuf DB IBUF_SIZE DUP (?) ;file input buffer EVENDATA obuf DB OBUF_SIZE DUP (?) ;file output buffer uuencode ENDS END start