/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ // Heavily based on Unarj 2.65 /* UNARJ.C, UNARJ, R JUNG, 06/05/02 * Main Extractor routine * Copyright (c) 1991-2002 by ARJ Software, Inc. All rights reserved. * * This code may be freely used in programs that are NOT ARJ archivers * (both compress and extract ARJ archives). * * If you wish to distribute a modified version of this program, you * MUST indicate that it is a modified version both in the program and * source code. * * We are holding the copyright on the source code, so please do not * delete our name from the program files or from the documentation. * * We wish to give credit to Haruhiko Okumura for providing the * basic ideas for ARJ and UNARJ in his program AR. Please note * that UNARJ is significantly different from AR from an archive * structural point of view. * */ #include "common/scummsys.h" #include "common/util.h" #include "common/unarj.h" namespace Common { static uint32 CRCtable[256]; static void InitCRC(void) { const uint32 poly = 0xEDB88320; int i, j; uint32 n; for (i = 0; i < 256; i++) { n = i; for (j = 0; j < 8; j++) n = (n & 1) ? ((n >> 1) ^ poly) : (n >> 1); CRCtable[i] = n; } } static uint32 GetCRC(byte *data, int len) { uint32 CRC = 0xFFFFFFFF; int i; for (i = 0; i < len; i++) CRC = (CRC >> 8) ^ CRCtable[(CRC ^ data[i]) & 0xFF]; return CRC ^ 0xFFFFFFFF; } ArjFile::ArjFile() : _uncompressedData(NULL) { InitCRC(); _isOpen = false; _fallBack = false; } ArjFile::~ArjFile() { close(); for (uint i = 0; i < _headers.size(); i++) delete _headers[i]; _headers.clear(); _fileMap.clear(); _archMap.clear(); } void ArjFile::registerArchive(const String &filename) { int32 first_hdr_pos; ArjHeader *header; if (!_currArchive.open(filename)) return; first_hdr_pos = findHeader(); if (first_hdr_pos < 0) { warning("ArjFile::registerArchive(): Could not find a valid header"); return; } _currArchive.seek(first_hdr_pos, SEEK_SET); if (readHeader() == NULL) return; while ((header = readHeader()) != NULL) { _headers.push_back(header); _currArchive.seek(header->compSize, SEEK_CUR); _fileMap[header->filename] = _headers.size() - 1; _archMap[header->filename] = filename; } _currArchive.close(); debug(0, "ArjFile::registerArchive(%s): Located %d files", filename.c_str(), _headers.size()); } int32 ArjFile::findHeader(void) { long arcpos, lastpos; int c; byte header[HEADERSIZE_MAX]; uint32 crc; uint16 headersize; arcpos = _currArchive.pos(); _currArchive.seek(0L, SEEK_END); lastpos = _currArchive.pos() - 2; if (lastpos > MAXSFX) lastpos = MAXSFX; for ( ; arcpos < lastpos; arcpos++) { _currArchive.seek(arcpos, SEEK_SET); c = _currArchive.readByte(); while (arcpos < lastpos) { if (c != HEADER_ID_LO) // low order first c = _currArchive.readByte(); else if ((c = _currArchive.readByte()) == HEADER_ID_HI) break; arcpos++; } if (arcpos >= lastpos) break; if ((headersize = _currArchive.readUint16LE()) <= HEADERSIZE_MAX) { _currArchive.read(header, headersize); crc = GetCRC(header, headersize); if (crc == _currArchive.readUint32LE()) { _currArchive.seek(arcpos, SEEK_SET); return arcpos; } } } return -1; // could not find a valid header } ArjHeader *ArjFile::readHeader() { ArjHeader header; ArjHeader *head; byte headData[HEADERSIZE_MAX]; header.id = _currArchive.readUint16LE(); if (header.id != HEADER_ID) { warning("ArjFile::readHeader(): Bad header ID (%x)", header.id); return NULL; } header.headerSize = _currArchive.readUint16LE(); if (header.headerSize == 0) return NULL; // end of archive if (header.headerSize > HEADERSIZE_MAX) { warning("ArjFile::readHeader(): Bad header"); return NULL; } int rSize = _currArchive.read(headData, header.headerSize); MemoryReadStream readS(headData, rSize); header.headerCrc = _currArchive.readUint32LE(); if (GetCRC(headData, header.headerSize) != header.headerCrc) { warning("ArjFile::readHeader(): Bad header CRC"); return NULL; } header.firstHdrSize = readS.readByte(); header.nbr = readS.readByte(); header.xNbr = readS.readByte(); header.hostOs = readS.readByte(); header.flags = readS.readByte(); header.method = readS.readByte(); header.fileType = readS.readByte(); (void)readS.readByte(); header.timeStamp = readS.readUint32LE(); header.compSize = readS.readSint32LE(); header.origSize = readS.readSint32LE(); header.fileCRC = readS.readUint32LE(); header.entryPos = readS.readUint16LE(); header.fileMode = readS.readUint16LE(); header.hostData = readS.readUint16LE(); if (header.origSize < 0 || header.compSize < 0) { warning("ArjFile::readHeader(): Wrong file size"); return NULL; } strncpy(header.filename, (const char *)&headData[header.firstHdrSize], FNAME_MAX); strncpy(header.comment, (const char *)&headData[header.firstHdrSize + strlen(header.filename) + 1], COMMENT_MAX); /* if extheadersize == 0 then no CRC */ /* otherwise read extheader data and read 4 bytes for CRC */ while ((header.extHeaderSize = _currArchive.readUint16LE()) != 0) _currArchive.seek((long)(header.extHeaderSize + 4), SEEK_CUR); header.pos = _currArchive.pos(); head = new ArjHeader(header); return head; } bool ArjFile::open(const Common::String &filename) { if (_isOpen) error("Attempt to open another instance of archive"); _isOpen = false; if (_fallBack) { _currArchive.open(filename); if (_currArchive.isOpen()) { _isOpen = true; _uncompressed = &_currArchive; return true; } } if (!_fileMap.contains(filename)) return false; _isOpen = true; ArjHeader *hdr = _headers[_fileMap[filename]]; _compsize = hdr->compSize; _origsize = hdr->origSize; // FIXME: This hotfix prevents Drascula from leaking memory. // As far as sanity checks go this is not bad, but the engine should be fixed. if (_uncompressedData) free(_uncompressedData); _uncompressedData = (byte *)malloc(_origsize); _outstream = new MemoryWriteStream(_uncompressedData, _origsize); _currArchive.open(_archMap[filename]); _currArchive.seek(hdr->pos, SEEK_SET); if (hdr->method == 0) { // store _currArchive.read(_uncompressedData, _origsize); } else { _compressedData = (byte *)malloc(_compsize); _currArchive.read(_compressedData, _compsize); _compressed = new MemoryReadStream(_compressedData, _compsize); if (hdr->method == 1 || hdr->method == 2 || hdr->method == 3) decode(); else if (hdr->method == 4) decode_f(); delete _compressed; free(_compressedData); } _currArchive.close(); delete _outstream; _outstream = NULL; _uncompressed = new MemoryReadStream(_uncompressedData, _origsize); return true; } void ArjFile::close() { if (!_isOpen) return; _isOpen = false; if (_fallBack) { _currArchive.close(); return; } else { delete _uncompressed; } _uncompressed = NULL; free(_uncompressedData); _uncompressedData = NULL; } uint32 ArjFile::read(void *dataPtr, uint32 dataSize) { return _uncompressed->read(dataPtr, dataSize); } bool ArjFile::eos() { return _uncompressed->eos(); } uint32 ArjFile::pos() { return _uncompressed->pos(); } uint32 ArjFile::size() { return _uncompressed->size(); } void ArjFile::seek(int32 offset, int whence) { _uncompressed->seek(offset, whence); } void ArjFile::init_getbits() { _bitbuf = 0; _subbitbuf = 0; _bitcount = 0; fillbuf(2 * CHAR_BIT); } void ArjFile::fillbuf(int n) { // Shift bitbuf n bits left, read n bits _bitbuf = (_bitbuf << n) & 0xFFFF; /* lose the first n bits */ while (n > _bitcount) { _bitbuf |= _subbitbuf << (n -= _bitcount); if (_compsize != 0) { _compsize--; _subbitbuf = _compressed->readByte(); } else _subbitbuf = 0; _bitcount = CHAR_BIT; } _bitbuf |= _subbitbuf >> (_bitcount -= n); } uint16 ArjFile::getbits(int n) { uint16 x; x = _bitbuf >> (2 * CHAR_BIT - n); fillbuf(n); return x; } /* Huffman decode routines */ void ArjFile::make_table(int nchar, byte *bitlen, int tablebits, uint16 *table, int tablesize) { uint16 count[17], weight[17], start[18], *p; uint i, k, len, ch, jutbits, avail, nextcode, mask; for (i = 1; i <= 16; i++) count[i] = 0; for (i = 0; (int)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] != (uint16) (1 << 16)) error("ArjFile::make_table(): bad file data"); jutbits = 16 - tablebits; for (i = 1; (int)i <= tablebits; i++) { start[i] >>= jutbits; weight[i] = 1 << (tablebits - i); } while (i <= 16) { weight[i] = 1 << (16 - i); i++; } i = start[tablebits + 1] >> jutbits; if (i != (uint16) (1 << 16)) { k = 1 << tablebits; while (i != k) table[i++] = 0; } avail = nchar; mask = 1 << (15 - tablebits); for (ch = 0; (int)ch < nchar; ch++) { if ((len = bitlen[ch]) == 0) continue; k = start[len]; nextcode = k + weight[len]; if ((int)len <= tablebits) { if (nextcode > (uint)tablesize) error("ArjFile::make_table(): bad file data"); for (i = start[len]; i < nextcode; i++) table[i] = ch; } else { p = &table[k >> jutbits]; i = len - tablebits; while (i != 0) { if (*p == 0) { _right[avail] = _left[avail] = 0; *p = avail++; } if (k & mask) p = &_right[*p]; else p = &_left[*p]; k <<= 1; i--; } *p = ch; } start[len] = nextcode; } } void ArjFile::read_pt_len(int nn, int nbit, int i_special) { int i, n; int16 c; uint16 mask; n = getbits(nbit); if (n == 0) { c = getbits(nbit); for (i = 0; i < nn; i++) _pt_len[i] = 0; for (i = 0; i < 256; i++) _pt_table[i] = c; } else { i = 0; while (i < n) { c = _bitbuf >> (13); if (c == 7) { mask = 1 << (12); while (mask & _bitbuf) { mask >>= 1; c++; } } fillbuf((c < 7) ? 3 : (int)(c - 3)); _pt_len[i++] = (byte)c; if (i == i_special) { c = getbits(2); while (--c >= 0) _pt_len[i++] = 0; } } while (i < nn) _pt_len[i++] = 0; make_table(nn, _pt_len, 8, _pt_table, PTABLESIZE); // replaced sizeof } } void ArjFile::read_c_len() { int16 i, c, n; uint16 mask; n = getbits(CBIT); if (n == 0) { c = getbits(CBIT); for (i = 0; i < NC; i++) _c_len[i] = 0; for (i = 0; i < CTABLESIZE; i++) _c_table[i] = c; } else { i = 0; while (i < n) { c = _pt_table[_bitbuf >> (8)]; if (c >= NT) { mask = 1 << (7); do { if (_bitbuf & mask) c = _right[c]; else c = _left[c]; mask >>= 1; } while (c >= NT); } fillbuf((int)(_pt_len[c])); if (c <= 2) { if (c == 0) c = 1; else if (c == 1) c = getbits(4) + 3; else c = getbits(CBIT) + 20; while (--c >= 0) _c_len[i++] = 0; } else _c_len[i++] = (byte)(c - 2); } while (i < NC) _c_len[i++] = 0; make_table(NC, _c_len, 12, _c_table, CTABLESIZE); // replaced sizeof } } uint16 ArjFile::decode_c() { uint16 j, mask; if (_blocksize == 0) { _blocksize = getbits(16); read_pt_len(NT, TBIT, 3); read_c_len(); read_pt_len(NP, PBIT, -1); } _blocksize--; j = _c_table[_bitbuf >> 4]; if (j >= NC) { mask = 1 << (3); do { if (_bitbuf & mask) j = _right[j]; else j = _left[j]; mask >>= 1; } while (j >= NC); } fillbuf((int)(_c_len[j])); return j; } uint16 ArjFile::decode_p() { uint16 j, mask; j = _pt_table[_bitbuf >> (8)]; if (j >= NP) { mask = 1 << (7); do { if (_bitbuf & mask) j = _right[j]; else j = _left[j]; mask >>= 1; } while (j >= NP); } fillbuf((int)(_pt_len[j])); if (j != 0) { j--; j = (1 << j) + getbits((int)j); } return j; } void ArjFile::decode_start() { _blocksize = 0; init_getbits(); } void ArjFile::decode() { int16 i; int16 j; int16 c; int16 r; int32 count; decode_start(); count = 0; r = 0; while (count < _origsize) { if ((c = decode_c()) <= ARJ_UCHAR_MAX) { _text[r] = (byte) c; count++; if (++r >= DDICSIZ) { r = 0; _outstream->write(_text, DDICSIZ); } } else { j = c - (ARJ_UCHAR_MAX + 1 - THRESHOLD); count += j; i = decode_p(); if ((i = r - i - 1) < 0) i += DDICSIZ; if (r > i && r < DDICSIZ - MAXMATCH - 1) { while (--j >= 0) _text[r++] = _text[i++]; } else { while (--j >= 0) { _text[r] = _text[i]; if (++r >= DDICSIZ) { r = 0; _outstream->write(_text, DDICSIZ); } if (++i >= DDICSIZ) i = 0; } } } } if (r != 0) _outstream->write(_text, r); } /* Macros */ #define BFIL {_getbuf|=_bitbuf>>_getlen;fillbuf(CODE_BIT-_getlen);_getlen=CODE_BIT;} #define GETBIT(c) {if(_getlen<=0)BFIL c=(_getbuf&0x8000)!=0;_getbuf<<=1;_getlen--;} #define BPUL(l) {_getbuf<<=l;_getlen-=l;} #define GETBITS(c,l) {if(_getlen>(CODE_BIT-l);BPUL(l)} int16 ArjFile::decode_ptr() { int16 c = 0; int16 width; int16 plus; int16 pwr; plus = 0; pwr = 1 << (STRTP); for (width = (STRTP); width < (STOPP); width++) { GETBIT(c); if (c == 0) break; plus += pwr; pwr <<= 1; } if (width != 0) GETBITS(c, width); c += plus; return c; } int16 ArjFile::decode_len() { int16 c = 0; int16 width; int16 plus; int16 pwr; plus = 0; pwr = 1 << (STRTL); for (width = (STRTL); width < (STOPL); width++) { GETBIT(c); if (c == 0) break; plus += pwr; pwr <<= 1; } if (width != 0) GETBITS(c, width); c += plus; return c; } void ArjFile::decode_f() { int16 i; int16 j; int16 c; int16 r; int16 pos1; int32 count; init_getbits(); _getlen = _getbuf = 0; count = 0; r = 0; while (count < _origsize) { c = decode_len(); if (c == 0) { GETBITS(c, CHAR_BIT); _text[r] = (byte)c; count++; if (++r >= DDICSIZ) { r = 0; _outstream->write(_text, DDICSIZ); } } else { j = c - 1 + THRESHOLD; count += j; pos1 = decode_ptr(); if ((i = r - pos1 - 1) < 0) i += DDICSIZ; while (j-- > 0) { _text[r] = _text[i]; if (++r >= DDICSIZ) { r = 0; _outstream->write(_text, DDICSIZ); } if (++i >= DDICSIZ) i = 0; } } } if (r != 0) _outstream->write(_text, r); } } // End of namespace Common