/* 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 * aint32 with this program; if not, write to the Free Software * * * Based on the original sources * Faery Tale II -- The Halls of the Dead * (c) 1993-1996 The Wyrmkeep Entertainment Co. */ #include "common/debug.h" #include "saga2/saga2.h" #include "saga2/hresmgr.h" #include "saga2/fta.h" namespace Saga2 { /* ===================================================================== * Resource context * ===================================================================== */ hResContext::hResContext() { _valid = false; _base = nullptr; _parent = nullptr; _numEntries = 0; _handle = &_file; _res = nullptr; _bytecount = _bytepos = 0; } hResContext::hResContext(hResContext *sire, hResID id, const char desc[]) { hResEntry *entry; const uint32 resourceSize = 12; _valid = false; _res = sire->_res; _numEntries = 0; _bytecount = 0; _bytepos = 0; _handle = &_file; _base = nullptr; _parent = nullptr; if (!_res->_valid) return; _parent = sire; debugC(3, kDebugResources, "Creating context %x (%s), %s", id, tag2str(id), desc); if ((entry = _parent->findEntry(id)) == nullptr) { debugC(3, kDebugResources, "Could not create context"); return; } _numEntries = entry->size / resourceSize; _base = (hResEntry *)((uint8*)_res->_table + entry->offset - _res->_firstGroupOffset); debugC(3, kDebugResources, "- _numEntries = %d, _base = %p, entry->offset = %d", _numEntries, (void *)_base, entry->offset); _valid = true; } hResContext::~hResContext() { releaseIndexData(); } hResEntry *hResContext::findEntry(hResID id) { hResEntry *entry; int16 i; _bytecount = 0; _bytepos = 0; if (!_valid) return nullptr; debugC(3, kDebugResources, "findEntry: looking for %x (%s)", id, tag2str(id)); for (i = 0, entry = _base; i < _numEntries; i++, entry++) { debugC(3, kDebugResources, "%d: Trying ID: %x (%s)", i, entry->id, tag2str(entry->id)); if (entry->id == id) { debugC(3, kDebugResources, "findEntry: found %x (%s)", entry->id, tag2str(entry->id)); return entry; } } debugC(3, kDebugResources, "findEntry: No entry found"); return nullptr; } uint32 hResContext::size(hResID id) { hResEntry *entry; if (!_valid) return 0; if ((entry = findEntry(id)) == nullptr) return 0; return entry->size; } uint32 hResContext::count() { return _numEntries; } uint32 hResContext::count(hResID id) { uint32 count = 0; hResEntry *entry; int16 i; _bytecount = 0; _bytepos = 0; if (!_valid) return 0; for (i = 0, entry = _base; i < _numEntries; i++, entry++) { if ((entry->id & 0x00ffffff) == (id & 0x00ffffff)) count++; } return count; } // this function sets _handle bool hResContext::seek(hResID id) { hResEntry *entry; _bytecount = 0; _bytepos = 0; if (!_valid) return false; if ((entry = findEntry(id)) == nullptr) return false; _bytecount = entry->size; _bytepos = entry->resOffset(); _res->_handle->seek(_bytepos, SEEK_SET); if (entry->isExternal()) { error("hResContext: External entries are not supported"); } _handle = _res->_handle; return true; } void hResContext::rest() { _bytecount = 0; _bytepos = 0; if (_valid && _handle && _handle != _res->_handle) { _handle->close(); _handle = nullptr; } } bool hResContext::read(void *buffer, uint32 size) { assert(_handle); if (!_valid) return false; _bytecount = 0; _bytepos = 0; return (_handle->read(buffer, size) != 0); } bool hResContext::eor() { return (_bytecount < 1); } uint32 hResContext::readbytes(void *buffer, uint32 size) { int32 bytesRead; if (!_valid || _bytecount < 1) return 0; _res->_handle->seek(_bytepos, SEEK_SET); bytesRead = _handle->read(buffer, MIN(size, _bytecount)); _bytecount -= bytesRead; _bytepos += bytesRead; return bytesRead; } bool hResContext::skip(uint32 amount) { if (!_valid) return false; _res->_handle->seek(amount, SEEK_CUR); _bytecount -= amount; _bytepos -= amount; return true; } bool hResContext::get(hResID id, void *buffer, uint32 size) { bool result = false; if (!_valid) return false; _bytecount = 0; _bytepos = 0; if (size == NATURAL_SIZE) { hResEntry *entry; if ((entry = findEntry(id)) == nullptr) return false; size = entry->size; } if (seek(id)) { result = read(buffer, size); rest(); } return result; } uint32 hResContext::getSize(hResID id, const char desc[]) { hResEntry *entry; if ((entry = findEntry(id)) == nullptr) { warning("Resource %d, %s not found", id, desc); return 0; } debugC(3, kDebugResources, "Size for %x (%s): %d", id, desc, entry->size); return entry->size; } byte *hResContext::loadResource(hResID id, const char desc[], const Common::Path &filename) { hResEntry *entry; debugC(3, kDebugResources, "Loading resource %x (%s)", id, desc); if ((entry = findEntry(id)) == nullptr) { warning("Resource %d, %s not found", id, desc); return nullptr; } byte *res = (byte*)malloc(entry->size); const Common::Path &path = filename.empty() ? _filename : filename; if (!_file.isOpen()) _file.open(path); _file.seek(entry->offset, SEEK_SET); _file.read(res, entry->size); return res; } byte *hResContext::loadIndexResource(int16 index, const char desc[], const Common::Path &filename) { hResEntry *entry; entry = &_base[index]; debugC(5, kDebugResources, "Loading indexed resource: %d (%s)", index, desc); if (!_valid || entry == nullptr) return nullptr; if (_indexData.contains(index)) return _indexData.getVal(index); byte *res = (byte*)malloc(entry->size); if (res == nullptr) { debugC(5, kDebugResources, "Could not allocate resources"); return nullptr; } debugC(5, kDebugResources, "_indexData: pushing (%d, %p)", index, (void*)res); _indexData.setVal(index, res); const Common::Path &path = filename.empty() ? _filename : filename; if (!_file.isOpen()) _file.open(path); _file.seek(entry->offset, SEEK_SET); _file.read(res, entry->size); return res; } void hResContext::releaseIndexData() { debugC(4, kDebugResources, "releaseIndexData():"); for (DataMap::iterator i = _indexData.begin(); i != _indexData.end(); ++i) { debugC(4, kDebugResources, "... %d, %p", i->_key, (void*)i->_value); if (i->_value) { free(i->_value); i->_value = nullptr; _indexData.erase(i); } } } /* ===================================================================== * Resource file * ===================================================================== */ void hResource::readEntry(hResEntry &element) { element.id = _file.readUint32BE(); element.offset = _file.readUint32LE(); element.size = _file.readUint32LE(); uint32 id = element.id; debugC(2, kDebugResources, "%s, offset: %x, size: %d", tag2str(id), element.offset, element.size); } void hResource::readResource(hResEntry &element) { element.id = _file.readUint32BE(); element.offset = _file.readUint32LE(); element.size = _file.readUint32LE(); uint32 id = element.id; debugC(3, kDebugResources, "%s, offset: %x, size: %d", tag2str(id), element.offset, element.size); } hResource::hResource(const char *resname) { hResEntry origin; int32 tableSize; const int32 resourceSize = 4 + 4 + 4; // id, offset, size _valid = false; _base = nullptr; _parent = nullptr; _table = nullptr; _firstGroupOffset = 0; _numEntries = 0; _filename = resname; debugC(1, kDebugResources, "Opening resource: %s", resname); if (!_file.open(resname)) warning("Unable to open file %s", resname); readResource(origin); if (origin.id != HRES_ID) return; _file.seek(origin.offset - sizeof(uint32), SEEK_SET); _firstGroupOffset = _file.readUint32LE(); // allocate buffers for root, groups and data _numEntries = origin.size / resourceSize; _base = new hResEntry[_numEntries](); tableSize = origin.offset - _firstGroupOffset - sizeof(uint32); _table = new hResEntry[tableSize / resourceSize](); if (_base == nullptr || _table == nullptr) return; debugC(1, kDebugResources, "Reading %d entries:", _numEntries); for (int i = 0; i < _numEntries; ++i) readEntry(_base[i]); debugC(1, kDebugResources, "Reading %d groups:", tableSize / resourceSize); _file.seek(_firstGroupOffset, SEEK_SET); for (int i = 0; i < tableSize / resourceSize; ++i) { readResource(_table[i]); } _res = this; _valid = true; } hResource::~hResource() { if (_base) delete[] _base; if (_table) delete[] _table; //if (_handle) free(_handle); } hResContext *hResource::newContext(hResID id, const char desc[]) { hResContext *result; result = new hResContext(this, id, desc); if (result == nullptr || !result->_valid) { error("Error accessing resource group."); } result->_filename = _filename; return result; } void hResource::disposeContext(hResContext *con) { if (con) delete con; } } // end of namespace Saga2