Skip to content

Commit

Permalink
Merge branch 'wrye-bash:dev' into 570-launchers-menu-gaz
Browse files Browse the repository at this point in the history
  • Loading branch information
BGazotti authored Mar 23, 2024
2 parents 33e04d1 + 9dfef00 commit dc74d26
Show file tree
Hide file tree
Showing 44 changed files with 12,210 additions and 12,093 deletions.
181 changes: 105 additions & 76 deletions Mopy/Docs/Wrye Bash Advanced Readme.html

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Mopy/Docs/Wrye Bash General Readme.html
Original file line number Diff line number Diff line change
Expand Up @@ -2784,6 +2784,12 @@ <h3>Homage Credits <a class="back2top" href="#contents">Back to top</a></h3>
<a href="Wrye%20Bash%20Advanced%20Readme.html#patch-tweak-compass-rd">Compass: Recognition Distance</a> was inspired by
<a href="https://www.nexusmods.com/oblivion/mods/4882">KseAli_POI_Visibility</a> by
<a href="https://www.nexusmods.com/users/114786">KseAli</a>.</span></li>
<li><img class="smallcred" alt="Zzyxzz's Icon" src="../bash/images/people/zzyxavi32.png"><span>
<a href="Wrye%20Bash%20Advanced%20Readme.html#patch-tweak-settings-combat-btavg">Combat: Block Time (Average)</a>,
<a href="Wrye%20Bash%20Advanced%20Readme.html#patch-tweak-settings-combat-btmax">Combat: Block Time (Maximum)</a> and
<a href="Wrye%20Bash%20Advanced%20Readme.html#patch-tweak-settings-combat-btmin">Combat: Block Time (Minimum)</a> were inspired by
<a href="https://www.nexusmods.com/skyrimspecialedition/mods/113064">NPC Infinite Block Fix</a> by
<a href="https://www.nexusmods.com/users/8515543">Zzyxzz</a>.</span></li>
<li><img class="smallcred" alt="Demuerte's Icon" src="../bash/images/bash.svg" role="img"><span>
<a href="Wrye%20Bash%20Advanced%20Readme.html#patch-tweak-magic-cr">Magic: Chameleon Refraction</a> was inspired by
<a href="https://www.nexusmods.com/oblivion/mods/2411">Chameleon-NoRefraction</a> by
Expand Down
19 changes: 14 additions & 5 deletions Mopy/bash/_games_lo.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
from collections import defaultdict

from . import bass, bolt, env, exception
from .bolt import FName, GPath, Path, dict_sort, attrgetter_cache
from .bolt import FName, GPath, Path, dict_sort, attrgetter_cache, \
classproperty
from .ini_files import get_ini_type_and_encoding

# Typing
Expand Down Expand Up @@ -699,6 +700,7 @@ def ini_dir_lo(self) -> Path:
@staticmethod
def _mk_ini(ini_fpath):
"""Creates a new IniFile from the specified bolt.Path object."""
# We don't support OBSE INIs here, only regular IniFile objects
ini_type, ini_encoding = get_ini_type_and_encoding(ini_fpath)
return ini_type(ini_fpath, ini_encoding)

Expand Down Expand Up @@ -1519,10 +1521,17 @@ class SkyrimSE(AsteriskGame):
class SkyrimVR(SkyrimSE):
must_be_active_if_present = (*SkyrimSE.must_be_active_if_present,
FName('SkyrimVR.esm'))
# No ESLs, reset these back to their pre-ESL versions
_ccc_filename = ''
max_espms = 255
max_esls = 0

##: This is nasty, figure out a way to get rid of it
@classproperty
def max_espms(cls):
from . import bush
return 253 if bush.game.has_esl else 255

@classproperty
def max_esls(cls):
from . import bush
return 4096 if bush.game.has_esl else 0

