diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-12 21:04:15 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-12 21:04:15 -0500 |
| commit | 3949dcfce95af4bd305f258ff5b170d7d50435f6 (patch) | |
| tree | 4c768be256c40f31794c3311f0a2c22933a6705a /libs/installer_bsplugins/src/TESData/RecordPath.cpp | |
| parent | 892070f3dec1dc690983fd862880e37e711c2516 (diff) | |
VFS perf fixes, icon/stylesheet compat, download filename fix
VFS:
- Open backing fd at mo2_open time for read-only handles so mo2_read can
splice without re-opening the file on every read call.
- Bump RLIMIT_NOFILE at mount time to fit the resulting fd pressure when
the game keeps hundreds of BSAs open.
- Dedicated node_cache_mutex so resolveByInode / mo2_lookup stop racing
unordered_map writes while multiple tree_mutex readers are active.
- max_read=1MB + matching conn->max_read and raised max_readahead/max_write
so the kernel merges Wine's small sequential reads into bigger FUSE
requests.
- Per-op wall-clock counters in mo2_init() logs so we can distinguish VFS
latency from game-side work.
Proton launch:
- Write dxvk.conf to the prefix and set DXVK_CONFIG_FILE to force
dxvk.enableGraphicsPipelineLibrary=False, avoiding long GPL compile
stalls on first run.
UI / plugin compat:
- Clamp IconDelegate's per-icon width to [8, 16] so narrow content
columns don't trigger QIcon::pixmap(0,0) returning null and logging
"failed to load icon" every repaint.
- Pre-create lowercase symlinks in the stylesheet directory so QSS files
authored on Windows resolve url(foo.svg) when on-disk files are Foo.svg.
- Flip content icons empty-chessboard.png and facegen.png to 8-bit RGBA
so Qt's built-in PNG reader loads them uniformly.
- Add mobase.Version.canonicalString shim for legacy Python plugins that
still call the old VersionInfo method on appVersion()'s return.
- BSPluginList: compare normalized plugin name lists instead of byte
hashes so the post-run case-fix refresh stops spuriously firing the
"load order changed" dialog.
- BSPluginList disabled by default.
Download manager:
- Parse CDN URL with QUrl + QUrlQuery and read the filename from the
response-content-disposition query param (RFC 6266 quoted / unquoted /
ext-value forms), not QFileInfo on the raw URL.
- When the API or URL gives us a CDN object key (no archive extension),
prefer the actual Content-Disposition header from the live response in
metaDataChanged and downloadFinished.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_bsplugins/src/TESData/RecordPath.cpp')
| -rw-r--r-- | libs/installer_bsplugins/src/TESData/RecordPath.cpp | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/libs/installer_bsplugins/src/TESData/RecordPath.cpp b/libs/installer_bsplugins/src/TESData/RecordPath.cpp new file mode 100644 index 0000000..ac6a575 --- /dev/null +++ b/libs/installer_bsplugins/src/TESData/RecordPath.cpp @@ -0,0 +1,158 @@ +#include "RecordPath.h" + +#include <algorithm> +#include <iomanip> +#include <iterator> +#include <sstream> + +namespace TESData +{ + +std::string RecordPath::string() const +{ + std::ostringstream ss; + ss << ":/"; + + for (int i = 0; i < m_Groups.size(); ++i) { + const TESFile::GroupData group = m_Groups[i]; + + if (group.hasFormType()) { + ss << group.formType().view() << "/"; + } else if (group.hasParent()) { + if (i == 0 || !m_Groups[i - 1].hasParent() || + m_Groups[i - 1].parent() != group.parent()) { + const auto parentId = group.parent(); + const std::uint8_t localIndex = parentId >> 24U; + const std::string& owner = m_Files.at(localIndex); + ss << owner << "|"; + ss << std::hex << std::setfill('0') << std::setw(6) << (parentId & 0xFFFFFF); + ss << "/"; + } else { + switch (group.type()) { + case TESFile::GroupType::CellPersistentChildren: + ss << "Persistent/"; + break; + case TESFile::GroupType::CellTemporaryChildren: + ss << "Temporary/"; + break; + case TESFile::GroupType::CellVisibleDistantChildren: + ss << "Visible Distant/"; + break; + } + } + } else if (group.hasBlock()) { + ss << group.block() << "/"; + } else if (group.hasGridCell()) { + auto [x, y] = group.gridCell(); + ss << x << ", " << y << "/"; + } + } + + if (hasFormId()) { + const auto id = formId(); + const std::uint8_t localIndex = id >> 24U; + const std::string& owner = m_Files.at(localIndex); + ss << owner << "|"; + ss << std::hex << std::setfill('0') << std::setw(6) << (id & 0xFFFFFF); + } else if (hasEditorId()) { + ss << editorId(); + } else if (hasTypeId()) { + ss << typeId(); + } + + return ss.str(); +} + +void RecordPath::setFormId(std::uint32_t formId, std::span<const std::string> masters, + const std::string& file) +{ + const std::uint8_t localIndex = formId >> 24U; + const std::string& owner = localIndex < masters.size() ? masters[localIndex] : file; + const std::uint8_t newIndex = static_cast<std::uint8_t>( + std::distance(std::begin(m_Files), TESFile::find(m_Files, owner))); + + if (newIndex == m_Files.size()) { + m_Files.push_back(owner); + } + + m_Identifier = (formId & 0xFFFFFF) | (newIndex << 24U); +} + +void RecordPath::unsetFormId() +{ + if (!hasFormId()) + return; + + const std::uint8_t localIndex = formId() >> 24U; + m_Identifier = {}; + + if (localIndex == m_Files.size() - 1) { + cleanLastFile(); + } +} + +void RecordPath::setEditorId(const std::string& editorId) +{ + unsetFormId(); + m_Identifier = editorId; +} + +void RecordPath::setTypeId(TESFile::Type type) +{ + unsetFormId(); + m_Identifier = type; +} + +void RecordPath::setIdentifier(const Identifier& identifier, + std::span<const std::string> masters) +{ + if (std::holds_alternative<std::uint32_t>(identifier)) { + setFormId(std::get<std::uint32_t>(identifier), masters, ""); + } else { + unsetFormId(); + m_Identifier = identifier; + } +} + +void RecordPath::push(TESFile::GroupData group, std::span<const std::string> masters, + const std::string& file) +{ + if (group.hasParent()) { + const std::uint8_t localIndex = group.parent() >> 24U; + const std::string& owner = localIndex < masters.size() ? masters[localIndex] : file; + const std::uint8_t newIndex = static_cast<std::uint8_t>( + std::distance(std::begin(m_Files), TESFile::find(m_Files, owner))); + + if (newIndex == m_Files.size()) { + m_Files.push_back(owner); + } + + group.setLocalIndex(newIndex); + } + + m_Groups.push_back(group); +} + +void RecordPath::pop() +{ + unsetFormId(); + + const auto& top = m_Groups.back(); + const std::uint8_t localIndex = top.hasParent() ? top.parent() >> 24U : 0xFF; + m_Groups.pop_back(); + + if (localIndex == m_Files.size() - 1) { + cleanLastFile(); + } +} + +void RecordPath::cleanLastFile() +{ + if (!std::ranges::any_of(m_Groups, [&](auto&& group) { + return group.hasParent() && group.parent() >> 24U == m_Files.size() - 1; + })) { + m_Files.pop_back(); + } +} + +} // namespace TESData |
