Initial commit
This commit is contained in:
432
engines/mads/resources.cpp
Normal file
432
engines/mads/resources.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
/* 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 "common/scummsys.h"
|
||||
#include "common/archive.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "mads/mads.h"
|
||||
#include "mads/resources.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
enum ResourceType {RESTYPE_ROOM, RESTYPE_SC, RESTYPE_TEXT, RESTYPE_QUO, RESTYPE_I,
|
||||
RESTYPE_OB, RESTYPE_FONT, RESTYPE_SOUND, RESTYPE_SPEECH, RESTYPE_HAS_EXT, RESTYPE_NO_EXT};
|
||||
|
||||
/**
|
||||
* HAG Archives implementation
|
||||
*/
|
||||
class HagArchive : public Common::Archive {
|
||||
private:
|
||||
/**
|
||||
* Details of a single entry in a HAG file index
|
||||
*/
|
||||
struct HagEntry {
|
||||
Common::Path _resourceName;
|
||||
uint32 _offset;
|
||||
uint32 _size;
|
||||
|
||||
HagEntry() : _offset(0), _size(0) {}
|
||||
HagEntry(const Common::Path &resourceName, uint32 offset, uint32 size)
|
||||
: _resourceName(resourceName), _offset(offset), _size(size) {
|
||||
}
|
||||
};
|
||||
|
||||
class HagIndex {
|
||||
public:
|
||||
Common::List<HagEntry> _entries;
|
||||
Common::Path _filename;
|
||||
};
|
||||
|
||||
Common::Array<HagIndex> _index;
|
||||
|
||||
/**
|
||||
* Load the index of all the game's HAG files
|
||||
*/
|
||||
void loadIndex(MADSEngine *vm);
|
||||
|
||||
/**
|
||||
* Given a resource name, opens up the correct HAG file and returns whether
|
||||
* an entry with the given name exists.
|
||||
*/
|
||||
bool getHeaderEntry(const Common::Path &resourceName, HagIndex &hagIndex, HagEntry &hagEntry) const;
|
||||
|
||||
/**
|
||||
* Returns the HAG resource filename that will contain a given resource
|
||||
*/
|
||||
Common::Path getResourceFilename(const Common::Path &resourceName) const;
|
||||
|
||||
/**
|
||||
* Return a resource type given a resource name
|
||||
*/
|
||||
ResourceType getResourceType(const Common::String &resourceName) const;
|
||||
public:
|
||||
explicit HagArchive(MADSEngine *vm);
|
||||
~HagArchive() override;
|
||||
|
||||
// Archive implementation
|
||||
bool hasFile(const Common::Path &path) const override;
|
||||
int listMembers(Common::ArchiveMemberList &list) const override;
|
||||
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
|
||||
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
|
||||
};
|
||||
|
||||
const char *const MADSCONCAT_STRING = "MADSCONCAT";
|
||||
|
||||
HagArchive::HagArchive(MADSEngine *vm) {
|
||||
loadIndex(vm);
|
||||
}
|
||||
|
||||
HagArchive::~HagArchive() {
|
||||
}
|
||||
|
||||
// Archive implementation
|
||||
bool HagArchive::hasFile(const Common::Path &path) const {
|
||||
HagIndex hagIndex;
|
||||
HagEntry hagEntry;
|
||||
return getHeaderEntry(path, hagIndex, hagEntry);
|
||||
}
|
||||
|
||||
int HagArchive::listMembers(Common::ArchiveMemberList &list) const {
|
||||
int members = 0;
|
||||
|
||||
for (uint hagCtr = 0; hagCtr < _index.size(); ++hagCtr) {
|
||||
HagIndex hagIndex = _index[hagCtr];
|
||||
Common::List<HagEntry>::iterator i;
|
||||
|
||||
for (i = hagIndex._entries.begin(); i != hagIndex._entries.end(); ++i) {
|
||||
list.push_back(Common::ArchiveMemberList::value_type(
|
||||
new Common::GenericArchiveMember((*i)._resourceName, *this)));
|
||||
++members;
|
||||
}
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
const Common::ArchiveMemberPtr HagArchive::getMember(const Common::Path &path) const {
|
||||
if (!hasFile(path))
|
||||
return Common::ArchiveMemberPtr();
|
||||
|
||||
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *HagArchive::createReadStreamForMember(const Common::Path &path) const {
|
||||
HagIndex hagIndex;
|
||||
HagEntry hagEntry;
|
||||
|
||||
if (getHeaderEntry(path, hagIndex, hagEntry)) {
|
||||
// Entry found. If the correct file is not already open, open it
|
||||
Common::File f;
|
||||
if (!f.open(hagIndex._filename))
|
||||
error("Could not open HAG file");
|
||||
|
||||
// Return a new stream for the specific resource
|
||||
f.seek(hagEntry._offset);
|
||||
return f.readStream(hagEntry._size);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HagArchive::loadIndex(MADSEngine *vm) {
|
||||
Common::File hagFile;
|
||||
|
||||
for (int sectionIndex = -1; sectionIndex < 11; ++sectionIndex) {
|
||||
if (sectionIndex == 0 && !Common::File::exists("SECTION0.HAG"))
|
||||
continue;
|
||||
|
||||
// Rex Nebular and Dragonsphere demos only have sections 1 and 9 - skip the rest
|
||||
if ((vm->getGameID() == GType_RexNebular || vm->getGameID() == GType_Dragonsphere) && vm->isDemo()) {
|
||||
if (sectionIndex != 1 && sectionIndex != 9)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phantom demo only has sections 1, 2 and 9 - skip the rest
|
||||
if (vm->getGameID() == GType_Phantom && vm->isDemo()) {
|
||||
if (sectionIndex != 1 && sectionIndex != 2 && sectionIndex != 9)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dragonsphere does not have some sections - skip them
|
||||
if (vm->getGameID() == GType_Dragonsphere) {
|
||||
if (sectionIndex == 7 || sectionIndex == 8)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Phantom and Forest don't have some sections - skip them
|
||||
if (vm->getGameID() == GType_Phantom || vm->getGameID() == GType_Forest) {
|
||||
if (sectionIndex == 6 || sectionIndex == 7 || sectionIndex == 8)
|
||||
continue;
|
||||
}
|
||||
|
||||
Common::Path filename = (sectionIndex == -1) ? Common::Path("GLOBAL.HAG") :
|
||||
Common::Path(Common::String::format("SECTION%d.HAG", sectionIndex));
|
||||
if (sectionIndex == 10) {
|
||||
// Speech
|
||||
if (!Common::File::exists("SPEECH.HAG"))
|
||||
break;
|
||||
else
|
||||
filename = "SPEECH.HAG";
|
||||
}
|
||||
if (!hagFile.open(filename))
|
||||
error("Could not locate HAG file - %s", filename.toString().c_str());
|
||||
|
||||
// Check for header
|
||||
char headerBuffer[16];
|
||||
if ((hagFile.read(headerBuffer, 16) != 16) ||
|
||||
(strncmp(headerBuffer, MADSCONCAT_STRING, 10) != 0))
|
||||
error("Invalid HAG file opened");
|
||||
|
||||
// Scan through the HAG index
|
||||
int numEntries = hagFile.readUint16LE();
|
||||
|
||||
HagIndex hagIndex;
|
||||
hagIndex._filename = filename;
|
||||
|
||||
for (int idx = 0; idx < numEntries; ++idx) {
|
||||
// Read in the details of the next resource
|
||||
char resourceBuffer[14];
|
||||
uint32 offset = hagFile.readUint32LE();
|
||||
uint32 size = hagFile.readUint32LE();
|
||||
hagFile.read(resourceBuffer, 14);
|
||||
|
||||
hagIndex._entries.push_back(HagEntry(resourceBuffer, offset, size));
|
||||
}
|
||||
|
||||
hagFile.close();
|
||||
_index.push_back(hagIndex);
|
||||
}
|
||||
}
|
||||
|
||||
bool HagArchive::getHeaderEntry(const Common::Path &resourceName,
|
||||
HagIndex &hagIndex, HagEntry &hagEntry) const {
|
||||
Common::Path resName = resourceName;
|
||||
resName.toUppercase();
|
||||
Common::String baseName(resName.baseName());
|
||||
if (baseName[0] == '*') {
|
||||
baseName.deleteChar(0);
|
||||
resName = resName.getParent().appendComponent(baseName);
|
||||
}
|
||||
|
||||
Common::Path hagFilename = getResourceFilename(resName);
|
||||
|
||||
// Find the index for the given file
|
||||
for (uint hagCtr = 0; hagCtr < _index.size(); ++hagCtr) {
|
||||
hagIndex = _index[hagCtr];
|
||||
|
||||
if (hagIndex._filename == hagFilename) {
|
||||
Common::List<HagEntry>::iterator ei;
|
||||
for (ei = hagIndex._entries.begin(); ei != hagIndex._entries.end(); ++ei) {
|
||||
hagEntry = *ei;
|
||||
if (hagEntry._resourceName.equalsIgnoreCase(resName))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::Path HagArchive::getResourceFilename(const Common::Path &resourceName) const {
|
||||
Common::String baseName(resourceName.baseName());
|
||||
ResourceType resType = getResourceType(baseName);
|
||||
Common::Path outputFilename = "GLOBAL.HAG";
|
||||
|
||||
if ((resType == RESTYPE_ROOM) || (resType == RESTYPE_SC)) {
|
||||
int value = atoi(baseName.c_str() + 2);
|
||||
int hagFileNum = (resType == RESTYPE_ROOM) ? value / 100 : value;
|
||||
|
||||
if (hagFileNum >= 0)
|
||||
outputFilename = Common::Path(Common::String::format("SECTION%d.HAG", hagFileNum));
|
||||
}
|
||||
|
||||
if (resType == RESTYPE_SPEECH)
|
||||
outputFilename = "SPEECH.HAG";
|
||||
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
ResourceType HagArchive::getResourceType(const Common::String &resourceName) const {
|
||||
if (resourceName.hasPrefix("RM")) {
|
||||
// Room resource
|
||||
return RESTYPE_ROOM;
|
||||
} else if (resourceName.hasPrefix("SC")) {
|
||||
// SC resource
|
||||
return RESTYPE_SC;
|
||||
} else if (resourceName.hasSuffix(".TXT")) {
|
||||
// Text resource
|
||||
return RESTYPE_TEXT;
|
||||
} else if (resourceName.hasSuffix(".QUO")) {
|
||||
// QUO resource
|
||||
return RESTYPE_QUO;
|
||||
} else if (resourceName.hasPrefix("I")) {
|
||||
// I resource
|
||||
return RESTYPE_I;
|
||||
} else if (resourceName.hasPrefix("OB")) {
|
||||
// OB resource
|
||||
return RESTYPE_OB;
|
||||
} else if (resourceName.hasPrefix("FONT")) {
|
||||
// FONT resource
|
||||
return RESTYPE_FONT;
|
||||
} else if (resourceName.hasPrefix("SOUND")) {
|
||||
// SOUND resource
|
||||
return RESTYPE_SOUND;
|
||||
} else if (resourceName.hasPrefix("SPCHC")) {
|
||||
// SPEECH resource
|
||||
return RESTYPE_SPEECH;
|
||||
}
|
||||
|
||||
// Check for a known extension
|
||||
const char *extPos = strchr(resourceName.c_str(), '.');
|
||||
if (extPos) {
|
||||
++extPos;
|
||||
if (!strcmp(extPos, "FL") || !strcmp(extPos, "LBM") || !strcmp(extPos, "ANM") ||
|
||||
!strcmp(extPos, "AA") || !strcmp(extPos, "SS")) {
|
||||
return RESTYPE_HAS_EXT;
|
||||
}
|
||||
}
|
||||
|
||||
return RESTYPE_NO_EXT;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void Resources::init(MADSEngine *vm) {
|
||||
SearchMan.add("HAG", new HagArchive(vm));
|
||||
}
|
||||
|
||||
Common::Path Resources::formatName(RESPREFIX resType, int id, const Common::String &ext) {
|
||||
Common::String result = "*";
|
||||
|
||||
if (resType == 3 && !id) {
|
||||
id = id / 100;
|
||||
}
|
||||
|
||||
if (!ext.empty()) {
|
||||
switch (resType) {
|
||||
case RESPREFIX_GL:
|
||||
result += "GL000";
|
||||
break;
|
||||
case RESPREFIX_SC:
|
||||
result += Common::String::format("SC%.3d", id);
|
||||
break;
|
||||
case RESPREFIX_RM:
|
||||
result += Common::String::format("RM%.3d", id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
result += ext;
|
||||
}
|
||||
|
||||
return Common::Path(result);
|
||||
}
|
||||
|
||||
Common::Path Resources::formatName(int prefix, char asciiCh, int id, EXTTYPE extType,
|
||||
const Common::String &suffix) {
|
||||
Common::String result;
|
||||
if (prefix <= 0) {
|
||||
result = "*";
|
||||
} else {
|
||||
result = Common::String::format("%s%.3d",
|
||||
(prefix < 100) ? "*SC" : "*RM", prefix);
|
||||
}
|
||||
|
||||
result += Common::String::format("%c", asciiCh);
|
||||
if (id >= 0)
|
||||
result += Common::String::format("%d", id);
|
||||
if (!suffix.empty())
|
||||
result += suffix;
|
||||
|
||||
switch (extType) {
|
||||
case EXT_SS:
|
||||
result += ".SS";
|
||||
break;
|
||||
case EXT_AA:
|
||||
result += ".AA";
|
||||
break;
|
||||
case EXT_DAT:
|
||||
result += ".DAT";
|
||||
break;
|
||||
case EXT_HH:
|
||||
result += ".HH";
|
||||
break;
|
||||
case EXT_ART:
|
||||
result += ".ART";
|
||||
break;
|
||||
case EXT_INT:
|
||||
result += ".INT";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Common::Path(result);
|
||||
}
|
||||
|
||||
Common::Path Resources::formatResource(const Common::String &resName,
|
||||
const Common::String &hagFilename) {
|
||||
// int v1 = 0, v2 = 0;
|
||||
|
||||
if (resName.hasPrefix("*")) {
|
||||
// Resource file specified
|
||||
error("TODO: formatResource");
|
||||
} else {
|
||||
// File outside of hag file
|
||||
return Common::Path(resName);
|
||||
}
|
||||
}
|
||||
|
||||
Common::Path Resources::formatAAName(int idx) {
|
||||
return formatName(0, 'I', idx, EXT_AA, "");
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void File::openFile(const Common::Path &filename) {
|
||||
if (!Common::File::open(filename))
|
||||
error("Could not open file - %s", filename.toString().c_str());
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void SynchronizedList::synchronize(Common::Serializer &s) {
|
||||
int v = 0;
|
||||
int count = size();
|
||||
s.syncAsUint16LE(count);
|
||||
|
||||
if (s.isSaving()) {
|
||||
for (int idx = 0; idx < count; ++idx) {
|
||||
v = (*this)[idx];
|
||||
s.syncAsSint32LE(v);
|
||||
}
|
||||
} else {
|
||||
clear();
|
||||
reserve(count);
|
||||
for (int idx = 0; idx < count; ++idx) {
|
||||
s.syncAsSint32LE(v);
|
||||
push_back(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace MADS
|
||||
Reference in New Issue
Block a user