title UUENCODE - UUDECODE maclib base80 ; CP/M-80 implementation of UNIX UUENCODE and UUDECODE ; ; UUENCODE converts binary to ASCII ; UUDECODE converts ASCII to binary ; ; Usage UU -E infile {outfile} {number mode} ; UU -D {infile} {outfile} ; ; Wildcards allowed on -D option request ; ; Copyright (c) Werner Cirsovius ; Hohe Weide 44 ; 20253 Hamburg ; Phone:(+49)40/4223247 ; ; Version 1.6 May 1994 @DEBUG equ FALSE ;@DEBUG equ TRUE ; Give some decode information $$Prg macro db 'UU' endm $$Ver macro db '1.6 May 1994' endm entry $memry entry UUENCODE,UUDECODE ext skpblk,UPPCON,tstdig,lowlin,decout,comp3,strcn0 ext parse,getver,wcard,cmdarg,curdrv,dirmax,files ext string,crlf,filnam,conout,combrk,condir,defio ext open,create,delete,close,dskwrt,emplin,fillin ext dskput,dskget,puteof,wrfcb,wrbuf,rdbfp,wrbfp ext setdma MINpar equ 2 MAXpar equ 4 one equ 0 ; Note IX, IY relative two equ 1 ; dto. three equ 2 ; dto. four equ 3 ; dto. Second equ 2 Third equ 3 Hunkbyt equ 3 ChapByt equ 4 ChpLine equ 60 UUMASK equ 00111111b offset equ ' ' lin.len equ 80 num.len equ 16 IO.RAM equ 1000b Mod4 equ 11b Cr.Lf equ 2 ; .string equ 9 ; Fin equ FCB ; ; Give error message on basic malfunction ; ErrBrk: ld c,.string call BDOS ; .. directly via OS jp OS ; ; ++++++++++++++++++++++++++++ ; +**************************+ ; +***** Start UUxxCODE *****+ ; +**************************+ ; ++++++++++++++++++++++++++++ ; start: sub a ; Test Z80 CPU ld de,$ill.CPU jp pe,ErrBrk ; .. nope call getver ; Check right OS ld de,$ill.OS jr c,ErrBrk ; .. nope ld de,$vers call string ; Tell who we are ld hl,Fout ld (wrfcb),hl ; Init output file ld hl,dma.out ld (wrbuf),hl ld hl,($memry) ld (PB),hl ; Init buffer ld sp,(BDOS+1) ; Get stack ld b,MAXpar ld de,CCPlen ld hl,ARGV call cmdarg ; Get parameters jr c,help ; .. nothing or too much found ld (ARGC),a ; Save length ld b,a call get.opt ; Get option jr nz,help ; .. none here cp 'E' ; Test UUENCODE ld a,MINpar+2 jr z,par.. ; .. yeap ld a,b dec a ; Test empty line jr z,AllWild ld a,MINpar+1 par..: cp b ; Test parameter count jr c,help ; .. too many call GetInFile ; Load input file jr c,IsWild ; .. wild card ld hl,OS ld (ErrAdr),hl ; Set error return ld a,(parsel) ; Get task cp 'E' ld a,(ARGC) push af call z,UUENCODE pop af ld (ErrStk),sp ; .. save stack call nz,UUDECODE call Statistic ; Be verbose jp OS ; ; Give little help ; help: ld de,$help OS.str: call string ; .. tell jp OS ; ; Process wild cards ; Level 1 : Got no file, set wildcard ; AllWild: ld de,Fin+1 ld hl,???F??? ld bc,.fname+.fext ldir ; .. set wild ; ; Level 2 : Got wildcard file ; IsWild: ld a,TRUE ld (W???flg),a ; Set wild card flag ld de,Fin+.fdrv+.fname ld a,(de) ; Test wild card extension cp '?' jr nz,No???ext ld hl,defext ld bc,.fext ldir ; .. set extension No???ext: call curdrv ; Get logged drive ld (LogDrv),a call dirmax ; Get max count ld c,l ld b,h ld hl,($memry) inc h ; Get next page ld de,Fin call files ; Get files ld de,$nofile jr c,OS.str ; .. none ld (FileCnt),bc ; .. save result ld (FilePtr),hl ld hl,WildNext ld (ErrAdr),hl ; Change return address UUD.Loop: ld de,Fin ld hl,(FilePtr) ld a,(LogDrv) inc a ld (hl),a ; Set drive ld bc,.fdrv+.fname+.fext ldir ; .. unpack name ld (FilePtr),hl call Reset ; Reset file ld (ErrStk),sp ; .. save stack ld a,second call UUDECODE ; .. start call Statistic ; Give statistic call crlf WildNext: call crlf ld a,RecLng ld (rdbfp),a ; Force new read xor a ld (wrbfp),a ; .. clear write buffer ld hl,(FileCnt) ; Get count dec hl ld (FileCnt),hl ld a,l ; Test more or h jr nz,UUD.Loop jp OS ; .. that's it ; ; ################################# ; # Task binary -->> ASCII coding # ; ################################# ; UUENCODE: cp second ; Test only filename jr z,UE.copf cp third ; Test file and extension jr z,UE.get2? call numget ; Get number jr get.out.F ; .. and file UE.get2?: ld hl,(ARGV+2*2) ; Get parameter ld a,(hl) call tstdig ; Test digit jr c,get.out.F ; .. yes call numset ; Set number jr UE.copf ; .. get file from 1st name get.out.F: call Get2nd ; Get output file ld a,(Fout+1) ; Test name here cp ' ' jr z,UE.copf ld de,Fout+.fdrv+.fname ld a,(de) ; Test extension here cp ' ' jr nz,UE.beg jr UE.ext UE.copf: ld de,Fout+1 ld hl,Fin+1 ld bc,.fname ldir ; Set file name UE.ext: ld hl,defext ld bc,.fext ldir ; .. and extension UE.beg: call create.out ; Create file ld de,$encode ld hl,Fin+1 call str.fil ; Tell encoding ld de,$toFile ld hl,Fout+1 call str.fil call crlf ld de,header call Fstring ; Print header ld de,(@num) call Fstring ld a,' ' call ..wrt ld hl,($memry) ld a,(defio) push af ld a,IO.RAM ld (defio),a ; Set RAM ld de,Fin+1 call filnam ; Save name ld (hl),0 pop af ld (defio),a ld de,($memry) push de ld b,0 call lowlin ; Convert to lower case pop de call Fstring call Fcrlf ; .. close line UE.loop: call encode1 ; .. encode jr nc,UE.loop ; Test end of file call E.term ; Terminate session ret ; ; The ENCODE task ; encode1: ld a,(numbytes) ; Get count cp Hunkbyt call z,flushhunk ; Flush bytes if filled call dskget ; Get byte ld hl,(hunk.ptr) ld (hl),0 ret c ; .. end of file ld (hl),a ; Set byte inc hl ld (hunk.ptr),hl ld hl,numbytes inc (hl) ; Bump count ret ; ; Expand hunk ; flushhunk: ld a,(linelength) ; Test line filled cp ChpLine call z,flushline ; Empty line if so ld ix,chars ld iy,hunk ld a,(iy+one) srl a ; 6 bits of 1st byte srl a ld (ix+one),a ld a,(iy+one) add a,a ; Next bits add a,a add a,a add a,a ld b,a ld a,(iy+two) ; .. from next srl a srl a srl a srl a add a,b ; Combine ld (ix+two),a ld a,(iy+two) add a,a ; Next bits add a,a ld b,a ld a,(iy+three) ; From last byte srl a srl a srl a srl a srl a srl a add a,b ld (ix+three),a ld a,(iy+three) ; Mask last and UUMASK ld (ix+four),a ld de,(line.ptr) ld b,ChapByt ld hl,linelength f.H: ld a,(ix+one) ; Get byte and UUMASK ; Mask add a,offset ; Add offset ld (de),a ; Save inc de inc ix inc (hl) djnz f.H ld (line.ptr),de ld hl,bytesInLine ld a,(numbytes) add a,(hl) ; Get total count ld (hl),a xor a ld (numbytes),a ; Clear a bit ld hl,hunk ld (hunk.ptr),hl ret ; ; Flush a line ; flushline: call action ; Display action ld a,(bytesInLine) add a,offset call writeout ; Write count ld a,(linelength) or a ; Test empty line jr z,f..ex ld b,a ld hl,line f.L: ld a,(hl) call writeout ; Write line inc hl djnz f.L xor a f..ex: ld (linelength),a ; Clear a bit ld (bytesInLine),a ld hl,line ld (line.ptr),hl call Fcrlf ; .. close line ret ; ; Write a character ; writeout: cp ' ' ; Test blank jr nz,..wrt ; .. no ld a,'`' ; .. map it ..wrt: call dskput ret nc ; Check error wrt.err: ld de,(wrfcb) ; Get FCB call close ; .. close file call c,delete ; .. delete if not closeable ld de,$wrt.err jp OS.str ; ; Close file ; E.term: ld a,(numbytes) ; Test bytes in buffer or a call nz,flushhunk ld a,(linelength) ; Test bytes in line or a call nz,flushline call flushline ; Last output ld de,trailer call Fstring call Fcrlf term..: call puteof close..: ld de,Fout call nc,close ; .. close file jr c,wrt.err ; .. error ret ; ; ################################# ; # Task ASCII -->> binary coding # ; ################################# ; UUDECODE: cp second ; Test file name ld a,TRUE jr z,UD.set call Get2nd ; Get second file name ld a,(Fout+1) cp ' ' ; Test name given ld a,TRUE jr z,UD.set ld a,FALSE ; .. file ok UD.set: ld (GetFlg),a ; Set mode for output file ld de,$decode ld hl,Fin+1 call str.fil ; Tell encoding call crlf call GetOutFile ; Get output file ld hl,0 ld (linenum),hl ; Clear line count UD.loop: ld de,line call Nextline ; Read line call CheckLine ; Test line ok jr c,UD.end ; .. end of file jr z,UD.loop ; .. empty line call DecodeLine ; Decode line jr UD.loop UD.end: call D.term ; Terminate file ret ; ; Read line from file ; ENTRY : Reg DE points to line ; EXIT : Carry set on end of file ; NextLine: IF @DEBUG push de dec de ; Fix to length call fillin ; Fill line pop de push af push de ld l,a ld h,0 ld b,a push bc sub Cr.Lf ; Strip off CR LF ld (LinLen),a ; Save length ld de,$num$ ld b,eot call decout ; Get length ld de,$num$ call string ld a,':' call conout pop bc pop hl ..nx.loop: ld a,(hl) call conout ; Echo line inc hl djnz ..nx.loop ELSE dec de ; Fix to length call fillin ; Fill line push af sub Cr.Lf ; Strip off CR LF ld (LinLen),a ; Save length call action ; Indicate line read ENDIF ; @DEBUG ld hl,(linenum) inc hl ld (linenum),hl ; Bump line pop af ret ; ; Check legal start of line ; EXIT : Carry set if illegal ; Zero set on empty line ; CheckLine: ld a,(LinLen) ; Test empty or a jr z,EmpWarn ; .. give warning ld a,(line) cp ' ' ; Test illegal scf ret z cp '`' ; Test illegal scf ret z ld a,1 or a ; .. set ok ret EmpWarn: ld de,$warning call string ld de,$blank call Warning ; Give warning xor a ; .. set return ret ; ; Abort program ; ENTRY : Reg DE points to message ; Abort: call crlf call Warning ; .. give warning ld de,Fout ld a,(CR.flg) cp TRUE ; Test file created call z,delete ; Delete if so ld sp,(ErrStk) ; Get back stack ld hl,(ErrAdr) jp (hl) ; .. exit ; ; Give warning ; Warning: ld hl,(linenum) ; Test any line read ld a,l or h jr z,skp.line push de ld de,$line call string ld de,$num$ ld b,eot call decout ; Get line number ld de,$num$ call string ld de,$delim call string pop de skp.line: call string ; Give message ret ; ; Get output file ; ..none: ld de,$none jr Abort GetOutFile: ld de,$header call NextLine ; Read line jr c,..none ; .. empty, error sub Cr.Lf ; .. note CR, LF ld b,a ld de,$header ld hl,header ld c,hd.len call comp3 ; Find 'begin' jr nz,GetOutFile call ParseHeader ; Get file ret ; ; Get word from header line ; ENTRY : Reg HL points to header line ; NextWord: ex de,hl call skpblk ; .. no blanks ex de,hl ld de,$inclomp ld a,(hl) ; Test end of line or a jr z,Abort ; .. error ld de,($memry) ; Get data field ld (PB),de next..: ld a,(hl) ; Get word cp ' ' ; .. till blank jr z,..word ld (de),a ; Unpack inc de inc hl jr next.. ..word: xor a ld (de),a ; Set end ret ; ; Parse header ; ENTRY : Reg B holds length of line ; ParseHeader: ld c,b ld b,0 ld hl,$header add hl,bc ld (hl),' ' ; Set blank at the end inc hl ld (hl),0 ld hl,$header+hd.len call NextWord ; .. skip mode call NextWord ; Get output file ld a,(GetFlg) ; Test output file given cp TRUE call z,.parse.F ; Build from source call create.out ; .. create ret ; ; Parse file - let drive intact ; .parse.F: ld a,(Fout) ; Get drive push af ; .. save call truncate? ; .. test file name be truncated call parse.F ; Parse pop af or a ; Test default ret z ; .. skip ld (Fout),a ; .. set it ret ; ; Test file name read too long ; truncate?: ld hl,(PB) ; Get pointer ld ix,$$PB ld c,0 ; Clear flag ld e,'.' ld b,.fname ; Set length call ..trunc ; Truncate ld e,0 ld b,.fext call ..trunc ld (ix+0),null bit 0,c ; Test bit set ret z ; .. nope ld de,$warning call string ; Tell warning ld de,(PB) call strcn0 ld de,$trunc call string ld de,$$PB call strcn0 call crlf ld de,(PB) ld hl,$$PB cp.trunc: ld a,(hl) ldi ; .. unpack or a jr nz,cp.trunc ret ; ; Truncate file name if necessary ; ENTRY Reg HL points to source ; Reg IX points to destination ; Reg B holds length ; ..trunc: ld a,(hl) or a ; Test end ret z ; .. yeap call st.fcb cp e ; .. or delimiter ret z djnz ..trunc ld a,(hl) ; .. may be end or a ret z ; .. yeap cp e ; .. or delimiter jr nz,..skp.tr st.fcb: ld (ix+0),a ; .. save inc ix inc hl ret ..skp.tr: set 0,c ; .. set overflow skp.trunc: ld a,(hl) or a ; Test end ret z ; .. yeap inc hl cp e ; .. or delimiter jr nz,skp.trunc ld (ix+0),e inc ix ret ; ; Decode one line ; DecodeLine: xor a ld (LineIndex),a ; Init a bit ld hl,line ld (line.ptr),hl ld a,Hunkbyt ld (byteNum),a call nextch ; Get character ret z ld b,a decode..: push bc call DecodeByte ; Pack bytes pop bc djnz decode.. ret ; ; Decode a byte ; DecodeByte: ld a,(byteNum) ; Test counter cp Hunkbyt call z,GetNextHunk ; Fill hunk ld a,(byteNum) ld hl,(hunk.ptr) ld a,(hl) call ..wrt ; Write to disk inc hl ld (hunk.ptr),hl ld hl,byteNum inc (hl) ; Bump count ret ; ; Get hunk ; GetNextHunk: ld b,ChapByt ld hl,chars hunk..loop: push bc push hl call nextch ; Get four bytes pop hl pop bc ld (hl),a ; .. save without offset inc hl djnz hunk..loop ld ix,hunk ld iy,chars ld a,(iy+one) ; Now set up bits add a,a add a,a ld b,a ld a,(iy+two) srl a srl a srl a srl a add a,b ld (ix+one),a ld a,(iy+two) add a,a add a,a add a,a add a,a ld b,a ld a,(iy+three) srl a srl a add a,b ld (ix+two),a ld a,(iy+three) add a,a add a,a add a,a add a,a add a,a add a,a ld b,a ld a,(iy+four) add a,b ld (ix+three),a xor a ld (byteNum),a ; Clear a bit ld hl,hunk ld (hunk.ptr),hl ret ; ; Get character from stream ; EXIT : Accu holds fixed character ; nextch: ld hl,LineIndex inc (hl) ; Bump line ld a,(LinLen) cp (hl) ; Test ok ld de,$short jp c,Abort ; Line too short ld hl,(line.ptr) ld a,(hl) ; Get character inc hl ld (line.ptr),hl sub offset ; Strip off offset and UUMASK ; Mask bits ret ; ; Terminate program ; D.term: ld de,line call NextLine ; Get last line jr c,D.abnorm ; .. error ld a,(LinLen) cp tr.len jr c,D.abnorm ld de,line ld hl,trailer ld c,tr.len call comp3 ; Find 'end' jr nz,D.abnorm ld a,(wrbfp) ; Test binary buffer filled cp reclng jp nz,term.. ; .. close record if not ld de,dma.out call setdma ; Set disk buffer ld de,Fout call dskwrt ; .. write last record jp close.. ; .. close file D.abnorm: ld de,$abnorm jp Abort ; ; !!!!!!!!!!!!!!!!!!!!!!!!! ; ! S U B R O U T I N E S ! ; !!!!!!!!!!!!!!!!!!!!!!!!! ; ; Print action on console ; action: push hl ld hl,actcnt inc (hl) ; Bump count ld a,(hl) and Mod4 ; .. modulo 4 ld hl,acttab add a,l ld l,a jr nc,act.. inc h act..: ld a,(hl) ; Get character call conout ld a,bs call conout pop hl ret ; ; Print string and file ; ENTRY : Reg DE points to string ; Reg HL points to name of file ; str.fil: call string ; Tell string ex de,hl call filnam ; .. and file ret ; ; Close line to file ; Fcrlf: ld a,cr call ..wrt ; CR ld a,lf call ..wrt ; LF ret ; ; Output line to file ; Fstring: push bc ld b,0 call emplin ; .. write pop bc ret nc jp wrt.err ; ; Get and find input file ; EXIT Carry set on wild card ; GetInFile: ld hl,(ARGV+2*1) ; Get 2nd parameter ld (PB),hl ld de,PB call parse ; Parse file name ld de,$S.bad jr c,bad.get ; .. illegal ld a,(parsel) cp 'D' ; Test UUDECODE jr nz,Get...In ld de,Fin+.fdrv+.fname ld a,(de) ; Test extension cp ' ' jr nz,Get...In. ld hl,defext ld bc,.fext ldir ; .. set .UUE Get...In.: ld de,Fin call wcard ; Find wild cards jr nz,Reset ; .. nope ld a,(ARGC) ; Test right count cp MINpar jr nz,Get...In ; .. nope, force error scf ; .. indicate it ret Get...In: ld de,Fin call wcard ; Find wild cards ld de,$nowild jr z,bad.get ; .. bad ; ; Reset file for read ; Reset: ld de,Fin call open ; Find file ret nc ; .. ok ld de,$cant ld hl,Fin+1 call str.fil ; Tell cannot find jr ..bad bad.get: call string ..bad: call crlf jp OS ; Leave UU ; ; Get 2nd file name or mode ; Get2nd: ld hl,(ARGV+2*2) ; Get 3rd parameter ld (PB),hl parse.F: ld hl,Fout ld (PB+2),hl ld de,PB call parse ; Parse file name ld de,$D.bad jr c,bad.get ; .. illegal ret ; ; Create output file ; create.out: ld de,Fout call wcard ; Find wild cards ld de,$nowild jr z,bad.get ; .. bad ld de,Fout call open ; Find file jr nc,ovr.wrt ; Test overwrite if known try.create: ld a,TRUE ld (CR.flg),a call create ; Create file ret nc ld de,$canwr call string ; Cannot create ld de,Fout+1 jp ..bad ovr.wrt: ld de,$ovwr ld hl,Fout+1 call str.fil ; Tell overwrite ld de,$YN call string wt.YN: call combrk call UPPCON cp 'N' ; Wait for NO jr z,N.. cp 'Y' ; Or YES jr nz,wt.YN N..: push af call condir ; .. type call crlf pop af cp 'Y' jr nz,no.wrt ld de,Fout call delete ; Delete file jr try.create ; .. makenew no.wrt: ld de,Fout+1 call filnam ld de,$nocre call string ld a,(W???flg) ; Test wild card in process cp TRUE jp nz,OS ; .. nope, break ld sp,(BDOS+1) ; Get back stack jp WildNext ; ; Unpack number from command line into buffer ; numget: ld hl,(ARGV+2*3) ; Fetch last parameter ; ; Unpack number into buffer ; ENTRY : Reg HL points to buffer ; numset: ld de,..numb ld (@num),de ; Set number mode ld b,num.len ; Set max set.num: ld a,(hl) ld (de),a ; Unpack or a ret z ; .. till end inc hl inc de call tstdig ; Test digit jr c,bad.num djnz set.num ; .. or max xor a ld (de),a ret bad.num: ld de,$num jp bad.get ; Should be number ; ; Get option ; EXIT : Zero flag set if one found ; Accu holds option ; get.opt: ld hl,(ARGV) ; .. get 1st ld a,(hl) cp '-' ; Test legal prefix ret nz inc hl ld a,(hl) ld (parsel),a cp 'E' ; Test UUENCODE ret z cp 'D' ; .. or UUDECODE ret ; ; Give statistic about file xxcoded ; Statistic: ld de,$res.in ld hl,Fin+1 call str.fil ; Tell input file call crlf ld de,$res.out ld hl,Fout+1 call str.fil ; .. and output file ret dseg $help: db 'CP/M-80 implementation of UNIX UUENCODE and ' db 'UUDECODE',cr,lf,lf db 'UUENCODE converts binary to ASCII',cr,lf db 'UUDECODE converts ASCII to binary',cr,lf,lf db 'Usage:',tab $$Prg db ' -E infile ' db '{outfile} {number mode}',cr,lf db tab $$Prg db ' -D {infile} ' db '{outfile}',cr,lf,lf db 'The UUxxCODE ASCII file extensions ' db 'default to .UUE',cr,lf db 'Wildcards or no source file name is only ' db 'allowed if option -D is requested',cr,lf,eot $ill.CPU: $$Prg db ' requires Z80 CPU',eot $ill.OS: $$Prg db ' requires CP/M 3.x',eot $vers: $$Prg db ' Version ' $$Ver db cr,lf,lf,eot $S.bad: db 'Invalid source file name',eot $d.bad: db 'Invalid destination file name',eot $nowild: db 'No wild cards allowed in file name',eot $nofile: db 'No file found to be UUDECODED',eot $cant: db 'Cann''t open ',eot $canwr: db 'Cann''t create ',eot $ovwr: db 'Overwrite current ',eot $YN: db '? [Y/N] ',eot $nocre: db ' not overwritten',cr,lf,eot $decode: db 'Decoding file ',eot $encode: db 'Uuencoding file ',eot $toFile: db ' to file ',eot $warning: db cr,'%WARNING ',eot $trunc: db ' truncated to ',eot $blank: db 'Blank line in file',cr,lf,eot $line: db 'Line ',eot $delim: db ': ',eot $none: db 'Nothing to decode.',eot $inclomp: db 'Inclompete header',eot $short: db 'Line too short',eot $abnorm: db 'Abnormal end',eot $num: db 'Number expected',eot $wrt.err: db 'Disk write error ... ',cr,lf db 'Conversion aborted',eot $res.in: db 'Source : ',eot $res.out: db 'Destination : ',eot ???F???: db '????????' defext: db 'UUE' header: db 'begin ' hd.len equ $-header db 0 trailer: db 'end' tr.len equ $-trailer db 0 mode: db '644',0 $memry: dw 0 $num$: db '65535',eot parsel: db 0 bytesInLine: db 0 lineLength: db 0 numbytes: db 0 LineIndex: db 0 byteNum: db 0 hunk.ptr: dw hunk hunk: ds Hunkbyt chars: ds ChapByt LinLen: db 0 line.ptr: dw line db lin.len line: ds ChpLine ds lin.len-ChpLine db lin.len $header: ds lin.len linenum: dw 0 PB: ds 2 dw Fin $$PB: ds .fname+1+.fext+1 CR.flg: db FALSE GetFlg: db FALSE ..numb: ds num.len+1 @num: dw mode actcnt: db -1 acttab: db '|/-\' Fout: db 0 ds fcblen-1 dma.out: ds reclng W???flg: db FALSE ARGC: db 0 ARGV: ds 2*MAXpar LogDrv: db 0 FileCnt: ds 2 FilePtr: ds 2 ErrAdr: ds 2 ErrStk: ds 2 end start