/**************************************************************************** ** *A unzoo.c Tools Martin Schoenert ** *H @(#)$Id: unzoo.c,v 1.5 1994/01/21 13:32:32 mschoene Exp $ ** *Y This file is in the Public Domain. ** ** SYNTAX ** ** 'unzoo' ** 'unzoo [-l] [-v] [.zoo] [..]' ** 'unzoo -x [-abnpo] [.zoo] [..]' ** ** DESCRIPTION ** ** 'unzoo' is a zoo archive extractor. A zoo archive is a file that ** contains several files, called its members, usually in compressed form to ** save space. 'unzoo' can list all or selected members or extract all or ** selected members, i.e., uncompress them and write them to files. It ** cannot add new members or delete members. For this you need the zoo ** archiver, called 'zoo', written by Rahul Dhesi. ** ** If you call 'unzoo' with no arguments, it will first print a summary of ** the commands and then prompt for command lines interactively, until you ** enter an empty line. This is useful on systems that do not support the ** notion of command line arguments such as the Macintosh. ** ** If you call 'unzoo' with the '-l' option, it lists the members in the ** archive . For each member 'unzoo' prints the size that the ** extracted file would have, the compression factor, the size that the ** member occupies in the archive (not counting the space needed to store ** the attributes such as the path name of the file), the date and time when ** the files were last modified, and finally the path name itself. Finally ** 'unzoo' prints a grand total for the file sizes, the compression factor, ** and the member sizes. ** ** The '-v' suboption causes 'unzoo' to append to each path name, separated ** by a ';', the generation number of the member, where higher numbers mean ** later generations. Members for which generations are disabled are listed ** with ';0'. Also 'unzoo' will print the comments associated with the ** archive itself or the members, preceeded by the string '# '. ** ** If you call 'unzoo' with the '-x' option, it extracts the members from ** the archive . Members are stored with a full path name in the ** archive and if the operating system supports this, they will be extracted ** into appropriate subdirectories, which will be created on demand. ** The members are usually extracted as binary files, with no translation. ** However, if a member has a comment that starts with the string '!TEXT!', ** it is extracted as a text file, i.e., it will be translated from the ** universal text file format (with as line separator as under UNIX) to ** the local text file format (e.g., with / as separator under DOS). ** If the archive itself has a comment that starts with '!TEXT!' then all ** members will be extracted as text files, even those that have no comment. ** For each member the name is printed followed by '-- extracted as binary' ** or '-- extracted as text' when the member has been completely extracted. ** ** The '-a' suboption causes 'unzoo' to extract all members as text files, ** even if they have no comment starting with '!TEXT!'. ** ** The '-b' suboption causes 'unzoo' to extract all members as binary files, ** even if they have a comment starting with '!TEXT!'. ** ** The '-n' suboption causes 'unzoo' to suppress writing the files. You use ** this suboption to test the integrity of the archive without extracting ** the members. For each member the name is printed followed by '-- tested' ** if the member is intact or by '-- error, CRC failed' if it is not. ** ** The '-p' suboption causes 'unzoo' to print the files to stdout instead of ** writing them to files. ** ** The '-o' suboption causes 'unzoo' to overwrite existing files without ** asking you for confirmation. The default is to ask for confirmation ** ' exists, overwrite it? (Yes/No/All/Ren)'. To this you can answer ** with 'y' to overwrite the file, 'n' to skip extraction of the file, 'a' ** to overwrite this and all following files, or 'r' to enter a new name for ** the file. 'unzoo' will never overwrite existing read-only files. ** ** The '-j ' suboption causes 'unzoo' to prepend the string ** to all path names for the members before they are extracted. So for ** example if an archive contains absolute path names under UNIX, '-j ./' ** can be used to convert them to relative pathnames. This option is also ** useful on the Macintosh where you start 'unzoo' by clicking, because ** then the current directory will be the one where 'unzoo' is, not the one ** where the archive is. Note that the directory must exist, ** 'unzoo' will not create it on demand. ** ** If no argument is given all members are listed or extracted. ** If one or more arguments are given, only members whose names ** match at least one of the patterns are listed or extracted. ** can contain the wildcard '?', which matches any character in ** names, and '*', which matches any number of characters in names. When ** you pass the arguments on the command line you will usually have ** to quote them to keep the shell from trying to expand them. ** ** Usually 'unzoo' will only list or extract the latest generation of each ** member. But if you append ';' to a path name pattern the generation ** with the number is listed or extracted. itself can contain the ** wildcard characters '?' and '*', so appending ';*' to a path name pattern ** causes all generations to be listed or extracted. ** ** ** COMPATIBILITY ** ** 'unzoo' is based heavily on the 'booz' archive extractor by Rahul Dhesi. ** I basically stuffed everything in one file (so no 'Makefile' is needed), ** cleaned it up (so that it is now more portable and a little bit faster), ** and added the support for long file names, directories, and comments. ** ** 'unzoo' differs in some details from 'booz' and the zoo archiver 'zoo'. ** ** 'unzoo' can only list and extract members from archives, like 'booz'. ** 'zoo' can also add members, delete members, etc. ** ** 'unzoo' can extract members as text files, converting from universal text ** format to the local text format, if the '-a' option is given or the '-b' ** option is not given and the member has a comment starting with '!TEXT!'. ** So in the absence of the '-a' option and comments starting with '!TEXT!', ** 'unzoo' behaves like 'zoo' and 'booz', which always extract as binary. ** But 'unzoo' can correctly extract text files from archives that were ** created under UNIX (or other systems using the universal text format) and ** extended with '!TEXT!' comments on systems such as DOS, VMS, Macintosh. ** ** 'unzoo' can handle long names, which it converts in a system dependent ** manner to local names, like 'zoo' (this may not be available on all ** systems). 'booz' always uses the short DOS format names. ** ** 'unzoo' extracts members into subdirectories, which it automatically ** creates, like 'zoo' (this may not be available on all systems). 'booz' ** always extracts all members into the current directory. ** ** 'unzoo' can handle comments and generations in the archive, like 'zoo'. ** 'booz' ignores all comments and generations. ** ** 'unzoo' cannot handle members compressed with the old method, only with ** the new high method or not compressed at all. 'zoo' and 'booz' also ** handle members compress with the old method. This shall be fixed soon. ** ** 'unzoo' can handle archives in binary format under VMS, i.e., it is not ** necessary to convert them to stream linefeed format with 'bilf' first. ** 'zoo' and 'booz' require this conversion. ** ** 'unzoo' is somewhat faster than 'zoo' and 'booz'. ** ** 'unzoo' should be much easier to port than both 'zoo' and 'booz'. ** ** COMPILATION ** ** Under UNIX with the standard C compiler, compile 'unzoo' as follows ** cc -o unzoo -DSYS_IS_UNIX -O unzoo.c ** If your UNIX has the 'mkdir' system call, you may add '-DSYS_HAS_MKDIR' ** for a slightly faster executable. BSD has it, else try 'man 2 mkdir'. ** ** Under DOS with the DJGPP GNU C compiler, compile 'unzoo' as follows ** gcc -o unzoo.out -DSYS_IS_DOS_DJGPP -O2 unzoo.c ** copy /b \djgpp\bin\go32.exe+unzoo.out unzoo.exe ** ** Under TOS with the GNU compiler and unixmode, compile 'unzoo' as follows ** gcc -o unzoo.ttp -DSYS_IS_TOS_GCC -O2 unzoo.c ** ** Under OS/2 2 with the emx development system, compile 'unzoo' as follows ** gcc -o unzoo.exe -DSYS_IS_OS2_EMX -Zomf -Zsys -O2 unzoo.c ** To create an executable that runs under OS/2 and DOS, but which requires ** the emx runtime, compile without the '-Zomf' and '-Zsys' options. ** ** On a VAX running VMS with the DEC C compiler, compile 'unzoo' as follows ** cc unzoo/define=SYS_IS_VMS ** link unzoo ** Then perform the following global symbolic assignment ** unzoo :== $:[]unzoo.exe ** where is the name of the directory where you have installed ** 'unzoo' and is the device on which this directory is, for example ** unzoo :== $dia1:[progs.archivers]unzoo ** You may want to put this symbolic assignment into your 'login.com' file. ** ** On a Macintosh with the MPW C compiler, compile 'unzoo' as follows ** C -model far -d SYS_IS_MAC_MPW -opt on unzoo.c ** Link -model far -d -c '????' -t APPL unzoo.c.o -o unzoo ** "{CLibraries}"StdClib.o "{Libraries}"SIOW.o ** "{Libraries}"Runtime.o "{Libraries}"Interface.o ** Rez -a "{RIncludes}"SIOW.r -o unzoo ** Afterwards choose the 'Get Info' command in the finder 'File' menu and ** increase the amount of memory 'unzoo' gets upon startup to 256 KBytes. ** To create a MPW tool instead of a standalone, link with creator 'MPS ' ** instead of '????', with type 'MPST' instead of 'APPL' and with 'Stubs.o' ** instead of 'SIOW.o'. The 'Rez' command is not required for the tool. ** Alternatively choose the 'Create Build Commands...' command from the MPW ** 'Build' menu to create a makefile. Edit it and add '-d SYS_IS_MAC_MPW' ** to the compile command. Choose the 'Build...' command from the 'Build' ** menu to build 'unzoo'. ** ** On other systems with a C compiler, try to compile 'unzoo' as follows ** cc -o unzoo -DSYS_IS_GENERIC -O unzoo.c ** ** PORTING ** ** If this does not work, you must supply new definitions for the macros ** 'OPEN_READ_ARCH', 'OPEN_READ_TEXT' and 'OPEN_WRIT_TEXT'. If you want ** 'unzoo' to keep long file names, you must supply a definition for the ** macro 'CONV_NAME'. If you want 'unzoo' to extract into subdirectories, ** you must supply a definition for the macro 'CONV_DIRE'. If you want ** 'unzoo' to automatically create directories, you must supply a definition ** for the macro 'MAKE_DIR'. If you want 'unzoo' to set the permissions of ** extracted members to those recorded in the archive, you must supply a ** definition for the macro 'SETF_PERM'. Finally if you want 'unzoo' to set ** the times of the extracted members to the times recorded in the archive, ** you must supply a definition for the macro 'SETF_TIME'. Everything else ** should be system independent. ** ** ACKNOWLEDGMENTS ** ** Rahul Dhesi wrote the 'zoo' archiver and the 'booz' archive extractor. ** Haruhiko Okumura wrote the LZH code (originally for his 'ar' archiver). ** David Schwaderer provided the CRC-16 calculation in PC Tech Journal 4/85. ** Jeff Damens wrote the name match code in 'booz' (originally for Kermit). ** Harald Boegeholz ported 'unzoo' to OS/2 with the emx development system. ** Dave Bayer ported 'unzoo' to the Macintosh, including Macbinary support. ** ** HISTORY *H $Log: unzoo.c,v $ *H Revision 1.5 1994/01/21 13:32:32 mschoene *H added Mac support from Dave Bayer *H *H Revision 1.4 1994/01/20 20:45:46 mschoene *H cleaned up determination of write mode *H *H Revision 1.3 1993/12/02 12:43:12 mschoene *H added OS/2 support from Harald Boegeholz *H *H Revision 1.2 1993/12/02 12:33:39 mschoene *H fixed several typos, renamed MS-DOS to DOS *H *H Revision 1.1 1993/11/09 07:17:50 mschoene *H Initial revision *H */ #include /**************************************************************************** ** *F OPEN_READ_ARCH() . . . . . . . . . . . open an archive for reading *F CLOS_READ_ARCH() . . . . . . . . . . . . . . . . . . . close an archive *F BLCK_READ_ARCH(,) . . . . . . . . read a block from an archive ** ** 'OPEN_READ_ARCH' returns 1 if the archive file with the path name ** (as specified by the user on the command line) could be opened for ** reading and 0 otherwise. Because archive files are binary files, ** 'OPEN_READ_ARCH' must open the file in binary mode. ** ** 'CLOS_READ_ARCH' closes the archive file opened by 'OPEN_READ_ARCH' ** again. ** ** 'BLCK_READ_ARCH' reads up to characters from the archive file ** opened with 'OPEN_READ_ARCH' into the blkfer , and returns the ** actual number of characters read. ** ** This operation is operating system dependent because the archive file ** must be opened in binary mode, so that for example no / <-> ** translation happens. You must supply a definition for each new port. */ #ifdef SYS_IS_UNIX FILE * ReadArch; #define OPEN_READ_ARCH(patl) ((ReadArch = fopen( (patl), "r" )) != 0) #define CLOS_READ_ARCH() (fclose( ReadArch ) == 0) #define BLCK_READ_ARCH(blk,len) fread( (blk), 1L, (len), ReadArch ) #define RWND_READ_ARCH() (fseek( ReadArch, 0, 0 ) == 0) #endif #ifdef SYS_IS_DOS_DJGPP FILE * ReadArch; #define OPEN_READ_ARCH(patl) ((ReadArch = fopen( (patl), "rb" )) != 0) #define CLOS_READ_ARCH() (fclose( ReadArch ) == 0) #define BLCK_READ_ARCH(blk,len) fread( (blk), 1L, (len), ReadArch ) #define RWND_READ_ARCH() (fseek( ReadArch, 0, 0 ) == 0) #endif #ifdef SYS_IS_OS2_EMX FILE * ReadArch; #define OPEN_READ_ARCH(patl) ((ReadArch = fopen( (patl), "rb" )) != 0) #define CLOS_READ_ARCH() (fclose( ReadArch ) == 0) #define BLCK_READ_ARCH(blk,len) fread( (blk), 1L, (len), ReadArch ) #define RWND_READ_ARCH() (fseek( ReadArch, 0, 0 ) == 0) #endif #ifdef SYS_IS_TOS_GCC FILE * ReadArch; #define OPEN_READ_ARCH(patl) ((ReadArch = fopen( (patl), "rb" )) != 0) #define CLOS_READ_ARCH() (fclose( ReadArch ) == 0) #define BLCK_READ_ARCH(blk,len) fread( (blk), 1L, (len), ReadArch ) #define RWND_READ_ARCH() (fseek( ReadArch, 0, 0 ) == 0) #endif #ifdef SYS_IS_VMS FILE * ReadArch; #define OPEN_READ_ARCH(patl) ((ReadArch = fopen( (patl), "r" )) != 0) #define CLOS_READ_ARCH() (fclose( ReadArch ) == 0) #define BLCK_READ_ARCH(blk,len) fread( (blk), 1L, (len), ReadArch ) #define RWND_READ_ARCH() (fseek( ReadArch, 0, 0 ) == 0) #endif #ifdef SYS_IS_MAC_MPW FILE * ReadArch; #define OPEN_READ_ARCH(patl) ((ReadArch = fopen( (patl), "r") ) != 0) #define BLCK_READ_ARCH(blk,len) fread( (blk), 1L, (len), ReadArch ) #define CLOS_READ_ARCH() (fclose( ReadArch ) == 0) #define RWND_READ_ARCH() (fseek( ReadArch, 0, 0 ) == 0) #endif #ifdef SYS_IS_GENERIC FILE * ReadArch; #define OPEN_READ_ARCH(patl) ((ReadArch = fopen( (patl), "r" )) != 0) #define CLOS_READ_ARCH() (fclose( ReadArch ) == 0) #define BLCK_READ_ARCH(blk,len) fread( (blk), 1L, (len), ReadArch ) #define RWND_READ_ARCH() (fseek( ReadArch, 0, 0 ) == 0) #endif #ifndef OPEN_READ_ARCH #include "You_must_specify_the_system.h" #endif /**************************************************************************** ** *F OPEN_READ_TEXT() . . . . . . . . . . . . . open a file for reading *F CLOS_READ_TEXT() . . . . . . . . . . . . . . . . . . . . . close a file *F BLCK_READ_TEXT(,) . . . . . . . . . . read a block from a file ** ** 'OPEN_READ_TEXT' returns 1 if the file with the path name (as ** specified by the user on the command line) could be opened for reading ** and 0 otherwise. 'OPEN_READ_TEXT' is used only for text files, so it ** should open the file in text mode. ** ** 'CLOS_READ_TEXT' closes the file opened by 'OPEN_READ_TEXT' again. ** ** 'BLCK_READ_TEXT' reads up to characters from the file opened with ** 'OPEN_READ_TEXT' into the blkfer , and returns the actual number of ** characters read. ** ** In 'unzoo' these functions are only used to test if a file exists. ** ** This operation is operating system dependent because it may be neccessary ** to translate between the local text format and the UNIX style text format ** usually used in archives. The default is to use 'fopen', 'fread', and ** 'fclose', which should work everywhere according to the ANSI standard. ** You may want to use 'open', 'read', and 'close' for better performance. */ #ifndef OPEN_READ_TEXT FILE * ReadText; #define OPEN_READ_TEXT(patl) ((ReadText = fopen( (patl), "r" )) != 0) #define CLOS_READ_TEXT() (fclose( ReadText ) == 0) #define BLCK_READ_TEXT(blk,len) fread( (blk), 1L, (len), ReadText ) #endif /**************************************************************************** ** *F OPEN_WRIT_TEXT() . . . . . . . . . . . . . open a file for writing *F CLOS_WRIT_TEXT() . . . . . . . . . . . . . . . . . . . . . close a file *F BLCK_WRIT_TEXT(,) . . . . . . . . . . . write a block to a file ** ** 'OPEN_WRIT_TEXT' returns 1 if the file with the path name (as ** specified by the user on the command line) could be opened for writing ** and 0 otherwise. 'OPEN_WRIT_TEXT' is used only for text files, so it ** must open the file in text mode. ** ** 'CLOS_WRIT_TEXT' closes the file opened by 'OPEN_WRIT_TEXT' again. ** ** 'BLCK_WRIT_TEXT' writes up to characters from into the file ** opened with 'OPEN_WRIT_TEXT', and returns the actual number of characters ** written. ** ** This operation is operating system dependent because it may be neccessary ** to translate between the UNIX style text format usually used in archives ** and the local text format. The default is to use 'fopen', 'fwrite', and ** 'fclose', which should work everywhere according to the ANSI standard. ** You may want to use 'open', 'write', and 'close' for better performance. */ #ifdef SYS_IS_MAC_MPW FILE * WritText; #define OPEN_WRIT_TEXT(patl) MacOpenWritText( (patl) ) #define CLOS_WRIT_TEXT() MacClosWritText() #define BLCK_WRIT_TEXT(blk,len) MacBlckWritText( (blk), (len) ) #endif #ifndef OPEN_WRIT_TEXT FILE * WritText; #define OPEN_WRIT_TEXT(patl) ((WritText = fopen( (patl), "w" )) != 0) #define CLOS_WRIT_TEXT() (fclose( WritText ) == 0) #define BLCK_WRIT_TEXT(blk,len) fwrite( (blk), 1L, (len), WritText ) #endif /**************************************************************************** ** *F OPEN_READ_BINR() . . . . . . . . . . . . . open a file for reading *F CLOS_READ_BINR() . . . . . . . . . . . . . . . . . . . . . close a file *F BLCK_READ_BINR(,) . . . . . . . . . . read a block from a file ** ** 'OPEN_READ_BINR' returns 1 if the file with the path name (as ** specified by the user on the command line) could be opened for reading ** and 0 otherwise. 'OPEN_READ_BINR' is used only for binary files, so it ** should open the file in binary mode. ** ** 'CLOS_READ_BINR' closes the file opened by 'OPEN_READ_BINR' again. ** ** 'BLCK_READ_BINR' reads up to characters from the file opened with ** 'OPEN_READ_BINR' into the blkfer , and returns the actual number of ** characters read. ** ** In 'unzoo' these functions are currently not used at all. ** ** This operation is operating system dependent because the file must be ** opened in binary mode, so that for example no / <-> ** translation happens. The default is to use 'fopen' with mode 'rb', ** 'fwrite', and 'fclose', with should work on most systems. */ #ifndef OPEN_READ_BINR FILE * ReadBinr; #define OPEN_READ_BINR(patl) ((ReadBinr = fopen( (patl), "rb" )) != 0) #define CLOS_READ_BINR() (fclose( ReadBinr ) == 0) #define BLCK_READ_BINR(blk,len) fread( (blk), 1L, (len), ReadBinr ) #endif /**************************************************************************** ** *F OPEN_WRIT_BINR() . . . . . . . . . . . . . open a file for writing *F CLOS_WRIT_BINR() . . . . . . . . . . . . . . . . . . . . . close a file *F BLCK_WRIT_BINR(,) . . . . . . . . . . . write a block to a file ** ** 'OPEN_WRIT_BINR' returns 1 if the file with the path name (as ** specified by the user on the command line) could be opened for writing ** and 0 otherwise. 'OPEN_WRIT_BINR' is used only for binary files, so it ** must open the file in binary mode. ** ** 'CLOS_WRIT_BINR' closes the file opened by 'OPEN_WRIT_BINR' again. ** ** 'BLCK_WRIT_BINR' writes up to characters from into the file ** opened with 'OPEN_WRIT_BINR', and returns the actual number of characters ** written. ** ** This operation is operating system dependent because the file must be ** opened in binary mode, so that for example no / <-> ** translation happens. The default is to use 'fopen' with mode 'wb', ** 'fwrite', and 'fclose', with should work on most systems. You must ** supply a definition is this does not work and you want 'unzoo' to extract ** binary files. */ #ifdef SYS_IS_VMS #include long WritBinr; #define OPEN_WRIT_BINR(patl) ((WritBinr = creat( (patl), 0, \ "rfm=fix", "mrs=512" )) != -1) #define BLCK_WRIT_BINR(blk,len) VmsBlckWritBinr( WritBinr, (blk), (len) ) #define CLOS_WRIT_BINR() (close( WritBinr ) == 0) #endif #ifndef OPEN_WRIT_BINR FILE * WritBinr; #define OPEN_WRIT_BINR(patl) ((WritBinr = fopen( (patl), "wb" )) != 0) #define BLCK_WRIT_BINR(blk,len) fwrite( (blk), 1L, (len), WritBinr ) #define CLOS_WRIT_BINR() (fclose( WritBinr ) == 0) #endif /**************************************************************************** ** *F CONV_NAME(,) . . . . . . . . . . . . . . convert a file name ** ** 'CONV_NAME' returns in the universal file name converted ** to the local format. may contain uppercase, lowercase, and all ** special characters, and may be up to 255 characters long. ** ** You must define this for a new port if you want 'unzoo' to keep the long ** names instead of using the default local format for the file names, which ** contains up to eight lowercase characters before an optional dot ('.'), ** up to three characters after the dot, and no special characters. You may ** want to use the universal conversion function 'ConvName'. */ #ifdef SYS_IS_UNIX #define CONV_NAME(naml,namu) strcpy( (naml), (namu) ) #endif #ifdef SYS_IS_DOS_DJGPP #define CONV_NAME(naml,namu) ConvName( (naml), (namu), 8L, 3L, '_' ) #endif #ifdef SYS_IS_OS2_EMX #define CONV_NAME(naml,namu) strcpy( (naml), (namu) ) #endif #ifdef SYS_IS_TOS_GCC #define CONV_NAME(naml,namu) strcpy( (naml), (namu) ) #endif #ifdef SYS_IS_VMS #define CONV_NAME(naml,namu) ConvName( (naml), (namu), 39L, 39L, '_' ) #endif #ifdef SYS_IS_MAC_MPW #define CONV_NAME(naml,namu) ConvName( (naml), (namu), 27L, 3L, '_' ) #endif #ifndef CONV_NAME #define CONV_NAME(naml,namu) ConvName( (naml), (namu), 8L, 3L, 'x' ) #endif /**************************************************************************** ** *F CONV_DIRE(,) . . . . . . . . . . . convert a directory name ** ** 'CONV_DIRE' returns in the universal directory name ** converted to the local format. contains an arbitrary number of ** components separated by slashes ('/'), where each component may contain ** uppercase, lowercase, and all special characters, and may be up to 255 ** characters long. ** ** You must define this for a new port if you want 'unzoo' to extract ** members into subdirectories, instead of extracting them to the current ** directory. You may want to use the universal conversion function ** 'ConvDire'. */ #ifdef SYS_IS_UNIX #define CONV_DIRE(dirl,diru) ConvDire((dirl),(diru),"/","/","","/","/") #endif #ifdef SYS_IS_DOS_DJGPP #define CONV_DIRE(dirl,diru) ConvDire((dirl),(diru),"\\","\\","","\\","\\") #endif #ifdef SYS_IS_OS2_EMX #define CONV_DIRE(dirl,diru) ConvDire((dirl),(diru),"/","/","","/","/") #endif #ifdef SYS_IS_TOS_GCC #define CONV_DIRE(dirl,diru) ConvDire((dirl),(diru),"\\","\\","","\\","\\") #endif #ifdef SYS_IS_VMS #define CONV_DIRE(dirl,diru) ConvDire((dirl),(diru),"[]","[","[.",".","]") #endif #ifdef SYS_IS_MAC_MPW #define CONV_DIRE(dirl,diru) ConvDire((dirl),(diru),"","",":",":",":") #endif #ifndef CONV_DIRE #define CONV_DIRE(dirl,diru) ((dirl)[0]='\0',1) #endif /**************************************************************************** ** *F MAKE_DIRE() . . . . . . . . . . . . . . . . . . . make a directory ** ** 'MAKE_DIRE' makes the directory with the local path name (as ** converted by 'CONV_NAME' and 'CONV_DIRE' with the prefix of 'MakeDirs'). ** ** You must define this for a new port if you want 'unzoo' to automatically ** create directories instead of requiring the user to create them. */ #ifdef SYS_IS_UNIX #ifdef SYS_HAS_MKDIR #define MAKE_DIRE(patl) mkdir( (patl), 0777L ) #else char Cmd [256]; #define MAKE_DIRE(patl) (sprintf(Cmd,"/bin/mkdir %s",(patl)),!system(Cmd)) #endif #endif #ifdef SYS_IS_DOS_DJGPP #define MAKE_DIRE(patl) mkdir( (patl), 0777L ) #endif #ifdef SYS_IS_OS2_EMX #include #define MAKE_DIRE(patl) mkdir( (patl), 0777L ) #endif #ifdef SYS_IS_TOS_GCC #define MAKE_DIRE(patl) mkdir( (patl), 0777L ) #endif #ifdef SYS_IS_VMS #define MAKE_DIRE(patl) VmsMakeDire( (patl) ) #endif #ifdef SYS_IS_MAC_MPW #define MAKE_DIRE(patl) MacMakeDire( (patl) ) #endif /**************************************************************************** ** *F SETF_TIME(,) . . . . . . . . . . . change the time of a file ** ** 'SETF_TIME' changes the time of the file with the local path name ** (as converted by 'CONV_NAME' and 'CONV_DIRE') to , which is the ** number of seconds since 1970/01/01 00:00:00. ** ** You must define this for a new port if you want 'unzoo' to extract ** members with the correct time as stored in the archive. */ #ifdef SYS_IS_UNIX unsigned long Secs [2]; #define SETF_TIME(patl,secs) (Secs[0]=Secs[1]=(secs),!utime((patl),Secs)) #endif #ifdef SYS_IS_DOS_DJGPP unsigned long Secs [2]; #define SETF_TIME(patl,secs) (Secs[0]=Secs[1]=(secs),!utime((patl),Secs)) #endif #ifdef SYS_IS_OS2_EMX #include struct utimbuf Secs; #define SETF_TIME(patl,secs) (Secs.actime=Secs.modtime=(secs),!utime((patl),&Secs)) #endif #ifdef SYS_IS_TOS_GCC unsigned long Secs [2]; #define SETF_TIME(patl,secs) (Secs[0]=Secs[1]=(secs),!utime((patl),Secs)) #endif #ifndef SETF_TIME #define SETF_TIME(patl,secs) (1) #endif /**************************************************************************** ** *F SETF_PERM(,) . . . . . . . change the permissions of a file ** ** 'SETF_PERM' changes the permissions of the file with the local path name ** (as converted by 'CONV_NAME' and 'CONV_DIRE') to , which is ** a UNIX style mode word. ** ** You must define this for a new port if you want 'unzoo' to extract ** members with the permissions stored in the archive. */ #ifdef SYS_IS_UNIX #define SETF_PERM(patl,mode) (!chmod((patl),(int)(mode))) #endif #ifdef SYS_IS_DOS_DJGPP #define SETF_PERM(patl,mode) (!chmod((patl),(int)(mode))) #endif #ifdef SYS_IS_OS2_EMX #include #define SETF_PERM(patl,mode) (!chmod((patl),(int)(mode))) #endif #ifdef SYS_IS_TOS_GCC #define SETF_PERM(patl,mode) (!chmod((patl),(int)(mode))) #endif #ifndef SETF_PERM #define SETF_PERM(patl,mode) (1) #endif /**************************************************************************** ** *F ConvName(...) . . . . . . . . . . . . convert a file name to local format ** ** 'ConvName( , ,
,, )'
**
**  'ConvName' returns in  the  universal file name  converted to
**  the local format described by 
, , and .
**
**  
 is the maximum number of characters  before the optional dot, 