class EnderalSE(SkyrimSE):
# Update.esm is forcibly loaded after the (empty) DLC plugins by the game
Expand Down
200 changes: 162 additions & 38 deletions Mopy/bash/bash.py

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions Mopy/bash/basher/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3897,9 +3897,9 @@ def warnTooManyModsBsas(self):
if not limit_fixers: return # Problem does not apply to this game
if not bass.inisettings['WarnTooManyFiles']: return
for lf in limit_fixers:
lf_path = bass.dirs[u'mods'].join(bush.game.Se.plugin_dir,
u'plugins', lf)
if lf_path.is_file():
lf_path = env.to_os_path(bass.dirs['mods'].join(
bush.game.Se.plugin_dir, 'plugins', lf))
if lf_path and lf_path.is_file():
return # Limit-fixing xSE plugin installed
if not len(bosh.bsaInfos): bosh.bsaInfos.refresh()
if len(bosh.bsaInfos) + len(bosh.modInfos) >= 325 and not \
Expand Down Expand Up @@ -3999,7 +3999,8 @@ def RefreshData(self, evt_active=True, booting=False):
#--Config helpers
initialization.lootDb.refreshBashTags()
#--Check bsas, needed to detect string files in modInfos refresh...
bosh.oblivionIni.get_ini_language(cached=False) # reread ini language
bosh.oblivionIni.get_ini_language(bush.game.Ini.default_game_lang,
cached=False) # Reread INI language
# refresh the backend - order matters, bsas must come first for strings
# inis and screens call refresh in ShowPanel
##: maybe we need to refresh inis and *not* refresh saves but on ShowPanel?
Expand Down
2 changes: 1 addition & 1 deletion Mopy/bash/basher/belt.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ def fnDataFileExists(self, *rel_paths):
if rel_path in bosh.modInfos:
return True # It's a (potentially ghosted) plugin
rel_path_os = to_os_path(bass.dirs['mods'].join(rel_path))
if not rel_path_os.exists():
if not rel_path_os or not rel_path_os.exists():
return False
return True

