; CONCAT.Z80 ; Vers equ 13 ; version number SubVers equ ' ' ; modification level ; ; Concatenates or appends two or more source files into a destination file. ; For ZCPR3 only. ; ; USAGE: ; ; CONCAT {dir:}outfile = {dir:}infile {{dir:}infile {...}} {/options} ; ; Any file without a DU or DIR specification is assumed to be in ; the current drive/user. ; ; OPTIONS: ; ; A Append mode. ; ; C Concatenation mode (default, as distributed). ; ; O Object (binary) file mode. Cancels D option. ; ; T Text file mode (default, as distributed). ; ; D Insert system date and time. ; ; I{s} Insert divider string s, or configured string. ; ; Q Toggle quiet mode. ; ; S Toggle disk space checking and file date stamping. ; ; F Toggle file stamping. ; ; For more information see the accompanying documentation file. ; ; HISTORY: See accompanying history file. ; ; To report bugs or make suggestions: ; Gene Pizzetta ; 481 Revere Street ; Revere, MA 02151 ; ; Voice: (617) 284-0891 ; Newton Centre Z-Node: (617) 965-7259 ; Ladera Z-Node Central: (213) 670-9465 ; ; System addresses . . . ; Bdos equ 05h CpmDma equ 80h StpBuf equ CpmDma ; ; Bdos functions . . . ; FRead equ 20 FWrite equ 21 SetDma equ 26 ; ; ASCII characters . . . ; CtrlC equ 03h ; control C TAB equ 09h ; tab LF equ 0Ah ; linefeed CR equ 0Dh ; carriage return CpmEOF equ 1Ah ; end-of-file (^Z) ; .request dslib,zslib,vlib,z3lib,syslib1,syslib0 ; ; FROM DSLIB ext timini,rclock,gstamp,pstamp ; FROM ZSLIB ext mtimm3,mout,mafhc,mhl4dc,mstr ; FROM VLIB ext stndout,stndend ; FROM Z3LIB ext z3vinit,zsyschk,gzmtop,puter2,inverror,zprsfn,z3log ext z33chk,z33fname,prtname,getquiet ; FROM SYSLIB ext f$exist,f$open,f$make,f$rename,f$delete,f$close,f$appl ext initfcb,logud,retud,epstr,pfn2,crlf,cout,condin,codend ext ma2hc,pafdc,phlfdc,dparams,dfree,getfs1,@fncmp ; public pstr ; jp Start ; db 'Z3ENV' db 1 Z3EAdr: dw 0FE00h ; address of environment descriptor ; ; Configuration area . . . ; dw 0 ; filler db 'CONCAT' ; for ZCNFG db Vers/10+'0',Vers mod 10+'0' ; QtFlag: db 0 ; 0=verbose, FFh=quiet AppFlg: db 0 ; 0=concatenate, FFh=append ObjFlg: db 0 ; 0=text, FFh=object (binary) SpcFlg: db 0 ; 0=check disk space, FFh=don't StpFlg: db 0 ; 0=transfer datestamp, FFh=don't DatFmt: db 0 ; 0=American date, FFh=European TimFmt: db 0 ; 0=civilian time, FFh=military ECFlag: db 0FFh ; 0=lower-case, FFh=uppercase ; TmStr1: db CR,LF,'--- [ ',0,0 ; date string prefix db 0,0,0,0,0,0,0,0,0,0 db 0 ; must be null terminated ; TmStr3: db ' ] ---',CR,LF,CR,LF ; date string suffix db 0,0,0,0,0,0,0,0,0,0 db 0 ; must be null terminated ; DivStr: db CR,LF,'----',CR,LF,0,0 db 0,0,0,0,0,0,0,0,0,0 db 0,0,0,0,0,0,0,0,0,0 db 0,0,0,0,0,0,0,0,0,0 db 0,0,0,0,0,0,0,0,0,0 db 0,0,0,0,0,0,0,0,0,0 db 0,0,0,0,0,0,0,0,0,0 db 0,0,0,0,0,0,0,0,0,0 db 0 ; must be null terminated ; ; Messages . . . ; MsgUse: db 'CONCAT Version ' db Vers/10+'0','.',Vers mod 10+'0',SubVers,CR,LF db 'Usage:',CR,LF,' ',0 MsgUs1: db ' {dir:}outfile = {dir:}infile {{dir:}infile {...}}' db ' {/options}',CR,LF db 'Concatenates or appends infiles to outfile.',CR,LF db 'Options:',CR,LF db ' A Append to existing file',0 MsgUs2: db CR,LF db ' C Concatenate to new file',0 MsgUs3: db CR,LF db ' O Object (binary) files',0 MsgUs4: db CR,LF db ' T Text files',0 MsgUs5: db CR,LF db ' D Insert system date and time',CR,LF db ' I{s} Insert divider string',CR,LF db ' Q Quiet mode o',0 MsgUs6: db CR,LF db ' S Space checking o',0 MsgUs7: db CR,LF db ' F File stamp transfer o',0 MsgUs8: db CR,LF db 'Option I must be last option given.',CR,LF db 'Option O turns off options D and I.',CR,LF db 'Option A turns off option F.',0 MsgOn: db 'n',0 MsgOff: db 'ff',0 MsgDft: db ' [default]',0 MsgWrt: db 'Writing to ',0 MsgApd: db ' Appending ',0 MsgRd: db ' Adding ',0 MsgFre: db 'Free ',0 MsgNed: db 'k, needed ',0 MsgAbt: db 'Aborted.',0 MsgSrc: db 'No source file.',0 MsgMem: db 'Not enough memory.',0 MsgFNF: db ' file not found.',0 MsgNSp: db 'Insufficient disk space.',0 MsgFul: db 'Output file is full.',0 MsgDes: db 'No output file.',0 MsgOpt: db 'Invalid option.',0 MsgInv: db 'Invalid directory.',0 MsgClk: db 'No clock.',0 MsgREr: db 'Read error.',0 MsgDir: db 'No directory space.',0 MsgWEr: db 'Disk full.',0 MsgCls: db 'Close error.',0 MsgAmb: db ' ambiguous filename.',0 MsgDlm: db 'Command line: "=" expected.',0 MsgDne: db 'Done.',0 ; Start: ld hl,(Z3EAdr) ; set up environment call zsyschk ; is this a z-system? ret nz ; (no, let's quit) call z3vinit ld (OldStk),sp ; save old stack pointer call gzmtop ; get top of memory ld (MemTop),hl ; store it ld sp,hl ; ..and setup new stack ; call codend ; set buffer addresses ld (InBuf),hl ld de,128 ; add 1 sector for output buffer address add hl,de ld (OutBuf),hl add hl,de ; add another sector for stack ex de,hl ; put buffer address in DE ld hl,(MemTop) ; get top of memory or a ; reset the carry flag sbc hl,de ; subtract jp c,NoMem ; (not enough memory) ld de,128 ; put divisor in DE call UDiv16 ; ..and divide work space into sectors sub a cp h ; maximum memory? jr nz,Start1 ; (yes) cp l ; at 1 sector? jp z,NoMem ; (no) ld a,l ; use number of sectors in L dec a ; ..less 1 ld (BufSiz),a jr Start2 ; Start1: ld a,255 ; set output buffer size ld (BufSiz),a Start2: sub a ; initialize option flags ld b,GetFlg-OpDFlg ld hl,OpDFlg InitLp: ld (hl),a inc hl djnz InitLp call getquiet ; is ZCPR quiet flag set? rra ; make it 0 or FFh sbc a,a jr nz,Start3 ; (yes) ld a,(QtFlag) ; no, get quiet config byte Start3: ld (OpQFlg),a ; ..and store in Q option flag ld hl,(AppFlg) ; move append and object defaults ld (OpAFlg),hl ; ..to option flags ld hl,(SpcFlg) ; move space and stamp defaults ld (OpSFlg),hl ; ..to option flags call retud ; get and store default disk and user ld (DftUsr),bc ld a,(CpmDma) ; see if there's a tail or a jp z,Usage ; (no, so tell 'em how to use this) ld bc,128 ; move command tail to storage ld hl,CpmDma ld de,CTail ldir ; call GetOpt ; get options, if any call ChkOpt ; resolve conflicting options ld a,(OpFFlg) ; file stamp transfer? or a call z,timini ; (yes, initialize it) ld hl,CTail+1 ; get output filename from tail call EatSpc ; eat any spaces or a ; is it NUL? jp z,Usage ; (yes, no filename) cp '/' ; is it a slash? jp z,NoOut ; (yes, no filename) cp '=' ; is it an equal sign? jp z,NoOut ; (yes, no filename) ld de,OutFcb ; ..and put it in FCB call Zparse jp nz,AmbOFn ; (ambiguous filename) ld a,(OutFcb+1) ; make sure there's a filename cp ' ' jp z,NoOut ; (there's not) call EatSpc cp '=' ; equal sign after filename? jp nz,NoDlm ; (no, that's a problem) inc hl ; get past delimiter ld (TailPt),hl ; save command tail pointer ld a,(OpAFlg) ; check for append mode or a jr nz,NoTmp ; (yes, no temporary file) ld hl,OutFcb+1 ld de,OutNam+1 ld bc,11 ldir ld hl,TmpTyp ld de,OutFcb+9 ld bc,3 ldir NoTmp: ld de,OutFcb call z3log ; log into output directory call retud ; get output DU ld (OutUsr),bc ; ..and store it ld a,(OutFcb+15) ; check for valid directory or a jp nz,BadDir ld a,(OpSFlg) ; checking disk space? or a call z,ChkSpc ; (yes, go do it) call OpnOut ; open output file call CkAbrt ; check for user abort ld a,(OpQFlg) ; quiet option? or a jr nz,NoWMsg ; (yes, don't print anything) ld a,(OpAFlg) ; append mode? or a ld hl,MsgWrt ld de,OutNam+1 jr z,WrtMsg ; (no, print name of new file) ld de,OutFcb+1 ; print name of append file WrtMsg: call epstr call PrtFn ; drive/user still in BC ; NoWMsg: call SetIn ; get first input file jp z,NoIn ; (file not found) ld a,(OpFFlg) ; file stamp transfer? or a call z,GetStp ; (yes, get it) ld a,(OpDFlg) ; are we inserting the date? or a call nz,PutDat ; (yes, go do it) ld a,(OpAFlg) ; append mode? or a call nz,PutDiv ; (yes, insert divider) call CkAbrt ; check for user abort call RdLoop ; read and write files ; MoreLp: call ClsIn ; close input file call CkAbrt call SetIn ; set up next input file jr z,NoMore ld a,(OpFFlg) ; file stamp transfer? or a call z,ChkStp ; (yes, check it for match) call PutDiv call RdLoop ; read and write files jr MoreLp ; NoMore: call FinOut ; close output file ld a,(OpAFlg) ; append mode? or a jr nz,Finish ; (yes, skip date stamping and renaming) ld a,(OpFFlg) ; transferring file stamp? or a call z,PutStp ; (yes, move it) ld hl,BakTyp ld de,OutFcb+9 ld bc,3 ldir ld de,OutFcb ; blind erase any existing BAK file call f$delete ld de,OutNam ; point to old XXX name ld hl,OutFcb ; point to new BAK name call f$rename ; ..and blind rename file ld hl,TmpTyp ; rename temporary $$$ file ld de,OutFcb+9 ; ..to output filename ld bc,3 ldir ld hl,OutNam ; point to new XXX name ld de,OutFcb ; point to old $$$ name call f$rename ; ..and rename output file ; Finish: ld a,(OpQFlg) ; quiet option? or a ld a,0 ; reset error flag jr nz,Exit ; (yes) ld hl,MsgDne ErExit: call epstr ; print message Exit: call puter2 ; set error code ld b,a ; put error code in B or a call nz,inverror ; call error handler ld sp,(OldStk) ; restore old stack pointer ret ; ..and return to CCP ; ; Error Exits . . . ; NoSpc: ld a,11 ; set error flag ld hl,MsgNSp ; say not ehough disk space jr ErExit ; NoDlm: ld a,4 ; set error flag ld hl,MsgDlm ; "=" expected jr ErExit ; OutFNF: ld de,OutFcb+1 ; print filename call pfn2 ld a,10 ; set error code ld hl,MsgFNF ; file not found jr ErExit ; AmbOFn: ld de,OutFcb+1 ; print filename call pfn2 ld a,8 ; set error flag ld hl,MsgAmb ; ambiguous output filename jr ErExit ; NoOut: ld a,8 ; set error flag ld hl,MsgDes ; no output file given jr ErExit ; NoMem: ld a,12 ; set error code ld hl,MsgMem ; not enough memory jr ErExit ; BadDir: ld a,2 ; set error code ld hl,MsgInv ; invalid directory jr SAbort ; InFNF: ld de,InFcb+1 ; print filename call pfn2 ld a,10 ; set error code ld hl,MsgFNF ; file not found jr SAbort ; NoIn: ld a,8 ; set error flag ld hl,MsgSrc ; no source file given jr SAbort ; AmbIFn: ld a,(OpSFlg) ; disk space mode? or a call z,crlf ; (yes, new line) ld de,InFcb+1 ; print filename call pfn2 ld a,8 ; set error flag ld hl,MsgAmb ; ambiguous input filename ; SAbort: ld b,a ; save error code ld a,(OpSFlg) ; space check mode? or a ld a,b ; get back error code jr z,ErExit ; (yes) ; Abort: push af ; save error code push hl ; save message call ClsOut ; close output file ld a,(OpAFlg) ; append mode? or a jr nz,Abort1 ; (yes, skip erase) call f$delete ; erase output file Abort1: pop hl ; get back message pop af jp ErExit ; ; Subroutines . . . ; ; SetIn -- Get input filename and set up for input. Z flag set if no ; more files. ; SetIn: call GetIn ; search for next input file ret z ; (none) push af ; save flags call OpnIn ; open file ld a,(OpQFlg) ; quiet mode? or a jr nz,SetIn2 ; (yes, don't print anything) ld hl,MsgRd ; print filename we're reading ld a,(OpAFlg) ; appending? or a jr z,SetIn1 ; (no, use reading message) ld hl,MsgApd ; yes, use append message SetIn1: call epstr ; ld bc,(InUsr) ; B=drive, C=user ld de,InFcb+1 call PrtFn SetIn2: pop af ret ; ; GetIn -- Get next input filename from command line ; GetIn: ld bc,(DftUsr) ; log default DU for parser call logud ld hl,(TailPt) ; recover command tail pointer call EatSpc cp ',' ; got comma? jr nz,GetIn1 ; (no) inc hl ; get past it call EatSpc GetIn1: or a ret z ; (no more files) cp '/' ret z ; (no more files) ; ; Initialize input file control block ; ld de,InFcb ; point to input FCB call Zparse ; ..and parse filespec jr nz,AmbIFn ; (ambiguous filename) ld a,(InFcb+15) ; check valid directory or a call nz,crlf jp nz,BadDir ; (bad directory) ld (TailPt),hl ; save command tail pointer call z3log ; log into DU call retud ; get DU ld (InUsr),bc ; ..and store it xor a ; reset Z flag inc a ret ; ; OpnIn -- open input file. Already logged to DU. ; OpnIn: ld a,128 ; initialize pointer ld (GetPtr),a sub a ; initialize end-of-file flag ld (GetFlg),a ld de,InFcb call initfcb call f$open ret z ; (all okay) ld a,(OpAFlg) ; append mode? or a call nz,FinOut ; (yes, write buffer) jp InFNF ; ; OpnOut -- open output file. ; OpnOut: sub a ; initialize counters ld (PutCnt),a ld (PutSec),a ld hl,(OutBuf) ; initialize buffer pointer ld (PutPtr),hl ld de,OutFcb call initfcb ; initialize FCB call OutDU ld a,(OpAFlg) ; append mode? or a jr nz,OpnApd ; (yes) call f$make ret z ; (okay) ld a,11 ; set error flag ld hl,MsgDir ; open error jp ErExit ; ; Open for append mode ; OpnApd: push de ; save FCB pointer ld de,(OutBuf) ; put DMA address in DE ld c,SetDMA ; ..and set DMA address call Bdos pop de ; recover FCB address call f$appl ; read append file or a ; any errors? jr nz,ApdErr ; (yes) ld a,(OpOFlg) ; object file mode? or a jr nz,ApdObj ; (yes) ld hl,(PutPtr) ; point to output buffer ld a,(PutCnt) ; get counter to B ld b,a ApdLp: ld a,(hl) ; get character cp CpmEof ; end of file? jr z,ApdEnd ; (end) inc hl ; increment pointer inc b ; increment counter ld a,b cp 128 ; end of sector? jr z,ApdObj ; (yes, no ^Z) jr ApdLp ; continue ; ApdEnd: ld a,b ; store current counter position ld (PutCnt),a ret ; ApdObj: ld a,128 ; set counter for full sector ld (PutCnt),a ret ; ApdErr: cp 3 ; file empty? ret z ; (yes, but who cares?) cp 2 ; file full? jp nz,OutFNF ; (no, just not found) ld a,4 ld hl,MsgFul jp ErExit ; ; RdLoop -- reads and writes until end of file ; RdLoop: call GetC ; get a character jr c,RdErr ; (read error) ret z ; (end of file) cp CpmEof ; end of file? call z,ChkEof ; (yes, check mode) ret z ; (yes) call PutC ; write character jr c,WrtErr ; (write error) jr RdLoop ; RdErr: ld a,4 ; set error flag ld hl,MsgREr ; we have an input read error jp Abort ; WrtErr: ld a,11 ; set error flag ld hl,MsgWEr ; we have an output write error jp Abort ; ; ChkEof -- checks for Option O and, if so, ignores end-of-file character ; ChkEof: ld b,a ; save character in B ld a,(OpOFlg) ; get object flag or a ld a,b ; get character back in A ret ; ; ClsIn -- close input file ; ClsIn: call InDU ; close input file ld de,InFcb call f$close ret z ; (okay) ld a,4 ; set error flag ld hl,MsgCls ; close error jp Abort ; ; FinOut -- writes buffer and closes output file ; FinOut: ld a,(OpOFlg) ; check option O flag or a jr nz,WrLst ; (object file transfer, skip EOF) ld a,CpmEof ; put end-of-file character call PutC ld a,(PutCnt) ; check pointer or a jr z,WrLst ; (sector finished) ld b,a ld a,128 ; fill rest of sector with ^Z sub b ld b,a FillZ: ld a,CpmEof push bc call PutC pop bc djnz FillZ jr WrLst ; WrLst: ld de,(OutBuf) ; get beginning buffer address to DE ld hl,PutPtr ; HL -> buffer pointer ld a,e ; is pointer at zero? cp (hl) jr nz,WrLst2 ; (no) ld a,d inc hl cp (hl) jr nz,WrLst2 ; (no) ld a,(PutCnt) ; is counter at zero? or a jr nz,WrLst2 ; (no) jr ClsOut ; nothing to write, so close it ; WrLst2: call FWrt ; write what's left or a ; check for error jr nz,WrtErr ; (yes, abort) ; ClsOut: call OutDU ; close output file ld de,OutFcb call f$close ret z ; (okay) ld a,4 ; set error flag ld hl,MsgCls ; close error jp ErExit ; ; PrtFn -- Prints drive/user and filename on console. Assumes we're ; logged into correct DU. ; PrtFn: call retud ; get DU call stndout ld a,b ; get drive add a,'A' ; make it printable call cout ; ..and print it ld a,c ; get user call pafdc ; ..and print it ld a,':' call cout call pfn2 ; print filename call stndend jp crlf ; new line and return to caller ; ; EatSpc -- gobbles up spaces and tabs, returns with ; first non-blank in A ; EatSp0: inc hl EatSpc: ld a,(hl) cp ' ' ; a space? jr z,EatSp0 ; (yes) cp TAB ; a tab jr z,EatSp0 ; (yes) ret ; ; OutDU -- sets default drive and user for output file ; OutDU: ld bc,(OutUsr) ; B=drive, C=user jp logud ; set it and return to caller ; ; InDU -- sets default drive and user for input file ; InDU: ld bc,(InUsr) ; B=drive, C=user jp logud ; set it and return to caller ; ; GetC -- returns character from file. Assumes file has been ; successfully opened. Returns character or ^Z (end-of-file) in ; A. Zero set (Z) on end of file. Carry set (C) if error. ; GetC: ld a,(GetFlg) ; check end-of-file flag or a jr nz,GetEof ; (yes) ld a,(GetPtr) ; get pointer cp 128 ; done with buffer? jr c,GetChr ; (no, get a character) call CkAbrt ; check for user abort ld de,(InBuf) ld c,SetDMA ; set DMA address call Bdos call InDU ; set DU ld de,InFcb ld c,FRead ; read more file call Bdos cp 1 ; return code? jr z,GetEof ; (end of file) jr nc,GetErr ; (a problem) ld (GetPtr),a ; put 0 in pointer ; GetChr: ld hl,(InBuf) ; point to DMA buffer ld e,a ; put pointer in DE ld d,0 add hl,de ; add it to HL ld a,(hl) ; get next character ld hl,GetPtr ; increment pointer inc (hl) scf ccf ; clear carry, leave Z flag alone ret ; GetEof: ld a,CpmEof ld (GetFlg),a ; set end-of-file flag scf ccf ; clear carry, leave Z flag alone ret ; GetErr: ld a,CpmEof ld (GetFlg),a scf ; set carry ret ; ; PutC -- Writes character to file. Assumes file has been successfully ; opened. Expects character in A. Returns carry set (C) on error. ; PutC: ld c,a ; save character in C ld a,(PutCnt) ; get counter cp 128 ; buffer full? jr c,PutChr ; (no, so do it) push bc ; the character is threatened from all sides ld a,(BufSiz) ; get buffer size ld b,a ; ..and put it in B ld a,(PutSec) ; get sector count cp b ; end of buffer? jr z,PutC2 ; (yes, we need to write it) inc a ; increment sector count ld (PutSec),a ; ..and store it ld hl,(PutPtr) ; get current sector pointer ld de,128 ; ..add 128 to it add hl,de ld (PutPtr),hl ; ..and store it xor a ; make A = 0 jr PutC3 ; PutC2: call FWrt ; write buffer to disk push af ld hl,(OutBuf) ; reset buffer pointer ld (PutPtr),hl xor a ; reset sector counter ld (PutSec),a pop af ; PutC3: pop bc ; get back output character or a ; return code? jr nz,PutErr ; (problem) ld (PutCnt),a ; reset counter to 0 ; PutChr: ld hl,(PutPtr) ; point to current DMA buffer ld e,a ; move counter to DE ld d,0 add hl,de ; ..and add it to HL ld (hl),c ; write character ld a,(PutCnt) inc a ; increment counter ld (PutCnt),a sub a ; clear carry ret ; PutErr: scf ; set carry ret ; ; FWrt -- write output buffer to disk ; FWrt: call OutDU ld a,(PutSec) ; get buffer sector count ld b,a ; put it in B ld de,(OutBuf) ; point to beginning of buffer FWrt2: push bc ; save sector count push de ; save DMA address call WrtSec ; write the sector pop hl ; DMA address recovered in HL pop bc ; get back sector count or a ; write error? ret nz ; (yes) cp b ; end of buffer? ret z ; (yes) dec b ; decrement sector count ld de,128 ; increment DMA address add hl,de ex de,hl jr FWrt2 ; WrtSec: ld c,SetDma ; set DMA address call Bdos ; drive/user already is set ld de,OutFcb ; ..write sector ld c,FWrite ; (drive/user already is set) jp Bdos ; ..and return to caller ; ; PutDiv -- Sends divider string to output file ; PutDiv: ld a,(OpIFlg) ; are we inserting divider? or a ret z ; (no) ld a,(ECFlag) ; initialize case flag ld (CasFlg),a ld hl,(OpIAdr) ; a command line divider string? ld a,(hl) or a jr nz,Echo ; (yes) ld hl,DivStr ; point to divider string jr PrtDat ; send it and return to caller ; ; PutDat -- Sends date and time string to output file ; PutDat: ld hl,TmStr1 ; send the prefix call PrtDat ld hl,TmStr2 ; send the date and time call PrtDat ld hl,TmStr3 ; send the suffix jr PrtDat ; print and return to caller ; PrtDat: ld a,(hl) ; get character or a ; is it null? ret z ; (yes, we're finished) push hl call PutC ; send character to outfile pop hl jp c,WrtErr inc hl jr PrtDat ; ; Echo -- unashamedly adapted from Carson Wilson's RCPECHO. ; Echo: call GetDC ; get character ret z ; (end of string) cp '^' ; control character prefix? jr nz,Echo1 ; (no) call GetDC ; yes, get next character ret z ; (end of string) and 1Fh ; convert to control character jr Echo5 ; ..and write it it ; Echo1: cp '%' ; escape prefix? jr nz,Echo5 ; (no, normal echo) call GetDC ; yes, get next character ret z ; (end of string) cp '<' ; up-shift? jr z,Echo2 ; (yes, store non-zero in case flag) cp '>' ; down-shift? jr nz,Echo3 ; (no) xor a ; yes, clear case flag Echo2: ld (CasFlg),a jr Echo ; continue ; Echo3: cp 'S' ; semi-colon request? jr nz,Echo4 ; (no) ld a,';' ; yes, write semi-colon jr Echo5 ; Echo4: cp 'D' ; delete character request? jr nz,Echo5 ; (no) ld a,07Fh ; yes, write DEL ; Echo5: call EOut ; write character and continue jr Echo ; Write character based on CasFlg. EOut: ld c,a ; save character in C cp 'A' ; less than 'A'? jr c,EOut1 ; (yes, leave as is) cp 'Z'+1 ; greater than 'Z'? jr nc,EOut1 ; (yes, leave as is) add 20h ; convert to lower EOut1: ld d,a ; save lower-case version in D ld a,(CasFlg) ; check case or a ld a,c ; assume upper-case jr nz,EOut2 ; (yes, it's upper) ld a,d ; no, get lower-case version EOut2: push hl push bc call PutC ; write character in A pop bc pop hl ret ; Return character from command tail buffer. Z flag set on end of string. GetDC: ld a,(hl) ; get character inc hl ; point to next one or a ; set zero flag if null ret ; ; ChkSpc -- checks free space on target drive and compares it with the ; total of the filesizes of the input files. Aborts if space is ; insufficient. ; ChkSpc: ld hl,(TailPt) ; save pointer push hl call OutDU ; select output drive call dparams call dfree ; get free space ld (DskSpc),de ; ..and store it ld a,(OpQFlg) ; quiet mode? or a jr nz,NoFree ; (yes, no messages) ld hl,MsgFre ; say how much call epstr ex de,hl ; get disk space into HL call phlfdc ld hl,MsgNed ; let them know we're working call epstr NoFree: call GetIn ; get first source call z,crlf jp z,NoIn ; (none) ld de,InFcb call f$exist call z,crlf jp z,InFNF call getfs1 ; get file size call Div8 ; convert to kilobytes ld (FilSiz),hl ; ..store it ex de,hl ld (FilRem),hl ; ..store remainder ChkSp1: call GetIn ; check for more input files jr z,ChkDne ; (none) ld de,InFcb call f$exist call z,crlf jp z,InFNF call getfs1 ; get file size call Div8 ; convert to kilobytes push de ; save remainder ex de,hl ld hl,(FilSiz) ; add to total file size add hl,de ld (FilSiz),hl ld hl,(FilRem) ; add remainder pop de add hl,de ld (FilRem),hl jr ChkSp1 ; ChkDne: ld hl,(FilRem) ; convert remainder to kilobytes ld a,(OpDFlg) ; insert date option? or a jr z,ChkDn0 ; (nope) inc hl ; yep, add another record ChkDn0: ld a,(OpIFlg) ; insert divider option? or a jr z,ChkDn1 ; (nope) inc hl ; yep, add another record ChkDn1: call Div8 xor a cp e ; check for remainder jr z,ChkDn2 ; (none) inc hl ; add one more K ChkDn2: ex de,hl ; remainder in DE ld hl,(FilSiz) ; add to file size add hl,de ld (FilSiz),hl ld a,(OpQFlg) ; quiet mode? or a jr nz,NoNeed ; (yes, no messages) call phlfdc ld a,'k' call cout call crlf NoNeed: ex de,hl ; put total file size in DE ld hl,(DskSpc) ; get free space in HL or a ; reset carry flag sbc hl,de ; subtract DE from HL jp c,NoSpc ; (not enough disk space) ld bc,(DftUsr) ; relog default DU call logud pop hl ; restore tail pointer ld (TailPt),hl ret ; ; GetOpt -- checks command tail for user supplied options and sets ; appropriate option flags. Invalid options are ignored. ; GetOpt: ld hl,CTail ; point to command tail ld a,(hl) ; anything there? or a ret z ; (no) ld b,a ; yes, put number of chars in B inc hl ; ..and increment pointer ScnDLp: ld a,(hl) ; get character cp '/' ; delimiter? jr z,ScnOpt ; (yes) ld d,a ; save character ScnDL2: inc hl ; no, keep looking djnz ScnDLp ret ; (none found, return) ; ScnOpt: push af ; save current character ld a,d ; get back previous character pop de ; put current character in D cp ' ' ; was previous char a space? jr nz,ScnDL2 ; (no) jr ScnOp2 ; ScnOLp: call ScnTbl ex de,hl ; point back to options ScnOp2: inc hl djnz ScnOLp ; loop through options ret ; ScnTbl: ld c,(hl) ; put option in C ld de,OptTbl ; point DE to option table ScnTLp: ld a,(de) ; get table option or a ; end of table? jr z,NoMat ; (yes, no match) inc de ; no, keep looking cp c ; match? jr z,TMatch ; (yes) inc de ; move pointer to next entry inc de jr ScnTLp ; ..and keep looking ; NoMat: ex de,hl ; no match ld a,c ; get back option cp ' ' ; was it a space? ret z ; that's okay ld a,19 ; set error flag ld hl,MsgOpt ; say bad option jp ErExit ; TMatch: push hl ; save option pointer ld a,(de) ; put address from table into HL ld l,a inc de ld a,(de) ld h,a pop de ; recover option pointer in DE ld a,0FFh ; set option flag by jumping to jp (hl) ; ..table routine and returning ; ; OptTbl -- Option Jump Table ; OptTbl: db '/' ; / = usage message dw Usage db 'A' ; A = append files dw OptA db 'C' ; C = concatenate files dw OptC db 'D' ; D = time and date in file dw OptD db 'F' ; F = file date stamps dw OptF db 'I' ; I = insert divider dw OptI db 'O' ; O = object (binary) files dw OptO db 'Q' ; Q = toggle quiet mode dw OptQ db 'S' ; S = toggle space checking and date stamping dw OptS db 'T' ; T = text files dw OptT db 0 ; end of option jump table ; ; Option setting routines. A=0FFh on entry ; OptC: inc a ; reset flag ; OptA: ld (OpAFlg),a ; store it ret ; OptD: ld (OpDFlg),a ; set flag call timini ; initialize clock routine jr z,OpDEr ; (error, no clock) push bc push de ld hl,TimStr ; point to BCD time string buffer call rclock ; read clock jr nz,OpDEr ; (error, bad clock) ld hl,TimStr ; ->BCD time/date string ld de,TmStr2 ; ASCII time/date buffer call MDate ; create date string ld a,' ' ; insert 2 spaces between date and time ld (de),a inc de ld (de),a inc de ld a,(TimFmt) or a jr nz,OptD3 call mtimc6 ; create civilian time string jr OptD4 OptD3: call mtimm3 ; create military time string OptD4: xor a ; stuff in a final null byte ld (de),a pop de pop bc ret ; OpDEr: ld a,4 ; no clock ld hl,MsgClk jp ErExit ; OptT: inc a ; reset flag ; OptO: ld (OpOFlg),a ; store it ret ; OptI: ld (OpIFlg),a ; store it inc de ; point to next byte ld (OpIAdr),de ld b,1 ret ; OptF: ld a,(OpFFlg) ; get F flag cpl ; flip it ld (OpFFlg),a ; store it ret ; OptQ: ld a,(OpQFlg) ; get Q flag cpl ; flip it ld (OpQFlg),a ; store it ret ; OptS: ld a,(OpSFlg) ; get S flag cpl ; flip it ld (OpSFlg),a ; store it ret ; ; ChkOpt -- check for and resolve conflicting options ; ChkOpt: ld a,(OpOFlg) ; object file option? or a jr z,ChkOp1 ; (no) xor a ld (OpDFlg),a ; yes, cancel D option ld (OpIFlg),a ; ..and I option ChkOp1: ld a,(OpAFlg) ; append on? or a ret z ; (no) ld a,0FFh ; yes, turn off F option ld (OpFFlg),a ret ; Usage: ld hl,MsgUse ; print usage message call epstr call prtname ; print name we're called by ld hl,MsgUs1 call epstr ld a,(AppFlg) or a call nz,PrtDft ld hl,MsgUs2 call epstr call z,PrtDft ld hl,MsgUs3 call epstr ld a,(ObjFlg) or a call nz,PrtDft ld hl,MsgUs4 call epstr call z,PrtDft ld hl,MsgUs5 call epstr ld a,(OpQFlg) ; check quiet mode or a ld hl,MsgOn jr z,Usage1 ; (off) ld hl,MsgOff Usage1: call epstr ld hl,MsgUs6 ; S option usage call epstr ld a,(SpcFlg) ; check space check mode or a ld hl,MsgOff jr z,Usage2 ; (on) ld hl,MsgOn Usage2: call epstr ld hl,MsgUs7 ; F option usage call epstr ld a,(StpFlg) ; check file stamping mode or a ld hl,MsgOff jr z,Usage3 ld hl,MsgOn Usage3: call epstr xor a ; reset error flag ld hl,MsgUs8 jp ErExit ; PrtDft: ld hl,MsgDft jp epstr ; print it and return to caller ; ; CkAbrt -- Check for user abort (^C). Ignore if in Append mode. ; CkAbrt: ld a,(OpAFlg) ; check append flag or a ret nz ; (yes, return) call condin ; do we have a key? ret z ; (no) cp CtrlC ; is it ^C? ret nz ; (no) ld a,4 ; yes, abort this mess ld hl,MsgAbt jp Abort ; ; Zparse -- use most precise available Z-System parser: ; either the Z33+ CPR parser (via Z3LIB's ; Z33FNAME) or Z3LIB's ZPRSFN. ; Zparse: call z33chk jr nz,notz33 ; No CPR parser, use Z3LIB's call z33fname ; Do parse via CPR entry push hl ; Save HL push bc ; & BC ld h,d ; Copy FCB pointer to HL ld l,e ld bc,12 ; Check entire FCB ld a,'?' ; for ambiguity cpir ; Z80-style pop bc ; Restore regs pop hl jr z,gotamb ; Oops, ambiguous! xor a ; Otherwise report OK (Z) ret gotamb: or a ; A = '?', make NZ. ret notz33: xor a ; DIR: before DU: jp zprsfn ; Return via Z3LIB parser ; ; ChkStp -- Check for filename match with output file. If names match, ; get date stamp. ; ChkStp: ld de,InFcb+1 ; point to source filename ld hl,OutNam+1 ; point to output filename ld b,11 ; checking 11 characters call @fncmp ret nz ; (no match) ; ; GetStp -- Get create stamp from original file, if available, and ; transfer it to new file. ; GetStp: ld de,InFcb ; point to input filename ld hl,StpBuf ; point to date stamp buffer call gstamp ; get file stamp ret z ; (error) ld a,(StpBuf+1) ; check for create date or a ld hl,StpBuf ; point to create date jr nz,GetSt1 ; (we've got a create date) ld a,(StpBuf+11) ; none, so check for modify date or a ret z ; (no date stamp) ld hl,StpBuf+10 ; point to modify date GetSt1: ld de,StpTmp ; move date to storage ld bc,5 ldir ret ; ; PutStp -- Takes create date if it exists in StpTmp buffer and makes ; it the create date of the new file. ; PutStp: ld de,OutFcb call initfcb ld hl,StpBuf ; point to date stamp buffer call gstamp ; get file stamp ret z ; (error) ld a,(StpTmp+1) ; do we have a stamp? or a ret z ; (no) ld hl,StpTmp ; move old stamp to create date string ld de,StpBuf ld bc,5 ldir ld de,OutFcb ; setup for file stamping ld hl,StpBuf jp pstamp ; set file stamp and return to caller ; ; Div8 -- divide HL by 8 ; Div8: push af ; save registers push bc ld de,8 ; divisor = 8 call UDiv16 pop bc pop af ret ; ; UDiv16 -- divide 2 unsigned 16-bit words and return a 16-bit unsigned ; quotient and remainder. ; Entry: L = low byte of dividend, H = high byte of dividend ; E = low byte of divisor, D = high byte of divisor ; Exit: L = low byte of quotient, H = high byte of quotient ; E = low byte of remainder, D = high byte of remainder ; Carry reset if no errors, Carry set if divide-by-zero error ; (and quotient = 0 and remainder = 0) ; Used: AF,BC,DE,HL ; ; check for division by zero UDiv16: ld a,e or d jr nz,Divide ; branch if divisor is non-zero ld hl,0 ; divide by zero error ld d,h ld e,l scf ; set carry, invalid result ret ; Divide: ld c,l ; C = low byte of dividend/quotient ld a,h ; A = high byte of dividend/quotient ld hl,0 ; HL = remainder ld b,16 ; 16 bits in dividend or a ; clear carry to start ; Shift next bit of quotient into bit 0 of dividend. Shift next most ; significant bit of dividend into least significant bit of remainder. ; BC holds both dividend and quotient. While we shift a bit from MSB ; of dividend, we shift next bit of quotient in from Carry. HL holds ; remainder. ; ; do a 32-bit left shift, shifting Carry to C, C to A, A to L, L to H DvLoop: rl c ; carry (next bit of quotient) to bit 0 rla ; shift remaining bytes rl l rl h ; clears carry since HL was 0 ; If remainder is greater than or equal to divisor, next bit of quotient ; is 1. This bit goes to Carry. push hl ; save current remainder sbc hl,de ; subtract divisor from remainder ccf ; complement borrow so 1 indicates a ; successful subtraction (this is next ; bit of quotient) jr c,Drop ; jump if remainder is >= dividend ex (sp),hl ; otherwise restore remainder Drop: inc sp ; drop remainder from top of stack inc sp djnz DvLoop ; continue until all bits done ; shift last carry bit into quotient ex de,hl ; de = remainder rl c ; carry to C ld l,c ; L = low byte of quotient rla ld h,a ; H = high byte of quotient or a ; clear carry, valid result ret ; ; MTIMC6 -- Stores civilian time with formatted hours ("hh:mm am", "h:mm am", ; "hh:mm pm", "h:mm pm") or relative time ("+nnnn ") to a memory buffer of ; variable size. ; Entry: HL = address of date as BCD yy mm dd hh mm ; DE = address of memory buffer (8 bytes maximum) ; Exit: DE = address of byte following output ; Uses: DE ; Modified from Carson Wilson's ZSMTIMC3.Z80 in ZSLIB 2.1. ; MTIMC6: push hl push bc push af ld b,'a' ; Default to a.m. ld c,'m' inc hl inc hl inc hl ; Point to time bit 7,(hl) ; Test relative jr z,REALTM ld a,'+' ; Show relative time ld b,' ' ld c,b call MOUT ld a,(hl) inc hl ld l,(hl) ld h,a res 7,h ; Remove flag call MHL4DC ; Store relative time jr TEXIT REALTM: ld a,(hl) ; Hours or a ; Hour 0 (00:xx a.m.)? jr z,NOTML1 ; Yes, make 12:xx a.m. cp 13h ; Time past 12:59 hours? jr c,NOTMIL ; No, don't change ld b,'p' ; Yes, p.m. sub 12h ; Xlate to civilian daa NOTMIL: cp 12h ; 12:xx p.m.? jr nz,NOTML2 ; No ld b,'p' ; Yes, show '12:xx pm' NOTML1: ld a,12h ; Make 12:xx NOTML2: call MAFHC ; Store formatted hours ld a,':' call MOUT inc hl ld a,(hl) call MA2HC ; Store minutes TEXIT: ld a,' ' call MOUT ld a,b call MOUT ; 'p' or 'a' or space ld a,c call MOUT ; 'm' or space pop af pop bc pop hl ret ; ; MDate -- Stores long form of date in American or European format ; ("March 2, 1988" or "2 March 1988"), based on the DatFmt configuration ; byte, to a variable-length memory buffer. ; Entry: HL = address of date as BCD yy mm dd ; DE = address of memory buffer (18 bytes maximum) ; Exit: DE = address of byte after output ; Uses: DE ; Modified from Carson Wilson's ZSMDAT1.Z80 in ZSLIB 2.1. ; MDate: push hl push bc push af ld c,(hl) ; save year inc hl ld b,(hl) ; save month inc hl ld a,(hl) push af ; save day ld a,(DatFmt) ; format? jr z,DoMnth ; (American) ; Store day if European format pop af ; get day call mafhc call Space ; Store month DoMnth: xor a ; clear carry for DAA ld hl,Months ld a,b ; get month push de ; save memory NxtMth: ld e,(hl) inc hl ld d,(hl) inc hl dec a daa ; decimal adjust jr nz,NxtMth ex de,hl ; HL --> month pop de ; restore memory call mstr ; month to memory call Space ld a,(DatFmt) ; format? jr nz,DoYear ; (European) ; Store day if American format pop af ; get day call mafhc ld a,',' ; print comma call mout call Space ; Store year DoYear: ld a,c ; get year cp 78h ld a,19h jr nc,Cent20 ; 20th century ld a,20h Cent20: call mafhc ; century ld a,c call ma2hc ; year pop af pop bc pop hl ret ; Space: ld a,' ' jp MOUT ; ; pstr -- makes zsyschk use epstr ; pstr: jp epstr ; ; Data storage . . . ; MONTHS: dw JAN dw FEB dw MAR dw APR dw MAY dw JUN dw JUL dw AUG dw SEP dw OCT dw NOV dw DEC ; JAN: db 'January',0 FEB: db 'February',0 MAR: db 'March',0 APR: db 'April',0 MAY: db 'May',0 JUN: db 'June',0 JUL: db 'July',0 AUG: db 'August',0 SEP: db 'September',0 OCT: db 'October',0 NOV: db 'November',0 DEC: db 'December',0 ; BakTyp: db 'BAK' ; for BAK file TmpTyp: db '$$$' ; for temporary filename ; DSEG ; ; Uninitialized storage . . . ; InFcb: ds 36 ; input file fcb OutFcb: ds 36 ; output file fcb ; OpQFlg: ds 1 ; quiet flag OpAFlg: ds 1 ; append option flag OpOFlg: ds 1 ; binary file flag OpSFlg: ds 1 ; space check and date stamp flag OpFFlg: ds 1 ; file stamp flag OpDFlg: ds 1 ; date flag OpIFlg: ds 1 ; insert divider flag CasFlg: ds 1 ; case flag for ECHO TimStr: ds 6 ; BCD time/date string TmStr2: ds 30 ; ASCII time/date string StpTmp: ds 5 ; new create date OutNam: ds 12 ; output filename ; GetFlg: ds 1 ; GetC end-of-file flag GetPtr: ds 1 ; GetC pointer PutCnt: ds 1 ; PutC counter PutPtr: ds 2 ; PutC pointer PutSec: ds 1 ; PutC sector counter InBuf: ds 2 ; input buffer address OutBuf: ds 2 ; output buffer address DftUsr: ds 1 ; default user area DftDsk: ds 1 ; default drive InUsr: ds 1 ; input file user InDrv: ds 1 ; input file drive OutUsr: ds 1 ; output file user OutDrv: ds 1 ; output file drive DskSpc: ds 2 ; disk free space FilSiz: ds 2 ; total file size (kilobytes) FilRem: ds 2 ; file size remainders (records) BufSiz: ds 1 ; output buffer size MemTop: ds 2 ; top of memory address OldStk: ds 2 ; old stack pointer OpIAdr: ds 2 ; address of CL divider string TailPt: ds 2 ; command tail index pointer CTail: ds 128 ; command tail storage ; end