**  is the maximum number of characters after the optional  dot, and  is
**  the character that replaces special characters.
*/
int             ConvName ( naml, namu, pre, pst, rpl )
    char *              naml;
    char *              namu;
    unsigned long       pre;
    unsigned long       pst;
    char                rpl;
{
    char *              dotu;           /* position of last dot in   */
    char *              l;              /* loop variable                   */
    char *              u;              /* loop variable                   */

    /* find the final dot                                                  */
    dotu = 0;
    for ( u = namu; *u != '\0'; u++ )
        if ( *u == '.' )
            dotu = u;
    if ( dotu == 0 )  dotu = u;

    /* copy the first part                                                 */
    l = naml;
    for ( u = namu; u < dotu && u < namu+pre; u++ ) {
        if      ( 'a' <= *u && *u <= 'z' )  *l++ = *u;
        else if ( 'A' <= *u && *u <= 'Z' )  *l++ = *u - 'A' + 'a';
        else if ( '0' <= *u && *u <= '9' )  *l++ = *u;
        else                                *l++ = rpl;
    }

    /* the part before the dot may not be empty                            */
    if ( l == naml )
        *l++ = rpl;

    /* if the universal file name had no dot, thats it                     */
    if ( *dotu == '\0' || pst == 0 ) {
        *l = '\0';
        return 1;
    }

    /* copy the dot                                                        */
    *l++ = '.';

    /* copy the remaining part                                             */
    for ( u = dotu+1; *u && u < dotu+1+pst; u++ ) {
        if      ( 'a' <= *u && *u <= 'z' )  *l++ = *u;
        else if ( 'A' <= *u && *u <= 'Z' )  *l++ = *u - 'A' + 'a';
        else if ( '0' <= *u && *u <= '9' )  *l++ = *u;
        else                                *l++ = rpl;
    }

    /* terminate the local name and indicate success                       */
    *l = '\0';
    return 1;
}


