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,375 @@
/* 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 "twine/resources/hqr.h"
#include "twine/resources/lzss.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/system.h"
#include "common/textconsole.h"
namespace TwinE {
namespace HQR {
#define wrap(cmd) \
if ((cmd) == 0) { \
warning("Failed to execute " #cmd); \
return 0; \
}
/**
* Decompress entry based in Yaz0r and Zink decompression code
* @param dst destination pointer where will be the decompressed entry
* @param compBuf compressed data pointer
* @param compSize @p compBuf buffer size
* @param decompsize real file size after decompression
* @param mode compression mode used
*/
static void decompressEntry(uint8 *dst, const uint8 *compBuf, uint32 compSize, int32 decompsize, int32 mode) {
Common::MemoryReadStream stream(compBuf, compSize);
do {
uint8 b = stream.readByte();
for (int32 d = 0; d < 8; d++) {
int32 length;
if (!(b & (1 << d))) {
const uint16 offset = stream.readUint16LE();
length = (offset & 0x0F) + (mode + 1);
const uint8 *ptr = dst - (offset >> 4) - 1;
for (int32 i = 0; i < length; i++) {
*(dst++) = *(ptr++);
}
} else {
length = 1;
*(dst++) = stream.readByte();
}
decompsize -= length;
if (decompsize <= 0) {
return;
}
}
} while (decompsize);
}
/**
* Get a HQR entry pointer
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
static int voxEntrySize(const char *filename, int32 index, int32 hiddenIndex) {
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
uint32 compSize = file.readUint32LE();
// exist hidden entries
for (int32 i = 0; i < hiddenIndex; i++) {
wrap(file.seek(offsetToData + compSize + 10)) // hidden entry
offsetToData = offsetToData + compSize + 10; // current hidden offset
realSize = file.readUint32LE();
compSize = file.readUint32LE();
}
return realSize;
}
int32 getEntry(uint8 *ptr, const char *filename, int32 index) {
if (!ptr) {
return 0;
}
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
uint32 compSize = file.readUint32LE();
uint16 mode = file.readUint16LE();
// uncompressed
if (mode == 0) {
wrap(file.read(ptr, realSize))
}
// compressed: modes (1 & 2)
else if (mode == 1 || mode == 2) {
uint8 *compDataPtr = (uint8 *)malloc(compSize);
wrap(file.read(compDataPtr, compSize))
decompressEntry(ptr, compDataPtr, compSize, realSize, mode);
free(compDataPtr);
}
debugC(1, TwinE::kDebugResources, "Loaded entry from %s for index %i with %i bytes", filename, index, realSize);
return realSize;
}
int32 entrySize(const char *filename, int32 index) {
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
return realSize;
}
int32 numEntries(const char *filename) {
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
return ((int)headerSize / 4) - 1;
}
Common::SeekableReadStream *makeReadStream(const char *filename, int index) {
Common::File *file = new Common::File();
if (!file->open(filename)) {
delete file;
warning("HQR: Could not open %s", filename);
return nullptr;
}
const uint32 headerSize = file->readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index: %i", index);
delete file;
return nullptr;
}
if (!file->seek(index * 4)) {
warning("HQR: Invalid index: %i", index);
delete file;
return nullptr;
}
const uint32 offsetToData = file->readUint32LE();
if (!file->seek(offsetToData)) {
warning("HQR: Invalid index: %i", index);
delete file;
return nullptr;
}
const uint32 realSize = file->readUint32LE();
const uint32 compressedSize = file->readUint32LE();
const uint16 mode = file->readUint16LE();
const uint32 begin = offsetToData + 10;
uint32 end = 0;
if (mode == 0) {
end = begin + realSize;
} else {
end = begin + compressedSize;
}
Common::SeekableReadStream *stream = new Common::SeekableSubReadStream(file, begin, end, DisposeAfterUse::YES);
if (mode != 0) {
stream = new LzssReadStream(stream, mode, realSize);
}
debugC(1, TwinE::kDebugResources, "Loaded entry from %s for index %i with %i bytes", filename, index, realSize);
return stream;
}
int32 getAllocEntry(uint8 **ptr, const char *filename, int32 index) {
if (*ptr) {
free(*ptr);
}
const int32 size = entrySize(filename, index);
if (size <= 0) {
*ptr = nullptr;
warning("HQR: failed to get entry for index %i from file: %s", index, filename);
return 0;
}
*ptr = (uint8 *)malloc(size * sizeof(uint8));
if (!*ptr) {
warning("HQR: unable to allocate entry memory");
return 0;
}
const int32 entrySize = getEntry(*ptr, filename, index);
assert(entrySize == size);
return entrySize;
}
bool dumpEntry(const char *filename, int32 index, const char *targetFileName) {
Common::DumpFile out;
if (!out.open(targetFileName, true)) {
warning("Failed to save to %s", targetFileName);
return false;
}
uint8 *content = nullptr;
const int size = getAllocEntry(&content, filename, index);
if (size == 0) {
warning("Could not get hqr entry in %s for index %i", filename, index);
return false;
}
out.write(content, size);
out.flush();
out.close();
free(content);
return true;
}
int32 getVoxEntry(uint8 *ptr, const char *filename, int32 index, int32 hiddenIndex) {
if (!ptr) {
return 0;
}
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
uint32 compSize = file.readUint32LE();
uint16 mode = file.readSint16LE();
// exist hidden entries
for (int32 i = 0; i < hiddenIndex; i++) {
wrap(file.seek(offsetToData + compSize + 10)) // hidden entry
offsetToData = offsetToData + compSize + 10; // current hidden offset
realSize = file.readUint32LE();
compSize = file.readUint32LE();
mode = file.readUint16LE();
}
// uncompressed
if (mode == 0) {
wrap(file.read(ptr, realSize))
}
// compressed: modes (1 & 2)
else if (mode == 1 || mode == 2) {
uint8 *compDataPtr = (uint8 *)malloc(compSize);
wrap(file.read(compDataPtr, compSize))
decompressEntry(ptr, compDataPtr, compSize, realSize, mode);
free(compDataPtr);
}
debugC(1, TwinE::kDebugResources, "Loaded vox entry from %s for index %i with %i bytes", filename, index, realSize);
return realSize;
}
bool getPaletteEntry(Graphics::Palette &palette, const char *filename, int32 index) {
byte paletteBuffer[NUMOFCOLORS * 3];
int32 size = HQR::getEntry(paletteBuffer, filename, index);
if (size <= 0) {
debugC(1, TwinE::kDebugResources, "Failed to load palette from %s for index %i", filename, index);
return false;
}
palette = Graphics::Palette(paletteBuffer, size / 3);
debugC(1, TwinE::kDebugResources, "Loaded palette from %s for index %i with %i color entries", filename, index, (int)palette.size());
debugC(1, TwinE::kDebugPalette, "Loaded palette from %s for index %i with %i color entries", filename, index, (int)palette.size());
return true;
}
int32 getAllocVoxEntry(uint8 **ptr, const char *filename, int32 index, int32 hiddenIndex) {
const int32 size = voxEntrySize(filename, index, hiddenIndex);
if (size == 0) {
warning("HQR: vox entry with 0 size found for index: %d", index);
return 0;
}
*ptr = (uint8 *)malloc(size * sizeof(uint8));
if (!*ptr) {
warning("HQR: unable to allocate entry memory of size %d for index: %d", size, index);
return 0;
}
const int32 entrySize = getVoxEntry(*ptr, filename, index, hiddenIndex);
assert(entrySize == size);
return entrySize;
}
#undef wrap
} // namespace HQR
} // namespace TwinE

View File

@@ -0,0 +1,124 @@
/* 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 TWINE_RESOURCES_HQR_H
#define TWINE_RESOURCES_HQR_H
#include "common/scummsys.h"
#include "common/stream.h"
#include "graphics/palette.h"
#include "twine/shared.h"
namespace TwinE {
class TwinEEngine;
/**
* High Quality Resource
*
* https://web.archive.org/web/20181218233826/http://lbafileinfo.kazekr.net/index.php?title=High_quality_resource
*/
namespace HQR {
/**
* Get a HQR entry pointer
* @param ptr pointer to save the entry
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getEntry(uint8 *ptr, const char *filename, int32 index);
inline int32 getEntry(uint8 *ptr, const TwineResource &resource) {
return getEntry(ptr, resource.hqr, resource.index);
}
/**
* Get a HQR entry pointer
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 entrySize(const char *filename, int32 index);
inline int32 entrySize(const TwineResource &resource) {
return entrySize(resource.hqr, resource.index);
}
/**
* Get a HQR total number of entries
* @param filename HQR file name
* @return total number of entries
*/
int32 numEntries(const char *filename);
/**
* Get a HQR entry pointer with memory allocation
* @param ptr pointer to save the entry. This pointer is automatically freed and therefore must be initialized
* to @c nullptr on the first run.
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getAllocEntry(uint8 **ptr, const char *filename, int32 index);
inline int32 getAllocEntry(uint8 **ptr, const TwineResource &resource) {
return getAllocEntry(ptr, resource.hqr, resource.index);
}
/**
* @brief Helper method to dump the content of the given hqr index to a file
*/
bool dumpEntry(const char *filename, int32 index, const char *targetFileName);
inline bool dumpEntry(const TwineResource &resource, const char *targetFileName) {
return dumpEntry(resource.hqr, resource.index, targetFileName);
}
/**
* Get a HQR entry pointer
* @param ptr pointer to save the entry
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getVoxEntry(uint8 *ptr, const char *filename, int32 index, int32 hiddenIndex);
/**
* Get a HQR entry pointer with memory allocation
* @param ptr pointer to save the entry. This pointer is automatically freed and therefore must be initialized
* to @c nullptr on the first run.
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getAllocVoxEntry(uint8 **ptr, const char *filename, int32 index, int32 hiddenIndex);
bool getPaletteEntry(Graphics::Palette &palette, const char *filename, int32 index);
inline bool getPaletteEntry(Graphics::Palette &palette, const TwineResource &resource) {
return getPaletteEntry(palette, resource.hqr, resource.index);
}
Common::SeekableReadStream *makeReadStream(const char *filename, int index);
inline Common::SeekableReadStream *makeReadStream(const TwineResource &resource) {
return makeReadStream(resource.hqr, resource.index);
}
} // namespace HQR
} // namespace TwinE
#endif

View File

@@ -0,0 +1,110 @@
/* 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 "twine/resources/lzss.h"
#include "common/textconsole.h"
namespace TwinE {
LzssReadStream::LzssReadStream(Common::ReadStream *indata, uint32 mode, uint32 realsize) {
_outLzssBufData = new uint8[realsize]();
decodeLZSS(indata, mode, realsize);
_size = realsize;
_pos = 0;
delete indata;
}
LzssReadStream::~LzssReadStream() {
delete[] _outLzssBufData;
}
void LzssReadStream::decodeLZSS(Common::ReadStream *in, uint32 mode, uint32 dataSize) {
if (in->eos() || in->err() || dataSize == 0) {
_err = dataSize > 0;
return;
}
uint8 *dst = _outLzssBufData;
int32 remainingBytes = (int32)dataSize;
do {
uint8 b = in->readByte();
for (int32 d = 0; d < 8; d++) {
if (in->eos() || in->err()) {
_err = dataSize > 0;
return;
}
int32 length;
if (!(b & (1 << d))) {
const uint16 offset = in->readUint16LE();
length = (offset & 0x0F) + (mode + 1);
const uint8 *ptr = dst - (offset >> 4) - 1;
if (remainingBytes < length) {
_err = true;
return;
}
remainingBytes -= length;
for (int32 i = 0; i < length; i++) {
*dst++ = *ptr++;
}
} else {
length = 1;
if (remainingBytes < length) {
_err = true;
return;
}
remainingBytes -= length;
*dst++ = in->readByte();
}
dataSize -= length;
if (dataSize <= 0) {
return;
}
}
} while (dataSize);
}
bool LzssReadStream::eos() const {
return _pos >= _size;
}
uint32 LzssReadStream::read(void *buf, uint32 dataSize) {
if (dataSize > _size - _pos) {
_err = true;
return 0;
}
memcpy(buf, &_outLzssBufData[_pos], dataSize);
_pos += dataSize;
return dataSize;
}
bool LzssReadStream::seek(int64 offset, int whence) {
if (whence == SEEK_SET) {
_pos = offset;
} else if (whence == SEEK_CUR) {
_pos += offset;
}
return true;
}
} // namespace TwinE

View File

@@ -0,0 +1,54 @@
/* 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 TWINE_RESOURCES_LZSS_H
#define TWINE_RESOURCES_LZSS_H
#include "common/stream.h"
namespace TwinE {
class LzssReadStream : public Common::SeekableReadStream {
private:
uint8 *_outLzssBufData;
uint32 _size;
uint32 _pos;
bool _err = false;
void decodeLZSS(Common::ReadStream *indata, uint32 mode, uint32 length);
public:
LzssReadStream(Common::ReadStream *indata, uint32 mode, uint32 realsize);
virtual ~LzssReadStream();
void clearErr() override { _err = false; }
bool err() const override { return _err; }
int64 pos() const override { return _pos; }
int64 size() const override { return _size; }
bool seek(int64 offset, int whence = SEEK_SET) override;
bool eos() const override;
uint32 read(void *buf, uint32 size) override;
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,325 @@
/* 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 "twine/resources/resources.h"
#include "common/file.h"
#include "common/tokenizer.h"
#include "common/util.h"
#include "graphics/palette.h"
#include "twine/audio/sound.h"
#include "twine/parser/anim3ds.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/scene/animations.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Resources::~Resources() {
for (size_t i = 0; i < ARRAYSIZE(_spriteTable); ++i) {
free(_spriteTable[i]);
}
for (size_t i = 0; i < ARRAYSIZE(_samplesTable); ++i) {
free(_samplesTable[i]);
}
free(_fontPtr);
free(_sjisFontPtr);
}
void Resources::initPalettes() {
if (!HQR::getPaletteEntry(_engine->_screens->_ptrPal, Resources::HQR_RESS_FILE, RESSHQR_MAINPAL)) {
error("Failed to load main palette");
}
_engine->setPalette(_engine->_screens->_ptrPal);
}
void Resources::preloadAnim3DS() {
const int index = HQR::numEntries(Resources::HQR_ANIM3DS_FILE) - 1;
_anim3DSData.loadFromHQR(Resources::HQR_ANIM3DS_FILE, index, _engine->isLBA1());
}
void Resources::loadEntityData(EntityData &entityData, int32 &index) {
if (_engine->isLBA1()) {
TwineResource modelRes(Resources::HQR_FILE3D_FILE, index);
if (!entityData.loadFromHQR(modelRes, _engine->isLBA1())) {
error("Failed to load actor 3d data for index: %i", index);
}
} else {
// TODO: don't allocate each time
TwineResource modelRes(Resources::HQR_RESS_FILE, 44);
uint8 *file3dBuf = nullptr;
const int32 holomapImageSize = HQR::getAllocEntry(&file3dBuf, modelRes);
if (!entityData.loadFromBuffer((uint8 *)(file3dBuf + *(((uint32 *)file3dBuf) + (index))), holomapImageSize, _engine->isLBA1())) {
delete file3dBuf;
error("Failed to load actor 3d data for index: %i", index);
}
delete file3dBuf;
}
}
const T_ANIM_3DS *Resources::getAnim(int index) const {
if (index < 0 || index >= (int)_anim3DSData.getAnims().size()) {
return nullptr;
}
return &_anim3DSData.getAnims()[index];
}
void Resources::preloadSprites() {
const int32 numEntries = HQR::numEntries(Resources::HQR_SPRITES_FILE);
const int32 maxSprites = _engine->isLBA1() ? 200 : NUM_SPRITES;
if (numEntries > maxSprites) {
error("Max allowed sprites exceeded: %i/%i", numEntries, maxSprites);
}
debugC(1, TwinE::kDebugResources, "preload %i sprites", numEntries);
for (int32 i = 0; i < numEntries; i++) {
_spriteSizeTable[i] = HQR::getAllocEntry(&_spriteTable[i], Resources::HQR_SPRITES_FILE, i);
if (!_spriteData[i].loadFromBuffer(_spriteTable[i], _spriteSizeTable[i], _engine->isLBA1())) {
warning("Failed to load sprite %i", i);
}
}
}
void Resources::preloadAnimations() {
const int32 numEntries = HQR::numEntries(Resources::HQR_ANIM_FILE);
const int32 maxAnims = _engine->isLBA1() ? 600 : NUM_ANIMS;
if (numEntries > maxAnims) {
error("Max allowed animations exceeded: %i/%i", numEntries, maxAnims);
}
debugC(1, TwinE::kDebugResources, "preload %i animations", numEntries);
for (int32 i = 0; i < numEntries; i++) {
_animData[i].loadFromHQR(Resources::HQR_ANIM_FILE, i, _engine->isLBA1());
}
}
static bool isLba1BlankSampleEntry(int32 index) {
// these indices contain blank hqr entries
const int32 blankIndices[] = {80, 81, 82, 83, 115, 118, 120, 124, 125, 139, 140, 154, 155};
for (int j = 0; j < ARRAYSIZE(blankIndices); ++j) {
if (index == blankIndices[j]) {
return true;
}
}
return false;
}
void Resources::preloadSamples() {
const int32 numEntries = HQR::numEntries(Resources::HQR_SAMPLES_FILE);
const int32 maxSamples = _engine->isLBA1() ? 243 : NUM_SAMPLES;
if (numEntries > maxSamples) {
error("Max allowed samples exceeded: %i/%i", numEntries, maxSamples);
}
debugC(1, TwinE::kDebugResources, "preload %i samples", numEntries);
for (int32 i = 0; i < numEntries; i++) {
if (_engine->isLBA1() && isLba1BlankSampleEntry(i)) {
_samplesSizeTable[i] = 0;
_samplesTable[i] = nullptr;
continue;
}
_samplesSizeTable[i] = HQR::getAllocEntry(&_samplesTable[i], Resources::HQR_SAMPLES_FILE, i);
if (_samplesSizeTable[i] == 0) {
warning("Failed to load sample %i", i);
continue;
}
// Fix incorrect sample files first byte
if (*_samplesTable[i] != 'C') {
debugC(1, TwinE::kDebugResources, "Sample %i has incorrect magic id (size: %u)", i, _samplesSizeTable[i]);
*_samplesTable[i] = 'C';
}
}
}
void Resources::preloadInventoryItems() {
if (!_engine->isLBA1()) {
// lba2 has this data in code
return;
}
int32 numEntries = HQR::numEntries(Resources::HQR_INVOBJ_FILE);
if (_engine->isPreview()) {
if (numEntries != 32) {
error("Unexpected inventory items for lba1 preview version: %i/32", numEntries);
}
// TODO: this is obviously a hack
numEntries = NUM_INVENTORY_ITEMS;
} else {
if (numEntries > NUM_INVENTORY_ITEMS) {
error("Max allowed inventory items exceeded: %i/%i", numEntries, NUM_INVENTORY_ITEMS);
}
}
debugC(1, TwinE::kDebugResources, "preload %i inventory items", numEntries);
for (int32 i = 0; i < numEntries; i++) {
_inventoryTable[i].loadFromHQR(Resources::HQR_INVOBJ_FILE, i, _engine->isLBA1());
}
}
void Resources::initResources() {
initPalettes();
_fontBufSize = HQR::getAllocEntry(&_fontPtr, Resources::HQR_RESS_FILE, RESSHQR_LBAFONT);
if (_fontBufSize == 0) {
error("Failed to load font");
}
const int kMinSjisSize = 11072 * 24 * 3;
Common::File f24;
if (f24.open("FNT24.DAT") && f24.size() >= kMinSjisSize) {
// Rest is garbage
_sjisFontPtr = (byte *)malloc(kMinSjisSize);
assert(_sjisFontPtr);
f24.read(_sjisFontPtr, kMinSjisSize);
}
_engine->_text->setFont(INTER_LEAVE, INTER_SPACE);
_engine->_text->setFontColor(COLOR_14);
_engine->_text->setTextCrossColor(136, 143, 2);
if (_engine->isLBA1()) {
if (!_spriteShadowPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_SPRITESHADOW), _engine->isLBA1())) {
error("Failed to load shadow sprites");
}
if (!_spriteBoundingBox.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_SPRITEBOXDATA), _engine->isLBA1())) {
error("Failed to load sprite bounding box data");
}
if (!_holomapTwinsenModelPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOTWINMDL), _engine->isLBA1())) {
error("Failed to load holomap twinsen model");
}
if (!_holomapPointModelPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOPOINTMDL), _engine->isLBA1())) {
if (!_engine->isPreview()) {
error("Failed to load holomap point model");
}
}
if (!_holomapArrowPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOARROWMDL), _engine->isLBA1())) {
error("Failed to load holomap arrow model");
}
if (!_holomapTwinsenArrowPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOTWINARROWMDL), _engine->isLBA1())) {
error("Failed to load holomap twinsen arrow model");
}
if (!_trajectories.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOPOINTANIM), _engine->isLBA1())) {
if (!_engine->isPreview()) {
error("Failed to parse trajectory data");
}
}
debugC(1, TwinE::kDebugResources, "preload %i trajectories", (int)_trajectories.getTrajectories().size());
} else if (_engine->isLBA2()) {
preloadAnim3DS();
}
preloadSprites();
preloadAnimations();
preloadSamples();
preloadInventoryItems();
loadMovieInfo();
if (!_engine->isPreview()) {
// TODO: where is the text in the preview version?
const int32 textEntryCount = _engine->isLBA1() ? 28 : 30;
for (int32 i = 0; i < textEntryCount / 2; ++i) {
if (!_textData.loadFromHQR(Resources::HQR_TEXT_FILE, (TextBankId)i, _engine->_cfgfile._languageId, _engine->isLBA1(), textEntryCount)) {
error("HQR ERROR: Parsing textbank %i failed for language %i (%i entries)", i, _engine->_cfgfile._languageId, textEntryCount);
}
}
debugC(1, TwinE::kDebugResources, "Loaded %i text banks", textEntryCount / 2);
}
}
const TextEntry *Resources::getText(TextBankId textBankId, TextId index) const {
return _textData.getText(textBankId, index);
}
const Trajectory *Resources::giveTrajPtr(int index) const {
return _trajectories.getTrajectory(index);
}
int Resources::findSmkMovieIndex(const char *name) const {
Common::String smkName = name;
smkName.toLowercase();
if (!_movieInfo.contains(smkName)) {
warning("Movie '%s' not found in movie info", smkName.c_str());
return -1;
}
const Common::Array<int32> &info = getMovieInfo(smkName);
return info[0];
}
void Resources::loadMovieInfo() {
uint8 *content = nullptr;
int32 size;
if (_engine->isLBA1()) {
if (_engine->isPreview()) {
size = 0;
} else {
size = HQR::getAllocEntry(&content, Resources::HQR_RESS_FILE, RESSHQR_FLAINFO);
}
} else {
size = HQR::getAllocEntry(&content, Resources::HQR_RESS_FILE, 48);
}
if (size == 0) {
return;
}
const Common::String str((const char *)content, size);
free(content);
debugC(2, TwinE::kDebugResources, "movie info:\n%s", str.c_str());
Common::StringTokenizer tok(str, "\r\n");
int videoIndex = 0;
while (!tok.empty()) {
Common::String line = tok.nextToken();
if (_engine->isLBA1()) {
Common::StringTokenizer lineTok(line);
if (lineTok.empty()) {
continue;
}
const Common::String &name = lineTok.nextToken();
Common::Array<int32> frames;
while (!lineTok.empty()) {
const Common::String &frame = lineTok.nextToken();
const int32 frameIdx = atoi(frame.c_str());
frames.push_back(frameIdx);
}
_movieInfo.setVal(name, frames);
} else {
Common::Array<int32> info(1);
info[0] = videoIndex;
line.toLowercase();
if (line.hasSuffix(".smk")) {
line = line.substr(0, line.size() - 4);
}
_movieInfo.setVal(line, info);
debugC(1, TwinE::kDebugResources, "movie name %s mapped to hqr index %i", line.c_str(), videoIndex);
++videoIndex;
}
}
}
const Common::Array<int32> &Resources::getMovieInfo(const Common::String &name) const {
return _movieInfo.getVal(name);
}
} // namespace TwinE

View File

@@ -0,0 +1,304 @@
/* 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 TWINE_RESOURCES_RESOURCES_H
#define TWINE_RESOURCES_RESOURCES_H
#include "common/hashmap.h"
#include "common/scummsys.h"
#include "twine/parser/anim3ds.h"
#include "twine/parser/body.h"
#include "twine/parser/holomap.h"
#include "twine/parser/sprite.h"
#include "twine/parser/text.h"
#include "twine/resources/hqr.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/scene.h"
namespace TwinE {
/** RESS.HQR FILE */
#define RESSHQR_MAINPAL 0
#define RESSHQR_LBAFONT 1
#define RESSHQR_BLANK 2
#define RESSHQR_SPRITEBOXDATA 3
#define RESSHQR_SPRITESHADOW 4
#define RESSHQR_HOLOPAL 5 // lba1
#define RESSHQR_HOLOSURFACE 6 // lba1
#define RESSHQR_HOLOIMG 7 // lba1
#define RESSHQR_HOLOARROWINFO 8 // lba1
#define RESSHQR_HOLOTWINMDL 9 // lba1
#define RESSHQR_HOLOARROWMDL 10 // lba1
#define RESSHQR_HOLOTWINARROWMDL 11 // lba1
#define RESSHQR_BLACKPAL 9 // lba2
#define RESSHQR_ECLAIRPAL 10 // lba2
#define RESSHQR_ARROWBIN 12 // lba2
#define SAMPLE_RAIN 13
#define RESSHQR_GAMEOVERMDL 21
#define RESSHQR_ALARMREDPAL 22
#define RESSHQR_FLAINFO 23
#define RESSHQR_DARKPAL 24
#define RESSHQR_HOLOPOINTMDL 29
#define RESSHQR_HOLOPOINTANIM 30
#define RESSHQR_PLASMAEFFECT 51
#define FLA_DRAGON3 "dragon3"
#define FLA_INTROD "introd"
#define FLA_THEEND "the_end"
#define FLA_BATEAU "bateau"
#define ACF_INTRO "INTRO"
#define FILE3DHQR_HERONORMAL 0
#define FILE3DHQR_HEROATHLETIC 1
#define FILE3DHQR_HEROAGGRESSIVE 2
#define FILE3DHQR_HERODISCRETE 3
#define FILE3DHQR_HEROPROTOPACK 4
/** Behaviour menu sprite values */
#define SPRITEHQR_KASHES 3
#define SPRITEHQR_LIFEPOINTS 4
#define SPRITEHQR_MAGICPOINTS 5
#define SPRITEHQR_KEY 6
#define SPRITEHQR_CLOVERLEAF 7
#define SPRITEHQR_CLOVERLEAFBOX 41
#define SPRITEHQR_MAGICBALL_YELLOW 1
#define SPRITEHQR_MAGICBALL_FIRE 13
#define SPRITEHQR_MAGICBALL_GREEN 42
#define SPRITEHQR_MAGICBALL_RED 43
#define SPRITEHQR_MAGICBALL_YELLOW_TRANS 44
#define SPRITEHQR_EXPLOSION_FIRST_FRAME 97 // 7 frames
#define SPRITEHQR_FENCE_1 18
#define SPRITEHQR_FENCE_2 19
#define SPRITEHQR_FENCE_3 22
#define SPRITEHQR_FENCE_4 23
#define SPRITEHQR_FENCE_METAL 35
#define SPRITEHQR_FENCE_METAL_2 54
#define SPRITEHQR_FENCE_METAL_3 83
#define SPRITEHQR_MUSHROOM 92
#define SPRITEHQR_DOOR_WODDEN_1 31
#define SPRITEHQR_DOOR_WODDEN_2 32
#define SPRITEHQR_DOOR_PRISON_WODDEN 37
#define SPRITEHQR_DOOR_PADLOCK 58
#define SPRITEHQR_DOOR_BRICKED_UP 76
#define SPRITEHQR_DOOR_1 104
#define SPRITEHQR_DOOR_2 107
#define SPRITEHQR_DOOR_3 24
#define SPRITEHQR_DOOR_4 11
#define SPRITEHQR_DOOR_5 12
#define SPRITEHQR_DOOR_PRISON_GRID 15
#define SPRITEHQR_DOOR_PRISON_HARMED 16
#define SPRITEHQR_DOOR_PRISON_WITH_F_LETTER 17
#define SPRITEHQR_MAGICBALL_GREEN_TRANS 109
#define SPRITEHQR_MAGICBALL_RED_TRANS 110
#define SPRITEHQR_DIAG_BUBBLE_RIGHT 90
#define SPRITEHQR_DIAG_BUBBLE_LEFT 91
/** Total number of animations allowed in the game */
#define NUM_ANIMS 2083 // 600 for lba1
/** Total number of samples allowed in the game */
#define NUM_SAMPLES 895 // 243 for lba1
class TwinEEngine;
class Resources {
private:
TwinEEngine *_engine;
void preloadInventoryItems();
/** Init standard menu and in-game palette */
void initPalettes();
/** Preload all sprites */
void preloadSprites();
/** Preload all animations */
void preloadAnimations();
void preloadAnim3DS();
void preloadSamples();
void loadMovieInfo();
using MovieInfoMap = Common::HashMap<Common::String, Common::Array<int32> >;
MovieInfoMap _movieInfo;
TrajectoryData _trajectories;
TextData _textData;
Anim3DSData _anim3DSData;
public:
Resources(TwinEEngine *engine) : _engine(engine) {}
~Resources();
/**
* For lba1 this is returning the gif images that are used as a placeholder for the fla movies
* For lba2 this is the list of videos that are mapped by their entry index
*/
const Common::Array<int32> &getMovieInfo(const Common::String &name) const;
/** Table with all loaded samples */
BodyData _inventoryTable[NUM_INVENTORY_ITEMS];
/** Table with all loaded sprites */
uint8 *_spriteTable[NUM_SPRITES]{nullptr};
/** Table with all loaded sprite sizes */
uint32 _spriteSizeTable[NUM_SPRITES]{0};
SpriteData _spriteData[NUM_SPRITES];
AnimData _animData[NUM_ANIMS]; // HQR_Anims
/** Table with all loaded samples */
uint8 *_samplesTable[NUM_SAMPLES]{nullptr};
/** Table with all loaded samples sizes */
uint32 _samplesSizeTable[NUM_SAMPLES]{0};
/** Font buffer pointer */
int32 _fontBufSize = 0;
uint8 *_fontPtr = nullptr;
uint8 *_sjisFontPtr = nullptr;
SpriteData _spriteShadowPtr;
SpriteBoundingBoxData _spriteBoundingBox;
BodyData _holomapPointModelPtr;
BodyData _holomapTwinsenModelPtr;
BodyData _holomapTwinsenArrowPtr;
BodyData _holomapArrowPtr;
/** Initialize resource pointers */
void initResources();
const Trajectory *giveTrajPtr(int index) const;
const TrajectoryData &getTrajectories() const {
return _trajectories;
}
void loadEntityData(EntityData &entityData, int32 &index);
const TextEntry *getText(TextBankId textBankId, TextId index) const;
const T_ANIM_3DS *getAnim(int index) const;
int findSmkMovieIndex(const char *name) const;
// main palette
static constexpr const char *HQR_RESS_FILE = "ress.hqr";
// dialoges
static constexpr const char *HQR_TEXT_FILE = "text.hqr";
// samples
static constexpr const char *HQR_SAMPLES_FILE = "samples.hqr";
/**
* This file contains isometric grids that are used to display area backgrounds and define 3D shape of the surface.
* Each of the entries is associated with the entry of lba_bll.hqr with the same index. lba_bll entries define block
* sets for use with the grids. Each grid may use only one set of blocks (one entry of lba_bll.hqr).
*/
static constexpr const char *HQR_LBA_GRI_FILE = "lba_gri.hqr";
// isometric libraries for use in grids.
static constexpr const char *HQR_LBA_BLL_FILE = "lba_bll.hqr";
/**
* isometric bricks, which are some kind of tiles, that are used for building the terrains in LBA 1 isometric scenes.
* One brick is the tiniest piece of a grid, which has 64 x 64 x 25 cells. Bricks cannot be used directly on a grid,
* but instead they are grouped into blocks by block libraries, which are then referenced by grids
* Bricks are images or sprites in a special format.
*/
static constexpr const char *HQR_LBA_BRK_FILE = "lba_brk.hqr";
// scenes (active area content (actors, scripts, etc.))
static constexpr const char *HQR_SCENE_FILE = "scene.hqr";
// full screen images (lba2)
static constexpr const char *HQR_SCREEN_FILE = "screen.hqr";
// sprites
static constexpr const char *HQR_SPRITES_FILE = "sprites.hqr";
/**
* model/animation entities
* contains data associating 3D models (Body.hqr) with animations (Anim.hqr) for the game characters.
*/
static constexpr const char *HQR_FILE3D_FILE = "file3d.hqr";
// 3d model data
static constexpr const char *HQR_BODY_FILE = "body.hqr";
// animations
static constexpr const char *HQR_ANIM_FILE = "anim.hqr";
static constexpr const char *HQR_ANIM3DS_FILE = "anim3ds.hqr";
// inventory objects
static constexpr const char *HQR_INVOBJ_FILE = "invobj.hqr";
// lba2 holomap
static constexpr const char *HQR_HOLOMAP_FILE = "holomap.hqr";
/**
* @brief Floppy version of the game uses gifs for replacing the videos
*/
static constexpr const char *HQR_FLAGIF_FILE = "fla_gif.hqr";
static constexpr const char *HQR_FLASAMP_FILE = "flasamp.hqr";
static constexpr const char *HQR_MIDI_MI_DOS_FILE = "midi_mi.hqr";
static constexpr const char *HQR_MIDI_MI_WIN_FILE = "midi_mi_win.hqr";
static constexpr const char *HQR_VIDEO_FILE = "video.hqr"; // lba2 - smk files
TwineImage adelineLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 27, 28);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 0, 1);
}
TwineImage lbaLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 49, 50);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 60, 61);
}
TwineImage eaLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 52, 53);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 74, 75);
}
TwineImage activisionLogo() const {
assert(_engine->isLBA2());
return TwineImage(Resources::HQR_SCREEN_FILE, 72, 73);
}
TwineImage virginLogo() const {
assert(_engine->isLBA2());
return TwineImage(Resources::HQR_SCREEN_FILE, 76, 77);
}
TwineImage relentLogo() const {
assert(_engine->isLBA1());
return TwineImage(Resources::HQR_RESS_FILE, 12, 13);
}
TwineImage menuBackground() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 14, -1);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 4, 5);
}
};
} // namespace TwinE
#endif