Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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