scummvm/engines/glk/scott/decompress_z80.cpp

915 lines
25 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/>.
*
*/
/*
* Based on ScottFree interpreter version 1.14 developed by Swansea
* University Computer Society without disassembly of any other game
* drivers, only of game databases as permitted by EEC law (for purposes
* of compatibility).
*
* Licensed under GPLv2
*
* https://github.com/angstsmurf/spatterlight/tree/master/terps/scott
*/
#include "common/algorithm.h"
#include "common/array.h"
#include "common/textconsole.h"
#include "glk/scott/decompress_z80.h"
namespace Glk {
namespace Scott {
/* Sizes of some of the arrays in the snap structure */
const int SNAPSHOT_RAM_PAGES = 16;
//const int SNAPSHOT_SLT_PAGES = 256;
//const int SNAPSHOT_ZXATASP_PAGES = 32;
//const int SNAPSHOT_ZXCF_PAGES = 64;
//const int SNAPSHOT_DOCK_EXROM_PAGES = 8;
//const int SNAPSHOT_JOYSTICKS = 7;
//const int SNAPSHOT_DIVIDE_PAGES = 4;
void *libspectrumReallocN(void *ptr, size_t nmemb, size_t size) {
if (nmemb > SIZE_MAX / size)
error("libspectrumReallocN: Can't reallocate to required size");
return realloc(ptr, nmemb * size);
}
/* Ensure there is room for `requested' characters after the current
position `ptr' in `buffer'. If not, renew() and update the
pointers as necessary */
void libspectrumMakeRoom(uint8_t **dest, size_t requested, uint8_t **ptr,
size_t *allocated) {
size_t current_length = 0;
if (*allocated == 0) {
(*allocated) = requested;
*dest = new uint8_t[requested];
} else {
current_length = *ptr - *dest;
/* If there's already enough room here, just return */
if (current_length + requested <= (*allocated))
return;
/* Make the new size the maximum of the new needed size and the
old allocated size * 2 */
(*allocated) = current_length + requested > 2 * (*allocated)
? current_length + requested
: 2 * (*allocated);
*dest = static_cast<uint8_t *>(libspectrumReallocN(*dest, *allocated, sizeof(uint8_t)));
}
/* Update the secondary pointer to the block */
*ptr = *dest + current_length;
}
struct LibspectrumSnap {
LibspectrumSnap() : pages(SNAPSHOT_RAM_PAGES) {}
/* Which machine are we using here? */
int machine;
/* Registers and the like */
uint16_t pc;
Common::Array<uint8_t *> pages;
};
/* Error handling */
/* The various errors which can occur */
enum LibspectrumError {
LIBSPECTRUM_ERROR_NONE = 0,
LIBSPECTRUM_ERROR_WARNING,
LIBSPECTRUM_ERROR_MEMORY,
LIBSPECTRUM_ERROR_UNKNOWN,
LIBSPECTRUM_ERROR_CORRUPT,
LIBSPECTRUM_ERROR_SIGNATURE,
LIBSPECTRUM_ERROR_SLT, /* .slt data found at end of a .z80 file */
LIBSPECTRUM_ERROR_INVALID, /* Invalid parameter supplied */
LIBSPECTRUM_ERROR_LOGIC = -1,
};
enum LibspectrumMachine {
LIBSPECTRUM_MACHINE_48,
LIBSPECTRUM_MACHINE_TC2048,
LIBSPECTRUM_MACHINE_128,
LIBSPECTRUM_MACHINE_PLUS2,
LIBSPECTRUM_MACHINE_PENT,
LIBSPECTRUM_MACHINE_PLUS2A,
LIBSPECTRUM_MACHINE_PLUS3,
/* Used by libspectrum_tape_guess_hardware if we can't work out what
hardware should be used */
LIBSPECTRUM_MACHINE_UNKNOWN,
LIBSPECTRUM_MACHINE_16,
LIBSPECTRUM_MACHINE_TC2068,
LIBSPECTRUM_MACHINE_SCORP,
LIBSPECTRUM_MACHINE_PLUS3E,
LIBSPECTRUM_MACHINE_SE,
LIBSPECTRUM_MACHINE_TS2068,
LIBSPECTRUM_MACHINE_PENT512,
LIBSPECTRUM_MACHINE_PENT1024,
LIBSPECTRUM_MACHINE_48_NTSC,
LIBSPECTRUM_MACHINE_128E,
};
enum LibspectrumMachineCapability {
LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY = (1u << 0),
LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_MEMORY = (1u << 1),
LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY = (1u << 2),
LIBSPECTRUM_MACHINE_CAPABILITY_SCORP_MEMORY = (1u << 3),
LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY = (1u << 4),
LIBSPECTRUM_MACHINE_CAPABILITY_PENT512_MEMORY = (1u << 5),
LIBSPECTRUM_MACHINE_CAPABILITY_PENT1024_MEMORY = (1u << 6),
};
/* Given a machine type, what features does it have? */
int libspectrumMachineCapabilities(LibspectrumMachine type) {
int capabilities = 0;
/* 128K Spectrum-style 0x7ffd memory paging */
switch (type) {
case LIBSPECTRUM_MACHINE_128:
case LIBSPECTRUM_MACHINE_PLUS2:
case LIBSPECTRUM_MACHINE_PLUS2A:
case LIBSPECTRUM_MACHINE_PLUS3:
case LIBSPECTRUM_MACHINE_PLUS3E:
case LIBSPECTRUM_MACHINE_128E:
case LIBSPECTRUM_MACHINE_PENT:
case LIBSPECTRUM_MACHINE_PENT512:
case LIBSPECTRUM_MACHINE_PENT1024:
case LIBSPECTRUM_MACHINE_SCORP:
/* FIXME: SE needs to have this capability to be considered a 128k machine
*/
case LIBSPECTRUM_MACHINE_SE:
capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY;
break;
default:
break;
}
/* +3 Spectrum-style 0x1ffd memory paging */
switch (type) {
case LIBSPECTRUM_MACHINE_PLUS2A:
case LIBSPECTRUM_MACHINE_PLUS3:
case LIBSPECTRUM_MACHINE_PLUS3E:
case LIBSPECTRUM_MACHINE_128E:
capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_MEMORY;
break;
default:
break;
}
/* T[CS]20[46]8-style 0x00fd memory paging */
switch (type) {
case LIBSPECTRUM_MACHINE_TC2048:
case LIBSPECTRUM_MACHINE_TC2068:
case LIBSPECTRUM_MACHINE_TS2068:
capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY;
break;
default:
break;
}
/* Scorpion-style 0x1ffd memory paging */
switch (type) {
case LIBSPECTRUM_MACHINE_SCORP:
capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_SCORP_MEMORY;
break;
default:
break;
}
/* SE-style 0x7ffd and 0x00fd memory paging */
switch (type) {
case LIBSPECTRUM_MACHINE_SE:
capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY;
break;
default:
break;
}
/* Pentagon 512-style memory paging */
switch (type) {
case LIBSPECTRUM_MACHINE_PENT512:
case LIBSPECTRUM_MACHINE_PENT1024:
capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_PENT512_MEMORY;
break;
default:
break;
}
/* Pentagon 1024-style memory paging */
switch (type) {
case LIBSPECTRUM_MACHINE_PENT1024:
capabilities |= LIBSPECTRUM_MACHINE_CAPABILITY_PENT1024_MEMORY;
break;
default:
break;
}
return capabilities;
}
/* Length of the basic .z80 headers */
static const int LIBSPECTRUM_Z80_HEADER_LENGTH = 30;
/* Length of the v2 extensions */
#define LIBSPECTRUM_Z80_V2_LENGTH 23
/* Length of the v3 extensions */
#define LIBSPECTRUM_Z80_V3_LENGTH 54
/* Length of xzx's extensions */
#define LIBSPECTRUM_Z80_V3X_LENGTH 55
/* The constants used for each machine type */
enum {
/* v2 constants */
Z80_MACHINE_48_V2 = 0,
Z80_MACHINE_48_IF1_V2 = 1,
Z80_MACHINE_48_SAMRAM_V2 = 2,
Z80_MACHINE_128_V2 = 3,
Z80_MACHINE_128_IF1_V2 = 4,
/* v3 constants */
Z80_MACHINE_48 = 0,
Z80_MACHINE_48_IF1 = 1,
Z80_MACHINE_48_SAMRAM = 2,
Z80_MACHINE_48_MGT = 3,
Z80_MACHINE_128 = 4,
Z80_MACHINE_128_IF1 = 5,
Z80_MACHINE_128_MGT = 6,
/* Extensions */
Z80_MACHINE_PLUS3 = 7,
Z80_MACHINE_PLUS3_XZX_ERROR = 8,
Z80_MACHINE_PENTAGON = 9,
Z80_MACHINE_SCORPION = 10,
Z80_MACHINE_PLUS2 = 12,
Z80_MACHINE_PLUS2A = 13,
Z80_MACHINE_TC2048 = 14,
Z80_MACHINE_TC2068 = 15,
Z80_MACHINE_TS2068 = 128,
/* The first extension ID; anything here or greater applies to both
v2 and v3 files */
Z80_MACHINE_FIRST_EXTENSION = Z80_MACHINE_PLUS3,
};
static LibspectrumError readHeader(const uint8_t *buffer, LibspectrumSnap *snap, const uint8_t **data, int *version, int *compressed);
static LibspectrumError getMachineType(LibspectrumSnap *snap, uint8_t type, uint8_t mgt_type, int version);
static LibspectrumError getMachineTypeV2(LibspectrumSnap *snap, uint8_t type);
static LibspectrumError getMachineTypeV3(LibspectrumSnap *snap, uint8_t type, uint8_t mgt_type);
static LibspectrumError getMachineTypeExtension(LibspectrumSnap *snap, uint8_t type);
static LibspectrumError readBlocks(const uint8_t *buffer, size_t buffer_length, LibspectrumSnap *snap, int version, int compressed);
static LibspectrumError readBlock(const uint8_t *buffer, LibspectrumSnap *snap, const uint8_t **next_block, const uint8_t *end, int version, int compressed);
static LibspectrumError readV1Block(const uint8_t *buffer, int is_compressed, uint8_t **uncompressed, const uint8_t **next_block, const uint8_t *end);
static LibspectrumError readV2Block(const uint8_t *buffer, uint8_t **block, size_t *length, int *page, const uint8_t **next_block, const uint8_t *end);
static void uncompressBlock(uint8_t **dest, size_t *dest_length, const uint8_t *src, size_t src_length);
void libspectrumSnapSetMachine(LibspectrumSnap *snap, int val) {
snap->machine = val;
}
uint8_t *libspectrumSnapPages(LibspectrumSnap *snap, int page) {
return snap->pages[page];
}
void libspectrumSnapSetPages(LibspectrumSnap *snap, int page, uint8_t *buf) {
snap->pages[page] = buf;
}
void libspectrumPrintError(LibspectrumError error) {
switch (error) {
case LIBSPECTRUM_ERROR_WARNING:
warning("warning");
break;
case LIBSPECTRUM_ERROR_MEMORY:
warning("memory error");
break;
case LIBSPECTRUM_ERROR_UNKNOWN:
warning("unknown error");
break;
case LIBSPECTRUM_ERROR_CORRUPT:
warning("corruption error");
break;
case LIBSPECTRUM_ERROR_SIGNATURE:
warning("signature error");
break;
case LIBSPECTRUM_ERROR_SLT:
warning("SLT data in Z80 error");
break;
case LIBSPECTRUM_ERROR_INVALID:
warning("invalid parameter error");
break;
case LIBSPECTRUM_ERROR_LOGIC:
warning("logic error");
break;
default:
warning("unhandled error");
break;
}
}
int libspectrumSnapMachine(LibspectrumSnap *snap) {
return snap->machine;
}
/* Read an LSB dword from buffer */
uint32_t libspectrumReadDword(const uint8_t **buffer) {
uint32_t value;
value = (*buffer)[0] + (*buffer)[1] * 0x100 + (*buffer)[2] * 0x10000 + (*buffer)[3] * 0x1000000;
(*buffer) += 4;
return value;
}
static LibspectrumError getMachineTypeExtension(LibspectrumSnap *snap, uint8_t type) {
switch (type) {
case Z80_MACHINE_PLUS3:
case Z80_MACHINE_PLUS3_XZX_ERROR:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_PLUS3);
break;
case Z80_MACHINE_PENTAGON:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_PENT);
break;
case Z80_MACHINE_SCORPION:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_SCORP);
break;
case Z80_MACHINE_PLUS2:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_PLUS2);
break;
case Z80_MACHINE_PLUS2A:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_PLUS2A);
break;
case Z80_MACHINE_TC2048:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_TC2048);
break;
case Z80_MACHINE_TC2068:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_TC2068);
break;
case Z80_MACHINE_TS2068:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_TS2068);
break;
default:
libspectrumPrintError(LIBSPECTRUM_ERROR_UNKNOWN);
warning("%s:get_machine_type: unknown extension machine type %d", __FILE__, type);
return LIBSPECTRUM_ERROR_UNKNOWN;
}
return LIBSPECTRUM_ERROR_NONE;
}
LibspectrumError internalZ80Read(LibspectrumSnap *snap, const uint8_t *buffer, size_t buffer_length);
uint8_t *decompressZ80(uint8_t *raw_data, size_t length) {
LibspectrumSnap *snap = new LibspectrumSnap;
for (int i = 0; i < SNAPSHOT_RAM_PAGES; i++)
libspectrumSnapSetPages(snap, i, nullptr);
if (internalZ80Read(snap, raw_data, length) != LIBSPECTRUM_ERROR_NONE) {
return nullptr;
}
uint8_t *uncompressed = new uint8_t[0xC000];
if (uncompressed == nullptr)
return nullptr;
Common::copy(snap->pages[5], snap->pages[5] + 0x4000, uncompressed);
Common::copy(snap->pages[2], snap->pages[2] + 0x4000, uncompressed + 0x4000);
Common::copy(snap->pages[0], snap->pages[0] + 0x4000, uncompressed + 0x8000);
for (int i = 0; i < SNAPSHOT_RAM_PAGES; i++)
if (snap->pages[i] != nullptr)
delete snap->pages[i];
delete snap;
return uncompressed;
}
LibspectrumError internalZ80Read(LibspectrumSnap *snap, const uint8_t *buffer, size_t buffer_length) {
LibspectrumError error;
const uint8_t *data;
int version, compressed = 1;
error = readHeader(buffer, snap, &data, &version, &compressed);
if (error != LIBSPECTRUM_ERROR_NONE)
return error;
error = readBlocks(data, buffer_length - (data - buffer), snap, version,
compressed);
if (error != LIBSPECTRUM_ERROR_NONE)
return error;
return LIBSPECTRUM_ERROR_NONE;
}
static LibspectrumError readHeader(const uint8_t *buffer, LibspectrumSnap *snap, const uint8_t **data, int *version, int *compressed) {
const uint8_t *header = buffer;
LibspectrumError error;
uint8_t header_6 = header[6];
uint8_t header_7 = header[7] * 0x100;
snap->pc = header_6 + header_7;
if (snap->pc == 0) { /* PC == 0x0000 => v2 or greater */
size_t extra_length;
const uint8_t *extra_header;
extra_length = header[LIBSPECTRUM_Z80_HEADER_LENGTH] + header[LIBSPECTRUM_Z80_HEADER_LENGTH + 1] * 0x100;
switch (extra_length) {
case LIBSPECTRUM_Z80_V2_LENGTH:
*version = 2;
break;
case LIBSPECTRUM_Z80_V3_LENGTH:
case LIBSPECTRUM_Z80_V3X_LENGTH:
*version = 3;
break;
default:
libspectrumPrintError(LIBSPECTRUM_ERROR_UNKNOWN);
warning("libspectrumReadZ80Header: unknown header length %d", (int)extra_length);
return LIBSPECTRUM_ERROR_UNKNOWN;
}
extra_header = buffer + LIBSPECTRUM_Z80_HEADER_LENGTH + 2;
snap->pc = extra_header[0] + extra_header[1] * 0x100;
error = getMachineType(snap, extra_header[2], extra_header[51], *version);
if (error)
return error;
if (extra_header[5] & 0x80) {
switch (libspectrumSnapMachine(snap)) {
case LIBSPECTRUM_MACHINE_48:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_16);
break;
case LIBSPECTRUM_MACHINE_128:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_PLUS2);
break;
case LIBSPECTRUM_MACHINE_PLUS3:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_PLUS2A);
break;
default:
break; /* Do nothing */
}
}
(*data) = buffer + LIBSPECTRUM_Z80_HEADER_LENGTH + 2 + extra_length;
} else { /* v1 .z80 file */
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_48);
*version = 1;
/* Need to flag this for later */
*compressed = (header[12] & 0x20) ? 1 : 0;
(*data) = buffer + LIBSPECTRUM_Z80_HEADER_LENGTH;
}
return LIBSPECTRUM_ERROR_NONE;
}
static LibspectrumError getMachineType(LibspectrumSnap *snap, uint8_t type, uint8_t mgt_type, int version) {
LibspectrumError error;
if (type < Z80_MACHINE_FIRST_EXTENSION) {
switch (version) {
case 2:
error = getMachineTypeV2(snap, type);
if (error)
return error;
break;
case 3:
error = getMachineTypeV3(snap, type, mgt_type);
if (error)
return error;
break;
default:
libspectrumPrintError(LIBSPECTRUM_ERROR_LOGIC);
warning("%s:getMachineType: unknown version %d", __FILE__, version);
return LIBSPECTRUM_ERROR_LOGIC;
}
} else {
error = getMachineTypeExtension(snap, type);
if (error)
return error;
}
return LIBSPECTRUM_ERROR_NONE;
}
static LibspectrumError getMachineTypeV2(LibspectrumSnap *snap, uint8_t type) {
switch (type) {
case Z80_MACHINE_48_V2:
case Z80_MACHINE_48_SAMRAM_V2:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_48);
break;
case Z80_MACHINE_48_IF1_V2:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_48);
break;
case Z80_MACHINE_128_V2:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_128);
break;
case Z80_MACHINE_128_IF1_V2:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_128);
break;
default:
libspectrumPrintError(LIBSPECTRUM_ERROR_UNKNOWN);
warning("%s: getMachineType: unknown v2 machine type %d", __FILE__, type);
return LIBSPECTRUM_ERROR_UNKNOWN;
}
return LIBSPECTRUM_ERROR_NONE;
}
static LibspectrumError getMachineTypeV3(LibspectrumSnap *snap, uint8_t type, uint8_t mgt_type) {
switch (type) {
case Z80_MACHINE_48:
case Z80_MACHINE_48_SAMRAM:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_48);
break;
case Z80_MACHINE_48_IF1:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_48);
break;
case Z80_MACHINE_48_MGT:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_48);
break;
case Z80_MACHINE_128:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_128);
break;
case Z80_MACHINE_128_MGT:
libspectrumSnapSetMachine(snap, LIBSPECTRUM_MACHINE_128);
break;
default:
libspectrumPrintError(LIBSPECTRUM_ERROR_UNKNOWN);
warning("%s:getMachineType: unknown v3 machine type %d", __FILE__, type);
return LIBSPECTRUM_ERROR_UNKNOWN;
}
return LIBSPECTRUM_ERROR_NONE;
}
/* Given a 48K memory dump `data', place it into the
appropriate bits of `snap' for a 48K machine */
LibspectrumError libspectrumSplitTo48kPages(LibspectrumSnap *snap, const uint8_t *data) {
uint8_t *buffer[3];
size_t i;
/* If any of the three pages are already occupied, barf */
if (libspectrumSnapPages(snap, 5) || libspectrumSnapPages(snap, 2) || libspectrumSnapPages(snap, 0)) {
libspectrumPrintError(LIBSPECTRUM_ERROR_LOGIC);
warning("libspectrumSplitTo48kPages: RAM page already in use");
return LIBSPECTRUM_ERROR_LOGIC;
}
for (i = 0; i < 3; i++) {
buffer[i] = new uint8_t[0x4000];
}
libspectrumSnapSetPages(snap, 5, buffer[0]);
libspectrumSnapSetPages(snap, 2, buffer[1]);
libspectrumSnapSetPages(snap, 0, buffer[2]);
/* Finally, do the copies... */
Common::copy(&data[0x0000], &data[0x0000] + 0x4000, libspectrumSnapPages(snap, 5));
Common::copy(&data[0x4000], &data[0x4000] + 0x4000, libspectrumSnapPages(snap, 2));
Common::copy(&data[0x8000], &data[0x8000] + 0x4000, libspectrumSnapPages(snap, 0));
return LIBSPECTRUM_ERROR_NONE;
}
static LibspectrumError readBlocks(const uint8_t *buffer, size_t buffer_length, LibspectrumSnap *snap, int version, int compressed) {
const uint8_t *end, *next_block;
end = buffer + buffer_length;
next_block = buffer;
while (next_block < end) {
LibspectrumError error;
error = readBlock(next_block, snap, &next_block, end, version, compressed);
if (error != LIBSPECTRUM_ERROR_NONE)
return error;
}
return LIBSPECTRUM_ERROR_NONE;
}
static LibspectrumError readBlock(const uint8_t *buffer, LibspectrumSnap *snap, const uint8_t **next_block, const uint8_t *end, int version, int compressed) {
LibspectrumError error;
uint8_t *uncompressed;
int capabilities = libspectrumMachineCapabilities(static_cast<LibspectrumMachine>(libspectrumSnapMachine(snap)));
if (version == 1) {
error = readV1Block(buffer, compressed, &uncompressed, next_block, end);
if (error != LIBSPECTRUM_ERROR_NONE)
return error;
libspectrumSplitTo48kPages(snap, uncompressed);
delete[] uncompressed;
} else {
size_t length;
int page;
error = readV2Block(buffer, &uncompressed, &length, &page, next_block, end);
if (error != LIBSPECTRUM_ERROR_NONE)
return error;
if (page <= 0 || page > 18) {
libspectrumPrintError(LIBSPECTRUM_ERROR_UNKNOWN);
warning("readBlock: unknown page %d", page);
delete[] uncompressed;
return LIBSPECTRUM_ERROR_UNKNOWN;
}
/* If it's a ROM page, just throw it away */
if (page < 3) {
delete[] uncompressed;
return LIBSPECTRUM_ERROR_NONE;
}
/* Page 11 is the Multiface ROM unless we're emulating something
Scorpion-like */
if (page == 11 && !(capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SCORP_MEMORY)) {
delete[] uncompressed;
return LIBSPECTRUM_ERROR_NONE;
}
/* Deal with 48K snaps -- first, throw away page 3, as it's a ROM.
Then remap the numbers slightly */
if (!(capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY)) {
switch (page) {
case 3:
delete[] uncompressed;
return LIBSPECTRUM_ERROR_NONE;
case 4:
page = 5;
break;
case 5:
page = 3;
break;
}
}
/* Now map onto RAM page numbers */
page -= 3;
if (libspectrumSnapPages(snap, page) == nullptr) {
libspectrumSnapSetPages(snap, page, uncompressed);
} else {
delete[] uncompressed;
libspectrumPrintError(LIBSPECTRUM_ERROR_UNKNOWN);
warning("readBlock: page %d duplicated", page);
return LIBSPECTRUM_ERROR_CORRUPT;
}
}
return LIBSPECTRUM_ERROR_NONE;
}
static LibspectrumError readV1Block(const uint8_t *buffer, int is_compressed, uint8_t **uncompressed, const uint8_t **next_block, const uint8_t *end) {
if (is_compressed) {
const uint8_t *ptr;
int state;
size_t uncompressed_length = 0;
state = 0;
ptr = buffer;
while (state != 4) {
if (ptr == end) {
libspectrumPrintError(LIBSPECTRUM_ERROR_CORRUPT);
warning("readV1Block: end marker not found");
return LIBSPECTRUM_ERROR_CORRUPT;
}
switch (state) {
case 0:
switch (*ptr++) {
case 0x00:
state = 1;
break;
default:
state = 0;
break;
}
break;
case 1:
switch (*ptr++) {
case 0x00:
state = 1;
break;
case 0xed:
state = 2;
break;
default:
state = 0;
break;
}
break;
case 2:
switch (*ptr++) {
case 0x00:
state = 1;
break;
case 0xed:
state = 3;
break;
default:
state = 0;
break;
}
break;
case 3:
switch (*ptr++) {
case 0x00:
state = 4;
break;
default:
state = 0;
break;
}
break;
default:
libspectrumPrintError(LIBSPECTRUM_ERROR_LOGIC);
warning("readV1Block: unknown state %d", state);
return LIBSPECTRUM_ERROR_LOGIC;
}
}
/* Length passed here is reduced by 4 to remove the end marker */
uncompressBlock(uncompressed, &uncompressed_length, buffer, (ptr - buffer - 4));
/* Uncompressed data must be exactly 48Kb long */
if (uncompressed_length != 0xc000) {
delete *uncompressed;
libspectrumPrintError(LIBSPECTRUM_ERROR_CORRUPT);
warning("readV1Block: data does not uncompress to 48Kb");
return LIBSPECTRUM_ERROR_CORRUPT;
}
*next_block = ptr;
} else { /* Snap isn't compressed */
/* Check we've got enough bytes to read */
if (end - *next_block < 0xc000) {
libspectrumPrintError(LIBSPECTRUM_ERROR_CORRUPT);
warning("readV1Block: not enough data in buffer");
return LIBSPECTRUM_ERROR_CORRUPT;
}
*uncompressed = new uint8_t[0xC000];
Common::copy(buffer, buffer + 0xC000, *uncompressed);
*next_block = buffer + 0xc000;
}
return LIBSPECTRUM_ERROR_NONE;
}
/* The signature used to designate the .slt extensions */
static uint8_t slt_signature[] = "\0\0\0SLT";
static size_t slt_signature_length = 6;
static LibspectrumError readV2Block(const uint8_t *buffer, uint8_t **block, size_t *length, int *page, const uint8_t **next_block, const uint8_t *end) {
size_t length2;
length2 = buffer[0] + buffer[1] * 0x100;
(*page) = buffer[2];
if (length2 == 0 && *page == 0) {
if (buffer + 8 < end && !memcmp(buffer, slt_signature, slt_signature_length)) {
/* Ah, we have what looks like SLT data... */
*next_block = buffer + 6;
return LIBSPECTRUM_ERROR_SLT;
}
}
/* A length of 0xffff => 16384 bytes of uncompressed data */
if (length2 != 0xffff) {
/* Check we're not going to run over the end of the buffer */
if (buffer + 3 + length2 > end) {
libspectrumPrintError(LIBSPECTRUM_ERROR_CORRUPT);
warning("readV2Block: not enough data in buffer");
return LIBSPECTRUM_ERROR_CORRUPT;
}
*length = 0;
uncompressBlock(block, length, buffer + 3, length2);
*next_block = buffer + 3 + length2;
} else { /* Uncompressed block */
/* Check we're not going to run over the end of the buffer */
if (buffer + 3 + 0x4000 > end) {
libspectrumPrintError(LIBSPECTRUM_ERROR_CORRUPT);
warning("readV2Block: not enough data in buffer");
return LIBSPECTRUM_ERROR_CORRUPT;
}
*block = new uint8_t[0x4000];
Common::copy(buffer + 3, buffer + 3 + 0x4000, *block);
*length = 0x4000;
*next_block = buffer + 3 + 0x4000;
}
return LIBSPECTRUM_ERROR_NONE;
}
static void uncompressBlock(uint8_t **dest, size_t *dest_length, const uint8_t *src, size_t src_length) {
const uint8_t *in_ptr;
uint8_t *out_ptr;
/* Allocate memory for dest if requested */
if (*dest_length == 0) {
*dest_length = src_length / 2;
*dest = new uint8_t[*dest_length];
}
in_ptr = src;
out_ptr = *dest;
while (in_ptr < src + src_length) {
/* If we're pointing at the last byte, just copy it across and exit */
if (in_ptr == src + src_length - 1) {
libspectrumMakeRoom(dest, 1, &out_ptr, dest_length);
*out_ptr++ = *in_ptr++;
continue;
}
/* If we're pointing at two successive 0xed bytes, that's a run. If not, just copy the byte across */
if (*in_ptr == 0xed && *(in_ptr + 1) == 0xed) {
size_t run_length;
uint8_t repeated;
in_ptr += 2;
run_length = *in_ptr++;
repeated = *in_ptr++;
libspectrumMakeRoom(dest, run_length, &out_ptr, dest_length);
while (run_length--) {
*out_ptr++ = repeated;
}
} else {
libspectrumMakeRoom(dest, 1, &out_ptr, dest_length);
*out_ptr++ = *in_ptr++;
}
}
*dest_length = out_ptr - *dest;
}
} // End of namespace Scott
} // End of namespace Glk