aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_bsplugins/src/TESData/RecordPath.cpp
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 21:04:15 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 21:04:15 -0500
commit3949dcfce95af4bd305f258ff5b170d7d50435f6 (patch)
tree4c768be256c40f31794c3311f0a2c22933a6705a /libs/installer_bsplugins/src/TESData/RecordPath.cpp
parent892070f3dec1dc690983fd862880e37e711c2516 (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.cpp158
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