Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!utgpu!water!watnot!watmath!clyde!cuae2!ihnp4!ptsfa!lll-lcc!pyramid!hplabs!felix!macintosh From: macintosh@felix.UUCP Newsgroups: mod.mac.sources Subject: Unpit version 0.3.1 Source (part 1 of 3) Message-ID: <2277@felix.UUCP> Date: Fri, 13-Feb-87 02:40:47 EST Article-I.D.: felix.2277 Posted: Fri Feb 13 02:40:47 1987 Date-Received: Sat, 14-Feb-87 15:04:44 EST Sender: macintosh@felix.UUCP Reply-To: macintosh@felix.UUCP (The Moderator) Organization: FileNet Corp., Costa Mesa, Ca. Lines: 604 Approved: bytebug@felix.UUCP (Roger L. Long) [Unpit version 0.3.1 Source - part 1 of 3] --- /* unpit - Macintosh PackIt file packer/unpacker This program is a Macintosh translation/expansion of 'unpit', a Unix program written by Allan G. Weber (Weber%Brand@USC-ECL) that unpacks Packit-I and -II files. Why port it to the Mac? Well, Packit-II is not free, but many people are posting {public domain, noncommercial, or shareware} programs to USENET's net.sources.mac and various other places in Packit-II format, apparently due to the popularity achieved by the free Packit-I. This program is intended to get rid of the need to choose between these undesirable alternatives: (1) losing access to freely-distributed software that's in Packit II format (2) uploading Packit-II files to a Unix box just for the purpose of running unpit on them (unpit is great if the files are already on the Unix box, but what does one do with files that come from, say, GENIE?) (3) forking over $10 not because one wants to use Packit-II but because one can't put up with (1) or (2), and has a sense of ethics that prevents: (4) using Packit-II to decode downloaded files without paying for it Translation by Thomas D. Newton (ARPA address: Thomas.Newton@spice.cs.cmu.edu) Copyright Information: By now, this program has a little bit of everything in it. I would assume that the Unix version is (C) 1986 Allan Weber, except for the pieces taken from macput.c and macget.c. I suspect that this version doesn't have much of the macget/macput code left (when on a Mac, it makes sense just to call the filesystem rather than to write an .INFO file). On the other hand, it does have some code taken from BINConvert ((C) 1986 Thomas D. Newton), and some code taken from SKEL, and some code from standard CS textbooks. As I am writing this using LightSpeed C, the executable version will have a C) Think notice. Taking everything into account, this version of Unpit is Copyright (C) 1986 Thomas D. Newton This program may be copied and used for noncommercial purposes. It may not be sold or offered as inducement to buy a product. Parts of this program are derived from code written by various authors which itself can be copied and used noncommercially. Compilation Information: This program was compiled using LightSpeed C (tm) on a Macintosh. It uses the "MacTraps" library, and the resources can be found in the companion file, 'unpit.proj.rsrc'. Modification History: Version Date Who/Comments ======= ====== ============ 0.1 Aug 86 Thomas Newton (tdn@spice.cs.cmu.edu) Created. Translated Unix 'unpit' to Macintosh; added SKEL-like user interface shell, Packit-I packing, delete file, and help commands; hacked up the structure of the program to gain speed. 0.2 Aug 86 Thomas Newton (tdn@spice.cs.cmu.edu) Added code to call Disk Initialization Package so blank disks don't act "dead" when inserted. Removed CLOVER-B as an alias for EDIT'CLEAR -- it doesn't seem to work with MockWrite, and it seems that keyboard events that happen while a DA window is active are not returned to Unpit, so Unpit can't "force" the alias to work. Fixed a few calls to Err() that didn't include the second parameter; changed main() to put up a dialog box rather than beeping if user tries to Print packed files from the Finder. 0.3 Sep 86 Thomas Newton (tdn@spice.cs.cmu.edu) Added the ability to produce compressed packed files, using Huffman compression (Knuth's "The Art of Computer Programming", Volume One, Page 402 describes how to build weighted trees with minimum total weight using Huffman's method). Reduced the amount of text in the "About" box, both to get the size of the executable version down and because Unpit is now roughly equal to PackIt-II, rather than to PackIt-1.5 (not that there ever was a Packit-1.5 . . .). 0.3.1 Dec 86 Reid Ellis (unicus!rae@yetti.uucp) Added the ability to do "batch" packing by selecting all the files to be packed from the finder and holding the option key down whilst booting. Moved all resources to a '.rsrc' file (I use ResEdit 1.0 to do resource munging). ------------------------------------------------------------------------------- Format of a Packit file: Repeat the following sequence for each file in the Packit file: 4 byte identifier ("PMag" = not compressed, "Pma4" = compressed) variable length compression data (if compressed file) 92 byte header (see struct pit_header below) * 2 bytes CRC number * data fork (length from header) * resource fork (length from header) * 2 bytes CRC number * Last file is followed by the 4 byte Ascii string, "Pend", and then the EOF. * these are in compressed form if compression is on for the file */ #include #include #include #include #include #include #include #include #include #include #include typedef char byte; typedef int word; struct pit_header { /* Packit file header (92 bytes) byte nlen; /* number of characters in packed file name */ byte name[63]; /* name of packed file */ byte type[4]; /* file type */ byte auth[4]; /* file creator */ word flags; /* Finder flags */ word lock; /* low-order bit seems to be lock; others are ??? */ long dlen; /* number of bytes in data fork */ long rlen; /* number of bytes in resource fork */ long ctim; /* file creation time */ long mtim; /* file modified time */ }; #define HDRBYTES 92 #define BYTEMASK 0xff #define H_NAMELEN 63 #define H_NLENOFF 0 #define H_NAMEOFF 1 #define H_TYPEOFF 64 #define H_AUTHOFF 68 #define H_FLAGOFF 72 #define H_LOCKOFF 74 #define H_DLENOFF 76 #define H_RLENOFF 80 #define H_CTIMOFF 84 #define H_MTIMOFF 88 #define INITED 0x0100 #define FLOCK 0x01 /* Screen locations of SFGetFile and SFPutFile boxes */ #define GET_FILE_X 82 #define GET_FILE_Y 92 #define PUT_FILE_X 104 #define PUT_FILE_Y 92 /* Screen location of disk initialization dialog */ #define INIT_X 112 #define INIT_Y 80 typedef struct nod { int flag, byte, index2; long count; struct nod *one, *zero; } node, *nodeptr; node nodelist[512]; /* 512 should be big enough */ nodeptr heap[513], freenode; struct { long bits; int bcount; } byte_code[256]; unsigned char hdr[HDRBYTES]; typedef int crctype; long datalen, rsrclen; int decode, bit; Boolean Filter; /* if TRUE, Unpack... shows only PIT and TEXT files */ Boolean Compress; /* if TRUE, Pack... uses Huffman compression */ Boolean optkeyDown; /* forward declarations */ nodeptr read_tree(); long get4(); crctype read_hdr(); crctype write_fork(); crctype getcrc(); Boolean pack_files(); /* * Error codes that can be used as the second parameter to Err (a function * declared below). They let Err() know roughly what the program was doing * at the time that an error occurred so that it can display somewhat more * informative error messages. */ enum { errCreateFile, /* creating output file */ errOpenDataFk, /* opening data fork for write */ errOpenRsrcFk, /* opening resource fork for write */ errGetFInfo, /* getting information for unpacked file */ errSetFInfo, /* setting information for unpacked file */ errWriting, /* writing output file */ errReading, /* reading input file */ errLock, /* locking unpacked file */ errDelete /* deleting file */ }; /* Report an error to the user. This function is used to process the return codes received from FSOpen and the like. ############################ Err ############################## */ Boolean Err(theErr, activity) OSErr theErr; int activity; { char *errText, *errWhy; if (theErr == noErr) return FALSE; else { switch (activity) { case errCreateFile: errWhy = "\Pcreating output file"; break; case errOpenDataFk: errWhy = "\Popening data fork"; break; case errOpenRsrcFk: errWhy = "\Popening resource fork"; break; case errGetFInfo: errWhy = "\Pgetting Finder information"; break; case errSetFInfo: errWhy = "\Psetting Finder information"; break; case errWriting: errWhy = "\Pwriting to output file"; break; case errReading: errWhy = "\Preading from Packit file"; break; case errLock: errWhy = "\Plocking unpacked file"; break; case errDelete: errWhy = "\Ptrying to delete file"; break; default: errWhy = "\P"; break; } switch (theErr) { case bdNamErr: errText = "\PBad file name"; break; case dupFNErr: errText = "\PDuplicate file name"; break; case dirFulErr: errText = "\PDirectory full"; break; case extFSErr: errText = "\PExternal file system"; break; case ioErr: errText = "\PDisk I/O error"; break; case nsvErr: errText = "\PNo such volume"; break; case vLckdErr: errText = "\PSoftware volume lock"; break; case wPrErr: errText = "\PHardware volume lock"; break; case mFulErr: errText = "\PMemory full"; break; case tmfoErr: errText = "\PToo many files open"; break; case dskFulErr: errText = "\PDisk full"; break; case fLckdErr: errText = "\PFile locked"; break; case fnOpnErr: errText = "\PFile not open"; break; case opWrErr: errText = "\PFile already open for writing"; break; case wrPermErr: errText = "\PPermission doesn't allow writing"; break; default: errText = "\P"; break; } #define alertboxid 258 ParamText(errWhy,errText,0L,0L); StopAlert(alertboxid, 0L); return TRUE; } } /* * Error codes that can be used as parameters to Err2 (below) */ enum { errHeaderCRC, /* file header CRC mismatch */ errFileCRC, /* file data/rsrc CRC mismatch */ errNotPackit, /* unrecognized PACKIT format */ errBadPrint, /* attempt to Print files from Finder */ errCompress /* internal error: out of room for compression */ } /* Report an error to the user. This function is used for error messages that aren't directly caused by I/O errors ############################ Err2 ############################## */ Err2(theErr) int theErr; { switch (theErr) { case errHeaderCRC: ParamText("\PError: file header CRC mismatch",0L,0L,0L); break; case errFileCRC: ParamText("\PError: file data/resource CRC mismatch",0L,0L,0L); break; case errNotPackit: ParamText("\PThis is not a Packit-I or Packit-II file.",0L,0L,0L); break; case errBadPrint: ParamText("\PPrinting packed files is not supported.",0L,0L,0L); break; case errCompress: ParamText("\POut of bits for character codes.",0L,0L,0L); break; } StopAlert(alertboxid+1,0L); } /* Open a file's resource fork ############################## Open_Rsrc ############################## */ Open_Rsrc(fName, vRefNum, perm, ref_num) StringPtr fName; int vRefNum, perm; int *ref_num; { ParamBlockRec ParamB; int ret_val; ParamB.ioParam.ioCompletion = 0L; ParamB.ioParam.ioNamePtr = fName; ParamB.ioParam.ioVRefNum = vRefNum; ParamB.ioParam.ioVersNum = 0; ParamB.ioParam.ioPermssn = perm; ParamB.ioParam.ioMisc = 0L; ret_val = PBOpenRF(&ParamB,0); *ref_num = ParamB.ioParam.ioRefNum; return ret_val; } /* Compare two strings that are four bytes long. This allows us to toss out the Strings library, since Unpit was pulling it in just for strncmp . . . ############################## Str_Cmp_4 ############################## */ Boolean str_cmp_4(str1, str2) char *str1, *str2; { int i; for (i = 0; i < 4; i++) if (str1[i] != str2[i]) return FALSE; return TRUE; } /* Read a byte from the file denoted by pit_ref_num, using buffered reads. Note: this routine is bypassed for speed when unpacking Packit-I files. ############################## Read_Byte ############################## */ #define BSIZE 4096 int pit_ref_num; /* Mac filesystem reference number */ long pit_bytes_left; /* # of bytes to be read from disk */ unsigned char pit_buffer[BSIZE]; /* Buffer for bytes read from disk */ int pit_index, pit_max_index; /* For tracking the buffer's state */ #define read_byte(ARG1) \ ((pit_index < pit_max_index) ? pit_buffer[pit_index++] : do_read(ARG1)) do_read(eflag) Boolean *eflag; { long Count; unsigned char Ch; if (pit_index < pit_max_index) return pit_buffer[pit_index++]; else if (pit_bytes_left == 0L) { Err(ioErr,errReading); *eflag = TRUE; return 0; } else { Count = (pit_bytes_left < BSIZE) ? pit_bytes_left : BSIZE; pit_index = 0; pit_max_index = (int) Count; pit_bytes_left -= Count; if (Err(FSRead(pit_ref_num, &Count, &pit_buffer), errReading)) { *eflag = TRUE; return 0; } else return pit_buffer[pit_index++]; } } /* Update the value of CRC to include the bytes in a random chunk of memory. ############################### Upd_Crc ############################### */ crctype upd_crc(crc, theBytes, byteCount) register crctype crc; unsigned char *theBytes; long byteCount; { register int i; register long n; for (n = 0; n < byteCount; n++) { crc = crc ^ ((int)theBytes[n] << 8); for (i = 0; i < 8; i++) if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; else crc <<= 1; } return(crc & 0xffff); } /* Put up a dialog box that allows the user to select a file to unpack. ################################ UnPack ############################### */ unpack() { SFReply PitRecord; SFTypeList PitTypes; Point where; ParamBlockRec ParamB; where.h = GET_FILE_X; where.v = GET_FILE_Y; PitTypes[0] = 'PIT '; PitTypes[1] = 'TEXT'; SFGetFile(where, "", 0L, Filter ? 2 : -1, PitTypes, 0L, &PitRecord); if (PitRecord.good) { ParamB.fileParam.ioCompletion = 0L; ParamB.fileParam.ioNamePtr = PitRecord.fName; ParamB.fileParam.ioVRefNum = PitRecord.vRefNum; ParamB.fileParam.ioFVersNum = 0; ParamB.fileParam.ioFDirIndex = 0; /* use NamePtr/VRefNum */ PBGetFInfo(&ParamB,0); pit_bytes_left = ParamB.fileParam.ioFlLgLen; if (pit_bytes_left < 4L) Err2(errNotPackit); /* more meaningful than "I/O Error..." */ else { pit_index = 0; pit_max_index = -1; FSOpen(PitRecord.fName,PitRecord.vRefNum,&pit_ref_num); unpit(); FSClose(pit_ref_num); } } } /* Put up a dialog box that allows the user to delete a file. ################################ Delete ############################### */ #define deleteconfirm 260 delete() { SFReply DelRecord; Point where; where.h = GET_FILE_X; where.v = GET_FILE_Y; SFGetFile(where, "", 0L, -1, 0L, 0L, &DelRecord); if (DelRecord.good) { ParamText(DelRecord.fName, 0L, 0L, 0L); if (CautionAlert(deleteconfirm, 0L) == 1) /* the OK button */ Err(FSDelete(DelRecord.fName, DelRecord.vRefNum),errDelete); } } /* Unpack a Packit-I or Packit-II files into its component files. ################################ UnPit ################################# This routine assumes that the input file has already been opened and that the input file can be read one byte at a time using read_byte(). Errors that occur during the attempted unpacking will (hopefully) be reported to the user within UnPit or functions it calls; in any case, the caller should close the input file when UnPit returns. */ #define statusid 263 unpit() { char temp[4]; /* used when searching for Packit identifier */ SFReply OutRecord; /* names and volume numbers of output files */ Point where; /* where to locate the SFPutFile dialog box */ Boolean saveOutput; /* to allow extracting pieces of a .PIT file */ ParamBlockRec ParamB; /* needed to make low-level filesystem calls */ int out_ref_num, i; crctype data_crc, crc; Boolean eflag = FALSE; DialogPtr theDialog = 0L; while(1) { for (i = 0; i < 4; i++) { /* PACKIT signature */ temp[i] = read_byte(&eflag); if (eflag) return; } if (str_cmp_4(temp, "PMag") || str_cmp_4(temp, "PMa4")) { if (temp[3] == '4') { freenode = nodelist; read_tree(&eflag); if (eflag) return; decode = 1; } else decode = 0; data_crc = read_hdr(&eflag); if (eflag) return; crc = getcrc(&eflag); if (eflag) return; if (crc != data_crc) { Err2(errHeaderCRC); return; } /* Get name of output file */ where.h = PUT_FILE_X; where.v = PUT_FILE_Y; SFPutFile(where, "\PSave as:", &hdr[H_NLENOFF], 0L, &OutRecord); if (OutRecord.good) { if ((i = Create(OutRecord.fName,OutRecord.vRefNum,'????','????')) == dupFNErr) { /* user said it was OK to replace...delete! */ FSDelete(OutRecord.fName, OutRecord.vRefNum); i = Create(OutRecord.fName,OutRecord.vRefNum,'????','????'); } if (Err(i,errCreateFile)) return; saveOutput = TRUE; if (Err(FSOpen(OutRecord.fName,OutRecord.vRefNum,&out_ref_num), errOpenDataFk)) return; } else { saveOutput = FALSE; out_ref_num = 0; } /* Give visual indication that file is being unpacked */ theDialog = GetNewDialog(statusid, 0L, -1L); ParamText(0L,0L,saveOutput ? "\PUnpack":"\PSkipp",OutRecord.fName); DrawDialog(theDialog); /* First, take care of writing the file's data fork */ data_crc = write_fork(saveOutput, out_ref_num, datalen, (crctype) 0, &eflag); if (saveOutput) FSClose(out_ref_num); if (eflag) goto error; /* Next, take care of writing the file's resource fork. */ if (saveOutput && (rsrclen > 0)) { if (Err(Open_Rsrc(OutRecord.fName,OutRecord.vRefNum,fsWrPerm, &out_ref_num), errOpenRsrcFk)) goto error; } else out_ref_num = 0; data_crc = write_fork(saveOutput, out_ref_num, rsrclen, data_crc, &eflag); if (saveOutput) FSClose(out_ref_num); if (eflag) goto error; /* Check the CRC to make sure the file was unpacked intact */ crc = getcrc(&eflag); if (eflag || (crc != data_crc)) { Err2(errFileCRC); if (saveOutput) FSDelete(OutRecord.fName, OutRecord.vRefNum); goto error; } /* Finally, set the filesystem and Finder info for the file */ if (saveOutput) { ParamB.fileParam.ioCompletion = 0L; ParamB.fileParam.ioNamePtr = OutRecord.fName; ParamB.fileParam.ioVRefNum = OutRecord.vRefNum; ParamB.fileParam.ioFVersNum = 0; ParamB.fileParam.ioFDirIndex = 0; /* use NamePtr/VRefNum */ if (Err(PBGetFInfo(&ParamB,0),errGetFInfo)) goto error; ParamB.fileParam.ioFlFndrInfo.fdType = get4(&hdr[H_TYPEOFF]); ParamB.fileParam.ioFlFndrInfo.fdCreator = get4(&hdr[H_AUTHOFF]); ParamB.fileParam.ioFlFndrInfo.fdFlags = (hdr[H_FLAGOFF] << 8) | hdr[H_FLAGOFF+1]; ParamB.fileParam.ioFlCrDat = get4(&hdr[H_CTIMOFF]); ParamB.fileParam.ioFlMdDat = get4(&hdr[H_MTIMOFF]); /* turn INITed flag off since Finder hasn't seen file yet */ ParamB.fileParam.ioFlFndrInfo.fdFlags &= (0xFFFF ^ INITED); if (Err(PBSetFInfo(&ParamB,0),errSetFInfo)) goto error; /* lock file if Packit header says to do so */ if (hdr[H_LOCKOFF+1] & 1) /* don't look at other bits */ if (Err(SetFLock(OutRecord.fName,OutRecord.vRefNum),errLock)) goto error; /* finally, flush volume to make sure that data is safe */ FlushVol(0L,OutRecord.vRefNum); --- end of part 1 ---