; *********************************************************************** ; * * ; * Fast Unsqueezer, v2.0 * ; * 28 June 1986 * ; * Steven Greenberg * ; *********************************************************************** .Z80 TITLE 'Fast Unsqueezer, v2.0' ASEG ; (see note concerning aseg / cseg controversy ORG 100H ; at end of program where "codtbl" is defined.) ; *** CSEG [out for now] ; v2.0 - 06/28/86 ; ; Supports optional destination as well as source drive spec- ; ifications. At Bob Freed's hint, the maximum size of "codtbl" ; has been reduced to 256 (see clarification at "SQUEEZED FILE ; FORMAT"; Bytes 5+n, 6+n). This reduces the required TPA size by ; 4k. Version also gracefully terminates (with an "Unexpected ; EOF" message) should the squeezed input file be truncated or ; otherwise badly damaged. Previous versions would continuously ; decode the final record if faced with this condition. All output ; filenames are automatically converted from lower to upper case ; (Where do these squeezed files with embedded lower-case filenames ; come from, anyway?). Includes additional filename processing to ; limit filename to a maximum of 8 characters plus three extension ; characters (ie, in no case will bytes be written beyond the ; filename area of the output FCB. v2.0 also gives usage (command ; line format) if called with no arguments. Thanks to H. Goldstein ; for some of these suggestions. ; - SGG ; ; v1.9 - 04/02/86 ; ; Program now zeroes the MS bits of the filename chars before ; putting them into the output FCB (as it turns out, these bits may ; or may not be guaranteed clear, depending on the program which ; performed the squeeze). This eliminates a possible problem if the ; squeezed file was Read-Only. Fixed a glitch which would cause ; unnecessary additional sectors to be appended to the output file ; in the unlikely event its length was an exact multiple of the ; output buffer. No longer prints the program name to the console ; so you can name your .COM file as you please. Restored some doc- ; umentation that inadvertently got removed in v1.8. Put 35/36ths ; of the output FCB after the end of the program since it is now ; fully initialized by "ZERFCB". Added code to type source file- ; name(s) as well as output filename(s) to console (particularly ; useful in wildcard mode). ; - SGG ; ; v1.8 - 03/04/86 ; ; The search-and-optional-erase of the destination file was re- ; moved in favor of a "blind" delete file call. The open-file call ; immediately after the make-file has been eliminated. The TPA size ; check has been changed to take into account that versions 1.1+ ; require the CCP to remain resident. Slightly increased the input ; buffer size for this assembly prog now req's memory up to to an ; even 9000H. If anyone has memory problems, change the buffer size ; equ's at the end ("IBUFSZ","OBUFSZ",or "MAXFLS"). I made Sigi's ; wildcard expansion buffer size settable at assembly time & is ; checked for overflow at run time. ; - SGG ; ; v1.7 - 03/01/86 ; Found a pre-stack-save Z80 test that I THINK everyone can agree ; on (it also works for HD64180s & NSC800s), went back to Z80 code ; for all stack pointer moves, converted Sigi's wildcard sorter to ; Zilog mnemonics so non-M80 folks can assemble it and threw in ; some JRs here and there. Restored the disk write error message ; routine from another(!) version 1.3 (not documented here) done ; done by Bill Duerr. ; - Bruce Morgen [bm] ; ; v1.6 - 02/18/86 ; Added wildcard support and suppress non-*?Q?-files completely. ; Moved code where it belongs (CSEG)! ; - Sigi Kluger ; ; v1.5 - 02/18/86 ; One of the changes made in v1.1 has been reversed- Apparently ; the BIOS for some machines (Kaypro & Osborne?) clobber the alter- ; nate registers. Thanks to Keith Peterson for info on this. ; ; No more tricks at "FATAL" or OS stack size assumptions Built ; CR/LF into the message routine; removed extraneous OR A's after ; INC A's from the modified file open success tests. ; ; Most of the changes are in the form of improved documentation, ; & some code segments have been shifted in position. ; ; - Steven Greenberg ; ; v1.4 - 02/17/86 [intermediate unreleased version] ; ; v1.3 - 02/17/86 ; Changed stack save and load routines to the old 8080 method ; since they must run even if you try to use an 8080 processor and ; get the error message. ; - D. Jewett, III [dj] ; ; v1.2 - 02/15/86 ; Fixed source code so it could used with the M80 assembler which ; is owned by more CP/M users than all other Z80 assemblers com- ; bined (and by a wide margin at that). It can still be assembled ; with the SLR Z80ASM for which it was originally written. ; - Irv Hoff ; ; v1.1 - 02/09/86 ; Various small changes to the CP/M interface aspect of the code. ; Fixed the test for file-open success (BDOS won't always return a ; zero). Moved local stack to Copyright message and saved OS stack ; to beginning of same, eliminating warm boot-on-exit (the algor- ; ithm is so fast that the CCP reload often took as much time as ; the unsqueezing itself). Took out now unnecessary stack trick at ; FATAL: and slimmed down on PUSH/POPs at BDOSAV: (BDOS does not ; use Z80 specific registers). ; - Bruce Morgen ; _________________________________________________________________ ; (FOR CP/M 2.0+, Z-80 only) ; ; v1.0 - 01/10/86 ; ; This program unsqueezes standard (Greenlaw style) squeezed ; programs. Simply specify the filename to be unsqueezed on the ; command line; the result filename will be generated automatically ; and written to the default drive. The difference between this un- ; squeezer and others lies in its unique architecture. This results ; in speed increases ranging from a factor of about 2 (compared to ; fastest assembly coded programs previously available) on up to ; factors greater than 5 (compared to standard C coded versions). ; ; The architecture, very briefly, is as follows. First the "dic- ; tionary" info contained in the squeezed file is "compiled" into a ; decoding program, which sits in memory just above the .COM file. ; Since each squeezed file generates its own unique program, the ; program is a highly efficient subroutine for performing the ac- ; tual unsqueeze operation. ; ; Thanks to Jeff D. Wilson for many worthwhile suggestions. ; ; - Steven Greenberg ; ________________________________________________________________________ ; SQUEEZED FILE FORMAT: Here is the format of the mysterious ; squeezed file, originally defined by Richard Greenlaw around '81. ; ; BYTES 0,1: 76,FF always. Identifies the file as "squeezed". ; ; BYTES 2,3: 16-bit checksum, lo-byte first. It is the sum of ; all bytes as they appear in the GENERATED file, modulo 2^16. ; ; BYTES 4,... 4+n: Variable length filename field, containing the ; name of the original file before it was squeezed. The end of ; this field is defined by a zero byte. The "." char separating ; the filename from its .EXT is included as as a char, and its ; position may float. ; ; BYTE 5+n: Zero, as mentioned above ; ; BYTE 6+n,7+n: The length of the "dictionary", which follows. ; The value is the number "word pairs" (described below), and has a ; maximum value of 0100H. The length of the dictionary in bytes is ; four times this value (2 bytes/word x 2 words/pair). ; ; The dictionary is a binary tree based structure. It con- ; tains "internal" and "terminal" (or leaf) nodes. The maximum #of ; codes to be represented is 257 (256 possible bytes plus a special ; EOF code). This may require as many as 257 leaf nodes plus 256 ; "internal" nodes (513 total). The standard squeezed file dic- ; tionary format represents the tree by using up to 256 pairs of 2- ; byte words; each word within the pair could be a pointer to an- ; other node or a could be a "leaf" word which is terminal and ; posseses a value. In most of the following discussion, the whole ; pair is called a "node". A worst case situation takes 256 word ; pair nodes, corresponding to 256 "table entries". These will be ; described in more detail later. Pre- v2.0 versions of this ; program allocated twice this amount, with the extra serving no ; particular purpose. ; ; BYTES 8+n,... (8+n)+(4*l)-1: Dictionary, as described above. ; ; REMAINING BYTES: Squeezed code to be thought of as a cont- ; inuous bit stream where byte boundaries are insignificant. Chars ; are defined by codes of varying lengths of bits (The more freq- ; uent the character the shorter the code). The bit stream is LS ; bit thru MS bit, then move on to next byte. The end of the file ; cannot be detected until the special end-of-file code "SPEOF" is ; decoded. ; ; ADDITIONAL NOTE: Multiple contiguous occurrences of the iden- ; tical character are encoded by the 3-byte sequence: ; ; 90H ; ; When "CHAR" is first decoded, it is outputted normally. When ; the 90H is decoded, a flag is set, the next char is decoded for ; a count val, and the first char is repeated an additional "count- ; 1" times. Encoding the byte 90H itself is achieved by the seq- ; uence 90H 00H i.e., when the count is 0 send a 90H, the byte ; before the 90H was irrelevant. ;______________________________________________________________________________ ; Opcode equates JPOP EQU 0C3H ; Opcode for "JP" instruction ; ASCII equates CR EQU 0DH LF EQU 0AH ; CP/M address equates DFCB EQU 5CH ; Default file control block DFCB2 EQU 6CH ; Default file control block #2 DDMA EQU 80H ; Default dma address BDOS EQU 0005H ; Bdos entrypoint ; BDOS function equates CONOUT EQU 2 ; Print char to console PRTSTR EQU 9 ; Print string to console OPEN EQU 15 ; Open file CLOSE EQU 16 ; Close file SFIRST EQU 17 ; Search for first SNEXT EQU 18 ; Search for next ERASE EQU 19 ; Erase file READ EQU 20 ; Read file (sequential) WRITE EQU 21 ; Write file (sequential) MAKE EQU 22 ; Make file SETDMA EQU 26 ; Set dma address ;______________________________________________________________________________ ENTRY: JP START ;.............................................................................. ; OLDSTK: DEFB 'Copyright (c) Steven Greenberg 6/28/86 201-670-8724; ' DEFB 'may be copied for non-profit use only' ;.............................................................................. START: LD A,7FH ; Find out if z80 with flag test ADD A,A ; Add 7fh to 7fh JP PE,Z80 ; Parity (overflow)=z80 LD DE,WRNGUP ; "program requires z-80 processor" JP MESS80 ; Non-z80s: print up and go home Z80: LD (OLDSTK),SP ; Save os's stack LD SP,START ; Set local stack LD A,(BDOS+2) ; Size up the tpa SUB EOBFHI+10 ; (includes 2k for the ccp) JR NC,ENOUGH ; LD DE,LAKMEM ; "not enough memory..." JP FATAL ; (fatal error) ENOUGH: LD DE,LOGO ; Version#, etc CALL MESAGE ; LD A,(DFCB+1) ; See if any input file specified CP ' ' ; JR NZ,NOHELP ; LD DE,USAGE ; User needs help (so to speak) JP FATAL ; NOHELP: LD A,'Q' ; Force .?q? !!! LD (DFCB+10),A ; (middle char of filename extension) LD DE,DFCB ; Point to specified file LD HL,FNBUFF ; And to filename buffer CALL WILDEX ; Do wildcard expansion JR Z,WERR ; (if no matching file found) LD DE,MAXFLS ; Check if too many matching files AND A ; Clear carry SBC HL,DE ; JR NC,TOOMNY ; LD HL,FNBUFF ; Get name buffer LD (BUFPTR),HL ; Set up buffer pointer LD A,(DFCB2+0) ; Get destination drive spec for all output LD (OFCB+0),A ; Put it in the drive byte of the output FCB ;______________________________________________________________________________ ; ; *** Come here for each new file *** NXTFIL: XOR A ; Zero the "EOF flag" LD (EOFLAG),A ; LD DE,DFCB+1 ; Clear input fcb PUSH DE CALL ZERFCB LD HL,(BUFPTR) POP DE PUSH HL ; Save filepointer LD BC,11 ; 11 characters INC HL LDIR ; Move next filename in place LD DE,OFCB+1 CALL ZERFCB ; Clean output fcb POP HL LD DE,16 ; Offset to next filename ADD HL,DE LD (BUFPTR),HL LD DE,DFCB LD C,OPEN CALL BDOSAV INC A JR NZ,PRIN ; Br if successful WERR: LD DE,ERR1 ; Else, "Input file not found" JP FATAL TOOMNY: LD DE,ERR3 ; "too many matching files" JP FATAL PRIN: LD DE,CRLF ; Print a CR/LF CALL PRINT ; LD HL,DFCB ; Print name of input file CALL PRNFIL ; ;______________________________________________________________________________ ; ; Before going too much further, take this opportunity to "clone" a ; 16 byte template of code into memory 256 times. This forms the ; skeleton for the compiled block of code "CODTBL". Various specific ; instructions and data will overwrite sections of this template after ; the dictionary info is read. CLONE: LD HL,TMPLAT ; Xfer one copy to the beg of "CODTBL" LD DE,CODTBL LD BC,16 LDIR LD HL,CODTBL ; Now copy it 255 more times. LD BC,255*16 ; De already points to "CODTBL+16" LDIR ; That does it ; Now load up the input buffer. The input buffer, the output buffer, ; and "CODTBL" are all page aligned and of page multiple lengths. There ; are no other criteria for the lengths of the buffers, except that the ; input buffer should have a minimum length of 1K plus 1 more page. This ; guarantees that the entire dictionary (plus miscellaneous header info) ; will be read in on the 1st pass, simplifying the program. CALL RELOAD ; (leaves HL pointing to beg of "ibuf") XOR A ; Init the "EOF flag" to zero (again) LD (EOFLAG),A ; (detects multiple reads of last sector) LD A,(HL) ; Get first byte of squeezed file CP 76H ; Check for "Squeezed File Header" 76H,FFH JR NZ,NTSQZD ; Br if not a squeezed file INC L ; Note buffer starts on a page boundary INC (HL) ; Chk for ff (clobber it along the way) NTSQZD: LD DE,NSQMSG ; Meanwhile , prep for poss err msg JP NZ,FATAL ; Fatal "not squeezed" condition LD DE,ARROW ; " --->" CALL PRINT ; Ok, print an arrow LD HL,(IBUF+2) ; Get the 16 bit checksum and save LD (CHKSUM),HL ; Goes there LD HL,IBUF+3 ; Init past "76FF" and 2 byte checksum (-1) LD DE,OFCB+1 ; Init to point to filename of output fcb LD B,11 ; Set loop cntr for max #of chars in filename EATLP: INC L ; LD A,(HL) ; Eat up the file name OR A ; A zero byte indicates end of filename JR Z,ATEIT ; Br when that is encountered AND 7FH ; Strip off any "attribits" CALL UCASE ; Upcase char if necessary CP '.' ; Check for name / ext division char JR Z,ISDOT ; Br when encountered LD (DE),A ; Else copy filename char to output fcb INC DE ; And incr that pointer DJNZ EATLP ; Continue, but not past filename area of FCB INC HL ; Once more (position should contain "0") JR ATEIT ; We are done, by definition. ; When "." is encountered, skip to the file extension bytes of the output ; FCB. (Any remaining non-extension bytes were init'd to blank). Do not ; copy the "." to the output FCB. ISDOT: LD DE,OFCB+9 ; Skip to filename .EXT area LD B,3 ; Adjust counter appropriately JR EATLP ; And continue ATEIT: PUSH HL ; Save current pointer value LD HL,OFCB ; Print the output filename CALL PRNFIL ; Print the filename to the console POP HL ; Restore value LD DE,OFCB ; Output fcb LD C,ERASE ; "blind erase" the dest file if it exists CALL BDOSAV ; (*** implement a prompt here? ***) LD C,MAKE ; In any case, make the new file CALL BDOSAV INC A JR NZ,MAKTBL ; Err cond check LD DE,ERR2 ; "file open error" JP FATAL ; Exit ;______________________________________________________________________________ ; ; Now create "CODTBL" by overwriting certain sections of the ; template created above. The dictionary contains 4 byte nodes- ; these are converted into 16 byte code segments. The maximum ; length of the original dictionary is (256*4) bytes (2K) corres- ; ponding to a maximum "CODTBL" length of 8K. ; ; NODE DEFINITION: As mentioned above, a "node" consists of two ; pairs of bytes. The first pair corresponds to a zero bit, the ; latter to a "1". To decode a character, we start at node #0. A ; bit is pulled off the bit stream. We then use the 1st or 2nd byte ; pair depending on the bit value. The byte pair takes on 1 of 2 ; forms "nn FF" or "xx 0x". The "nn FF" type is a terminal node, ; it means we have our next output value - that value specifically ; being the 1's complement (makes it more mysterious) of "nn". If ; the node is of the second type, it is a pointer to another node ; (an absolute offset from the beg of the dictionary in terms of ; node#, must be multiplied by 4 for a byte offset. It has a max- ; imum value of 256, and is expressed as a 16-bit #, lo-byte ; first). In this case we go to that node, pull another bit off ; the input stream, and continue the process. ; ; There is actually a 3rd node type, which just comes up once. ; Its form is "FF FE". It's a special end-of-file marker called ; "SPEOF". ; ; HOW THE PROGRAM WORKS: Each node is converted into a 16-byte ; (actually 13 plus 3 nop's) series of instructions. These in- ; structions later perform the unsqueezing operation. The whole ; block of code starts at "CODTBL" (which is also the entrypoint). ; Each 13 byte "node code" consists of a 7 byte header. The header ; shifts out the next bit from reg "b", then conditionally branches ; to the first or second half of the remaining code (3 bytes per ; half; 7+3+3 = 13). The 3 bytes in each half are either the 2 ; instruc-tions "LD A," followed by "RET" (terminal node) or ; the single instruction "JP ". All calls to "CODTBL" ; even-tually hit a terminal node and perform a normal return. The ; only exception is the special end-of-file node which compiles to ; "JP SPEOF"; on this particular return the stack is manually ad- ; justed to compensate for the lack of a "RET" instruction. ; ; THE TEMPLATE: This is the "template" which was "cloned" earlier ; ; The following 3 instructions form the header code for every ; node. They are identical for every node (since the jump is rel- ; ative). ;.............................................................................. TMPLAT: SRL B ; Shift out next bit CALL Z,REFILL ; Refill reg when empty JR C,BITIS1 ; If bit is "1" ;.............................................................................. ; ; After the header code gets executed, one of 2 halves of the ; remainder of the node gets executed. Which half depends on the ; bit shifted out above ("0" for the first half, "1" for the 2nd). ; Each half-node has two possible forms. The terminal form loads ; an appropriate value and returns. The non-terminal form jumps to ; the header of another node. This 16-byte "node-code template" ; assumes the former case by default, since 2 of 3 bytes in that ; case are fixed (only the value need be in-serted). If it turns ; out to be the latter case, all 3 bytes will be overwritten with a ; "jmp" opcode plus an appropriate address. ;.............................................................................. BITIS0: LD A,00H ; (00h gets replaced with actual value RET ; To be returned.) BITIS1: LD A,00H ; 2nd half of the node, likewise RET ; NOP ; } NOP ; } so the template is exactly 16 bytes NOP ; } ;______________________________________________________________________________ ; ; --- Create the "node code" table --- MAKTBL: INC L ; Now points one past the filename eof LD E,(HL) ; Get #of nodes (lo byte) INC L LD D,(HL) ; Hi byte of same INC L LD A,D ; An additional file validity check: though the #of nodes could in theory ; theory as high as 0100H, the high byte shoul never be 2 or more SUB 2 ; If this happens, assume it is not a JP NC,NTSQZD ; Squeezed file. ; HL indexes through source dictionary (already initialized), & HL' is ; current dest pointer (indexes thru "codtbl"). DE is initialized to the ; # of nodes and is decreased to 0. EXX ; Init some constants LD DE,10 ; Used for incrementing HL' LD HL,CODTBL+7 ; Init HL' itself EXX ; ; Remember, the whole "header" code and some other instructions are already ; there (from when "template" was duplicated). Only specific details need ; now be filled in. NODELP: CALL MAKHAF ; Make the first ("0") half-node CALL MAKHAF ; 2nd ("1") half-node ; Source pointer has already been incremented 4 times, as desired. Dest ; pointer has only been incremented 6 times, however. EXX ; } ADD HL,DE ; } so take care of that EXX ; } DEC DE ; Loop counter LD A,D OR E JR NZ,NODELP ; Continue till done JR RUN ; Go run, hl is is ready, pointing to ; the first byte of squeezed code ;______________________________________________________________________________ ; ; Create a "half-node" MAKHAF: LD C,(HL) INC HL LD A,(HL) ; Get a byte pair from the dictionary INC HL OR A ; If it is negative, it is "terminal" JP M,TERMOD ; Branch if that is the case. ;.............................................................................. ; ; Create code for one half-node of the non-terminal variety. ; Byte pair is in A,C. Multiply it by 16 (bytes/node in "codtbl") SLA C RLA SLA C RLA SLA C RLA SLA C RLA ADD A,CDTBLH ; Add offset to beginning of "CODTBL", LD B,A ; Now bc has the jump address ALTENT: PUSH BC ; Save it EXX ; Switch to dest pointers LD (HL),JPOP ; Insert the "jp" opcode INC L ; Remember "codtbl" is page aligned POP BC ; Get addr back LD (HL),C ; Jump addr, lo INC L ; LD (HL),B ; Jump addr, hi INC L ; EXX ; Back to source pointers RET ; Thats all ;.............................................................................. ; ; Create a half-node of the terminal variety TERMOD: CP 0FEH ; Check for special eof terminal node JR Z,SPEOF ; Br for that unique case LD A,C ; Else this byte is the complement of ; The returned value CPL EXX ; Switch to dest pointers INC L ; Just 2nd of 3 bytes need be inserted LD (HL),A ; Put it in INC L ; INC L ; But make sure hl gets incr'd 3 times EXX ; RET ; That's all ;.............................................................................. ; ; Special EOF returns to a special address in mainline code, rather ; than using "RET". Stack is adjusted accordingly there. SPEOF: LD BC,DONE ; The special address JR ALTENT ; Use convenient code subsection above ;______________________________________________________________________________ ; ; Code to refill register 'B' with the next byte REFILL: INC L JR Z,POSRLD ; If l is zero, may be at end of buffer CONT: LD B,(HL) ; Else get next byte ; Now we pre-shift out the next bit, shifting in a "1" from the left. ; Since the leftmost bit in the reg is a guaranteed "1", testing the ; zero stat of the reg is a necesssary and sufficient condition for ; determining that all the bits in the reg have been used up (see ; header code for "TMPLATE"). The only things to be careful of is that ; the the last bit is NOT used, and that the bit now in the carry flag ; IS used upon return from this subroutine. SCF ; To shift in the flag bit RR B ; Shift out real bit as described RET ; That's it POSRLD: INC H ; Check if time to reload the input LD A,EIBFHI ; Buffer with additional data. CP H ; CALL Z,RELOAD ; Reload if necessary (resets hl) JR CONT ;______________________________________________________________________________ ; ; Main code to perform the unsqueeze. In general, the alternate regs ; are used as output pointers, flags, etc. while the primary registers ; are used for input pointing and general purpose use. RUN: EXX ; First initialize the alternate regs LD HL,OBUF ; HL', output pntr, to beg of output bfr LD BC,0 ; C' always has a copy of the previous EXX ; char output; B' is a "repeat flag". ; Primary register initialization: initialize HL, the input pntr, to point to ; the first byte of squeezed code -1. Initialize DE, the checksum accumulator, ; to zero. Initialize 'B', which contains bits currently being decoded, to ; to 0 so first "decode" call will immediately load it with the first byte. DEC HL ; First byte of squeeze code -1 LD DE,0 ; Checksum accumulator LD B,D ; (zero) ;.............................................................................. ; ; *** Main operational loop *** MAINLP: CALL CODTBL ; Unsqueeze a character CALL SEND ; Output it to the output buffer JR MAINLP ; And repeat "forever" (see "SPEOF" ;______________________________________________________________________________ ; ; Reload the input buffer, & reset HL to point to the beginning of it. ; Assumes input bfr starts page boundry and is of page multiple length. RELOAD: PUSH AF PUSH BC PUSH DE LD B,IBUFSZ ; Loop counter, buffer length in pages LD D,IBUFHI ; Beg of buffer (hi) RLDLP: LD E,0 ; Lo byte of current dma CALL RDSEC ; Read in 128 bytes (1/2 page) JR NZ,RLDRTN ; (return if eof enecountered) LD E,80H ; To read in the next half page CALL RDSEC ; Do that JR NZ,RLDRTN ; As above INC D ; Next page DJNZ RLDLP ; Loop till done RLDRTN: POP DE ; Restore regs POP BC POP AF LD HL,IBUF ; Reset input pointer RET ; And return ;.............................................................................. ; ; Subr for abover, reads 128 bytes to memory starting at HL RDSEC: PUSH DE ; Save dma before clobbering it with fcb LD C,SETDMA ; Set dma function CALL BDOSAV ; LD DE,DFCB ; Input fcb LD C,READ ; CALL BDOSAV ; Read a record POP DE ; Restore dma to original dma address OR A ; Set non-zero status RET Z ; Normal return, barring EOF condition LD A,(EOFLAG) ; Check if EOF is hit twice OR A ; JR NZ,HTCHED ; This is an abnormal condition CPL ; Else (first EOF) put "FF" in "eoflag" LD (EOFLAG),A ; OR A ; Set non-zero status and return RET ; HTCHED: LD DE,CHOPPD ; "Unexpected EOF enountered" JP FATAL ; (file is truncated or damaged) ;______________________________________________________________________________ ; ; When "SPEOF" is encountered, a jump to here is made to exit "CODTBL" ; rather than the normal RET instruction. DONE: INC SP ; So adjust the stack immediately! INC SP ; LD A,(CHKSUM+0) ; Make sure the checksum checks out CP E ; Lo-byte JR NZ,NFG ; Br if nfg LD A,(CHKSUM+1) ; Likewise CP D ; JR Z,CKSMOK ; Ok ; If a checksum error is detected, report the warning. Let the guy ; have his file anyway, for whats its worth. NFG: LD DE,CHKERR ; "checksum error detected" CALL MESAGE ; ; Switch to alternate regs for output. The total #of bytes generated ; should always be a multiple of 128. This assumption is not made, ; however, as it may not be true if the file was squeezed on non-CP/M ; systems. Compute # of sectors to write- specifically subtract the ; buffer start addr from the current pointer value, add 7FH and divide ; by 128. If the byte count was in fact a multiple of 128, this has ; no effect; otherwise it makes sure the final sector gets written. CKSMOK: EXX ; Switch to alt regs AND A ; Clear carry LD DE,OBUF-7FH ; Take care of adding 7fh in advance SBC HL,DE ; Subtract SLA L ; Divide by 128 RL H ; Result now in h LD B,H ; Use 'b' as the counter CALL WRTOUT ; Writes 'b' sectors to the output file EXX ; Back to primary regs LD DE,OFCB ; Close the output file LD C,CLOSE CALL BDOSAV LD DE,DFCB ; Likewise the input file LD C,CLOSE CALL BDOSAV ; Fall through EXIT: LD HL,(NMBFLS) DEC HL LD (NMBFLS),HL LD A,H OR L JP NZ,NXTFIL ; Next file LD SP,(OLDSTK) ; Restore os stack RET ; To ccp ;______________________________________________________________________________ ; ; Write 'B' 128 byte sectors to the output file ; WRTOUT: LD A,B ; If b=0, don't write any sectors OR A ; RET Z ; LD DE,OBUF ; Init dma addr to beg of output bfr WRTLP: LD C,SETDMA ; Set dma to there CALL BDOSAV PUSH DE ; Save that address LD DE,OFCB ; Specify the output file LD C,WRITE ; Write a record CALL BDOSAV ; OR A JR NZ,WRTERR ; Br on error POP DE ; Address as saved above DJNZ NEXSEC ; Decrement counter & continue if not done RET ; NEXSEC: LD E,80H ; Else incr by 1/2 page LD C,SETDMA ; CALL BDOSAV ; PUSH DE ; Save dma pntr LD DE,OFCB ; Output fcb LD C,WRITE ; Write another sec CALL BDOSAV ; OR A ; JR NZ,WRTERR ; Br if error POP DE ; Get back orig pointer INC D ; Inc hi-byte, 0 the lo to effect LD E,0 ; Another 80h incr DJNZ WRTLP ; Loop till done RET ;______________________________________________________________________________ ; BDOSAV: ; Bdos call w/ all regs and alts saved EXX ; except for AF, AF', IX, & IY PUSH BC PUSH DE PUSH HL EXX PUSH BC PUSH DE PUSH HL CALL BDOS POP HL POP DE POP BC EXX POP HL POP DE POP BC EXX RET WRTERR: LD DE,WRTMSG ; Write error, falls through to "fatal" ;______________________________________________________________________________ ; ; For fatal errors- print the message, restore the os stack & return ; This rountine is "jumped to", not called ; FATAL: CALL MESAGE ; LD DE,CRLF ; CALL PRINT ; LD SP,(OLDSTK) ; Restore stack pointer Z80-style RET ; MESAGE: EX DE,HL ; Save pntr to message (supplied in de) LD DE,CRLF ; First print a cr/lf sequence LD C,PRTSTR CALL BDOSAV EX DE,HL ; Then the message in question PRINT: LD C,PRTSTR ; Entry here if no cr/lf desired JP BDOSAV MESS80: LD C,PRTSTR ; For non-Z80 mesage, don't use "BDOSAV" JP BDOS ; ;______________________________________________________________________________ ; ; Send character to the output buffer, plus related processing ; SEND: EXX ; Alt regs used for output processing SRL B ; If reg is "1", repeat flag is set ; (note, clears itself automatically) JR C,REPEAT ; Go perf the repeat CP 90H ; Else see if char is the repeat spec JR Z,SETRPT ; Br if so LD C,A ; Else nothing special- but always keep CALL OUT ; Else just output the char; EXX ; Back to normal regs RET ; ; Set repeat flag; count value will come as the next byte. (Note: don't ; clobber C with the "90H"- it still has the prev character, the one to ; be repeated) SETRPT: INC B ; Set flag EXX ; Switch to primary regs & return. RET ; Repeat flag was previously set; current byte in a is a count value. ; A zero count is a special case which means send 90H itself. Otherwise ; use B (was the flag) as a counter. The byte itself goes in A. REPEAT: OR A ; Check for special case JR Z,SND90H ; Jump if so DEC A ; Compute "count-1" LD B,A ; Juggle registers LD A,C ; AGAIN: CALL OUT ; Repeat b occurrences of byte in 'a' DJNZ AGAIN ; Leaves b, the rpt flag, 0 as desired EXX ; Restore regs & rtn RET SND90H: LD A,90H ; Special case code to send the byte 90h CALL OUT ; Itself EXX ; RET ; (90h "squeezes" into 2 bytes!) ;.............................................................................. ; ; Output character in 'A' directly to the output buffer ; OUT: EXX ; Back to primary regs briefly LD C,A ; Save a in c ADD A,E ; De is the running checksum LD E,A JR NC,NOCARY INC D NOCARY: LD A,C ; Put the char back into a EXX ; Back to output (alternate) regs LD (HL),A ; Put byte into the next avail position INC L ; Increment pointer RET NZ ; Return if not passing a page boundry INC H ; Incr pointer high byte, check limit LD L,A ; Use l, which is 0, for temp storage LD A,EOBFHI ; Limit CP H ; Check LD A,L ; But first restore regs LD L,0 ; RET NZ ; Ret if limit not reached PUSH AF PUSH BC LD B,OBUFSZ*2 ; Number of 128 byte records to write CALL WRTOUT POP BC POP AF LD HL,OBUF RET ;______________________________________________________________________________ ; ; Initialize an FCB. DE points to the FCB +1. ; ZERFCB: LD B,11 ; Fill filename with blanks LD A,' ' CALL ZL LD B,24 ; Then zero remainder XOR A ZL: LD (DE),A INC DE DJNZ ZL RET ;______________________________________________________________________________ ; ; Print name of output file. HL should point to the FCB. ; PRNFIL: LD A,(HL) ; Get drive spec INC HL ; Move to 1st char of filename OR A ; Drive = default? JR Z,DEFDRV ; Br if so ADD A,'A'-1 ; Else convert to a letter CALL TYPE ; LD A,':' ; Follow drive spec with a ":" CALL TYPE ; DEFDRV: LD B,12 ; Loop cntr (max #of chars plus ".") CHARLP: LD A,(HL) ; Get a char CP " " ; Blank? JR Z,SKPTYP ; Supress them TYPEIT: CALL TYPE ; Type the char SKPTYP: DEC B ; Loop counter RET Z ; Rtn when done LD A,B ; Check loop counter CP 4 ; At this point, type a "." JR NZ,NOT4 ; LD A,"." ; JR TYPEIT ; Type it. do not incr hl or reload a. NOT4: INC HL ; Advance pointer JR CHARLP ; Repeat till done ;______________________________________________________________________________ ; TYPE: LD C,CONOUT ; Type the char in "a" to the console LD E,A ; (clobbers c & e) CALL BDOSAV ; RET ; ;______________________________________________________________________________ ; ;WILDEX - wildcard expansion module ; S. Kluger 04/15/84 ; ; This module, for use with SYSLIB, can be used to expand a wildcard ; filename into a table of file names as found in current DU: ; ENTRY: ; HL = .buffer ; DE = .afn fcb ; EXIT: ; HL = number of files ; ACC= zero flag set if error ; the buffer contains HL file names of 16 char each ; Char 0 contains the user number! WILDEX: LD (BUFPTR),HL LD HL,0 LD (NMBFLS),HL LD C,SFIRST CALL BDOSAV CP 0FFH RET Z ; Nothing found -- error CALL MOVEN ; Move name WLOOP: LD C,SNEXT ; Search for next CALL BDOSAV CP 0FFH JR Z,DONEW ; Finished CALL MOVEN JR WLOOP DONEW: OR A LD HL,(NMBFLS) RET MOVEN: PUSH DE LD HL,(BUFPTR) ADD A,A ADD A,A ADD A,A ADD A,A ADD A,A ADD A,80H LD C,A LD B,0 LD D,16 ; Move 16 chars MOVLP: LD A,(BC) LD (HL),A INC HL INC BC DEC D JR NZ,MOVLP LD (BUFPTR),HL POP DE LD HL,(NMBFLS) INC HL LD (NMBFLS),HL RET ;______________________________________________________________________________ ; UCASE: ; "Upcase" the letter in "A", if necessary CP 'a' ; RET C ; If < "a", forget it CP 'z'+1 ; RET NC ; Likewise if > "z" SUB 20H ; Else convert RET ; ;______________________________________________________________________________ LOGO: DEFB 'Fast Unsqueezer v2.0',CR,LF,'$' ERR1: DEFB 'Input file not found.$' ERR2: DEFB 'File open error.$' ERR3: DEFB 'Too many matching files.$' ARROW: DEFB ' ---> $' LAKMEM: DEFB 'Out of memory.$' NSQMSG: DEFB 'Not a squeezed file.$' CHKERR: DEFB 'Checksum error detected.$' WRNGUP: DEFB 'Program needs Z-80.$' WRTMSG: DEFB 'Output error.$' CHOPPD: DEFB 'Unexpected EOF encountered.$' USAGE: DEFB 'Usage: UF [d:] [d:]',CR,LF DEFB ' optional drive specs are source & dest. respectively.$' CRLF: DEFB CR,LF,'$' ;______________________________________________________________________________ ; BUFPTR: DEFW 0 ; Used for indexing thru "fnbuff" NMBFLS: DEFW 0 ; #of wild card matches found ;.............................................................................. CHKSUM: DS 2 ; Checksum kept here EOFLAG: DS 1 ; "EOF Flag"; set from 0 to FF when EOF is hit OFCB: DS 36 ; Output fcb; gets initialized by "zerfcb" MAXFLS EQU 400 ; Max #of filenames in a wild card expansion ; (should be sufficient!) FNBUFF: DS (16*MAXFLS) ; Wildcard expansion buffer ENDFNB EQU $ ; End of above buffer ;______________________________________________________________________________ ; ; Compute next page boundary following the end of the above buffer. Do ; this by adding 0FFH and "anding" with 0FF00H. This will become the ; beginning of "CODTBL". ; *** NOTE *** [sgg] ; If this code is CSEG'd, M80 will reject this. You can fake ; out the assembler using a "HIGH" , then multiplying by 256, ; but then it will link incorrectly. If you just EQU "codtbl", ; the resulting code isn't really relocatable anyway... ; ; I think there are ways around this, but they involve the ; assumption that this whole file is "page relocatable" only. PGBND EQU (ENDFNB+0FFH) AND 0FF00H ; (works with aseg only) ORG PGBND ;.............................................................................. ; ; Minimum input buffer size is 5 pages to guarantee that the max possible ; dictionary size (256 x 4) bytes plus overhead) loads the first pass. ; ; Output buffer can be any page multiple length (note it too is also page ; aligned). Increases beyond 64 pages (16k) may do little to improve perf. IBUFSZ EQU 16 ; Input buffer size (pages) OBUFSZ EQU 64 ; Output buffer size (pages) PAGE EQU 256 ; (for clarity) CODTBL: DS 256*16 ; "codtbl"; 256 entries x 16 bytes/entry IBUF: DS IBUFSZ*PAGE ; Input buffer OBUF: DS OBUFSZ*PAGE ; Output buffer EOBUF EQU $ ; End of output buffer CDTBLH EQU HIGH CODTBL ; } IBUFHI EQU HIGH IBUF ; } high bytes of the beginning addresses EIBFHI EQU HIGH OBUF ; } of the buffers just defined EOBFHI EQU HIGH EOBUF ; } END