/****************************************************************************
**
*F  ConvDire(...) . . . . . . . . .  convert a directory name to local format
**
**  'ConvDire( , , ,,,, )'
**
**  'ConvDire'  returns  in    the  universal  directory  name   
**  converted to the  local format.   contains an  arbitrary number  of
**  components separated by  slashes ('/'),  where each component may contain
**  uppercase,  lowercase,  and all special characters,  and may be up to 255
**  characters long.
**
**   is the string that is used for the root directory in local format.
**   is the string that starts absolute directory names in local format,
**   starts relative names, directory components are separated by ,
**  and  separates the directory part and a proper file name.
**
**  If  is the empty string, then 'ConvDire' returns in  also the
**  empty string, instead of ''.
*/
int             ConvDire ( dirl, diru, root, abs, rel, sep, end )
    char *              dirl;
    char *              diru;
    char *              root;
    char *              abs;
    char *              rel;
    char *              sep;
    char *              end;
{
    char                namu [256];     /* file name part, univ.           */
    char                naml [256];     /* file name part, local           */
    char *              d;              /* loop variable                   */
    char *              s;              /* loop variable                   */

    /* special case for the root directory                                 */
    if ( *diru == '/' && diru[1] == '\0' ) {
        for ( s = root; *s != '\0'; s++ )  *dirl++ = *s;
        *dirl = '\0';
        return 1;
    }

    /* start the file name with  or                              */
    d = diru;
    if ( *diru == '/' )
        for ( d++, s = abs; *s != '\0'; s++ )  *dirl++ = *s;
    else if ( *diru != '\0' )
        for (      s = rel; *s != '\0'; s++ )  *dirl++ = *s;

    /* add the components of the directory part separated by          */
    while ( *d != '\0' ) {
        s = namu;
        while ( *d != '\0' && *d != '/' )  *s++ = *d++;
        *s = '\0';
        CONV_NAME( naml, namu );
        for ( s = naml; *s != '\0'; s++ )  *dirl++ = *s;
        if ( *d == '/' )
            for ( d++, s = sep; *s != '\0'; s++ )  *dirl++ = *s;
    }

    /* add the divisor                                                */
    if ( *diru != '\0' )
        for ( s = end; *s != '\0'; s++ )  *dirl++ = *s;

    /* terminate the file name and indicate success                        */
    *dirl = '\0';
    return 1;
}


/****************************************************************************
**
*F  VmsBlckWritBinr(,)  .  write a block to a binary file under VMS
*F  VmsMakeDire() . . . . . . . . . . . .  create a directory under VMS
**
**  'VmsBlckWritBinr' writes  the block   of  length   to  the file
**  opened with 'OPEN_WRIT_BINR'.
**
**  'VmsMakeDire' creates a directory  under VMS.  It has  to change the path
**  name from '[]' to '[.]'.
*/
#ifdef  SYS_IS_VMS

unsigned long   VmsBlckWritBinr ( blk, len )
    unsigned char *     blk;
    unsigned long       len;
{
    unsigned char       buf [512];      /* local buffer (padded with 0)    */
    long                i,  k,  l;      /* loop variables                  */

    /* write the full 512 byte blocks                                      */
    for ( i = 0; i+512 < len; i += 512 ) {
        if ( (l = write( WritBinr, blk+i, 512 )) != 512 )
            return i + l;
    }

    /* write an incomplete last block padded with 0                        */
    for ( k = 0; k < 512; k++ )
        buf[k] = (i+k < len ? blk[i+k] : 0);
    if ( (l = write( WritBinr, buf, 512 )) != 512 )
        return i + l;

    /* indicate success                                                    */
    return len;
}

int             VmsMakeDire ( patl )
    char *              patl;
{
    char *              p;

    /* replace the separator with a dot                                    */
    for ( p = patl; *p != '\0' && *p != ']'; p++ )  ;
    if ( *p == ']' )  *p = '.';

    /* append another separator                                            */
    for ( ; *p != '\0'; p++ ) ;
    *p++ = ']';
    *p = '\0';

    /* make the directory and indicate success                             */
    return mkdir( patl, 0 );
}

#endif


/****************************************************************************
**
*F  MacOpenWritText() . . . . .  open a text file for writing under MPW
*F  MacClosWritText() . . . . . . . . . . . . . . close a text file under MPW
*F  MacBlckWritText(,)  . .  write a block to a text file under MPW
*F  OPEN_WRIT_MACB()  . . . open a MacBinary file for writing under MPW
*F  CLOS_WRIT_MACB()  . . . . . . . . . . .  close a MacBinary file under MPW
*F  BLCK_WRIT_MACB(,) . write a block to a MacBinary file under MPW
*F  MacMakeDire() . . . . . . . . . . . .  create a directory under MPW
**
**  'MacBlckWritText' writes the block  of length  to the text file
**  opened   with 'OPEN_WRIT_TEXT'.  It    converts  ('\012') characters,
**  which represent   in universal text  format, to '\n' characters,
**  which represent  in the system defined text format.
**
**  'MacMakeDire' creates  the directory  with local  path  name .  The
**  code comes from the Macintosh 'tar' port by Gail Zacharias.
*/
#ifdef  SYS_IS_MAC_MPW

#include        

int             MacOpenWritText ( patl )
    char *              patl;
{
    FInfo               fndrInfo;

    /* open the file                                                       */
    if ( ! (WritText = fopen( (patl), "w" )) )
        return 0;

    /* set the file type to 'TEXT' and the creator to TeachText            */
    getfinfo( patl, 0, &fndrInfo );
    if ( fndrInfo.fdType == 0 )
        fndrInfo.fdType    = 'TEXT';
    if ( fndrInfo.fdCreator == 0 )
        fndrInfo.fdCreator = 'ttxt';
    setfinfo( patl, 0, &fndrInfo );

    /* indicate success                                                    */
    return 1;
}

int             MacClosWritText ()
{
    return (fclose( WritText ) == 0);
}

unsigned long   MacBlckWritText ( blk, len )
    unsigned char *     blk;
    unsigned long       len;
{
    unsigned long       i;              /* loop variable                   */

    for ( i = 0; i < len; i++ ) {
        if (fputc( (blk[i] != '\012' ? blk[i] : '\n'), WritText ) == EOF)
            return i;
    }
    return len;
}

char            WritName [256];         /* name of the file                */
IOParam         WritIOPB;               /* IO parameter block              */
FileParam       WritFIPB;               /* Finder Info parameter block     */
unsigned long   WritPart;               /* current part of MacBinary file  */
unsigned long   WritType;               /* type of file, e.g. 'TEXT'       */
unsigned long   WritCrtr;               /* creator of file, e.g. 'ttxt'    */
unsigned long   WritFlgs;               /* finder flags                    */
unsigned long   WritCDat;               /* creation date of file           */
unsigned long   WritMDat;               /* last modification date of file  */
unsigned long   WritLDat;               /* nr. of bytes left in data fork  */
unsigned long   WritLRsc;               /* nr. of bytes left in resource   */

int             OPEN_WRIT_MACB ( patl )
    char *              patl;
{
    unsigned long       i;              /* loop variable                   */

    /* find the last semicolon                                             */
    for ( i = strlen(patl); 0 < i && patl[i] != ':'; i-- )
        ;

    /* copy the directory part to 'WritName'                               */
    WritName[0] = (0 < i ? i+1 : 0);
    for ( i = 1; i <= WritName[0]; i++ )
        WritName[i] = patl[i-1];

    /* indicate success                                                    */
    WritPart = 0;
    return 1;
}

int             CLOS_WRIT_MACB ()
{

    /* first get the current settings                                      */
    WritFIPB.ioNamePtr   = WritName;
    WritFIPB.ioVRefNum   = 0;
    WritFIPB.ioFVersNum  = 0;
    WritFIPB.ioFDirIndex = 0;
    if ( PBGetFInfo( (ParmBlkPtr)&WritFIPB, 0 ) ) {
        return 0;
    }

    /* now set some fields to the values found in the MacBinary header     */
    WritFIPB.ioFlFndrInfo.fdType    = WritType;
    WritFIPB.ioFlFndrInfo.fdCreator = WritCrtr;
    WritFIPB.ioFlFndrInfo.fdFlags   = WritFlgs;
    WritFIPB.ioFlCrDat              = WritCDat;
    WritFIPB.ioFlMdDat              = WritMDat;
    if ( PBSetFInfo( (ParmBlkPtr)&WritFIPB, 0 ) ) {
        return 0;
    }

    /* indicate success                                                    */
    return 1;
}