Expand Down
6 changes: 3 additions & 3 deletions Mopy/bash/basher/frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,10 @@ def CheckMods(self, _new_value=None):
self.check_mods_text = mods_metadata.checkMods(*args)
except CancelError:
return # user pressed cancel early
log_path = bass.dirs[u'saveBase'].join(u'ModChecker.html')
css_dir = bass.dirs[u'mopy'].join(u'Docs')
wrye_text.convert_wtext_to_html(log_path, self.check_mods_text, css_dir)
if web_viewer_available():
log_path = bass.dirs[u'saveBase'].join(u'ModChecker.html')
css_dir = bass.dirs[u'mopy'].join(u'Docs')
wrye_text.convert_wtext_to_html(log_path, self.check_mods_text, css_dir)
self._html_ctrl.try_load_html(log_path)
else:
self._html_ctrl.load_text(self.check_mods_text)
Expand Down
22 changes: 14 additions & 8 deletions Mopy/bash/basher/gui_patchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,8 @@ def __init__(self, _text, index):
def _check(self): return self.index == tweak.chosen
def Execute(self): _self.tweak_choice(self.index, tweakIndex)
class _ValueLinkCustom(_ValueLink):
def Execute(self): _self.tweak_custom_choice(self.index,tweakIndex)
def Execute(self):
_self.tweak_custom_choice(self.index, tweakIndex)
for index, itm_txt in enumerate(choiceLabels):
if itm_txt == '----':
links.append_link(SeparatorLink())
Expand Down Expand Up @@ -740,14 +741,17 @@ def tweak_custom_choice(self, index, tweakIndex):
##: Mirrors chosen_eids, but all this is hacky - we should
# enforce that keys for settings tweaks *must* be tuples and
# then get rid of this
key_display = u'\n\n' + tweak.tweak_key[i] if isinstance(
key_display = tweak.tweak_key[i] if isinstance(
tweak.tweak_key, tuple) else tweak.tweak_key
default_tweak_fmt = ' ' + _('(Default: %(default_tweak_val)s)')
else:
key_display = u''
key_display = ''
default_tweak_fmt = _('Default: %(default_tweak_val)s')
default_tweak_fmt %= {'default_tweak_val': v}
if isinstance(v, float):
msg = (_('Enter the desired custom tweak value.') + '\n\n' + _(
'Note: A floating point number is expected here.'))
msg = f'{msg}{key_display}'
msg = (f'{_('Enter the desired custom tweak value.')}\n\n'
f'{_('Note: A floating point number is expected '
'here.')}\n\n{key_display}{default_tweak_fmt}')
while new is None: # keep going until user entered valid float
new = askText(self.gConfigPanel, msg,
title=_('%(tweak_title)s - Custom Tweak Value') % {
Expand All @@ -767,7 +771,8 @@ def tweak_custom_choice(self, index, tweakIndex):
'tweak_title': tweak.tweak_name})
new = None # invalid float, try again
elif isinstance(v, int):
msg = _('Enter the desired custom tweak value.') + key_display
msg = (f"{_('Enter the desired custom tweak value.')}\n\n"
f"{key_display}{default_tweak_fmt}")
new = askNumber(self.gConfigPanel, msg, prompt=_('Value'),
title=_('%(tweak_title)s - Custom Tweak Value') % {
'tweak_title': tweak.tweak_name},
Expand All @@ -777,7 +782,8 @@ def tweak_custom_choice(self, index, tweakIndex):
return
values.append(new)
elif isinstance(v, str):
msg = _(u'Enter the desired custom tweak text.') + key_display
msg = (f"{_('Enter the desired custom tweak text.')}\n\n"
f"{key_display}{default_tweak_fmt}")
# Don't strip - at least for Tweak Names, custom choices with
# trailing whitespace are necessary (e.g. consider a custom
# choice '%s* ', which renames 'Fireball' to 'D* Fireball')
Expand Down
4 changes: 2 additions & 2 deletions Mopy/bash/basher/installers_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ def _enable(self): return bosh.oblivionIni.abs_path.exists()

def Execute(self):
super(Installers_BsaRedirection, self).Execute()
if bass.settings[self._bl_key]:
if should_redirect := bass.settings[self._bl_key]:
# Delete ArchiveInvalidation.txt, if it exists
bosh.bsaInfos.remove_invalidation_file()
##: Hack, this should not use display_name
Expand All @@ -491,7 +491,7 @@ def Execute(self):
with balt.Progress(
_('Enabling BSA Redirection…')) as progress:
bsaFile.undo_alterations(progress)
bosh.oblivionIni.setBsaRedirection(bass.settings[self._bl_key])
bosh.bsaInfos.set_bsa_redirection(do_redirect=should_redirect)

#------------------------------------------------------------------------------
class Installers_ShowInactiveConflicts(_Installers_BoolLink_Refresh):
Expand Down
9 changes: 3 additions & 6 deletions Mopy/bash/basher/settings_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,16 +499,13 @@ def _l10n_dir(self):
return bass.dirs[u'l10n']

def on_apply(self):
super(LanguagePage, self).on_apply()
super().on_apply()
selected_lang = self._lang_dropdown.get_value()
if selected_lang != self._initial_lang:
internal_name = self._localized_to_internal[selected_lang]
##: #26, our oldest open issue; This should be a
# parameterless restart request, with us having saved the
# new language to some 'early boot' info file
bass.boot_settings['Boot']['locale'] = internal_name
self._request_restart(_('Language: %(selected_language)s') % {
'selected_language': selected_lang},
[('--Language', internal_name)])
'selected_language': selected_lang})

# Misc Appearance -------------------------------------------------------------
class MiscAppearancePage(_AFixedPage):
Expand Down
12 changes: 11 additions & 1 deletion Mopy/bash/bass.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"""This module just stores some data that all modules have to be able to access
without worrying about circular imports. Currently used to expose layout
and environment issues - do not modify or imitate (ut)."""

