Files
scummvm-cursorfix/engines/twine/resources/resources.cpp
2026-02-02 04:50:13 +01:00

326 lines
11 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 "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