unsigned long   BLCK_WRIT_MACB ( blk, len )
    unsigned char *     blk;
    unsigned long       len;
{
    unsigned long       cnt;            /* number of bytes written         */
    unsigned long       i;              /* loop variable                   */

    /* first comes the header (128 bytes long)                             */
    cnt = 0;
    if ( WritPart == 0 ) {
        for ( i = 1; i <= blk[1]; i++ )
            WritName[WritName[0]+i] = blk[i+1];
        WritName[0] += blk[1];
        WritType = (blk[65]<<24) + (blk[66]<<16) + (blk[67]<< 8) + (blk[68]);
        WritCrtr = (blk[69]<<24) + (blk[70]<<16) + (blk[71]<< 8) + (blk[72]);
        WritFlgs = (blk[73]<< 8) + 0;
        WritLDat = (blk[83]<<24) + (blk[84]<<16) + (blk[85]<< 8) + (blk[86]);
        WritLRsc = (blk[87]<<24) + (blk[88]<<16) + (blk[89]<< 8) + (blk[90]);
        WritCDat = (blk[91]<<24) + (blk[92]<<16) + (blk[93]<< 8) + (blk[94]);
        WritMDat = (blk[95]<<24) + (blk[96]<<16) + (blk[97]<< 8) + (blk[98]);
        cnt += 128;
        WritPart = 1;
    }

    /* open the data fork                                                  */
    if ( WritPart == 1 && cnt < len ) {
        WritIOPB.ioNamePtr = WritName;
        WritIOPB.ioVRefNum = 0;
        WritIOPB.ioVersNum = 0;
        WritIOPB.ioPermssn = fsWrPerm;
        WritIOPB.ioMisc    = 0;
        WritIOPB.ioRefNum  = 0;
        if ( PBCreate( (ParmBlkPtr)&WritIOPB, 0 ) ) {
            return cnt;
        }
        if ( PBOpen(   (ParmBlkPtr)&WritIOPB, 0 ) ) {
            return cnt;
        }
        WritPart = 2;
    }

    /* next comes the data fork (padded to a multiple of 128 bytes)        */
    if ( WritPart == 2 ) {
        while ( WritLDat != 0 && cnt < len ) {
            WritIOPB.ioReqCount  = (128 <= WritLDat ? 128 : WritLDat);
            WritIOPB.ioPosMode   = fsAtMark;
            WritIOPB.ioPosOffset = 0;
            WritIOPB.ioBuffer    = (Ptr) (blk + cnt);
            if ( PBWrite( (ParmBlkPtr)&WritIOPB, 0 )
              || WritIOPB.ioActCount != WritIOPB.ioReqCount ) {
                PBClose( (ParmBlkPtr)&WritIOPB, 0 );
                return cnt;
            }
            cnt += 128;
            WritLDat -= WritIOPB.ioReqCount;
        }
        if ( WritLDat == 0 )  WritPart = 3;
    }

    /* close the data fork                                                 */
    if ( WritPart == 3 ) {
        PBClose( (ParmBlkPtr)&WritIOPB, 0 );
        WritPart = 4;
    }

    /* open the resource fork                                              */
    if ( WritPart == 4 && cnt < len ) {
        if ( PBOpenRF( (ParmBlkPtr)&WritIOPB, 0 ) ) {
            return cnt;
        }
        WritPart = 5;
    }
        
    /* and finally comes the resource fork                                 */
    if ( WritPart == 5 ) {
        while ( WritLRsc != 0 && cnt < len ) {
            WritIOPB.ioReqCount  = (128 <= WritLRsc ? 128 : WritLRsc);
            WritIOPB.ioPosMode   = fsAtMark;
            WritIOPB.ioPosOffset = 0;
            WritIOPB.ioBuffer    = (Ptr) (blk + cnt);
            if ( PBWrite( (ParmBlkPtr)&WritIOPB, 0 )
              || WritIOPB.ioActCount != WritIOPB.ioReqCount ) {
                PBClose( (ParmBlkPtr)&WritIOPB, 0 );
                return cnt;
            }
            cnt += 128;
            WritLRsc -= WritIOPB.ioReqCount;
        }
        if ( WritLRsc == 0 )  WritPart = 6;
    }

    /* close the resource fork                                             */
    if ( WritPart == 6 ) {
        PBClose( (ParmBlkPtr)&WritIOPB, 0 );
        WritPart = 7;
    }

    /* indicate success                                                    */
    return cnt;
}

int             MacMakeDire ( patl )
    char *              patl;
{
    HFileParam          request;        /* structure describing request    */
    char                patp [256];     /*  as a Pascal string       */
    int                 len;            /* length of                 */

    /* convert  from a C string to a Pascal string                   */
    len = strlen( patl );
    len = len < 256 ? len : 255;
    patp[0] = len;
    strncpy( patp+1, patl, len );

    /* set up the request                                                  */
    request.ioNamePtr = (unsigned char*)patp;
    request.ioVRefNum = 0;
    request.ioDirID   = 0;
    PBDirCreate( (HParmBlkPtr)&request, 0 );

    /* return result                                                       */
    return (request.ioResult == 0);
}

#endif


/****************************************************************************
**
*F  MakeDirs(
,)  . . . . . . . . . . . . . .  make all directories
**
**  'MakeDirs' tries  to  make all the directories   along the universal path
**  name  (i.e., with components separated by '/').   
 is a prefix
**  that is prepended to all path names.
*/
#ifdef  MAKE_DIRE

int             MakeDirs ( pre, patu )
    char *              pre;
    char *              patu;
{
    char                patl [1024];    /* path name, local                */
    char                diru [256];     /* directory part of , univ. */
    char                dirl [256];     /* directory part of , local */
    char                namu [256];     /* file name part of , univ. */
    char                naml [256];     /* file name part of , local */
    char                * d,  * n;      /* loop variables                  */

    /* if  is an absolute path, copy the slash '/'                   */
    d = diru;
    if ( *patu == '/' )  *d++ = *patu++;

    while ( *patu != '\0' ) {

        /* copy the file name part of  into                    */
        for ( n = namu; *patu != '\0' && *patu != '/'; ) *n++ = *patu++;
        if ( *patu != '\0' )  patu++;

        /* convert the name into local format and make the directory       */
        *d = '\0';  *n = '\0';
        CONV_DIRE( dirl, diru );
        CONV_NAME( naml, namu );
        strcpy( patl, pre  );
        strcat( patl, dirl );
        strcat( patl, naml );
        /*N 1993/11/03 martin what should I do with the return code?       */
        /*N 1993/11/03 martin it could be 0 if the directory exists!       */
        MAKE_DIRE( patl );

        /* append the file name part to the directory part                 */
        if ( d != diru && d[-1] != '/' )  *d++ = '/';
        for ( n = namu; *n != '\0'; ) *d++ = *n++;

    }

    /* indicate success                                                    */
    return 1;
}

#endif


/****************************************************************************
**
*F  IsMatchName(,)  . . test if a string matches a wildcard pattern
**
**  'IsMatchName' return 1 if the pattern   matches the string  and
**  0 otherwise.  A   '?' in   matches any  character  in ,  a  '*'
**  matches any string  in , other characters in    match the  same
**  character in .  Characters for which 'IsSpec[]' is true will not
**  be matched by '?' and '*'.
**
**  Jeff Damens  wrote the name match code in 'booz' (originally for Kermit).
*/
int             IsSpec [256];           /* nonzero for special characters  */

int             IsMatchName ( pat, str )
    char *              pat;            /* pattern to match against        */
    char *              str;            /* string  to match                */
{
    char *              pos = 0;        /* pos. after last '*' in pattern  */
    char *              tmp = 0;        /* corresponding match in string   */

    /* try to match the name part                                          */
    while ( *pat != '\0' || *str != '\0' ) {
        if      ( *pat==*str                  ) { pat++;       str++;       }
        else if ( *pat=='?' && ! IsSpec[*str] ) { pat++;       str++;       }
        else if ( *pat=='?' && *str != '\0'   ) { pat++;       str++;       }
        else if ( *pat=='*'                   ) { pos = ++pat; tmp =   str; }
        else if ( tmp != 0  && ! IsSpec[*tmp] ) { pat =   pos; str = ++tmp; }
        else                                    break;
    }
    return *pat == '\0' && *str == '\0';
}


/****************************************************************************
**
*F  OpenReadArch()  . . . . . . . . . . . . . . try to open the archive
*F  ClosReadArch()  . . . . . . . . . . . . . . . . . . . . close the archive
*F  GotoReadArch() . . . . . .  goto an absolute position in the archive
*F  ByteReadArch()  . . . . . . . . . read a  8 bit unsigned from the archive
*F  HalfReadArch()  . . . . . . . . . read a 16 bit unsigned from the archive
*F  TripReadArch()  . . . . . . . . . read a 24 bit unsigned from the archive
*F  WordReadArch()  . . . . . . . . . read a 32 bit unsigned from the archive
*F  BlckReadArch(,) . . . .  read a block of bytes from the archive
*V  Descript  . . . . . . . . . . . . . . . . . . . . header from the archive
*F  DescReadArch()  . . . . . . . . . . . .  read the header from the archive
*V  Entry . . . . . . . . . . . . . . . . header of a member from the archive
*F  EntrReadArch()  . . . . . .  read the header of a member from the archive
**
**  'OpenReadArch' tries to open the archive with  local path name  (as
**  specified by the user on the command  line) for reading  and returns 1 to
**  indicate success or 0 to indicate that the file cannot be opened.
**
**  'ClosReadArch' closes the archive again.
**
**  'GotoReadArch'  positions the  archive  at the  position , i.e., the
**  next call to 'ByteReadArch' will return the byte at position .  Note
**  that 'GotoReadArch' does not use 'fseek', because 'fseek' is unreliable.
**
**  'ByteReadArch' returns the next   byte  unsigned  8 bit from the archive.
**  'HalfReadArch' returns the next 2 bytes unsigned 16 bit from the archive.
**  'TripReadArch' returns the next 3 bytes unsigned 24 bit from the archive.
**  'WordReadArch' returns the next 4 bytes unsigned 32 bit from the archive.
**  'BlckReadArch' reads  bytes into the buffer .
**
**  'Descript' is the description of the archive.
**
**  'DescReadArch' reads the description  of the archive  that starts at  the
**  current position into the structure 'Descript'.  It should of course only
**  be called at the start of the archive file.
**
**  'Entry' is the directory entry of the current member from the archive.
**
**  'EntrReadArch'  reads the directory entry of  a member that starts at the
**  current position into the structure 'Entry'.
*/
unsigned char   BufArch [64+4096];      /* buffer for the archive          */

unsigned char * PtrArch;                /* pointer to the next byte        */

unsigned char * EndArch;                /* pointer to the last byte        */

unsigned long   PosArch;                /* position of 'BufArch[0]'        */

int             OpenReadArch ( patl )
    char *              patl;
{
    PtrArch = EndArch = (BufArch+64);
    PosArch = 0;
    return OPEN_READ_ARCH( patl );
}

int     ClosReadArch ()
{
    return CLOS_READ_ARCH();
}

int             FillReadArch ()
{
    unsigned char *     s;              /* loop variable                   */
    unsigned char *     d;              /* loop variable                   */

    /* copy the last characters to the beginning (for short backward seeks)*/
    d = BufArch;
    for ( s = EndArch-64; s < EndArch; s++ )
        *d++ = *s;
    PosArch += EndArch - (BufArch+64);

    /* read a block                                                        */
    PtrArch = BufArch+64;
    EndArch = PtrArch + BLCK_READ_ARCH( PtrArch, 4096 );

    /* return the first character                                          */
    return (PtrArch < EndArch ? *PtrArch++ : EOF);
}

int             GotoReadArch ( pos )
    unsigned long       pos;
{
    /* for long backward seeks goto the beginning of the file              */
    if ( pos+64 < PosArch ) {
        if ( ! RWND_READ_ARCH() )
            return 0;
        PtrArch = EndArch = BufArch+64;
        PosArch = 0;
    }

    /* jump forward bufferwise                                             */
    while ( PosArch + (EndArch - (BufArch+64)) <= pos ) {
        if ( FillReadArch() == EOF )
            return 0;
    }

    /* and goto the position (which is now in the buffer)                  */
    PtrArch = (BufArch+64) + (pos - PosArch);

    /* indicate success                                                    */
    return 1;
}

#define ByteReadArch()          (PtrArchZ"       */
    unsigned long       magic;          /* magic word 0xfdc4a7dc           */
    unsigned long       posent;         /* position of first directory ent.*/
    unsigned long       klhvmh;         /* two's complement of posent      */
    unsigned char       majver;         /* major version needed to extract */
    unsigned char       minver;         /* minor version needed to extract */
    unsigned char       type;           /* type of current member (0,1)    */
    unsigned long       poscmt;         /* position of comment, 0 if none  */
    unsigned short      sizcmt;         /* length   of comment, 0 if none  */
    unsigned char       modgen;         /* gens. on, gen. limit            */
    /* the following are not in the archive file and are computed          */
    unsigned long       sizorg;         /* uncompressed size of members    */
    unsigned long       siznow;         /*   compressed size of members    */
    unsigned long       number;         /* number of members               */

}               Descript;

