Initial commit
This commit is contained in:
619
engines/sci/resource/decompressor.cpp
Normal file
619
engines/sci/resource/decompressor.cpp
Normal file
@@ -0,0 +1,619 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Resource library
|
||||
|
||||
#include "common/compression/dcl.h"
|
||||
#include "common/util.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "sci/resource/decompressor.h"
|
||||
#include "sci/sci.h"
|
||||
#include "sci/resource/resource.h"
|
||||
|
||||
namespace Sci {
|
||||
int Decompressor::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
|
||||
while (nPacked && !(src->eos() || src->err())) {
|
||||
uint32 chunk = MIN<uint32>(1024, nPacked);
|
||||
src->read(dest, chunk);
|
||||
nPacked -= chunk;
|
||||
dest += chunk;
|
||||
}
|
||||
return (src->eos() || src->err()) ? 1 : 0;
|
||||
}
|
||||
|
||||
void Decompressor::init(Common::ReadStream *src, byte *dest, uint32 nPacked,
|
||||
uint32 nUnpacked) {
|
||||
_src = src;
|
||||
_dest = dest;
|
||||
_szPacked = nPacked;
|
||||
_szUnpacked = nUnpacked;
|
||||
_nBits = 0;
|
||||
_dwRead = _dwWrote = 0;
|
||||
_dwBits = 0;
|
||||
}
|
||||
|
||||
void Decompressor::fetchBitsMSB() {
|
||||
while (_nBits <= 24) {
|
||||
_dwBits |= ((uint32)_src->readByte()) << (24 - _nBits);
|
||||
_nBits += 8;
|
||||
_dwRead++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 Decompressor::getBitsMSB(int n) {
|
||||
// fetching more data to buffer if needed
|
||||
if (_nBits < n)
|
||||
fetchBitsMSB();
|
||||
uint32 ret = _dwBits >> (32 - n);
|
||||
_dwBits <<= n;
|
||||
_nBits -= n;
|
||||
return ret;
|
||||
}
|
||||
|
||||
byte Decompressor::getByteMSB() {
|
||||
return getBitsMSB(8);
|
||||
}
|
||||
|
||||
void Decompressor::fetchBitsLSB() {
|
||||
while (_nBits <= 24) {
|
||||
_dwBits |= ((uint32)_src->readByte()) << _nBits;
|
||||
_nBits += 8;
|
||||
_dwRead++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 Decompressor::getBitsLSB(int n) {
|
||||
// fetching more data to buffer if needed
|
||||
if (_nBits < n)
|
||||
fetchBitsLSB();
|
||||
uint32 ret = (_dwBits & ~(0xFFFFFFFFU << n));
|
||||
_dwBits >>= n;
|
||||
_nBits -= n;
|
||||
return ret;
|
||||
}
|
||||
|
||||
byte Decompressor::getByteLSB() {
|
||||
return getBitsLSB(8);
|
||||
}
|
||||
|
||||
void Decompressor::putByte(byte b) {
|
||||
_dest[_dwWrote++] = b;
|
||||
}
|
||||
//-------------------------------
|
||||
// Huffman decompressor
|
||||
//-------------------------------
|
||||
int DecompressorHuffman::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked,
|
||||
uint32 nUnpacked) {
|
||||
init(src, dest, nPacked, nUnpacked);
|
||||
byte numnodes;
|
||||
int16 c;
|
||||
uint16 terminator;
|
||||
|
||||
numnodes = _src->readByte();
|
||||
terminator = _src->readByte() | 0x100;
|
||||
_nodes = new byte [numnodes << 1];
|
||||
_src->read(_nodes, numnodes << 1);
|
||||
|
||||
while ((c = getc2()) != terminator && (c >= 0) && !isFinished())
|
||||
putByte(c);
|
||||
|
||||
delete[] _nodes;
|
||||
return _dwWrote == _szUnpacked ? 0 : 1;
|
||||
}
|
||||
|
||||
int16 DecompressorHuffman::getc2() {
|
||||
byte *node = _nodes;
|
||||
int16 next;
|
||||
while (node[1]) {
|
||||
if (getBitsMSB(1)) {
|
||||
next = node[1] & 0x0F; // use lower 4 bits
|
||||
if (next == 0)
|
||||
return getByteMSB() | 0x100;
|
||||
} else
|
||||
next = node[1] >> 4; // use higher 4 bits
|
||||
node += next << 1;
|
||||
}
|
||||
return (int16)(*node | (node[1] << 8));
|
||||
}
|
||||
|
||||
//-------------------------------
|
||||
// LZW Decompressor for SCI0/01/1
|
||||
//-------------------------------
|
||||
|
||||
int DecompressorLZW::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
|
||||
byte *buffer = nullptr;
|
||||
|
||||
switch (_compression) {
|
||||
case kCompLZW: // SCI0 LZW compression
|
||||
case kCompLZW1: // SCI01/1 LZW compression
|
||||
return unpackLZW(src, dest, nPacked, nUnpacked);
|
||||
case kCompLZW1View:
|
||||
buffer = new byte[nUnpacked];
|
||||
unpackLZW(src, buffer, nPacked, nUnpacked);
|
||||
reorderView(buffer, dest);
|
||||
break;
|
||||
case kCompLZW1Pic:
|
||||
buffer = new byte[nUnpacked];
|
||||
unpackLZW(src, buffer, nPacked, nUnpacked);
|
||||
reorderPic(buffer, dest, nUnpacked);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
delete[] buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Decompresses SCI0 LZW and SCI01/1 LZW, depending on _compression value.
|
||||
//
|
||||
// SCI0: LSB-first.
|
||||
// SCI01/1: MSB-first, code size is increased one code earlier than necessary.
|
||||
// This is known as an "early change" bug in LZW implementations.
|
||||
int DecompressorLZW::unpackLZW(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
|
||||
init(src, dest, nPacked, nUnpacked);
|
||||
|
||||
uint16 codeBitLength = 9;
|
||||
uint16 tableSize = 258;
|
||||
uint16 codeLimit = (_compression == kCompLZW) ? 512 : 511;
|
||||
|
||||
// LZW table
|
||||
uint16 *stringOffsets = new uint16[4096]; // 0-257: unused
|
||||
uint16 *stringLengths = new uint16[4096]; // 0-257: unused
|
||||
|
||||
while (!isFinished()) {
|
||||
uint16 code = (_compression == kCompLZW) ?
|
||||
getBitsLSB(codeBitLength) :
|
||||
getBitsMSB(codeBitLength);
|
||||
|
||||
if (code >= tableSize) {
|
||||
warning("LZW code %x exceeds table size %x", code, tableSize);
|
||||
break;
|
||||
}
|
||||
|
||||
if (code == 257) { // terminator
|
||||
break;
|
||||
}
|
||||
|
||||
if (code == 256) { // reset command
|
||||
codeBitLength = 9;
|
||||
tableSize = 258;
|
||||
codeLimit = (_compression == kCompLZW) ? 512 : 511;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16 newStringOffset = _dwWrote;
|
||||
if (code <= 255) {
|
||||
// Code is a literal byte
|
||||
putByte(code);
|
||||
} else {
|
||||
// Code is a table index
|
||||
|
||||
// Boundary check included because the previous decompressor had a
|
||||
// comment saying it's "a normal situation" for a string to attempt
|
||||
// to write beyond the destination. I have not seen this occur.
|
||||
for (int i = 0; i < stringLengths[code] && !isFinished(); i++) {
|
||||
putByte(dest[stringOffsets[code] + i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop adding to the table once it is full
|
||||
if (tableSize >= 4096) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Increase code size once a bit limit has been reached
|
||||
if (tableSize == codeLimit && codeBitLength < 12) {
|
||||
codeBitLength++;
|
||||
codeLimit = 1 << codeBitLength;
|
||||
if (_compression != kCompLZW) {
|
||||
codeLimit--;
|
||||
}
|
||||
}
|
||||
|
||||
// Append code to table
|
||||
stringOffsets[tableSize] = newStringOffset;
|
||||
stringLengths[tableSize] = _dwWrote - newStringOffset + 1;
|
||||
tableSize++;
|
||||
}
|
||||
|
||||
delete[] stringOffsets;
|
||||
delete[] stringLengths;
|
||||
|
||||
return _dwWrote == _szUnpacked ? 0 : SCI_ERROR_DECOMPRESSION_ERROR;
|
||||
}
|
||||
|
||||
#define PAL_SIZE 1284
|
||||
#define EXTRA_MAGIC_SIZE 15
|
||||
#define VIEW_HEADER_COLORS_8BIT 0x80
|
||||
|
||||
void DecompressorLZW::decodeRLE(byte **rledata, byte **pixeldata, byte *outbuffer, int size) {
|
||||
int pos = 0;
|
||||
byte nextbyte;
|
||||
byte *rd = *rledata;
|
||||
byte *ob = outbuffer;
|
||||
byte *pd = *pixeldata;
|
||||
|
||||
while (pos < size) {
|
||||
nextbyte = *rd++;
|
||||
*ob++ = nextbyte;
|
||||
pos++;
|
||||
switch (nextbyte & 0xC0) {
|
||||
case 0x40:
|
||||
case 0x00:
|
||||
memcpy(ob, pd, nextbyte);
|
||||
pd += nextbyte;
|
||||
ob += nextbyte;
|
||||
pos += nextbyte;
|
||||
break;
|
||||
case 0xC0:
|
||||
default:
|
||||
break;
|
||||
case 0x80:
|
||||
nextbyte = *pd++;
|
||||
*ob++ = nextbyte;
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*rledata = rd;
|
||||
*pixeldata = pd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the same this as decodeRLE, only to determine the length of the
|
||||
* compressed source data.
|
||||
*/
|
||||
int DecompressorLZW::getRLEsize(byte *rledata, int dsize) {
|
||||
int pos = 0;
|
||||
int size = 0;
|
||||
|
||||
while (pos < dsize) {
|
||||
byte nextbyte = *(rledata++);
|
||||
pos++;
|
||||
size++;
|
||||
|
||||
switch (nextbyte & 0xC0) {
|
||||
case 0x40:
|
||||
case 0x00:
|
||||
pos += nextbyte;
|
||||
break;
|
||||
case 0xC0:
|
||||
default:
|
||||
break;
|
||||
case 0x80:
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
enum {
|
||||
PIC_OPX_EMBEDDED_VIEW = 1,
|
||||
PIC_OPX_SET_PALETTE = 2,
|
||||
PIC_OP_OPX = 0xfe
|
||||
};
|
||||
|
||||
void DecompressorLZW::reorderPic(byte *src, byte *dest, int dsize) {
|
||||
uint16 view_size, view_start, cdata_size;
|
||||
int i;
|
||||
byte *seeker = src;
|
||||
byte *writer = dest;
|
||||
char viewdata[7];
|
||||
byte *cdata, *cdata_start;
|
||||
|
||||
*writer++ = PIC_OP_OPX;
|
||||
*writer++ = PIC_OPX_SET_PALETTE;
|
||||
|
||||
for (i = 0; i < 256; i++) /* Palette translation map */
|
||||
*writer++ = i;
|
||||
|
||||
WRITE_LE_UINT32(writer, 0); /* Palette stamp */
|
||||
writer += 4;
|
||||
|
||||
view_size = READ_LE_UINT16(seeker);
|
||||
seeker += 2;
|
||||
view_start = READ_LE_UINT16(seeker);
|
||||
seeker += 2;
|
||||
cdata_size = READ_LE_UINT16(seeker);
|
||||
seeker += 2;
|
||||
|
||||
memcpy(viewdata, seeker, sizeof(viewdata));
|
||||
seeker += sizeof(viewdata);
|
||||
|
||||
memcpy(writer, seeker, 4*256); /* Palette */
|
||||
seeker += 4*256;
|
||||
writer += 4*256;
|
||||
|
||||
if (view_start != PAL_SIZE + 2) { /* +2 for the opcode */
|
||||
memcpy(writer, seeker, view_start-PAL_SIZE-2);
|
||||
seeker += view_start - PAL_SIZE - 2;
|
||||
writer += view_start - PAL_SIZE - 2;
|
||||
}
|
||||
|
||||
if (dsize != view_start + EXTRA_MAGIC_SIZE + view_size) {
|
||||
memcpy(dest + view_size + view_start + EXTRA_MAGIC_SIZE, seeker,
|
||||
dsize - view_size - view_start - EXTRA_MAGIC_SIZE);
|
||||
seeker += dsize - view_size - view_start - EXTRA_MAGIC_SIZE;
|
||||
}
|
||||
|
||||
cdata_start = cdata = (byte *)malloc(cdata_size);
|
||||
memcpy(cdata, seeker, cdata_size);
|
||||
seeker += cdata_size;
|
||||
|
||||
writer = dest + view_start;
|
||||
*writer++ = PIC_OP_OPX;
|
||||
*writer++ = PIC_OPX_EMBEDDED_VIEW;
|
||||
*writer++ = 0;
|
||||
*writer++ = 0;
|
||||
*writer++ = 0;
|
||||
WRITE_LE_UINT16(writer, view_size + 8);
|
||||
writer += 2;
|
||||
|
||||
memcpy(writer, viewdata, sizeof(viewdata));
|
||||
writer += sizeof(viewdata);
|
||||
|
||||
*writer++ = 0;
|
||||
|
||||
decodeRLE(&seeker, &cdata, writer, view_size);
|
||||
|
||||
free(cdata_start);
|
||||
}
|
||||
|
||||
void DecompressorLZW::buildCelHeaders(byte **seeker, byte **writer, int celindex, int *cc_lengths, int max) {
|
||||
for (int c = 0; c < max; c++) {
|
||||
memcpy(*writer, *seeker, 6);
|
||||
*seeker += 6;
|
||||
*writer += 6;
|
||||
int w = *((*seeker)++);
|
||||
WRITE_LE_UINT16(*writer, w); /* Zero extension */
|
||||
*writer += 2;
|
||||
|
||||
*writer += cc_lengths[celindex];
|
||||
celindex++;
|
||||
}
|
||||
}
|
||||
|
||||
void DecompressorLZW::reorderView(byte *src, byte *dest) {
|
||||
byte *cellengths;
|
||||
int loopheaders;
|
||||
int lh_present;
|
||||
int lh_mask;
|
||||
int pal_offset;
|
||||
int cel_total;
|
||||
int unknown;
|
||||
byte *seeker = src;
|
||||
char celcounts[100];
|
||||
byte *writer = dest;
|
||||
byte *lh_ptr;
|
||||
byte *rle_ptr, *pix_ptr;
|
||||
int l, lb, c, celindex, lh_last = -1;
|
||||
int chptr;
|
||||
int w;
|
||||
int *cc_lengths;
|
||||
byte **cc_pos;
|
||||
|
||||
/* Parse the main header */
|
||||
cellengths = src + READ_LE_UINT16(seeker) + 2;
|
||||
seeker += 2;
|
||||
loopheaders = *seeker++;
|
||||
lh_present = *seeker++;
|
||||
lh_mask = READ_LE_UINT16(seeker);
|
||||
seeker += 2;
|
||||
unknown = READ_LE_UINT16(seeker);
|
||||
seeker += 2;
|
||||
pal_offset = READ_LE_UINT16(seeker);
|
||||
seeker += 2;
|
||||
cel_total = READ_LE_UINT16(seeker);
|
||||
seeker += 2;
|
||||
|
||||
cc_pos = (byte **) malloc(sizeof(byte *) * cel_total);
|
||||
cc_lengths = (int *) malloc(sizeof(int) * cel_total);
|
||||
|
||||
for (c = 0; c < cel_total; c++)
|
||||
cc_lengths[c] = READ_LE_UINT16(cellengths + 2 * c);
|
||||
|
||||
*writer++ = loopheaders;
|
||||
*writer++ = VIEW_HEADER_COLORS_8BIT;
|
||||
WRITE_LE_UINT16(writer, lh_mask);
|
||||
writer += 2;
|
||||
WRITE_LE_UINT16(writer, unknown);
|
||||
writer += 2;
|
||||
WRITE_LE_UINT16(writer, pal_offset);
|
||||
writer += 2;
|
||||
|
||||
lh_ptr = writer;
|
||||
writer += 2 * loopheaders; /* Make room for the loop offset table */
|
||||
|
||||
pix_ptr = writer;
|
||||
|
||||
memcpy(celcounts, seeker, lh_present);
|
||||
seeker += lh_present;
|
||||
|
||||
lb = 1;
|
||||
celindex = 0;
|
||||
|
||||
rle_ptr = pix_ptr = cellengths + (2 * cel_total);
|
||||
w = 0;
|
||||
|
||||
for (l = 0; l < loopheaders; l++) {
|
||||
if (lh_mask & lb) { /* The loop is _not_ present */
|
||||
if (lh_last == -1) {
|
||||
warning("Error: While reordering view: Loop not present, but can't re-use last loop");
|
||||
lh_last = 0;
|
||||
}
|
||||
WRITE_LE_UINT16(lh_ptr, lh_last);
|
||||
lh_ptr += 2;
|
||||
} else {
|
||||
lh_last = writer - dest;
|
||||
WRITE_LE_UINT16(lh_ptr, lh_last);
|
||||
lh_ptr += 2;
|
||||
WRITE_LE_UINT16(writer, celcounts[w]);
|
||||
writer += 2;
|
||||
WRITE_LE_UINT16(writer, 0);
|
||||
writer += 2;
|
||||
|
||||
/* Now, build the cel offset table */
|
||||
chptr = (writer - dest) + (2 * celcounts[w]);
|
||||
|
||||
for (c = 0; c < celcounts[w]; c++) {
|
||||
WRITE_LE_UINT16(writer, chptr);
|
||||
writer += 2;
|
||||
cc_pos[celindex+c] = dest + chptr;
|
||||
chptr += 8 + READ_LE_UINT16(cellengths + 2 * (celindex + c));
|
||||
}
|
||||
|
||||
buildCelHeaders(&seeker, &writer, celindex, cc_lengths, celcounts[w]);
|
||||
|
||||
celindex += celcounts[w];
|
||||
w++;
|
||||
}
|
||||
|
||||
lb = lb << 1;
|
||||
}
|
||||
|
||||
if (celindex < cel_total) {
|
||||
warning("View decompression generated too few (%d / %d) headers", celindex, cel_total);
|
||||
free(cc_pos);
|
||||
free(cc_lengths);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Figure out where the pixel data begins. */
|
||||
for (c = 0; c < cel_total; c++)
|
||||
pix_ptr += getRLEsize(pix_ptr, cc_lengths[c]);
|
||||
|
||||
rle_ptr = cellengths + (2 * cel_total);
|
||||
for (c = 0; c < cel_total; c++)
|
||||
decodeRLE(&rle_ptr, &pix_ptr, cc_pos[c] + 8, cc_lengths[c]);
|
||||
|
||||
if (pal_offset) {
|
||||
*writer++ = 'P';
|
||||
*writer++ = 'A';
|
||||
*writer++ = 'L';
|
||||
|
||||
for (c = 0; c < 256; c++)
|
||||
*writer++ = c;
|
||||
|
||||
seeker -= 4; /* The missing four. Don't ask why. */
|
||||
memcpy(writer, seeker, 4*256 + 4);
|
||||
}
|
||||
|
||||
free(cc_pos);
|
||||
free(cc_lengths);
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// DCL decompressor for SCI1.1
|
||||
//----------------------------------------------
|
||||
|
||||
int DecompressorDCL::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked,
|
||||
uint32 nUnpacked) {
|
||||
return Common::decompressDCL(src, dest, nPacked, nUnpacked) ? 0 : SCI_ERROR_DECOMPRESSION_ERROR;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
//----------------------------------------------
|
||||
// STACpack/LZS decompressor for SCI32
|
||||
// Based on Andre Beck's code from
|
||||
// https://web.archive.org/web/20070817214826/http://micky.ibh.de/~beck/stuff/lzs4i4l/
|
||||
//----------------------------------------------
|
||||
int DecompressorLZS::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
|
||||
init(src, dest, nPacked, nUnpacked);
|
||||
return unpackLZS();
|
||||
}
|
||||
|
||||
int DecompressorLZS::unpackLZS() {
|
||||
uint16 offs = 0;
|
||||
uint32 clen;
|
||||
|
||||
while (!isFinished()) {
|
||||
if (getBitsMSB(1)) { // Compressed bytes follow
|
||||
if (getBitsMSB(1)) { // Seven bit offset follows
|
||||
offs = getBitsMSB(7);
|
||||
if (!offs) // This is the end marker - a 7 bit offset of zero
|
||||
break;
|
||||
if (!(clen = getCompLen())) {
|
||||
warning("lzsDecomp: length mismatch");
|
||||
return SCI_ERROR_DECOMPRESSION_ERROR;
|
||||
}
|
||||
copyComp(offs, clen);
|
||||
} else { // Eleven bit offset follows
|
||||
offs = getBitsMSB(11);
|
||||
if (!(clen = getCompLen())) {
|
||||
warning("lzsDecomp: length mismatch");
|
||||
return SCI_ERROR_DECOMPRESSION_ERROR;
|
||||
}
|
||||
copyComp(offs, clen);
|
||||
}
|
||||
} else // Literal byte follows
|
||||
putByte(getByteMSB());
|
||||
} // end of while ()
|
||||
return _dwWrote == _szUnpacked ? 0 : SCI_ERROR_DECOMPRESSION_ERROR;
|
||||
}
|
||||
|
||||
uint32 DecompressorLZS::getCompLen() {
|
||||
uint32 clen;
|
||||
int nibble;
|
||||
// The most probable cases are hardcoded
|
||||
switch (getBitsMSB(2)) {
|
||||
case 0:
|
||||
return 2;
|
||||
case 1:
|
||||
return 3;
|
||||
case 2:
|
||||
return 4;
|
||||
default:
|
||||
switch (getBitsMSB(2)) {
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 6;
|
||||
case 2:
|
||||
return 7;
|
||||
default:
|
||||
// Ok, no shortcuts anymore - just get nibbles and add up
|
||||
clen = 8;
|
||||
do {
|
||||
nibble = getBitsMSB(4);
|
||||
clen += nibble;
|
||||
} while (nibble == 0xf);
|
||||
return clen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DecompressorLZS::copyComp(int offs, uint32 clen) {
|
||||
int hpos = _dwWrote - offs;
|
||||
|
||||
while (clen--)
|
||||
putByte(_dest[hpos++]);
|
||||
}
|
||||
|
||||
#endif // #ifdef ENABLE_SCI32
|
||||
|
||||
} // End of namespace Sci
|
||||
190
engines/sci/resource/decompressor.h
Normal file
190
engines/sci/resource/decompressor.h
Normal file
@@ -0,0 +1,190 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_RESOURCE_DECOMPRESSOR_H
|
||||
#define SCI_RESOURCE_DECOMPRESSOR_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace Common {
|
||||
class ReadStream;
|
||||
}
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum ResourceCompression {
|
||||
kCompUnknown = -1,
|
||||
kCompNone = 0,
|
||||
kCompLZW,
|
||||
kCompHuffman,
|
||||
kCompLZW1, // LZW-like compression used in SCI01 and SCI1
|
||||
kCompLZW1View, // Comp3 + view Post-processing
|
||||
kCompLZW1Pic, // Comp3 + pic Post-processing
|
||||
#ifdef ENABLE_SCI32
|
||||
kCompSTACpack, // ? Used in SCI32
|
||||
#endif
|
||||
kCompDCL
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for decompressors.
|
||||
* Simply copies nPacked bytes from src to dest.
|
||||
*/
|
||||
class Decompressor {
|
||||
public:
|
||||
Decompressor() :
|
||||
_dwBits(0),
|
||||
_nBits(0),
|
||||
_szPacked(0),
|
||||
_szUnpacked(0),
|
||||
_dwRead(0),
|
||||
_dwWrote(0),
|
||||
_src(nullptr),
|
||||
_dest(nullptr)
|
||||
{}
|
||||
|
||||
virtual ~Decompressor() {}
|
||||
|
||||
|
||||
virtual int unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Initialize decompressor.
|
||||
* @param src source stream to read from
|
||||
* @param dest destination stream to write to
|
||||
* @param nPacked size of packed data
|
||||
* @param nUnpacket size of unpacked data
|
||||
* @return 0 on success, non-zero on error
|
||||
*/
|
||||
virtual void init(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked);
|
||||
|
||||
/**
|
||||
* Get a number of bits from _src stream, starting with the most
|
||||
* significant unread bit of the current four byte block.
|
||||
* @param n number of bits to get
|
||||
* @return n-bits number
|
||||
*/
|
||||
uint32 getBitsMSB(int n);
|
||||
|
||||
/**
|
||||
* Get a number of bits from _src stream, starting with the least
|
||||
* significant unread bit of the current four byte block.
|
||||
* @param n number of bits to get
|
||||
* @return n-bits number
|
||||
*/
|
||||
uint32 getBitsLSB(int n);
|
||||
|
||||
/**
|
||||
* Get one byte from _src stream.
|
||||
* @return byte
|
||||
*/
|
||||
byte getByteMSB();
|
||||
byte getByteLSB();
|
||||
|
||||
void fetchBitsMSB();
|
||||
void fetchBitsLSB();
|
||||
|
||||
/**
|
||||
* Write one byte into _dest stream
|
||||
* @param b byte to put
|
||||
*/
|
||||
|
||||
virtual void putByte(byte b);
|
||||
|
||||
/**
|
||||
* Returns true if all expected data has been unpacked to _dest
|
||||
* and there is no more data in _src.
|
||||
*/
|
||||
bool isFinished() {
|
||||
return (_dwWrote == _szUnpacked) && (_dwRead >= _szPacked);
|
||||
}
|
||||
|
||||
uint32 _dwBits; ///< bits buffer
|
||||
byte _nBits; ///< number of unread bits in _dwBits
|
||||
uint32 _szPacked; ///< size of the compressed data
|
||||
uint32 _szUnpacked; ///< size of the decompressed data
|
||||
uint32 _dwRead; ///< number of bytes read from _src
|
||||
uint32 _dwWrote; ///< number of bytes written to _dest
|
||||
Common::ReadStream *_src;
|
||||
byte *_dest;
|
||||
};
|
||||
|
||||
/**
|
||||
* Huffman decompressor
|
||||
*/
|
||||
class DecompressorHuffman : public Decompressor {
|
||||
public:
|
||||
int unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) override;
|
||||
|
||||
protected:
|
||||
int16 getc2();
|
||||
|
||||
byte *_nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* LZW decompressor for SCI0/01/1
|
||||
* TODO: Clean-up post-processing functions
|
||||
*/
|
||||
class DecompressorLZW : public Decompressor {
|
||||
public:
|
||||
DecompressorLZW(ResourceCompression compression) : _compression(compression) {}
|
||||
int unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) override;
|
||||
|
||||
protected:
|
||||
int unpackLZW(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked);
|
||||
|
||||
// functions to post-process view and pic resources
|
||||
void reorderPic(byte *src, byte *dest, int dsize);
|
||||
void reorderView(byte *src, byte *dest);
|
||||
void decodeRLE(byte **rledata, byte **pixeldata, byte *outbuffer, int size);
|
||||
int getRLEsize(byte *rledata, int dsize);
|
||||
void buildCelHeaders(byte **seeker, byte **writer, int celindex, int *cc_lengths, int max);
|
||||
|
||||
ResourceCompression _compression;
|
||||
};
|
||||
|
||||
/**
|
||||
* DCL decompressor for SCI1.1
|
||||
*/
|
||||
class DecompressorDCL : public Decompressor {
|
||||
public:
|
||||
int unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) override;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* STACpack decompressor for SCI32
|
||||
*/
|
||||
class DecompressorLZS : public Decompressor {
|
||||
public:
|
||||
int unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) override;
|
||||
protected:
|
||||
int unpackLZS();
|
||||
uint32 getCompLen();
|
||||
void copyComp(int offs, uint32 clen);
|
||||
};
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_RESOURCE_DECOMPRESSOR_H
|
||||
3125
engines/sci/resource/resource.cpp
Normal file
3125
engines/sci/resource/resource.cpp
Normal file
File diff suppressed because it is too large
Load Diff
733
engines/sci/resource/resource.h
Normal file
733
engines/sci/resource/resource.h
Normal file
@@ -0,0 +1,733 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_RESOURCE_RESOURCE_H
|
||||
#define SCI_RESOURCE_RESOURCE_H
|
||||
|
||||
#include "common/str.h"
|
||||
#include "common/list.h"
|
||||
#include "common/hashmap.h"
|
||||
|
||||
#include "sci/graphics/helpers.h" // for ViewType
|
||||
#include "sci/resource/decompressor.h"
|
||||
#include "sci/sci.h"
|
||||
#include "sci/util.h"
|
||||
#include "sci/version.h"
|
||||
|
||||
namespace Common {
|
||||
class File;
|
||||
class FSList;
|
||||
class FSNode;
|
||||
class WriteStream;
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum {
|
||||
#ifdef ENABLE_SCI32
|
||||
// Hack to treat RESMAP.PAT/RESSCI.PAT as the highest volume
|
||||
kResPatVolumeNumber = 100,
|
||||
#endif
|
||||
|
||||
kResourceHeaderSize = 2, ///< patch type + header size
|
||||
|
||||
/** The maximum allowed size for a compressed or decompressed resource */
|
||||
SCI_MAX_RESOURCE_SIZE = 0x0400000
|
||||
};
|
||||
|
||||
/** Resource status types */
|
||||
enum ResourceStatus {
|
||||
kResStatusNoMalloc = 0,
|
||||
kResStatusAllocated,
|
||||
kResStatusEnqueued, /**< In the LRU queue */
|
||||
kResStatusLocked /**< Allocated and in use */
|
||||
};
|
||||
|
||||
/** Resource error codes. Should be in sync with s_errorDescriptions */
|
||||
enum ResourceErrorCodes {
|
||||
SCI_ERROR_NONE = 0,
|
||||
SCI_ERROR_IO_ERROR = 1,
|
||||
SCI_ERROR_EMPTY_RESOURCE = 2,
|
||||
SCI_ERROR_RESMAP_INVALID_ENTRY = 3, /**< Invalid resource.map entry */
|
||||
SCI_ERROR_RESMAP_NOT_FOUND = 4,
|
||||
SCI_ERROR_NO_RESOURCE_FILES_FOUND = 5, /**< No resource at all was found */
|
||||
SCI_ERROR_UNKNOWN_COMPRESSION = 6,
|
||||
SCI_ERROR_DECOMPRESSION_ERROR = 7, /**< sanity checks failed during decompression */
|
||||
SCI_ERROR_RESOURCE_TOO_BIG = 8 /**< Resource size exceeds SCI_MAX_RESOURCE_SIZE */
|
||||
};
|
||||
|
||||
enum {
|
||||
MAX_OPENED_VOLUMES = 5 ///< Max number of simultaneously opened volumes
|
||||
};
|
||||
|
||||
enum ResourceType {
|
||||
kResourceTypeView = 0,
|
||||
kResourceTypePic,
|
||||
kResourceTypeScript,
|
||||
kResourceTypeText,
|
||||
kResourceTypeSound,
|
||||
kResourceTypeMemory,
|
||||
kResourceTypeVocab,
|
||||
kResourceTypeFont,
|
||||
kResourceTypeCursor,
|
||||
kResourceTypePatch,
|
||||
kResourceTypeBitmap,
|
||||
kResourceTypePalette,
|
||||
kResourceTypeCdAudio = 12,
|
||||
#ifdef ENABLE_SCI32
|
||||
kResourceTypeWave = 12,
|
||||
#endif
|
||||
kResourceTypeAudio,
|
||||
kResourceTypeSync,
|
||||
kResourceTypeMessage,
|
||||
kResourceTypeMap,
|
||||
kResourceTypeHeap,
|
||||
kResourceTypeAudio36,
|
||||
kResourceTypeSync36,
|
||||
kResourceTypeTranslation, // Currently unsupported
|
||||
|
||||
// SCI2.1+ Resources
|
||||
kResourceTypeRobot,
|
||||
kResourceTypeVMD,
|
||||
kResourceTypeChunk,
|
||||
kResourceTypeAnimation,
|
||||
|
||||
// SCI3 Resources
|
||||
kResourceTypeEtc,
|
||||
kResourceTypeDuck,
|
||||
kResourceTypeClut,
|
||||
kResourceTypeTGA,
|
||||
kResourceTypeZZZ,
|
||||
|
||||
// Mac-only resources
|
||||
kResourceTypeMacIconBarPictN, // IBIN resources (icon bar, not selected)
|
||||
kResourceTypeMacIconBarPictS, // IBIS resources (icon bar, selected)
|
||||
kResourceTypeMacPict, // PICT resources (inventory)
|
||||
|
||||
kResourceTypeRave, // KQ6 hires RAVE (special sync) resources
|
||||
|
||||
kResourceTypeInvalid
|
||||
};
|
||||
|
||||
const char *getResourceTypeName(ResourceType restype);
|
||||
const char *getResourceTypeExtension(ResourceType restype);
|
||||
|
||||
enum ResVersion {
|
||||
kResVersionUnknown,
|
||||
kResVersionSci0Sci1Early,
|
||||
kResVersionSci1Middle,
|
||||
kResVersionKQ5FMT,
|
||||
kResVersionSci1Late,
|
||||
kResVersionSci11,
|
||||
kResVersionSci11Mac,
|
||||
kResVersionSci2,
|
||||
kResVersionSci3
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as Sci::getSciVersion, but this version doesn't assert on unknown SCI
|
||||
* versions. Only used by the fallback detector.
|
||||
*/
|
||||
SciVersion getSciVersionForDetection();
|
||||
|
||||
/**
|
||||
* Convenience function converting an SCI version into a human-readable string.
|
||||
*/
|
||||
const char *getSciVersionDesc(SciVersion version);
|
||||
|
||||
class ResourceManager;
|
||||
class ResourceSource;
|
||||
class ResourcePatcher;
|
||||
|
||||
class ResourceId {
|
||||
static inline ResourceType fixupType(ResourceType type) {
|
||||
if (type >= kResourceTypeInvalid)
|
||||
return kResourceTypeInvalid;
|
||||
return type;
|
||||
}
|
||||
|
||||
ResourceType _type;
|
||||
uint16 _number;
|
||||
uint32 _tuple; // Only used for audio36 and sync36
|
||||
|
||||
static Common::String intToBase36(uint32 number, int minChar) {
|
||||
// Convert from an integer to a base36 string
|
||||
Common::String string;
|
||||
|
||||
while (minChar--) {
|
||||
int character = number % 36;
|
||||
string = ((character < 10) ? (character + '0') : (character + 'A' - 10)) + string;
|
||||
number /= 36;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
friend void syncWithSerializer(Common::Serializer &s, ResourceId &obj);
|
||||
|
||||
public:
|
||||
ResourceId() : _type(kResourceTypeInvalid), _number(0), _tuple(0) { }
|
||||
|
||||
ResourceId(ResourceType type_, uint16 number_, uint32 tuple_ = 0)
|
||||
: _type(fixupType(type_)), _number(number_), _tuple(tuple_) {
|
||||
}
|
||||
|
||||
ResourceId(ResourceType type_, uint16 number_, byte noun, byte verb, byte cond, byte seq)
|
||||
: _type(fixupType(type_)), _number(number_) {
|
||||
_tuple = (noun << 24) | (verb << 16) | (cond << 8) | seq;
|
||||
}
|
||||
|
||||
Common::String toString() const {
|
||||
Common::String retStr = Common::String::format("%s.%d", getResourceTypeName(_type), _number);
|
||||
|
||||
if (_tuple != 0) {
|
||||
retStr += Common::String::format("(%d, %d, %d, %d)", _tuple >> 24, (_tuple >> 16) & 0xff, (_tuple >> 8) & 0xff, _tuple & 0xff);
|
||||
}
|
||||
|
||||
return retStr;
|
||||
}
|
||||
|
||||
// Convert from a resource ID to a base36 patch name
|
||||
Common::String toPatchNameBase36() const {
|
||||
Common::String output;
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
output += (getType() == kResourceTypeAudio36) ? 'A' : 'S'; // Identifier
|
||||
} else {
|
||||
output += (getType() == kResourceTypeAudio36) ? '@' : '#'; // Identifier
|
||||
}
|
||||
output += intToBase36(getNumber(), 3); // Map
|
||||
output += intToBase36(getTuple() >> 24, 2); // Noun
|
||||
output += intToBase36((getTuple() >> 16) & 0xff, 2); // Verb
|
||||
output += '.'; // Separator
|
||||
output += intToBase36((getTuple() >> 8) & 0xff, 2); // Cond
|
||||
output += intToBase36(getTuple() & 0xff, 1); // Seq
|
||||
|
||||
assert(output.size() == 12); // We should always get 12 characters in the end
|
||||
return output;
|
||||
}
|
||||
|
||||
inline ResourceType getType() const { return _type; }
|
||||
inline uint16 getNumber() const { return _number; }
|
||||
inline uint32 getTuple() const { return _tuple; }
|
||||
|
||||
inline uint hash() const {
|
||||
return ((uint)((_type << 16) | _number)) ^ _tuple;
|
||||
}
|
||||
|
||||
bool operator==(const ResourceId &other) const {
|
||||
return (_type == other._type) && (_number == other._number) && (_tuple == other._tuple);
|
||||
}
|
||||
|
||||
bool operator!=(const ResourceId &other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
bool operator<(const ResourceId &other) const {
|
||||
return (_type < other._type) || ((_type == other._type) && (_number < other._number))
|
||||
|| ((_type == other._type) && (_number == other._number) && (_tuple < other._tuple));
|
||||
}
|
||||
};
|
||||
|
||||
struct ResourceIdHash : public Common::UnaryFunction<ResourceId, uint> {
|
||||
uint operator()(ResourceId val) const { return val.hash(); }
|
||||
};
|
||||
|
||||
/** Class for storing resources in memory */
|
||||
class Resource : public SciSpan<const byte> {
|
||||
friend class ResourceManager;
|
||||
friend class ResourcePatcher;
|
||||
|
||||
// FIXME: These 'friend' declarations are meant to be a temporary hack to
|
||||
// ease transition to the ResourceSource class system.
|
||||
friend class ResourceSource;
|
||||
friend class PatchResourceSource;
|
||||
friend class WaveResourceSource;
|
||||
friend class AudioVolumeResourceSource;
|
||||
friend class MacResourceForkResourceSource;
|
||||
#ifdef ENABLE_SCI32
|
||||
friend class ChunkResourceSource;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Holds the extra header data from view, pic, and palette patches so that
|
||||
* these patches can be rewritten to disk as valid patch files by the
|
||||
* `diskdump` debugger command.
|
||||
*/
|
||||
byte *_header;
|
||||
uint32 _headerSize;
|
||||
|
||||
public:
|
||||
Resource(ResourceManager *resMan, ResourceId id);
|
||||
~Resource();
|
||||
void unalloc();
|
||||
|
||||
inline ResourceType getType() const { return _id.getType(); }
|
||||
inline uint16 getNumber() const { return _id.getNumber(); }
|
||||
bool isLocked() const { return _status == kResStatusLocked; }
|
||||
/**
|
||||
* Write the resource to the specified stream.
|
||||
* This method is used only by the "dump" debugger command.
|
||||
*/
|
||||
void writeToStream(Common::WriteStream *stream) const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
Common::SeekableReadStream *makeStream() const;
|
||||
#endif
|
||||
|
||||
const Common::Path &getResourceLocation() const;
|
||||
|
||||
// FIXME: This audio specific method is a hack. After all, why should a
|
||||
// Resource have audio specific methods? But for now we keep this, as it
|
||||
// eases transition.
|
||||
uint32 getAudioCompressionType() const;
|
||||
|
||||
uint16 getNumLockers() const { return _lockers; }
|
||||
|
||||
protected:
|
||||
ResourceId _id; // TODO: _id could almost be made const, only readResourceInfo() modifies it...
|
||||
int32 _fileOffset; /**< Offset in file */
|
||||
ResourceStatus _status;
|
||||
uint16 _lockers; /**< Number of places where this resource was locked */
|
||||
ResourceSource *_source;
|
||||
ResourceManager *_resMan;
|
||||
|
||||
bool loadPatch(Common::SeekableReadStream *file);
|
||||
bool loadFromPatchFile();
|
||||
bool loadFromWaveFile(Common::SeekableReadStream *file);
|
||||
bool loadFromAudioVolumeSCI1(Common::SeekableReadStream *file);
|
||||
bool loadFromAudioVolumeSCI11(Common::SeekableReadStream *file);
|
||||
int decompress(ResVersion volVersion, Common::SeekableReadStream *file);
|
||||
int readResourceInfo(ResVersion volVersion, Common::SeekableReadStream *file, uint32 &szPacked, ResourceCompression &compression);
|
||||
};
|
||||
|
||||
typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash> ResourceMap;
|
||||
|
||||
class IntMapResourceSource;
|
||||
class ResourceManager {
|
||||
// FIXME: These 'friend' declarations are meant to be a temporary hack to
|
||||
// ease transition to the ResourceSource class system.
|
||||
friend class ResourceSource;
|
||||
friend class DirectoryResourceSource;
|
||||
friend class PatchResourceSource;
|
||||
friend class ExtMapResourceSource;
|
||||
friend class IntMapResourceSource;
|
||||
friend class AudioVolumeResourceSource;
|
||||
friend class ExtAudioMapResourceSource;
|
||||
friend class WaveResourceSource;
|
||||
friend class MacResourceForkResourceSource;
|
||||
friend class ResourcePatcher;
|
||||
#ifdef ENABLE_SCI32
|
||||
friend class ChunkResourceSource;
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new SCI resource manager.
|
||||
*/
|
||||
ResourceManager(const bool detectionMode = false);
|
||||
~ResourceManager();
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the resource manager.
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* Adds all of the resource files for a game
|
||||
*/
|
||||
void addAppropriateSources();
|
||||
|
||||
/**
|
||||
* Similar to the function above, only called from the fallback detector
|
||||
*/
|
||||
void addAppropriateSourcesForDetection(const Common::FSList &fslist); // TODO: Switch from FSList to Common::Archive?
|
||||
|
||||
/**
|
||||
* Looks up a resource's data.
|
||||
* @param id The resource type to look for
|
||||
* @param lock non-zero iff the resource should be locked
|
||||
* @return The resource, or NULL if it doesn't exist
|
||||
* @note Locked resources are guaranteed not to have their contents freed until
|
||||
* they are unlocked explicitly (by unlockResource).
|
||||
*/
|
||||
Resource *findResource(ResourceId id, bool lock);
|
||||
|
||||
/**
|
||||
* Unlocks a previously locked resource.
|
||||
* @param res The resource to free
|
||||
*/
|
||||
void unlockResource(Resource *res);
|
||||
|
||||
/**
|
||||
* Tests whether a resource exists.
|
||||
*
|
||||
* This function may often be much faster than finding the resource
|
||||
* and should be preferred for simple tests.
|
||||
* The resource object returned is, indeed, the resource in question, but
|
||||
* it should be used with care, as it may be unallocated.
|
||||
* Use scir_find_resource() if you want to use the data contained in the resource.
|
||||
*
|
||||
* @param id Id of the resource to check
|
||||
* @return non-NULL if the resource exists, NULL otherwise
|
||||
*/
|
||||
Resource *testResource(const ResourceId &id) const;
|
||||
|
||||
/**
|
||||
* Returns a list of all resources of the specified type.
|
||||
* @param type The resource type to look for
|
||||
* @param mapNumber For audio36 and sync36, limit search to this map
|
||||
* @return The resource list
|
||||
*/
|
||||
Common::List<ResourceId> listResources(ResourceType type, int mapNumber = -1);
|
||||
|
||||
/**
|
||||
* Returns if there are any resources of the specified type.
|
||||
*/
|
||||
bool hasResourceType(ResourceType type);
|
||||
|
||||
bool setAudioLanguage(int language);
|
||||
void unloadAudioLanguage();
|
||||
int getAudioLanguage() const;
|
||||
void changeAudioDirectory(const Common::Path &path);
|
||||
void changeMacAudioDirectory(const Common::Path &path);
|
||||
bool isGMTrackIncluded();
|
||||
bool isSci11Mac() const { return _volVersion == kResVersionSci11Mac; }
|
||||
ViewType getViewType() const { return _viewType; }
|
||||
const char *getMapVersionDesc() const { return versionDescription(_mapVersion); }
|
||||
const char *getVolVersionDesc() const { return versionDescription(_volVersion); }
|
||||
ResVersion getVolVersion() const { return _volVersion; }
|
||||
|
||||
/**
|
||||
* Adds the appropriate GM patch from the Sierra MIDI utility as 4.pat, without
|
||||
* requiring the user to rename the file to 4.pat. Thus, the original Sierra
|
||||
* archive can be extracted in the extras directory, and the GM patches can be
|
||||
* applied per game, if applicable.
|
||||
*/
|
||||
void addNewGMPatch(SciGameId gameId);
|
||||
void addNewD110Patch(SciGameId gameId);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Parses all resources from a SCI2.1 chunk resource and adds them to the
|
||||
* resource manager.
|
||||
*/
|
||||
void addResourcesFromChunk(uint16 id);
|
||||
|
||||
/**
|
||||
* Updates the currently active disc number.
|
||||
*/
|
||||
void findDisc(const int16 discNo);
|
||||
|
||||
/**
|
||||
* Gets the currently active disc number.
|
||||
*/
|
||||
int16 getCurrentDiscNo() const { return _currentDiscNo; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* The currently active disc number.
|
||||
*/
|
||||
int16 _currentDiscNo;
|
||||
|
||||
/**
|
||||
* If true, the game has multiple audio volumes that contain different
|
||||
* audio files for each disc.
|
||||
*/
|
||||
bool _multiDiscAudio;
|
||||
|
||||
public:
|
||||
#endif
|
||||
|
||||
// Detects, if standard font of current game includes extended characters (>0x80)
|
||||
bool detectFontExtended();
|
||||
// Detects, if SCI1.1 game uses palette merging
|
||||
bool detectPaletteMergingSci11();
|
||||
// Detects, if SCI0EARLY game also has SCI0EARLY sound resources
|
||||
bool detectEarlySound();
|
||||
|
||||
/**
|
||||
* Finds the internal Sierra ID of the current game from script 0.
|
||||
*/
|
||||
Common::String findSierraGameId();
|
||||
|
||||
/**
|
||||
* Finds the location of the game object from script 0.
|
||||
* @param addSci11ScriptOffset Adjust the return value for SCI1.1 and newer
|
||||
* games. Needs to be false when the heap is accessed directly inside
|
||||
* findSierraGameId().
|
||||
*/
|
||||
reg_t findGameObject(const bool addSci11ScriptOffset);
|
||||
|
||||
/**
|
||||
* Converts a map resource type to our type
|
||||
* @param sciType The type from the map/patch
|
||||
* @return The ResourceType
|
||||
*/
|
||||
ResourceType convertResType(byte type);
|
||||
|
||||
protected:
|
||||
bool _detectionMode;
|
||||
|
||||
// Maximum number of bytes to allow being allocated for resources
|
||||
// Note: maxMemory will not be interpreted as a hard limit, only as a restriction
|
||||
// for resources which are not explicitly locked. However, a warning will be
|
||||
// issued whenever this limit is exceeded.
|
||||
int _maxMemoryLRU;
|
||||
|
||||
ViewType _viewType; // Used to determine if the game has EGA or VGA graphics
|
||||
typedef Common::List<ResourceSource *> SourcesList;
|
||||
SourcesList _sources;
|
||||
int _memoryLocked; ///< Amount of resource bytes in locked memory
|
||||
int _memoryLRU; ///< Amount of resource bytes under LRU control
|
||||
Common::List<Resource *> _LRU; ///< Last Resource Used list
|
||||
ResourceMap _resMap;
|
||||
Common::List<Common::File *> _volumeFiles; ///< list of opened volume files
|
||||
ResourceSource *_audioMapSCI1; ///< Currently loaded audio map for SCI1
|
||||
ResVersion _volVersion; ///< resource.0xx version
|
||||
ResVersion _mapVersion; ///< resource.map version
|
||||
bool _isSci2Mac;
|
||||
|
||||
/**
|
||||
* Add a path to the resource manager's list of sources.
|
||||
* @return a pointer to the added source structure, or NULL if an error occurred.
|
||||
*/
|
||||
ResourceSource *addPatchDir(const Common::Path &path);
|
||||
|
||||
ResourceSource *findVolume(ResourceSource *map, int volume_nr);
|
||||
|
||||
/**
|
||||
* Adds a source to the resource manager's list of sources.
|
||||
* @param source The new source to add
|
||||
* @return A pointer to the added source structure, or NULL if an error occurred.
|
||||
*/
|
||||
ResourceSource *addSource(ResourceSource *source);
|
||||
|
||||
/**
|
||||
* Add an external (i.e., separate file) map resource to the resource
|
||||
* manager's list of sources.
|
||||
* @param filename The name of the volume to add
|
||||
* @param volume_nr The volume number the map starts at, 0 for <SCI2.1
|
||||
* @return A pointer to the added source structure, or NULL if an error occurred.
|
||||
*/
|
||||
ResourceSource *addExternalMap(const Common::Path &filename, int volume_nr = 0);
|
||||
|
||||
ResourceSource *addExternalMap(const Common::FSNode *mapFile, int volume_nr = 0);
|
||||
|
||||
/**
|
||||
* Scans newly registered resource sources for resources, earliest addition first.
|
||||
* @param detected_version Pointer to the detected version number,
|
||||
* used during startup. May be NULL.
|
||||
* @return One of SCI_ERROR_*.
|
||||
*/
|
||||
void scanNewSources();
|
||||
|
||||
bool addAudioSources();
|
||||
void addScriptChunkSources();
|
||||
void freeResourceSources();
|
||||
|
||||
/**
|
||||
* Returns a string describing a ResVersion.
|
||||
* @param version The resource version
|
||||
* @return The description of version
|
||||
*/
|
||||
const char *versionDescription(ResVersion version) const;
|
||||
|
||||
/**
|
||||
* All calls to getVolumeFile must be followed with a corresponding
|
||||
* call to disposeVolumeFileStream once the stream is finished being used.
|
||||
* Do NOT call delete directly on returned streams, as they may be cached.
|
||||
*/
|
||||
Common::SeekableReadStream *getVolumeFile(ResourceSource *source);
|
||||
void disposeVolumeFileStream(Common::SeekableReadStream *fileStream, ResourceSource *source);
|
||||
void loadResource(Resource *res);
|
||||
void freeOldResources();
|
||||
bool validateResource(const ResourceId &resourceId, const Common::Path &sourceMapLocation, const Common::Path &sourceName, const uint32 offset, const uint32 size, const uint32 sourceSize) const;
|
||||
Resource *addResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size = 0, const Common::Path &sourceMapLocation = Common::Path("(no map location)"));
|
||||
Resource *updateResource(ResourceId resId, ResourceSource *src, uint32 size, const Common::Path &sourceMapLocation = Common::Path("(no map location)"));
|
||||
Resource *updateResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size, const Common::Path &sourceMapLocation = Common::Path("(no map location)"));
|
||||
void removeAudioResource(ResourceId resId);
|
||||
|
||||
/**--- Resource map decoding functions ---*/
|
||||
ResVersion detectMapVersion();
|
||||
ResVersion detectVolVersion();
|
||||
#ifdef ENABLE_SCI32
|
||||
bool detectSci2Mac();
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Reads the SCI0 resource.map file from a local directory.
|
||||
* @param map The map
|
||||
* @return 0 on success, an SCI_ERROR_* code otherwise
|
||||
*/
|
||||
int readResourceMapSCI0(ResourceSource *map);
|
||||
|
||||
/**
|
||||
* Reads the SCI1 resource.map file from a local directory.
|
||||
* @param map The map
|
||||
* @return 0 on success, an SCI_ERROR_* code otherwise
|
||||
*/
|
||||
int readResourceMapSCI1(ResourceSource *map);
|
||||
|
||||
/**
|
||||
* Reads SCI1.1 audio map resources.
|
||||
* @param map The map
|
||||
* @return 0 on success, an SCI_ERROR_* code otherwise
|
||||
*/
|
||||
int readAudioMapSCI11(IntMapResourceSource *map);
|
||||
|
||||
/**
|
||||
* Reads SCI1 audio map files.
|
||||
* @param map The map
|
||||
* @param unload Unload the map instead of loading it
|
||||
* @return 0 on success, an SCI_ERROR_* code otherwise
|
||||
*/
|
||||
int readAudioMapSCI1(ResourceSource *map, bool unload = false);
|
||||
|
||||
/**--- Patch management functions ---*/
|
||||
|
||||
/**
|
||||
* Reads patch files from a local directory.
|
||||
*/
|
||||
void readResourcePatches();
|
||||
void readResourcePatchesBase36();
|
||||
|
||||
/**
|
||||
* Determines whether or not a patch file matching the given resource ID
|
||||
* should be ignored when processing patches.
|
||||
*/
|
||||
bool isBlacklistedPatch(const ResourceId &resId) const;
|
||||
|
||||
void processPatch(ResourceSource *source, ResourceType resourceType, uint16 resourceNr, uint32 tuple = 0);
|
||||
|
||||
/**
|
||||
* Process wave files as patches for Audio resources.
|
||||
*/
|
||||
void readWaveAudioPatches();
|
||||
void processWavePatch(ResourceId resourceId, const Common::Path &name);
|
||||
|
||||
/**
|
||||
* Process AIFF files as patches for Audio resources.
|
||||
*/
|
||||
#ifdef ENABLE_SCI32
|
||||
void readAIFFAudioPatches();
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Applies to all versions before 0.000.395 (i.e. KQ4 old, XMAS 1988 and LSL2).
|
||||
* Old SCI versions used two word header for script blocks (first word equal
|
||||
* to 0x82, meaning of the second one unknown). New SCI versions used one
|
||||
* word header.
|
||||
* Also, old SCI versions assign 120 degrees to left & right, and 60 to up
|
||||
* and down. Later versions use an even 90 degree distribution.
|
||||
*/
|
||||
bool hasOldScriptHeader();
|
||||
|
||||
void addToLRU(Resource *res);
|
||||
void removeFromLRU(Resource *res);
|
||||
|
||||
ResourceCompression getViewCompression();
|
||||
ViewType detectViewType();
|
||||
bool hasSci0Voc999();
|
||||
bool hasSci1Voc900();
|
||||
bool checkResourceDataForSignature(Resource *resource, const byte *signature);
|
||||
bool checkResourceForSignatures(ResourceType resourceType, uint16 resourceNr, const byte *signature1, const byte *signature2);
|
||||
void detectSciVersion();
|
||||
|
||||
public:
|
||||
/** Returns the file name of the game's Mac executable. */
|
||||
Common::Path getMacExecutableName() const;
|
||||
bool isKoreanMessageMap(ResourceSource *source);
|
||||
|
||||
private:
|
||||
// For better or worse, because the patcher is added as a ResourceSource,
|
||||
// its destruction is managed by freeResourceSources.
|
||||
ResourcePatcher *_patcher;
|
||||
bool _hasBadResources;
|
||||
};
|
||||
|
||||
class SoundResource {
|
||||
public:
|
||||
struct Channel {
|
||||
byte number;
|
||||
byte flags;
|
||||
byte poly;
|
||||
uint16 prio;
|
||||
SciSpan<const byte> data;
|
||||
uint16 curPos;
|
||||
long time;
|
||||
byte prev;
|
||||
|
||||
Channel() :
|
||||
number(0),
|
||||
flags(0),
|
||||
poly(0),
|
||||
prio(0),
|
||||
data(),
|
||||
curPos(0) {
|
||||
time = 0;
|
||||
prev = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct Track {
|
||||
byte type;
|
||||
byte channelCount;
|
||||
SciSpan<const byte> header;
|
||||
Channel *channels;
|
||||
int16 digitalChannelNr;
|
||||
uint16 digitalSampleRate;
|
||||
uint16 digitalSampleSize;
|
||||
uint16 digitalSampleStart;
|
||||
uint16 digitalSampleEnd;
|
||||
};
|
||||
public:
|
||||
SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersion soundVersion);
|
||||
~SoundResource();
|
||||
#if 0
|
||||
Track *getTrackByNumber(uint16 number);
|
||||
#endif
|
||||
Track *getTrackByType(byte type);
|
||||
Track *getDigitalTrack();
|
||||
int getChannelFilterMask(int hardwareMask, bool wantsRhythm);
|
||||
#if 0
|
||||
byte getInitialVoiceCount(byte channel);
|
||||
#endif
|
||||
byte getSoundPriority() const { return _soundPriority; }
|
||||
bool exists() const { return _resource != nullptr; }
|
||||
|
||||
private:
|
||||
SciVersion _soundVersion;
|
||||
int _trackCount;
|
||||
Track *_tracks;
|
||||
Resource *_resource;
|
||||
ResourceManager *_resMan;
|
||||
byte _soundPriority;
|
||||
};
|
||||
|
||||
ResourceId convertPatchNameBase36(ResourceType type, const Common::String &filename);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_RESOURCE_RESOURCE_H
|
||||
1317
engines/sci/resource/resource_audio.cpp
Normal file
1317
engines/sci/resource/resource_audio.cpp
Normal file
File diff suppressed because it is too large
Load Diff
242
engines/sci/resource/resource_intern.h
Normal file
242
engines/sci/resource/resource_intern.h
Normal file
@@ -0,0 +1,242 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_RESOURCE_RESOURCE_INTERN_H
|
||||
#define SCI_RESOURCE_RESOURCE_INTERN_H
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
|
||||
namespace Common {
|
||||
class MacResManager;
|
||||
}
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum ResSourceType {
|
||||
kSourceDirectory = 0, ///< Directories containing game resources/patches
|
||||
kSourcePatch, ///< External resource patches
|
||||
kSourceVolume, ///< Game resources (resource.* or ressci.*)
|
||||
kSourceExtMap, ///< Non-audio resource maps
|
||||
kSourceIntMap, ///< SCI1.1 and later audio resource maps
|
||||
kSourceAudioVolume, ///< Audio resources - resource.sfx / resource.aud
|
||||
kSourceExtAudioMap, ///< SCI1 audio resource maps
|
||||
kSourceWave, ///< External WAVE files, patched in as sound resources
|
||||
kSourceMacResourceFork, ///< Mac SCI1.1 and later resource forks
|
||||
kSourceChunk, ///< Script chunk resources (*.chk)
|
||||
kSourceScummVM ///< Built-in resource patcher
|
||||
};
|
||||
|
||||
|
||||
class ResourceSource {
|
||||
protected:
|
||||
const ResSourceType _sourceType;
|
||||
const Common::Path _name;
|
||||
|
||||
public:
|
||||
bool _scanned;
|
||||
const Common::FSNode * const _resourceFile;
|
||||
const int _volumeNumber;
|
||||
|
||||
protected:
|
||||
ResourceSource(ResSourceType type, const Common::Path &name, int volNum = 0, const Common::FSNode *resFile = 0);
|
||||
public:
|
||||
virtual ~ResourceSource();
|
||||
|
||||
ResSourceType getSourceType() const { return _sourceType; }
|
||||
const Common::Path &getLocationName() const { return _name; }
|
||||
|
||||
// Auxiliary method, used by loadResource implementations.
|
||||
Common::SeekableReadStream *getVolumeFile(ResourceManager *resMan, Resource *res);
|
||||
|
||||
/**
|
||||
* TODO: Document this
|
||||
*/
|
||||
virtual ResourceSource *findVolume(ResourceSource *map, int volNum) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan this source for TODO.
|
||||
*/
|
||||
virtual void scanSource(ResourceManager *resMan) {}
|
||||
|
||||
/**
|
||||
* Load a resource.
|
||||
*/
|
||||
virtual void loadResource(ResourceManager *resMan, Resource *res);
|
||||
|
||||
// FIXME: This audio specific method is a hack. After all, why should a
|
||||
// ResourceSource or a Resource (which uses this method) have audio
|
||||
// specific methods? But for now we keep this, as it eases transition.
|
||||
virtual uint32 getAudioCompressionType() const { return 0; }
|
||||
};
|
||||
|
||||
class DirectoryResourceSource : public ResourceSource {
|
||||
public:
|
||||
DirectoryResourceSource(const Common::Path &name) : ResourceSource(kSourceDirectory, name) {}
|
||||
|
||||
void scanSource(ResourceManager *resMan) override;
|
||||
};
|
||||
|
||||
class PatchResourceSource : public ResourceSource {
|
||||
public:
|
||||
PatchResourceSource(const Common::Path &name) : ResourceSource(kSourcePatch, name) {}
|
||||
|
||||
void loadResource(ResourceManager *resMan, Resource *res) override;
|
||||
};
|
||||
|
||||
class VolumeResourceSource : public ResourceSource {
|
||||
protected:
|
||||
ResourceSource * const _associatedMap;
|
||||
|
||||
public:
|
||||
VolumeResourceSource(const Common::Path &name, ResourceSource *map, int volNum, ResSourceType type = kSourceVolume)
|
||||
: ResourceSource(type, name, volNum), _associatedMap(map) {
|
||||
}
|
||||
|
||||
VolumeResourceSource(const Common::Path &name, ResourceSource *map, int volNum, const Common::FSNode *resFile)
|
||||
: ResourceSource(kSourceVolume, name, volNum, resFile), _associatedMap(map) {
|
||||
}
|
||||
|
||||
ResourceSource *findVolume(ResourceSource *map, int volNum) override {
|
||||
if (_associatedMap == map && _volumeNumber == volNum)
|
||||
return this;
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
class ExtMapResourceSource : public ResourceSource {
|
||||
public:
|
||||
ExtMapResourceSource(const Common::Path &name, int volNum, const Common::FSNode *resFile = 0)
|
||||
: ResourceSource(kSourceExtMap, name, volNum, resFile) {
|
||||
}
|
||||
|
||||
void scanSource(ResourceManager *resMan) override;
|
||||
};
|
||||
|
||||
class IntMapResourceSource : public ResourceSource {
|
||||
public:
|
||||
uint16 _mapNumber;
|
||||
IntMapResourceSource(const Common::Path &name, int volNum, int mapNum)
|
||||
: ResourceSource(kSourceIntMap, name, volNum), _mapNumber(mapNum) {
|
||||
}
|
||||
|
||||
void scanSource(ResourceManager *resMan) override;
|
||||
};
|
||||
|
||||
class AudioVolumeResourceSource : public VolumeResourceSource {
|
||||
protected:
|
||||
struct CompressedTableEntry {
|
||||
uint32 offset;
|
||||
uint32 size;
|
||||
};
|
||||
|
||||
uint32 _audioCompressionType;
|
||||
Common::HashMap<uint32, CompressedTableEntry> _compressedOffsets;
|
||||
|
||||
public:
|
||||
AudioVolumeResourceSource(ResourceManager *resMan, const Common::Path &name, ResourceSource *map, int volNum);
|
||||
|
||||
void loadResource(ResourceManager *resMan, Resource *res) override;
|
||||
|
||||
uint32 getAudioCompressionType() const override;
|
||||
|
||||
bool relocateMapOffset(uint32 &offset, uint32 &size) const {
|
||||
if (_audioCompressionType == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_compressedOffsets.contains(offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const CompressedTableEntry &entry = _compressedOffsets.getVal(offset);
|
||||
offset = entry.offset;
|
||||
size = entry.size;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ExtAudioMapResourceSource : public ResourceSource {
|
||||
public:
|
||||
ExtAudioMapResourceSource(const Common::Path &name, int volNum)
|
||||
: ResourceSource(kSourceExtAudioMap, name, volNum) {
|
||||
}
|
||||
|
||||
void scanSource(ResourceManager *resMan) override;
|
||||
};
|
||||
|
||||
class WaveResourceSource : public ResourceSource {
|
||||
public:
|
||||
WaveResourceSource(const Common::Path &name) : ResourceSource(kSourceWave, name) {}
|
||||
|
||||
void loadResource(ResourceManager *resMan, Resource *res) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads SCI1.1+ resources from a Mac resource fork.
|
||||
*/
|
||||
class MacResourceForkResourceSource : public ResourceSource {
|
||||
public:
|
||||
MacResourceForkResourceSource(const Common::Path &name, int volNum);
|
||||
~MacResourceForkResourceSource() override;
|
||||
|
||||
void scanSource(ResourceManager *resMan) override;
|
||||
|
||||
void loadResource(ResourceManager *resMan, Resource *res) override;
|
||||
|
||||
protected:
|
||||
Common::MacResManager *_macResMan;
|
||||
|
||||
bool isCompressableResource(ResourceType type) const;
|
||||
void decompressResource(Common::SeekableReadStream *stream, Resource *resource) const;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
/**
|
||||
* Reads resources from SCI2.1+ chunk resources
|
||||
*/
|
||||
class ChunkResourceSource : public ResourceSource {
|
||||
public:
|
||||
ChunkResourceSource(const Common::Path &name, uint16 number);
|
||||
|
||||
void scanSource(ResourceManager *resMan) override;
|
||||
void loadResource(ResourceManager *resMan, Resource *res) override;
|
||||
|
||||
uint16 getNumber() const { return _number; }
|
||||
|
||||
protected:
|
||||
uint16 _number;
|
||||
|
||||
struct ResourceEntry {
|
||||
uint32 offset;
|
||||
uint32 length;
|
||||
};
|
||||
|
||||
Common::HashMap<ResourceId, ResourceEntry, ResourceIdHash> _resMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_RESOURCE_RESOURCE_INTERN_H
|
||||
745
engines/sci/resource/resource_patcher.cpp
Normal file
745
engines/sci/resource/resource_patcher.cpp
Normal file
@@ -0,0 +1,745 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/workarounds.h" // for SciMedia
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/resource/resource_patcher.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Start of internal resource patcher macros. Please do not use these directly
|
||||
// in resource patches.
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
#define _PACKINT32(n) (((uint32)n) & 0xFF), (((uint32)n) >> 8 & 0xFF), (((uint32)n) >> 16 & 0xFF), (((uint32)n) >> 24 & 0xFF)
|
||||
#else
|
||||
#define _PACKINT32(n) (((uint32)n) >> 24 & 0xFF), (((uint32)n) >> 16 & 0xFF), (((uint32)n) >> 8 & 0xFF), (((uint32)n) & 0xFF)
|
||||
#endif
|
||||
#define _BYTEOP(op, ...) op, _PACKINT32(NUMARGS(__VA_ARGS__)), __VA_ARGS__
|
||||
#define _NUMBEROP(op, type, value) op, sizeof(type), _PACKINT32(value)
|
||||
#define _FILLOP(op, numBytes, value) op, _PACKINT32(numBytes), value
|
||||
// End of internal resource patcher macros
|
||||
|
||||
/**
|
||||
* Advances the current position by `numBytes` bytes without changing any data.
|
||||
*/
|
||||
#define SKIP(numBytes) kSkipBytes, _PACKINT32(numBytes)
|
||||
|
||||
/**
|
||||
* Replaces data at the current position.
|
||||
*/
|
||||
#define REPLACE(...) _BYTEOP(kReplaceBytes, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Inserts new data at the current position.
|
||||
*/
|
||||
#define INSERT(...) _BYTEOP(kInsertBytes, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Replaces a number of the given type at the current position with the given
|
||||
* value.
|
||||
*/
|
||||
#define REPLACE_NUMBER(type, value) _NUMBEROP(kReplaceNumber, type, value)
|
||||
|
||||
/**
|
||||
* Adjusts a number of the given type at the current position by the given
|
||||
* delta.
|
||||
*/
|
||||
#define ADJUST_NUMBER(type, delta) _NUMBEROP(kAdjustNumber, type, delta)
|
||||
|
||||
/**
|
||||
* Inserts a number of the given type at the current position with the given
|
||||
* value.
|
||||
*/
|
||||
#define INSERT_NUMBER(type, value) _NUMBEROP(kInsertNumber, type, value)
|
||||
|
||||
/**
|
||||
* Replaces N bytes at the current position with the given value.
|
||||
*/
|
||||
#define REPLACE_FILL(value, numBytes) _FILLOP(kReplaceFill, numBytes, value)
|
||||
|
||||
/**
|
||||
* Inserts N bytes at the current position with the given value.
|
||||
*/
|
||||
#define INSERT_FILL(value, numBytes) _FILLOP(kInsertFill, numBytes, value)
|
||||
|
||||
/**
|
||||
* A required marker indicating that the end of the patch data has been reached
|
||||
* and no new patch operations will occur.
|
||||
*/
|
||||
#define END kEndOfPatch
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Laura Bow 2
|
||||
|
||||
// LB2CD removed diagonal walking loops from the views of actors in the museum,
|
||||
// but Sierra forgot to do this to the transparent placeholder view used when
|
||||
// actors are in a different room. It remains 9 loops instead of 5 like the
|
||||
// rest. The standing loop at the end is in position 8 instead of 4.
|
||||
// This causes StopWalk to query the loop count when an actor is off screen
|
||||
// and set their loop to an invalid index for their real view, causing all
|
||||
// standing actors to appear to face north west instead of their real heading.
|
||||
// Patching out diagonal loops 4-7 and using loop 8 as loop 4 fixes this.
|
||||
// See also: script patch laurabow2CDPatchFixMuseumActorLoops
|
||||
static const byte lauraBow2CdView828[] = {
|
||||
SKIP(0x02),
|
||||
REPLACE(0x05), // view header: 5 loops instead of 9
|
||||
SKIP(0x51),
|
||||
REPLACE(0x08), // loop 4: 8 cels instead of 1
|
||||
SKIP(0x09),
|
||||
REPLACE_NUMBER(uint32, 0x01c2), // loop 4: offset to loop 8 cel data
|
||||
END
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Leisure Suit Larry 1
|
||||
|
||||
// LSL1 Russian contains a bad sound that uses 0xFE as a track entry terminator
|
||||
// instead of the correct value 0xFF. This would freeze Sierra's interpreter
|
||||
// if it parsed this entry, but that usually wouldn't happen since it would
|
||||
// find a matching track entry with the correct terminator earlier in the list.
|
||||
// Sound 205 is the engine noise that plays when summoning a taxi.
|
||||
static const byte lsl1RussianSound205[] = {
|
||||
SKIP(0x31),
|
||||
REPLACE(0xFF),
|
||||
SKIP(0x29),
|
||||
REPLACE(0xFF),
|
||||
END
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Phantasmagoria
|
||||
|
||||
// Phantasmagoria view 64001 contains a bad palette that overwrites parts of the
|
||||
// palette used by the background picture in room 6400, causing the black
|
||||
// shadows to become tan, and many of the other background colors to end up a
|
||||
// little bit off. View 64001 renders fine using the existing palette created by
|
||||
// the background image, so just disable the embedded palette.
|
||||
static const byte phant1View64001Palette[] = {
|
||||
SKIP(8),
|
||||
REPLACE_NUMBER(uint32, 0),
|
||||
END
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Police Quest 4
|
||||
|
||||
// Police Quest 4 can support speech+subtitles mode but it includes no view that
|
||||
// can be used to show that this mode is active in the UI, so we have to add our
|
||||
// own.
|
||||
static const byte pq4EnhancedAudioToggleView[] = {
|
||||
INSERT_NUMBER(uint16, 16), // header size
|
||||
INSERT_NUMBER(uint8, 1), // loop count
|
||||
INSERT(0x00, 0x01), // unused
|
||||
INSERT_NUMBER(uint8, 0), // resolution flag
|
||||
INSERT_NUMBER(uint16, 0), // unused
|
||||
INSERT_NUMBER(uint32, 70), // palette offset
|
||||
INSERT_NUMBER(uint8, 16), // loop header size
|
||||
INSERT_NUMBER(uint8, 36), // cel header size
|
||||
INSERT_NUMBER(uint16, 640), // x-resolution
|
||||
INSERT_NUMBER(uint16, 480), // y-resolution
|
||||
INSERT_NUMBER(int8, -1), // alternate loop header
|
||||
INSERT_NUMBER(uint8, 0), // mirror flag
|
||||
INSERT_NUMBER(uint8, 1), // cel count
|
||||
INSERT_NUMBER(int32, -1), // unused
|
||||
INSERT_NUMBER(uint8, 0), // unused
|
||||
INSERT_NUMBER(uint32, 0), // unused
|
||||
INSERT_NUMBER(uint32, 34), // cel header offset
|
||||
INSERT_NUMBER(uint16, 85), // width
|
||||
INSERT_NUMBER(uint16, 23), // height
|
||||
INSERT_NUMBER(int16, 42), // x-origin
|
||||
INSERT_NUMBER(int16, 22), // y-origin
|
||||
INSERT_NUMBER(uint8, 255), // transparent color
|
||||
INSERT_NUMBER(uint8, 0), // compression type (none)
|
||||
INSERT_NUMBER(uint16, 0), // transparency/remap flags
|
||||
INSERT_NUMBER(uint32, 0), // unused
|
||||
INSERT_NUMBER(uint32, 0), // unused
|
||||
INSERT_NUMBER(uint32, 0), // unused
|
||||
INSERT_NUMBER(uint32, 0x46b), // data offset
|
||||
INSERT_NUMBER(uint32, 0), // unused (for compressed data)
|
||||
INSERT_NUMBER(uint32, 0), // unused (for compressed data)
|
||||
|
||||
// palette data
|
||||
INSERT_NUMBER(uint8, 14), // magic number
|
||||
INSERT_FILL(0x00, 9), // garbage
|
||||
INSERT_NUMBER(uint8, 1), // number of palettes
|
||||
INSERT_NUMBER(uint16, 0), // garbage
|
||||
INSERT_NUMBER(uint8, 22), // first palette offset
|
||||
INSERT_FILL(0x00, 11), // garbage
|
||||
INSERT_NUMBER(uint8, 0), // start color
|
||||
INSERT_FILL(0x00, 3), // garbage
|
||||
INSERT_NUMBER(uint16, 256), // number of colors
|
||||
INSERT_NUMBER(uint8, 1), // used
|
||||
INSERT_NUMBER(uint8, 0), // shared used
|
||||
INSERT_NUMBER(uint32, 0), // version
|
||||
INSERT( // color data
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x1B, 0x1B, 0x1B, 0x01, 0x2B,
|
||||
0x2F, 0x2B, 0x01, 0x33, 0x33, 0x33, 0x01, 0x37, 0x3B, 0x37,
|
||||
0x01, 0x47, 0x47, 0x47, 0x01, 0x4B, 0x4B, 0x4B, 0x01, 0x53,
|
||||
0x57, 0x53, 0x01, 0x63, 0x67, 0x63, 0x01, 0x6B, 0x6B, 0x6B,
|
||||
0x01, 0x6F, 0x77, 0x6F, 0x01, 0x7B, 0x7F, 0x7B, 0x01, 0x93,
|
||||
0x9B, 0x93, 0x01, 0xAF, 0xB3, 0xAB, 0x01, 0x0F, 0x17, 0x3F,
|
||||
0x01, 0x1F, 0x27, 0x57, 0x01, 0x2B, 0x43, 0x6F, 0x01, 0x5B,
|
||||
0x87, 0xA7, 0x01, 0x63, 0x3B, 0x1B, 0x01, 0x97, 0x63, 0x3F,
|
||||
0x01, 0xCB, 0x7B, 0x4B, 0x01, 0xE3, 0xA3, 0x63, 0x01, 0x00,
|
||||
0xAF, 0x27, 0x01, 0x00, 0x87, 0x27, 0x01, 0x00, 0x5F, 0x23,
|
||||
0x01, 0x8B, 0x6B, 0x53, 0x01, 0xAB, 0x87, 0x67, 0x01, 0xC7,
|
||||
0xA3, 0x73, 0x01, 0xEF, 0xDB, 0x9B, 0x01, 0x57, 0x2B, 0x1F,
|
||||
0x01, 0x7F, 0x27, 0x1F, 0x01, 0x8F, 0x3F, 0x33, 0x01, 0xBB,
|
||||
0x3F, 0x33, 0x01, 0xCB, 0x4F, 0x33, 0x01, 0x07, 0x07, 0xCB,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
|
||||
0xFF, 0xFF
|
||||
),
|
||||
INSERT_FILL(0x00, 872), // unused color entries
|
||||
|
||||
// pixel data
|
||||
INSERT(
|
||||
0x07, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x07, 0x05, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x00,
|
||||
0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04,
|
||||
0x04, 0x04, 0x16, 0x16, 0x04, 0x04, 0x16, 0x16, 0x16, 0x04,
|
||||
0x04, 0x16, 0x16, 0x16, 0x04, 0x16, 0x16, 0x16, 0x04, 0x04,
|
||||
0x16, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x05, 0x05, 0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x16, 0x16, 0x16, 0x04, 0x16, 0x16, 0x16,
|
||||
0x04, 0x16, 0x04, 0x04, 0x04, 0x16, 0x04, 0x16, 0x16, 0x16,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16,
|
||||
0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x16, 0x04, 0x04, 0x04,
|
||||
0x16, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x00,
|
||||
0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x16,
|
||||
0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04,
|
||||
0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16,
|
||||
0x04, 0x16, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x05, 0x05, 0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x04, 0x16, 0x04, 0x16, 0x04, 0x04, 0x04, 0x16, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x16, 0x16, 0x16, 0x04, 0x04, 0x16, 0x16, 0x16, 0x04,
|
||||
0x16, 0x16, 0x16, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16,
|
||||
0x16, 0x16, 0x16, 0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x00,
|
||||
0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x16, 0x16, 0x04, 0x04, 0x04, 0x16, 0x04,
|
||||
0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04,
|
||||
0x04, 0x16, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x05, 0x05, 0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04,
|
||||
0x16, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x00,
|
||||
0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x16,
|
||||
0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04,
|
||||
0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x16, 0x04, 0x04, 0x04,
|
||||
0x04, 0x16, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x05, 0x05, 0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x16, 0x04, 0x04, 0x16, 0x04, 0x04,
|
||||
0x04, 0x04, 0x16, 0x04, 0x16, 0x04, 0x04, 0x04, 0x16, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x16, 0x16, 0x04,
|
||||
0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x16, 0x16, 0x16, 0x04,
|
||||
0x16, 0x16, 0x16, 0x04, 0x04, 0x16, 0x16, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x00,
|
||||
0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x16,
|
||||
0x04, 0x04, 0x16, 0x16, 0x16, 0x04, 0x16, 0x04, 0x04, 0x04,
|
||||
0x16, 0x04, 0x04, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x05, 0x05, 0x04, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x00, 0x07, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x07, 0x05, 0x04, 0x00,
|
||||
0x07, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x07, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x00, 0x04, 0x04, 0x07, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x07, 0x04, 0x07, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x07,
|
||||
0x04, 0x04, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, 0x00, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x04, 0x04,
|
||||
0x05, 0x04, 0x04, 0x07, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
0x0a, 0x0a, 0x07, 0x04, 0x04, 0x05, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04
|
||||
),
|
||||
END
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Quest For Glory 1 VGA
|
||||
|
||||
// The QFG1VGA character stat sheet (pic 904) is drawn at night with unintended
|
||||
// palette colors. The Mac version fixed this, but did so by reworking the
|
||||
// day/night transitions. We fix this here by patching pic 904's alternate
|
||||
// palette to use the same colors as those in the embedded palette. Pic 904
|
||||
// is now drawn consistently and without interfering with any existing PalVary
|
||||
// that might be occurring. Fixes bug #10295
|
||||
static const byte qfg1vgaPalette904[] = {
|
||||
SKIP(254), // color 54
|
||||
REPLACE_NUMBER(uint8, 0x8b), // r
|
||||
REPLACE_NUMBER(uint8, 0x1f), // g
|
||||
REPLACE_NUMBER(uint8, 0x0f), // b
|
||||
SKIP(9), // color 57
|
||||
REPLACE_NUMBER(uint8, 0xe3), // r
|
||||
REPLACE_NUMBER(uint8, 0x7b), // g
|
||||
REPLACE_NUMBER(uint8, 0x6b), // b
|
||||
SKIP(1), // color 58
|
||||
REPLACE_NUMBER(uint8, 0xfb), // r
|
||||
REPLACE_NUMBER(uint8, 0xab), // g
|
||||
REPLACE_NUMBER(uint8, 0x93), // b
|
||||
SKIP(13), // color 62
|
||||
REPLACE_NUMBER(uint8, 0xbb), // r
|
||||
REPLACE_NUMBER(uint8, 0x6b), // g
|
||||
REPLACE_NUMBER(uint8, 0x23), // b
|
||||
SKIP(1), // color 63
|
||||
REPLACE_NUMBER(uint8, 0xdb), // r
|
||||
REPLACE_NUMBER(uint8, 0x7b), // g
|
||||
REPLACE_NUMBER(uint8, 0x23), // b
|
||||
SKIP(9), // color 66
|
||||
REPLACE_NUMBER(uint8, 0x5b), // r
|
||||
REPLACE_NUMBER(uint8, 0x33), // g
|
||||
REPLACE_NUMBER(uint8, 0x0f), // b
|
||||
SKIP(1), // color 67
|
||||
REPLACE_NUMBER(uint8, 0x7b), // r
|
||||
REPLACE_NUMBER(uint8, 0x53), // g
|
||||
REPLACE_NUMBER(uint8, 0x2b), // b
|
||||
SKIP(1), // color 68
|
||||
REPLACE_NUMBER(uint8, 0x9b), // r
|
||||
REPLACE_NUMBER(uint8, 0x6b), // g
|
||||
REPLACE_NUMBER(uint8, 0x3b), // b
|
||||
SKIP(1), // color 69
|
||||
REPLACE_NUMBER(uint8, 0xbb), // r
|
||||
REPLACE_NUMBER(uint8, 0x8b), // g
|
||||
REPLACE_NUMBER(uint8, 0x5b), // b
|
||||
SKIP(1), // color 70
|
||||
REPLACE_NUMBER(uint8, 0xdb), // r
|
||||
REPLACE_NUMBER(uint8, 0xb3), // g
|
||||
REPLACE_NUMBER(uint8, 0x7b), // b
|
||||
SKIP(1), // color 71
|
||||
REPLACE_NUMBER(uint8, 0xfb), // r
|
||||
REPLACE_NUMBER(uint8, 0xdb), // g
|
||||
REPLACE_NUMBER(uint8, 0xa3), // b
|
||||
END
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Torin passage Russian
|
||||
|
||||
// Picture 61101 is truncated in Russian version by city is truncated by
|
||||
// 7 bytes. Just put black. Other Russian versions also get patched as we don't
|
||||
// have a way to specify only one version but it ends up in appending few bytes
|
||||
// with no effect
|
||||
static const byte torinPassageRussianPic61101[] = {
|
||||
SKIP(199705),
|
||||
INSERT_FILL(0, 7),
|
||||
END
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Patch table
|
||||
|
||||
static const GameResourcePatch resourcePatches[] = {
|
||||
{ GID_LAURABOW2, SCI_MEDIA_CD, Common::UNK_LANG, kResourceTypeView, 828, lauraBow2CdView828, false },
|
||||
{ GID_LSL1, SCI_MEDIA_ALL, Common::RU_RUS, kResourceTypeSound, 205, lsl1RussianSound205, false },
|
||||
{ GID_PHANTASMAGORIA, SCI_MEDIA_ALL, Common::UNK_LANG, kResourceTypeView, 64001, phant1View64001Palette, false },
|
||||
{ GID_PQ4, SCI_MEDIA_CD, Common::EN_ANY, kResourceTypeView, 10988, pq4EnhancedAudioToggleView, true },
|
||||
{ GID_QFG1VGA, SCI_MEDIA_ALL, Common::UNK_LANG, kResourceTypePalette, 904, qfg1vgaPalette904, false },
|
||||
{ GID_TORIN, SCI_MEDIA_ALL, Common::RU_RUS, kResourceTypePic, 61101, torinPassageRussianPic61101,false }
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark ResourcePatcher
|
||||
|
||||
ResourcePatcher::ResourcePatcher(const SciGameId gameId, const bool isCD, const Common::Platform platform, const Common::Language gameLanguage) :
|
||||
ResourceSource(kSourceScummVM, "-scummvm-") {
|
||||
for (int i = 0; i < ARRAYSIZE(resourcePatches); ++i) {
|
||||
const GameResourcePatch &patch = resourcePatches[i];
|
||||
if (patch.gameId == gameId &&
|
||||
(patch.media == SCI_MEDIA_ALL ||
|
||||
(patch.media == SCI_MEDIA_FLOPPY && !isCD) ||
|
||||
(patch.media == SCI_MEDIA_CD && isCD) ||
|
||||
(patch.media == SCI_MEDIA_MAC && platform == Common::kPlatformMacintosh && !isCD)) &&
|
||||
(patch.gameLanguage == Common::UNK_LANG || patch.gameLanguage == gameLanguage)) {
|
||||
_patches.push_back(patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourcePatcher::applyPatch(Resource &resource) const {
|
||||
PatchList::const_iterator it;
|
||||
for (it = _patches.begin(); it != _patches.end(); ++it) {
|
||||
if (it->resourceType == resource.getType() && it->resourceNumber == resource.getNumber()) {
|
||||
debugC(kDebugLevelPatcher, "Applying resource patch to %s", resource._id.toString().c_str());
|
||||
patchResource(resource, *it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ResourcePatcher::scanSource(ResourceManager *resMan) {
|
||||
PatchList::const_iterator it;
|
||||
for (it = _patches.begin(); it != _patches.end(); ++it) {
|
||||
ResourceId resourceId(it->resourceType, it->resourceNumber);
|
||||
if (it->isNewResource && !resMan->testResource(resourceId)) {
|
||||
// Unlike other resources, ResourcePatcher does not have any files
|
||||
// to open to retrieve its resources, so the resource has to get
|
||||
// created and added manually instead of going through
|
||||
// `ResourceManager::addResource` or else the file validation will
|
||||
// blow up.
|
||||
Resource *res = new Resource(resMan, resourceId);
|
||||
res->_status = kResStatusNoMalloc;
|
||||
res->_source = this;
|
||||
res->_headerSize = 0;
|
||||
res->_fileOffset = 0;
|
||||
res->_size = 0;
|
||||
resMan->_resMap.setVal(resourceId, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourcePatcher::patchResource(Resource &resource, const GameResourcePatch &patch) const {
|
||||
const byte *oldData;
|
||||
const byte *source = resource.data();
|
||||
byte *target;
|
||||
|
||||
// New resources that came from ResourcePatcher need to get allocated or
|
||||
// else they will keep getting patched over themselves
|
||||
if (resource._source == this) {
|
||||
if (resource._status != kResStatusNoMalloc) {
|
||||
return;
|
||||
}
|
||||
|
||||
resource._status = kResStatusAllocated;
|
||||
}
|
||||
|
||||
const PatchSizes size = calculatePatchSizes(patch.patchData);
|
||||
if (size.expected > resource.size()) {
|
||||
ResourceId resourceId(patch.resourceType, patch.resourceNumber);
|
||||
warning("Unable to apply patch %s: patch expects at least %u bytes but resource is only %u bytes", resourceId.toString().c_str(), size.expected, resource.size());
|
||||
return;
|
||||
}
|
||||
|
||||
if (size.delta != 0) {
|
||||
// In the future it should be possible to have a negative size delta for
|
||||
// resources that need to be truncated, but for now just keep it
|
||||
// positive until there's a need for truncation
|
||||
assert(size.delta > 0);
|
||||
|
||||
const int32 newSize = resource.size() + size.delta;
|
||||
assert(newSize > 0);
|
||||
|
||||
target = new byte[newSize];
|
||||
|
||||
oldData = resource._data;
|
||||
resource._data = target;
|
||||
resource._size = newSize;
|
||||
} else {
|
||||
target = const_cast<byte *>(source);
|
||||
oldData = nullptr;
|
||||
}
|
||||
|
||||
const byte *patchData = patch.patchData;
|
||||
ResourcePatchOp op;
|
||||
while ((op = static_cast<ResourcePatchOp>(*patchData++)) != kEndOfPatch) {
|
||||
switch (op) {
|
||||
case kSkipBytes: {
|
||||
const int32 skipSize = readBlockSize(patchData);
|
||||
if (target != source) {
|
||||
memcpy(target, source, skipSize);
|
||||
}
|
||||
source += skipSize;
|
||||
target += skipSize;
|
||||
break;
|
||||
}
|
||||
case kReplaceBytes:
|
||||
case kInsertBytes: {
|
||||
const int32 blockSize = readBlockSize(patchData);
|
||||
memcpy(target, patchData, blockSize);
|
||||
patchData += blockSize;
|
||||
if (op == kReplaceBytes) {
|
||||
source += blockSize;
|
||||
}
|
||||
target += blockSize;
|
||||
break;
|
||||
}
|
||||
case kReplaceNumber:
|
||||
case kAdjustNumber:
|
||||
case kInsertNumber: {
|
||||
const uint8 width = *patchData++;
|
||||
assert(width == 1 || width == 2 || width == 4);
|
||||
|
||||
int32 value = *reinterpret_cast<const int32 *>(patchData);
|
||||
switch (width) {
|
||||
case 1:
|
||||
if (op == kAdjustNumber) {
|
||||
value += static_cast<int8>(*source);
|
||||
}
|
||||
assert(value >= -128 && value <= 255);
|
||||
*target = value;
|
||||
break;
|
||||
case 2:
|
||||
if (op == kAdjustNumber) {
|
||||
value += static_cast<int16>(READ_SCI11ENDIAN_UINT16(source));
|
||||
}
|
||||
assert(value >= -32768 && value <= 65535);
|
||||
WRITE_SCI11ENDIAN_UINT16(target, value);
|
||||
break;
|
||||
case 4:
|
||||
if (op == kAdjustNumber) {
|
||||
value += static_cast<int32>(READ_SCI11ENDIAN_UINT32(source));
|
||||
}
|
||||
WRITE_SCI11ENDIAN_UINT32(target, value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
patchData += sizeof(int32);
|
||||
if (op != kInsertNumber) {
|
||||
source += width;
|
||||
}
|
||||
target += width;
|
||||
break;
|
||||
}
|
||||
case kReplaceFill:
|
||||
case kInsertFill: {
|
||||
const int32 blockSize = readBlockSize(patchData);
|
||||
const byte value = *patchData++;
|
||||
memset(target, value, blockSize);
|
||||
if (op != kInsertFill) {
|
||||
source += blockSize;
|
||||
}
|
||||
target += blockSize;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error("Invalid control code %02x in patch data", op);
|
||||
}
|
||||
}
|
||||
|
||||
if (source && target != source) {
|
||||
memcpy(target, source, resource._size - (target - resource._data));
|
||||
}
|
||||
|
||||
delete[] oldData;
|
||||
}
|
||||
|
||||
ResourcePatcher::PatchSizes ResourcePatcher::calculatePatchSizes(const byte *patchData) const {
|
||||
int32 deltaSize = 0;
|
||||
uint32 dataSize = 0;
|
||||
|
||||
ResourcePatchOp op;
|
||||
while ((op = static_cast<ResourcePatchOp>(*patchData++)) != kEndOfPatch) {
|
||||
switch (op) {
|
||||
case kSkipBytes:
|
||||
case kReplaceBytes:
|
||||
case kInsertBytes: {
|
||||
const int32 blockSize = readBlockSize(patchData);
|
||||
|
||||
if (op == kReplaceBytes || op == kInsertBytes) {
|
||||
patchData += blockSize;
|
||||
}
|
||||
|
||||
if (op == kInsertBytes) {
|
||||
deltaSize += blockSize;
|
||||
} else {
|
||||
dataSize += blockSize;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kReplaceNumber:
|
||||
case kAdjustNumber:
|
||||
case kInsertNumber: {
|
||||
const uint8 width = *patchData++;
|
||||
assert(width == 1 || width == 2 || width == 4);
|
||||
|
||||
if (op == kInsertNumber) {
|
||||
deltaSize += width;
|
||||
} else {
|
||||
dataSize += width;
|
||||
}
|
||||
|
||||
patchData += /* value */ sizeof(int32);
|
||||
break;
|
||||
}
|
||||
case kReplaceFill:
|
||||
case kInsertFill: {
|
||||
const int32 blockSize = readBlockSize(patchData);
|
||||
/* value */ ++patchData;
|
||||
|
||||
if (op == kInsertFill) {
|
||||
deltaSize += blockSize;
|
||||
} else {
|
||||
dataSize += blockSize;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error("Invalid control code %02x in patch data", op);
|
||||
}
|
||||
}
|
||||
|
||||
return { dataSize, deltaSize };
|
||||
}
|
||||
|
||||
int32 ResourcePatcher::readBlockSize(const byte * &patchData) const {
|
||||
const int32 blockSize = *reinterpret_cast<const int32 *>(patchData);
|
||||
assert(blockSize >= 1);
|
||||
patchData += sizeof(int32);
|
||||
return blockSize;
|
||||
}
|
||||
} // End of namespace Sci
|
||||
157
engines/sci/resource/resource_patcher.h
Normal file
157
engines/sci/resource/resource_patcher.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_RESOURCE_RESOURCE_PATCHER_H
|
||||
#define SCI_RESOURCE_RESOURCE_PATCHER_H
|
||||
|
||||
#include "common/language.h"
|
||||
#include "sci/sci.h"
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/resource/resource_intern.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum ResourcePatchOp {
|
||||
// Using high bytes to make it less likely that accidental raw data will be
|
||||
// treated like an operator instead of an error
|
||||
kSkipBytes = 0xF0,
|
||||
kReplaceBytes,
|
||||
kInsertBytes,
|
||||
kReplaceNumber,
|
||||
kAdjustNumber,
|
||||
kInsertNumber,
|
||||
kReplaceFill,
|
||||
kInsertFill,
|
||||
kEndOfPatch
|
||||
};
|
||||
|
||||
enum SciMedia : uint;
|
||||
|
||||
struct GameResourcePatch {
|
||||
/**
|
||||
* The game to patch.
|
||||
*/
|
||||
SciGameId gameId;
|
||||
|
||||
/**
|
||||
* The media to patch. Use SCI_MEDIA_ALL for all.
|
||||
*/
|
||||
SciMedia media;
|
||||
|
||||
/**
|
||||
* The language to patch. Use `Common::UNK_LANG` to apply the patch to all
|
||||
* languages.
|
||||
*/
|
||||
Common::Language gameLanguage;
|
||||
|
||||
/**
|
||||
* The resource type to patch.
|
||||
*/
|
||||
ResourceType resourceType;
|
||||
|
||||
/**
|
||||
* The resource number to patch.
|
||||
*/
|
||||
uint16 resourceNumber;
|
||||
|
||||
/**
|
||||
* Patch instructions to apply to the resource.
|
||||
*/
|
||||
const byte *patchData;
|
||||
|
||||
/**
|
||||
* Set to true if the patch resource is actually a new resource, rather than
|
||||
* a patch for an existing resource.
|
||||
*/
|
||||
bool isNewResource;
|
||||
};
|
||||
|
||||
/**
|
||||
* A basic class for generating patched resource data at runtime.
|
||||
*/
|
||||
class ResourcePatcher : public ResourceSource {
|
||||
public:
|
||||
ResourcePatcher(const SciGameId gameId, const bool isCD, const Common::Platform platform, const Common::Language gameLanguage);
|
||||
|
||||
~ResourcePatcher() override {}
|
||||
|
||||
/**
|
||||
* Finds and applies a patch to the given resource.
|
||||
*
|
||||
* @returns true if a patch was applied.
|
||||
*/
|
||||
bool applyPatch(Resource &resource) const;
|
||||
|
||||
/**
|
||||
* Adds new resources from the patch table to the resource manager. This
|
||||
* is needed since otherwise tests for these resources via `kResCheck`
|
||||
* would fail, and so they would never actually be loaded.
|
||||
*/
|
||||
void scanSource(ResourceManager *resMan) override;
|
||||
|
||||
/**
|
||||
* Load a resource. Since resources using this source are patched explicitly
|
||||
* after they get loaded by any other resource source, this method does
|
||||
* nothing.
|
||||
*/
|
||||
void loadResource(ResourceManager *resMan, Resource *res) override {}
|
||||
|
||||
private:
|
||||
struct PatchSizes {
|
||||
/**
|
||||
* The minimum number of bytes required in the source data.
|
||||
*/
|
||||
uint32 expected;
|
||||
|
||||
/**
|
||||
* The difference in size between the original data and the patched
|
||||
* data.
|
||||
*/
|
||||
int32 delta;
|
||||
};
|
||||
|
||||
typedef Common::Array<GameResourcePatch> PatchList;
|
||||
|
||||
/**
|
||||
* The list of patches that should apply to the currently loaded game.
|
||||
*/
|
||||
PatchList _patches;
|
||||
|
||||
/**
|
||||
* Patches the given Resource using patch data from the given
|
||||
* GameResourcePatch.
|
||||
*/
|
||||
void patchResource(Resource &resource, const GameResourcePatch &patch) const;
|
||||
|
||||
/**
|
||||
* Calculates expected and extra data sizes from the patch data.
|
||||
*/
|
||||
PatchSizes calculatePatchSizes(const byte *patchData) const;
|
||||
|
||||
/**
|
||||
* Reads a block size from the patch data, validates it, and advances the
|
||||
* patch data pointer.
|
||||
*/
|
||||
int32 readBlockSize(const byte * &patchData) const;
|
||||
};
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_RESOURCE_RESOURCE_PATCHER_H
|
||||
Reference in New Issue
Block a user