Compare commits

...
Author SHA1 Message Date
pre-commit-ci[bot] efe2a02d5d [pre-commit.ci] Pre-commit autoupdate. (#2421)
updates:
- [github.com/pre-commit/mirrors-clang-format: v22.1.2 → v22.1.5](https://github.com/pre-commit/mirrors-clang-format/compare/v22.1.2...v22.1.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-08 15:58:05 +02:00
Mikaël Capelle a931277b2b Update VCPKG registries to use 7z 26.01. (#2413) 2026-06-22 20:20:18 +02:00
Mikaël Capelle 4907704797 Send local saves to recycle bin when switching from global to local saves instead of deleting them directly. (#2401) 2026-06-06 09:28:12 +02:00
Jonathan Feenstra c097670b09 Update about dialog (#2400) 2026-05-23 10:28:27 -05:00
Jeremy Rimpo a329ce59ac Translation file updates 2026-05-21 23:53:44 -05:00
Jeremy Rimpo 7a4bb3652e Restore tutorials 2026-05-17 02:29:21 -05:00
Jonathan Feenstra ab38cf0e05 Improve code for reading text files line-by-line 2026-05-17 02:28:30 -05:00
Jeremy Rimpo 0c95bf22e6 Add app.manifest to set longPathAware 2026-05-17 02:25:59 -05:00
Jonathan Feenstra 55de581ee9 Add setting to show notifications when downloads complete or fail (#2338) 2026-05-15 02:23:09 -05:00
Jeremy Rimpo 6748953d35 Use new Nexus Tools location for MO2 2026-05-15 02:00:05 -05:00
Jonathan Feenstra 22b1695701 Install manager metadata updates
* Fix missing author and uploader when installing mods
* Refactor doInstall parameters into a struct
2026-05-14 01:43:46 -05:00
3efaf7c946 Migrating to OAuth Authentication (#2374)
Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com>
Co-authored-by: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com>
2026-05-12 13:50:45 -05:00
Jeremy Rimpo 41ffb25c03 Use filesystem paths with BSATK input / output (#2391) 2026-05-07 15:46:47 -05:00
9c6f48a440 Stable DownloadId refactor (#2375)
* Encapsulate the downloads directory watcher in DirWatcherManager

QFileSystemWatcher suppression currently relies on public static start/end
methods and a static counter. Seven call sites pair them raw, one of them
outside the class. Any exception between a pair permanently disables the
watcher, and the static counter implies a singleton DownloadManager.

A new DirWatcherManager owns the watcher, the counter (now an instance
member), and the filtering. The only way to suspend is an RAII Guard
obtained via a scopedGuard() factory. All raw pairs migrate to guards. A
TODO flags the existing processEvents() in the dtor as a known reentrancy
hazard worth replacing later.

* Replace aboutToUpdate/update(int) with ModelResetGuard

Replace the fragile two-signal protocol with a refcounted RAII
ModelResetGuard. Split update(int) into aboutToResetModel/modelReset
(guard only) and rowChanged(int); notifyRowChanged() is suppressed while
a reset is active.

Fixes "beginResetModel without endResetModel" warnings from three sites
in downloadFinished/removeDownload that were pairing reset with a row
update. removePending only opens a guard when an actual match is removed.

* Centralize row notifications in setState and fix missed emits

setState emits notifyRowChanged itself, uses indexByInfo (-1 when
untracked), and re-looks up the row at each use so reply->abort() and
plugin callbacks that re-enter and erase info don't produce stale
signals.

Remove the trailing emit loop from createMetaFile and the now-redundant
notifyRowChanged calls scattered after setState. Add the two missing
emits in restoreDownload (after m_Hidden) and metaDataChanged (after
rename). Guard downloadFinished with a top-level DirWatcherGuard to
prevent filesystem events from its writes racing with model updates.

* Fix comma operator in addNXMDownload pending-dedup check

The game-name comparison result was discarded by the comma operator,
so the dedup only matched modId/fileId across all games.

* Fix lost finished() signal on fast downloads

Hoist the file-exists prompt out of startDownload so setup is straight-line.
Connect finished() last and dispatch manually if the reply already finished.

* Fix memory leak in DownloadInfo::createFromMeta

Move the allocation past the early-return checks so path-mismatch and
hidden-skip paths no longer leak a fresh DownloadInfo.

* Sanitize suffix path in getDownloadFileName

The collision-avoidance branch was using the raw baseName, so invalid
characters sanitized out of the initial path leaked into the suffixed one.

* [pre-commit.ci] Auto fixes from pre-commit.com hooks.

* Remove unused alphabetical translation vector

m_AlphabeticalTranslation was written but never read; drop it along with
refreshAlphabeticalTranslation, ByName, and the LessThanWrapper helper.

* Address PR feedback: fix redundant check and move refresh outside try catch.

* Coalesce the removeDownload reset with the following refreshList

Moves the ModelResetGuard out of the try-catch so it also wraps the
refreshList() call below. Without this, one reset fires when the guard
destructs at the end of the try block and another fires from
refreshList's own guard, producing two resets where one is sufficient.

* Guard the .meta creation in openMetaFile against the directory watcher

openMetaFile creates the .meta file via QSettings when one does not
exist; the disk write fires directoryChanged and triggers a spurious
refreshList. Wrap it in a DirWatcherManager::Guard like the other
meta-file editing paths.

* Extract getValidGameShortName method in download manager (#2380)

* Add stable download id index and PendingDownload struct

Replace the (game, mod, file) tuple backing m_PendingDownloads with a
named struct, and add m_ByID as an O(1) m_DownloadID-to-info index kept
in sync with every m_ActiveDownloads mutation. Encapsulate the id
counter behind DownloadInfo::newDownloadID(), the only supported way to
consume from s_NextDownloadID.

Infrastructure only; external behaviour is unchanged.

* Return stable ids from the plugin-facing download API

startDownloadURLs / startDownloadNexusFile / addNXMDownload now reserve
and return m_DownloadID instead of a stale index. Plugin callbacks fire
with m_DownloadID; downloadPath looks up via m_ByID. nxmDownloadURLsAvailable
threads the reserved id into the materializing DownloadInfo, and Nexus
API failures wake waiting plugins via notifyPendingDownloadFailed.

Incidental: startDownload now returns bool and frees newDownload on
output-open failure; createMetaFile is deferred past that check so
failed starts no longer leave an orphan .meta.

* Split downloadFinished into onReplyFinished slot and finishDownload

The old dual-use downloadFinished(int = 0) took either an explicit index
or relied on sender() when called as a slot. Split into a sender-resolved
slot and an id-based direct call, removing the ambiguous index-zero path.

* Introduce DownloadID alias and row/id accessors

Add a DownloadID type alias for the stable per-download handle and two
public accessors (downloadIDAtRow, rowForDownloadID) so callers can
translate between the view's row vocabulary and the model's id
vocabulary without reaching into the manager's internals. DownloadList
now embeds the DownloadID in QModelIndex::internalId() so any code
holding an index can identify the download directly.

* Convert cancel/pause/resume action methods to take DownloadID

The four methods (cancel, pause, resume, resumeDownloadInt) now accept
a DownloadID, resolve through m_ByID, and no longer care about row
positions. Internal callers iterate DownloadInfo* or look up via id;
DownloadsTab translates row -> id at the connect boundary so the
view's int-shaped signals keep working unchanged.

Also switches the remaining unsigned int signatures that refer to the
download id (finishDownload, downloadInfoByID, PendingDownload::reservedID,
m_ByID, newDownloadID, s_NextDownloadID) to the DownloadID alias.

Drive-by fix: finishDownload's retry branch could read info->m_Tries
after info had been deleted in the CANCELED/retries-exhausted branch
above; now re-resolves via m_ByID.value(id) before touching any fields.

* [pre-commit.ci] Auto fixes from pre-commit.com hooks.

* fix warnings about unused variables and size_t types

* cleanup dead code

* avoid calling processEvents when releasing the DirWatcherGuard

* [pre-commit.ci] Auto fixes from pre-commit.com hooks.

* use QEventLoop instead of manual ProcessEvents

* don't call processEvents in download started and defer handling finish state in event loop

* Cleanup pending download in case of failure.

* Add missing notifyPendingDownloadFailed if user cancels

* [pre-commit.ci] Auto fixes from pre-commit.com hooks.

* Refactor pending download failure handling and cover rename failures

* fix rebase bug, addNXMDownload not returning the correct type

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com>
2026-05-07 17:38:51 +02:00
Jonathan Feenstra 3070a60ab7 Don't apply LOOT-sorted load order until user clicks "Apply" (#2382)
* Add sorted plugin list to LOOT dialog (markdown and button)
* Prefix plugins with checkboxes to show whether they're enabled
2026-05-06 12:51:17 -05:00
Jeremy Rimpo 6d02ef2b75 Defer second nxmhandler call (#2390)
- Calls run in parallel leading to extraneous setup dialogs
- This only calls the nxm schema registration after modl is done
2026-05-06 11:08:57 -05:00
Jeremy Rimpo f5d89ad625 Download command: Add game instance check (#2388)
* Add game instance check
- Should be ignored if not passed to command
- Functions much like NXM game check
2026-05-04 16:40:32 -05:00
Al ca7a149874 Improve mod update check accuracy (#2385)
* Index API response into lookup maps

* extract new findLatestActiveSuccessor method from update check function

* extract method to check if a file is active

* use new isActiveFileStatus in modInfoRegular

* fix bug when merging during mod install

* remember ordering of installed nexus file ids

* refactor update check logic to prioritize Nexus file IDs over filenames

* refactor nxmUpdatesAvailable to simplify update checking logic

* refactor update check logic to find Nexus file IDs by filename and streamline successor retrieval

* refactor update checking to streamline version resolution and improve successor retrieval
2026-05-03 21:47:20 -05:00
Jeremy Rimpo ef7499aade Extended MODL / direct download handling (#2384)
* Extended MODL / direct download handling
- name, modname, version, and source options added to download command
- nxmhandler init adds schemas and MODL entry with default launch args
- Add MODL register button to general tab
- On window display, call meta function to trigger both registrations
2026-05-03 03:04:49 -05:00
Jeremy Rimpo 2e393aa3cc Handle Nexus collections links (#2383)
- Pops up dialog when the NXM link is a collection
- Collection link data available, though still unsupported
2026-05-02 10:45:09 -05:00
Mick dc420a258a Change IconDelegate::paintIcons to only execute when iconWidth > 0 (#2362) 2026-04-27 08:58:17 +02:00
Jonathan Feenstra 49da80c2a4 Make command-line arguments -i "" launch the portable instance (#2341) 2026-04-27 08:57:00 +02:00
Al f80ad0435c fix crash when CWD is not set to app directory (#2379) 2026-04-26 21:43:16 -05:00
Jeremy Rimpo 925bade315 Disabling tutorials (#2366) 2026-04-17 10:17:59 +02:00
Jonathan Feenstra 9deaf71362 Add more contributors to the about dialog (#2369) 2026-04-17 10:17:11 +02:00
Jeremy Rimpo 3a5140bb8f Starfield: Updated blueprint / blueprint prefix support (#2368)
* Add blueprint handling with blueprintships

* Blueprint changes
- Add tooltips
- Add warnings
- Handle blueprint prefixes properly

* Make sure we're assigning the property
2026-04-16 10:20:50 -05:00
Jeremy Rimpo 05593c0347 Update libloot dll name in sanity check (#2370) 2026-04-16 10:17:13 -05:00
Jeremy Rimpo ca4e81ca86 Removing references to openssl (#2367) 2026-04-16 14:09:53 +02:00
Jeremy Rimpo 6bd8bcc239 Update vcpkg targets (#2365) 2026-04-15 09:59:11 +02:00
Mick 7528d023c4 fix header guard typos (#2364) 2026-04-15 09:27:38 +02:00
pre-commit-ci[bot] 662f033295 [pre-commit.ci] Pre-commit autoupdate. (#2357)
* [pre-commit.ci] Pre-commit autoupdate.

updates:
- [github.com/pre-commit/mirrors-clang-format: v21.1.8 → v22.1.2](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.8...v22.1.2)

* [pre-commit.ci] Auto fixes from pre-commit.com hooks.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-04-09 13:16:16 +02:00
Jonathan Feenstra 2043d9931c Remove "Categories: " tooltips when there are no categories (#2339) 2026-02-18 19:48:12 +01:00
Jonathan Feenstra 4da0bffeee Add instance manager to plugin API (#2335) 2026-02-08 10:08:10 +01:00
Jonathan Feenstra a394f02e97 Add executables list to plugin API (#2327) 2026-01-31 16:49:46 +01:00
Jonathan Feenstra aa44561e86 Change IOrganizer::profile return type to a shared_ptr (#2322) 2026-01-11 17:31:26 +01:00
Jonathan Feenstra 717b5ac389 Add instanceName and profiles methods to plugin API (#2321) 2026-01-11 12:36:07 +01:00
pre-commit-ci[bot] 1505519ecd [pre-commit.ci] Pre-commit autoupdate. (#2320)
updates:
- [github.com/pre-commit/mirrors-clang-format: v21.1.2 → v21.1.8](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.2...v21.1.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-05 19:38:47 +01:00
Jonathan Feenstra e5ac1cc82d Add executable setting to minimize MO2 to the system tray while running (#2313) 2026-01-03 15:48:06 +01:00
Mikaël Capelle d5bd9603c6 Add missing Qt dependencies in CI. (#2317) 2026-01-03 14:49:24 +01:00
pre-commit-ci[bot] cc78137eb5 [pre-commit.ci] Pre-commit autoupdate. (#2295)
updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
- [github.com/pre-commit/mirrors-clang-format: v20.1.7 → v21.1.2](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.7...v21.1.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-10-07 15:01:47 +02:00
itch 8016e77723 fix(qt): remove obsolete call to AA_EnableHighDpiScaling (#2283) 2025-09-15 09:40:50 +02:00
Mikaël Capelle b77b2722b7 Fix an issue with implementation of CombinedModDataContent. (#2278) 2025-09-03 10:13:06 +02:00
Jonathan Feenstra d52fcccb83 Add author and uploader columns to mod list (#2269) 2025-08-18 07:55:52 +02:00
Mikaël Capelle 6c64236e2e Allow non-cache build in CI when Azure variables are not available. (#2270) 2025-08-17 12:32:23 +02:00
pre-commit-ci[bot] 95b9ab2e45 [pre-commit.ci] Pre-commit autoupdate. (#2258)
updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.5 → v20.1.7](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.5...v20.1.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-07-08 07:50:31 +02:00
Mikaël Capelle 28e712c8a3 Add version of MO2 plugin loaded in logs. (#2252) 2025-06-06 15:06:11 +02:00
112 changed files with 6558 additions and 3920 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ on:
types: [opened, synchronize, reopened]
env:
VCPKG_BINARY_SOURCES: clear;x-azblob,${{ vars.AZ_BLOB_VCPKG_URL }},${{ secrets.AZ_BLOB_SAS }},readwrite
VCPKG_BINARY_SOURCES: ${{ vars.AZ_BLOB_VCPKG_URL != '' && format('clear;x-azblob,{0},{1},readwrite', vars.AZ_BLOB_VCPKG_URL, secrets.AZ_BLOB_SAS) || '' }}
jobs:
build:
@@ -17,7 +17,7 @@ jobs:
id: build-modorganizer
uses: ModOrganizer2/build-with-mob-action@master
with:
qt-modules: qtpositioning qtwebchannel qtwebengine qtwebsockets
qt-modules: qtpositioning qtwebchannel qtwebengine qtwebsockets qtnetworkauth
mo2-dependencies: usvfs uibase bsatk esptk archive lootcli
- name: Install ModOrganizer
+4 -1
View File
@@ -8,9 +8,12 @@ src/*.bak
CMakeLists.txt.user
edit
/CMakeFiles
.idea
.idea/*
!.idea/filetypes/
!.idea/filetypes/qt-translations.xml
/msbuild.log
/*std*.log
/*build
/src/version.aps
.idea/
+2 -2
View File
@@ -1,13 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-case-conflict
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v19.1.5
rev: v22.1.5
hooks:
- id: clang-format
'types_or': [c++, c]
+2 -4
View File
@@ -1,5 +1,3 @@
[![Build status](https://ci.appveyor.com/api/projects/status/hxenwxmpaob5xung?svg=true)](https://ci.appveyor.com/project/ModOrganizer2/modorganizer-736bd)
# Mod Organizer
Mod Organizer (MO) is a tool for managing mod collections of arbitrary size. It is specifically designed for people who like to experiment with mods and thus need an easy and reliable way to install and uninstall them.
@@ -20,14 +18,14 @@ If you want to submit your code changes, please use a good formatting style like
Through the work of a few people of the community MO2 has come quite far, now it needs some more of those people to go further.
## Reporting Issues:
Issues should be reported to the GitHub page or on the open discord server: [Mod Organizer 2](https://discord.gg/ewUVAqyrQX). Here is also where dev builds are tested, bugs are reported and investigated, suggestions are discussed and a lot more.
Issues should be reported to the GitHub page or on the open Discord server: [Mod Organizer 2](https://discord.gg/ewUVAqyrQX). Here is also where dev builds are tested, bugs are reported and investigated, suggestions are discussed and a lot more.
Credits to Tannin, LePresidente, Silarn, erasmux, AL12, LostDragonist, AnyOldName3, isa, Holt59, Project579, przester, Qudix, RJ, Jonathan Feenstra and many others for the development.
## Download Location
* on [GitHub.com](https://github.com/Modorganizer2/modorganizer/releases)
* on [NexusMods.com](https://www.nexusmods.com/skyrimspecialedition/mods/6194)
* on [NexusMods.com](https://www.nexusmods.com/site/mods/6)
## Old Download Location
+8 -2
View File
@@ -12,7 +12,7 @@ find_package(mo2-esptk CONFIG REQUIRED)
find_package(mo2-dds-header CONFIG REQUIRED)
find_package(mo2-libbsarch CONFIG REQUIRED)
find_package(Qt6 REQUIRED COMPONENTS WebEngineWidgets WebSockets)
find_package(Qt6 REQUIRED COMPONENTS WebEngineWidgets WebSockets NetworkAuth)
find_package(Boost CONFIG REQUIRED COMPONENTS program_options thread interprocess signals2 uuid accumulators)
find_package(7zip CONFIG REQUIRED)
find_package(lz4 CONFIG REQUIRED)
@@ -41,7 +41,7 @@ target_link_libraries(organizer PRIVATE
usvfs::usvfs mo2::uibase mo2::archive mo2::libbsarch
mo2::bsatk mo2::esptk mo2::lootcli-header
Boost::program_options Boost::signals2 Boost::uuid Boost::accumulators
Qt6::WebEngineWidgets Qt6::WebSockets Version Dbghelp)
Qt6::WebEngineWidgets Qt6::WebSockets Qt6::NetworkAuth Version Dbghelp)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt6"
DESTINATION ${_bin}/dlls
@@ -109,6 +109,7 @@ mo2_add_filter(NAME src/application GROUPS
multiprocess
sanitychecks
selfupdater
systemtraymanager
updatedialog
)
@@ -129,6 +130,9 @@ mo2_add_filter(NAME src/core GROUPS
githubpp
installationmanager
nexusinterface
nexusoauthlogin
nexusoauthtokens
nexusoauthconfig
nxmaccessmanager
organizercore
game_features
@@ -273,7 +277,9 @@ mo2_add_filter(NAME src/profiles GROUPS
mo2_add_filter(NAME src/proxies GROUPS
downloadmanagerproxy
executableslistproxy
gamefeaturesproxy
instancemanagerproxy
modlistproxy
organizerproxy
pluginlistproxy
-2
View File
@@ -45,7 +45,6 @@ AboutDialog::AboutDialog(const QString& version, QWidget* parent)
m_LicenseFiles[LICENSE_CCBY3] = "BY-SA-v3.0.txt";
m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt";
m_LicenseFiles[LICENSE_PYTHON] = "python.txt";
m_LicenseFiles[LICENSE_SSL] = "openssl.txt";
m_LicenseFiles[LICENSE_CPPTOML] = "cpptoml.txt";
m_LicenseFiles[LICENSE_UDIS] = "udis86.txt";
m_LicenseFiles[LICENSE_SPDLOG] = "spdlog.txt";
@@ -69,7 +68,6 @@ AboutDialog::AboutDialog(const QString& version, QWidget* parent)
addLicense("ANTLR", LICENSE_ANTLR);
addLicense("LOOT", LICENSE_GPL3);
addLicense("Python", LICENSE_PYTHON);
addLicense("OpenSSL", LICENSE_SSL);
addLicense("cpptoml", LICENSE_CPPTOML);
addLicense("Udis86", LICENSE_UDIS);
addLicense("spdlog", LICENSE_SPDLOG);
-1
View File
@@ -51,7 +51,6 @@ private:
LICENSE_BOOST,
LICENSE_CCBY3,
LICENSE_PYTHON,
LICENSE_SSL,
LICENSE_CPPTOML,
LICENSE_7ZIP,
LICENSE_ZLIB,
+893 -721
View File
File diff suppressed because it is too large Load Diff
+15 -4
View File
@@ -19,12 +19,17 @@ APIUserAccount::APIUserAccount() : m_type(APIUserAccountTypes::None) {}
bool APIUserAccount::isValid() const
{
return !m_key.isEmpty();
return !m_accessToken.isEmpty() || !m_apiKey.isEmpty();
}
const QString& APIUserAccount::accessToken() const
{
return m_accessToken;
}
const QString& APIUserAccount::apiKey() const
{
return m_key;
return m_apiKey;
}
const QString& APIUserAccount::id() const
@@ -47,9 +52,15 @@ const APILimits& APIUserAccount::limits() const
return m_limits;
}
APIUserAccount& APIUserAccount::apiKey(const QString& key)
APIUserAccount& APIUserAccount::accessToken(const QString& token)
{
m_key = key;
m_accessToken = token;
return *this;
}
APIUserAccount& APIUserAccount::apiKey(const QString& apiKey)
{
m_apiKey = apiKey;
return *this;
}
+14 -4
View File
@@ -65,7 +65,12 @@ public:
bool isValid() const;
/**
* api key
* OAuth access token
*/
const QString& accessToken() const;
/**
* OAuth access token
*/
const QString& apiKey() const;
@@ -90,9 +95,14 @@ public:
const APILimits& limits() const;
/**
* sets the api key
* sets the OAuth access token
*/
APIUserAccount& apiKey(const QString& key);
APIUserAccount& accessToken(const QString& token);
/**
* sets the OAuth access token
*/
APIUserAccount& apiKey(const QString& apiKey);
/**
* sets the user id
@@ -132,7 +142,7 @@ public:
bool exhausted() const;
private:
QString m_key, m_id, m_name;
QString m_accessToken, m_apiKey, m_id, m_name;
APIUserAccountTypes m_type;
APILimits m_limits;
};
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="microsoft.com">true</longPathAware>
</windowsSettings>
</application>
</assembly>
+2 -2
View File
@@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ARCHIVEFILENETRY_H
#define ARCHIVEFILENTRY_H
#ifndef ARCHIVEFILETREE_H
#define ARCHIVEFILETREE_H
#include <archive/archive.h>
#include <uibase/ifiletree.h>
+20 -14
View File
@@ -57,13 +57,17 @@ void CategoryFactory::loadCategories()
QFile categoryFile(categoriesFilePath());
bool needLoad = false;
if (!categoryFile.open(QIODevice::ReadOnly)) {
if (!categoryFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
needLoad = true;
} else {
int lineNum = 0;
while (!categoryFile.atEnd()) {
QByteArray line = categoryFile.readLine();
++lineNum;
const auto lines = categoryFile.readAll().split('\n');
categoryFile.close();
for (int lineNum = 0; lineNum < lines.size(); ++lineNum) {
const auto& line = lines[lineNum];
if (line.isEmpty()) {
continue;
}
QList<QByteArray> cells = line.split('|');
if (cells.count() == 4) {
std::vector<NexusCategory> nexusCats;
@@ -76,7 +80,7 @@ void CategoryFactory::loadCategories()
if (!ok) {
log::error(tr("invalid category id {0}"), iter->constData());
}
nexusCats.push_back(NexusCategory("Unknown", temp));
nexusCats.emplace_back("Unknown", temp);
}
}
bool cell0Ok = true;
@@ -103,16 +107,19 @@ void CategoryFactory::loadCategories()
line.constData(), cells.count());
}
}
categoryFile.close();
QFile nexusMapFile(nexusMappingFilePath());
if (!nexusMapFile.open(QIODevice::ReadOnly)) {
if (!nexusMapFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
needLoad = true;
} else {
int nexLineNum = 0;
while (!nexusMapFile.atEnd()) {
QByteArray nexLine = nexusMapFile.readLine();
++nexLineNum;
const auto nexLines = nexusMapFile.readAll().split('\n');
nexusMapFile.close();
for (int nexLineNum = 0; nexLineNum < nexLines.size(); ++nexLineNum) {
const auto& nexLine = nexLines[nexLineNum];
if (nexLine.isEmpty()) {
continue;
}
QList<QByteArray> nexCells = nexLine.split('|');
if (nexCells.count() == 3) {
std::vector<NexusCategory> nexusCats;
@@ -129,12 +136,11 @@ void CategoryFactory::loadCategories()
m_NexusMap.insert_or_assign(nexID, NexusCategory(nexName, nexID));
m_NexusMap.at(nexID).setCategoryID(catID);
} else {
log::error(tr("invalid nexus category line {0}: {1} ({2} cells)"), lineNum,
log::error(tr("invalid nexus category line {0}: {1} ({2} cells)"), nexLineNum,
nexLine.constData(), nexCells.count());
}
}
}
nexusMapFile.close();
}
std::sort(m_Categories.begin(), m_Categories.end());
setParents();
+56 -6
View File
@@ -10,6 +10,8 @@
#include <log.h>
#include <report.h>
#include <boost/optional/optional_io.hpp>
namespace cl
{
@@ -221,8 +223,12 @@ std::optional<int> CommandLine::runEarly()
std::optional<int> CommandLine::runPostApplication(MOApplication& a)
{
// handle -i with no arguments
if (m_vm.count("instance") && m_vm["instance"].as<std::string>() == "") {
const auto instanceArg = m_vm.find("instance");
if (instanceArg != m_vm.end() &&
!instanceArg->second.as<boost::optional<std::string>>().has_value()) {
// handle -i with no arguments (distinct from -i "", which will launch the
// portable instance if it exists, hence the use of boost::optional)
env::Console c;
if (auto i = InstanceManager::singleton().currentInstance()) {
@@ -314,7 +320,9 @@ void CommandLine::createOptions()
("logs", "duplicates the logs to stdout")
("instance,i", po::value<std::string>()->implicit_value(""),
("instance,i",
po::value<boost::optional<std::string>>()->implicit_value(
boost::none),
"use the given instance (defaults to last used)")
("profile,p", po::value<std::string>(),
@@ -402,8 +410,14 @@ std::optional<QString> CommandLine::instance() const
if (m_shortcut.isValid() && m_shortcut.hasInstance()) {
return m_shortcut.instanceName();
} else if (m_vm.count("instance")) {
return QString::fromStdString(m_vm["instance"].as<std::string>());
} else {
const auto instanceArg = m_vm.find("instance");
if (instanceArg != m_vm.end()) {
const auto& instanceVal = instanceArg->second.as<boost::optional<std::string>>();
if (instanceVal.has_value()) {
return QString::fromStdString(instanceVal.value());
}
}
}
return {};
@@ -839,6 +853,19 @@ Command::Meta DownloadFileCommand::meta() const
return {"download", "downloads a file", "URL", ""};
}
po::options_description DownloadFileCommand::getVisibleOptions() const
{
po::options_description d;
d.add_options()("game,g", po::value<std::string>(), "managed game")(
"name,n", po::value<std::string>(), "(optional) the download name")(
"modname,m", po::value<std::string>(), "(optional) the mod name")(
"version,v", po::value<std::string>(), "(optional) the download / mod version")(
"source,s", po::value<std::string>(), "(optional) the download source");
return d;
}
po::options_description DownloadFileCommand::getInternalOptions() const
{
po::options_description d;
@@ -865,16 +892,39 @@ bool DownloadFileCommand::canForwardToPrimary() const
std::optional<int> DownloadFileCommand::runPostOrganizer(OrganizerCore& core)
{
const QString url = QString::fromStdString(vm()["URL"].as<std::string>());
QString game("");
QString name, modName, version, source;
if (!url.startsWith("https://")) {
reportError(QObject::tr("Download URL must start with https://"));
return 1;
}
if (vm().count("game")) {
game = QString::fromStdString(vm()["game"].as<std::string>());
}
if (vm().count("name")) {
name = QString::fromStdString(vm()["name"].as<std::string>());
}
if (vm().count("modname")) {
modName = QString::fromStdString(vm()["modname"].as<std::string>());
}
if (vm().count("version")) {
version = QString::fromStdString(vm()["version"].as<std::string>());
}
if (vm().count("source")) {
source = QString::fromStdString(vm()["source"].as<std::string>());
}
log::debug("starting direct download from command line: {}", url.toStdString());
MessageDialog::showMessage(QObject::tr("Download started"), qApp->activeWindow(),
false);
core.downloadManager()->startDownloadURLs(QStringList() << url);
core.downloadManager()->startDownloadURLWithMeta(url, game, name, modName, version,
source);
return {};
}
+1
View File
@@ -212,6 +212,7 @@ class DownloadFileCommand : public Command
protected:
Meta meta() const override;
po::options_description getVisibleOptions() const override;
po::options_description getInternalOptions() const override;
po::positional_options_description getPositional() const override;
+1 -1
View File
@@ -27,7 +27,7 @@ class Settings;
//
// pages can be disabled if they return true in skip(), which happens globally
// for some (IntroPage has a setting in the registry), depending on context
// (NexusPage is skipped if the API key already exists) or explicitly (when
// (NexusPage is skipped if the Nexus authorization already exists) or explicitly (when
// only some info about the instance is missing on startup, such as a game
// variant)
//
+2 -1
View File
@@ -103,6 +103,7 @@ void Page::next()
bool Page::action(CreateInstanceDialog::Actions a)
{
Q_UNUSED(a);
// no-op
return false;
}
@@ -1203,7 +1204,7 @@ NexusPage::NexusPage(CreateInstanceDialog& dlg) : Page(dlg), m_skip(false)
// just check it once, or connecting and then going back and forth would skip
// the page, which would be unexpected
m_skip = GlobalSettings::hasNexusApiKey();
m_skip = GlobalSettings::hasNexusOAuthTokens() || GlobalSettings::hasNexusApiKey();
}
NexusPage::~NexusPage() = default;
+1 -2
View File
@@ -6,14 +6,13 @@
<file name="dxcompiler.dll" />
<file name="dxil.dll" />
<file name="libbsarchd.dll" />
<file name="libcrypto-3-x64.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="Qt6Guid.dll" />
<file name="Qt6Networkd.dll" />
<file name="Qt6NetworkAuthd.dll" />
<file name="Qt6OpenGLd.dll" />
<file name="Qt6OpenGLWidgetsd.dll" />
<file name="Qt6Positioningd.dll" />
+1 -2
View File
@@ -6,14 +6,13 @@
<file name="dxcompiler.dll" />
<file name="dxil.dll" />
<file name="libbsarch.dll" />
<file name="libcrypto-3-x64.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="Qt6Gui.dll" />
<file name="Qt6Network.dll" />
<file name="Qt6NetworkAuth.dll" />
<file name="Qt6OpenGL.dll" />
<file name="Qt6OpenGLWidgets.dll" />
<file name="Qt6Positioning.dll" />
+20 -13
View File
@@ -35,8 +35,10 @@ DownloadList::DownloadList(OrganizerCore& core, QObject* parent)
: QAbstractTableModel(parent), m_manager(*core.downloadManager()),
m_settings(core.settings())
{
connect(&m_manager, SIGNAL(update(int)), this, SLOT(update(int)));
connect(&m_manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate()));
connect(&m_manager, &DownloadManager::aboutToResetModel, this,
&DownloadList::onAboutToResetModel);
connect(&m_manager, &DownloadManager::modelReset, this, &DownloadList::onModelReset);
connect(&m_manager, &DownloadManager::rowChanged, this, &DownloadList::onRowChanged);
}
int DownloadList::rowCount(const QModelIndex& parent) const
@@ -56,7 +58,9 @@ int DownloadList::columnCount(const QModelIndex&) const
QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const
{
return createIndex(row, column, row);
// Embed the stable DownloadID in internalId() so any consumer of the index
// can identify the download without having to track row shifts.
return createIndex(row, column, m_manager.downloadIDAtRow(row));
}
QModelIndex DownloadList::parent(const QModelIndex&) const
@@ -110,14 +114,14 @@ QVariant DownloadList::data(const QModelIndex& index, int role) const
bool pendingDownload = index.row() >= m_manager.numTotalDownloads();
if (role == Qt::DisplayRole) {
if (pendingDownload) {
std::tuple<QString, int, int> nexusids =
const DownloadManager::PendingDownload pending =
m_manager.getPendingDownload(index.row() - m_manager.numTotalDownloads());
switch (index.column()) {
case COL_NAME:
return tr("< game %1 mod %2 file %3 >")
.arg(std::get<0>(nexusids))
.arg(std::get<1>(nexusids))
.arg(std::get<2>(nexusids));
.arg(pending.gameName)
.arg(pending.modID)
.arg(pending.fileID);
case COL_SIZE:
return tr("Unknown");
case COL_STATUS:
@@ -239,16 +243,19 @@ QVariant DownloadList::data(const QModelIndex& index, int role) const
return QVariant();
}
void DownloadList::aboutToUpdate()
void DownloadList::onAboutToResetModel()
{
emit beginResetModel();
beginResetModel();
}
void DownloadList::update(int row)
void DownloadList::onModelReset()
{
if (row < 0)
emit endResetModel();
else if (row < this->rowCount())
endResetModel();
}
void DownloadList::onRowChanged(int row)
{
if (row < this->rowCount())
emit dataChanged(
this->index(row, 0, QModelIndex()),
this->index(row, this->columnCount(QModelIndex()) - 1, QModelIndex()));

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