def refresh_lo(cached=False, cached_active=True): """Refresh cached_lord, reverting if locked to the saved one. If any of cached or cached_active are True, we will keep the cached values for those except if _game_handle.***_changed() respective methods return True. In the case of timestamp games, cached is effectively always False, as load_order_changed returns True - that's not slow, as getting the load order just involves getting mtime info from modInfos cache. This last one **must be up to date** for correct load order/active validation.""" if _lords_pickle is None: __load_pickled_load_orders() # once only if locked and _saved_load_orders: saved = _saved_load_orders[_current_list_index].lord # type: LoadOrder lord, acti = _game_handle.set_load_order( # make sure saved lo is valid list(saved.loadOrder), list(saved.activeOrdered), dry_run=True) fixed = LoadOrder(lord, acti) if fixed != saved: bolt.deprint(u'Saved load order is no longer valid: %s' u'\nCorrected to %s' % (saved, fixed)) saved = fixed else: saved = None if cached_lord is not __empty: lo = cached_lord.loadOrder if ( cached and not _game_handle.load_order_changed()) else None active = cached_lord.activeOrdered if ( cached_active and not _game_handle.active_changed()) else None else: active = lo = None _update_cache(lo, active) if locked and saved is not None: if cached_lord.loadOrder != saved.loadOrder: save_lo(saved.loadOrder, saved.activeOrdered) global warn_locked warn_locked = True
def shellDeletePass(node, parent=None): """Delete tmp dirs/files - ignore errors (but log them).""" if node.exists(): try: shellDelete(node, parent=parent, confirm=False, recycle=False) except OSError: deprint(u"Error deleting %s:" % node, traceback=True)
def _update_cache(lord=None, acti_sorted=None, __index_move=0): """ :type lord: tuple[bolt.Path] | list[bolt.Path] :type acti_sorted: tuple[bolt.Path] | list[bolt.Path] """ global cached_lord try: fix_lo = games.FixInfo() lord, acti_sorted = _game_handle.get_load_order( lord, acti_sorted, fix_lo) fix_lo.lo_deprint() cached_lord = LoadOrder(lord, acti_sorted) except Exception: bolt.deprint(u'Error updating load_order cache') cached_lord = __empty raise finally: if cached_lord is not __empty: global _current_list_index if _current_list_index < 0 or ( not __index_move and cached_lord != _saved_load_orders[_current_list_index].lord): # either getting or setting, plant the new load order in _current_list_index += 1 _new_entry() elif __index_move: # attempted to undo/redo _current_list_index += __index_move target = _saved_load_orders[_current_list_index].lord if target != cached_lord: # we partially redid/undid # put it after (redo) or before (undo) the target _current_list_index += int(math.copysign(1, __index_move)) # list[-1:-1] won't do what we want _current_list_index = max(0, _current_list_index) _new_entry()
def compressionSettings(archive_path, blockSize, isSolid): archiveType = writeExts.get(archive_path.cext) if not archiveType: #--Always fall back to using the defaultExt archive_path = GPath(archive_path.sbody + defaultExt).tail archiveType = writeExts.get(archive_path.cext) if archive_path.cext in noSolidExts: # zip solid = u'' else: if isSolid: if blockSize: solid = u'-ms=on -ms=%dm' % blockSize else: solid = u'-ms=on' else: solid = u'-ms=off' userArgs = bass.inisettings['7zExtraCompressionArguments'] if userArgs: if reSolid.search(userArgs): if not solid: # zip, will blow if ms=XXX is passed in old = userArgs userArgs = reSolid.sub(u'', userArgs).strip() if old != userArgs: deprint(archive_path.s + u': 7zExtraCompressionArguments ini option "' + old + u'" -> "' + userArgs + u'"') solid = userArgs else: solid += userArgs return archive_path, archiveType, solid
def _import_bush_and_set_game(opts, bashIni): import bush bolt.deprint(u'Searching for game to manage:') ret = bush.detect_and_set_game(opts.oblivionPath, bashIni) if ret is not None: # None == success if len(ret) == 0: msgtext = _( u"Wrye Bash could not find a game to manage. Please use " u"-o command line argument to specify the game path") else: msgtext = _( u"Wrye Bash could not determine which game to manage. " u"The following games have been detected, please select " u"one to manage.") msgtext += u'\n\n' msgtext += _( u'To prevent this message in the future, use the -o command ' u'line argument or the bash.ini to specify the game path') retCode = _wxSelectGame(ret, msgtext) if retCode is None: bolt.deprint(u"No games were found or Selected. Aborting.") return None # Add the game to the command line, so we use it if we restart bass.update_sys_argv(['--oblivionPath', bush.game_path(retCode).s]) bush.detect_and_set_game(opts.oblivionPath, bashIni, retCode) # Force Python mode if CBash can't work with this game bolt.CBash = opts.mode if bush.game.esp.canCBash else 1 #1 = python mode... return bush.game
def _get_app_links(apps_dir): """Scan Mopy/Apps folder for shortcuts (.lnk files). Windows only ! :param apps_dir: the absolute Path to Mopy/Apps folder :return: a dictionary of shortcut properties tuples keyed by the absolute Path of the Apps/.lnk shortcut """ if win32client is None: return {} links = {} try: sh = win32client.Dispatch('WScript.Shell') for lnk in apps_dir.list(): lnk = apps_dir.join(lnk) if lnk.cext == u'.lnk' and lnk.isfile(): shortcut = sh.CreateShortCut(lnk.s) description = shortcut.Description if not description: description = u' '.join((_(u'Launch'), lnk.sbody)) links[lnk] = ( shortcut.TargetPath, shortcut.IconLocation, # shortcut.WorkingDirectory, shortcut.Arguments, description) except: deprint(_(u"Error initializing links:"), traceback=True) return links
def _fix_load_order(self, lord, fix_lo): super(TimestampGame, self)._fix_load_order(lord, fix_lo) if fix_lo is not None and fix_lo.lo_added: # should not occur, except if undoing bolt.deprint(u'Incomplete load order passed in to set_load_order. ' u'Missing: ' + u', '.join(x.s for x in fix_lo.lo_added)) lord[:] = self.__calculate_mtime_order(mods=lord)
def _fix_load_order(self, lord, fix_lo): """Fix inconsistencies between given loadorder and actually installed mod files as well as impossible load orders. We need a refreshed bosh.modInfos reflecting the contents of Data/. Called in get_load_order() to fix a newly fetched LO and in set_load_order() to check if a load order passed in is valid. Needs rethinking as save load and active should be an atomic operation - leads to hacks (like the _selected parameter). :type lord: list[bolt.Path] """ if fix_lo is None: fix_lo = FixInfo() # discard fix info old_lord = lord[:] # game's master might be out of place (if using timestamps for load # ordering or a manually edited loadorder.txt) so move it up master_name = self.master_path master_dex = 0 try: master_dex = lord.index(master_name) except ValueError: if not master_name in self.mod_infos: raise exception.BoltError(u'%s is missing or corrupted' % master_name) fix_lo.lo_added = {master_name} if master_dex > 0: bolt.deprint(u'%s has index %d (must be 0)' % (master_name, master_dex)) lord.remove(master_name) lord.insert(0, master_name) fix_lo.lo_reordered = True # below do not apply to timestamp method (on getting it) loadorder_set = set(lord) mods_set = set(self.mod_infos.keys()) fix_lo.lo_removed = loadorder_set - mods_set # may remove corrupted mods # present in text file, we are supposed to take care of that fix_lo.lo_added |= mods_set - loadorder_set # Remove non existent plugins from load order lord[:] = [x for x in lord if x not in fix_lo.lo_removed] # See if any esm files are loaded below an esp and reorder as necessary ol = lord[:] lord.sort(key=lambda m: not self.in_master_block(self.mod_infos[m])) fix_lo.lo_reordered |= ol != lord # Append new plugins to load order index_first_esp = self._index_of_first_esp(lord) for mod in fix_lo.lo_added: if self.in_master_block(self.mod_infos[mod]): if not mod == master_name: lord.insert(index_first_esp, mod) else: lord.insert(0, master_name) bolt.deprint(u'%s inserted to Load order' % master_name) index_first_esp += 1 else: lord.append(mod) # end textfile get fix_lo.lo_duplicates = self._check_for_duplicates(lord) fix_lo.lo_reordered |= self._order_fixed(lord) if fix_lo.lo_reordered: fix_lo.lo_reordered = old_lord, lord
def __get_error_info(): try: sErrorInfo = u'\n'.join(u' %s: %s' % (key, envDefs[key]) for key in sorted(envDefs)) except UnicodeDecodeError: deprint(u'Error decoding _os.environ', traceback=True) sErrorInfo = u'\n'.join(u' %s: %s' % (key, decode(envDefs[key])) for key in sorted(envDefs)) return sErrorInfo
def SetActivePlugins(self,plugins): plugins = [_enc(x) for x in plugins] num = len(plugins) plugins = list_of_strings(plugins) try: _Clo_set_active_plugins(self._DB, plugins, num) except LibloError as ex: # plugins.txt not written ! deprint(u'lo_set_active_plugins failed:', traceback=True) raise BoltError(ex.msg), None, sys.exc_info()[2]
def SetLoadOrder(self, plugins): plugins = [_enc(x) for x in plugins] num = len(plugins) plugins = list_of_strings(plugins) try: _Clo_set_load_order(self._DB, plugins, num) except LibloError as ex: # must notify the user that lo was not set deprint(u'lo_set_load_order failed:', traceback=True) raise BoltError(ex.msg), None, sys.exc_info()[2]
def _fetch_active_plugins(self): acti, _lo = self._parse_plugins_txt() if self.master_path in acti: acti.remove(self.master_path) self._write_plugins_txt(acti, acti) bolt.deprint(u'Removed %s from %s' % (self.master_path, self.plugins_txt_path)) acti.insert(0, self.master_path) return acti
def restore_ini(self): if self._timestamped_old is self.__unset: return # we did not move bash.ini if self._timestamped_old is not None: bolt.deprint(u'Restoring bash.ini') GPath(self._timestamped_old).copyTo(u'bash.ini') elif self._bash_ini_path: # remove bash.ini as it is the one from the backup bolt.GPath(u'bash.ini').remove()
def parse_ccc_file(cls): _ccc_path = bass.dirs['app'].join(cls._ccc_filename) try: with open(_ccc_path.s, 'r') as ins: lines = map(bolt.GPath, map(str.strip, ins.readlines())) cls.must_be_active_if_present += tuple(lines) except (OSError, IOError) as e: if e.errno != errno.ENOENT: bolt.deprint(u'Failed to open %s' % _ccc_path, traceback=True)
def GetActivePlugins(self): plugins = c_char_p_p() num = c_size_t() try: _Clo_get_active_plugins(self._DB, byref(plugins), byref(num)) except LibloError as err: if err.code != LIBLO_WARN_INVALID_LIST: raise deprint(u'lo_get_active_plugins WARN_INVALID_LIST:', traceback=True) return map(GPath, plugins[:num.value])
def backup_settings(self, balt_): deprint(u'') deprint(_(u'BACKUP BASH SETTINGS: ') + self._backup_dest_file.s) temp_settings_backup_dir = bolt.Path.tempDir() try: self._backup_settings(temp_settings_backup_dir) self._backup_success(balt_) finally: if temp_settings_backup_dir: temp_settings_backup_dir.rmtree(safety=u'WryeBash_')
def __setGame(foundGames_, name, msg): """Set bush game globals - raise if they are already set.""" global gamePath, game, foundGames if game is not None: raise BoltError(u'Trying to reset the game') foundGames = foundGames_ gamePath = foundGames_[name] game = _allGames[name] deprint(msg % {'gamename': name}, gamePath) # Unload the other modules from the cache for i in _allGames.keys(): if i != name: del _allGames[i] game.init()
def warn_lo(self): if not self.lo_changed(): return added = _pl(self.lo_added) or u'None' removed = _pl(self.lo_removed) or u'None' duplicates = (u'lo_duplicates(%s), ' % _pl(self.lo_duplicates)) \ if self.lo_duplicates else u'' reordered = u'(No)' if not self.lo_reordered else _pl( self.lo_reordered[0], u'from:\n', joint=u'\n') + _pl( self.lo_reordered[1], u'\nto:\n', joint=u'\n') msg = u'Fixed Load Order: added(%s), removed(%s), %sreordered %s' % ( added, removed, duplicates, reordered) bolt.deprint(msg)
def rmTempDir(): """Remove the current temporary directory, and set the current Temp Dir to None - meaning a new one will be generated on getTempDir()""" global _tempDir if _tempDir is None: return try: _tempDir.rmtree(safety=_tempDir.stail) except OSError: from bolt import deprint deprint(u'Failed to remove %s' % _tempDir, traceback=True) _tempDir = None
def __fixActive(acti, lord): # filter plugins not present in modInfos - this will disable corrupted too! actiFiltered = [x for x in acti if x in bosh.modInfos] #preserve acti order _removed = set(acti) - set(actiFiltered) if _removed: # take note as we may need to rewrite plugins txt msg = u'Those mods were present in plugins.txt but were not present ' \ u'in Data/ directory or were corrupted: ' + _pl(_removed) + u'\n' bosh.modInfos.selectedBad = _removed else: msg = u'' # again is below needed ? Apparently not with liblo 4 (acti is [Skyrim.esm, # Update.esm] on empty plugins.txt) - Keep it cause eventually (when liblo # is made to return actual contents of plugins.txt at all times) I may # need to correct this here addUpdateEsm = False if bush.game.fsName == 'Skyrim': skyrim = bolt.GPath(u'Skyrim.esm') if not skyrim in actiFiltered: actiFiltered.insert(0, skyrim) msg += u'Skyrim.esm not present in active mods' + u'\n' updateEsm = bolt.GPath(u'Update.esm') if updateEsm in lord and not updateEsm in actiFiltered: msg += (u'Update.esm not present in plugins.txt while present in ' u'Data folder') + u'\n' addUpdateEsm = True dexDict = {mod: index for index, mod in enumerate(lord)} # not needed for oblivion, for skyrim liblo will write plugins.txt in order # STILL restore for skyrim to warn on LO change if usingTxtFile(): actiSorted = actiFiltered[:] actiSorted.sort(key=dexDict.__getitem__) # all present in lord if actiFiltered != actiSorted: # were mods in an order that disagrees with lord ? msg += (u'Plugins.txt order of plugins (%s) differs from current ' u'load order (%s)') % (_pl(actiFiltered), _pl(actiSorted)) else: actiSorted = sorted(actiFiltered, key=dexDict.__getitem__) if addUpdateEsm: # insert after the last master (as does liblo) actiSorted.insert(_indexFirstEsp(actiSorted), updateEsm) # check if we have more than 256 active mods if len(actiSorted) > 255: msg += u'Plugins.txt contains more than 255 plugins - the following ' \ u'plugins will be deactivated: ' bosh.modInfos.selectedExtra = actiSorted[255:] msg += _pl(bosh.modInfos.selectedExtra) acti[:] = actiSorted[:255] # chop off extra if msg: ##: Notify user - maybe backup previous plugin txt ? bolt.deprint(u'Invalid Plugin txt corrected' + u'\n' + msg) SetActivePlugins(acti, lord, _fixed=True) return True # changes, saved return False # no changes, not saved
def __setGame(name, msg): """Set bush game globals - raise if they are already set.""" global game, game_mod if game is not None: raise BoltError(u'Trying to reset the game') gamePath = foundGames[name] game = _allGames[name](gamePath) game_mod = _allModules[name] deprint(msg % {'gamename': name}, gamePath) # Unload the other modules from the cache for i in _allGames.keys(): if i != name: del _allGames[i] del _allModules[i] # the keys should be the same game.init()
def _parse_plugins_txt_(path, mod_infos, _star): """Parse loadorder.txt and plugins.txt files with or without stars. Return two lists which are identical except when _star is True, whereupon the second list is the load order while the first the active plugins. In all other cases use the first list, which is either the list of active mods (when parsing plugins.txt) or the load order (when parsing loadorder.txt) :type path: bolt.Path :type mod_infos: bosh.ModInfos :type _star: bool :rtype: (list[bolt.Path], list[bolt.Path]) """ with path.open('r') as ins: #--Load Files active, modnames = [], [] for line in ins: # Oblivion/Skyrim saves the plugins.txt file in cp1252 format # It wont accept filenames in any other encoding modname = _re_plugins_txt_comment.sub('', line).strip() if not modname: continue # use raw strings below is_active = not _star or modname.startswith('*') if _star and is_active: modname = modname[1:] try: test = bolt.decode(modname, encoding='cp1252') except UnicodeError: bolt.deprint(u'%r failed to properly decode' % modname) continue if bolt.GPath(test) not in mod_infos: # The automatic encoding detector could have returned # an encoding it actually wasn't. Luckily, we # have a way to double check: modInfos.data for encoding in bolt.encodingOrder: try: test2 = unicode(modname, encoding) if bolt.GPath(test2) not in mod_infos: continue modname = bolt.GPath(test2) break except UnicodeError: pass else: modname = bolt.GPath(test) else: modname = bolt.GPath(test) modnames.append(modname) if is_active: active.append(modname) return active, modnames
def __fixActive(acti, lord): # filter plugins not present in modInfos - this will disable corrupted too! actiFiltered = [x for x in acti if x in bosh.modInfos] #preserve acti order _removed = set(acti) - set(actiFiltered) if _removed: # take note as we may need to rewrite plugins txt msg = u'Those mods were present in plugins.txt but were not present ' \ u'in Data/ directory or were corrupted: ' + _pl(_removed) + u'\n' bosh.modInfos.selectedBad = _removed else: msg = u'' # again is below needed ? Apparently not with liblo 4 (acti is [Skyrim.esm, # Update.esm] on empty plugins.txt) - Keep it cause eventually (when liblo # is made to return actual contents of plugins.txt at all times) I may # need to correct this here addUpdateEsm = False if bush.game.fsName == 'Skyrim': skyrim = bolt.GPath(u'Skyrim.esm') if not skyrim in actiFiltered: actiFiltered.insert(0, skyrim) msg += u'Skyrim.esm not present in active mods' + u'\n' updateEsm = bolt.GPath(u'Update.esm') if updateEsm in lord and not updateEsm in actiFiltered: msg += (u'Update.esm not present in plugins.txt while present in ' u'Data folder') + u'\n' addUpdateEsm = True dexDict = {mod:index for index, mod in enumerate(lord)} # not needed for oblivion, for skyrim liblo will write plugins.txt in order # STILL restore for skyrim to warn on LO change if usingTxtFile(): actiSorted = actiFiltered[:] actiSorted.sort(key=dexDict.__getitem__) # all present in lord if actiFiltered != actiSorted: # were mods in an order that disagrees with lord ? msg += (u'Plugins.txt order of plugins (%s) differs from current ' u'load order (%s)') % (_pl(actiFiltered), _pl(actiSorted)) else: actiSorted = sorted(actiFiltered, key=dexDict.__getitem__) if addUpdateEsm: # insert after the last master (as does liblo) actiSorted.insert(_indexFirstEsp(actiSorted), updateEsm) # check if we have more than 256 active mods if len(actiSorted) > 255: msg += u'Plugins.txt contains more than 255 plugins - the following ' \ u'plugins will be deactivated: ' bosh.modInfos.selectedExtra = actiSorted[255:] msg += _pl(bosh.modInfos.selectedExtra) acti[:] = actiSorted[:255] # chop off extra if msg: ##: Notify user - maybe backup previous plugin txt ? bolt.deprint(u'Invalid Plugin txt corrected' + u'\n' + msg) SetActivePlugins(acti, lord, _fixed=True) return True # changes, saved return False # no changes, not saved
def __write_plugins(out, lord, active, _star): def asterisk(active_set=frozenset(active)): return '*' if _star and (mod in active_set) else '' for mod in (_star and lord) or active: # Ok, this seems to work for Oblivion, but not Skyrim # Skyrim seems to refuse to have any non-cp1252 named file in # plugins.txt. Even activating through the SkyrimLauncher # doesn't work. try: out.write(asterisk() + bolt.encode(mod.s, firstEncoding='cp1252')) out.write('\r\n') except UnicodeEncodeError: bolt.deprint(mod.s + u' failed to properly encode and was not ' u'included in plugins.txt')
def _updateCache(lord=None, actiSorted=None): global _current_lo try: if lord is None: lord = _getLoFromLiblo() _setLoTxtModTime() # got a valid load order - now to active... if actiSorted is None: actiSorted = _getActiveFromLiblo(lord) _setPluginsTxtModTime() _current_lo = LoadOrder(lord, actiSorted) except Exception: bolt.deprint('Error updating load_order cache from liblo') _current_lo = __empty raise
def assure_single_instance(instance): """Ascertain that only one instance of Wrye Bash is running. If this is the second instance running, then display an error message and exit. 'instance' must stay alive for the whole execution of the program. See: https://wxpython.org/Phoenix/docs/html/wx.SingleInstanceChecker.html :type instance: wx.SingleInstanceChecker""" if instance.IsAnotherRunning(): bolt.deprint(u'Only one instance of Wrye Bash can run. Exiting.') msg = _(u'Only one instance of Wrye Bash can run.') _app = _wx.App(False) with _wx.MessageDialog(None, msg, _(u'Wrye Bash'), _wx.OK) as dialog: dialog.ShowModal() sys.exit(1)
def _backup_settings(self, temp_dir): # copy all files to ~tmp backup dir for tpath, fpath in self.files.iteritems(): deprint(tpath.s + u' <-- ' + fpath.s) fpath.copyTo(temp_dir.join(tpath)) # dump the version info and file listing with temp_dir.join(u'backup.dat').open('wb') as out: # Bash version the settings were saved with, if this is newer # than the installed settings version, do not allow restore cPickle.dump(bass.settings['bash.version'], out, -1) # app version, if this doesn't match the installed settings # version, warn the user on restore cPickle.dump(AppVersion, out, -1) # create the backup archive in 7z format WITH solid compression # may raise StateError backup_dir, dest7z = self._backup_dest_file.head, self._backup_dest_file.tail command = archives.compressCommand(dest7z, backup_dir, temp_dir) archives.compress7z(command, backup_dir, dest7z, temp_dir) bass.settings['bash.backupPath'] = backup_dir
def _fetch_load_order(self, cached_load_order, cached_active): """Read data from plugins.txt file. If plugins.txt does not exist create it. Discards information read if cached is passed in.""" exists = self.plugins_txt_path.exists() active, lo = self._parse_modfile( self.plugins_txt_path) # empty if not exists lo, active = (lo if cached_load_order is None else cached_load_order, active if cached_active is None else cached_active) to_drop = [] for rem in self.remove_from_plugins_txt: if rem in active or rem in lo: to_drop.append(rem) lo, active = self._readd_in_lists(lo, active) if not exists or to_drop: msg = u'Created %s' if not exists else ( u'Removed ' + u' ,'.join(map(unicode, to_drop)) + u' from %s') self._persist_load_order(lo, active) bolt.deprint(msg % self.plugins_txt_path) return lo, active
def Apply(self): if not self.PromptFile(): return deprint('') deprint('BACKUP BASH SETTINGS: ' + self.dir.join(self.archive).s) # copy all files to ~tmp backup dir for tpath, fpath in self.files.iteritems(): deprint(tpath.s + ' <-- ' + fpath.s) fpath.copyTo(self.tmp.join(tpath)) #end for # dump the version info and file listing out = self.tmp.join('backup.dat').open('wb') cPickle.dump( self.verDat, out, -1 ) #data version, if this doesn't match the installed data version, do not allow restore cPickle.dump( self.verApp, out, -1 ) #app version, if this doesn't match the installer app version, warn the user on restore out.close() # create the backup archive try: pack7z(self.dir.join(self.archive), self.tmp) except StateError, e: raise return
def _get_default_app_icon(idex, target): # Use the default icon for that file type if winreg is None: return u'not\\a\\path', idex try: if target.isdir(): global __folderIcon if not __folderIcon: # Special handling of the Folder icon folderkey = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, u'Folder') iconkey = winreg.OpenKey(folderkey, u'DefaultIcon') filedata = winreg.EnumValue(iconkey, 0) # filedata == ('', u'%SystemRoot%\\System32\\shell32.dll,3', 2) filedata = filedata[1] __folderIcon = filedata else: filedata = __folderIcon else: file_association = winreg.QueryValue(winreg.HKEY_CLASSES_ROOT, target.cext) pathKey = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, u'%s\\DefaultIcon' % file_association) filedata = winreg.EnumValue(pathKey, 0)[1] winreg.CloseKey(pathKey) if _os.path.isabs(filedata) and _os.path.isfile(filedata): icon = filedata else: icon, idex = filedata.split(u',') icon = _os.path.expandvars(icon) if not _os.path.isabs(icon): # Get the correct path to the dll for dir_ in _os.environ['PATH'].split(u';'): test = _os.path.join(dir_, icon) if _os.path.exists(test): icon = test break except: # TODO(ut) comment the code above - what exception can I get here? deprint(_(u'Error finding icon for %s:') % target.s, traceback=True) icon = u'not\\a\\path' return icon, idex
def _supportedGames(useCache=True): """Set games supported by Bash and return their paths from the registry.""" if useCache and _allGames: return _registryGames.copy() # rebuilt cache _allGames.clear() _registryGames.clear() _fsName_display.clear() _display_fsName.clear() import pkgutil # Detect the known games for importer, modname, ispkg in pkgutil.iter_modules(game_init.__path__): if not ispkg: continue # game support modules are packages # Equivalent of "from game import <modname>" try: module = __import__('game', globals(), locals(), [modname], -1) submod = getattr(module, modname) game_type = submod.GAME_TYPE _allModules[game_type.fsName] = submod _allGames[game_type.fsName] = game_type _fsName_display[game_type.fsName] = game_type.displayName #--Get this game's install path game_path = get_registry_game_path(game_type) except (ImportError, AttributeError): deprint(u'Error in game support module:', modname, traceback=True) continue if game_path: _registryGames[game_type.fsName] = game_path del module # unload some modules, _supportedGames is meant to run once del pkgutil _display_fsName.update({v: k for k, v in _fsName_display.iteritems()}) deprint(u'Detected the following supported games via Windows Registry:') for foundName in _registryGames: deprint(u' %s:' % foundName, _registryGames[foundName]) return _registryGames.copy()
def _fixed_order_plugins(self): """Return the semi fixed plugins after pinning them in correct order by timestamping them.""" # get existing add = [self.master_path] add.extend(x for x in self.must_be_active_if_present if x in self.mod_infos) # rewrite mtimes master_mtime = self.mod_infos[self.master_path].mtime update = bolt.GPath(u'Update.esm') for dlc in add[1:]: if dlc == update: master_mtime = self.mod_infos[update].mtime else: master_mtime += self.__dlc_spacing dlc_mtime = self.mod_infos[dlc].mtime if dlc_mtime != master_mtime: self.mod_infos[dlc].setmtime(master_mtime) bolt.deprint(u'Restamped %s from %s to %s' % (dlc, bolt.formatDate(dlc_mtime), bolt.formatDate(master_mtime))) return add
def setGame(gameName, workingDir=u'', bashIni=None): foundGames_, name = _detectGames(workingDir, bashIni) gameName = gameName.lower() #--See if the specified game is one that was found if gameName in foundGames_: # The game specified was found __setGame(foundGames_, gameName, u'Specified game "%(gamename)s" was found:') return None #--Specified game not found, or game was not specified, # so use the game found via workingDir or the cwd else: if gameName == u'': deprint(u'No preferred game specified.') else: deprint(u'Specified game "%s" was not found.' % gameName) if name in foundGames_: # Game specified was not found, or no game was specified # try the game returned by detectGames() __setGame(foundGames_, name, u' Using %(gamename)s game:') return None # No match found return the list of possible games return foundGames_.keys() # may be empty if nothing is found in registry
def setGame(gameName,workingDir=u''): global gamePath global game foundGames,allGames,name = detectGames(workingDir) gameName = gameName.lower() #--See if the specified game is one that was found if gameName in foundGames: # The game specified was found gamePath = foundGames[gameName] game = allGames[gameName] deprint(u'Specified game "%s" was found:' % gameName, gamePath) # Unload the other modules for i in allGames.keys(): if i != gameName: del allGames[i] game.init() return False #--Specified game not found, or game was not specified, # so use the game found via workingDir or the cwd else: if gameName == u'': deprint(u'No preferred game specified.') else: deprint(u'Specified game "%s" was not found.' % gameName) if name in foundGames: # Game specified was not found, or no game was specified # try the game based on Wrye Bash install location gamePath = foundGames[name] deprint(u' Using %s game:' % name, gamePath) game = allGames[name] # Unload the other modules for i in allGames.keys(): if i != name: del allGames[i] game.init() return False # No match found return the list of possible games # Unload all the modules del allGames return foundGames.keys()
def _supportedGames(useCache=True): """Set games supported by Bash and return their paths from the registry.""" if useCache and _allGames: return _registryGames.copy() # rebuilt cache _allGames.clear() _registryGames.clear() import pkgutil import game as _game # Detect the known games for importer,modname,ispkg in pkgutil.iter_modules(_game.__path__): if not ispkg: continue # game support modules are packages # Equivalent of "from game import <modname>" try: module = __import__('game',globals(),locals(),[modname],-1) except ImportError: deprint(u'Error in game support module:', modname, traceback=True) continue submod = getattr(module,modname) if not hasattr(submod,'fsName') or not hasattr(submod,'exe'): continue _allGames[submod.fsName.lower()] = submod #--Get this game's install path if not winreg: del module continue for hkey in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE): for wow6432 in (u'',u'Wow6432Node\\'): for (subkey,entry) in submod.regInstallKeys: try: key = winreg.OpenKey(hkey, u'Software\\%s%s' % (wow6432,subkey)) value = winreg.QueryValueEx(key,entry) except: continue if value[1] != winreg.REG_SZ: continue installPath = GPath(value[0]) if not installPath.exists(): continue exePath = installPath.join(submod.exe) if not exePath.exists(): continue _registryGames[submod.fsName.lower()] = installPath del module # unload some modules, _supportedGames is meant to run once del pkgutil del _game deprint(u'Detected the following supported games via Windows Registry:') for foundName in _registryGames: deprint(u' %s:' % foundName, _registryGames[foundName]) return _registryGames.copy()
def Apply(self): if not self.PromptFile(): return deprint(u'') deprint(_(u'BACKUP BASH SETTINGS: ') + self.dir.join(self.archive).s) # copy all files to ~tmp backup dir for tpath,fpath in self.files.iteritems(): deprint(tpath.s + u' <-- ' + fpath.s) fpath.copyTo(self.tmp.join(tpath)) #end for # dump the version info and file listing with self.tmp.join(u'backup.dat').open('wb') as out: cPickle.dump(self.verDat, out, -1) #data version, if this doesn't match the installed data version, do not allow restore cPickle.dump(self.verApp, out, -1) #app version, if this doesn't match the installer app version, warn the user on restore # create the backup archive try: pack7z(self.dir.join(self.archive),self.tmp) except StateError, e: raise
def Apply(self): if not self.PromptFile(): return deprint(u'') deprint(_(u'BACKUP BASH SETTINGS: ') + self.dir.join(self.archive).s) with BusyCursor(): # copy all files to ~tmp backup dir for tpath,fpath in self.files.iteritems(): deprint(tpath.s + u' <-- ' + fpath.s) fpath.copyTo(self.tmp.join(tpath)) # dump the version info and file listing with self.tmp.join(u'backup.dat').open('wb') as out: # data version, if this doesn't match the installed data # version, do not allow restore cPickle.dump(self.verDat, out, -1) # app version, if this doesn't match the installer app version, # warn the user on restore cPickle.dump(self.verApp, out, -1) # create the backup archive in 7z format WITH solid compression # may raise StateError command = bosh.compressCommand(self.archive, self.dir, self.tmp) bolt.compress7z(command, self.dir, self.archive, self.tmp) bosh.settings['bash.backupPath'] = self.dir self.InfoSuccess()
# GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from ctypes import * from bolt import deprint import subprocess try: from ctypes.wintypes import MAX_PATH except ValueError: deprint("ctypes.wintypes import failure", traceback=True) try: MAX_PATH = int(subprocess.check_output(['getconf', 'PATH_MAX', '/'])) except (ValueError, subprocess.CalledProcessError, OSError): deprint('calling getconf failed - error:', traceback=True) MAX_PATH = 4096 try: import win32gui except ImportError: # linux win32gui = None raise from bass import winreg BUTTONID_OFFSET = 1000 #---Internal Flags. Leave these alone unless you know what you're doing---#
def detectGames(workingDir=u''): """Detect which supported games are intalled. - First, read the windows registry, checking for the install keys for the games. - Next, check for a valid game at "workingDir" - Finally, also look one directory up from the cwd.""" #--First: Find all supported games via the registry import pkgutil import game as _game foundGames = {} allGames = {} # Detect the known games for importer,modname,ispkg in pkgutil.iter_modules(_game.__path__): # Equivalent of "from game import <modname>" try: module = __import__('game',globals(),locals(),[modname],-1) except: deprint(u'Error in game support file:', modname, traceback=True) continue submod = getattr(module,modname) if not hasattr(submod,'name') or not hasattr(submod,'exe'): continue allGames[submod.name.lower()] = submod #--Get this game's install path for hkey in (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE): for wow6432 in (u'',u'Wow6432Node\\'): for (subkey,entry) in submod.regInstallKeys: try: key = _winreg.OpenKey(hkey, u'Software\\%s%s' % (wow6432,subkey)) value = _winreg.QueryValueEx(key,entry) except: continue if value[1] != _winreg.REG_SZ: continue installPath = GPath(value[0]) if not installPath.exists(): continue exePath = installPath.join(submod.exe) if not exePath.exists(): continue foundGames[submod.name.lower()] = installPath del module # unload some modules del pkgutil del _game deprint(u'Detected the following supported games via Windows Registry:') for foundName in foundGames: deprint(u' %s:' % foundName, foundGames[foundName]) #--Second: Detect what game is installed on directory up from Mopy path = Path.getcwd() if path.cs[-4:] == u'mopy': path = GPath(path.s[:-5]) installPaths = [path] #--Third: Detect what game is installed at the specified "workingDir" if workingDir != u'': path = GPath(workingDir) if not path.isabs(): path = Path.getcwd().join(path) installPaths.insert(0,path) deprint(u'Detecting games via relative path and the -o argument:') name = None for path in installPaths: _name = path.tail.cs if _name in allGames: # We have a config for that game deprint(u' %s:' % _name, path) foundGames[_name] = path name = _name break else: # Folder name wasn't found, try looking by exe name for file in path.list(): for _name in allGames: if allGames[_name].exe == file: # Must be this game deprint(u' %s:' % _name, path) name = _name foundGames[name] = path break else: continue break else: continue break return foundGames,allGames,name
def Apply(self): if self.ErrorConflict(): self.WarnFailed() return elif not self.PromptMismatch(): raise BackupCancelled() deprint(u'') deprint(_(u'RESTORE BASH SETTINGS: ') + self.dir.join(self.archive).s) # reinitialize bosh.dirs using the backup copy of bash.ini if it exists game = bush.game.name tmpBash = self.tmp.join(game+u'\\Mopy\\bash.ini') opts, args = bash.opts, bash.extra bash.SetUserPath(tmpBash.s,opts.userPath) bashIni = bash.GetBashIni(tmpBash.s) bosh.initBosh(opts.personalPath,opts.localAppDataPath,opts.oblivionPath) # restore all the settings files restore_paths = ( (dirs['mopy'], game+u'\\Mopy'), (dirs['mods'].join(u'Bash'), game+u'\\Data\\Bash'), (dirs['mods'].join(u'Bash Patches'),game+u'\\Data\\Bash Patches'), (dirs['mods'].join(u'Docs'), game+u'\\Data\\Docs'), (dirs['mods'].join(u'INI Tweaks'), game+u'\\Data\\INI Tweaks'), (dirs['modsBash'], game+u' Mods\\Bash Mod Data'), (dirs['modsBash'].join(u'INI Data'),game+u' Mods\\Bash Mod Data\\INI Data'), (dirs['bainData'], game+u' Mods\\Bash Installers\\Bash'), (dirs['userApp'], u'LocalAppData\\'+game), (dirs['saveBase'], u'My Games\\'+game), ) if 293 >= self.verApp: # restore from old data paths restore_paths += ( (dirs['l10n'], game+u'\\Data'),) if self.restore_images: restore_paths += ( (dirs['images'], game+u'\\Mopy\\images'),) else: restore_paths += ( (dirs['l10n'], game+u'\\bash\\l10n'),) if self.restore_images: restore_paths += ( (dirs['images'], game+u'\\Mopy\\bash\\images'),) for fpath, tpath in restore_paths: path = self.tmp.join(tpath) if path.exists(): for name in path.list(): if path.join(name).isfile(): deprint(GPath(tpath).join(name).s + u' --> ' + fpath.join(name).s) path.join(name).copyTo(fpath.join(name)) #restore savegame profile settings tpath = GPath(u'My Games\\'+game+u'\\Saves') fpath = dirs['saveBase'].join(u'Saves') path = self.tmp.join(tpath) if path.exists(): for root, folders, files in path.walk(True,None,True): root = GPath(u'.'+root.s) for name in files: deprint(tpath.join(root,name).s + u' --> ' + fpath.join(root,name).s) path.join(root,name).copyTo(fpath.join(root,name)) # tell the user the restore is compete and warn about restart self.WarnRestart() if basher.bashFrame: # should always exist basher.bashFrame.Destroy()
def _detectGames(cli_path=u'',bashIni=None): """Detect which supported games are installed. - If Bash supports no games raise. - For each game supported by Bash check for a supported game executable in the following dirs, in decreasing precedence: - the path provided by the -o cli argument if any - the sOblivionPath Bash Ini entry if present - one directory up from Mopy If a game exe is found update the path to this game and return immediately. Return (foundGames, name) - foundGames: a dict from supported games to their paths (the path will default to the windows registry path to the game, if present) - name: the game found in the first installDir or None if no game was found - a 'suggestion' for a game to use (if no game is specified/found via -g argument). """ #--Find all supported games and all games in the windows registry foundGames_ = _supportedGames() # sets _allGames if not set if not _allGames: # if allGames is empty something goes badly wrong raise BoltError(_(u'No game support modules found in Mopy/bash/game.')) # check in order of precedence the -o argument, the ini and our parent dir installPaths = collections.OrderedDict() #key->(path, found msg, error msg) #--First: path specified via the -o command line argument if cli_path != u'': path = GPath(cli_path) if not path.isabs(): path = Path.getcwd().join(path) installPaths['cmd'] = (path, _(u'Set game mode to %(gamename)s specified via -o argument') + u': ', _(u'No known game in the path specified via -o argument: ' + u'%(path)s')) #--Second: check if sOblivionPath is specified in the ini if bashIni and bashIni.has_option(u'General', u'sOblivionPath') \ and not bashIni.get(u'General', u'sOblivionPath') == u'.': path = GPath(bashIni.get(u'General', u'sOblivionPath').strip()) if not path.isabs(): path = Path.getcwd().join(path) installPaths['ini'] = (path, _(u'Set game mode to %(gamename)s based on sOblivionPath setting ' u'in bash.ini') + u': ', _(u'No known game in the path specified in sOblivionPath ini ' u'setting: %(path)s')) #--Third: Detect what game is installed one directory up from Mopy path = Path.getcwd() if path.cs[-4:] == u'mopy': path = GPath(path.s[:-5]) if not path.isabs(): path = Path.getcwd().join(path) installPaths['upMopy'] = (path, _(u'Set game mode to %(gamename)s found in parent directory of' u' Mopy') + u': ', _(u'No known game in parent directory of Mopy: %(path)s')) #--Detect deprint(u'Detecting games via the -o argument, bash.ini and relative path:') # iterate installPaths in insert order ('cmd', 'ini', 'upMopy') for path, foundMsg, errorMsg in installPaths.itervalues(): for name, module in _allGames.items(): if path.join(module.exe).exists(): # Must be this game deprint(foundMsg % {'gamename':name}, path) foundGames_[name] = path return foundGames_, name # no game exe in this install path - print error message deprint(errorMsg % {'path': path.s}) # no game found in installPaths - foundGames are the ones from the registry return foundGames_, None
# Move the new plugins.txt and loadorder.txt here for use move = newPath.join(u'plugins.txt') if move.exists(): move.copyTo(_plugins_txt_path) _plugins_txt_path.mtime = time.time() # copy will not change mtime, bad move = newPath.join(u'loadorder.txt') if move.exists(): move.copyTo(_loadorder_txt_path) _loadorder_txt_path.mtime = time.time()#update mtime to trigger refresh #----------------------------------------------------------------------REFACTOR _liblo_handle, _liblo_error = _liblo.Init(bosh.dirs['compiled'].s) # That didn't work - Wrye Bash isn't installed correctly if not _liblo.liblo: raise bolt.BoltError(u'The libloadorder API could not be loaded.') bolt.deprint(u'Using libloadorder API version:', _liblo.version) _liblo_handle = _liblo_handle(bosh.dirs['app'].s, bush.game.fsName, bosh.dirs['userApp'].s) if bush.game.fsName == u'Oblivion' and bosh.dirs['mods'].join( u'Nehrim.esm').isfile(): _liblo_handle.SetGameMaster(u'Nehrim.esm') if bosh.dirs['saveBase'] == bosh.dirs['app']: #--If using the game directory as rather than the appdata dir. _dir = bosh.dirs['app'] else: _dir = bosh.dirs['userApp'] _plugins_txt_path = _dir.join(u'plugins.txt') _loadorder_txt_path = _dir.join(u'loadorder.txt') mtimePlugins = 0
def __fixLoadOrder(lord, _selected=None): """HACK: Fix inconsistencies between given loadorder and actually installed mod files as well as impossible load orders - save the fixed order via liblo. Called in _getLoFromLiblo() to fix a newly fetched LO and in SaveLoadOrder() to check if a load order passed in is valid. Needs rethinking as save load and active should be an atomic operation - complicated by the fact that liblo does not support this. As a consequence a lot of hacks are needed (like the _selected parameter). """ oldLord = lord[:] ### print # game's master might be out of place (if using timestamps for load # ordering or a manually edited loadorder.txt) so move it up masterName = bosh.modInfos.masterName masterDex = lord.index(masterName) if masterDex > 0: bolt.deprint(u'%s in %d position' % (masterName, masterDex)) lord.remove(masterName) lord.insert(0, masterName) _reordered = True else: _reordered = False loadOrder = set(lord) modFiles = set(bosh.modInfos.keys()) _removedFiles = loadOrder - modFiles # may remove corrupted mods returned # from liblo (text file), we are supposed to take care of that _addedFiles = modFiles - loadOrder # Remove non existent plugins from load order lord[:] = [x for x in lord if x not in _removedFiles] indexFirstEsp = _indexFirstEsp(lord) # Check to see if any esm files are loaded below an esp and reorder as necessary for mod in lord[indexFirstEsp:]: # SEEMS NOT NEEDED, liblo does this if mod in bosh.modInfos and bosh.modInfos[mod].isEsm(): lord.remove(mod) lord.insert(indexFirstEsp, mod) indexFirstEsp += 1 _reordered = True # Append new plugins to load order for mod in _addedFiles: if bosh.modInfos[mod].isEsm(): lord.insert(indexFirstEsp, mod) indexFirstEsp += 1 else: lord.append(mod) if _addedFiles and not usingTxtFile(): lord = bosh.modInfos.calculateLO(mods=lord) # Save changes if necessary if _removedFiles or _addedFiles or _reordered: if _removedFiles or _reordered: # must fix the active too # If _selected is not None we come from SaveLoadOrder which needs # to save _selected too - so fix this list instead of # _current_lo.activeOrdered. If fixed and saved, empty it so we do # not resave it. If selected was empty to begin with we need extra # hacks (wasEmpty in SaveLoadOrder) if _selected is None: if _current_lo is not __empty: # else we are on first refresh _selected = list(_current_lo.activeOrdered) if _selected is not None and __fixActive(_selected, lord): _selected[:] = [] # avoid resaving bolt.deprint(u'Fixed Load Order: added(%s), removed(%s), reordered(%s)' % (_pl(_addedFiles) or u'None', _pl(_removedFiles) or u'None', u'No' if not _reordered else _pl(oldLord, u'from:\n') + _pl(lord, u'\nto:\n'))) SaveLoadOrder(lord, _fixed=True) return True # changes, saved return False # no changes, not saved