674 lines
16 KiB
C++
674 lines
16 KiB
C++
#include <algorithm>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <zlib.h>
|
|
|
|
#include "resources.h"
|
|
#include "exceptions.h"
|
|
#include "unicode.h"
|
|
#include "convert.h"
|
|
#include "i18n.h"
|
|
#include "utils.h"
|
|
|
|
|
|
ResourcesCollection *resources = NULL;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// UnpackedResourceStream
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
class UnpackedResourceStream: public ResourceStream
|
|
{
|
|
private:
|
|
std::ifstream &stream;
|
|
size_t size;
|
|
long offset;
|
|
long pos;
|
|
|
|
public:
|
|
UnpackedResourceStream(std::ifstream &stream, long offset,
|
|
size_t size);
|
|
|
|
public:
|
|
virtual size_t getSize() { return size; };
|
|
virtual void seek(long offset);
|
|
virtual void read(char *buffer, size_t size);
|
|
virtual long getPos() { return pos; };
|
|
};
|
|
|
|
UnpackedResourceStream::UnpackedResourceStream(std::ifstream &s,
|
|
long off, size_t sz): stream(s)
|
|
{
|
|
offset = off;
|
|
size = sz;
|
|
pos = 0;
|
|
}
|
|
|
|
void UnpackedResourceStream::seek(long off)
|
|
{
|
|
if ((off < 0) || ((size_t)off > size))
|
|
throw Exception(L"Invalid seek in ResourceStream");
|
|
pos = off;
|
|
}
|
|
|
|
void UnpackedResourceStream::read(char *buffer, size_t sz)
|
|
{
|
|
if (! buffer)
|
|
throw Exception(L"Invalid buffer in ResourceStream");
|
|
if (sz + pos > size)
|
|
throw Exception(L"Attempt of reading after resource end");
|
|
stream.seekg(offset + pos, std::ios::beg);
|
|
stream.read(buffer, sz);
|
|
pos += sz;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// MemoryResourceStream
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
class MemoryResourceStream: public ResourceStream
|
|
{
|
|
private:
|
|
char *data;
|
|
size_t size;
|
|
ResVariant *resource;
|
|
long pos;
|
|
|
|
public:
|
|
MemoryResourceStream(ResVariant *resource);
|
|
virtual ~MemoryResourceStream();
|
|
|
|
public:
|
|
virtual size_t getSize() { return size; };
|
|
virtual void seek(long offset);
|
|
virtual void read(char *buffer, size_t size);
|
|
virtual long getPos() { return pos; };
|
|
};
|
|
|
|
MemoryResourceStream::MemoryResourceStream(ResVariant *res)
|
|
{
|
|
resource = res;
|
|
data = (char*)res->getRef(size);
|
|
pos = 0;
|
|
}
|
|
|
|
MemoryResourceStream::~MemoryResourceStream()
|
|
{
|
|
resource->delRef(data);
|
|
}
|
|
|
|
void MemoryResourceStream::seek(long off)
|
|
{
|
|
if ((off < 0) || ((size_t)off > size))
|
|
throw Exception(L"Invalid seek in ResourceStream");
|
|
pos = off;
|
|
}
|
|
|
|
void MemoryResourceStream::read(char *buffer, size_t sz)
|
|
{
|
|
if (! buffer)
|
|
throw Exception(L"Invalid buffer in ResourceStream");
|
|
if (sz + pos > size)
|
|
throw Exception(L"Attempt of reading after resource end");
|
|
memcpy(buffer, data, sz);
|
|
pos += sz;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// ResourceFile
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
ResourceFile::ResourceFile(const std::wstring &fileName, Buffer *buf):
|
|
name(fileName)
|
|
{
|
|
if (buf) {
|
|
buffer = buf;
|
|
ownBuffer = false;
|
|
} else {
|
|
buffer = new Buffer();
|
|
ownBuffer = true;
|
|
}
|
|
|
|
stream.open(toMbcs(fileName).c_str(), std::ios::in | std::ios::binary);
|
|
if (stream.fail())
|
|
throw Exception(L"Error loading resource file '" + name + L"'");
|
|
|
|
char sign[4];
|
|
stream.read(sign, 4);
|
|
int readed = stream.gcount();
|
|
if (stream.fail() || (readed != 4) || (sign[0] != 'C') ||
|
|
(sign[1] != 'R') || (sign[2] != 'F') || sign[3])
|
|
throw Exception(L"Invalid resource file '" + name + L"'");
|
|
|
|
int major = readInt(stream);
|
|
readed = stream.gcount();
|
|
int minor = readInt(stream);
|
|
readed += stream.gcount();
|
|
priority = readInt(stream);
|
|
readed += stream.gcount();
|
|
if (stream.fail() || (readed != 12) || (major != 2) || (minor < 0))
|
|
throw Exception(L"Incompatible version of resource file '" +
|
|
name + L"'");
|
|
}
|
|
|
|
|
|
ResourceFile::~ResourceFile()
|
|
{
|
|
stream.close();
|
|
if (ownBuffer)
|
|
delete buffer;
|
|
}
|
|
|
|
|
|
void ResourceFile::getDirectory(Directory &directory)
|
|
{
|
|
stream.seekg(-8, std::ios::end);
|
|
if (stream.fail())
|
|
throw Exception(L"Error reading " + name + L" directory");
|
|
int start = readInt(stream);
|
|
int count = readInt(stream);
|
|
stream.seekg(start, std::ios::beg);
|
|
if (stream.fail())
|
|
throw Exception(L"Error reading " + name + L" directory");
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
DirectoryEntry entry;
|
|
entry.name = readString(stream);
|
|
entry.unpackedSize = readInt(stream);
|
|
entry.offset = readInt(stream);
|
|
entry.packedSize = readInt(stream);
|
|
entry.level = readInt(stream);
|
|
entry.group = readString(stream);
|
|
directory.push_back(entry);
|
|
}
|
|
|
|
if (stream.fail())
|
|
throw Exception(L"Error reading " + name + L" directory");
|
|
}
|
|
|
|
|
|
void ResourceFile::unpack(char *in, int inSize, char *out, int outSize)
|
|
{
|
|
z_stream zs;
|
|
|
|
memset(&zs, 0, sizeof(z_stream));
|
|
zs.next_in = (Bytef*)in;
|
|
zs.avail_in = inSize;
|
|
zs.next_out = (Bytef*)out;
|
|
zs.avail_out = outSize;
|
|
|
|
if (inflateInit(&zs) != Z_OK)
|
|
throw Exception(name + L": Error initializing inflate stream.");
|
|
|
|
if (inflate(&zs, Z_FINISH) != Z_STREAM_END)
|
|
throw Exception(name + L": Error decompresing element.");
|
|
|
|
if (inflateEnd(&zs) != Z_OK)
|
|
throw Exception(name + L": Error finishing decompresing.");
|
|
}
|
|
|
|
|
|
void ResourceFile::load(char *buf, long offset, long packedSize,
|
|
long unpackedSize, int level)
|
|
{
|
|
char *inBuf=NULL;
|
|
|
|
try {
|
|
if (! level) {
|
|
stream.seekg(offset, std::ios::beg);
|
|
stream.read(buf, unpackedSize);
|
|
return;
|
|
}
|
|
|
|
buffer->setSize(packedSize);
|
|
stream.seekg(offset, std::ios::beg);
|
|
stream.read((char*)buffer->getData(), packedSize);
|
|
unpack((char*)buffer->getData(), packedSize, buf, unpackedSize);
|
|
} catch (Exception &e) {
|
|
if (inBuf) free(inBuf);
|
|
throw e;
|
|
} catch (...) {
|
|
if (inBuf) free(inBuf);
|
|
throw Exception(name + L": Error loading resource");
|
|
}
|
|
}
|
|
|
|
|
|
void* ResourceFile::load(long offset, long packedSize, long unpackedSize,
|
|
int level)
|
|
{
|
|
char *outBuf=NULL;
|
|
|
|
try {
|
|
outBuf = (char*)malloc(unpackedSize);
|
|
if (! outBuf)
|
|
throw Exception(name + L": Error allocating memory");
|
|
load(outBuf, offset, packedSize, unpackedSize, level);
|
|
} catch (Exception &e) {
|
|
if (outBuf) free(outBuf);
|
|
throw e;
|
|
} catch (...) {
|
|
if (outBuf) free(outBuf);
|
|
throw Exception(name + L": Error loading resource");
|
|
}
|
|
|
|
return outBuf;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// SimpleResourceFile
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
SimpleResourceFile::SimpleResourceFile(const std::wstring &fileName,
|
|
Buffer *buf): ResourceFile(fileName, buf)
|
|
{
|
|
Directory entries;
|
|
getDirectory(entries);
|
|
for (Directory::iterator i = entries.begin(); i != entries.end(); i++) {
|
|
DirectoryEntry &e = *i;
|
|
directory[e.name] = e;
|
|
}
|
|
}
|
|
|
|
void* SimpleResourceFile::load(const std::wstring &name, int &size)
|
|
{
|
|
DirectoryMap::iterator i = directory.find(name);
|
|
if (i != directory.end()) {
|
|
DirectoryEntry &e = (*i).second;
|
|
size = e.unpackedSize;
|
|
return ResourceFile::load(e.offset, e.packedSize, e.unpackedSize,
|
|
e.level);
|
|
} else
|
|
throw Exception(L"Resource '" + name + L"' not found");
|
|
}
|
|
|
|
void SimpleResourceFile::load(const std::wstring &name, Buffer &outBuf)
|
|
{
|
|
DirectoryMap::iterator i = directory.find(name);
|
|
if (i != directory.end()) {
|
|
DirectoryEntry &e = (*i).second;
|
|
outBuf.setSize(e.unpackedSize);
|
|
ResourceFile::load((char*)outBuf.getData(), e.offset,
|
|
e.packedSize, e.unpackedSize, e.level);
|
|
} else
|
|
throw Exception(L"Resource '" + name + L"' not found");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// ResVariant
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
ResVariant::ResVariant(ResourceFile *f, int score,
|
|
const ResourceFile::DirectoryEntry &e)
|
|
{
|
|
file = f;
|
|
i18nScore = score;
|
|
offset = e.offset;
|
|
unpackedSize = e.unpackedSize;
|
|
packedSize = e.packedSize;
|
|
level = e.level;
|
|
refCnt = 0;
|
|
data = NULL;
|
|
}
|
|
|
|
|
|
ResVariant::~ResVariant()
|
|
{
|
|
if (data)
|
|
free((char*)data - sizeof(ResVariant*));
|
|
}
|
|
|
|
void* ResVariant::getRef()
|
|
{
|
|
if (! refCnt) {
|
|
char* d = (char*)malloc(unpackedSize + sizeof(void*));
|
|
if (! d)
|
|
throw Exception(L"ResVariant::getRef memory allocation error");
|
|
ResVariant *self = this;
|
|
file->load(d + sizeof(self), offset, packedSize, unpackedSize,
|
|
level);
|
|
memcpy(d, &self, sizeof(self));
|
|
data = d + sizeof(self);
|
|
}
|
|
|
|
refCnt++;
|
|
return data;
|
|
}
|
|
|
|
void* ResVariant::getRef(size_t &sz)
|
|
{
|
|
sz = unpackedSize;
|
|
return getRef();
|
|
}
|
|
|
|
void ResVariant::delRef(void *dta)
|
|
{
|
|
if ((! refCnt) || (dta != data))
|
|
throw Exception(L"Invalid ResVariant::delRef call");
|
|
|
|
refCnt--;
|
|
if (! refCnt) {
|
|
free((char*)data - sizeof(ResVariant*));
|
|
data = NULL;
|
|
}
|
|
}
|
|
|
|
void* ResVariant::getDynData()
|
|
{
|
|
if (! refCnt)
|
|
return file->load(offset, packedSize, unpackedSize, level);
|
|
else {
|
|
char* d = (char*)malloc(unpackedSize);
|
|
if (! d)
|
|
throw Exception(L"ResVariant::getDynData memory allocation error");
|
|
memcpy(d, data, unpackedSize);
|
|
return data;
|
|
}
|
|
}
|
|
|
|
void ResVariant::getData(Buffer &buffer)
|
|
{
|
|
buffer.setSize(unpackedSize);
|
|
if (! refCnt)
|
|
file->load((char*)buffer.getData(), offset, packedSize,
|
|
unpackedSize, level);
|
|
else
|
|
memcpy((char*)buffer.getData(), data, unpackedSize);
|
|
}
|
|
|
|
ResourceStream* ResVariant::createStream()
|
|
{
|
|
if (refCnt || level)
|
|
return new MemoryResourceStream(this);
|
|
else
|
|
return new UnpackedResourceStream(file->getStream(), offset,
|
|
packedSize);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// Resource
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
Resource::Resource(ResourceFile *file, int i18nScore,
|
|
const ResourceFile::DirectoryEntry &entry, const std::wstring &n):
|
|
name(n)
|
|
{
|
|
addVariant(file, i18nScore, entry);
|
|
}
|
|
|
|
Resource::~Resource()
|
|
{
|
|
for (Variants::iterator i = variants.begin(); i != variants.end(); i++)
|
|
delete *i;
|
|
}
|
|
|
|
class ScorePredicate
|
|
{
|
|
public:
|
|
int score;
|
|
|
|
ScorePredicate(int sc) { score = sc; }
|
|
|
|
bool operator() (const ResVariant *r) const {
|
|
return r->getI18nScore() == score;
|
|
};
|
|
};
|
|
|
|
class ResVariantMoreThen
|
|
{
|
|
public:
|
|
bool operator() (const ResVariant *v1, const ResVariant *v2) const {
|
|
return v1->getI18nScore() > v2->getI18nScore();
|
|
};
|
|
};
|
|
|
|
void Resource::addVariant(ResourceFile *file, int i18nScore,
|
|
const ResourceFile::DirectoryEntry &entry)
|
|
{
|
|
if (! variants.size()) {
|
|
variants.push_back(new ResVariant(file, i18nScore, entry));
|
|
return;
|
|
}
|
|
|
|
ScorePredicate p(i18nScore);
|
|
Variants::iterator i = std::find_if(variants.begin(), variants.end(), p);
|
|
if (i != variants.end()) {
|
|
delete *i;
|
|
*i = new ResVariant(file, i18nScore, entry);
|
|
} else {
|
|
variants.push_back(new ResVariant(file, i18nScore, entry));
|
|
ResVariantMoreThen comparator;
|
|
std::sort(variants.begin(), variants.end(), comparator);
|
|
}
|
|
}
|
|
|
|
|
|
void* Resource::getRef(int variant)
|
|
{
|
|
return variants[variant]->getRef();
|
|
}
|
|
|
|
|
|
void* Resource::getRef(int *size, int variant)
|
|
{
|
|
if (size)
|
|
*size = variants[variant]->getSize();
|
|
return variants[variant]->getRef();
|
|
}
|
|
|
|
|
|
void Resource::delRef(void *data)
|
|
{
|
|
ResVariant *v = *(ResVariant**)((char*)data - sizeof(ResVariant*));
|
|
v->delRef(data);
|
|
}
|
|
|
|
|
|
void Resource::getData(Buffer &buffer, int variant)
|
|
{
|
|
variants[variant]->getData(buffer);
|
|
}
|
|
|
|
ResourceStream* Resource::createStream(int variant)
|
|
{
|
|
return variants[variant]->createStream();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// ResourcesCollection
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
class ResFileMoreThen
|
|
{
|
|
public:
|
|
bool operator() (const ResourceFile *f1, const ResourceFile *f2) const {
|
|
return f1->getPriority() > f2->getPriority();
|
|
};
|
|
};
|
|
|
|
ResourcesCollection::ResourcesCollection(StringList &directories)
|
|
{
|
|
loadResourceFiles(directories);
|
|
ResFileMoreThen comparator;
|
|
std::sort(files.begin(), files.end(), comparator);
|
|
processFiles();
|
|
}
|
|
|
|
|
|
ResourcesCollection::~ResourcesCollection()
|
|
{
|
|
for (ResourcesMap::iterator i = resources.begin(); i != resources.end(); i++)
|
|
delete (*i).second;
|
|
for (ResourceFiles::iterator i = files.begin(); i != files.end(); i++)
|
|
delete *i;
|
|
}
|
|
|
|
|
|
void ResourcesCollection::loadResourceFiles(StringList &directories)
|
|
{
|
|
for (StringList::iterator i = directories.begin();
|
|
i != directories.end(); i++)
|
|
{
|
|
const std::wstring &d = *i;
|
|
DIR *dir = opendir(toMbcs(d).c_str());
|
|
if (dir) {
|
|
struct dirent *de;
|
|
while ((de = readdir(dir)))
|
|
if (de->d_name[0] != '.') {
|
|
std::wstring s(fromMbcs(de->d_name));
|
|
if ((s.length() > 4) &&
|
|
(toLowerCase(s.substr(s.length() - 4)) == L".res"))
|
|
files.push_back(new ResourceFile(d + L"/" + s, &buffer));
|
|
}
|
|
closedir(dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ResourcesCollection::processFiles()
|
|
{
|
|
ResourceFile::Directory dir;
|
|
for (std::vector<ResourceFile*>::iterator i = files.begin();
|
|
i != files.end(); i++)
|
|
{
|
|
ResourceFile *file = *i;
|
|
file->getDirectory(dir);
|
|
for (ResourceFile::Directory::iterator j = dir.begin();
|
|
j != dir.end(); j++)
|
|
{
|
|
ResourceFile::DirectoryEntry &de = *j;
|
|
std::wstring name, ext, language, country;
|
|
splitFileName(de.name, name, ext, language, country);
|
|
int score = getScore(language, country, locale);
|
|
if (score > 0) {
|
|
std::wstring resName = name + L"." + ext;
|
|
Resource *res = resources[resName];
|
|
if (! res) {
|
|
res = new Resource(file, score, de, resName);
|
|
resources[resName] = res;
|
|
if (de.group.length())
|
|
groups[de.group].push_back(res);
|
|
} else
|
|
res->addVariant(file, score, de);
|
|
}
|
|
}
|
|
dir.clear();
|
|
}
|
|
}
|
|
|
|
|
|
Resource* ResourcesCollection::getResource(const std::wstring &name)
|
|
{
|
|
Resource *r = resources[name];
|
|
if (! r)
|
|
throw Exception(L"Resource '" + name + L"' not found");
|
|
return r;
|
|
}
|
|
|
|
|
|
void* ResourcesCollection::getRef(const std::wstring &name, int &size)
|
|
{
|
|
Resource *r = getResource(name);
|
|
ResVariant *v = r->getVariant(0);
|
|
size = v->getSize();
|
|
return v->getRef();
|
|
}
|
|
|
|
|
|
void* ResourcesCollection::getRef(const std::wstring &name)
|
|
{
|
|
Resource *r = getResource(name);
|
|
ResVariant *v = r->getVariant(0);
|
|
return v->getRef();
|
|
}
|
|
|
|
ResourceStream* ResourcesCollection::createStream(const std::wstring &name)
|
|
{
|
|
Resource *r = getResource(name);
|
|
return r->createStream();
|
|
}
|
|
|
|
void ResourcesCollection::delRef(void *data)
|
|
{
|
|
ResVariant *v = *(ResVariant**)((char*)data - sizeof(ResVariant*));
|
|
v->delRef(data);
|
|
}
|
|
|
|
void ResourcesCollection::forEachInGroup(const std::wstring &name,
|
|
Visitor<Resource*> &visitor)
|
|
{
|
|
if (groups.count(name) > 0) {
|
|
ResourcesList &l = groups[name];
|
|
for (ResourcesList::iterator i = l.begin(); i != l.end(); i++) {
|
|
Resource *r = *i;
|
|
visitor.onVisit(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResourcesCollection::loadData(const std::wstring &name, Buffer &buffer)
|
|
{
|
|
Resource *r = getResource(name);
|
|
r->getData(buffer);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
// ResDataHolder
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
ResDataHolder::ResDataHolder()
|
|
{
|
|
data = NULL;
|
|
size = 0;
|
|
}
|
|
|
|
ResDataHolder::ResDataHolder(const std::wstring &name)
|
|
{
|
|
load(name);
|
|
}
|
|
|
|
ResDataHolder::~ResDataHolder()
|
|
{
|
|
if (data)
|
|
resources->delRef(data);
|
|
}
|
|
|
|
void ResDataHolder::load(const std::wstring &name)
|
|
{
|
|
int s;
|
|
data = resources->getRef(name, s);
|
|
size = (size_t)s;
|
|
}
|