/* * Compress "srcLen" bytes from "pStraw" to "fp". */ NuError Nu_CompressDeflate(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, ulong srcLen, ulong* pDstLen, ushort* pCrc) { NuError err = kNuErrNone; z_stream zstream; int zerr; Bytef* outbuf = nil; Assert(pArchive != nil); Assert(pStraw != nil); Assert(fp != nil); Assert(srcLen > 0); Assert(pDstLen != nil); Assert(pCrc != nil); err = Nu_AllocCompressionBufferIFN(pArchive); if (err != kNuErrNone) return err; /* allocate a similarly-sized buffer for the output */ outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); BailAlloc(outbuf); /* * Initialize the zlib stream. */ zstream.zalloc = Nu_zalloc; zstream.zfree = Nu_zfree; zstream.opaque = pArchive; zstream.next_in = nil; zstream.avail_in = 0; zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; zstream.data_type = Z_UNKNOWN; zerr = deflateInit(&zstream, kNuDeflateLevel); if (zerr != Z_OK) { err = kNuErrInternal; if (zerr == Z_VERSION_ERROR) { Nu_ReportError(NU_BLOB, err, "installed zlib is not compatible with linked version (%s)", ZLIB_VERSION); } else { Nu_ReportError(NU_BLOB, err, "call to deflateInit failed (zerr=%d)", zerr); } goto bail; } /* * Loop while we have data. */ do { ulong getSize; int flush; /* should be able to read a full buffer every time */ if (zstream.avail_in == 0 && srcLen) { getSize = (srcLen > kNuGenCompBufSize) ? kNuGenCompBufSize : srcLen; DBUG(("+++ reading %ld bytes\n", getSize)); err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getSize); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "deflate read failed"); goto z_bail; } srcLen -= getSize; *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getSize); zstream.next_in = pArchive->compBuf; zstream.avail_in = getSize; } if (srcLen == 0) flush = Z_FINISH; /* tell zlib that we're done */ else flush = Z_NO_FLUSH; /* more to come! */ zerr = deflate(&zstream, flush); if (zerr != Z_OK && zerr != Z_STREAM_END) { err = kNuErrInternal; Nu_ReportError(NU_BLOB, err, "zlib deflate call failed (zerr=%d)", zerr); goto z_bail; } /* write when we're full or when we're done */ if (zstream.avail_out == 0 || (zerr == Z_STREAM_END && zstream.avail_out != kNuGenCompBufSize)) { DBUG(("+++ writing %d bytes\n", zstream.next_out - outbuf)); err = Nu_FWrite(fp, outbuf, zstream.next_out - outbuf); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "fwrite failed in deflate"); goto z_bail; } zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; } } while (zerr == Z_OK); Assert(zerr == Z_STREAM_END); /* other errors should've been caught */ *pDstLen = zstream.total_out; z_bail: deflateEnd(&zstream); /* free up any allocated structures */ bail: if (outbuf != nil) free(outbuf); return err; }
/* * Skip past some or all of the thread data in the archive. For file * archives, we scan all the threads, but for streaming archives we only * want to scan up to the filename thread. (If the filename thread comes * after one of the data threads, we have a problem!) * * The tricky part here is that we don't want to skip over a filename * thread. We actually want to read it in, so that we have something to * show to the application. (Someday I'll get AndyN for putting me * through this...) */ NuError Nu_ScanThreads(NuArchive* pArchive, NuRecord* pRecord, long numThreads) { NuError err = kNuErrNone; NuThread* pThread; FILE* fp; Assert(pArchive != NULL); Assert(pRecord != NULL); fp = pArchive->archiveFp; Assert(numThreads <= (long)pRecord->recTotalThreads); pThread = pRecord->pThreads; while (numThreads--) { if (pRecord->threadFilenameMOR == NULL && NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == kNuThreadIDFilename) { /* it's the first filename thread, read the whole thing */ if (pThread->thCompThreadEOF > kNuReasonableFilenameLen) { err = kNuErrBadRecord; Nu_ReportError(NU_BLOB, err, "Bad thread filename len (%u)", pThread->thCompThreadEOF); goto bail; } pRecord->threadFilenameMOR = Nu_Malloc(pArchive, pThread->thCompThreadEOF +1); BailAlloc(pRecord->threadFilenameMOR); /* note there is no CRC on a filename thread */ (void) Nu_ReadBytes(pArchive, fp, pRecord->threadFilenameMOR, pThread->thCompThreadEOF); if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "Failed reading filename thread"); goto bail; } /* null-terminate on the actual len, not the buffer len */ pRecord->threadFilenameMOR[pThread->thThreadEOF] = '\0'; Nu_StripHiIfAllSet(pRecord->threadFilenameMOR); /* prefer this one over the record one, but only one should exist */ if (pRecord->filenameMOR != NULL) { DBUG(("--- HEY: got record filename and thread filename\n")); } pRecord->filenameMOR = pRecord->threadFilenameMOR; } else { /* not a filename (or not first filename), skip past it */ err = Nu_SeekArchive(pArchive, pArchive->archiveFp, pThread->thCompThreadEOF, SEEK_CUR); BailError(err); } pThread++; } /* * Should've had one by now. Supposedly, older versions of ShrinkIt * wouldn't prompt for a disk image name on DOS 3.3 volumes, so you'd * end up with a disk image that had no name attached. This will tend * to confuse things, so we go ahead and give it a name. */ if (pRecord->filenameMOR == NULL) { DBUG(("+++ no filename found, using default record name\n")); pRecord->filenameMOR = kNuDefaultRecordName; } pArchive->currentOffset += pRecord->totalCompLength; if (!Nu_IsStreaming(pArchive)) { Assert(pArchive->currentOffset == ftell(pArchive->archiveFp)); } bail: return err; }
/* * Expand from "infp" to "pFunnel". */ NuError Nu_ExpandDeflate(NuArchive* pArchive, const NuRecord* pRecord, const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, ushort* pCrc) { NuError err = kNuErrNone; z_stream zstream; int zerr; ulong compRemaining; Bytef* outbuf; Assert(pArchive != nil); Assert(pThread != nil); Assert(infp != nil); Assert(pFunnel != nil); err = Nu_AllocCompressionBufferIFN(pArchive); if (err != kNuErrNone) return err; /* allocate a similarly-sized buffer for the output */ outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); BailAlloc(outbuf); compRemaining = pThread->thCompThreadEOF; /* * Initialize the zlib stream. */ zstream.zalloc = Nu_zalloc; zstream.zfree = Nu_zfree; zstream.opaque = pArchive; zstream.next_in = nil; zstream.avail_in = 0; zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; zstream.data_type = Z_UNKNOWN; zerr = inflateInit(&zstream); if (zerr != Z_OK) { err = kNuErrInternal; if (zerr == Z_VERSION_ERROR) { Nu_ReportError(NU_BLOB, err, "installed zlib is not compatible with linked version (%s)", ZLIB_VERSION); } else { Nu_ReportError(NU_BLOB, err, "call to inflateInit failed (zerr=%d)", zerr); } goto bail; } /* * Loop while we have data. */ do { ulong getSize; /* read as much as we can */ if (zstream.avail_in == 0) { getSize = (compRemaining > kNuGenCompBufSize) ? kNuGenCompBufSize : compRemaining; DBUG(("+++ reading %ld bytes (%ld left)\n", getSize, compRemaining)); err = Nu_FRead(infp, pArchive->compBuf, getSize); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "inflate read failed"); goto z_bail; } compRemaining -= getSize; zstream.next_in = pArchive->compBuf; zstream.avail_in = getSize; } /* uncompress the data */ zerr = inflate(&zstream, Z_NO_FLUSH); if (zerr != Z_OK && zerr != Z_STREAM_END) { err = kNuErrInternal; Nu_ReportError(NU_BLOB, err, "zlib inflate call failed (zerr=%d)", zerr); goto z_bail; } /* write every time there's anything (buffer will usually be full) */ if (zstream.avail_out != kNuGenCompBufSize) { DBUG(("+++ writing %d bytes\n", zstream.next_out - outbuf)); err = Nu_FunnelWrite(pArchive, pFunnel, outbuf, zstream.next_out - outbuf); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "write failed in inflate"); goto z_bail; } if (pCrc != nil) *pCrc = Nu_CalcCRC16(*pCrc, outbuf, zstream.next_out - outbuf); zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; } } while (zerr == Z_OK); Assert(zerr == Z_STREAM_END); /* other errors should've been caught */ if (zstream.total_out != pThread->actualThreadEOF) { err = kNuErrBadData; Nu_ReportError(NU_BLOB, err, "size mismatch on inflated file (%ld vs %ld)", zstream.total_out, pThread->actualThreadEOF); goto z_bail; } z_bail: inflateEnd(&zstream); /* free up any allocated structures */ bail: if (outbuf != nil) free(outbuf); return err; }
/* * Read the threads from the current archive file position. * * The storage for the threads is allocated here, in one block. We could * have used a linked list like NuLib, but that doesn't really provide any * benefit for us, and adds complexity. */ NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, uint16_t* pCrc) { NuError err = kNuErrNone; NuThread* pThread; long count; Boolean needFakeData, needFakeRsrc; needFakeData = true; needFakeRsrc = (pRecord->recStorageType == kNuStorageExtended); Assert(pArchive != NULL); Assert(pRecord != NULL); Assert(pCrc != NULL); if (!pRecord->recTotalThreads) { /* not sure if this is reasonable, but we can handle it */ DBUG(("--- WEIRD: no threads in the record?\n")); goto bail; } pRecord->pThreads = Nu_Malloc(pArchive, pRecord->recTotalThreads * sizeof(NuThread)); BailAlloc(pRecord->pThreads); count = pRecord->recTotalThreads; pThread = pRecord->pThreads; while (count--) { err = Nu_ReadThreadHeader(pArchive, pThread, pCrc); BailError(err); if (pThread->thThreadClass == kNuThreadClassData) { if (pThread->thThreadKind == kNuThreadKindDataFork) { needFakeData = false; } else if (pThread->thThreadKind == kNuThreadKindRsrcFork) { needFakeRsrc = false; } else if (pThread->thThreadKind == kNuThreadKindDiskImage) { /* needFakeRsrc shouldn't be set, but clear anyway */ needFakeData = needFakeRsrc = false; } } /* * Some versions of ShrinkIt write an invalid thThreadEOF for disks, * so we have to figure out what it's supposed to be. */ if (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == kNuThreadIDDiskImage) { if (pRecord->recStorageType <= 13) { /* supposed to be block size, but SHK v3.0.1 stored it wrong */ pThread->actualThreadEOF = pRecord->recExtraType * 512; } else if (pRecord->recStorageType == 256 && pRecord->recExtraType == 280 && pRecord->recFileSysID == kNuFileSysDOS33) { /* * Fix for less-common ShrinkIt problem: looks like an old * version of GS/ShrinkIt used 256 as the block size when * compressing DOS 3.3 images from 5.25" disks. If that * appears to be the case here, crank up the block size. */ DBUG(("--- no such thing as a 70K disk image!\n")); pThread->actualThreadEOF = pRecord->recExtraType * 512; } else { pThread->actualThreadEOF = pRecord->recExtraType * pRecord->recStorageType; } } else { pThread->actualThreadEOF = pThread->thThreadEOF; } pThread->used = false; pThread++; } /* * If "mask threadless" is set, create "fake" threads with empty * data and resource forks as needed. */ if ((needFakeData || needFakeRsrc) && pArchive->valMaskDataless) { int firstNewThread = pRecord->recTotalThreads; if (needFakeData) { pRecord->recTotalThreads++; pRecord->fakeThreads++; } if (needFakeRsrc) { pRecord->recTotalThreads++; pRecord->fakeThreads++; } pRecord->pThreads = Nu_Realloc(pArchive, pRecord->pThreads, pRecord->recTotalThreads * sizeof(NuThread)); BailAlloc(pRecord->pThreads); pThread = pRecord->pThreads + firstNewThread; if (needFakeData) { pThread->thThreadClass = kNuThreadClassData; pThread->thThreadFormat = kNuThreadFormatUncompressed; pThread->thThreadKind = kNuThreadKindDataFork; pThread->thThreadCRC = kNuInitialThreadCRC; pThread->thThreadEOF = 0; pThread->thCompThreadEOF = 0; pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); pThread->actualThreadEOF = 0; pThread->fileOffset = -99999999; pThread->used = false; pThread++; } if (needFakeRsrc) { pThread->thThreadClass = kNuThreadClassData; pThread->thThreadFormat = kNuThreadFormatUncompressed; pThread->thThreadKind = kNuThreadKindRsrcFork; pThread->thThreadCRC = kNuInitialThreadCRC; pThread->thThreadEOF = 0; pThread->thCompThreadEOF = 0; pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); pThread->actualThreadEOF = 0; pThread->fileOffset = -99999999; pThread->used = false; } } bail: return err; }