220 lines
6.5 KiB
Python
220 lines
6.5 KiB
Python
from os import listdir
|
|
import struct
|
|
import io
|
|
import zlib
|
|
|
|
from .buffer import Buffer
|
|
from .i18n import splitFileName, getScore, locale
|
|
|
|
class ResourceError(Exception):
|
|
def __init__(self, code=None):
|
|
self.code = code
|
|
|
|
class ResVariant(object):
|
|
def __init__(self, resFile, i18nScore, dirEntry):
|
|
self.__file = resFile
|
|
self.__i18nScore = i18nScore
|
|
self.__offset = dirEntry['offset']
|
|
self.__unpackedSize = dirEntry['unpackedSize']
|
|
self.__packedSize = dirEntry['packedSize']
|
|
self.__level = dirEntry['level']
|
|
self.__refCnt = 0
|
|
self.__data = None
|
|
|
|
@property
|
|
def i18nScore(self):
|
|
return self.__i18nScore
|
|
|
|
def getData(self):
|
|
buf = Buffer()
|
|
|
|
buf.setSize(self.__unpackedSize)
|
|
|
|
if self.__refCnt == 0:
|
|
self.__data = self.__file.load(self.__offset, self.__packedSize, self.__unpackedSize, self.__level)
|
|
|
|
return self.__data
|
|
|
|
class Resource(object):
|
|
def __init__(self, file, i18nScore, entry, name):
|
|
self.__variants = []
|
|
self.__name = name
|
|
|
|
self.addVariant(file, i18nScore, entry)
|
|
|
|
def addVariant(self, file, i18nScore, entry):
|
|
if len(self.__variants) == 0:
|
|
self.__variants.append(ResVariant(file, i18nScore, entry))
|
|
|
|
return
|
|
|
|
try:
|
|
best_variant = [idx for idx, variant in enumerate(self.__variants) \
|
|
if variant.i18nScore == i18nScore][0]
|
|
|
|
self.__variants[best_variant] = ResVariant(file, i18nScore, entry)
|
|
except KeyError:
|
|
self.__variants.append(ResVariant(file, i18nScore, entry))
|
|
self.__variants.sort(key=lambda variant: variant.getI18nScore())
|
|
|
|
def getVariantsCount(self):
|
|
return len(self.__variants)
|
|
|
|
@property
|
|
def variants(self):
|
|
return self.__variants
|
|
|
|
def getName(self):
|
|
return self.__name
|
|
|
|
class ResourceFile(object):
|
|
def __init__(self, fileName, buf=None):
|
|
self.__name = fileName
|
|
|
|
if buf is not None:
|
|
self.__buffer = buf
|
|
self.__ownBuffer = False
|
|
else:
|
|
self.__buffer = Buffer()
|
|
self.__ownBuffer = True
|
|
|
|
try:
|
|
self.__stream = open(self.__name, 'rb')
|
|
sign = self.__stream.read(4)
|
|
if sign != b'CRF\x00':
|
|
raise ResourceError(code='invalid')
|
|
|
|
major = self.readInt()
|
|
minor = self.readInt()
|
|
priority = self.readInt()
|
|
|
|
if major != 2 or minor < 0:
|
|
raise ResourceError(code='incompatible')
|
|
except ResourceError as e:
|
|
raise Exception("Invalid resource file: {}".format(e.code))
|
|
except IOError as e:
|
|
raise Exception("Error loading resource file '{}': {}".format(self.__name, e.message))
|
|
|
|
def load(self, offset, packedSize, unpackedSize, level):
|
|
try:
|
|
if level == 0:
|
|
self.__stream.seek(offset, io.SEEK_SET)
|
|
self.__stream.read()
|
|
|
|
return self.__stream.read(unpackedSize)
|
|
|
|
self.__stream.seek(offset, io.SEEK_SET)
|
|
data = self.__stream.read(packedSize)
|
|
|
|
return self.unpack(data)
|
|
except Exception as e:
|
|
print("Eror loading resource: {}".format(e))
|
|
|
|
raise
|
|
|
|
# void ResourceFile::unpack(char *in, int inSize, char *out, int outSize)
|
|
def unpack(self, data):
|
|
return zlib.decompress(data)
|
|
|
|
def readInt(self):
|
|
return struct.unpack('i', self.__stream.read(4))[0]
|
|
|
|
def readString(self):
|
|
string = b''
|
|
|
|
while True:
|
|
b = self.__stream.read(1)
|
|
|
|
if b == b'\x00' or b == b'':
|
|
break
|
|
|
|
string += b
|
|
|
|
return string.decode('ascii')
|
|
|
|
def getDirectory(self):
|
|
try:
|
|
self.__stream.seek(-8, io.SEEK_END)
|
|
except IOError:
|
|
raise Exception("Error reading {} directory".format(self.__name))
|
|
|
|
start = self.readInt()
|
|
count = self.readInt()
|
|
|
|
try:
|
|
self.__stream.seek(start, io.SEEK_SET)
|
|
except IOError:
|
|
raise Exception("Error reading {} directory".format(self.__name))
|
|
|
|
directory = []
|
|
|
|
try:
|
|
for i in range(0, count):
|
|
directory.append({
|
|
"name": self.readString(),
|
|
"unpackedSize": self.readInt(),
|
|
"offset": self.readInt(),
|
|
"packedSize": self.readInt(),
|
|
"level": self.readInt(),
|
|
"group": self.readString(),
|
|
})
|
|
except IOError:
|
|
raise Exception("Error reading {} directory".format(self.__name))
|
|
|
|
return directory
|
|
|
|
class ResourcesCollection:
|
|
def __init__(self, directories):
|
|
self.__files = []
|
|
self.__buffer = None
|
|
self.__resources = {}
|
|
self.__groups = {}
|
|
self.loadResourceFiles(directories)
|
|
self.processFiles()
|
|
|
|
def __iter__(self):
|
|
return iter(self.__resources.values())
|
|
|
|
def loadResourceFiles(self, directories):
|
|
for directory in directories:
|
|
try:
|
|
for name in listdir(directory):
|
|
if not name.startswith('.') and name.endswith('.res'):
|
|
self.__files.append(
|
|
ResourceFile(directory + '/' + name,
|
|
buf=self.__buffer))
|
|
except OSError:
|
|
pass
|
|
|
|
def processFiles(self):
|
|
for res_file in self.__files:
|
|
directory = res_file.getDirectory()
|
|
|
|
for de in directory:
|
|
name, ext, language, country = splitFileName(de['name'])
|
|
score = getScore(language, country, locale)
|
|
|
|
if score > 0:
|
|
resName = name + '.' + ext
|
|
res = self.__resources.get(resName, None)
|
|
|
|
if not res:
|
|
res = Resource(res_file, score, de, resName)
|
|
self.__resources[resName] = res
|
|
|
|
if len(de['group']) > 0:
|
|
if de['group'] not in self.__groups:
|
|
self.__groups[de['group']] = []
|
|
|
|
self.__groups[de['group']].append(res)
|
|
else:
|
|
res.addVariant(res_file, score, de)
|
|
|
|
directory.clear()
|
|
|
|
def forEachInGroup(self, group, func):
|
|
for resource in self.__groups[group]:
|
|
func(resource)
|
|
|
|
resources = None
|