int             DescReadArch ()
{
    /* read the text at the beginning                                      */
    BlckReadArch(Descript.text,20L);  Descript.text[20] = '\0';

    /* try to read the magic words                                         */
    if ( (Descript.magic = WordReadArch()) != (unsigned long)0xfdc4a7dcL )
        return 0;

    /* read the old part of the description                                */
    Descript.posent = WordReadArch();
    Descript.klhvmh = WordReadArch();
    Descript.majver = ByteReadArch();
    Descript.minver = ByteReadArch();

    /* read the new part of the description if present                     */
    Descript.type   = (34 < Descript.posent ? ByteReadArch() : 0);
    Descript.poscmt = (34 < Descript.posent ? WordReadArch() : 0);
    Descript.sizcmt = (34 < Descript.posent ? HalfReadArch() : 0);
    Descript.modgen = (34 < Descript.posent ? ByteReadArch() : 0);

    /* initialize the fake entries                                         */
    Descript.sizorg = 0;
    Descript.siznow = 0;
    Descript.number = 0;

    /* indicate success                                                    */
    return 1;
}

struct {
    unsigned long       magic;          /* magic word 0xfdc4a7dc           */
    unsigned char       type;           /* type of current member (1)      */
    unsigned char       method;         /* packing method of member (0..2) */
    unsigned long       posnxt;         /* position of next member         */
    unsigned long       posdat;         /* position of data                */
    unsigned short      datdos;         /* date (in DOS format)            */
    unsigned short      timdos;         /* time (in DOS format)            */
    unsigned short      crcdat;         /* crc value of member             */
    unsigned long       sizorg;         /* uncompressed size of member     */
    unsigned long       siznow;         /*   compressed size of member     */
    unsigned char       majver;         /* major version needed to extract */
    unsigned char       minver;         /* minor version needed to extract */
    unsigned char       delete;         /* 1 if member is deleted, 0 else  */
    unsigned char       spared;         /* spare entry to pad entry        */
    unsigned long       poscmt;         /* position of comment, 0 if none  */
    unsigned short      sizcmt;         /* length   of comment, 0 if none  */
    char                nams [14];      /* short name of member or archive */
    unsigned short      lvar;           /* length of variable part         */
    unsigned char       timzon;         /* time zone                       */
    unsigned short      crcent;         /* crc value of entry              */
    unsigned char       lnamu;          /* length of long name             */
    unsigned char       ldiru;          /* length of directory             */
    char                namu [256];     /* univ. name of member of archive */
    char                diru [256];     /* univ. name of directory         */
    unsigned short      system;         /* system identifier               */
    unsigned long       permis;         /* file permissions                */
    unsigned char       modgen;         /* gens. on, last gen., gen. limit */
    unsigned short      ver;            /* version number of member        */
    /* the following are not in the archive file and are computed          */
    char                naml [256];     /* local name of member of archive */
    char                dirl [256];     /* local name of directory         */
    char                patl [512];     /* local path name of member       */
    char                patv [512];     /* ditto but with version number   */
    char *              patw;           /* name used by '-l'               */
    unsigned long       year;           /* years since 1900                */
    unsigned long       month;          /* month since January             */
    unsigned long       day;            /* day of month                    */
    unsigned long       hour;           /* hours since midnight            */
    unsigned long       min;            /* minutes after the hour          */
    unsigned long       sec;            /* seconds after the minutes       */
}               Entry;

int             EntrReadArch ()
{
    unsigned long       l;              /* 'Entry.lnamu+Entry.ldiru'       */
    char *              p;              /* loop variable                   */

    /* try to read the magic words                                         */
    if ( (Entry.magic = WordReadArch()) != (unsigned long)0xfdc4a7dcL )
        return 0;

    /* read the fixed part of the directory entry                          */
    Entry.type   = ByteReadArch();
    Entry.method = ByteReadArch();
    Entry.posnxt = WordReadArch();
    Entry.posdat = WordReadArch();
    Entry.datdos = HalfReadArch();
    Entry.timdos = HalfReadArch();
    Entry.crcdat = HalfReadArch();
    Entry.sizorg = WordReadArch();
    Entry.siznow = WordReadArch();
    Entry.majver = ByteReadArch();
    Entry.minver = ByteReadArch();
    Entry.delete = ByteReadArch();
    Entry.spared = ByteReadArch();
    Entry.poscmt = WordReadArch();
    Entry.sizcmt = HalfReadArch();
    BlckReadArch(Entry.nams,13L);  Entry.nams[13] = '\0';

    /* handle the long name and the directory in the variable part         */
    Entry.lvar   = (Entry.type == 2  ? HalfReadArch() : 0);
    Entry.timzon = (Entry.type == 2  ? ByteReadArch() : 127);
    Entry.crcent = (Entry.type == 2  ? HalfReadArch() : 0);
    Entry.lnamu  = (0 < Entry.lvar   ? ByteReadArch() : 0);
    Entry.ldiru  = (1 < Entry.lvar   ? ByteReadArch() : 0);
    BlckReadArch(Entry.namu,(unsigned long)Entry.lnamu);
    Entry.namu[Entry.lnamu] = '\0';
    BlckReadArch(Entry.diru,(unsigned long)Entry.ldiru);
    Entry.diru[Entry.ldiru] = '\0';
    l = Entry.lnamu + Entry.ldiru;
    Entry.system = (l+2 < Entry.lvar ? HalfReadArch() : 0);
    Entry.permis = (l+4 < Entry.lvar ? TripReadArch() : 0);
    Entry.modgen = (l+7 < Entry.lvar ? ByteReadArch() : 0);
    Entry.ver    = (l+7 < Entry.lvar ? HalfReadArch() : 0);

    /* convert the names to local format                                   */
    if ( Entry.system == 0 || Entry.system == 2 ) {
        CONV_DIRE( Entry.dirl, Entry.diru );
        CONV_NAME( Entry.naml, (Entry.lnamu ? Entry.namu : Entry.nams) );
    }
    else {
        strcpy( Entry.dirl, Entry.diru );
        strcpy( Entry.naml, (Entry.lnamu ? Entry.namu : Entry.nams) );
    }
    strcpy( Entry.patl, Entry.dirl );
    strcat( Entry.patl, Entry.naml );

    /* create the name with the version appended                           */
    strcpy( Entry.patv, Entry.patl );
    p = Entry.patv;  while ( *p != '\0' )  p++;
    *p++ = ';';
    for ( l = 10000; 0 < l; l /= 10 )
        if ( l == 1 || l <= Entry.ver )
            *p++ = (Entry.ver / l) % 10 + '0';
    *p = '\0';
    Entry.patw = ((Entry.modgen&0xc0)!=0x80 ? Entry.patl : Entry.patv);

    /* convert the time                                                    */
    Entry.year  = ((Entry.datdos >>  9) & 0x7f) + 80;
    Entry.month = ((Entry.datdos >>  5) & 0x0f) - 1;
    Entry.day   = ((Entry.datdos      ) & 0x1f);
    Entry.hour  = ((Entry.timdos >> 11) & 0x1f);
    Entry.min   = ((Entry.timdos >>  5) & 0x3f);
    Entry.sec   = ((Entry.timdos      ) & 0x1f) * 2;

    /* indicate success                                                    */
    return 1;
}


/****************************************************************************
**
*F  OpenReadFile(,)  . . . . . . . . . . . open a file for reading
*F  ClosReadFile()  . . . . . . . . . . . . . . . . . . .  close a file again
*F  BlckReadFile(,) . . . . . . .  write a block of bytes to a file
*F  BufFile[] . . . . . . . . . . . . . . . . . . . . . . buffer for the file
**
**  'OpenReadFile' tries to open the archive  with local path name  (as
**  converted by 'CONV_NAME'  and 'CONV_DIRE') for reading  and returns 1  to
**  indicate success  and 0 to  indicate that the file cannot  be opened.  If
**   is  0, the file is opened   as a text file,   otherwise the file is
**  opened as a binary file.
**
**  'ClosReadFile' closes the file again.
**
**  'BlckReadFile' reads   bytes from the  file to the buffer   and
**  returns the  number    of bytes actually   read.   If  no file    is open
**  'BlckReadFile' only returns 0.
**
**  'BufFile'  is  a buffer for  the  file (which is not   used  by the above
**  functions).
*/
unsigned long   IsOpenReadFile;

int             OpenReadFile ( patl, bin )
    char *              patl;
    unsigned long       bin;
{
    if      ( bin == 0 && OPEN_READ_TEXT(patl) ) {
        IsOpenReadFile = 1;
        return 1;
    }
    else if ( bin == 1 && OPEN_READ_BINR(patl) ) {
        IsOpenReadFile = 2;
        return 1;
    }
    else {
        return 0;
    }
}

int             ClosReadFile ()
{
    if      ( IsOpenReadFile == 1 ) {
        IsOpenReadFile = 0;
        return CLOS_READ_TEXT();
    }
    else if ( IsOpenReadFile == 2 ) {
        IsOpenReadFile = 0;
        return CLOS_READ_BINR();
    }
    else {
        return 0;
    }
}

unsigned long   BlckReadFile ( blk, len )
    char *              blk;
    unsigned long       len;
{
    if      ( IsOpenReadFile == 1 ) {
        return BLCK_READ_TEXT( blk, len );
    }
    else if ( IsOpenReadFile == 2 ) {
        return BLCK_READ_BINR( blk, len );
    }
    else {
        return 0;
    }
}

char            BufFile [8192];         /* at least MAX_OFF                */


/****************************************************************************
**
*F  OpenWritFile(,)  . . . . . . . . . . . open a file for writing
*F  ClosWritFile()  . . . . . . . . . . . . . . . . . . .  close a file again
*F  BlckWritFile(,) . . . . . . .  write a block of bytes to a file
**
**  'OpenWritFile' tries to open the archive  with local path name  (as
**  converted by 'CONV_NAME'  and 'CONV_DIRE') for writing  and returns  1 to
**  indicate success  and 0 to indicate  that the file cannot  be opened.  If
**   is  0, the file  is opened as a text   file, otherwise the  file is
**  opened as a binary file.
**
**  'ClosWritFile' closes the file again.
**
**  'BlckWritFile' writes   bytes from the  buffer  to the file and
**  returns the number  of bytes actually written,  which is less than  
**  only when a write error happened.  If no file is open 'BlckWritFile' only
**  returns .
*/
unsigned long   IsOpenWritFile;

int             OpenWritFile ( patl, bin )
    char *              patl;
    unsigned long       bin;
{
    if ( patl == 0 ) {
        IsOpenWritFile = 1;
        return 1;
    }
    else if ( bin == 1 && OPEN_WRIT_TEXT(patl) ) {
        IsOpenWritFile = 2;
        return 1;
    }
    else if ( bin == 2 && OPEN_WRIT_BINR(patl) ) {
        IsOpenWritFile = 3;
        return 1;
    }
#ifdef  SYS_IS_MAC_MPW
    else if ( bin == 3 && OPEN_WRIT_MACB(patl) ) {
        IsOpenWritFile = 4;
        return 1;
    }
#endif
    else {
        return 0;
    }
}

int             ClosWritFile ()
{
    if      ( IsOpenWritFile == 1 ) {
        return 1;
    }
    else if ( IsOpenWritFile == 2 ) {
        IsOpenWritFile = 0;
        return CLOS_WRIT_TEXT();
    }
    else if ( IsOpenWritFile == 3 ) {
        IsOpenWritFile = 0;
        return CLOS_WRIT_BINR();
    }
#ifdef  SYS_IS_MAC_MPW
    else if ( IsOpenWritFile == 4 ) {
        IsOpenWritFile = 0;
        return CLOS_WRIT_MACB();
    }
#endif
    else {
        return 0;
    }
}

unsigned long   BlckWritFile ( blk, len )
    char *              blk;
    unsigned long       len;
{
    unsigned long       i;              /* loop variable                   */
    if      ( IsOpenWritFile == 1 ) {
        for ( i = 0; i < len; i++ )
            putchar( blk[i] );
        return len;
    }
    else if ( IsOpenWritFile == 2 ) {
        return BLCK_WRIT_TEXT( blk, len );
    }
    else if ( IsOpenWritFile == 3 ) {
        return BLCK_WRIT_BINR( blk, len );
    }
#ifdef  SYS_IS_MAC_MPW
    else if ( IsOpenWritFile == 4 ) {
        return BLCK_WRIT_MACB( blk, len );
    }
#endif
    else {
        return len;
    }
}