import copy
from collections import defaultdict
from enum import Enum
from typing import TYPE_CHECKING, NewType
Expand All @@ -46,6 +46,16 @@
# settings read from the Mopy/bash.ini file in _parse_bash_ini()
inisettings = {}

# Settings read from the per-user boot-settings.toml file. Used to house things
# like locale and last chosen game, which we need before we set the game
boot_settings_defaults = {
'Boot': {
'locale': None,
'last_game': None,
},
}
boot_settings = copy.deepcopy(boot_settings_defaults)

# settings dictionary - belongs to a dedicated settings module below bolt - WIP !
# bash.launchers: dict[name: str][path, args: str]
settings = None # bolt.Settings !
Expand Down
13 changes: 13 additions & 0 deletions Mopy/bash/bolt.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# =============================================================================
from __future__ import annotations

import builtins
import collections
import copy
import datetime
Expand Down Expand Up @@ -235,6 +236,18 @@ def encode_complex_string(string_val: str, max_size: int | None = None,
bytes_val += b'\x00' * num_nulls
return bytes_val

def failsafe_underscore(s: str):
"""A version of _() that doesn't fail when gettext has not been set up yet.
Use as "from bolt import failsafe_underscore as _".
Used by e.g. ini_files, which has to be used very early during boot for
correct case sensitivity handling in INIs, so the gettext translation
function may not be set up yet."""
try:
return builtins._(s)
except AttributeError:
return s # We're being invoked very early in boot

class Tee:
"""Similar to the Unix utility tee, this class redirects writes etc. to two
separate IO streams. The name comes from T-splitters (often called tees),
Expand Down
54 changes: 47 additions & 7 deletions Mopy/bash/bosh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import pickle
import re
import sys
import time
from collections import defaultdict, deque, OrderedDict
from collections.abc import Iterable, Callable
from dataclasses import dataclass, field
Expand Down Expand Up @@ -937,9 +938,9 @@ def isMissingStrings(self, cached_ini_info, ci_cached_strings_paths):
to all strings files. They must match the format returned by
_string_files_paths (i.e. starting with 'strings/')."""
if not getattr(self.header.flags1, 'localized', False): return False
lang = oblivionIni.get_ini_language()
i_lang = oblivionIni.get_ini_language(bush.game.Ini.default_game_lang)
bsa_infos = self._find_string_bsas(cached_ini_info)
for assetPath in self._string_files_paths(lang):
for assetPath in self._string_files_paths(i_lang):
# Check loose files first
if assetPath.lower() in ci_cached_strings_paths:
continue
Expand Down Expand Up @@ -1078,7 +1079,7 @@ def BestIniFile(abs_ini_path):
if game_ini:
return game_ini
inferred_ini_type, detected_encoding = get_ini_type_and_encoding(
abs_ini_path)
abs_ini_path, consider_obse_inis=bush.game.Ini.has_obse_inis)
return inferred_ini_type(abs_ini_path, detected_encoding)

def best_ini_files(abs_ini_paths):
Expand All @@ -1098,7 +1099,8 @@ def best_ini_files(abs_ini_paths):
found_types.add(IniFileInfo)
continue
try:
detected_type, detected_enc = get_ini_type_and_encoding(aip)
detected_type, detected_enc = get_ini_type_and_encoding(aip,
consider_obse_inis=bush.game.Ini.has_obse_inis)
except FailedIniInferError:
# Come back to this later using the found types
ambigous_paths.add(aip)
Expand All @@ -1112,7 +1114,8 @@ def best_ini_files(abs_ini_paths):
single_found_type = next(iter(found_types))
for aip in ambigous_paths:
detected_type, detected_enc = get_ini_type_and_encoding(aip,
fallback_type=single_found_type)
fallback_type=single_found_type,
consider_obse_inis=bush.game.Ini.has_obse_inis)
ret[aip] = detected_type(aip, detected_enc)
return ret

