scummvm/engines/glk/adrift/sctaffil.cpp

732 lines
20 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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 "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/adrift/detection.h"
#include "common/algorithm.h"
#include "common/compression/deflate.h"
#include "common/memstream.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Put integer and boolean read functions in here?
*/
/* Assorted definitions and constants. */
static const sc_uint TAF_MAGIC = 0x5bdcfa41;
enum {
VERSION_HEADER_SIZE = 14,
V400_HEADER_EXTRA = 8
};
enum {
OUT_BUFFER_SIZE = 31744,
IN_BUFFER_SIZE = 16384,
GROW_INCREMENT = 8
};
static const sc_char NEWLINE = '\n';
static const sc_char CARRIAGE_RETURN = '\r';
static const sc_char NUL = '\0';
/*
* Game TAF data structure. The game structure contains the original TAF
* file header, a growable array of "slab" descriptors, each of which holds
* metadata for a "slab" (around a decompression buffer full of TAF strings),
* the length of the descriptor array and elements allocated, and a current
* location for iteration.
*
* Saved game files (.TAS) are just like TAF files except that they lack the
* header. So for files of this type, the header is all zeroes.
*/
struct sc_slabdesc_t {
sc_byte *data;
sc_int size;
};
typedef sc_slabdesc_t *sc_slabdescref_t;
struct sc_taf_s {
sc_uint magic;
sc_byte header[VERSION_HEADER_SIZE + V400_HEADER_EXTRA];
sc_int version;
sc_int total_in_bytes;
sc_slabdescref_t slabs;
sc_int slab_count;
sc_int slabs_allocated;
sc_bool is_unterminated;
sc_int current_slab;
sc_int current_offset;
};
typedef sc_taf_s sc_taf_t;
/* Microsoft Visual Basic PRNG magic numbers, initial and current state. */
static const sc_int PRNG_CST1 = 0x43fd43fd,
PRNG_CST2 = 0x00c39ec3,
PRNG_CST3 = 0x00ffffff,
PRNG_INITIAL_STATE = 0x00a09e86;
static sc_int taf_random_state = 0x00a09e86;
/*
* taf_random()
* taf_random_reset()
*
* Version 3.9 and version 3.8 games are obfuscated by xor'ing each character
* with the PRNG in Visual Basic. So here we have to emulate that, to unob-
* fuscate data from such game files. The PRNG generates 0..prng_cst3, which
* we multiply by 255 and then divide by prng_cst3 + 1 to get output in the
* range 0..254. Thanks to Rik Snel for uncovering this obfuscation.
*/
static sc_byte taf_random(void) {
/* Generate and return the next pseudo-random number. */
taf_random_state = (taf_random_state * PRNG_CST1 + PRNG_CST2) & PRNG_CST3;
return (BYTE_MAX * (sc_uint) taf_random_state) / (sc_uint)(PRNG_CST3 + 1);
}
static void taf_random_reset(void) {
/* Reset PRNG to initial conditions. */
taf_random_state = PRNG_INITIAL_STATE;
}
/*
* taf_is_valid()
*
* Return TRUE if pointer is a valid TAF structure, FALSE otherwise.
*/
static sc_bool taf_is_valid(sc_tafref_t taf) {
return taf && taf->magic == TAF_MAGIC;
}
/*
* taf_create_empty()
*
* Allocate and return a new, empty TAF structure.
*/
static sc_tafref_t taf_create_empty(void) {
sc_tafref_t taf;
/* Create an empty TAF structure. */
taf = (sc_tafref_t)sc_malloc(sizeof(*taf));
taf->magic = TAF_MAGIC;
memset(taf->header, 0, sizeof(taf->header));
taf->version = TAF_VERSION_NONE;
taf->total_in_bytes = 0;
taf->slabs = nullptr;
taf->slab_count = 0;
taf->slabs_allocated = 0;
taf->is_unterminated = FALSE;
taf->current_slab = 0;
taf->current_offset = 0;
/* Return the new TAF structure. */
return taf;
}
/*
* taf_destroy()
*
* Free TAF memory, and destroy a TAF structure.
*/
void taf_destroy(sc_tafref_t taf) {
sc_int index_;
assert(taf_is_valid(taf));
/* First free each slab in the slabs array,... */
for (index_ = 0; index_ < taf->slab_count; index_++)
sc_free(taf->slabs[index_].data);
/*
* ...then free slabs growable array, and poison and free the TAF structure
* itself.
*/
sc_free(taf->slabs);
memset(taf, 0xaa, sizeof(*taf));
sc_free(taf);
}
/*
* taf_finalize_last_slab()
*
* Insert nul's into slab data so that it turns into a series of nul-terminated
* strings. Nul's are used to replace carriage return and newline pairs.
*/
static void taf_finalize_last_slab(sc_tafref_t taf) {
sc_slabdescref_t slab;
sc_int index_;
/* Locate the final slab in the slab descriptors array. */
assert(taf->slab_count > 0);
slab = taf->slabs + taf->slab_count - 1;
/*
* Replace carriage return and newline pairs with nuls, and individual
* carriage returns with a single newline.
*/
for (index_ = 0; index_ < slab->size; index_++) {
if (slab->data[index_] == CARRIAGE_RETURN) {
if (index_ < slab->size - 1 && slab->data[index_ + 1] == NEWLINE) {
slab->data[index_] = NUL;
slab->data[index_ + 1] = NUL;
index_++;
} else
slab->data[index_] = NEWLINE;
}
/* Also protect against unlikely incoming nul characters. */
else if (slab->data[index_] == NUL)
slab->data[index_] = NEWLINE;
}
}
/*
* taf_find_buffer_extent()
*
* Search backwards from the buffer end for a terminating carriage return and
* line feed. If none, found, return length and set is_unterminated to TRUE.
* Otherwise, return the count of usable bytes found in the buffer.
*/
static sc_int taf_find_buffer_extent(const sc_byte *buffer, sc_int length, sc_bool *is_unterminated) {
sc_int bytes;
/* Search backwards from the buffer end for the final line feed. */
for (bytes = length; bytes > 1; bytes--) {
if (buffer[bytes - 2] == CARRIAGE_RETURN && buffer[bytes - 1] == NEWLINE)
break;
}
if (bytes < 2) {
/* No carriage return and newline termination found. */
*is_unterminated = TRUE;
return length;
}
*is_unterminated = FALSE;
return bytes;
}
/*
* taf_append_buffer()
*
* Append a buffer of TAF lines to an existing TAF structure. Returns the
* number of characters consumed from the buffer.
*/
static sc_int taf_append_buffer(sc_tafref_t taf, const sc_byte *buffer, sc_int length) {
sc_int bytes;
sc_bool is_unterminated;
/* Locate the extent of appendable data in the buffer. */
bytes = taf_find_buffer_extent(buffer, length, &is_unterminated);
/* See if the last buffer handled contained at least one data line. */
if (!taf->is_unterminated) {
sc_slabdescref_t slab;
/* Extend the slabs array if we've reached the current allocation. */
if (taf->slab_count == taf->slabs_allocated) {
taf->slabs_allocated += GROW_INCREMENT;
taf->slabs = (sc_slabdescref_t)sc_realloc(taf->slabs,
taf->slabs_allocated * sizeof(*taf->slabs));
}
/* Advance to the next unused slab in the slab descriptors array. */
slab = taf->slabs + taf->slab_count;
taf->slab_count++;
/* Copy the input buffer into the new slab. */
slab->data = (sc_byte *)sc_malloc(bytes);
memcpy(slab->data, buffer, bytes);
slab->size = bytes;
} else {
sc_slabdescref_t slab;
/* Locate the final slab in the slab descriptors array. */
assert(taf->slab_count > 0);
slab = taf->slabs + taf->slab_count - 1;
/*
* The last buffer we saw had no line endings in it. In this case,
* append the input buffer to the end of the last slab's data, rather
* than creating a new slab. This may cause allocation to overflow
* the system limits on single allocated areas on some platforms.
*/
slab->data = (sc_byte *)sc_realloc(slab->data, slab->size + bytes);
memcpy(slab->data + slab->size, buffer, bytes);
slab->size += bytes;
/*
* Use a special case for the final carriage return and newline pairing
* that are split over two buffers; force correct termination of this
* slab.
*/
if (slab->size > 1
&& slab->data[slab->size - 2] == CARRIAGE_RETURN
&& slab->data[slab->size - 1] == NEWLINE)
is_unterminated = FALSE;
}
/*
* Note if this buffer requires that the next be coalesced with it. If it
* doesn't, finalize the last slab by breaking it into separate lines.
*/
taf->is_unterminated = is_unterminated;
if (!is_unterminated)
taf_finalize_last_slab(taf);
/* Return count of buffer bytes consumed. */
return bytes;
}
/*
* taf_unobfuscate()
*
* Unobfuscate a version 3.9 and version 3.8 TAF file from data read by
* repeated calls to the callback() function. Callback() should return the
* count of bytes placed in the buffer, or 0 if no more (end of file).
* Assumes that the file has been read past the header.
*/
static sc_bool taf_unobfuscate(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
sc_byte *buffer;
sc_int bytes, used_bytes, total_bytes, index_;
/* Reset the PRNG, and synchronize with the header already read. */
taf_random_reset();
for (index_ = 0; index_ < VERSION_HEADER_SIZE; index_++)
taf_random();
/*
* Malloc buffer, done to help systems with limited stacks, and initialize
* count of bytes read and used in the buffer to zero.
*/
buffer = (sc_byte *)sc_malloc(IN_BUFFER_SIZE);
used_bytes = 0;
total_bytes = 0;
/* Unobfuscate in buffer sized chunks. */
do {
/* Try to obtain more data. */
bytes = callback(opaque,
buffer + used_bytes, IN_BUFFER_SIZE - used_bytes);
/* Unobfuscate data read in. */
for (index_ = 0; index_ < bytes; index_++)
buffer[used_bytes + index_] ^= taf_random();
/*
* Add data read in and unobfuscated to buffer used data, and if
* unobfuscated data is available, add it to the TAF.
*/
used_bytes += bytes;
if (used_bytes > 0) {
sc_int consumed;
/* Add lines from this buffer to the TAF. */
consumed = taf_append_buffer(taf, buffer, used_bytes);
/* Move unused buffer data to buffer start. */
memmove(buffer, buffer + consumed, IN_BUFFER_SIZE - consumed);
/* Note counts of bytes consumed and remaining in the buffer. */
used_bytes -= consumed;
total_bytes += consumed;
}
} while (bytes > 0);
/*
* Unobfuscation completed, note the total bytes read. This value is
* actually not used for version 3.9 and version 3.8 games, but we maintain
* it just in case.
*/
taf->total_in_bytes = total_bytes;
if (is_gamefile)
taf->total_in_bytes += VERSION_HEADER_SIZE;
/* Check that we found the end of the input file as expected. */
if (used_bytes > 0) {
sc_error("taf_unobfuscate:"
" warning: %ld unhandled bytes in the buffer\n", used_bytes);
}
if (taf->is_unterminated)
sc_fatal("taf_unobfuscate: unterminated final data slab\n");
/* Return successfully. */
sc_free(buffer);
return TRUE;
}
#define BUFFER_SIZE 16384
/*
* taf_decompress()
*
* Decompress a version 4.0 TAF
*/
static sc_bool taf_decompress(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
Common::SeekableReadStream *src = (Common::SeekableReadStream *)opaque;
assert(src);
Common::MemoryWriteStreamDynamic dest(DisposeAfterUse::YES);
size_t startingPos = src->pos();
Common::SeekableReadStream *gzStream = wrapCompressedReadStream(src, DisposeAfterUse::NO);
if (!gzStream) {
return false;
}
dest.writeStream(gzStream);
if (!gzStream->eos() || gzStream->err()) {
delete gzStream;
return false;
}
delete gzStream;
// Iterate through pushing data out to the taf file
const byte *pTemp = dest.getData();
int bytesRemaining = dest.size();
while (bytesRemaining > 0) {
int consumed = taf_append_buffer(taf, pTemp, bytesRemaining);
bytesRemaining -= consumed;
}
taf->total_in_bytes = src->pos() - startingPos;
return true;
}
/*
* taf_read_raw()
*
* Read an uncompressed version 4.0 TAF save chunk used by ScummVM
*/
static sc_bool taf_read_raw(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
byte *buffer = new byte[BUFFER_SIZE];
size_t bytesRead, bytesLeft = 0;
size_t totalBytes, bytesWritten;
for (;;) {
bytesRead = callback(opaque, buffer + bytesLeft, BUFFER_SIZE - bytesLeft);
if ((bytesLeft + bytesRead) == 0)
break;
totalBytes = bytesLeft + bytesRead;
bytesWritten = taf_append_buffer(taf, buffer, totalBytes);
bytesLeft = totalBytes - bytesWritten;
if (bytesLeft)
Common::copy(buffer + bytesWritten, buffer + totalBytes, buffer);
}
delete[] buffer;
return true;
}
/*
* taf_create_from_callback()
*
* Create a TAF structure from data read in by repeated calls to the
* callback() function. Callback() should return the count of bytes placed
* in the buffer, or 0 if no more (end of file).
*/
static sc_tafref_t taf_create_from_callback(sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
sc_tafref_t taf;
sc_bool status = FALSE;
assert(callback);
/* Create an empty TAF structure. */
taf = taf_create_empty();
/*
* Determine the TAF file version in use. For saved games, we always use
* version 4.0 format. For others, it's determined from the header.
*/
if (is_gamefile) {
sc_int in_bytes;
/*
* Read in the ADRIFT header for game files. Start by reading in the
* shorter header common to all.
*/
in_bytes = callback(opaque, taf->header, VERSION_HEADER_SIZE);
if (in_bytes != VERSION_HEADER_SIZE) {
sc_error("taf_create: not enough data for standard TAF header\n");
taf_destroy(taf);
return nullptr;
}
/* Handle different TAF versions */
int version = AdriftMetaEngine::detectGameVersion(taf->header);
if (version == TAF_VERSION_500 || version == TAF_VERSION_390 ||
version == TAF_VERSION_380) {
taf->version = version;
} else if (version == TAF_VERSION_400) {
/* Read in the version 4.0 header extension. */
in_bytes = callback(opaque,
taf->header + VERSION_HEADER_SIZE,
V400_HEADER_EXTRA);
if (in_bytes != V400_HEADER_EXTRA) {
sc_error("taf_create:"
" not enough data for extended TAF header\n");
taf_destroy(taf);
return nullptr;
}
taf->version = TAF_VERSION_400;
} else {
taf_destroy(taf);
return nullptr;
}
} else {
/* Saved games are always considered to be for ScummVM, version 5.0. */
taf->version = TAF_VERSION_SAVE;
}
/*
* Call the appropriate game file reader function. For version 4.0 games,
* data is compressed with Zlib. For version 3.9 and version 3.8 games,
* it's obfuscated with the Visual Basic PRNG.
*/
switch (taf->version) {
case TAF_VERSION_SAVE:
status = taf_read_raw(taf, callback, opaque, is_gamefile);
break;
case TAF_VERSION_500:
sc_error("taf_create: ADRIFT 5 games are not yet supported");
break;
case TAF_VERSION_400:
status = taf_decompress(taf, callback, opaque, is_gamefile);
break;
case TAF_VERSION_390:
case TAF_VERSION_380:
status = taf_unobfuscate(taf, callback, opaque, is_gamefile);
break;
default:
sc_fatal("taf_create: invalid version\n");
}
if (!status) {
taf_destroy(taf);
return nullptr;
}
/* Return successfully. */
return taf;
}
/*
* taf_create()
* taf_create_tas()
*
* Public entry points for taf_create_from_callback(). Return a taf object
* constructed from either *.TAF (game) or *.TAS (saved game state) file data.
*/
sc_tafref_t taf_create(sc_read_callbackref_t callback, void *opaque) {
return taf_create_from_callback(callback, opaque, TRUE);
}
sc_tafref_t taf_create_tas(sc_read_callbackref_t callback, void *opaque) {
return taf_create_from_callback(callback, opaque, FALSE);
}
/*
* taf_first_line()
*
* Iterator rewind function, reset current slab location to TAF data start.
*/
void taf_first_line(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* Set current locations to TAF start. */
taf->current_slab = 0;
taf->current_offset = 0;
}
/*
* taf_next_line()
*
* Iterator function, return the next line of data from a TAF, or NULL
* if no more lines.
*/
const sc_char *taf_next_line(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* If there is a next line, return it and advance current. */
if (taf->current_slab < taf->slab_count) {
sc_char *line;
/* Get the effective address of the current line. */
line = (sc_char *) taf->slabs[taf->current_slab].data;
line += taf->current_offset;
/*
* Advance to the next line. The + 2 skips the NULs used to replace the
* carriage return and line feed.
*/
taf->current_offset += strlen(line) + 2;
if (taf->current_offset >= taf->slabs[taf->current_slab].size) {
taf->current_slab++;
taf->current_offset = 0;
}
return line;
}
/* No more lines, so return NULL. */
return nullptr;
}
/*
* taf_more_lines()
*
* Iterator end function, returns TRUE if more TAF lines are readable.
*/
sc_bool taf_more_lines(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* Return TRUE if not at TAF data end. */
return taf->current_slab < taf->slab_count;
}
/*
* taf_get_game_data_length()
*
* Returns the number of bytes read to decompress the game. Resources are
* appended to the TAF file after the game, so this value allows them to
* be located.
*/
sc_int taf_get_game_data_length(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/*
* Return the count of bytes inflated; this includes the TAF header length
* for TAF, rather than TAS, files. For TAS files, the count of file bytes
* read is irrelevant, and is never used.
*/
return taf->total_in_bytes;
}
/*
* taf_get_version()
*
* Return the version number of the TAF file, 400, 390, or 380.
*/
sc_int taf_get_version(sc_tafref_t taf) {
assert(taf_is_valid(taf));
assert(taf->version != TAF_VERSION_NONE);
return taf->version;
}
/*
* taf_debug_is_taf_string()
* taf_debug_dump()
*
* Print out a complete TAF structure. The first function is a helper for
* properties debugging, indicating if a given address is a string in a TAF
* slab, and therefore safe to print.
*/
sc_bool taf_debug_is_taf_string(sc_tafref_t taf, const void *addr) {
const sc_byte *const addr_ = (const sc_byte *)addr;
sc_int index_;
/*
* Compare pointer, by address directly, against all memory contained in
* the TAF slabs. Return TRUE if in range.
*/
for (index_ = 0; index_ < taf->slab_count; index_++) {
if (addr_ >= taf->slabs[index_].data
&& addr_ < taf->slabs[index_].data + taf->slabs[index_].size)
return TRUE;
}
return FALSE;
}
void taf_debug_dump(sc_tafref_t taf) {
sc_int index_, current_slab, current_offset;
assert(taf_is_valid(taf));
/* Dump complete structure. */
sc_trace("TAFfile: debug dump follows...\n");
sc_trace("taf->header =");
for (index_ = 0; index_ < (sc_int) sizeof(taf->header); index_++)
sc_trace(" %02x", taf->header[index_]);
sc_trace("\n");
sc_trace("taf->version = %s\n",
taf->version == TAF_VERSION_400 ? "4.00" :
taf->version == TAF_VERSION_390 ? "3.90" :
taf->version == TAF_VERSION_380 ? "3.80" : "[Unknown]");
sc_trace("taf->slabs = \n");
for (index_ = 0; index_ < taf->slab_count; index_++) {
sc_trace("%3ld : %p, %ld bytes\n", index_,
(void *)taf->slabs[index_].data, taf->slabs[index_].size);
}
sc_trace("taf->slab_count = %ld\n", taf->slab_count);
sc_trace("taf->slabs_allocated = %ld\n", taf->slabs_allocated);
sc_trace("taf->current_slab = %ld\n", taf->current_slab);
sc_trace("taf->current_offset = %ld\n", taf->current_offset);
/* Save current location. */
current_slab = taf->current_slab;
current_offset = taf->current_offset;
/* Print out taf lines using taf iterators. */
sc_trace("\ntaf iterators:\n");
taf_first_line(taf);
for (index_ = 0; taf_more_lines(taf); index_++)
sc_trace("%5ld %s\n", index_, taf_next_line(taf));
/* Restore current location. */
taf->current_slab = current_slab;
taf->current_offset = current_offset;
}
} // End of namespace Adrift
} // End of namespace Glk