/****************************************************************************
**
*V  Crc . . . . . . . . . . . . . . . . current cyclic redundancy check value
*F  CRC_BYTE(,)  . . . . . cyclic redundancy check value of a byte
*F  InitCrc() . . . . . . . . . . . . initialize cylic redundancy check table
**
**  'Crc'  is used by  the  decoding  functions to  communicate  the computed
**  CRC-16 value to the calling function.
**
**  'CRC_BYTE' returns the new value that one gets by updating the old CRC-16
**  value  with the additional byte  .  It is  used to compute the
**  ANSI CRC-16 value for  each member of the archive.   They idea is that if
**  not  too many bits  of a member have corrupted,  then  the CRC-16 will be
**  different, and so the corruption can be detected.
**
**  'InitCrc' initialize the table that 'CRC_BYTE' uses.   You must call this
**  before using 'CRC_BYTE'.
**
**  The  ANSI CRC-16  value  for a sequence of    bits of lenght   is
**  computed by shifting the bits through the following shift register (where
**  'O' are the latches and '+' denotes logical xor)
**
**                  bit          bit            ...  bit   bit   bit   -->-
**                          -1          3     2     1     |
**                                                                        V
**      -<-------<---------------------------------------------------<----+
**      |       |                                                   |     ^
**      V       V                                                   V     |
**      ->O-->O-+>O-->O-->O-->O-->O-->O-->O-->O-->O-->O-->O-->O-->O-+>O-->-
**       MSB                                                         LSB
**
**  Mathematically we compute in the polynomial ring $GF(2)[x]$ the remainder
**
**      $$\sum_{i=1}^{i=length}{bit_i x^{length+16-i}} mod crcpol$$
**
**  where  $crcpol = x^{16}  + x^{15}  +  x^2 +  1$.  Then  the  CRC-16 value
**  consists  of the  coefficients   of  the remainder,  with    the constant
**  coefficient being  the most significant bit (MSB)  and the coefficient of
**  $x^{15}$ the least significant bit (LSB).
**
**  Changing  a  single bit will  always cause  the  CRC-16  value to change,
**  because $x^{i} mod crcpol$ is never zero.
**
**  Changing two  bits  will cause the CRC-16   value to change,  unless  the
**  distance between the bits is a multiple  of 32767, which  is the order of
**  $x$ modulo $crcpol = (x+1)(x^{15} + x + 1)$ ($x^{15}+x+1$ is primitive).
**
**  Changing  16 adjacent  bits will always  cause the  CRC value  to change,
**  because $x^{16}$ and $crcpol$ are relatively prime.
**
**  David Schwaderer provided the CRC-16 calculation in PC Tech Journal 4/85.
*/
unsigned long   Crc;

unsigned long   CrcTab [256];

#define CRC_BYTE(crc,byte)      (((crc)>>8) ^ CrcTab[ ((crc)^(byte))&0xff ])

int             InitCrc ()
{
    unsigned long       i, k;           /* loop variables                  */
    for ( i = 0; i < 256; i++ ) {
        CrcTab[i] = i;
        for ( k = 0; k < 8; k++ )
            CrcTab[i] = (CrcTab[i]>>1) ^ ((CrcTab[i] & 1) ? 0xa001 : 0);
    }
    return 1;
}


/****************************************************************************
**
*V  ErrMsg  . . . . . . . . . . . . . . . . . . . . . . . . . . error message
**
**  'ErrMsg' is used by the  decode functions to communicate  the cause of an
**  error to the calling function.
*/
char *          ErrMsg;


/****************************************************************************
**
*F  DecodeCopy(). . . . . . . . . . . .  extract an uncompressed member
**
**  'DecodeCopy' simply  copies  bytes  from the  archive to the output
**  file.
*/
int             DecodeCopy ( size )
    unsigned long       size;
{
    unsigned long       siz;            /* size of current block           */
    unsigned long       crc;            /* CRC-16 value                    */
    unsigned long       i;              /* loop variable                   */

    /* initialize the crc value                                            */
    crc = 0;

    /* loop until everything has been copied                               */
    while ( 0 < size ) {

        /* read as many bytes as possible in one go                        */
        siz = (sizeof(BufFile) < size ? sizeof(BufFile) : size);
        if ( BlckReadArch( BufFile, siz ) != siz ) {
            ErrMsg = "unexpected  in the archive";
            return 0;
        }

        /* write them                                                      */
        if ( BlckWritFile( BufFile, siz ) != siz ) {
            ErrMsg = "cannot write output file";
            return 0;
        }

        /* compute the crc                                                 */
        for ( i = 0; i < siz; i++ )
            crc = CRC_BYTE( crc, BufFile[i] );

        /* on to the next block                                            */
        size -= siz;
    }

    /* store the crc and indicate success                                  */
    Crc = crc;
    return 1;
}


/****************************************************************************
**
*F  DecodeLzd() . . . . . . . . . . . . . . .  extract a LZ compressed member
**
*N  1993/10/21 martin add LZD.
*/
int             DecodeLzd ()
{
    ErrMsg = "LZD not yet implemented";
    return 0;
}


/****************************************************************************
**
*F  DecodeLzh() . . . . . . . . . . . . . . . extract a LZH compressed member
**
**  'DecodeLzh'  decodes  a LZH  (Lempel-Ziv 77  with dynamic Huffman coding)
**  encoded member from the archive to the output file.
**
**  Each member is encoded as a  series of blocks.  Each  block starts with a
**  16  bit field that contains the  number of codes  in this block .
**  The member is terminated by a block with 0 codes.
**
**  Next each block contains the  description of three Huffman codes,  called
**  pre code, literal/length code, and log code.  The purpose of the pre code
**  is to encode the description of  the literal/length code.  The purpose of
**  the literal/length code and the  log code is   to encode the  appropriate
**  fields in the LZ code.   I am too stupid to  understand the format of the
**  description.
**
**  Then   each block contains    codewords.  There  are two kinds of
**  codewords, *literals* and *copy instructions*.
**
**  A literal represents a certain byte.  For  the moment imaging the literal
**  as having 9 bits.   The first bit  is zero, the other  8 bits contain the
**  byte.
**
**      +--+----------------+
**      | 0|          |
**      +--+----------------+
**
**  When a  literal is  encountered, the byte   that  it represents  is
**  appended to the output.
**
**  A copy  instruction represents a certain  sequence of bytes that appeared
**  already  earlier in the output.  The  copy instruction  consists of three
**  parts, the length, the offset logarithm, and the offset mantissa.
**
**      +--+----------------+--------+--------------------+
**      | 1|   -3   |   |          |
**      +--+----------------+--------+--------------------+
**
**    is  the  length  of the sequence   which  this copy instruction
**  represents.  We store '-3', because  is never 0, 1, or 2;
**  such sequences are better represented by 0, 1, or  2 literals.   and
**    together represent the offset at  which the sequence of bytes
**  already  appeared.  '-1'  is  the number of   bits in the 
**  field, and the offset is $2^{-1} + $.  For example
**
**      +--+----------------+--------+----------+
**      | 1|        9       |    6   | 0 1 1 0 1|
**      +--+----------------+--------+----------+
**
**  represents the sequence of 12 bytes that appeared $2^5 + 8 + 4  + 1 = 45$
**  bytes earlier in the output (so those 18 bits of input represent 12 bytes
**  of output).
**
**  When a copy instruction  is encountered, the  sequence of   bytes
**  that appeared    bytes earlier  in the  output  is again appended
**  (copied) to   the output.   For this  purpose  the last    bytes are
**  remembered,  where    is the   maximal  used offset.   In 'zoo' this
**  maximal offset is $2^{13} =  8192$.  The buffer in  which those bytes are
**  remembered is  called   a sliding  window for   reasons  that  should  be
**  obvious.
**
**  To save even  more space the first 9  bits of each code, which  represent
**  the type of code and either the literal value or  the length, are encoded
**  using  a Huffman code  called the literal/length  code.   Also the next 4
**  bits in  copy instructions, which represent  the logarithm of the offset,
**  are encoded using a second Huffman code called the log code.
**
**  Those  codes  are fixed, i.e.,  not  adaptive, but  may  vary between the
**  blocks, i.e., in each block  literals/lengths and logs  may be encoded by
**  different codes.  The codes are described at the beginning of each block.
**
**  Haruhiko Okumura  wrote the  LZH code (originally for his 'ar' archiver).
*/
#define MAX_LIT                 255     /* maximal literal code            */
#define MIN_LEN                 3       /* minimal length of match         */
#define MAX_LEN                 256     /* maximal length of match         */
#define MAX_CODE                (MAX_LIT+1 + MAX_LEN+1 - MIN_LEN)
#define BITS_CODE               9       /* 2^BITS_CODE > MAX_CODE (+1?)    */
#define MAX_OFF                 8192    /* 13 bit sliding directory        */
#define MAX_LOG                 13      /* maximal log_2 of offset         */
#define BITS_LOG                4       /* 2^BITS_LOG > MAX_LOG (+1?)      */
#define MAX_PRE                 18      /* maximal pre code                */
#define BITS_PRE                5       /* 2^BITS_PRE > MAX_PRE (+1?)      */

unsigned short  TreeLeft [2*MAX_CODE+1];/* tree for codes   (upper half)   */
unsigned short  TreeRight[2*MAX_CODE+1];/* and  for offsets (lower half)   */
unsigned short  TabCode  [4096];        /* table for fast lookup of codes  */
unsigned char   LenCode  [MAX_CODE+1];  /* number of bits used for code    */
unsigned short  TabLog   [256];         /* table for fast lookup of logs   */
unsigned char   LenLog   [MAX_LOG+1];   /* number of bits used for logs    */
unsigned short  TabPre   [256];         /* table for fast lookup of pres   */
unsigned char   LenPre   [MAX_PRE+1];   /* number of bits used for pres    */

int             MakeTablLzh ( nchar, bitlen, tablebits, table )
    int                 nchar;
    unsigned char       bitlen[];
    int                 tablebits;
    unsigned short      table[];
{
    unsigned short      count[17], weight[17], start[18], *p;
    unsigned int        i, k, len, ch, jutbits, avail, mask;

    for (i = 1; i <= 16; i++) count[i] = 0;
    for (i = 0; i < nchar; i++) count[bitlen[i]]++;

    start[1] = 0;
    for (i = 1; i <= 16; i++)
        start[i + 1] = start[i] + (count[i] << (16 - i));
    if (start[17] != (unsigned short)((unsigned) 1 << 16))
        return 0;

    jutbits = 16 - tablebits;
    for (i = 1; i <= tablebits; i++) {
        start[i] >>= jutbits;
        weight[i] = (unsigned) 1 << (tablebits - i);
    }
    while (i <= 16) {
        weight[i] = (unsigned) 1 << (16 - i);
        i++;
    }

    i = start[tablebits + 1] >> jutbits;
    if (i != (unsigned short)((unsigned) 1 << 16)) {
        k = 1 << tablebits;
        while (i != k) table[i++] = 0;
    }

    avail = nchar;
    mask = (unsigned) 1 << (15 - tablebits);
    for (ch = 0; ch < nchar; ch++) {
        if ((len = bitlen[ch]) == 0) continue;
        if (len <= tablebits) {
            for ( i = 0; i < weight[len]; i++ )  table[i+start[len]] = ch;
        }
        else {
            k = start[len];
            p = &table[k >> jutbits];
            i = len - tablebits;
            while (i != 0) {
                if (*p == 0) {
                    TreeRight[avail] = TreeLeft[avail] = 0;
                    *p = avail++;
                }
                if (k & mask) p = &TreeRight[*p];
                else          p = &TreeLeft[*p];
                k <<= 1;  i--;
            }
            *p = ch;
        }
        start[len] += weight[len];
    }

    /* indicate success                                                    */
    return 1;
}