Expand Down Expand Up @@ -1856,7 +1859,8 @@ def ini_info_factory(fullpath, **kwargs) -> INIInfo:
:param fullpath: Full path to the INI file to wrap
:param load_cache: Dummy param used in INIInfos.new_info factory call
:param kwargs: Cached ghost status information, ignored for INIs"""
inferred_ini_type, detected_encoding = get_ini_type_and_encoding(fullpath)
inferred_ini_type, detected_encoding = get_ini_type_and_encoding(fullpath,
consider_obse_inis=bush.game.Ini.has_obse_inis)
ini_info_type = (ObseIniInfo if inferred_ini_type == OBSEIniFile
else INIInfo)
return ini_info_type(fullpath, detected_encoding)
Expand Down Expand Up @@ -3551,12 +3555,48 @@ def new_info(self, fileName, _in_refresh=False, owner=None,
@property
def bash_dir(self): return dirs[u'modsBash'].join(u'BSA Data')

# BSA Redirection ---------------------------------------------------------
_aii_name = 'ArchiveInvalidationInvalidated!.bsa'
_bsa_redirectors = {_aii_name.lower(), '..\\obmm\\bsaredirection.bsa'}

@staticmethod
def remove_invalidation_file():
"""Removes ArchiveInvalidation.txt, if it exists in the game folder.
This is used when disabling other solutions to the Archive Invalidation
problem prior to enabling WB's BSA Redirection."""
dirs[u'app'].join(u'ArchiveInvalidation.txt').remove()
dirs['app'].join('ArchiveInvalidation.txt').remove()

def set_bsa_redirection(self, *, do_redirect: bool):
"""Activate or deactivate BSA redirection - game ini must exist!"""
if oblivionIni.isCorrupted: return
br_section, br_key = bush.game.Ini.bsa_redirection_key
if not br_section or not br_key: return
aii_bsa = self.get(self._aii_name)
aiBsaMTime = time.mktime((2006, 1, 2, 0, 0, 0, 0, 2, 0))
if aii_bsa and aii_bsa.ftime > aiBsaMTime:
aii_bsa.setmtime(aiBsaMTime)
# check if BSA redirection is active
sArchives = oblivionIni.getSetting(br_section, br_key, '')
is_bsa_redirection_active = any(x for x in sArchives.split(',')
if x.strip().lower() in self._bsa_redirectors)
if do_redirect == is_bsa_redirection_active:
return
if do_redirect and not aii_bsa:
source = dirs['templates'].join(
bush.game.template_dir, self._aii_name)
source.mtime = aiBsaMTime
try:
env.shellCopy({source: self.store_dir.join(self._aii_name)},
allow_undo=True, auto_rename=True)
except (PermissionError, CancelError, SkipError):
return
# Strip any existing redirectors out, then add our own
bsa_archs = [x_s for x in sArchives.split(',') if
(x_s := x.strip()).lower() not in self._bsa_redirectors]
if do_redirect:
bsa_archs.insert(0, self._aii_name)
sArchives = ', '.join(bsa_archs)
oblivionIni.saveSetting('Archive', 'sArchiveList', sArchives)

#------------------------------------------------------------------------------
class ScreenInfos(_AFileInfos):
Expand Down
3 changes: 2 additions & 1 deletion Mopy/bash/bosh/_mergeability.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ def _pbash_mergeable_no_load(modInfo, minfos, reasons):
if modInfo.fn_key in minfos.missing_strings:
if not verbose: return False
from . import oblivionIni
i_lang = oblivionIni.get_ini_language(bush.game.Ini.default_game_lang)
strings_example = (f'{os.path.join("Strings", modInfo.fn_key.fn_body)}'
f'_{oblivionIni.get_ini_language()}.STRINGS')
f'_{i_lang}.STRINGS')
reasons.append(_('Missing string translation files '
'(%(strings_example)s, etc).') % {
'strings_example': strings_example})
Expand Down
Loading

0 comments on commit dc74d26

Please sign in to comment.