mirror of
https://github.com/scummvm/scummvm-tools.git
synced 2026-05-21 05:40:44 +00:00
03e9e87b61
svn-id: r42296
318 lines
8.9 KiB
C++
318 lines
8.9 KiB
C++
/* 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$
|
|
*
|
|
* This is a utility to unpack Delphine's Cinematique engine's archive files.
|
|
* Should at least work with Future Wars and Operation Stealth.
|
|
* Supports using Operation Stealth's 'vol.cnf' file as input for selecting
|
|
* which archive files to unpack.
|
|
*
|
|
* Note that this isn't polished code so caveat emptor.
|
|
*
|
|
* FIXME: Make this code endian safe.
|
|
* FIXME: Recognize "vol.cnf" also when given something else than simply "vol.cnf"
|
|
* as the input file (e.g. "./vol.cnf" or "/games/os/vol.cnf").
|
|
*/
|
|
|
|
#include "extract_cine.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32 CineUnpacker::readSource() {
|
|
if (_src < _srcBegin || _src + 4 > _srcEnd) {
|
|
_error = true;
|
|
return 0; // The source pointer is out of bounds, returning a default value
|
|
}
|
|
uint32 value = READ_BE_UINT32(_src);
|
|
_src -= 4;
|
|
return value;
|
|
}
|
|
|
|
unsigned int CineUnpacker::rcr(bool inputCarry) {
|
|
unsigned int outputCarry = (_chunk32b & 1);
|
|
_chunk32b >>= 1;
|
|
if (inputCarry) {
|
|
_chunk32b |= 0x80000000;
|
|
}
|
|
return outputCarry;
|
|
}
|
|
|
|
unsigned int CineUnpacker::nextBit() {
|
|
unsigned int carry = rcr(false);
|
|
// Normally if the chunk becomes zero then the carry is one as
|
|
// the end of chunk marker is always the last to be shifted out.
|
|
if (_chunk32b == 0) {
|
|
_chunk32b = readSource();
|
|
_crc ^= _chunk32b;
|
|
carry = rcr(true); // Put the end of chunk marker in the most significant bit
|
|
}
|
|
return carry;
|
|
}
|
|
|
|
unsigned int CineUnpacker::getBits(unsigned int numBits) {
|
|
unsigned int c = 0;
|
|
while (numBits--) {
|
|
c <<= 1;
|
|
c |= nextBit();
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void CineUnpacker::unpackRawBytes(unsigned int numBytes) {
|
|
if (_dst >= _dstEnd || _dst - numBytes + 1 < _dstBegin) {
|
|
_error = true;
|
|
return; // Destination pointer is out of bounds for this operation
|
|
}
|
|
while (numBytes--) {
|
|
*_dst = (byte)getBits(8);
|
|
--_dst;
|
|
}
|
|
}
|
|
|
|
void CineUnpacker::copyRelocatedBytes(unsigned int offset, unsigned int numBytes) {
|
|
if (_dst + offset >= _dstEnd || _dst - numBytes + 1 < _dstBegin) {
|
|
_error = true;
|
|
return; // Destination pointer is out of bounds for this operation
|
|
}
|
|
while (numBytes--) {
|
|
*_dst = *(_dst + offset);
|
|
--_dst;
|
|
}
|
|
}
|
|
|
|
bool CineUnpacker::unpack(const byte *src, unsigned int srcLen, byte *dst, unsigned int dstLen) {
|
|
// Initialize variables used for detecting errors during unpacking
|
|
_error = false;
|
|
_srcBegin = src;
|
|
_srcEnd = src + srcLen;
|
|
_dstBegin = dst;
|
|
_dstEnd = dst + dstLen;
|
|
|
|
// Initialize other variables
|
|
_src = _srcBegin + srcLen - 4;
|
|
uint32 unpackedLength = readSource(); // Unpacked length in bytes
|
|
_dst = _dstBegin + unpackedLength - 1;
|
|
_crc = readSource();
|
|
_chunk32b = readSource();
|
|
_crc ^= _chunk32b;
|
|
|
|
while (_dst >= _dstBegin && !_error) {
|
|
/*
|
|
Bits => Action:
|
|
0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..8)
|
|
1 1 1 => unpackRawBytes(8 bits + 9) i.e. unpackRawBytes(9..264)
|
|
0 1 => copyRelocatedBytes(8 bits, 2) i.e. copyRelocatedBytes(0..255, 2)
|
|
1 0 0 => copyRelocatedBytes(9 bits, 3) i.e. copyRelocatedBytes(0..511, 3)
|
|
1 0 1 => copyRelocatedBytes(10 bits, 4) i.e. copyRelocatedBytes(0..1023, 4)
|
|
1 1 0 => copyRelocatedBytes(12 bits, 8 bits + 1) i.e. copyRelocatedBytes(0..4095, 1..256)
|
|
*/
|
|
if (!nextBit()) { // 0...
|
|
if (!nextBit()) { // 0 0
|
|
unsigned int numBytes = getBits(3) + 1;
|
|
unpackRawBytes(numBytes);
|
|
} else { // 0 1
|
|
unsigned int numBytes = 2;
|
|
unsigned int offset = getBits(8);
|
|
copyRelocatedBytes(offset, numBytes);
|
|
}
|
|
} else { // 1...
|
|
unsigned int c = getBits(2);
|
|
if (c == 3) { // 1 1 1
|
|
unsigned int numBytes = getBits(8) + 9;
|
|
unpackRawBytes(numBytes);
|
|
} else if (c < 2) { // 1 0 x
|
|
unsigned int numBytes = c + 3;
|
|
unsigned int offset = getBits(c + 9);
|
|
copyRelocatedBytes(offset, numBytes);
|
|
} else { // 1 1 0
|
|
unsigned int numBytes = getBits(8) + 1;
|
|
unsigned int offset = getBits(12);
|
|
copyRelocatedBytes(offset, numBytes);
|
|
}
|
|
}
|
|
}
|
|
return !_error && (_crc == 0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void unpackFile(FILE *fp, const char *outDir) {
|
|
char filePath[512], fileName[15];
|
|
|
|
unsigned int entryCount = readUint16BE(fp); // How many entries?
|
|
unsigned int entrySize = readUint16BE(fp); // How many bytes per entry?
|
|
assert(entrySize == 0x1e);
|
|
while (entryCount--) {
|
|
fp.read(fileName, 14, 1);
|
|
fileName[14] = '\0';
|
|
sprintf(filePath, "%s/%s", outDir, fileName);
|
|
FILE *fpOut = fopen(filePath, "wb");
|
|
|
|
uint32 offset = readUint32BE(fp);
|
|
unsigned int packedSize = readUint32BE(fp);
|
|
unsigned int unpackedSize = readUint32BE(fp);
|
|
readUint32BE(fp);
|
|
unsigned int savedPos = fp.pos();
|
|
|
|
if (!fpOut) {
|
|
printf("ERROR: unable to open '%s' for writing\n", filePath);
|
|
continue;
|
|
}
|
|
printf("unpacking '%s' ... ", filePath);
|
|
|
|
fseek(fp, offset, SEEK_SET);
|
|
assert(unpackedSize >= packedSize);
|
|
uint8 *data = (uint8 *)calloc(unpackedSize, 1);
|
|
uint8 *packedData = (uint8 *)calloc(packedSize, 1);
|
|
assert(data);
|
|
assert(packedData);
|
|
fp.read(packedData, packedSize, 1);
|
|
bool status = true;
|
|
if (packedSize != unpackedSize) {
|
|
CineUnpacker cineUnpacker;
|
|
status = cineUnpacker.unpack(packedData, packedSize, data, unpackedSize);
|
|
} else {
|
|
memcpy(data, packedData, packedSize);
|
|
}
|
|
free(packedData);
|
|
fpOut.write(data, unpackedSize, 1);
|
|
fclose(fpOut);
|
|
free(data);
|
|
|
|
if (!status) {
|
|
printf("CRC ERROR");
|
|
} else {
|
|
printf("ok");
|
|
}
|
|
printf(", packedSize %u unpackedSize %u\n", packedSize, unpackedSize);
|
|
fseek(fp, savedPos, SEEK_SET);
|
|
}
|
|
}
|
|
|
|
void fixVolCnfFileName(char *dst, const uint8 *src) {
|
|
char *ext, *end;
|
|
|
|
memcpy(dst, src, 8);
|
|
src += 8;
|
|
dst[8] = 0;
|
|
ext = strchr(dst, ' ');
|
|
if (!ext) {
|
|
ext = &dst[8];
|
|
}
|
|
if (*src == ' ') {
|
|
*ext = 0;
|
|
} else {
|
|
*ext++ = '.';
|
|
memcpy(ext, src, 3);
|
|
end = strchr(ext, ' ');
|
|
if (!end) {
|
|
end = &ext[3];
|
|
}
|
|
*end = 0;
|
|
}
|
|
}
|
|
|
|
void unpackAllResourceFiles(const char *filename, const char *outDir) {
|
|
FILE *fp = fopen(filename, "rb");
|
|
if (!fp) {
|
|
error("Unable to open file '%s'", filename);
|
|
}
|
|
|
|
uint32 unpackedSize, packedSize;
|
|
{
|
|
char header[8];
|
|
fp.read(header, 8, 1);
|
|
if (memcmp(header, "ABASECP", 7) == 0) {
|
|
unpackedSize = readUint32BE(fp);
|
|
packedSize = readUint32BE(fp);
|
|
} else {
|
|
fseek(fp, 0, SEEK_END);
|
|
unpackedSize = packedSize = fp.pos(); /* Get file size */
|
|
fseek(fp, 0, SEEK_SET);
|
|
}
|
|
}
|
|
assert(unpackedSize >= packedSize);
|
|
uint8 *buf = (uint8 *)calloc(unpackedSize, 1);
|
|
assert(buf);
|
|
fp.read(buf, packedSize, 1);
|
|
fclose(fp);
|
|
if (packedSize != unpackedSize) {
|
|
CineUnpacker cineUnpacker;
|
|
if (!cineUnpacker.unpack(buf, packedSize, buf, unpackedSize)) {
|
|
error("Failed to unpack 'vol.cnf' data");
|
|
}
|
|
}
|
|
unsigned int resourceFilesCount = READ_BE_UINT16(&buf[0]);
|
|
unsigned int entrySize = READ_BE_UINT16(&buf[2]);
|
|
printf("--- Unpacking all %d resource files from 'vol.cnf' (entrySize = %d):\n", resourceFilesCount, entrySize);
|
|
char resourceFileName[9];
|
|
for (unsigned int i = 0; i < resourceFilesCount; ++i) {
|
|
memcpy(resourceFileName, &buf[4 + i * entrySize], 8);
|
|
resourceFileName[8] = 0;
|
|
FILE *fpResFile = fopen(resourceFileName, "rb");
|
|
if (fpResFile) {
|
|
printf("--- Unpacking resource file %s:\n", resourceFileName);
|
|
unpackFile(fpResFile, outDir);
|
|
fclose(fpResFile);
|
|
} else {
|
|
printf("ERROR: Unable to open resource file %s\n", resourceFileName);
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
int showUsage() {
|
|
printf("USAGE: extract_cine [input file] [output directory]\n" \
|
|
"Supports using Operation Stealth's 'vol.cnf' file as input.\n");
|
|
return -1;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int i;
|
|
char tmp[512];
|
|
|
|
if (argc == 3) {
|
|
strcpy(tmp, argv[1]);
|
|
for (i = 0; tmp[i] != 0; i++) {
|
|
tmp[i] = toupper(tmp[i]);
|
|
}
|
|
if (!strcmp(tmp, "VOL.CNF")) {
|
|
/* Unpack all archive files listed in 'vol.cnf' */
|
|
unpackAllResourceFiles(argv[1], argv[2]);
|
|
} else {
|
|
/* Unpack a single archive file */
|
|
FILE *fp = fopen(argv[1], "rb");
|
|
if (fp) {
|
|
unpackFile(fp, argv[2]);
|
|
fclose(fp);
|
|
} else {
|
|
printf("Couldn't open input file '%s'\n", argv[1]);
|
|
return -1;
|
|
}
|
|
}
|
|
} else {
|
|
return showUsage();
|
|
}
|
|
return 0;
|
|
}
|