int             DecodeLzh ()
{
    unsigned long       cnt;            /* number of codes in block        */
    unsigned long       cnt2;           /* number of stuff in pre code     */
    unsigned long       code;           /* code from the Archive           */
    unsigned long       len;            /* length of match                 */
    unsigned long       log;            /* log_2 of offset of match        */
    unsigned long       off;            /* offset of match                 */
    unsigned long       pre;            /* pre code                        */
    char *              cur;            /* current position in BufFile     */
    char *              pos;            /* position of match               */
    char *              end;            /* pointer to the end of BufFile   */
    char *              stp;            /* stop pointer during copy        */
    unsigned long       crc;            /* cyclic redundancy check value   */
    unsigned long       i;              /* loop variable                   */
    unsigned long       bits;           /* the bits we are looking at      */
    unsigned long       bitc;           /* number of bits that are valid   */

#define PEEK_BITS(N)            ((bits >> (bitc-(N))) & ((1L<<(N))-1))
#define FLSH_BITS(N)            if ( (bitc -= (N)) < 16 ) {              \
                                    bits  = (bits<<16) + FlahReadArch(); \
                                    bitc += 16;                          }

    /* initialize bit source, output pointer, and crc                      */
    bits = 0;  bitc = 0;  FLSH_BITS(0);
    cur = BufFile;  end = BufFile + MAX_OFF;
    crc = 0;

    /* loop until all blocks have been read                                */
    cnt = PEEK_BITS( 16 );  FLSH_BITS( 16 );
    while ( cnt != 0 ) {

        /* read the pre code                                               */
        cnt2 = PEEK_BITS( BITS_PRE );  FLSH_BITS( BITS_PRE );
        if ( cnt2 == 0 ) {
            pre = PEEK_BITS( BITS_PRE );  FLSH_BITS( BITS_PRE );
            for ( i = 0; i <      256; i++ )  TabPre[i] = pre;
            for ( i = 0; i <= MAX_PRE; i++ )  LenPre[i] = 0;
        }
        else {
            i = 0;
            while ( i < cnt2 ) {
                len = PEEK_BITS( 3 );  FLSH_BITS( 3 );
                if ( len == 7 ) {
                    while ( PEEK_BITS( 1 ) ) { len++; FLSH_BITS( 1 ); }
                    FLSH_BITS( 1 );
                }
                LenPre[i++] = len;
                if ( i == 3 ) {
                    len = PEEK_BITS( 2 );  FLSH_BITS( 2 );
                    while ( 0 < len-- )  LenPre[i++] = 0;
                }
            }
            while ( i <= MAX_PRE )  LenPre[i++] = 0;
            if ( ! MakeTablLzh( MAX_PRE+1, LenPre, 8, TabPre ) ) {
                ErrMsg = "pre code description corrupted";
                return 0;
            }
        }

        /* read the code (using the pre code)                              */
        cnt2 = PEEK_BITS( BITS_CODE );  FLSH_BITS( BITS_CODE );
        if ( cnt2 == 0 ) {
            code = PEEK_BITS( BITS_CODE );  FLSH_BITS( BITS_CODE );
            for ( i = 0; i <      4096; i++ )  TabCode[i] = code;
            for ( i = 0; i <= MAX_CODE; i++ )  LenCode[i] = 0;
        }
        else {
            i = 0;
            while ( i < cnt2 ) {
                len = TabPre[ PEEK_BITS( 8 ) ];
                if ( len <= MAX_PRE ) {
                    FLSH_BITS( LenPre[len] );
                }
                else {
                    FLSH_BITS( 8 );
                    do {
                        if ( PEEK_BITS( 1 ) )  len = TreeRight[len];
                        else                   len = TreeLeft [len];
                        FLSH_BITS( 1 );
                    } while ( MAX_PRE < len );
                }
                if ( len <= 2 ) {
                    if      ( len == 0 ) {
                        len = 1;
                    }
                    else if ( len == 1 ) {
                        len = PEEK_BITS(4)+3;  FLSH_BITS(4);
                    }
                    else {
                        len = PEEK_BITS(BITS_CODE)+20; FLSH_BITS(BITS_CODE);
                    }
                    while ( 0 < len-- )  LenCode[i++] = 0;
                }
                else {
                    LenCode[i++] = len - 2;
                }
            }
            while ( i <= MAX_CODE )  LenCode[i++] = 0;
            if ( ! MakeTablLzh( MAX_CODE+1, LenCode, 12, TabCode ) ) {
                ErrMsg = "literal/length code description corrupted";
                return 0;
            }
        }

        /* read the log_2 of offsets                                       */
        cnt2 = PEEK_BITS( BITS_LOG );  FLSH_BITS( BITS_LOG );
        if ( cnt2 == 0 ) {
            log = PEEK_BITS( BITS_LOG );  FLSH_BITS( BITS_LOG );
            for ( i = 0; i <      256; i++ )  TabLog[i] = log;
            for ( i = 0; i <= MAX_LOG; i++ )  LenLog[i] = 0;
        }
        else {
            i = 0;
            while ( i < cnt2 ) {
                len = PEEK_BITS( 3 );  FLSH_BITS( 3 );
                if ( len == 7 ) {
                    while ( PEEK_BITS( 1 ) ) { len++; FLSH_BITS( 1 ); }
                    FLSH_BITS( 1 );
                }
                LenLog[i++] = len;
            }
            while ( i <= MAX_LOG )  LenLog[i++] = 0;
            if ( ! MakeTablLzh( MAX_LOG+1, LenLog, 8, TabLog ) ) {
                ErrMsg = "log code description corrupted";
                return 0;
            }
        }

        /* read the codes                                                  */
        while ( 0 < cnt-- ) {

            /* try to decode the code the fast way                         */
            code = TabCode[ PEEK_BITS( 12 ) ];

            /* if this code needs more than 12 bits look it up in the tree */
            if ( code <= MAX_CODE ) {
                FLSH_BITS( LenCode[code] );
            }
            else {
                FLSH_BITS( 12 );
                do {
                    if ( PEEK_BITS( 1 ) )  code = TreeRight[code];
                    else                   code = TreeLeft [code];
                    FLSH_BITS( 1 );
                } while ( MAX_CODE < code );
            }

            /* if the code is a literal, stuff it into the buffer          */
            if ( code <= MAX_LIT ) {
                *cur++ = code;
                crc = CRC_BYTE( crc, code );
                if ( cur == end ) {
                    if ( BlckWritFile(BufFile,cur-BufFile) != cur-BufFile ) {
                        ErrMsg = "cannot write output file";
                        return 0;
                    }
                    cur = BufFile;
                }
            }

            /* otherwise compute match length and offset and copy          */
            else {
                len = code - (MAX_LIT+1) + MIN_LEN;

                /* try to decodes the log_2 of the offset the fast way     */
                log = TabLog[ PEEK_BITS( 8 ) ];
                /* if this log_2 needs more than 8 bits look in the tree   */
                if ( log <= MAX_LOG ) {
                    FLSH_BITS( LenLog[log] );
                }
                else {
                    FLSH_BITS( 8 );
                    do {
                        if ( PEEK_BITS( 1 ) )  log = TreeRight[log];
                        else                   log = TreeLeft [log];
                        FLSH_BITS( 1 );
                    } while ( MAX_LOG < log );
                }

                /* compute the offset                                      */
                if ( log == 0 ) {
                    off = 0;
                }
                else {
                    off = ((unsigned)1 << (log-1)) + PEEK_BITS( log-1 );
                    FLSH_BITS( log-1 );
                }

                /* copy the match (this accounts for ~ 50% of the time)    */
                pos = BufFile + (((cur-BufFile) - off - 1) & (MAX_OFF - 1));
                if ( cur < end-len && pos < end-len ) {
                    stp = cur + len;
                    do {
                        code = *pos++;
                        crc = CRC_BYTE( crc, code );
                        *cur++ = code;
                    } while ( cur < stp );
                }
                else {
                    while ( 0 < len-- ) {
                        code = *pos++;
                        crc = CRC_BYTE( crc, code );
                        *cur++ = code;
                        if ( pos == end ) {
                            pos = BufFile;
                        }
                        if ( cur == end ) {
                            if ( BlckWritFile(BufFile,cur-BufFile)
                                 != cur-BufFile ) {
                                ErrMsg = "cannot write output file";
                                return 0;
                            }
                            cur = BufFile;
                        }
                    }
                }

            }

        }

        cnt = PEEK_BITS( 16 );  FLSH_BITS( 16 );
    }

    /* write out the rest of the buffer                                    */
    if ( BlckWritFile(BufFile,cur-BufFile) != cur-BufFile ) {
        ErrMsg = "cannot write output file";
        return 0;
    }

    /* indicate success                                                    */
    Crc = crc;
    return 1;
}


/****************************************************************************
**
*F  ListArch(,,,) . . list the members of the archive
**
**  'ListArch'  lists the members  of the  archive with  the name   that
**  match one  of the file name  patterns '[0] .. [-1]'.
**  If  is 1, comments are also printed.
*/
unsigned long   BeginMonth [12] = {
   0,    31,   59,   90,  120,  151,  181,  212,  243,  273,  304,  334
};

char            NameMonth [12] [4] = {
"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
};

int             ListArch ( ver, arc, filec, files )
    unsigned long       ver;
    char *              arc;
    unsigned long       filec;
    char *              files [];
{
    char                arczoo [256];   /*  with '.zoo' tacked on     */
    int                 chr;            /* character from comment          */
    unsigned long       i;              /* loop variable                   */

    /* try to open the archive under various names                         */
    strcpy(arczoo,arc);  strcat(arczoo,".zoo");
    if ( OpenReadArch(arc) ) {
        if ( ! DescReadArch() ) {
            ClosReadArch();
            if ( ! OpenReadArch(arczoo) || ! DescReadArch() ) {
                printf("unzoo: found bad description in archive '%s'\n",arc);
                return 0;
            }
        }
    }
    else if ( OpenReadArch(arczoo) ) {
        if ( ! DescReadArch() ) {
            printf("unzoo: found bad description in archive '%s'\n",arczoo);
            return 0;
        }
    }
    else {
        printf("unzoo: could not open archive '%s'\n",arc);
        return 0;
    }

    /* if present, print the archive comment                               */
    if ( ver && Descript.sizcmt != 0 ) {
        if ( ! GotoReadArch( Descript.poscmt ) ) {
            printf("unzoo: cannot find comment in archive '%s'\n",arc);
            return 0;
        }
        chr = '\n';
        for ( i = 0; i < Descript.sizcmt; i++ ) {
            if ( chr == '\n' )  printf("# ");
            chr = ByteReadArch();
            if ( chr == '\012' )  chr = '\n';
            printf("%c",chr);
        }
        if ( chr != '\n' )  printf("\n");
        fflush( stdout );
    }

    /* print the header                                                    */
    printf("Length    CF  Size Now  Date      Time    \n");
    printf("--------  --- --------  --------- --------\n");
    fflush( stdout );

    /* loop over the members of the archive                                */
    Entry.posnxt = Descript.posent;
    while ( 1 ) {

        /* read the directory entry for the next member                    */
        if ( ! GotoReadArch( Entry.posnxt ) || ! EntrReadArch() ) {
            printf("unzoo: found bad directory entry in archive '%s'\n",arc);
            return 0;
        }
        if ( ! Entry.posnxt )  break;

        /* skip members we don't care about                                */
        if ( Entry.delete == 1 )
            continue;
        if ( filec == 0 && ! IsMatchName( "*", Entry.patw ) )
            continue;
        for ( i = 0; i < filec; i++ )
            if ( IsMatchName( files[i], Entry.patv )
              || IsMatchName( files[i], Entry.patw ) )
                break;
        if ( filec != 0 && i == filec )
            continue;

        /* print the information about the member                          */
        printf("%8lu %3lu%% %8lu  %2lu %3s %02lu %02lu:%02lu:%02lu   %s\n",
               Entry.sizorg,
               (100*(Entry.sizorg-Entry.siznow)+Entry.sizorg/2)
               / (Entry.sizorg != 0 ? Entry.sizorg : 1),
               Entry.siznow,
               Entry.day, NameMonth[Entry.month], Entry.year % 100,
               Entry.hour, Entry.min, Entry.sec,
               (ver ? Entry.patv : Entry.patw) );
        fflush( stdout );

        /* update the counts for the whole archive                         */
        Descript.sizorg += Entry.sizorg;
        Descript.siznow += Entry.siznow;
        Descript.number += 1;

        /* if present print the file comment                               */
        if ( ver && Entry.sizcmt != 0 ) {
            if ( ! GotoReadArch( Entry.poscmt ) ) {
                printf("unzoo: cannot find comment in archive '%s'\n",arc);
                return 0;
            }
            chr = '\n';
            for ( i = 0; i < Entry.sizcmt; i++ ) {
                if ( chr == '\n' )  printf("# ");
                chr = ByteReadArch();
                if ( chr == '\012' )  chr = '\n';
                printf("%c",chr);
            }
            if ( chr != '\n' )  printf("\n");
        }
        fflush( stdout );

    }

    /* print the footer                                                    */
    printf("--------  --- --------  --------- --------\n");
    printf("%8lu %3lu%% %8lu  %4lu files\n",
           Descript.sizorg,
           (100*(Descript.sizorg-Descript.siznow)+Descript.sizorg/2)
           / (Descript.sizorg != 0 ? Descript.sizorg : 1),
           Descript.siznow,
           Descript.number );
    fflush( stdout );

    /* close the archive file                                              */
    if ( ! ClosReadArch() ) {
        printf("unzoo: could not close archive '%s'\n",arc);
        return 0;
    }

    /* indicate success                                                    */
    return 1;
}


