mirror of
https://github.com/ModOrganizer2/modorganizer.git
synced 2026-07-27 13:58:24 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc75009169 | ||
|
|
8be73d2e3b | ||
|
|
358e2c4e74 | ||
|
|
be2d39b035 | ||
|
|
8f0b42d466 | ||
|
|
37b1bdd9e6 | ||
|
|
e037caf58e | ||
|
|
e33da8402c | ||
|
|
c4bfee0089 | ||
|
|
5a24d2b60c | ||
|
|
9465966b2c | ||
|
|
47dc5cded7 | ||
|
|
bf9d187036 | ||
|
|
208eaccbf7 | ||
|
|
fb1c376533 | ||
|
|
0f8852f487 | ||
|
|
7eba6837dd | ||
|
|
cc2bd15256 | ||
|
|
b409a0bef9 | ||
|
|
d62669e1b4 | ||
|
|
a1d92fb673 | ||
|
|
fd9eb032b6 | ||
|
|
5c39a762e6 | ||
|
|
a905ce130d | ||
|
|
fed89352d1 | ||
|
|
74dd2bb914 | ||
|
|
dcbcb29ec7 | ||
|
|
92e3fa2fdd | ||
|
|
5a98d69dcd | ||
|
|
5dd0e3b77e | ||
|
|
4baba035dd | ||
|
|
5eb03a687c | ||
|
|
dd288fe152 | ||
|
|
ba0a1bd501 | ||
|
|
2d2a2b8b74 | ||
|
|
0e0c72422c | ||
|
|
82d4742e28 | ||
|
|
3cceb6502c | ||
|
|
92a8a3dce8 | ||
|
|
bcc4c8d6aa | ||
|
|
149ce846b5 | ||
|
|
192b0bafa5 | ||
|
|
0c792d548f | ||
|
|
336216ba9a | ||
|
|
b71d472e08 | ||
|
|
bbce6506cd | ||
|
|
2def87a143 | ||
|
|
90165b02ab | ||
|
|
8095be550f | ||
|
|
2fa1b04cd4 | ||
|
|
a4b0524c70 | ||
|
|
8dee1d108b | ||
|
|
e0f003a9a6 | ||
|
|
ccb5ad53e2 | ||
|
|
9c596ceb79 | ||
|
|
8305d92157 | ||
|
|
9f61ef5c9e | ||
|
|
887d464f2c | ||
|
|
0d4a59fd91 | ||
|
|
8f8beea427 | ||
|
|
0983c88c17 | ||
|
|
9ee2831c59 | ||
|
|
d6aab781ee | ||
|
|
a7f17c6429 | ||
|
|
12161d68fb | ||
|
|
090d660831 | ||
|
|
b7b12bb789 | ||
|
|
30a9f61d6d | ||
|
|
f7ab05767c | ||
|
|
d7e6adb3de | ||
|
|
00d8c517ec | ||
|
|
c7f660e061 | ||
|
|
d59b28847c | ||
|
|
5b565b5634 | ||
|
|
673a5034f0 | ||
|
|
7c87e9a645 | ||
|
|
52501e73c9 | ||
|
|
513c062932 | ||
|
|
7cf0c1a29b | ||
|
|
a873f7cfe2 | ||
|
|
1b85cda626 | ||
|
|
c7e1d9738f | ||
|
|
a540a542a4 | ||
|
|
3eee61c67b | ||
|
|
3556d4f58f | ||
|
|
8e97f3f84c | ||
|
|
e6f999ffbb | ||
|
|
5fcd51aad8 | ||
|
|
c1421d0734 | ||
|
|
4a45e1a2a7 | ||
|
|
50c244d7e1 | ||
|
|
b82f9f1006 | ||
|
|
8ebd79ee71 | ||
|
|
e70ee1e895 | ||
|
|
0a4fa10ca4 | ||
|
|
ea6292168a | ||
|
|
28714f0eb8 | ||
|
|
5e5c9c0729 | ||
|
|
5ad912e193 | ||
|
|
0fa6f7feff | ||
|
|
ee7d801b86 | ||
|
|
1cdfb7105d | ||
|
|
0bcd752b27 | ||
|
|
1f34f7d8c1 | ||
|
|
c2cc2a82c8 | ||
|
|
7e2b83c922 | ||
|
|
9624d86b46 | ||
|
|
9fd3f6c4e4 | ||
|
|
4804903f3a | ||
|
|
61898074fe | ||
|
|
ea7c391c33 | ||
|
|
7dfe0c9a8f | ||
|
|
0241d71da3 | ||
|
|
5be7c9a0b5 | ||
|
|
f7374a8525 | ||
|
|
dd8f73178c | ||
|
|
3f5f56397b | ||
|
|
e85285934e | ||
|
|
e1fc0d22b6 | ||
|
|
9ddbf25809 | ||
|
|
ed08faa4a8 | ||
|
|
0c4cdb89ad | ||
|
|
28b0411cca | ||
|
|
d6b645413c | ||
|
|
3a17614bed | ||
|
|
ba0cd0e10d | ||
|
|
8c34131bb8 | ||
|
|
3fdb00e0c9 | ||
|
|
95b258c0df | ||
|
|
45b3614fbc | ||
|
|
7dbb4cedda | ||
|
|
5d5a7b8511 | ||
|
|
0413bf27a5 | ||
|
|
d267b8aaf3 | ||
|
|
ca54760c13 | ||
|
|
d2c5ed352d | ||
|
|
71995f0343 | ||
|
|
5579db7f90 | ||
|
|
d2f90e6a76 | ||
|
|
785852cf21 | ||
|
|
14b06dd2e8 | ||
|
|
58ec83f8c8 | ||
|
|
d06972729d | ||
|
|
39efb9a7c2 | ||
|
|
c1a56f77ea | ||
|
|
91bd9b1eb6 | ||
|
|
548744d746 | ||
|
|
57e65abf75 | ||
|
|
d41cd8d877 | ||
|
|
9c82a1102e | ||
|
|
e623c13400 | ||
|
|
9c9a43ec8d | ||
|
|
eb3cfe2594 | ||
|
|
482f3d1f6d | ||
|
|
fed4ea257a | ||
|
|
a4e43b68cd | ||
|
|
cdf588e7c4 |
+6
-5
@@ -1,14 +1,15 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
|
||||
|
||||
ADD_COMPILE_OPTIONS($<$<CXX_COMPILER_ID:MSVC>:/MP>)
|
||||
|
||||
PROJECT(organizer)
|
||||
|
||||
# sets ModOrganizer as StartUp Project in Visual Studio
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY VS_STARTUP_PROJECT "ModOrganizer")
|
||||
|
||||
SET(DEPENDENCIES_DIR CACHE PATH "")
|
||||
# hint to find qt in dependencies path
|
||||
LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake)
|
||||
FILE(GLOB_RECURSE BOOST_ROOT ${DEPENDENCIES_DIR}/boost*/project-config.jam)
|
||||
GET_FILENAME_COMPONENT(BOOST_ROOT ${BOOST_ROOT} DIRECTORY)
|
||||
|
||||
SET(ZLIB_ROOT ${DEPENDENCIES_DIR}/zlib)
|
||||
LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll)
|
||||
|
||||
ADD_SUBDIRECTORY(src)
|
||||
|
||||
@@ -1,42 +1,83 @@
|
||||
# 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.
|
||||
Mod Organizer 2 is a mod manager created by Tannin to support 64-bit games like Skyrim Special Edition and Fallout 4 in addition to all the 32-bit games MO1 already supported.
|
||||
|
||||
Tannin discontinued the project when he was hired by the Nexus team to develop their new Vortex mod manager.
|
||||
MO2 was not completed and was left with many issues. LePresidente managed to fix it to a usable state by himself after SkyrimSE came out, adding support for it, and releasing v2.0.8.
|
||||
The project took up speed again after a few more coders showed up in late 2017, and a lot of progress has been made.
|
||||
|
||||
## Help Wanted!
|
||||
Mod Organizer 2 is an open project in the hands of the community, there are problems that need to be solved and things that could be added. MO2 really needs developers and if you have the programming skills and some free time you can really improve the experience of the modding community.
|
||||
|
||||
To have more information, please join the open MO2 Development discord server :* [ModOrganizerDevs](https://discord.gg/vD2ZbfX)
|
||||
If you want to help translate MO2 to your language you should join the discord server too and head to the #translation channel.
|
||||
To setup a development environment on your machine, there is the [umbrella project](https://github.com/Modorganizer2/modorganizer-umbrella) that handles that.
|
||||
If you want to submit your code changes, please use a good formating style like the default one in Visual Studio.
|
||||
|
||||
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: [ModOrganizerDevs](https://discord.gg/vD2ZbfX). 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 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)
|
||||
|
||||
## Old Download Location
|
||||
|
||||
* [TanninOne/modorganizer/releases](https://github.com/TanninOne/modorganizer/releases)
|
||||
|
||||
## Building
|
||||
|
||||
Please refer to [TanninOne/modorganizer-umbrella](https://github.com/TanninOne/modorganizer-umbrella) for build instructions.
|
||||
Please refer to [Modorganizer2/modorganizer-umbrella](https://github.com/Modorganizer2/modorganizer-umbrella) for build instructions.
|
||||
|
||||
## Other Repositories
|
||||
|
||||
MO consists of multiple repositories on github. The Umbrella project will download them automatically as required. They should however also be buildable individually.
|
||||
MO2 consists of multiple repositories on github. The Umbrella project will download them automatically as required. They should however also be buildable individually.
|
||||
Here is a complete list:
|
||||
* https://github.com/TanninOne/modorganizer
|
||||
* https://github.com/TanninOne/modorganizer-uibase
|
||||
* https://github.com/TanninOne/modorganizer-hookdll
|
||||
* https://github.com/TanninOne/modorganizer-bsatk
|
||||
* https://github.com/TanninOne/modorganizer-esptk
|
||||
* https://github.com/TanninOne/modorganizer-archive
|
||||
* https://github.com/TanninOne/modorganizer-nxmhandler
|
||||
* https://github.com/TanninOne/modorganizer-lootcli
|
||||
* https://github.com/TanninOne/modorganizer-helper
|
||||
|
||||
* https://github.com/LePresidente/cpython-1
|
||||
* https://github.com/Modorganizer2/githubpp
|
||||
* https://github.com/Modorganizer2/modorganizer
|
||||
* https://github.com/Modorganizer2/modorganizer-archive
|
||||
* https://github.com/Modorganizer2/modorganizer-bsatk
|
||||
* https://github.com/Modorganizer2/modorganizer-bsa_extractor
|
||||
* https://github.com/Modorganizer2/modorganizer-check_fnis
|
||||
* https://github.com/Modorganizer2/modorganizer-diagnose_basic
|
||||
* https://github.com/Modorganizer2/modorganizer-esptk
|
||||
* https://github.com/Modorganizer2/modorganizer-helper
|
||||
* https://github.com/Modorganizer2/modorganizer-game_fallout3
|
||||
* https://github.com/Modorganizer2/modorganizer-game_fallout4
|
||||
* https://github.com/Modorganizer2/modorganizer-game_fallout4vr
|
||||
* https://github.com/Modorganizer2/modorganizer-game_falloutnv
|
||||
* https://github.com/Modorganizer2/modorganizer-game_features
|
||||
* https://github.com/Modorganizer2/modorganizer-game_gamebryo
|
||||
* https://github.com/Modorganizer2/modorganizer-game_morrowind
|
||||
* https://github.com/Modorganizer2/modorganizer-game_oblivion
|
||||
* https://github.com/Modorganizer2/modorganizer-game_skyrim
|
||||
* https://github.com/Modorganizer2/modorganizer-game_skyrimSE
|
||||
* https://github.com/Modorganizer2/modorganizer-game_ttw
|
||||
* https://github.com/Modorganizer2/modorganizer-installer_bain
|
||||
* https://github.com/Modorganizer2/modorganizer-installer_bundle
|
||||
* https://github.com/Modorganizer2/modorganizer-installer_fomod
|
||||
* https://github.com/Modorganizer2/modorganizer-installer_manual
|
||||
* https://github.com/Modorganizer2/modorganizer-installer_ncc
|
||||
* https://github.com/Modorganizer2/modorganizer-installer_quick
|
||||
* https://github.com/Modorganizer2/modorganizer-lootcli
|
||||
* https://github.com/Modorganizer2/modorganizer-NCC
|
||||
* https://github.com/Modorganizer2/modorganizer-nxmhandler
|
||||
* https://github.com/Modorganizer2/modorganizer-plugin_python
|
||||
* https://github.com/Modorganizer2/modorganizer-preview_base
|
||||
* https://github.com/Modorganizer2/modorganizer-tool_configurator
|
||||
* https://github.com/Modorganizer2/modorganizer-tool_inibakery
|
||||
* https://github.com/Modorganizer2/modorganizer-uibase
|
||||
* https://github.com/Modorganizer2/usvfs
|
||||
|
||||
### Unused Repositories
|
||||
* https://github.com/Modorganizer2/modorganizer-hookdll
|
||||
* https://github.com/TanninOne/modorganizer-tool_nmmimport
|
||||
* https://github.com/TanninOne/modorganizer-tool_configurator
|
||||
* https://github.com/TanninOne/modorganizer-tool_inieditor
|
||||
* https://github.com/TanninOne/modorganizer-preview_base
|
||||
* https://github.com/TanninOne/modorganizer-diagnose_basic
|
||||
* https://github.com/TanninOne/modorganizer-installer_quick
|
||||
* https://github.com/TanninOne/modorganizer-installer_manual
|
||||
* https://github.com/TanninOne/modorganizer-installer_fomod
|
||||
* https://github.com/TanninOne/modorganizer-installer_bundle
|
||||
* https://github.com/TanninOne/modorganizer-installer_bain
|
||||
* https://github.com/TanninOne/modorganizer-game_skyrim
|
||||
* https://github.com/TanninOne/modorganizer-game_oblivion
|
||||
* https://github.com/TanninOne/modorganizer-game_fallout3
|
||||
* https://github.com/TanninOne/modorganizer-game_fallout4
|
||||
* https://github.com/TanninOne/modorganizer-game_falloutnv
|
||||
* https://github.com/TanninOne/modorganizer-game_gamebryo
|
||||
* https://github.com/TanninOne/modorganizer-game_features
|
||||
* https://github.com/TanninOne/modorganizer-check_fnis
|
||||
* https://github.com/TanninOne/modorganizer-plugin_python
|
||||
* https://github.com/TanninOne/modorganizer-bsa_extractor
|
||||
* https://github.com/TanninOne/usvfs
|
||||
|
||||
|
||||
+25
-12
@@ -1,4 +1,4 @@
|
||||
CMAKE_MINIMUM_REQUIRED (VERSION 2.8)
|
||||
CMAKE_MINIMUM_REQUIRED (VERSION 2.8.11)
|
||||
|
||||
CMAKE_POLICY(SET CMP0020 NEW)
|
||||
#CMAKE_POLICY(SET CMP0043 NEW)
|
||||
@@ -249,10 +249,13 @@ FIND_PACKAGE(Qt5Quick REQUIRED)
|
||||
FIND_PACKAGE(Qt5Network REQUIRED)
|
||||
FIND_PACKAGE(Qt5WinExtras REQUIRED)
|
||||
FIND_PACKAGE(Qt5WebEngineWidgets REQUIRED)
|
||||
FIND_PACKAGE(Qt5Script REQUIRED)
|
||||
FIND_PACKAGE(Qt5Qml REQUIRED)
|
||||
FIND_PACKAGE(Qt5LinguistTools)
|
||||
QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS})
|
||||
QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS})
|
||||
QT5_CREATE_TRANSLATION(organizer_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/organizer_en.ts)
|
||||
ADD_CUSTOM_TARGET(translations DEPENDS ${organizer_translations_qm})
|
||||
|
||||
INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES})
|
||||
|
||||
@@ -277,6 +280,7 @@ SET(default_project_path "${CMAKE_SOURCE_DIR}/..")
|
||||
GET_FILENAME_COMPONENT(${default_project_path} ${default_project_path} REALPATH)
|
||||
|
||||
SET(project_path "${default_project_path}" CACHE PATH "path to the other mo projects")
|
||||
#TODO this should not be a hardcoded path
|
||||
SET(lib_path "${project_path}/../../install/libs")
|
||||
|
||||
|
||||
@@ -287,13 +291,13 @@ INCLUDE_DIRECTORIES(${project_path}/uibase/src
|
||||
${project_path}/../usvfs/include
|
||||
${project_path}/game_gamebryo/src
|
||||
${project_path}/game_features/src
|
||||
${project_path}/githubpp/src)
|
||||
|
||||
${project_path}/githubpp/src
|
||||
${LZ4_ROOT}/include)
|
||||
|
||||
INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS})
|
||||
LINK_DIRECTORIES(${lib_path}
|
||||
${project_path}/../zlib/lib)
|
||||
|
||||
${ZLIB_ROOT}/lib
|
||||
${LZ4_ROOT}/dll)
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND git log -1 --format=%h
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
@@ -312,11 +316,12 @@ ENDIF()
|
||||
ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS} ${organizer_translations_qm})
|
||||
TARGET_LINK_LIBRARIES(ModOrganizer
|
||||
Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick
|
||||
Qt5::Script Qt5::Qml Qt5::QuickWidgets Qt5::Network
|
||||
${Boost_LIBRARIES}
|
||||
zlibstatic
|
||||
uibase esptk bsatk githubpp
|
||||
${usvfs_name}
|
||||
Dbghelp advapi32 Version Shlwapi)
|
||||
Dbghelp advapi32 Version Shlwapi liblz4)
|
||||
IF (NOT "${OPTIMIZE_COMPILE_FLAGS}" STREQUAL "")
|
||||
SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS_RELWITHDEBINFO
|
||||
${OPTIMIZE_COMPILE_FLAGS})
|
||||
@@ -325,9 +330,6 @@ SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES LINK_FLAGS_RELWITHDEBINFO
|
||||
"/LARGEADDRESSAWARE ${OPTIMIZE_LINK_FLAGS}")
|
||||
|
||||
|
||||
QT5_USE_MODULES(ModOrganizer Widgets Script Qml QuickWidgets Quick Network WebEngineWidgets)
|
||||
|
||||
|
||||
###############
|
||||
## Installation
|
||||
|
||||
@@ -347,10 +349,16 @@ INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt5
|
||||
SET(windeploy_parameters "--no-translations --plugindir qtplugins --libdir dlls --release-with-debug-info --no-compiler-runtime")
|
||||
INSTALL(
|
||||
CODE
|
||||
"EXECUTE_PROCESS(
|
||||
COMMAND
|
||||
"EXECUTE_PROCESS(COMMAND
|
||||
${qt5bin}/windeployqt.exe ModOrganizer.exe --webenginewidgets ${windeploy_parameters}
|
||||
COMMAND
|
||||
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin
|
||||
)
|
||||
# run it a second time because on the first run it misses some files
|
||||
EXECUTE_PROCESS(COMMAND
|
||||
${qt5bin}/windeployqt.exe ModOrganizer.exe --webenginewidgets ${windeploy_parameters}
|
||||
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin
|
||||
)
|
||||
EXECUTE_PROCESS(COMMAND
|
||||
${qt5bin}/windeployqt.exe uibase.dll ${windeploy_parameters}
|
||||
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin
|
||||
)
|
||||
@@ -363,6 +371,11 @@ INSTALL(
|
||||
file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/qtplugins)"
|
||||
)
|
||||
|
||||
# qdds.dll needs installing manually as Qt no longer ships with it by default.
|
||||
INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../qdds.dll DESTINATION bin/dlls/imageformats)
|
||||
|
||||
install(FILES ${organizer_translations_qm} DESTINATION bin/translations)
|
||||
|
||||
INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/stylesheets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tutorials
|
||||
DESTINATION bin)
|
||||
|
||||
+32
-10
@@ -29,7 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <QTextBrowser>
|
||||
#include <QVariant>
|
||||
#include <Qt>
|
||||
|
||||
#include <QFontDatabase>
|
||||
|
||||
AboutDialog::AboutDialog(const QString &version, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
@@ -37,24 +37,44 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
m_LicenseFiles[LICENSE_LGPL3] = "lgpl-3.0.txt";
|
||||
m_LicenseFiles[LICENSE_GPL3] = "gpl-3.0.txt";
|
||||
m_LicenseFiles[LICENSE_BSD3] = "bsd3.txt";
|
||||
m_LicenseFiles[LICENSE_LGPL3] = "LGPL-v3.0.txt";
|
||||
m_LicenseFiles[LICENSE_LGPL21] = "GNU-LGPL-v2.1.txt";
|
||||
m_LicenseFiles[LICENSE_GPL3] = "GPL-v3.0.txt";
|
||||
m_LicenseFiles[LICENSE_GPL2] = "GPL-v2.0.txt";
|
||||
m_LicenseFiles[LICENSE_BOOST] = "boost.txt";
|
||||
m_LicenseFiles[LICENSE_CCBY3] = "by-sa3.txt";
|
||||
m_LicenseFiles[LICENSE_7ZIP] = "7zip.txt";
|
||||
m_LicenseFiles[LICENSE_CCBY3] = "BY-SA-v3.0.txt";
|
||||
m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt";
|
||||
m_LicenseFiles[LICENSE_APACHE2] = "apache-license-2.0.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";
|
||||
m_LicenseFiles[LICENSE_FMT] = "fmt.txt";
|
||||
m_LicenseFiles[LICENSE_SIP] = "sip.txt";
|
||||
m_LicenseFiles[LICENSE_CASTLE] = "Castle.txt";
|
||||
m_LicenseFiles[LICENSE_ANTLR] = "AntlrBuildTask.txt";
|
||||
m_LicenseFiles[LICENSE_DXTEX] = "DXTex.txt";
|
||||
|
||||
addLicense("Qt", LICENSE_LGPL3);
|
||||
addLicense("Qt Json", LICENSE_GPL3);
|
||||
addLicense("Boost Library", LICENSE_BOOST);
|
||||
addLicense("7-zip", LICENSE_LGPL3);
|
||||
addLicense("ZLib", LICENSE_ZLIB);
|
||||
addLicense("7-zip", LICENSE_7ZIP);
|
||||
addLicense("ZLib", LICENSE_NONE);
|
||||
addLicense("Tango Icon Theme", LICENSE_NONE);
|
||||
addLicense("RRZE Icon Set", LICENSE_CCBY3);
|
||||
addLicense("Icons by Lorc, Delapouite and sbed available on http://game-icons.net", LICENSE_CCBY3);
|
||||
addLicense("Castle Core", LICENSE_APACHE2);
|
||||
addLicense("Castle Core", LICENSE_CASTLE);
|
||||
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);
|
||||
addLicense("{fmt}", LICENSE_FMT);
|
||||
addLicense("SIP", LICENSE_SIP);
|
||||
addLicense("DXTex Headers", LICENSE_DXTEX);
|
||||
|
||||
ui->nameLabel->setText(QString("<span style=\"font-size:12pt; font-weight:600;\">%1 %2</span>").arg(ui->nameLabel->text()).arg(version));
|
||||
#if defined(HGID)
|
||||
@@ -64,6 +84,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent)
|
||||
#else
|
||||
ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown");
|
||||
#endif
|
||||
|
||||
ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +107,7 @@ void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QL
|
||||
{
|
||||
auto iter = m_LicenseFiles.find(current->data(Qt::UserRole).toInt());
|
||||
if (iter != m_LicenseFiles.end()) {
|
||||
QString filePath = qApp->applicationDirPath() + "/license/" + iter->second;
|
||||
QString filePath = qApp->applicationDirPath() + "/licenses/" + iter->second;
|
||||
QString text = MOBase::readFileText(filePath);
|
||||
ui->licenseText->setText(text);
|
||||
} else {
|
||||
|
||||
+13
-2
@@ -45,12 +45,23 @@ private:
|
||||
enum Licenses {
|
||||
LICENSE_NONE,
|
||||
LICENSE_LGPL3,
|
||||
LICENSE_LGPL21,
|
||||
LICENSE_GPL3,
|
||||
LICENSE_BSD3,
|
||||
LICENSE_GPL2,
|
||||
LICENSE_BOOST,
|
||||
LICENSE_CCBY3,
|
||||
LICENSE_PYTHON,
|
||||
LICENSE_SSL,
|
||||
LICENSE_CPPTOML,
|
||||
LICENSE_7ZIP,
|
||||
LICENSE_ZLIB,
|
||||
LICENSE_APACHE2
|
||||
LICENSE_UDIS,
|
||||
LICENSE_SPDLOG,
|
||||
LICENSE_FMT,
|
||||
LICENSE_SIP,
|
||||
LICENSE_CASTLE,
|
||||
LICENSE_ANTLR,
|
||||
LICENSE_DXTEX
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
+108
-48
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>604</width>
|
||||
<height>369</height>
|
||||
<width>1010</width>
|
||||
<height>477</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -120,14 +120,14 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string notr="true">Copyright 2011-2016 Sebastian Herbord</string>
|
||||
<string notr="true"><html><head/><body><p>Copyright 2011-2016 Sebastian Herbord</p><p>Copyright 2016-2018 Mod Organizer 2 contributors</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string notr="true"><html><head/><body><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p><p>See the GNU General Public License for more details.</p><p>Source code can be found at <a href="https://github.com/TanninOne/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html></string>
|
||||
<string notr="true"><html><head/><body><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p><p>See the GNU General Public License for more details.</p><p>Source code can be found at <a href="https://github.com/Modorganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
@@ -154,7 +154,7 @@
|
||||
<string>Thanks</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Current Maintainers</string>
|
||||
@@ -167,12 +167,17 @@
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">LePresidente</string>
|
||||
<string notr="true">LePresidente (Project Lead)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Silarn</string>
|
||||
<string>AL12</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Diana</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
@@ -182,7 +187,7 @@
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Diana</string>
|
||||
<string notr="true">Silarn</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
@@ -201,36 +206,6 @@
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">pndrev (German)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">DaWul (Spanish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Fiama (Spanish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Alyndiar (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Jlkawaii (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Rigoletto (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Scythe1912 (Chinese)</string>
|
||||
@@ -248,7 +223,47 @@
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">tokcdk (Russian)</string>
|
||||
<string>Cyb3r (Dutch)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Alyndiar (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>fruttyx (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Jlkawaii (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Rigoletto (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Yoplala (French)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Faron (German)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">pndrev (German)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Mordan (Greek)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
@@ -261,7 +276,37 @@
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>... more (Can't track)</string>
|
||||
<string>Yoosk (Polish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Brgodfx (Portuguese)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">tokcdk (Russian)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">DaWul (Spanish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Fiama (Spanish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Jax (Swedish)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>...and all other Transifex contributors!</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
@@ -275,7 +320,7 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Other Supporter</string>
|
||||
<string>Other Supporters && Contributors</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
@@ -283,9 +328,9 @@
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Al12 (Discord) </string>
|
||||
<string notr="true">AnyOldName3</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
@@ -308,6 +353,11 @@
|
||||
<string notr="true">Bridger</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Brixified</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">GamerPoet</string>
|
||||
@@ -323,6 +373,21 @@
|
||||
<string notr="true">GSDFan</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">ogrotten</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Schilduin</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">SuperSandro2000</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Uhuru</string>
|
||||
@@ -343,11 +408,6 @@
|
||||
<string notr="true">thosrtanner</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ogrotten</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ void CategoryFactory::saveCategories()
|
||||
}
|
||||
|
||||
|
||||
unsigned int CategoryFactory::countCategories(std::tr1::function<bool (const Category &category)> filter)
|
||||
unsigned int CategoryFactory::countCategories(std::function<bool (const Category &category)> filter)
|
||||
{
|
||||
unsigned int result = 0;
|
||||
for (const Category &cat : m_Categories) {
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ public:
|
||||
* @param filter the filter to test
|
||||
* @return number of matching categories
|
||||
*/
|
||||
unsigned int countCategories(std::tr1::function<bool (const Category &category)> filter);
|
||||
unsigned int countCategories(std::function<bool (const Category &category)> filter);
|
||||
|
||||
/**
|
||||
* @brief get the id of the parent category
|
||||
|
||||
+23
-25
@@ -1,28 +1,26 @@
|
||||
<?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="libeay32.dll"/>
|
||||
<file name="libEGL.dll"/>
|
||||
<file name="libGLESV2.dll"/>
|
||||
<file name="liblz4.dll"/>
|
||||
<file name="opengl32sw.dll"/>
|
||||
<file name="Qt5Core.dll"/>
|
||||
<file name="Qt5Gui.dll"/>
|
||||
<file name="Qt5Network.dll"/>
|
||||
<file name="Qt5Positioning.dll"/>
|
||||
<file name="Qt5PrintSupport.dll"/>
|
||||
<file name="Qt5Qml.dll"/>
|
||||
<file name="Qt5Quick.dll"/>
|
||||
<file name="Qt5QuickWidgets.dll"/>
|
||||
<file name="Qt5WebChannel.dll"/>
|
||||
<file name="Qt5SerialPort.dll"/>
|
||||
<file name="Qt5Svg.dll"/>
|
||||
<file name="Qt5WebEngineCore.dll"/>
|
||||
<file name="Qt5WebEngineWidgets.dll"/>
|
||||
<file name="Qt5Widgets.dll"/>
|
||||
<file name="Qt5WinExtras.dll"/>
|
||||
<file name="ssleay32.dll"/>
|
||||
<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="libEGL.dll"/>
|
||||
<file name="libGLESV2.dll"/>
|
||||
<file name="liblz4.dll"/>
|
||||
<file name="opengl32sw.dll"/>
|
||||
<file name="Qt5Core.dll"/>
|
||||
<file name="Qt5Gui.dll"/>
|
||||
<file name="Qt5Network.dll"/>
|
||||
<file name="Qt5Positioning.dll"/>
|
||||
<file name="Qt5PrintSupport.dll"/>
|
||||
<file name="Qt5Qml.dll"/>
|
||||
<file name="Qt5Quick.dll"/>
|
||||
<file name="Qt5QuickWidgets.dll"/>
|
||||
<file name="Qt5WebChannel.dll"/>
|
||||
<file name="Qt5SerialPort.dll"/>
|
||||
<file name="Qt5Svg.dll"/>
|
||||
<file name="Qt5WebEngineCore.dll"/>
|
||||
<file name="Qt5WebEngineWidgets.dll"/>
|
||||
<file name="Qt5Widgets.dll"/>
|
||||
<file name="Qt5WinExtras.dll"/>
|
||||
</assembly>
|
||||
@@ -318,7 +318,7 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel *
|
||||
if (hidden) {
|
||||
menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView()));
|
||||
} else {
|
||||
menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView()));
|
||||
menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView()));
|
||||
}
|
||||
} else if (state == DownloadManager::STATE_DOWNLOADING){
|
||||
menu.addAction(tr("Cancel"), this, SLOT(issueCancel()));
|
||||
@@ -334,8 +334,8 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel *
|
||||
menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll()));
|
||||
if (!hidden) {
|
||||
menu.addSeparator();
|
||||
menu.addAction(tr("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
|
||||
menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll()));
|
||||
menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
|
||||
menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll()));
|
||||
}
|
||||
menu.exec(mouseEvent->globalPos());
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem
|
||||
if (hidden) {
|
||||
menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView()));
|
||||
} else {
|
||||
menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView()));
|
||||
menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView()));
|
||||
}
|
||||
} else if (state == DownloadManager::STATE_DOWNLOADING){
|
||||
menu.addAction(tr("Cancel"), this, SLOT(issueCancel()));
|
||||
@@ -319,8 +319,8 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem
|
||||
menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll()));
|
||||
if (!hidden) {
|
||||
menu.addSeparator();
|
||||
menu.addAction(tr("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
|
||||
menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll()));
|
||||
menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
|
||||
menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll()));
|
||||
}
|
||||
menu.exec(mouseEvent->globalPos());
|
||||
|
||||
|
||||
+73
-12
@@ -460,7 +460,29 @@ void DownloadManager::addNXMDownload(const QString &url)
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto pair : m_PendingDownloads) {
|
||||
if (pair.first == nxmInfo.modId() && pair.second == nxmInfo.fileId()) {
|
||||
qDebug("download requested is already started (mod id: %s, file id: %s)", qPrintable(QString(nxmInfo.modId())), qPrintable(QString(nxmInfo.fileId())));
|
||||
QMessageBox::information(nullptr, tr("Already Started"), tr("A download for this mod file has already been queued."), QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (DownloadInfo *download : m_ActiveDownloads) {
|
||||
if (download->m_FileInfo->modID == nxmInfo.modId() && download->m_FileInfo->fileID == nxmInfo.fileId()) {
|
||||
if (download->m_State == STATE_DOWNLOADING || download->m_State == STATE_PAUSED || download->m_State == STATE_STARTED) {
|
||||
qDebug("download requested is already started (mod: %s, file: %s)", qPrintable(download->m_FileInfo->modName),
|
||||
qPrintable(download->m_FileInfo->fileName));
|
||||
|
||||
QMessageBox::information(nullptr, tr("Already Started"), tr("There is already a download started for this file (%2).")
|
||||
.arg(download->m_FileInfo->modName).arg(download->m_FileInfo->name), QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit aboutToUpdate();
|
||||
|
||||
m_PendingDownloads.append(std::make_pair(nxmInfo.modId(), nxmInfo.fileId()));
|
||||
|
||||
emit update(-1);
|
||||
@@ -558,18 +580,20 @@ void DownloadManager::removeDownload(int index, bool deleteFile)
|
||||
emit aboutToUpdate();
|
||||
|
||||
if (index < 0) {
|
||||
DownloadState minState = index < -1 ? STATE_INSTALLED : STATE_READY;
|
||||
index = 0;
|
||||
for (QVector<DownloadInfo*>::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) {
|
||||
if ((*iter)->m_State >= minState) {
|
||||
removeFile(index, deleteFile);
|
||||
delete *iter;
|
||||
iter = m_ActiveDownloads.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
DownloadState minState = index == -1 ? STATE_READY : STATE_INSTALLED;
|
||||
index = 0;
|
||||
for (QVector<DownloadInfo*>::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) {
|
||||
if ((*iter)->m_State >= minState) {
|
||||
removeFile(index, deleteFile);
|
||||
delete *iter;
|
||||
iter = m_ActiveDownloads.erase(iter);
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
else {
|
||||
++iter;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index >= m_ActiveDownloads.size()) {
|
||||
reportError(tr("remove: invalid download index %1").arg(index));
|
||||
@@ -878,6 +902,26 @@ void DownloadManager::markInstalled(int index)
|
||||
setState(m_ActiveDownloads.at(index), STATE_INSTALLED);
|
||||
}
|
||||
|
||||
void DownloadManager::markInstalled(QString fileName)
|
||||
{
|
||||
int index = indexByName(fileName);
|
||||
if (index >= 0) {
|
||||
markInstalled(index);
|
||||
} else {
|
||||
DownloadInfo *info = getDownloadInfo(fileName);
|
||||
if (info != nullptr) {
|
||||
QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat);
|
||||
metaFile.setValue("installed", true);
|
||||
metaFile.setValue("uninstalled", false);
|
||||
}
|
||||
delete info;
|
||||
}
|
||||
}
|
||||
|
||||
DownloadManager::DownloadInfo* DownloadManager::getDownloadInfo(QString fileName)
|
||||
{
|
||||
return DownloadInfo::createFromMeta(fileName, true);
|
||||
}
|
||||
|
||||
void DownloadManager::markUninstalled(int index)
|
||||
{
|
||||
@@ -893,6 +937,23 @@ void DownloadManager::markUninstalled(int index)
|
||||
}
|
||||
|
||||
|
||||
void DownloadManager::markUninstalled(QString fileName)
|
||||
{
|
||||
int index = indexByName(fileName);
|
||||
if (index >= 0) {
|
||||
markUninstalled(index);
|
||||
} else {
|
||||
QString filePath = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + fileName;
|
||||
DownloadInfo *info = getDownloadInfo(filePath);
|
||||
if (info != nullptr) {
|
||||
QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat);
|
||||
metaFile.setValue("uninstalled", true);
|
||||
}
|
||||
delete info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString DownloadManager::getDownloadFileName(const QString &baseName) const
|
||||
{
|
||||
QString fullPath = m_OutputDirectory + "/" + baseName;
|
||||
|
||||
@@ -307,6 +307,8 @@ public:
|
||||
*/
|
||||
void markInstalled(int index);
|
||||
|
||||
void markInstalled(QString download);
|
||||
|
||||
/**
|
||||
* @brief mark a download as uninstalled
|
||||
*
|
||||
@@ -314,6 +316,8 @@ public:
|
||||
*/
|
||||
void markUninstalled(int index);
|
||||
|
||||
void markUninstalled(QString download);
|
||||
|
||||
/**
|
||||
* @brief refreshes the list of downloads
|
||||
*/
|
||||
@@ -431,6 +435,7 @@ private slots:
|
||||
private:
|
||||
|
||||
void createMetaFile(DownloadInfo *info);
|
||||
DownloadManager::DownloadInfo* getDownloadInfo(QString fileName);
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -115,6 +115,9 @@ void EditExecutablesDialog::saveExecutable()
|
||||
m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(),
|
||||
ui->newFilesModBox->currentText());
|
||||
}
|
||||
else {
|
||||
m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+90
-11
@@ -73,24 +73,94 @@ void InstanceManager::setCurrentInstance(const QString &name)
|
||||
m_AppSettings.setValue(INSTANCE_KEY, name);
|
||||
}
|
||||
|
||||
bool InstanceManager::deleteLocalInstance(const QString &instanceId) const
|
||||
{
|
||||
bool result = true;
|
||||
QString instancePath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId);
|
||||
|
||||
QString InstanceManager::queryInstanceName() const
|
||||
if (QMessageBox::warning(nullptr, QObject::tr("Deleting folder"),
|
||||
QObject::tr("I'm about to delete the following folder: \"%1\". Proceed?").arg(instancePath), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::No ){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MOBase::shellDelete(QStringList(instancePath),true))
|
||||
{
|
||||
qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(instancePath), ::GetLastError());
|
||||
if (!MOBase::removeDir(instancePath))
|
||||
{
|
||||
qWarning("regular delete failed too");
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString InstanceManager::manageInstances(const QStringList &instanceList) const
|
||||
{
|
||||
SelectionDialog selection(
|
||||
QString("<h3>%1</h3><br>%2")
|
||||
.arg(QObject::tr("Choose Instance to Delete"))
|
||||
.arg(QObject::tr("Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")),
|
||||
nullptr);
|
||||
for (const QString &instance : instanceList)
|
||||
{
|
||||
selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance);
|
||||
}
|
||||
|
||||
if (selection.exec() == QDialog::Rejected) {
|
||||
return(chooseInstance(instances()));
|
||||
}
|
||||
else {
|
||||
QString choice = selection.getChoiceData().toString();
|
||||
{
|
||||
if (QMessageBox::warning(nullptr, QObject::tr("Are you sure?"),
|
||||
QObject::tr("Are you really sure you want to delete the Instance \"%1\" with all its files?").arg(choice), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
|
||||
{
|
||||
if (!deleteLocalInstance(choice))
|
||||
{
|
||||
QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"),
|
||||
QObject::tr("Could not delete Instance \"%1\". \nIf the folder was still in use, restart MO and try again.").arg(choice), QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return(manageInstances(instances()));
|
||||
}
|
||||
|
||||
QString InstanceManager::queryInstanceName(const QStringList &instanceList) const
|
||||
{
|
||||
QString instanceId;
|
||||
while (instanceId.isEmpty()) {
|
||||
QInputDialog dialog;
|
||||
|
||||
dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance"));
|
||||
dialog.setLabelText(QObject::tr("Enter a new name or select one from the sugested list (only letters and numbers allowed):"));
|
||||
// would be neat if we could take the names from the game plugins but
|
||||
// the required initialization order requires the ini file to be
|
||||
// available *before* we load plugins
|
||||
dialog.setComboBoxItems({ "Oblivion", "Skyrim", "SkyrimSE", "Fallout 3",
|
||||
"Fallout NV", "Fallout 4" });
|
||||
dialog.setComboBoxItems({ "NewName", "Fallout 4", "SkyrimSE", "Skyrim", "Fallout 3",
|
||||
"Fallout NV", "FO4VR", "Oblivion" });
|
||||
dialog.setComboBoxEditable(true);
|
||||
dialog.setWindowTitle(QObject::tr("Enter Instance Name"));
|
||||
dialog.setLabelText(QObject::tr("Name"));
|
||||
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
throw MOBase::MyException(QObject::tr("Canceled"));
|
||||
}
|
||||
instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), "");
|
||||
|
||||
bool alreadyExists=false;
|
||||
for (const QString &instance : instanceList) {
|
||||
if(instanceId==instance)
|
||||
alreadyExists=true;
|
||||
}
|
||||
if(alreadyExists)
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText( QObject::tr("The instance \"%1\" already exists.").arg(instanceId) );
|
||||
msgBox.setInformativeText(QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId));
|
||||
msgBox.exec();
|
||||
instanceId="";
|
||||
}
|
||||
}
|
||||
return instanceId;
|
||||
}
|
||||
@@ -100,7 +170,8 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const
|
||||
{
|
||||
enum class Special : uint8_t {
|
||||
NewInstance,
|
||||
Portable
|
||||
Portable,
|
||||
Manage
|
||||
};
|
||||
|
||||
SelectionDialog selection(
|
||||
@@ -108,10 +179,10 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const
|
||||
.arg(QObject::tr("Choose Instance"))
|
||||
.arg(QObject::tr(
|
||||
"Each Instance is a full set of MO data files (mods, "
|
||||
"downloads, profiles, configuration, ...). Use multiple "
|
||||
"instances for different games. If your MO folder is "
|
||||
"writable, you can also store a single instance locally (called "
|
||||
"a portable install).")),
|
||||
"downloads, profiles, configuration, ...). You can use multiple "
|
||||
"instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. "
|
||||
"If your MO folder is writable, you can also store a single instance locally (called "
|
||||
"a Portable install, and all the MO data files will be inside the installation folder).")),
|
||||
nullptr);
|
||||
selection.disableCancel();
|
||||
for (const QString &instance : instanceList) {
|
||||
@@ -128,6 +199,10 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const
|
||||
static_cast<uint8_t>(Special::Portable));
|
||||
}
|
||||
|
||||
selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"),
|
||||
QObject::tr("Delete an Instance."),
|
||||
static_cast<uint8_t>(Special::Manage));
|
||||
|
||||
if (selection.exec() == QDialog::Rejected) {
|
||||
qDebug("rejected");
|
||||
throw MOBase::MyException(QObject::tr("Canceled"));
|
||||
@@ -139,8 +214,12 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const
|
||||
return choice.toString();
|
||||
} else {
|
||||
switch (choice.value<uint8_t>()) {
|
||||
case Special::NewInstance: return queryInstanceName();
|
||||
case Special::NewInstance: return queryInstanceName(instanceList);
|
||||
case Special::Portable: return QString();
|
||||
case Special::Manage: {
|
||||
|
||||
return(manageInstances(instances()));
|
||||
}
|
||||
default: throw std::runtime_error("invalid selection");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,13 @@ private:
|
||||
|
||||
QStringList instances() const;
|
||||
|
||||
bool deleteLocalInstance(const QString &instanceId) const;
|
||||
|
||||
QString manageInstances(const QStringList &instanceList) const;
|
||||
|
||||
void setCurrentInstance(const QString &name);
|
||||
|
||||
QString queryInstanceName() const;
|
||||
QString queryInstanceName(const QStringList &instanceList) const;
|
||||
QString chooseInstance(const QStringList &instanceList) const;
|
||||
|
||||
void createDataPath(const QString &dataPath) const;
|
||||
|
||||
+163
-53
@@ -78,6 +78,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
@@ -139,6 +140,60 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// Parses the first parseArgCount arguments of the current process command line and returns
|
||||
// them in parsedArgs, the rest of the command line is returned untouched.
|
||||
LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector<std::wstring>& parsedArgs)
|
||||
{
|
||||
LPCWSTR cmd = GetCommandLineW();
|
||||
LPCWSTR arg = nullptr; // to skip executable name
|
||||
for (; parseArgCount >= 0 && *cmd; ++cmd)
|
||||
{
|
||||
if (*cmd == '"') {
|
||||
int escaped = 0;
|
||||
for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd)
|
||||
escaped = *cmd == '\\' ? escaped + 1 : 0;
|
||||
}
|
||||
if (*cmd == ' ') {
|
||||
if (arg)
|
||||
if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"')
|
||||
parsedArgs.push_back(std::wstring(arg+1, cmd-1));
|
||||
else
|
||||
parsedArgs.push_back(std::wstring(arg, cmd));
|
||||
arg = cmd + 1;
|
||||
--parseArgCount;
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) {
|
||||
PROCESS_INFORMATION pi{ 0 };
|
||||
STARTUPINFO si{ 0 };
|
||||
si.cb = sizeof(si);
|
||||
std::wstring commandLineCopy = commandLine;
|
||||
|
||||
if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) {
|
||||
// A bit of a problem where to log the error message here, at least this way you can get the message
|
||||
// using a either DebugView or a live debugger:
|
||||
std::wostringstream ost;
|
||||
ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError();
|
||||
OutputDebugStringW(ost.str().c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
DWORD exitCode = (DWORD)-1;
|
||||
::GetExitCodeProcess(pi.hProcess, &exitCode);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return static_cast<int>(exitCode);
|
||||
}
|
||||
|
||||
static DWORD WaitForProcess() {
|
||||
|
||||
}
|
||||
|
||||
static bool HaveWriteAccess(const std::wstring &path)
|
||||
{
|
||||
bool writable = false;
|
||||
@@ -347,6 +402,8 @@ void setupPath()
|
||||
qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(
|
||||
QCoreApplication::applicationDirPath())));
|
||||
|
||||
QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths());
|
||||
|
||||
boost::scoped_array<TCHAR> oldPath(new TCHAR[BUFSIZE]);
|
||||
DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE);
|
||||
if (offset > BUFSIZE) {
|
||||
@@ -354,20 +411,66 @@ void setupPath()
|
||||
::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset);
|
||||
}
|
||||
|
||||
std::wstring newPath(oldPath.get());
|
||||
std::wstring newPath(ToWString(QDir::toNativeSeparators(
|
||||
QCoreApplication::applicationDirPath())) + L"\\dlls");
|
||||
newPath += L";";
|
||||
newPath += ToWString(QDir::toNativeSeparators(
|
||||
QCoreApplication::applicationDirPath()))
|
||||
.c_str();
|
||||
newPath += L"\\dlls";
|
||||
newPath += oldPath.get();
|
||||
|
||||
::SetEnvironmentVariableW(L"PATH", newPath.c_str());
|
||||
}
|
||||
|
||||
static void preloadSsl()
|
||||
{
|
||||
QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath());
|
||||
if (GetModuleHandleA("libeay32.dll"))
|
||||
qWarning("libeay32.dll already loaded?!");
|
||||
else {
|
||||
QString libeay32 = appPath + "\\libeay32.dll";
|
||||
if (!QFile::exists(libeay32))
|
||||
qWarning("libeay32.dll not found: %s", qPrintable(libeay32));
|
||||
else if (!LoadLibraryW(libeay32.toStdWString().c_str()))
|
||||
qWarning("failed to load: %s, %d", qPrintable(libeay32), GetLastError());
|
||||
}
|
||||
if (GetModuleHandleA("ssleay32.dll"))
|
||||
qWarning("ssleay32.dll already loaded?!");
|
||||
else {
|
||||
QString ssleay32 = appPath + "\\ssleay32.dll";
|
||||
if (!QFile::exists(ssleay32))
|
||||
qWarning("ssleay32.dll not found: %s", qPrintable(ssleay32));
|
||||
else if (!LoadLibraryW(ssleay32.toStdWString().c_str()))
|
||||
qWarning("failed to load: %s, %d", qPrintable(ssleay32), GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
static QString getVersionDisplayString()
|
||||
{
|
||||
VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath()));
|
||||
return VersionInfo(version.dwFileVersionMS >> 16,
|
||||
version.dwFileVersionMS & 0xFFFF,
|
||||
version.dwFileVersionLS >> 16,
|
||||
version.dwFileVersionLS & 0xFFFF).displayString();
|
||||
}
|
||||
|
||||
int runApplication(MOApplication &application, SingleInstance &instance,
|
||||
const QString &splashPath)
|
||||
{
|
||||
qDebug("start main application");
|
||||
|
||||
qDebug("Starting Mod Organizer version %s revision %s", qPrintable(getVersionDisplayString()),
|
||||
#if defined(HGID)
|
||||
HGID
|
||||
#elif defined(GITID)
|
||||
GITID
|
||||
#else
|
||||
"unknown"
|
||||
#endif
|
||||
);
|
||||
|
||||
#if !defined(QT_NO_SSL)
|
||||
preloadSsl();
|
||||
qDebug("ssl support: %d", QSslSocket::supportsSsl());
|
||||
#else
|
||||
qDebug("non-ssl build");
|
||||
#endif
|
||||
|
||||
QString dataPath = application.property("dataPath").toString();
|
||||
qDebug("data path: %s", qPrintable(dataPath));
|
||||
@@ -389,6 +492,14 @@ int runApplication(MOApplication &application, SingleInstance &instance,
|
||||
// global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime
|
||||
OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt());
|
||||
|
||||
qDebug("Loaded settings:");
|
||||
settings.beginGroup("Settings");
|
||||
for (auto k : settings.allKeys())
|
||||
if (!k.contains("username") && !k.contains("password"))
|
||||
qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data());
|
||||
settings.endGroup();
|
||||
|
||||
|
||||
qDebug("initializing core");
|
||||
OrganizerCore organizer(settings);
|
||||
if (!organizer.bootstrap()) {
|
||||
@@ -450,36 +561,42 @@ int runApplication(MOApplication &application, SingleInstance &instance,
|
||||
|
||||
// if we have a command line parameter, it is either a nxm link or
|
||||
// a binary to start
|
||||
if (arguments.size() > 1) {
|
||||
if (MOShortcut shortcut{ arguments.at(1) }) {
|
||||
try {
|
||||
organizer.runShortcut(shortcut);
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
reportError(
|
||||
QObject::tr("failed to start shortcut: %1").arg(e.what()));
|
||||
return 1;
|
||||
}
|
||||
} else if (OrganizerCore::isNxmLink(arguments.at(1))) {
|
||||
qDebug("starting download from command line: %s",
|
||||
qPrintable(arguments.at(1)));
|
||||
organizer.externalMessage(arguments.at(1));
|
||||
} else {
|
||||
QString exeName = arguments.at(1);
|
||||
qDebug("starting %s from command line", qPrintable(exeName));
|
||||
arguments.removeFirst(); // remove application name (ModOrganizer.exe)
|
||||
arguments.removeFirst(); // remove binary name
|
||||
// pass the remaining parameters to the binary
|
||||
try {
|
||||
organizer.startApplication(exeName, arguments, QString(), QString());
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
reportError(
|
||||
QObject::tr("failed to start application: %1").arg(e.what()));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arguments.size() > 1) {
|
||||
if (MOShortcut shortcut{ arguments.at(1) }) {
|
||||
if (shortcut.hasExecutable()) {
|
||||
try {
|
||||
organizer.runShortcut(shortcut);
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
reportError(
|
||||
QObject::tr("failed to start shortcut: %1").arg(e.what()));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (OrganizerCore::isNxmLink(arguments.at(1))) {
|
||||
qDebug("starting download from command line: %s",
|
||||
qPrintable(arguments.at(1)));
|
||||
organizer.externalMessage(arguments.at(1));
|
||||
}
|
||||
else {
|
||||
QString exeName = arguments.at(1);
|
||||
qDebug("starting %s from command line", qPrintable(exeName));
|
||||
arguments.removeFirst(); // remove application name (ModOrganizer.exe)
|
||||
arguments.removeFirst(); // remove binary name
|
||||
// pass the remaining parameters to the binary
|
||||
try {
|
||||
organizer.startApplication(exeName, arguments, QString(), QString());
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
reportError(
|
||||
QObject::tr("failed to start application: %1").arg(e.what()));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap pixmap(splashPath);
|
||||
QSplashScreen splash(pixmap);
|
||||
@@ -525,28 +642,21 @@ int runApplication(MOApplication &application, SingleInstance &instance,
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
//Should allow for better scaling of ui with higher resolution displays
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
|
||||
if (argc >= 4) {
|
||||
std::vector<std::wstring> arg;
|
||||
auto args = UntouchedCommandLineArguments(2, arg);
|
||||
if (arg[0] == L"launch")
|
||||
return SpawnWaitProcess(arg[1].c_str(), args);
|
||||
}
|
||||
|
||||
MOApplication application(argc, argv);
|
||||
QStringList arguments = application.arguments();
|
||||
|
||||
if ((arguments.length() >= 4) && (arguments.at(1) == "launch")) {
|
||||
// all we're supposed to do is launch another process
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(QDir::fromNativeSeparators(arguments.at(2)));
|
||||
process.setProgram(QDir::fromNativeSeparators(arguments.at(3)));
|
||||
process.setArguments(arguments.mid(4));
|
||||
process.start();
|
||||
process.waitForFinished(-1);
|
||||
return process.exitCode();
|
||||
}
|
||||
|
||||
setupPath();
|
||||
|
||||
#if !defined(QT_NO_SSL)
|
||||
qDebug("ssl support: %d", QSslSocket::supportsSsl());
|
||||
#else
|
||||
qDebug("non-ssl build");
|
||||
#endif
|
||||
|
||||
bool forcePrimary = false;
|
||||
if (arguments.contains("update")) {
|
||||
arguments.removeAll("update");
|
||||
|
||||
+407
-108
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user