Compare commits

...
24 Commits
Author SHA1 Message Date
Mikaël Capelle 9c130cbf2f Bump version to 2.5.2. (#2090) 2024-08-04 09:58:53 +02:00
Mikaël Capelle ea38fdc6ce Replace chopped by mid in download list. (#2089) 2024-08-03 13:37:39 +02:00
Mikaël Capelle 89f39ef200 Bump version to 2.5.2rc2. (#2084) 2024-08-01 10:58:31 +02:00
Mikaël Capelle acd201157d Move slot connections to avoid issue when starting MO2 to download a NXM link. (#2082)
* Move slot connections to avoid issue when starting MO2 to download a NXM link.
2024-08-01 10:40:44 +02:00
Mikaël Capelle f11925ab88 Fix FIXABLE status for ModDataChecker. (#2080)
* Fix FIXABLE status for ModDataChecker.
* Fix issue with registerFeature() without games.
2024-07-28 14:39:20 +02:00
Mikaël Capelle 2395c13c25 Bump version to 2.5.2rc1. (#2078) 2024-07-21 16:49:38 +02:00
Mikaël Capelle cc92f6cf88 Set default style to 'windowsvista' and allow stylesheet to modify base style (#2072)
* Set 'windowsvista' as the default style.
* Allow stylesheet to modify base style.
2024-07-19 09:49:53 +02:00
Jeremy Rimpo 193442e773 Archive preview support (#2056)
* Support for archive file previews
- Should account for alternates
- Extracts files and requests preview from plugins that claim support
2024-07-11 16:46:29 +02:00
Jeremy Rimpo a4f6298111 Parse unmanaged file location when creating ModInfoForeign (#2053)
* Parse unmanaged file location when creating ModInfoForeign
- Fixes issues with secondaryDataDirectories
* Revert SF memory address changes
- CCC implementation prevents the need to determine core plugin LO by dependency chains
* The light flag wins over the medium flag
- I had expected the opposite, but apparently the light flag still wins if both are set. This shouldn't really happen but it's possible, even with the CK
* Update display to account for multi-flagged plugins
- Show both icons, warn if both set
2024-06-22 08:31:26 +02:00
Twinki d3b647ab2b Use new Skip File & Skip Directory in usvfs (#2033)
* Use new Skip File & Skip Directory in usvfs.

# Motivations
https://github.com/ModOrganizer2/usvfs/pull/61 Highlights some reasons why the ability to skip files & directories would be beneficial

# Modifications
- Add two new settings, `skip_file_suffixes` and `skip_directories`
- Wire the two new settings up to usvfs
- Add two new buttons to the `Workarounds` dialog, one to adjust Skip File Suffixes and another for Skip Directories, both buttons act nearly identical to the Executable Blacklist button
- Add a new grouping in the `Workarounds` dialog box that contains the usvfs buttons to keep the dialog a tad organized
2024-06-15 18:51:47 +02:00
Jeremy Rimpo 6d08d434a8 Add medium plugin support (Starfield) (#2048)
* Add medium plugin support (Starfield)
- Coopt the overlay support for the new 'medium' / ESH plugin flag
- Update various displays to include ESH info
* Rework address display for SF weirdness
* Fix core ESH display
2024-06-13 17:51:09 +02:00
Mikaël Capelle 3d8bfdd1d4 Fix manifest for debug mode. (#2047) 2024-06-10 20:46:00 +02:00
RJ fff03d34e2 PluginList::refresh speedup (#2046)
* Move loadOrderMechanism out of loop
* PluginList::refresh reduce number of loops
2024-06-10 15:55:32 +02:00
Mikaël Capelle 88c386d74d Refactoring of game features for better management. (#2043) 2024-06-09 12:18:17 +02:00
Mikaël Capelle c43535f5bc Update for new USVFS function scheme. (#2044)
* Update for new USVFS function scheme.
* Fix call to usvfsVersionString().
* Move USVFS to mo2 dependencies instead of third party in CI.
2024-06-09 11:00:13 +02:00
Jeroen Ruigrok van der Werven 3d18f0cd38 Only list instance directory with a MO INI file (#1965) 2024-06-08 07:56:25 +02:00
Mikaël Capelle a11617ff65 Use MO2 formatting action. (#2032) 2024-05-26 12:20:02 +02:00
Mikaël CapelleandTwinki 1871f32a8d Use lootGameName() and displayGameName() in initial places. (#2030)
* Use `displayGameName()` in create instance dialogs & the main window, this doesn't cover all places `gameName()` was being used for purely display reasons, but it covers the bulk.
* Use `lootGameName()` instead of `gameShortName()` for LOOT cli initiation.
* Use game display name in status bar.

---------

Co-authored-by: Twinki <Twinki@users.noreply.github.com>
2024-05-26 11:12:08 +02:00
Mikaël Capelle fa82d1cca1 Switch from fmtlib to std::format. (#2031)
* Switch from fmtlib to std::format.
* Remove libffi from dependencies in Github action.
2024-05-25 13:14:46 +02:00
Jeremy Rimpo 9ee4c5afe1 Disable ESLs when no ESL support (#2026)
* Show ESL plugins but disable and warn if unsupported
2024-05-19 09:40:47 +02:00
RJandLiderate a1d199134d Fixes for stylesheets that use transparent backgrounds (#2029)
* Remove uneeded "Fix" text

* Don't set Qt::WA_OpaquePaintEvent when imagesThumbnails has transparent base

---------

Co-authored-by: Liderate <Liderate@users.noreply.github.com>
2024-05-19 09:40:17 +02:00
RJandLiderate 16423b2682 Directly open logs and crashDumps folders from diagnostics settings (#2028)
Co-authored-by: Liderate <Liderate@users.noreply.github.com>
2024-05-19 09:15:50 +02:00
Mikaël Capelle 5adb1ec43d Merge pull request #1962 from ashemedai/ignore_cache_instance
Ignore Qt-created 'cache' directory in list of instances.
2024-01-01 12:47:18 +01:00
Jeroen Ruigrok van der Werven 4472df8656 Ignore Qt-created 'cache' directory 2023-12-31 13:35:26 +01:00
75 changed files with 2405 additions and 1382 deletions
+3 -3
View File
@@ -15,6 +15,6 @@ jobs:
with:
qt-modules: qtpositioning qtwebchannel qtwebengine qtwebsockets
mo2-third-parties:
7z zlib fmt gtest libbsarch libloot openssl libffi bzip2 python lz4 spdlog
boost boost-di sip pyqt pybind11 ss licenses explorerpp usvfs
mo2-dependencies: cmake_common uibase githubpp bsatk esptk archive lootcli game_gamebryo
7z zlib gtest libbsarch libloot openssl bzip2 python lz4 spdlog
boost boost-di sip pyqt pybind11 ss licenses explorerpp DirectXTex
mo2-dependencies: usvfs cmake_common uibase githubpp bsatk esptk archive lootcli game_gamebryo
+3 -3
View File
@@ -10,8 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run clang-format
uses: jidicula/clang-format-action@v4.11.0
- name: Check format
uses: ModOrganizer2/check-formatting-action@master
with:
clang-format-version: "15"
check-path: "."
exclude-regex: "third-party"
+10 -2
View File
@@ -7,12 +7,18 @@ mo2_configure_executable(organizer
EXTRA_TRANSLATIONS ${MO2_SUPER_PATH}/game_gamebryo/src ${MO2_UIBASE_PATH}/src
PRIVATE_DEPENDS
uibase githubpp bsatk esptk archive usvfs lootcli boost::program_options
Qt::WebEngineWidgets Qt::WebSockets)
DirectXTex libbsarch Qt::WebEngineWidgets Qt::WebSockets)
target_link_libraries(organizer PUBLIC Shlwapi)
target_include_directories(organizer PUBLIC ${DDS_ROOT})
mo2_install_target(organizer)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt5"
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt6"
DESTINATION bin/dlls
CONFIGURATIONS Release RelWithDebInfo
RENAME dlls.manifest)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.debug.qt6"
DESTINATION bin/dlls
CONFIGURATIONS Debug
RENAME dlls.manifest)
install(DIRECTORY
@@ -55,6 +61,7 @@ mo2_add_filter(NAME src/core GROUPS
nexusinterface
nxmaccessmanager
organizercore
game_features
plugincontainer
apiuseraccount
processrunner
@@ -196,6 +203,7 @@ mo2_add_filter(NAME src/profiles GROUPS
mo2_add_filter(NAME src/proxies GROUPS
downloadmanagerproxy
gamefeaturesproxy
modlistproxy
organizerproxy
pluginlistproxy
+2
View File
@@ -28,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QtGlobal>
using namespace MOBase;
ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets,
QWidget* parent)
: TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog)
+3 -4
View File
@@ -24,9 +24,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "tutorabledialog.h"
#include <QObject>
class QString;
class QWidget;
#include <QString>
#include <QWidget>
#include <set>
@@ -49,7 +48,7 @@ public:
* @param missingPlugins a map containing missing plugins that need to be activated
* @param parent ... Defaults to 0.
**/
explicit ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets,
explicit ActivateModsDialog(MOBase::SaveGameInfo::MissingAssets const& missingAssets,
QWidget* parent = 0);
~ActivateModsDialog();
+11 -15
View File
@@ -74,7 +74,7 @@ void CategoryFactory::loadCategories()
bool ok = false;
int temp = iter->toInt(&ok);
if (!ok) {
log::error(tr("invalid category id {}").toStdString(), iter->constData());
log::error(tr("invalid category id {0}"), iter->constData());
}
nexusCats.push_back(NexusCategory("Unknown", temp));
}
@@ -84,8 +84,7 @@ void CategoryFactory::loadCategories()
int id = cells[0].toInt(&cell0Ok);
int parentID = cells[3].trimmed().toInt(&cell3Ok);
if (!cell0Ok || !cell3Ok) {
log::error(tr("invalid category line {}: {}").toStdString(), lineNum,
line.constData());
log::error(tr("invalid category line {0}: {1}"), lineNum, line.constData());
}
addCategory(id, QString::fromUtf8(cells[1].constData()), nexusCats, parentID);
} else if (cells.count() == 3) {
@@ -94,14 +93,13 @@ void CategoryFactory::loadCategories()
int id = cells[0].toInt(&cell0Ok);
int parentID = cells[2].trimmed().toInt(&cell3Ok);
if (!cell0Ok || !cell3Ok) {
log::error(tr("invalid category line {}: {}").toStdString(), lineNum,
line.constData());
log::error(tr("invalid category line {0}: {1}"), lineNum, line.constData());
}
addCategory(id, QString::fromUtf8(cells[1].constData()),
std::vector<NexusCategory>(), parentID);
} else {
log::error(tr("invalid category line {}: {} ({} cells)").toStdString(), lineNum,
log::error(tr("invalid category line {0}: {1} ({2} cells)"), lineNum,
line.constData(), cells.count());
}
}
@@ -122,19 +120,17 @@ void CategoryFactory::loadCategories()
bool ok = false;
int nexID = nexCells[2].toInt(&ok);
if (!ok) {
log::error(tr("invalid nexus ID {}").toStdString(),
nexCells[2].constData());
log::error(tr("invalid nexus ID {}"), nexCells[2].constData());
}
int catID = nexCells[0].toInt(&ok);
if (!ok) {
log::error(tr("invalid category id {}").toStdString(),
nexCells[0].constData());
log::error(tr("invalid category id {}"), nexCells[0].constData());
}
m_NexusMap.insert_or_assign(nexID, NexusCategory(nexName, nexID));
m_NexusMap.at(nexID).setCategoryID(catID);
} else {
log::error(tr("invalid nexus category line {}: {} ({} cells)").toStdString(),
lineNum, nexLine.constData(), nexCells.count());
log::error(tr("invalid nexus category line {0}: {1} ({2} cells)"), lineNum,
nexLine.constData(), nexCells.count());
}
}
}
@@ -394,7 +390,7 @@ bool CategoryFactory::isDescendantOfImpl(int id, int parentID,
return isDescendantOfImpl(m_Categories[index].parentID(), parentID, seen);
}
} else {
log::warn(tr("{} is no valid category id").toStdString(), id);
log::warn(tr("{} is no valid category id"), id);
return false;
}
}
@@ -513,11 +509,11 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const
auto result = m_NexusMap.find(nexusID);
if (result != m_NexusMap.end()) {
if (m_IDMap.count(result->second.categoryID())) {
log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID,
log::debug(tr("nexus category id {0} maps to internal {1}"), nexusID,
m_IDMap.at(result->second.categoryID()));
return m_IDMap.at(result->second.categoryID());
}
}
log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID);
log::debug(tr("nexus category id {} not mapped"), nexusID);
return 0U;
}
+18 -14
View File
@@ -296,7 +296,8 @@ void GamePage::select(IPluginGame* game, const QString& dir)
// ask the user
const auto path = QFileDialog::getExistingDirectory(
&m_dlg, QObject::tr("Find game installation for %1").arg(game->gameName()));
&m_dlg,
QObject::tr("Find game installation for %1").arg(game->displayGameName()));
if (path.isEmpty()) {
// cancelled
@@ -388,7 +389,7 @@ void GamePage::warnUnrecognized(const QString& path)
// put the list of supported games in the details textbox
QString supportedGames;
for (auto* game : sortedGamePlugins()) {
supportedGames += game->gameName() + "\n";
supportedGames += game->displayGameName() + "\n";
}
QMessageBox dlg(&m_dlg);
@@ -417,7 +418,7 @@ std::vector<IPluginGame*> GamePage::sortedGamePlugins() const
// natsort
std::sort(v.begin(), v.end(), [](auto* a, auto* b) {
return (naturalCompare(a->gameName(), b->gameName()) < 0);
return (naturalCompare(a->displayGameName(), b->displayGameName()) < 0);
});
return v;
@@ -469,7 +470,7 @@ void GamePage::updateButton(Game* g)
return;
}
g->button->setText(g->game->gameName().replace("&", "&&"));
g->button->setText(g->game->displayGameName().replace("&", "&&"));
g->button->setIcon(g->game->gameIcon());
if (g->installed) {
@@ -570,7 +571,8 @@ void GamePage::fillList()
continue;
}
if (!m_filter.matches(g->game->gameName())) {
if (!m_filter.matches(g->game->gameName()) &&
!m_filter.matches(g->game->displayGameName())) {
// filtered out
continue;
}
@@ -667,9 +669,10 @@ bool GamePage::confirmMicrosoftStore(const QString& path, IPluginGame* game)
"Organizer"
" and will not work properly.")
.arg(path))
.button({game ? QObject::tr("Use this folder for %1").arg(game->gameName())
: QObject::tr("Use this folder"),
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
.button(
{game ? QObject::tr("Use this folder for %1").arg(game->displayGameName())
: QObject::tr("Use this folder"),
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
.button({QObject::tr("Cancel"), QMessageBox::Cancel})
.exec();
@@ -688,8 +691,8 @@ bool GamePage::confirmUnknown(const QString& path, IPluginGame* game)
"bold;\">%2</span> or "
"for any other game Mod Organizer can manage.")
.arg(path)
.arg(game->gameName()))
.button({QObject::tr("Use this folder for %1").arg(game->gameName()),
.arg(game->displayGameName()))
.button({QObject::tr("Use this folder for %1").arg(game->displayGameName()),
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
.button({QObject::tr("Cancel"), QMessageBox::Cancel})
.exec();
@@ -711,11 +714,12 @@ IPluginGame* GamePage::confirmOtherGame(const QString& path, IPluginGame* select
"not "
"<span style=\"white-space: nowrap; font-weight: bold;\">%3</span>.")
.arg(path)
.arg(guessedGame->gameName())
.arg(selectedGame->gameName()))
.button({QObject::tr("Manage %1 instead").arg(guessedGame->gameName()),
.arg(guessedGame->displayGameName())
.arg(selectedGame->displayGameName()))
.button({QObject::tr("Manage %1 instead").arg(guessedGame->displayGameName()),
QMessageBox::Ok})
.button({QObject::tr("Use this folder for %1").arg(selectedGame->gameName()),
.button({QObject::tr("Use this folder for %1")
.arg(selectedGame->displayGameName()),
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
.button({QObject::tr("Cancel"), QMessageBox::Cancel})
.exec();
+7 -7
View File
@@ -16,13 +16,13 @@ QString UnmanagedModName();
DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent,
Ui::MainWindow* mwui)
: m_core(core), m_pluginContainer(pc),
m_parent(parent), ui{mwui->tabWidget,
mwui->dataTab,
mwui->dataTabRefresh,
mwui->dataTree,
mwui->dataTabShowOnlyConflicts,
mwui->dataTabShowFromArchives},
: m_core(core), m_pluginContainer(pc), m_parent(parent),
ui{mwui->tabWidget,
mwui->dataTab,
mwui->dataTabRefresh,
mwui->dataTree,
mwui->dataTabShowOnlyConflicts,
mwui->dataTabShowFromArchives},
m_needUpdate(true)
{
m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree));
+16 -16
View File
@@ -22,9 +22,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "shared/filesorigin.h"
#include "envfs.h"
#include "game_features.h"
#include "iplugingame.h"
#include "modinfo.h"
#include "modinfodialogfwd.h"
#include "organizercore.h"
#include "report.h"
#include "settings.h"
#include "shared/util.h"
@@ -138,7 +140,7 @@ void dumpStats(std::vector<DirectoryStats>& stats)
if (run == 0) {
std::ofstream out(file, std::ios::out | std::ios::trunc);
out << fmt::format("what,run,{}", DirectoryStats::csvHeader()) << "\n";
out << std::format("what,run,{}", DirectoryStats::csvHeader()) << "\n";
}
std::sort(stats.begin(), stats.end(), [](auto&& a, auto&& b) {
@@ -150,17 +152,17 @@ void dumpStats(std::vector<DirectoryStats>& stats)
DirectoryStats total;
for (const auto& s : stats) {
out << fmt::format("{},{},{}", s.mod, run, s.toCsv()) << "\n";
out << std::format("{},{},{}", s.mod, run, s.toCsv()) << "\n";
total += s;
}
out << fmt::format("total,{},{}", run, total.toCsv()) << "\n";
out << std::format("total,{},{}", run, total.toCsv()) << "\n";
++run;
}
DirectoryRefresher::DirectoryRefresher(std::size_t threadCount)
: m_threadCount(threadCount), m_lastFileCount(0)
DirectoryRefresher::DirectoryRefresher(OrganizerCore* core, std::size_t threadCount)
: m_Core(*core), m_threadCount(threadCount), m_lastFileCount(0)
{}
DirectoryEntry* DirectoryRefresher::stealDirectoryStructure()
@@ -204,11 +206,9 @@ void DirectoryRefresher::addModBSAToStructure(DirectoryEntry* root,
const QString& directory,
const QStringList& archives)
{
const IPluginGame* game = qApp->property("managed_game").value<IPluginGame*>();
QStringList loadOrder;
GamePlugins* gamePlugins = game->feature<GamePlugins>();
auto gamePlugins = m_Core.gameFeatures().gameFeature<GamePlugins>();
if (gamePlugins) {
loadOrder = gamePlugins->getLoadOrder();
}
@@ -320,6 +320,7 @@ void DirectoryRefresher::addModToStructure(DirectoryEntry* directoryStructure,
struct ModThread
{
GameFeatures* gameFeatures;
DirectoryRefreshProgress* progress = nullptr;
DirectoryEntry* ds = nullptr;
std::wstring modName;
@@ -355,10 +356,8 @@ struct ModThread
ds->addFromOrigin(walker, modName, path, prio, *stats);
if (Settings::instance().archiveParsing()) {
const IPluginGame* game = qApp->property("managed_game").value<IPluginGame*>();
QStringList loadOrder;
GamePlugins* gamePlugins = game->feature<GamePlugins>();
auto gamePlugins = gameFeatures->gameFeature<GamePlugins>();
if (gamePlugins) {
loadOrder = gamePlugins->getLoadOrder();
}
@@ -420,11 +419,12 @@ void DirectoryRefresher::addMultipleModsFilesToStructure(
} else {
auto& mt = g_threads.request();
mt.progress = progress;
mt.ds = directoryStructure;
mt.modName = e.modName.toStdWString();
mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString();
mt.prio = prio;
mt.gameFeatures = &m_Core.gameFeatures();
mt.progress = progress;
mt.ds = directoryStructure;
mt.modName = e.modName.toStdWString();
mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString();
mt.prio = prio;
mt.archives.clear();
for (auto&& a : e.archives) {
+5 -1
View File
@@ -30,6 +30,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <tuple>
#include <vector>
class OrganizerCore;
/**
* @brief used to asynchronously generate the virtual view of the combined data
*directory
@@ -54,7 +56,7 @@ public:
int priority;
};
DirectoryRefresher(std::size_t threadCount);
DirectoryRefresher(OrganizerCore* core, std::size_t threadCount);
/**
* @brief retrieve the updated directory structure
@@ -146,6 +148,8 @@ signals:
void refreshed();
private:
OrganizerCore& m_Core;
std::vector<EntryInfo> m_Mods;
std::set<QString> m_EnabledArchives;
std::unique_ptr<MOShared::DirectoryEntry> m_Root;
-46
View File
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
<file name="7z.dll"/>
<file name="archive.dll"/>
<file name="d3dcompiler_47.dll"/>
<file name="libbsarch.dll"/>
<file name="libcrypto-1_1-x64.dll"/>
<file name="libEGL.dll"/>
<file name="libGLESV2.dll"/>
<file name="liblz4.dll"/>
<file name="libssl-1_1-x64.dll"/>
<file name="opengl32sw.dll"/>
<file name="Qt6Concurrent.dll"/>
<file name="Qt6Core.dll"/>
<file name="Qt6Core5Compat.dll"/>
<file name="Qt6Gui.dll"/>
<file name="Qt6Network.dll"/>
<file name="Qt6OpenGL.dll"/>
<file name="Qt6OpenGLWidgets.dll"/>
<file name="Qt6Positioning.dll"/>
<file name="Qt6PrintSupport.dll"/>
<file name="Qt6Qml.dll"/>
<file name="Qt6QmlLocalStorage.dll"/>
<file name="Qt6QmlModels.dll"/>
<file name="Qt6QmlWorkerScript.dll"/>
<file name="Qt6QmlXmlListModel.dll"/>
<file name="Qt6Quick.dll"/>
<file name="Qt6QuickControls2.dll"/>
<file name="Qt6QuickControls2Impl.dll"/>
<file name="Qt6QuickDialogs2.dll"/>
<file name="Qt6QuickDialogs2QuickImpl.dll"/>
<file name="Qt6QuickDialogs2Utils.dll"/>
<file name="Qt6QuickLayouts.dll"/>
<file name="Qt6QuickParticles.dll"/>
<file name="Qt6QuickShapes.dll"/>
<file name="Qt6QuickTemplates2.dll"/>
<file name="Qt6QuickWidgets.dll"/>
<file name="Qt6Sql.dll"/>
<file name="Qt6Svg.dll"/>
<file name="Qt6WebChannel.dll"/>
<file name="Qt6WebEngineCore.dll"/>
<file name="Qt6WebEngineWidgets.dll"/>
<file name="Qt6WebSockets.dll"/>
<file name="Qt6Widgets.dll"/>
</assembly>
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86" />
<file name="7z.dll" />
<file name="archive.dll" />
<file name="d3dcompiler_47.dll" />
<file name="libbsarch.dll" />
<file name="libcrypto-3-x64.dll" />
<file name="libEGL.dll" />
<file name="libGLESV2.dll" />
<file name="liblz4.dll" />
<file name="libssl-3-x64.dll" />
<file name="opengl32sw.dll" />
<file name="Qt6Concurrentd.dll" />
<file name="Qt6Cored.dll" />
<file name="Qt6Core5Compatd.dll" />
<file name="Qt6Guid.dll" />
<file name="Qt6Networkd.dll" />
<file name="Qt6OpenGLd.dll" />
<file name="Qt6OpenGLWidgetsd.dll" />
<file name="Qt6Positioningd.dll" />
<file name="Qt6PrintSupportd.dll" />
<file name="Qt6Qmld.dll" />
<file name="Qt6QmlLocalStoraged.dll" />
<file name="Qt6QmlModelsd.dll" />
<file name="Qt6QmlWorkerScriptd.dll" />
<file name="Qt6QmlXmlListModeld.dll" />
<file name="Qt6Quickd.dll" />
<file name="Qt6QuickControls2d.dll" />
<file name="Qt6QuickControls2Impld.dll" />
<file name="Qt6QuickDialogs2d.dll" />
<file name="Qt6QuickDialogs2QuickImpld.dll" />
<file name="Qt6QuickDialogs2Utilsd.dll" />
<file name="Qt6QuickLayoutsd.dll" />
<file name="Qt6QuickParticlesd.dll" />
<file name="Qt6QuickShapesd.dll" />
<file name="Qt6QuickTemplates2d.dll" />
<file name="Qt6QuickWidgetsd.dll" />
<file name="Qt6Sqld.dll" />
<file name="Qt6Svgd.dll" />
<file name="Qt6WebChanneld.dll" />
<file name="Qt6WebEngineCored.dll" />
<file name="Qt6WebEngineWidgetsd.dll" />
<file name="Qt6WebSocketsd.dll" />
<file name="Qt6Widgetsd.dll" />
</assembly>
-46
View File
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
<file name="7z.dll"/>
<file name="archive.dll"/>
<file name="d3dcompiler_47.dll"/>
<file name="libbsarch.dll"/>
<file name="libcrypto-1_1-x64.dll"/>
<file name="libEGL.dll"/>
<file name="libGLESV2.dll"/>
<file name="liblz4.dll"/>
<file name="libssl-1_1-x64.dll"/>
<file name="opengl32sw.dll"/>
<file name="Qt6Concurrent.dll"/>
<file name="Qt6Core.dll"/>
<file name="Qt6Core5Compat.dll"/>
<file name="Qt6Gui.dll"/>
<file name="Qt6Network.dll"/>
<file name="Qt6OpenGL.dll"/>
<file name="Qt6OpenGLWidgets.dll"/>
<file name="Qt6Positioning.dll"/>
<file name="Qt6PrintSupport.dll"/>
<file name="Qt6Qml.dll"/>
<file name="Qt6QmlLocalStorage.dll"/>
<file name="Qt6QmlModels.dll"/>
<file name="Qt6QmlWorkerScript.dll"/>
<file name="Qt6QmlXmlListModel.dll"/>
<file name="Qt6Quick.dll"/>
<file name="Qt6QuickControls2.dll"/>
<file name="Qt6QuickControls2Impl.dll"/>
<file name="Qt6QuickDialogs2.dll"/>
<file name="Qt6QuickDialogs2QuickImpl.dll"/>
<file name="Qt6QuickDialogs2Utils.dll"/>
<file name="Qt6QuickLayouts.dll"/>
<file name="Qt6QuickParticles.dll"/>
<file name="Qt6QuickShapes.dll"/>
<file name="Qt6QuickTemplates2.dll"/>
<file name="Qt6QuickWidgets.dll"/>
<file name="Qt6Sql.dll"/>
<file name="Qt6Svg.dll"/>
<file name="Qt6WebChannel.dll"/>
<file name="Qt6WebEngineCore.dll"/>
<file name="Qt6WebEngineWidgets.dll"/>
<file name="Qt6WebSockets.dll"/>
<file name="Qt6Widgets.dll"/>
</assembly>
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86" />
<file name="7z.dll" />
<file name="archive.dll" />
<file name="d3dcompiler_47.dll" />
<file name="libbsarch.dll" />
<file name="libcrypto-3-x64.dll" />
<file name="libEGL.dll" />
<file name="libGLESV2.dll" />
<file name="liblz4.dll" />
<file name="libssl-3-x64.dll" />
<file name="opengl32sw.dll" />
<file name="Qt6Concurrent.dll" />
<file name="Qt6Core.dll" />
<file name="Qt6Core5Compat.dll" />
<file name="Qt6Gui.dll" />
<file name="Qt6Network.dll" />
<file name="Qt6OpenGL.dll" />
<file name="Qt6OpenGLWidgets.dll" />
<file name="Qt6Positioning.dll" />
<file name="Qt6PrintSupport.dll" />
<file name="Qt6Qml.dll" />
<file name="Qt6QmlLocalStorage.dll" />
<file name="Qt6QmlModels.dll" />
<file name="Qt6QmlWorkerScript.dll" />
<file name="Qt6QmlXmlListModel.dll" />
<file name="Qt6Quick.dll" />
<file name="Qt6QuickControls2.dll" />
<file name="Qt6QuickControls2Impl.dll" />
<file name="Qt6QuickDialogs2.dll" />
<file name="Qt6QuickDialogs2QuickImpl.dll" />
<file name="Qt6QuickDialogs2Utils.dll" />
<file name="Qt6QuickLayouts.dll" />
<file name="Qt6QuickParticles.dll" />
<file name="Qt6QuickShapes.dll" />
<file name="Qt6QuickTemplates2.dll" />
<file name="Qt6QuickWidgets.dll" />
<file name="Qt6Sql.dll" />
<file name="Qt6Svg.dll" />
<file name="Qt6WebChannel.dll" />
<file name="Qt6WebEngineCore.dll" />
<file name="Qt6WebEngineWidgets.dll" />
<file name="Qt6WebSockets.dll" />
<file name="Qt6Widgets.dll" />
</assembly>
+1 -1
View File
@@ -221,7 +221,7 @@ QVariant DownloadList::data(const QModelIndex& index, int role) const
.arg(info->modName)
.arg(m_manager.getModID(index.row()))
.arg(info->version.canonicalString())
.arg(info->description.chopped(4096));
.arg(info->description.mid(0, 4096));
}
return text;
}
+8 -4
View File
@@ -1,8 +1,12 @@
#include "envshell.h"
#include <format>
#include <log.h>
#include <utility.h>
#include <windowsx.h>
#include "envshell.h"
namespace env
{
@@ -16,7 +20,7 @@ class MenuFailed : public std::runtime_error
public:
MenuFailed(HRESULT r, const std::string& what)
: runtime_error(
fmt::format("{}, {}", what,
std::format("{}, {}", what,
QString::fromStdWString(formatSystemMessage(r)).toStdString()))
{}
};
@@ -478,7 +482,7 @@ void ShellMenu::invoke(const QPoint& p, int cmd)
const auto r = m_cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info);
if (FAILED(r)) {
throw MenuFailed(r, fmt::format("InvokeCommand failed, verb={}", cmd));
throw MenuFailed(r, std::format("InvokeCommand failed, verb={}", cmd));
}
}
@@ -568,7 +572,7 @@ void ShellMenuCollection::exec(const QPoint& pos)
}
if (!m_active) {
log::debug("SMC: command {} selected without active submenu");
log::debug("SMC: command {} selected without active submenu", cmd);
return;
}
+2 -6
View File
@@ -246,16 +246,12 @@ void FileTree::activate(FileTreeItem* item)
return;
}
if (item->isFromArchive()) {
log::warn("cannot activate file from archive '{}'", item->filename());
return;
}
const auto tryPreview = m_core.settings().interface().doubleClicksOpenPreviews();
if (tryPreview) {
const QFileInfo fi(item->realPath());
if (m_plugins.previewGenerator().previewSupported(fi.suffix().toLower())) {
if (m_plugins.previewGenerator().previewSupported(fi.suffix().toLower(),
item->isFromArchive())) {
preview(item);
return;
}
+348
View File
@@ -0,0 +1,348 @@
#include "game_features.h"
#include <algorithm>
#include <QTimer>
#include "iplugingame.h"
#include "log.h"
#include "bsainvalidation.h"
#include "dataarchives.h"
#include "gameplugins.h"
#include "localsavegames.h"
#include "organizercore.h"
#include "plugincontainer.h"
#include "savegameinfo.h"
#include "scriptextender.h"
#include "unmanagedmods.h"
using namespace MOBase;
// avoid updating features more than once every 100ms
constexpr std::chrono::milliseconds UPDATE_QUEUE_TIMER{100};
// specific type index for checker and content that behaves differently
const std::type_index ModDataCheckerIndex{typeid(ModDataChecker)};
const std::type_index ModDataContentIndex{typeid(ModDataContent)};
class GameFeatures::CombinedModDataChecker : public ModDataChecker
{
std::vector<std::shared_ptr<ModDataChecker>> m_modDataCheckers;
mutable std::shared_ptr<ModDataChecker> m_fixer = nullptr;
public:
void setCheckers(std::vector<std::shared_ptr<ModDataChecker>> checkers)
{
m_modDataCheckers = std::move(checkers);
m_fixer = nullptr;
}
bool isValid() const { return !m_modDataCheckers.empty(); }
CheckReturn
dataLooksValid(std::shared_ptr<const MOBase::IFileTree> fileTree) const override
{
m_fixer = nullptr;
// go through the available mod-data checker, if any returns valid, we also
// return valid, otherwise, return the first one that is fixable
for (const auto& modDataChecker : m_modDataCheckers) {
auto check = modDataChecker->dataLooksValid(fileTree);
switch (check) {
case CheckReturn::FIXABLE:
// only update fixer if there is not one with higher priority
if (!m_fixer) {
m_fixer = modDataChecker;
}
break;
case CheckReturn::VALID:
// clear fixer if one were found before and return VALID, not mandatory
// but cleaner
m_fixer = nullptr;
return CheckReturn::VALID;
case CheckReturn::INVALID:
break;
}
}
return m_fixer ? CheckReturn::FIXABLE : CheckReturn::INVALID;
}
std::shared_ptr<MOBase::IFileTree>
fix(std::shared_ptr<MOBase::IFileTree> fileTree) const override
{
if (m_fixer) {
return m_fixer->fix(fileTree);
}
return nullptr;
}
};
class GameFeatures::CombinedModDataContent : public ModDataContent
{
// store the ModDataContent and the offset to add to the content
std::vector<std::pair<std::shared_ptr<ModDataContent>, int>> m_modDataContents;
std::vector<Content> m_allContents;
public:
bool isValid() const { return !m_modDataContents.empty(); }
void setContents(std::vector<std::shared_ptr<ModDataContent>> modDataContents)
{
m_modDataContents.clear();
m_modDataContents.reserve(modDataContents.size());
m_allContents.clear();
// update all contents and offsets
std::size_t offset = 0;
for (auto& modDataContent : modDataContents) {
m_modDataContents.emplace_back(modDataContent, static_cast<int>(offset));
// add to the list of contents
auto contents = modDataContent->getAllContents();
m_allContents.insert(m_allContents.end(),
std::make_move_iterator(contents.begin()),
std::make_move_iterator(contents.end()));
// increase offset for next mod data content
offset += contents.size();
}
}
std::vector<Content> getAllContents() const override { return m_allContents; }
std::vector<int>
getContentsFor(std::shared_ptr<const MOBase::IFileTree> fileTree) const
{
std::vector<int> contentsFor;
for (auto& modDataContent : m_modDataContents) {
auto contentsForFrom = modDataContent.first->getContentsFor(fileTree);
std::transform(contentsForFrom.begin(), contentsForFrom.end(),
std::back_inserter(contentsFor),
[offset = modDataContent.second](auto content) {
return content + offset;
});
}
return contentsFor;
}
};
GameFeatures::GameFeatures(OrganizerCore* core, PluginContainer* plugins)
: m_pluginContainer(*plugins),
m_modDataChecker(std::make_unique<CombinedModDataChecker>()),
m_modDataContent(std::make_unique<CombinedModDataContent>())
{
// can be nullptr since the plugin container can be initialized with a Core (e.g.,
// on first MO2 start)
if (core) {
connect(core, &OrganizerCore::managedGameChanged, [this] {
updateCurrentFeatures();
});
}
auto updateFeatures = [this] {
QTimer::singleShot(UPDATE_QUEUE_TIMER, [this] {
updateCurrentFeatures();
});
};
connect(plugins, &PluginContainer::pluginEnabled, updateFeatures);
connect(plugins, &PluginContainer::pluginDisabled, updateFeatures);
connect(plugins, &PluginContainer::pluginRegistered, updateFeatures);
connect(plugins, &PluginContainer::pluginUnregistered,
[this, updateFeatures](MOBase::IPlugin* plugin) {
// remove features from the current plugin
for (auto& [_, features] : m_allFeatures) {
features.erase(std::remove_if(features.begin(), features.end(),
[plugin](const auto& feature) {
return feature.plugin() == plugin;
}),
features.end());
}
// update current features
updateFeatures();
});
}
GameFeatures::~GameFeatures() {}
GameFeatures::CombinedModDataChecker& GameFeatures::modDataChecker() const
{
return dynamic_cast<CombinedModDataChecker&>(*m_modDataChecker);
}
GameFeatures::CombinedModDataContent& GameFeatures::modDataContent() const
{
return dynamic_cast<CombinedModDataContent&>(*m_modDataContent);
}
void GameFeatures::updateCurrentFeatures(std::type_index const& index)
{
auto& features = m_allFeatures[index];
m_currentFeatures[index].clear();
// this can occur when starting MO2, just wait for the next update
if (!m_pluginContainer.managedGame()) {
return;
}
for (const auto& dataFeature : features) {
// registering plugin is disabled
if (!m_pluginContainer.isEnabled(dataFeature.plugin())) {
continue;
}
// games does not match
if (!dataFeature.games().isEmpty() &&
!dataFeature.games().contains(m_pluginContainer.managedGame()->gameName())) {
continue;
}
m_currentFeatures[index].push_back(dataFeature.feature());
}
// update mod data checker
if (index == ModDataCheckerIndex) {
auto& currentCheckers = m_currentFeatures[ModDataCheckerIndex];
std::vector<std::shared_ptr<ModDataChecker>> checkers;
checkers.reserve(currentCheckers.size());
std::transform(currentCheckers.begin(), currentCheckers.end(),
std::back_inserter(checkers), [](auto const& checker) {
return std::dynamic_pointer_cast<ModDataChecker>(checker);
});
modDataChecker().setCheckers(std::move(checkers));
emit modDataCheckerUpdated(gameFeature<ModDataChecker>().get());
}
// update mod data content
if (index == ModDataContentIndex) {
auto& currentContents = m_currentFeatures[ModDataContentIndex];
std::vector<std::shared_ptr<ModDataContent>> contents;
contents.reserve(currentContents.size());
std::transform(currentContents.begin(), currentContents.end(),
std::back_inserter(contents), [](auto const& checker) {
return std::dynamic_pointer_cast<ModDataContent>(checker);
});
modDataContent().setContents(std::move(contents));
emit modDataContentUpdated(gameFeature<ModDataContent>().get());
}
}
void GameFeatures::updateCurrentFeatures()
{
// TODO: this completely refilters everything currently, which should be ok since
// this should only be triggered by function that are uncommon (enabling/disabling
// plugin or changing the game), it should be possible to filter more finely by
// maintaining more information (e.g., only removing features from current when a
// plugin is disabled or unregistered)
//
for (auto& [info, _] : m_allFeatures) {
updateCurrentFeatures(info);
}
}
bool GameFeatures::registerGameFeature(MOBase::IPlugin* plugin,
QStringList const& games,
std::shared_ptr<MOBase::GameFeature> feature,
int priority)
{
auto& features = m_allFeatures[feature->typeInfo()];
if (std::find_if(features.begin(), features.end(), [&feature](const auto& data) {
return data.feature() == feature;
}) != features.end()) {
log::error("cannot register feature multiple time");
return false;
}
std::decay_t<decltype(features)>::iterator it;
if (dynamic_cast<IPluginGame*>(plugin)) {
it = features.end();
} else {
it = std::lower_bound(features.begin(), features.end(), priority,
[](const auto& feature, int priority) {
return feature.priority() > priority;
});
}
features.emplace(it, feature, plugin, games, std::numeric_limits<int>::min());
// TODO: only update if relevant
updateCurrentFeatures(feature->typeInfo());
return true;
}
// unregister game features
//
bool GameFeatures::unregisterGameFeature(std::shared_ptr<MOBase::GameFeature> feature)
{
bool removed = false;
for (auto& [_, features] : m_allFeatures) {
auto it =
std::find_if(features.begin(), features.end(), [&feature](const auto& data) {
return data.feature() == feature;
});
// the feature can only exist for one kind of features and cannot be duplicated
if (it != features.end()) {
features.erase(it);
removed = true;
break;
}
}
if (removed) {
updateCurrentFeatures();
}
return removed;
}
int GameFeatures::unregisterGameFeatures(MOBase::IPlugin* plugin,
std::type_info const& info)
{
auto& features = m_allFeatures[info];
const auto initialSize = features.size();
features.erase(std::remove_if(features.begin(), features.end(),
[plugin](const auto& feature) {
return feature.plugin() == plugin;
}),
features.end());
const int removed = features.size() - initialSize;
if (removed) {
updateCurrentFeatures();
}
return removed;
}
std::shared_ptr<GameFeature> GameFeatures::gameFeature(std::type_info const& info) const
{
if (info == ModDataCheckerIndex) {
return modDataChecker().isValid() ? m_modDataChecker : nullptr;
}
if (info == ModDataContentIndex) {
return modDataContent().isValid() ? m_modDataContent : nullptr;
}
auto it = m_currentFeatures.find(info);
if (it == m_currentFeatures.end() || it->second.empty()) {
return nullptr;
}
return it->second.front();
}
+117
View File
@@ -0,0 +1,117 @@
#ifndef MO2_GAME_FEATURES_H
#define MO2_GAME_FEATURES_H
#include <any>
#include <memory>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#include <QObject>
#include "game_feature.h"
#include "moddatachecker.h"
#include "moddatacontent.h"
namespace MOBase
{
class IPlugin;
class IPluginGame;
} // namespace MOBase
class OrganizerCore;
class PluginContainer;
/**
* Class managing game features, either registered or from the game plugin.
*/
class GameFeatures : public QObject
{
Q_OBJECT
public:
/**
*
*/
GameFeatures(OrganizerCore* core, PluginContainer* plugins);
~GameFeatures();
// register game features
//
bool registerGameFeature(MOBase::IPlugin* plugin, QStringList const& games,
std::shared_ptr<MOBase::GameFeature> feature, int priority);
// unregister game features
//
bool unregisterGameFeature(std::shared_ptr<MOBase::GameFeature> feature);
int unregisterGameFeatures(MOBase::IPlugin* plugin, std::type_info const& info);
// retrieve a game feature
//
template <class T>
std::shared_ptr<T> gameFeature() const
{
return std::dynamic_pointer_cast<T>(gameFeature(typeid(T)));
}
signals:
void modDataCheckerUpdated(const MOBase::ModDataChecker* check);
void modDataContentUpdated(const MOBase::ModDataContent* check);
private:
friend class GameFeaturesProxy;
class GameFeatureWithData
{
// feature
std::shared_ptr<MOBase::GameFeature> m_feature;
// plugin that registered the feature
MOBase::IPlugin* m_plugin;
// games this plugin applies to - empty list indicates all games
QStringList m_games;
// priority of the plugin
int m_priority;
public:
GameFeatureWithData(std::shared_ptr<MOBase::GameFeature> feature,
MOBase::IPlugin* plugin, QStringList games, int priority)
: m_feature(feature), m_plugin(plugin), m_games(games), m_priority(priority)
{}
const auto& feature() const { return m_feature; }
auto* const plugin() const { return m_plugin; }
const auto& games() const { return m_games; }
auto priority() const { return m_priority; }
};
// retrieve a game feature from info
//
std::shared_ptr<MOBase::GameFeature> gameFeature(std::type_info const& index) const;
// update current features by filtering
//
void updateCurrentFeatures(std::type_index const& index);
void updateCurrentFeatures();
// implementation details
class CombinedModDataChecker;
class CombinedModDataContent;
CombinedModDataChecker& modDataChecker() const;
CombinedModDataContent& modDataContent() const;
PluginContainer& m_pluginContainer;
std::unordered_map<std::type_index, std::vector<GameFeatureWithData>> m_allFeatures;
std::unordered_map<std::type_index, std::vector<std::shared_ptr<MOBase::GameFeature>>>
m_currentFeatures;
std::shared_ptr<CombinedModDataChecker> m_modDataChecker;
std::shared_ptr<CombinedModDataContent> m_modDataContent;
};
#endif
+48
View File
@@ -0,0 +1,48 @@
#include "gamefeaturesproxy.h"
#include "game_features.h"
#include "organizerproxy.h"
GameFeaturesProxy::GameFeaturesProxy(OrganizerProxy* coreProxy,
GameFeatures& gameFeatures)
: m_CoreProxy(*coreProxy), m_Features(gameFeatures)
{}
bool GameFeaturesProxy::registerFeature(QStringList const& games,
std::shared_ptr<MOBase::GameFeature> feature,
int priority, bool replace)
{
if (replace) {
m_Features.unregisterGameFeatures(m_CoreProxy.plugin(), feature->typeInfo());
}
return m_Features.registerGameFeature(m_CoreProxy.plugin(), games, feature, priority);
}
bool GameFeaturesProxy::registerFeature(MOBase::IPluginGame* game,
std::shared_ptr<MOBase::GameFeature> feature,
int priority, bool replace)
{
return registerFeature({game->gameName()}, feature, priority, replace);
}
bool GameFeaturesProxy::registerFeature(std::shared_ptr<MOBase::GameFeature> feature,
int priority, bool replace)
{
return registerFeature(QStringList(), feature, priority, replace);
}
bool GameFeaturesProxy::unregisterFeature(std::shared_ptr<MOBase::GameFeature> feature)
{
return m_Features.unregisterGameFeature(feature);
}
std::shared_ptr<MOBase::GameFeature>
GameFeaturesProxy::gameFeatureImpl(std::type_info const& info) const
{
return m_Features.gameFeature(info);
}
int GameFeaturesProxy::unregisterFeaturesImpl(std::type_info const& info)
{
return m_Features.unregisterGameFeatures(m_CoreProxy.plugin(), info);
}

Some files were not shown because too many files have changed in this diff Show More