/****************************************************************************
**
*F  ExtrArch(,,,
,,,) . extract members
**
**  'ExtrArch' extracts the members  of the archive with  the name  that
**  match one  of the file name  patterns '[0] .. [-1]'.
**  If  is 0, members with comments starting with '!TEXT!' are extracted
**  as text files and the other members are extracted as  binary files; if it
**  is 1,  all members are extracted  as text files; if  it is 2, all members
**  are  extracted as binary  files. If   is 0, no members are extracted
**  and only tested  for integrity; if it  is 1, the  members are printed  to
**  stdout, i.e., to the screen.  and if it  is 2, the members are extracted.
**  If  is 0, members will not overwrite  existing files; otherwise they
**  will.  
 is a prefix that is prepended to all path names.
*/
int             ExtrArch ( bim, out, ovr, pre, arc, filec, files )
    unsigned long       bim;
    unsigned long       out;
    unsigned long       ovr;
    char *              pre;
    char *              arc;
    unsigned long       filec;
    char *              files [];
{
    char                arczoo [256];   /*  with '.zoo' tacked on     */
    char                ans [256];      /* to read the answer              */
    char                patl [1024];    /* local name with prefix          */
    unsigned long       bin;            /* extraction mode text/binary     */
    unsigned long       res;            /* status of decoding              */
    unsigned long       secs;           /* seconds since 70/01/01 00:00:00 */
    unsigned long       i;              /* loop variable                   */

    /* try to open the archive under various names                         */
    strcpy(arczoo,arc);  strcat(arczoo,".zoo");
    if ( OpenReadArch(arc) ) {
        if ( ! DescReadArch() ) {
            ClosReadArch();
            if ( ! OpenReadArch(arczoo) || ! DescReadArch() ) {
                printf("unzoo: found bad description in archive '%s'\n",arc);
                return 0;
            }
        }
    }
    else if ( OpenReadArch(arczoo) ) {
        if ( ! DescReadArch() ) {
            printf("unzoo: found bad description in archive '%s'\n",arczoo);
            return 0;
        }
    }
    else {
        printf("unzoo: could not open archive '%s'\n",arc);
        return 0;
    }

    /* test if the archive has a comment starting with '!TEXT!'            */
    if ( bim == 0
      && 6 <= Descript.sizcmt  && GotoReadArch( Descript.poscmt )
      && ByteReadArch() == '!' && ByteReadArch() == 'T'
      && ByteReadArch() == 'E' && ByteReadArch() == 'X'
      && ByteReadArch() == 'T' && ByteReadArch() == '!' )
        bim = 1;

    /* test if the archive has a comment starting with '!MACBINARY!'       */
#ifdef  SYS_IS_MAC_MPW
    else if ( bim == 0
      && 11 <= Descript.sizcmt && GotoReadArch( Descript.poscmt )
      && ByteReadArch() == '!' && ByteReadArch() == 'M'
      && ByteReadArch() == 'A' && ByteReadArch() == 'C'
      && ByteReadArch() == 'B' && ByteReadArch() == 'I'
      && ByteReadArch() == 'N' && ByteReadArch() == 'A'
      && ByteReadArch() == 'R' && ByteReadArch() == 'Y'
      && ByteReadArch() == '!' )
        bim = 3;
#endif

    /* loop over the members of the archive                                */
    Entry.posnxt = Descript.posent;
    while ( 1 ) {

        /* read the directory entry for the next member                    */
        if ( ! GotoReadArch( Entry.posnxt ) || ! EntrReadArch() ) {
            printf("unzoo: found bad directory entry in archive '%s'\n",arc);
            return 0;
        }
        if ( ! Entry.posnxt )  break;

        /* skip members we don't care about                                */
        if ( Entry.delete == 1 )
            continue;
        if ( filec == 0 && ! IsMatchName( "*", Entry.patw ) )
            continue;
        for ( i = 0; i < filec; i++ )
            if ( IsMatchName( files[i], Entry.patv )
              || IsMatchName( files[i], Entry.patw ) )
                break;
        if ( filec != 0 && i == filec )
            continue;

        /* check that we can decode this file                              */
        if ( (2 < Entry.method) || (2 < Entry.majver)
          || (2 == Entry.majver && 1 < Entry.minver) ) {
            printf("unzoo: unknown method, you need a later version\n");
            continue;
        }

        /* check that such a file does not already exist                   */
        strcpy( patl, pre );  strcat( patl, Entry.patl );
        if ( out == 2 && ovr == 0 && OpenReadFile(patl,0L) ) {
            ClosReadFile();
            do {
                printf("'%s' exists, overwrite it? (Yes/No/All/Ren): ",patl);
                fflush( stdout );
                if ( fgets( ans, sizeof(ans), stdin ) == (char*)0 )
                    return 0;
            } while ( *ans!='y' && *ans!='n' && *ans!='a' && *ans!='r'
                   && *ans!='Y' && *ans!='N' && *ans!='A' && *ans!='R' );
            if      ( *ans == 'n' || *ans == 'N' ) {
                continue;
            }
            else if ( *ans == 'a' || *ans == 'A' ) {
                ovr = 1;
            }
            else if ( *ans == 'r' || *ans == 'R' ) {
                do {
                    printf("enter a new local path name: ");
                    fflush( stdout );
                    if ( fgets( patl, sizeof(patl), stdin ) == (char*)0 )
                        return 0;
                    for ( i = 0; patl[i] != '\0' && patl[i] != '\n'; i++ ) ;
                    patl[i] = '\0';
                } while ( OpenReadFile(patl,0L) && ClosReadFile() );
            }
        }

        /* decide whether or not we want to open the file binary           */
        if ( bim == 0
          && 6 <= Entry.sizcmt     && GotoReadArch( Entry.poscmt )
          && ByteReadArch() == '!' && ByteReadArch() == 'T'
          && ByteReadArch() == 'E' && ByteReadArch() == 'X'
          && ByteReadArch() == 'T' && ByteReadArch() == '!' )
            bin = 1;
#ifdef  SYS_IS_MAC_MPW
        else if ( bim == 0
          && 11 <= Entry.sizcmt    && GotoReadArch( Entry.poscmt )
          && ByteReadArch() == '!' && ByteReadArch() == 'M'
          && ByteReadArch() == 'A' && ByteReadArch() == 'C'
          && ByteReadArch() == 'B' && ByteReadArch() == 'I'
          && ByteReadArch() == 'N' && ByteReadArch() == 'A'
          && ByteReadArch() == 'R' && ByteReadArch() == 'Y'
          && ByteReadArch() == '!' )
            bin = 3;
#endif
        else if ( bim == 0 )
            bin = 2;
        else
            bin = bim;

        /* open the file for creation                                      */
        if ( out == 2 && ! OpenWritFile(patl,bin)
#ifdef  MAKE_DIRE
          && (! MakeDirs(pre,Entry.diru) || ! OpenWritFile(patl,bin))
#endif
            ) {
            printf("unzoo: '%s' cannot be created, ",patl);
#ifndef MAKE_DIRE
            if ( Entry.dirl[0] != '\0' )
                printf("check that the directory '%s' exists\n",Entry.dirl);
            else
                printf("check the permissions\n");
#else
            printf("check the permissions\n");
#endif
            continue;
        }

        /* or ``open'' stdout for printing                                 */
        if ( out == 1 )
            OpenWritFile( (char*)0, 0L );

        /* decode the file                                                 */
        if ( ! GotoReadArch( Entry.posdat ) ) {
            printf("unzoo: cannot find data in archive '%s'\n",arc);
            return 0;
        }
        res = 0;
        ErrMsg = "this should not happen";
        if ( out == 0 || out == 2 )
            printf("%s \t-- ",Entry.patl);
        else
            printf("********\n%s\n********\n",Entry.patl);
        fflush( stdout );
        if ( Entry.method == 0 )  res = DecodeCopy( Entry.siznow );
        if ( Entry.method == 1 )  res = DecodeLzd();
        if ( Entry.method == 2 )  res = DecodeLzh();

        /* check that everything went ok                                   */
        if      ( res == 0             )  printf("error, %s\n",ErrMsg);
        else if ( Crc != Entry.crcdat  )  printf("error, CRC failed\n");
        else if ( out == 2 && bin == 1 )  printf("extracted as text\n");
        else if ( out == 2 && bin == 2 )  printf("extracted as binary\n");
#ifdef  SYS_IS_MAC_MPW
        else if ( out == 2 && bin == 3 )  printf("extracted as MacBinary\n");
#endif
        else if ( out == 0             )  printf("tested\n");
        fflush( stdout );

        /* close the file after extraction                                 */
        if ( out == 1 || out == 2 )
            ClosWritFile();

        /* set the file time, evt. correct for timezone of packing system  */
        secs = 24*60*60L*(365*(Entry.year - 70)
                         + BeginMonth[Entry.month]
                         + Entry.day - 1
                         + (Entry.year -  69) / 4
                         + (Entry.year %   4 ==   0 && 1 < Entry.month)
                         - (Entry.year + 299) / 400
                         - (Entry.year % 400 == 100 && 1 < Entry.month))
                 +60*60L*Entry.hour + 60L*Entry.min + Entry.sec;
        if      ( Entry.timzon < 127 )  secs += 15*60*(Entry.timzon      );
        else if ( 127 < Entry.timzon )  secs += 15*60*(Entry.timzon - 256);
        if ( out == 2 ) {
            if ( ! SETF_TIME( patl, secs ) )
                printf("unzoo: '%s' could not set the times\n",patl);
        }

        /* set the file permissions                                        */
        if ( out == 2 && (Entry.permis >> 22) == 1 ) {
            if ( ! SETF_PERM( patl, Entry.permis ) )
                printf("unzoo: '%s' could not set the permissions\n",patl);
        }

    }

    /* close the archive file                                              */
    if ( ! ClosReadArch() ) {
        printf("unzoo: could not close the archive '%s'\n",arc);
        return 0;
    }

    /* indicate success                                                    */
    return 1;
}


/****************************************************************************
**
*F  HelpArch()  . . . . . . . . . . . . . . . . . . . . . . . print some help
**
**  'HelpArch' prints some help about 'unzoo'.
*/
int             HelpArch ()
{
    printf("unzoo -- a zoo archive extractor by Martin Schoenert\n");
    printf("  ($Id: unzoo.c,v 1.5 1994/01/21 13:32:32 mschoene Exp $)\n");
    printf("  based on 'booz' version 2.0 by Rahul Dhesi\n");
    printf("\n");
    printf("unzoo [-l] [-v] [.zoo] [..]\n");
    printf("  list the members of the archive\n");
    printf("  -v:  list also the generation numbers and the comments\n");
    printf("  : list only files matching at least one pattern,\n");
    printf("          '?' matches any char, '*' matches any string.\n");
    printf("\n");
    printf("unzoo -x [-abnpo] [-j ] [.zoo] [..]\n");
    printf("  extract the members of the archive\n");
    printf("  -a:  extract all members as text files ");
    printf("(not only those with !TEXT! comments)\n");
    printf("  -b:  extract all members as binary files ");
    printf("(even those with !TEXT! comments)\n");
    printf("  -n:  extract no members, only test the integrity\n");
    printf("  -p:  extract to stdout\n");
    printf("  -o:  extract over existing files\n");
    printf("  -j:  extract to ''\n");
    printf("  : extract only files matching at least one pattern,\n");
    printf("          '?' matches any char, '*' matches any string.\n");
    return 1;
}


/****************************************************************************
**
*F  main(,) . . . . . . . . . . . . . . . . . . . .  main program
**
**  'main' is the main program, it decodes the arguments  and then  calls the
**  appropriate function.
*/
int             main ( argc, argv )
    int                 argc;
    char *              argv [];
{
    unsigned long       res;            /* result of command               */
    unsigned long       cmd;            /* command help/list/extract       */
    unsigned long       ver;            /* list verbose option             */
    unsigned long       bim;            /* extraction mode option          */
    unsigned long       out;            /* output destination option       */
    unsigned long       ovr;            /* overwrite file option           */
    char *              pre;            /* prefix to prepend to path names */
    char                argl [256];     /* interactive command line        */
    int                 argd;           /* interactive command count       */
    char *              argw [256];     /* interactive command vector      */
    char *              p;              /* loop variable                   */

    /* repeat until the user enters an empty line                          */
    InitCrc();
    IsSpec['\0'] = 1;  IsSpec[';'] = 1;
    argd = 1;
    do {

        /* scan the command line arguments                                 */
        cmd = 1;  ver = 0;  bim = 0;  out = 2;  ovr = 0;
        pre = "";
        while ( 1 < argc && argv[1][0] == '-' ) {
            if ( argv[1][2] != '\0' )  cmd = 0;
            switch ( argv[1][1] ) {
            case 'l': case 'L': if ( cmd != 0 )  cmd = 1;            break;
            case 'v': case 'V': if ( cmd != 1 )  cmd = 0;  ver = 1;  break;
            case 'x': case 'X': if ( cmd != 0 )  cmd = 2;            break;
            case 'a': case 'A': if ( cmd != 2 )  cmd = 0;  bim = 1;  break;
            case 'b': case 'B': if ( cmd != 2 )  cmd = 0;  bim = 2;  break;
            case 'n': case 'N': if ( cmd != 2 )  cmd = 0;  out = 0;  break;
            case 'p': case 'P': if ( cmd != 2 )  cmd = 0;  out = 1;  break;
            case 'o': case 'O': if ( cmd != 2 )  cmd = 0;  ovr = 1;  break;
            case 'j': case 'J': if ( argc == 2 ) { cmd = 0;  break; }
                                pre = argv[2];  argc--;  argv++;
                                break;
            default:            cmd = 0;  break;
            }
            argc--;  argv++;
        }

        /* execute the command or print help                               */
        if      ( cmd == 1 && 1 < argc )
            res = ListArch( ver, argv[1],
                            (unsigned long)argc-2, argv+2 );
        else if ( cmd == 2 && 1 < argc )
            res = ExtrArch( bim, out, ovr, pre, argv[1],
                            (unsigned long)argc-2, argv+2 );
        else
            res = HelpArch();

        /* in interactive mode read another line                           */
        if ( 1 < argd || argc <= 1 ) {

            /* read a command line                                         */
            printf("\nEnter a command line or an empty line to quit:\n");
            fflush( stdout );
            if ( fgets( argl, sizeof(argl), stdin ) == (char*)0 )  break;

            /* parse the command line into argc                            */
            argd = 1;
            p = argl;
            while ( *p==' ' || *p=='\t' || *p=='\n' )  *p++ = '\0';
            while ( *p != '\0' ) {
                argw[argd++] = p;
                while ( *p!=' ' && *p!='\t' && *p!='\n' && *p!='\0' )  p++;
                while ( *p==' ' || *p=='\t' || *p=='\n' )  *p++ = '\0';
            }
            argc = argd;  argv = argw;

        }

    } while ( 1 < argd );

    /* just to please lint                                                 */
    return ! res;
}