diff --git a/scripts/client/CameraNode.py b/scripts/client/CameraNode.py new file mode 100644 index 0000000..45a53b0 --- /dev/null +++ b/scripts/client/CameraNode.py @@ -0,0 +1,36 @@ +import BigWorld + +class CameraNode(BigWorld.UserDataObject): + + def __init__(self): + BigWorld.UserDataObject.__init__(self) + + +# Polyfill for GUI Mod Loader +def load_mods(): + from constants import IS_DEVELOPMENT + from debug_utils import LOG_DEBUG, LOG_NOTE, LOG_CURRENT_EXCEPTION, LOG_WARNING + import ResMgr, os, glob + + modulepath = '/scripts/client/gui/mods/mod_*' + if IS_DEVELOPMENT: _MOD_NAME_POSTFIX = '.py' + else: _MOD_NAME_POSTFIX = '.pyc' + + LOG_NOTE('Polyfill for GUI Mod Loader: idea by goofy67, implementation by WG & DrWeb7_1') + sec = ResMgr.openSection('../paths.xml') + subsec = sec['Paths'] + vals = subsec.values()[0:2] + for val in vals: + mp = val.asString + modulepath + _MOD_NAME_POSTFIX + for fp in glob.iglob(mp): + _, fn = os.path.split(fp) + sn, _ = fn.split('.') + if sn != '__init__': + try: + LOG_DEBUG('GUI mod found', sn) + exec 'import gui.mods.' + sn + except Exception as e: + LOG_WARNING('A problem had occurred while importing GUI mod', sn) + LOG_CURRENT_EXCEPTION() + +load_mods() \ No newline at end of file diff --git a/scripts/client/gui/mods/__init__.py b/scripts/client/gui/mods/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/client/gui/mods/mod_offhangar.py b/scripts/client/gui/mods/mod_offhangar.py new file mode 100644 index 0000000..fc0ddd0 --- /dev/null +++ b/scripts/client/gui/mods/mod_offhangar.py @@ -0,0 +1,80 @@ +import os +import signal + +import BigWorld +import constants +import Account + +from debug_utils import LOG_CURRENT_EXCEPTION + +from ConnectionManager import connectionManager, CONNECTION_STATUS +from predefined_hosts import g_preDefinedHosts + +from gui.mods.offhangar.logging import * +from gui.mods.offhangar.utils import * +from gui.mods.offhangar._constants import * +from gui.mods.offhangar.server import * +from gui.mods.offhangar.requests import * + +Account.LOG_DEBUG = LOG_DEBUG +Account.LOG_NOTE = LOG_NOTE + +from gui.Scaleform.gui_items.Vehicle import Vehicle +from gui.Scaleform.Login import Login + +def fini(): + # Force killing game process + os.kill(os.getpid(), signal.SIGTERM) + +g_preDefinedHosts._hosts.append(g_preDefinedHosts._makeHostItem(OFFLINE_SERVER_ADDRESS, OFFLINE_SERVER_ADDRESS, OFFLINE_SERVER_ADDRESS)) + +@override(Vehicle, 'canSell') +def Vehicle_canSell(baseFunc, baseSelf): + return BigWorld.player().isOffline or baseFunc(baseSelf) + +@override(Login, 'populateUI') +def Login_populateUI(baseFunc, baseSelf, proxy): + baseFunc(baseSelf, proxy) + connectionManager.connect(OFFLINE_SERVER_ADDRESS, OFFLINE_LOGIN, OFFLINE_PWD, False, False, False) + +@override(Account.PlayerAccount, '__init__') +def Account_init(baseFunc, baseSelf): + baseSelf.isOffline = not baseSelf.name + if baseSelf.isOffline: + constants.IS_SHOW_SERVER_STATS = False + baseSelf.fakeServer = FakeServer() + baseSelf.name = OFFLINE_NICKNAME + baseSelf.serverSettings = OFFLINE_SERVER_SETTINGS + + baseFunc(baseSelf) + + if baseSelf.isOffline: + BigWorld.player(baseSelf) + print BigWorld.player().serverSettings + +@override(Account.PlayerAccount, '__getattribute__') +def Account_getattribute(baseFunc, baseSelf, name): + if name in ('cell', 'base', 'server') and baseSelf.isOffline: + name = 'fakeServer' + return baseFunc(baseSelf, name) + +@override(Account.PlayerAccount, 'onBecomePlayer') +def Account_onBecomePlayer(baseFunc, baseSelf): + baseFunc(baseSelf) + if baseSelf.isOffline: + baseSelf.showGUI(OFFLINE_GUI_CTX) + +@override(BigWorld, 'clearEntitiesAndSpaces') +def BigWorld_clearEntitiesAndSpaces(baseFunc, *args): + if getattr(BigWorld.player(), 'isOffline', False): + return + baseFunc(*args) + +@override(BigWorld, 'connect') +def BigWorld_connect(baseFunc, server, loginParams, progressFn): + if server == OFFLINE_SERVER_ADDRESS: + LOG_DEBUG('BigWorld.connect') + progressFn(1, "LOGGED_ON", {}) + BigWorld.createEntity('Account', BigWorld.createSpace(), 0, (0, 0, 0), (0, 0, 0), {}) + else: + baseFunc(server, loginParams, progressFn) \ No newline at end of file diff --git a/scripts/client/gui/mods/offhangar/__init__.py b/scripts/client/gui/mods/offhangar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/client/gui/mods/offhangar/_constants.py b/scripts/client/gui/mods/offhangar/_constants.py new file mode 100644 index 0000000..5cc2f3f --- /dev/null +++ b/scripts/client/gui/mods/offhangar/_constants.py @@ -0,0 +1,50 @@ +import cPickle +from chat_shared import CHAT_RESPONSES, CHAT_ACTIONS, CHAT_COMMANDS + +OFFLINE_SERVER_ADDRESS = 'offline.loc' +OFFLINE_NICKNAME = 'DrWeb7_1' +OFFLINE_LOGIN = OFFLINE_NICKNAME + '@' + OFFLINE_SERVER_ADDRESS +OFFLINE_PWD = '1' +OFFLINE_DBID = 13028161 + +OFFLINE_GUI_CTX = cPickle.dumps({ + 'databaseID': OFFLINE_DBID, + 'logUXEvents': False, + 'aogasStartedAt': 0, + 'sessionStartedAt': 0, + 'isAogasEnabled': False, + 'collectUiStats': False, + 'isLongDisconnectedFromCenter': False, +}, cPickle.HIGHEST_PROTOCOL) + +OFFLINE_SERVER_SETTINGS = { + 'regional_settings': {'starting_day_of_a_new_week': 0, 'starting_time_of_a_new_game_day': 0, 'starting_time_of_a_new_day': 0, 'starting_day_of_a_new_weak': 0}, + 'xmpp_enabled': False, + 'xmpp_port': 0, + 'xmpp_host': '', + 'xmpp_muc_enabled': False, + 'xmpp_muc_services': [], + 'xmpp_resource': '', + 'xmpp_bosh_connections': [], + 'xmpp_connections': [], + 'xmpp_alt_connections': [], + 'file_server': {}, + 'voipDomain': '', + 'voipUserDomain': '' +} + +CHAT_ACTION_DATA = { + 'requestID': None, + 'action': None, + 'actionResponse': CHAT_RESPONSES.internalError.index(), + 'time': 0, + 'sentTime': 0, + 'channel': 0, + 'originator': 0, + 'originatorNickName': '', + 'group': 0, + 'data': {}, + 'flags': 0 +} + +REQUEST_CALLBACK_TIME = 0.5 \ No newline at end of file diff --git a/scripts/client/gui/mods/offhangar/data.py b/scripts/client/gui/mods/offhangar/data.py new file mode 100644 index 0000000..89ebda4 --- /dev/null +++ b/scripts/client/gui/mods/offhangar/data.py @@ -0,0 +1,190 @@ +import nations +import items +import functools +import time + +from constants import ACCOUNT_ATTR +from items import ITEM_TYPE_INDICES, ITEM_TYPE_NAMES +items.init(True) +from items import vehicles +from items.vehicles import g_list, g_cache + +from gui.mods.offhangar.logging import * +from gui.mods.offhangar.utils import * +from gui.mods.offhangar._constants import * + +doLog = functools.partial(doLog, 'OFFHANGAR') +LOG_NOTE = functools.partial(doLog, '[NOTE]') +LOG_DEBUG = functools.partial(doLog, '[DEBUG]') + +def getOfflineShop(): + return { + 'berthsPrices': (16,16,[300]), + 'freeXPConversion': (25,1), + 'dropSkillsCost': { + 0: { 'xpReuseFraction': 0.5, 'gold': 0, 'credits': 0 }, + 1: { 'xpReuseFraction': 0.75, 'gold': 0, 'credits': 20000 }, + 2: { 'xpReuseFraction': 1.0, 'gold': 200, 'credits': 0 } + }, + 'refSystem': { + 'maxNumberOfReferrals': 50, + 'posByXPinTeam': 10, + 'maxReferralXPPool': 350000, + 'periods': [(24, 3.0), (168, 2.0), (876000, 1.5)] + }, + 'playerEmblemCost': { + 0: (15, True), + 30: (6000, False), + 7: (1500, False) + }, + 'premiumCost': { + 1: 250, + 3: 650, + 7: 1250, + 360: 24000, + 180: 13500, + 30: 2500 + }, + 'winXPFactorMode': 0, + 'sellPriceModif': 0.5, + 'passportChangeCost': 50, + 'exchangeRateForShellsAndEqs': 400, + 'exchangeRate': 400, + 'tankmanCost': ({ + 'isPremium': False, + 'baseRoleLoss': 0.20000000298023224, + 'gold': 0, + 'credits': 0, + 'classChangeRoleLoss': 0.20000000298023224, + 'roleLevel': 50 + }, + { + 'isPremium': False, + 'baseRoleLoss': 0.10000000149011612, + 'gold': 0, + 'credits': 20000, + 'classChangeRoleLoss': 0.10000000149011612, + 'roleLevel': 75 + }, + { + 'isPremium': True, + 'baseRoleLoss': 0.0, + 'gold': 200, + 'credits': 0, + 'classChangeRoleLoss': 0.0, + 'roleLevel': 100 + }), + 'paidRemovalCost': 10, + 'dailyXPFactor': 2, + 'changeRoleCost': 500, + 'isEnabledBuyingGoldShellsForCredits': True, + 'items': {}, + 'slotsPrices': (9, [300]), + 'freeXPToTManXPRate': 10, + 'defaults': { + 'items': {}, + 'freeXPToTManXPRate': 0, + 'goodies': { 'prices': { } } + }, + 'sellPriceFactor': 0.5, + 'isEnabledBuyingGoldEqsForCredits': True, + 'playerInscriptionCost': { + 0: (15, True), + 7: (1500, False), + 30: (6000, False), + 'nations': { } + } + } + +def getOfflineInventory(): + data = dict((k, {}) for k in ITEM_TYPE_INDICES) + + data[ITEM_TYPE_INDICES['vehicle']] = { + 'repair': {}, + 'lastCrew': {}, + 'settings': {}, + 'compDescr': {}, + 'eqs': {}, + 'shells': {}, + 'lock': {}, + 'shellsLayout': {}, + 'vehicle': {} + } + + return { + 'inventory': data + } + +def getOfflineStats(): + unlocksSet = set() + vehiclesSet = set() + + for nationID in nations.INDICES.values(): + unlocksSet.update([vehicles.makeIntCompactDescrByID('vehicleChassis', nationID, i) for i in g_cache.chassis(nationID).keys()]) + unlocksSet.update([vehicles.makeIntCompactDescrByID('vehicleEngine', nationID, i) for i in g_cache.engines(nationID).keys()]) + unlocksSet.update([vehicles.makeIntCompactDescrByID('vehicleFuelTank', nationID, i) for i in g_cache.fuelTanks(nationID).keys()]) + unlocksSet.update([vehicles.makeIntCompactDescrByID('vehicleRadio', nationID, i) for i in g_cache.radios(nationID).keys()]) + unlocksSet.update([vehicles.makeIntCompactDescrByID('vehicleTurret', nationID, i) for i in g_cache.turrets(nationID).keys()]) + unlocksSet.update([vehicles.makeIntCompactDescrByID('vehicleGun', nationID, i) for i in g_cache.guns(nationID).keys()]) + unlocksSet.update([vehicles.makeIntCompactDescrByID('shell', nationID, i) for i in g_cache.shells(nationID).keys()]) + + vData = [vehicles.makeIntCompactDescrByID('vehicle', nationID, i) for i in g_list.getList(nationID).keys()] + unlocksSet.update(vData) + vehiclesSet.update(vData) + + attrs = 0 + for field in dir(ACCOUNT_ATTR): + value = getattr(ACCOUNT_ATTR, field, None) + if isinstance(value, (int, long)): + attrs |= value + + vehTypeXP = dict([(i, 0) for i in vehiclesSet]) + + return { + 'stats': { + 'berths': 40, + 'accOnline': 0, + 'autoBanTime': 0, + 'gold': 1000000, + 'crystal': 1000, + 'isFinPswdVerified': True, + 'finPswdAttemptsLeft': 0, + 'denunciationsLeft': 0, + 'freeVehiclesLeft': 0, + 'refSystem': {'referrals': {}}, + 'slots': 0, + 'battlesTillCaptcha': 0, + 'hasFinPassword': True, + 'clanInfo': (None, None, 0, 0, 0), + 'unlocks': unlocksSet, + 'mayConsumeWalletResources': True, + 'freeTMenLeft': 0, + 'vehicleSellsLeft': 0, + 'SPA': {'/common/goldfish_bonus_applied/': u'1'}, + 'vehTypeXP': vehTypeXP, + 'unitAcceptDeadline': 0, + 'globalVehicleLocks': {}, + 'freeXP': 100000000, + 'captchaTriesLeft': 0, + 'fortResource': 0, + 'premiumExpiryTime': time.time()+86400, + 'tkillIsSuspected': False, + 'credits': 100000000, + 'vehTypeLocks': {}, + 'dailyPlayHours': [0], + 'globalRating': 0, + 'restrictions': {}, + 'oldVehInvID': 0, + 'accOffline': 0, + 'dossier': '', + 'multipliedXPVehs': unlocksSet, + 'tutorialsCompleted': 33553532, + 'eliteVehicles': vehiclesSet, + 'playLimits': ((0, ''), (0, '')), + 'clanDBID': 0, + 'attrs': attrs, + } + } + +def getOfflineQuestsProgress(): + return {'quests': {}} \ No newline at end of file diff --git a/scripts/client/gui/mods/offhangar/logging.py b/scripts/client/gui/mods/offhangar/logging.py new file mode 100644 index 0000000..5568c84 --- /dev/null +++ b/scripts/client/gui/mods/offhangar/logging.py @@ -0,0 +1,6 @@ +import functools +from gui.mods.offhangar.utils import * + +doLog = functools.partial(doLog, 'OFFHANGAR') +LOG_NOTE = functools.partial(doLog, '[NOTE]') +LOG_DEBUG = functools.partial(doLog, '[DEBUG]') \ No newline at end of file diff --git a/scripts/client/gui/mods/offhangar/requests.py b/scripts/client/gui/mods/offhangar/requests.py new file mode 100644 index 0000000..78d8c11 --- /dev/null +++ b/scripts/client/gui/mods/offhangar/requests.py @@ -0,0 +1,53 @@ +import BigWorld +import functools +import AccountCommands +import zlib +import cPickle +import game + +from collections import namedtuple + +from gui.mods.offhangar.logging import * +from gui.mods.offhangar.server import * +from gui.mods.offhangar._constants import * +from gui.mods.offhangar.data import * + +RequestResult = namedtuple('RequestResult', ['resultID', 'errorStr', 'data']) + +def baseRequest(cmdID): + def wrapper(func): + def requester(requestID, *args): + result = func(requestID, *args) + return requestID, result.resultID, result.errorStr, result.data + BASE_REQUESTS[cmdID] = requester + return func + return wrapper + +def packStream(requestID, data): + data = zlib.compress(cPickle.dumps(data)) + desc = cPickle.dumps((len(data), zlib.crc32(data))) + return functools.partial(game.onStreamComplete, requestID, desc, data) + +@baseRequest(AccountCommands.CMD_COMPLETE_TUTORIAL) +def completeTutorial(requestID, revision, dataLen, dataCrc): + return RequestResult(AccountCommands.RES_SUCCESS, '', {}) + +@baseRequest(AccountCommands.CMD_SYNC_DATA) +def syncData(requestID, revision, crc, _): + data = {'rev':revision + 2, 'prevRev': revision} + data.update(getOfflineInventory()) + data.update(getOfflineStats()) + data.update(getOfflineQuestsProgress()) + return RequestResult(AccountCommands.RES_SUCCESS, '', data) + +@baseRequest(AccountCommands.CMD_SYNC_SHOP) +def syncShop(requestID, revision, dataLen, dataCrc): + data = {'rev':revision + 2, 'prevRev': revision} + data.update(getOfflineShop()) + BigWorld.callback(REQUEST_CALLBACK_TIME, packStream(requestID, data)) + return RequestResult(AccountCommands.RES_STREAM, '', None) + +@baseRequest(AccountCommands.CMD_SYNC_DOSSIERS) +def syncDossiers(requestID, revision, maxChangeTime, _): + BigWorld.callback(REQUEST_CALLBACK_TIME, packStream(requestID, (revision + 2, []))) + return RequestResult(AccountCommands.RES_STREAM, '', None) \ No newline at end of file diff --git a/scripts/client/gui/mods/offhangar/server.py b/scripts/client/gui/mods/offhangar/server.py new file mode 100644 index 0000000..244b8f0 --- /dev/null +++ b/scripts/client/gui/mods/offhangar/server.py @@ -0,0 +1,74 @@ +import BigWorld +import functools +import AccountCommands +import cPickle + +from gui.mods.offhangar._constants import CHAT_ACTION_DATA +from gui.mods.offhangar.logging import * + +BASE_REQUESTS = {} + +class FakeServer(object): + def __call__(self, *args, **kwargs): + if not self.__isMuted: + LOG_DEBUG('%s ( %s, %s )' % (self.__name, args, kwargs)) + + def __init__(self, name='Server', isMuted=False): + super(FakeServer, self).__init__() + self.__isMuted = isMuted + self.__name = name + + def __getattr__(self, name): + try: + return super(FakeServer, self).__getattribute__(name) + except AttributeError: + return FakeServer(name='%s.%s' % (self.__name, name), isMuted=self.__isMuted) + + def chatCommandFromClient(self, requestID, action, channelID, int64Arg, int16Arg, stringArg1, stringArg2): + chatActionData = CHAT_ACTION_DATA.copy() + chatActionData['requestID'] = requestID + chatActionData['action'] = action + BigWorld.player().onChatAction(chatActionData) + + def doCmdStr(self, requestID, cmd, str): + LOG_DEBUG('Server.doCmdStr', requestID, str) + self.__doCmd(requestID, cmd, str) + + def doCmdIntStr(self, requestID, cmd, int, str): + LOG_DEBUG('Server.doCmdIntStr', requestID, cmd, int, str) + self.__doCmd(requestID, cmd, int, str) + + def doCmdInt3(self, requestID, cmd, int1, int2, int3): + LOG_DEBUG('Server.doCmdInt3', requestID, cmd, int1, int2, int3) + self.__doCmd(requestID, cmd, int1, int2, int3) + + def doCmdInt4(self, requestID, cmd, int1, int2, int3, int4): + LOG_DEBUG('Server.doCmdInt4', requestID, cmd, int1, int2, int3, int4) + self.__doCmd(requestID, cmd, int1, int2, int3, int4) + + def doCmdInt2Str(self, requestID, cmd, int1, int2, str): + LOG_DEBUG('Server.doCmdInt2Str', requestID, cmd, int1, int2, str) + self.__doCmd(requestID, cmd, int1, int2, str) + + def doCmdIntArr(self, requestID, cmd, arr): + LOG_DEBUG('Server.doCmdIntArr', requestID, cmd, arr) + self.__doCmd(requestID, cmd, arr) + + def doCmdIntArrStrArr(self, requestID, cmd, intArr, strArr): + LOG_DEBUG('Server.doCmdIntArrStrArr', requestID, cmd, intArr, strArr) + self.__doCmd(requestID, cmd, intArr, strArr) + + def __doCmd(self, requestID, cmd, *args): + cmdCall = BASE_REQUESTS.get(cmd) + if cmdCall: + requestID, resultID, errorStr, ext = cmdCall(requestID, *args) + else: + LOG_DEBUG('Server.requestFail', requestID, cmd, args) + requestID, resultID, errorStr, ext = requestID, AccountCommands.RES_FAILURE, '', None + + if ext is not None: + callback = functools.partial(BigWorld.player().onCmdResponseExt, requestID, resultID, errorStr, cPickle.dumps(ext)) + else: + callback = functools.partial(BigWorld.player().onCmdResponse, requestID, resultID, errorStr) + + BigWorld.callback(0.0, callback) \ No newline at end of file diff --git a/scripts/client/gui/mods/offhangar/utils.py b/scripts/client/gui/mods/offhangar/utils.py new file mode 100644 index 0000000..c6bdb3e --- /dev/null +++ b/scripts/client/gui/mods/offhangar/utils.py @@ -0,0 +1,154 @@ +'''This module contains common functions that used in game modifications''' + +__author__ = "Iliev Renat, DrWeb7_1 (0.8.x port attempt)" +__email__ = "iliahonz@gmail.com, " + +import BigWorld +import ResMgr +import functools +import inspect +import os +import shutil +import tempfile +import time +import json +import re + +def byteify(data): + '''Encodes data with UTF-8 + :param data: Data to encode''' + if isinstance(data, dict): + return dict([(byteify(key), byteify(data)) for key, data in data.iteritems()]) + elif isinstance(data, list): + return [byteify(element) for element in data] + elif isinstance(data, unicode): + return data.encode('utf-8') + else: + return data + +def jsonDump(obj, needFmt=False): + '''Serializes an object into a string + :param obj: Object + :param needFmt: Indicates that the result should be formatted for human reading''' + if needFmt: + return json.dumps(obj, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True, encoding='utf-8') + return json.dumps(obj) + +def jsonLoad(src): + '''Returns json data from source + It supports comments in json + :param src: Data source (file handler or string)''' + if not isinstance(src, (str, unicode)): + src = src.read() + return jsonParse(src) + +def jsonParse(data): + '''Pareses json string into dict + It supports comments in json + :param data: JSON string''' + def comments(text): + regex = r'\s*(#|\/{2}).*$' + regex_inline = r'(:?(?:\s)*([A-Za-z\d\.{}]*)|((?<=\").*\"),?)(?:\s)*(((#|(\/{2})).*)|)$' + lines = text.split('\n') + excluded = [] + for index, line in enumerate(lines): + if re.search(regex, line): + if re.search(r'^' + regex, line, re.IGNORECASE): + excluded.append(lines[index]) + elif re.search(regex_inline, line): + lines[index] = re.sub(regex_inline, r'\1', line) + for line in excluded: + lines.remove(line) + return '\n'.join(lines) + return byteify(json.loads(comments(data), encoding='utf-8')) + +def deepUpdate(obj, new): + '''Recursive updating of the dictionary (including dictionaries in it) + :param obj: Dictionary to be updated + :param new: Diff dictionary''' + for key, value in new.iteritems(): + if isinstance(value, dict): + obj[key] = deepUpdate(obj.get(key, {}), value) + else: + obj[key] = value + return obj + +def isAlly(vehicle): + '''Checks is vehicle in player's team + :param vehicle: Entity ID or object + :return: Is given entity in player team''' + player = BigWorld.player() + vehicles = player.arena.vehicles + vehicleID = vehicle.id if isinstance(vehicle, BigWorld.Entity) else vehicle + return vehicleID in vehicles and vehicles[player.playerVehicleID]['team'] == vehicles[vehicleID]['team'] + +def doLog(project, *args, **kwargs): + '''Prints arguments to stdout with tag + :param project: Tag for log string + :param args: Arguments, it reduces to string by join with space + :param kwargs: Key-value arguments, it reduces to string by repr''' + + kwargs = repr(kwargs) if kwargs else '' + args = ' '.join([unicode(s) for s in args]) + print '[%s] %s %s' % (project, args, kwargs) + +def unpackVFS(prefix, *paths): + '''Unpacks files from VFS with ResMgr into temporary folder + :param postfix: Postfix for temporary folder name + :param prefix: Prefix for temporary folder name + :param paths: Path to files in VFS + :return: List of absolute paths to unpacked files''' + + folder = os.path.join(tempfile.gettempdir(), '_'.join([str(prefix), str(int(time.time()))])) + + if os.path.exists(folder): + shutil.rmtree(folder, ignore_errors=True) + os.makedirs(folder) + + result = [] + for path in paths: + filepath = os.path.join(folder, os.path.basename(path)) + result.append(filepath) + with open(filepath, 'wb') as f: + f.write(str(ResMgr.openSection(path).asBinary)) + return result + +def override(obj, prop, getter=None, setter=None, deleter=None): + '''Overrides attribute in object. + Attribute should be property or callable. + Getter, setter and deleter should be callable or None. + :param obj: Object + :param prop: Name of any attribute in object (can be not mangled) + :param getter: Getter function + :param setter: Setter function + :param deleter: Deleter function''' + + if inspect.isclass(obj) and prop.startswith('__') and prop not in dir(obj) + dir(type(obj)): + prop = obj.__name__ + prop + if not prop.startswith('_'): + prop = '_' + prop + + src = getattr(obj, prop) + if type(src) is property and (getter or setter or deleter): + assert getter is None or callable(getter) , 'Getter is not callable!' + assert setter is None or callable(setter) , 'Setter is not callable!' + assert deleter is None or callable(deleter), 'Deleter is not callable!' + + getter = functools.partial(getter, src.fget) if getter else src.fget + setter = functools.partial(setter, src.fset) if setter else src.fset + deleter = functools.partial(deleter, src.fdel) if deleter else src.fdel + + setattr(obj, prop, property(getter, setter, deleter)) + return getter + elif getter: + assert callable(src), 'Source property is not callable!' + assert callable(getter), 'Handler is not callable!' + + getter_new = lambda *args, **kwargs: getter(src, *args, **kwargs) + if not isinstance(src, type(BigWorld.Entity.__getattribute__)) and not inspect.ismethod(src) and inspect.isclass(obj): + getter_new = staticmethod(getter_new) + + setattr(obj, prop, getter_new) + return getter + else: + return functools.partial(override, obj, prop) \ No newline at end of file