#include #include #include #include #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::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 &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; }