Compare commits

...

48 Commits
1.4.0 ... 1.4.2

Author SHA1 Message Date
6e65cd4405 Merge pull request #1118 from flowln/backport_major_version_filter_fix 2022-09-08 18:54:56 +02:00
98fbb3613d refactor: create mod pages and filter widget by factory methods
This takes most expensive operations out of the constructors.

Signed-off-by: flow <flowlnlnln@gmail.com>
2022-09-08 12:47:37 -03:00
2666beafb1 fix: use more robust method of finding matches for major version
This uses the proper version list to find all MC versions matching the
major number (_don't say anything about SemVer_ 🔫).

Signed-off-by: flow <flowlnlnln@gmail.com>
2022-09-08 12:37:11 -03:00
cf8a76be6b chore: bump version
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-09-08 15:24:48 +02:00
e96f482544 Merge pull request #965 from flowln/fat_files_in_memory
Refactor a bit EnsureMetadataTask and calculate hashes in a incremental manner
2022-09-08 14:35:59 +02:00
dc32f0671b Merge pull request #941 from Scrumplex/bump-cxx-standard
Bump to C++17
2022-09-08 14:35:30 +02:00
d6d7794cd0 Merge pull request #1107 from DioEgizio/smaller-about 2022-09-08 13:11:06 +02:00
94d95d45d4 Merge pull request #1095 from flowln/ensure_file_path_in_override 2022-09-08 13:11:06 +02:00
5e767a91d9 Merge pull request #1080 from flowln/eternal_cache
Never invalidate libraries cache entries by time elapsed
2022-09-08 13:11:06 +02:00
c89f8b4657 Merge pull request #1087 from DioEgizio/fix-ftblegacy-url
fix: fix urls on ftb legacy
2022-09-08 13:11:06 +02:00
d7fc0f53d3 Merge pull request #1073 from DioEgizio/update-copying
fix(COPYING): fix COPYING.md by adding some missing copyright notices
2022-09-08 13:11:04 +02:00
e81cf8a845 Merge pull request #1035 from Scrumplex/fix-coremods
Make Coremods / Mods seperation more clear
2022-09-08 13:10:37 +02:00
c116885ab1 Merge pull request #1044 from flowln/better_orphan_fix 2022-09-08 13:10:37 +02:00
d68d5ca23f Merge pull request #1007 from Gingeh/disable-update-button 2022-09-08 13:10:37 +02:00
6d94338a56 Merge pull request #1058 from DioEgizio/fix-update-org.polymc.PolyMC.metainfo.xml.in 2022-09-08 13:10:37 +02:00
1e1a1cef05 Merge pull request #1049 from flowln/waiting_for_news_-_- 2022-09-08 13:10:37 +02:00
34bab3e1b2 Merge pull request #968 from magneticflux-/utf8-logging
Decode process lines as UTF-8
2022-09-08 13:10:37 +02:00
be6d6501e8 Merge pull request #1039 from budak7273/fix-world-safety-nag-title-text 2022-09-08 13:10:37 +02:00
10a70732ce Merge pull request #920 from flowln/metacache_fix 2022-09-08 13:10:37 +02:00
a725dc82a7 Merge pull request #1018 from Scrumplex/fix-infinite-auth-loop 2022-09-08 13:10:37 +02:00
97ce8a94e9 Merge pull request #1017 from flowln/kill_orphan_metadata
Remove orphaned metadata to avoid problems with auto-updating instances
2022-09-08 13:10:37 +02:00
85f0904872 Merge pull request #1014 from DioEgizio/downgrade-qt-macos
chore: downgrade to Qt 6.3.0 on macos
2022-09-08 13:10:37 +02:00
cb0a5e42df Merge pull request #1006 from DioEgizio/appimage-ubuntu-moment
fix: work around ubuntu 22.04 openssl appimage issues by copying openssl libs
2022-09-08 13:10:36 +02:00
a0c7fa30c0 Merge pull request #1019 from Scrumplex/fix-openbsd-root
Add root path detection on OpenBSD
2022-09-08 13:10:36 +02:00
b7490b479c Merge pull request #970 from PolyMC/Bump-1.4.1
bump version to 1.4.1
2022-07-28 09:42:56 +01:00
74120fe1f3 Merge pull request #699 from Scrumplex/resolve-ftb-mods-cf 2022-07-28 09:43:39 +02:00
336f1f4f50 Merge pull request #974 from flowln/accounts_qt6
Fix adding multiple accounts in Qt6
2022-07-26 16:08:05 -03:00
1c256d8876 Merge pull request #958 from jopejoe1/readme 2022-07-27 00:16:35 +05:30
1ce0f0e7a5 fix: progress dialog going away even if the task was not aborted
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-26 20:33:33 +02:00
fbf1901d86 refactor: shuffle some things around to improve readability
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-26 20:33:33 +02:00
0382f33c46 fix(ui): pump events to show "Copying modpack files..." text
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-26 20:33:33 +02:00
13372f3f99 chore: clean up FTBPackInstallTask a bit and connect missing signals
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-26 20:33:33 +02:00
e741cb7f0a fix: add abort handlign in Flame's FileResolvingTask
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-26 20:33:33 +02:00
fb289c6b17 chore: add license headers
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-07-26 20:33:29 +02:00
75a7ea55d4 feat: implement mod resolving for FTB
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-07-26 20:33:10 +02:00
86573a5ccd Merge pull request #961 from flowln/fix_resource_filter 2022-07-26 20:14:18 +02:00
6fe55a79f1 fix: use const qualifier for operator==() and allow other comparisons
This fixes an implicit behavior changed by C++17.

Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-26 15:11:56 -03:00
1a6cb9ee99 chore: add some debugging prints in AccountList
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-26 15:11:56 -03:00
a495d9eca5 chore: Normalize sentence in readme
Signed-off-by: txtsd <thexerothermicsclerodermoid@gmail.com>
2022-07-26 22:00:40 +05:30
65a945f968 Merge pull request #956 from flowln/jar_mods_aa
Fix segmentation fault when using jar mods
2022-07-25 06:57:40 +01:00
0d35edbbf3 bump version 2022-07-25 06:44:34 +01:00
3aa2003951 fix: filter in external resource pages not working
For some reason, using setFilterFixedString() doesn't seem to update the
QRegularExpression object with a new value, instead leaving it empty. It
updates QRegExp just fine, so maybe that's an Qt bug? o.O

Anyway, using regex in the filter is kinda cool actually :D

Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-23 23:50:56 -03:00
0e473f4570 Merge pull request #951 from flowln/performace_tab_fix
Hide the entire performance tab on the instance settings in non-Linux OSes
2022-07-23 18:31:04 -03:00
bfa824ee71 Fix broken url in readme
Signed-off-by: jopejoe1 <johannes@joens.email>
2022-07-23 23:22:39 +02:00
1157436a24 fix: sigsegv when trying to use jar mods
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-23 14:24:24 -03:00
4596e78df0 Merge pull request #952 from Scrumplex/fix-pre-post-launch
Fix variable substitution in pre launch/post exit hooks
2022-07-23 18:06:12 +01:00
813dfbd2d3 fix: hide the entire performance tab on instance settings in non-Linux
"just woke up after a major release has just been made" coding style

Signed-off-by: flow <flowlnlnln@gmail.com>
2022-07-23 13:29:39 -03:00
ba7dfb360c fix: actually substitute variables in pre/post launch
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-07-23 17:51:58 +02:00
80 changed files with 1246 additions and 1122 deletions

View File

@ -43,7 +43,7 @@ jobs:
macosx_deployment_target: 10.14 macosx_deployment_target: 10.14
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_version: '6.3.1' qt_version: '6.3.0'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_path: /Users/runner/work/PolyMC/Qt qt_path: /Users/runner/work/PolyMC/Qt
@ -315,6 +315,9 @@ jobs:
cp -r /home/runner/work/PolyMC/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines cp -r /home/runner/work/PolyMC/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}//usr/lib/
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64"

View File

@ -29,10 +29,10 @@ set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
######## Set compiler flags ######## ######## Set compiler flags ########
set(CMAKE_CXX_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD_REQUIRED true)
set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) include(GenerateExportHeader)
set(CMAKE_CXX_FLAGS "-Wall -pedantic -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE) if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif() endif()
@ -81,7 +81,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 4) set(Launcher_VERSION_MINOR 4)
set(Launcher_VERSION_HOTFIX 0) set(Launcher_VERSION_HOTFIX 2)
# Build number # Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@ -319,7 +319,6 @@ endif()
add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # class parser library add_subdirectory(libraries/classparser) # class parser library
add_subdirectory(libraries/optional-bare)
add_subdirectory(libraries/tomlc99) # toml parser add_subdirectory(libraries/tomlc99) # toml parser
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
add_subdirectory(libraries/gamemode) add_subdirectory(libraries/gamemode)

View File

@ -32,27 +32,47 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
# MinGW runtime (Windows) # MinGW-w64 runtime (Windows)
Copyright (c) 2012 MinGW.org project Copyright (c) 2009, 2010, 2011, 2012, 2013 by the mingw-w64 project
Permission is hereby granted, free of charge, to any person obtaining a This license has been certified as open source. It has also been designated
copy of this software and associated documentation files (the "Software"), as GPL compatible by the Free Software Foundation (FSF).
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice, this permission notice and the below disclaimer Redistribution and use in source and binary forms, with or without
shall be included in all copies or substantial portions of the Software. modification, are permitted provided that the following conditions are met:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1. Redistributions in source code must retain the accompanying copyright
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, notice, this list of conditions, and the following disclaimer.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 2. Redistributions in binary form must reproduce the accompanying
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER copyright notice, this list of conditions, and the following disclaimer
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING in the documentation and/or other materials provided with the
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER distribution.
DEALINGS IN THE SOFTWARE. 3. Names of the copyright holders must not be used to endorse or promote
products derived from this software without prior written permission
from the copyright holders.
4. The right to distribute this software or to use it for any purpose does
not give you the right to use Servicemarks (sm) or Trademarks (tm) of
the copyright holders. Use of them is covered by separate agreement
with the copyright holders.
5. If any files are modified, you must cause the modified files to carry
prominent notices stating that you changed the files and the date of
any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Information on third party licenses used in MinGW-w64 can be found in its COPYING.MinGW-w64-runtime.txt.
# Qt 5/6 # Qt 5/6
@ -295,34 +315,6 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
# optional-bare
Code from https://github.com/martinmoene/optional-bare/
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
# tomlc99 # tomlc99
MIT License MIT License
@ -373,3 +365,32 @@
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## Gamemode
Copyright (c) 2017-2022, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -64,7 +64,7 @@ The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate.
## Download information ## Download information
To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/blob/master/src/download.md) To modify download information or change packaging information send a pull request or issue to the website [here](https://github.com/PolyMC/polymc.github.io/tree/master/src/download).
## Forking/Redistributing/Custom builds policy ## Forking/Redistributing/Custom builds policy

View File

@ -321,7 +321,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{ {
// Root path is used for updates and portable data // Root path is used for updates and portable data
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr
m_rootPath = foo.absolutePath(); m_rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
@ -864,6 +864,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath()); m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath()); m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath()); m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath()); m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
@ -1258,6 +1259,9 @@ bool Application::launch(
} }
connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded); connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded);
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed); connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
connect(controller.get(), &LaunchController::aborted, this, [this] {
controllerFailed(tr("Aborted"));
});
addRunningInstance(); addRunningInstance();
controller->start(); controller->start();
return true; return true;

View File

@ -494,6 +494,8 @@ set(API_SOURCES
modplatform/modrinth/ModrinthAPI.cpp modplatform/modrinth/ModrinthAPI.cpp
modplatform/helpers/NetworkModAPI.h modplatform/helpers/NetworkModAPI.h
modplatform/helpers/NetworkModAPI.cpp modplatform/helpers/NetworkModAPI.cpp
modplatform/helpers/HashUtils.h
modplatform/helpers/HashUtils.cpp
) )
set(FTB_SOURCES set(FTB_SOURCES
@ -988,7 +990,6 @@ target_link_libraries(Launcher_logic
Launcher_murmur2 Launcher_murmur2
nbt++ nbt++
${ZLIB_LIBRARIES} ${ZLIB_LIBRARIES}
optional-bare
tomlc99 tomlc99
BuildConfig BuildConfig
Katabasis Katabasis

View File

@ -516,6 +516,7 @@ bool overrideFolder(QString overwritten_path, QString override_path)
for (auto file : listFolderPaths(root_override)) { for (auto file : listFolderPaths(root_override)) {
QString destination = file; QString destination = file;
destination.replace(override_path, overwritten_path); destination.replace(override_path, overwritten_path);
ensureFilePathExists(destination);
qDebug() << QString("Applying override %1 in %2").arg(file, destination); qDebug() << QString("Applying override %1 in %2").arg(file, destination);

View File

@ -44,7 +44,7 @@
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "modplatform/flame/PackManifest.h" #include "modplatform/flame/PackManifest.h"
#include <nonstd/optional> #include <optional>
class QuaZip; class QuaZip;
namespace Flame namespace Flame
@ -90,8 +90,8 @@ private: /* data */
QString m_archivePath; QString m_archivePath;
bool m_downloadRequired = false; bool m_downloadRequired = false;
std::unique_ptr<QuaZip> m_packZip; std::unique_ptr<QuaZip> m_packZip;
QFuture<nonstd::optional<QStringList>> m_extractFuture; QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
QVector<Flame::File> m_blockedMods; QVector<Flame::File> m_blockedMods;
enum class ModpackType{ enum class ModpackType{
Unknown, Unknown,

View File

@ -145,16 +145,26 @@ void LaunchController::login() {
return; return;
} }
// we try empty password first :)
QString password;
// we loop until the user succeeds in logging in or gives up // we loop until the user succeeds in logging in or gives up
bool tryagain = true; bool tryagain = true;
// the failure. the default failure. unsigned int tries = 0;
const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. <br /> <br /> This could be caused by a password change.");
QString failReason = needLoginAgain;
while (tryagain) while (tryagain)
{ {
if (tries > 0 && tries % 3 == 0) {
auto result = QMessageBox::question(
m_parentWidget,
tr("Continue launch?"),
tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?")
.arg(tries)
);
if (result == QMessageBox::No) {
emitAborted();
return;
}
}
tries++;
m_session = std::make_shared<AuthSession>(); m_session = std::make_shared<AuthSession>();
m_session->wants_online = m_online; m_session->wants_online = m_online;
m_accountToUse->fillSession(m_session); m_accountToUse->fillSession(m_session);

View File

@ -34,8 +34,9 @@
*/ */
#include "LoggedProcess.h" #include "LoggedProcess.h"
#include "MessageLevel.h"
#include <QDebug> #include <QDebug>
#include <QTextDecoder>
#include "MessageLevel.h"
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{ {
@ -59,25 +60,26 @@ LoggedProcess::~LoggedProcess()
} }
} }
QStringList reprocess(const QByteArray & data, QString & leftover) QStringList reprocess(const QByteArray& data, QTextDecoder& decoder)
{ {
QString str = leftover + QString::fromLocal8Bit(data); auto str = decoder.toUnicode(data);
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
str.remove('\r'); auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
QStringList lines = str.split("\n"); #else
leftover = lines.takeLast(); auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
#endif
return lines; return lines;
} }
void LoggedProcess::on_stdErr() void LoggedProcess::on_stdErr()
{ {
auto lines = reprocess(readAllStandardError(), m_err_leftover); auto lines = reprocess(readAllStandardError(), m_err_decoder);
emit log(lines, MessageLevel::StdErr); emit log(lines, MessageLevel::StdErr);
} }
void LoggedProcess::on_stdOut() void LoggedProcess::on_stdOut()
{ {
auto lines = reprocess(readAllStandardOutput(), m_out_leftover); auto lines = reprocess(readAllStandardOutput(), m_out_decoder);
emit log(lines, MessageLevel::StdOut); emit log(lines, MessageLevel::StdOut);
} }
@ -86,18 +88,6 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
// save the exit code // save the exit code
m_exit_code = exit_code; m_exit_code = exit_code;
// Flush console window
if (!m_err_leftover.isEmpty())
{
emit log({m_err_leftover}, MessageLevel::StdErr);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
emit log({m_err_leftover}, MessageLevel::StdOut);
m_out_leftover.clear();
}
// based on state, send signals // based on state, send signals
if (!m_is_aborting) if (!m_is_aborting)
{ {

View File

@ -36,6 +36,7 @@
#pragma once #pragma once
#include <QProcess> #include <QProcess>
#include <QTextDecoder>
#include "MessageLevel.h" #include "MessageLevel.h"
/* /*
@ -88,8 +89,8 @@ private:
void changeState(LoggedProcess::State state); void changeState(LoggedProcess::State state);
private: private:
QString m_err_leftover; QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
QString m_out_leftover; QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
bool m_killed = false; bool m_killed = false;
State m_state = NotRunning; State m_state = NotRunning;
int m_exit_code = 0; int m_exit_code = 0;

View File

@ -141,9 +141,10 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
QSet<QString> addedFiles; QSet<QString> addedFiles;
// Modify the jar // Modify the jar
for (auto i = mods.constEnd(); i != mods.constBegin(); --i) // This needs to be done in reverse-order to ensure we respect the loading order of components
for (auto i = mods.crbegin(); i != mods.crend(); i++)
{ {
const Mod* mod = *i; const auto* mod = *i;
// do not merge disabled mods. // do not merge disabled mods.
if (!mod->enabled()) if (!mod->enabled())
continue; continue;
@ -267,7 +268,7 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
// ours // ours
nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{ {
QDir directory(target); QDir directory(target);
QStringList extracted; QStringList extracted;
@ -276,7 +277,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
auto numEntries = zip->getEntriesCount(); auto numEntries = zip->getEntriesCount();
if(numEntries < 0) { if(numEntries < 0) {
qWarning() << "Failed to enumerate files in archive"; qWarning() << "Failed to enumerate files in archive";
return nonstd::nullopt; return std::nullopt;
} }
else if(numEntries == 0) { else if(numEntries == 0) {
qDebug() << "Extracting empty archives seems odd..."; qDebug() << "Extracting empty archives seems odd...";
@ -285,7 +286,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
else if (!zip->goToFirstFile()) else if (!zip->goToFirstFile())
{ {
qWarning() << "Failed to seek to first file in zip"; qWarning() << "Failed to seek to first file in zip";
return nonstd::nullopt; return std::nullopt;
} }
do do
@ -322,7 +323,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
{ {
qWarning() << "Failed to extract file" << original_name << "to" << absFilePath; qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
JlCompress::removeFile(extracted); JlCompress::removeFile(extracted);
return nonstd::nullopt; return std::nullopt;
} }
extracted.append(absFilePath); extracted.append(absFilePath);
@ -340,7 +341,7 @@ bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &tar
} }
// ours // ours
nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir) std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
{ {
QuaZip zip(fileCompressed); QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
@ -351,13 +352,13 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
return QStringList(); return QStringList();
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
return nonstd::nullopt; return std::nullopt;
} }
return MMCZip::extractSubDir(&zip, "", dir); return MMCZip::extractSubDir(&zip, "", dir);
} }
// ours // ours
nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir) std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
{ {
QuaZip zip(fileCompressed); QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
@ -368,7 +369,7 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
return QStringList(); return QStringList();
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
return nonstd::nullopt; return std::nullopt;
} }
return MMCZip::extractSubDir(&zip, subdir, dir); return MMCZip::extractSubDir(&zip, subdir, dir);
} }

View File

@ -42,7 +42,7 @@
#include <functional> #include <functional>
#include <quazip/JlCompress.h> #include <quazip/JlCompress.h>
#include <nonstd/optional> #include <optional>
namespace MMCZip namespace MMCZip
{ {
@ -95,7 +95,7 @@ namespace MMCZip
/** /**
* Extract a subdirectory from an archive * Extract a subdirectory from an archive
*/ */
nonstd::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); std::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
bool extractRelFile(QuaZip *zip, const QString & file, const QString &target); bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
@ -106,7 +106,7 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty. * \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure. * \return The list of the full paths of the files extracted, empty on failure.
*/ */
nonstd::optional<QStringList> extractDir(QString fileCompressed, QString dir); std::optional<QStringList> extractDir(QString fileCompressed, QString dir);
/** /**
* Extract a subdirectory from an archive * Extract a subdirectory from an archive
@ -116,7 +116,7 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty. * \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure. * \return The list of the full paths of the files extracted, empty on failure.
*/ */
nonstd::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir); std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
/** /**
* Extract a single file from an archive into a directory * Extract a single file from an archive into a directory

View File

@ -77,10 +77,12 @@ public:
{ {
return m_ptr; return m_ptr;
} }
bool operator==(const shared_qobject_ptr<T>& other) { template<typename U>
bool operator==(const shared_qobject_ptr<U>& other) const {
return m_ptr == other.m_ptr; return m_ptr == other.m_ptr;
} }
bool operator!=(const shared_qobject_ptr<T>& other) { template<typename U>
bool operator!=(const shared_qobject_ptr<U>& other) const {
return m_ptr != other.m_ptr; return m_ptr != other.m_ptr;
} }

View File

@ -282,35 +282,22 @@ void LaunchTask::emitFailed(QString reason)
Task::emitFailed(reason); Task::emitFailed(reason);
} }
void LaunchTask::substituteVariables(const QStringList &args) const void LaunchTask::substituteVariables(QStringList &args) const
{ {
auto variables = m_instance->getVariables(); auto env = m_instance->createEnvironment();
auto envVariables = QProcessEnvironment::systemEnvironment();
for (auto arg : args) { for (auto key : env.keys())
for (auto key : variables)
{ {
arg.replace("$" + key, variables.value(key)); args.replaceInStrings("$" + key, env.value(key));
}
for (auto env : envVariables.keys())
{
arg.replace("$" + env, envVariables.value(env));
}
} }
} }
QString LaunchTask::substituteVariables(const QString &cmd) const void LaunchTask::substituteVariables(QString &cmd) const
{ {
QString out = cmd; auto env = m_instance->createEnvironment();
auto variables = m_instance->getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it) for (auto key : env.keys())
{ {
out.replace("$" + it.key(), it.value()); cmd.replace("$" + key, env.value(key));
} }
auto env = QProcessEnvironment::systemEnvironment();
for (auto var : env.keys())
{
out.replace("$" + var, env.value(var));
}
return out;
} }

View File

@ -105,8 +105,8 @@ public: /* methods */
shared_qobject_ptr<LogModel> getLogModel(); shared_qobject_ptr<LogModel> getLogModel();
public: public:
void substituteVariables(const QStringList &args) const; void substituteVariables(QStringList &args) const;
QString substituteVariables(const QString &cmd) const; void substituteVariables(QString &cmd) const;
QString censorPrivateInfo(QString in); QString censorPrivateInfo(QString in);
protected: /* methods */ protected: /* methods */

View File

@ -56,9 +56,10 @@ void PostLaunchCommand::executeTask()
const QString program = args.takeFirst(); const QString program = args.takeFirst();
m_process.start(program, args); m_process.start(program, args);
#else #else
QString postlaunch_cmd = m_parent->substituteVariables(m_command); m_parent->substituteVariables(m_command);
emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::Launcher);
m_process.start(postlaunch_cmd); emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher);
m_process.start(m_command);
#endif #endif
} }

View File

@ -56,9 +56,10 @@ void PreLaunchCommand::executeTask()
const QString program = args.takeFirst(); const QString program = args.takeFirst();
m_process.start(program, args); m_process.start(program, args);
#else #else
QString prelaunch_cmd = m_parent->substituteVariables(m_command); m_parent->substituteVariables(m_command);
emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::Launcher);
m_process.start(prelaunch_cmd); emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher);
m_process.start(m_command);
#endif #endif
} }

View File

@ -140,6 +140,13 @@ VersionPtr VersionList::getVersion(const QString &version)
return out; return out;
} }
bool VersionList::hasVersion(QString version) const
{
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
[&](Meta::VersionPtr const& a){ return a->version() == version; });
return (ver != m_versions.constEnd());
}
void VersionList::setName(const QString &name) void VersionList::setName(const QString &name)
{ {
m_name = name; m_name = name;

View File

@ -66,6 +66,7 @@ public:
QString humanReadable() const; QString humanReadable() const;
VersionPtr getVersion(const QString &version); VersionPtr getVersion(const QString &version);
bool hasVersion(QString version) const;
QVector<VersionPtr> versions() const QVector<VersionPtr> versions() const
{ {

View File

@ -88,6 +88,9 @@ QList<NetAction::Ptr> Library::getDownloads(
options |= Net::Download::Option::AcceptLocalFiles; options |= Net::Download::Option::AcceptLocalFiles;
} }
// Don't add a time limit for the libraries cache entry validity
options |= Net::Download::Option::MakeEternal;
if(sha1.size()) if(sha1.size())
{ {
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());

View File

@ -53,12 +53,12 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <nonstd/optional> #include <optional>
using nonstd::optional; using std::optional;
using nonstd::nullopt; using std::nullopt;
GameType::GameType(nonstd::optional<int> original): GameType::GameType(std::optional<int> original):
original(original) original(original)
{ {
if(!original) { if(!original) {

View File

@ -16,11 +16,11 @@
#pragma once #pragma once
#include <QFileInfo> #include <QFileInfo>
#include <QDateTime> #include <QDateTime>
#include <nonstd/optional> #include <optional>
struct GameType { struct GameType {
GameType() = default; GameType() = default;
GameType (nonstd::optional<int> original); GameType (std::optional<int> original);
QString toTranslatedString() const; QString toTranslatedString() const;
QString toLogString() const; QString toLogString() const;
@ -33,7 +33,7 @@ struct GameType {
Adventure, Adventure,
Spectator Spectator
} type = Unknown; } type = Unknown;
nonstd::optional<int> original; std::optional<int> original;
}; };
class World class World

View File

@ -109,8 +109,10 @@ QStringList AccountList::profileNames() const {
void AccountList::addAccount(const MinecraftAccountPtr account) void AccountList::addAccount(const MinecraftAccountPtr account)
{ {
// NOTE: Do not allow adding something that's already there // NOTE: Do not allow adding something that's already there. We shouldn't let it continue
if(m_accounts.contains(account)) { // because of the signal / slot connections after this.
if (m_accounts.contains(account)) {
qDebug() << "Tried to add account that's already on the accounts list!";
return; return;
} }
@ -123,6 +125,8 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
if(profileId.size()) { if(profileId.size()) {
auto existingAccount = findAccountByProfileId(profileId); auto existingAccount = findAccountByProfileId(profileId);
if(existingAccount != -1) { if(existingAccount != -1) {
qDebug() << "Replacing old account with a new one with the same profile ID!";
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount]; MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
m_accounts[existingAccount] = account; m_accounts[existingAccount] = account;
if(m_defaultAccount == existingAccountPtr) { if(m_defaultAccount == existingAccountPtr) {
@ -138,9 +142,12 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
// if we don't have this profileId yet, add the account to the end // if we don't have this profileId yet, add the account to the end
int row = m_accounts.count(); int row = m_accounts.count();
qDebug() << "Inserting account at index" << row;
beginInsertRows(QModelIndex(), row, row); beginInsertRows(QModelIndex(), row, row);
m_accounts.append(account); m_accounts.append(account);
endInsertRows(); endInsertRows();
onListChanged(); onListChanged();
} }

View File

@ -63,6 +63,9 @@ void ModFolderModel::startWatching()
if(is_watching) if(is_watching)
return; return;
// Remove orphaned metadata next time
m_first_folder_load = true;
update(); update();
// Watch the mods folder // Watch the mods folder
@ -113,7 +116,8 @@ bool ModFolderModel::update()
} }
auto index_dir = indexDir(); auto index_dir = indexDir();
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed); auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
m_first_folder_load = false;
m_update = task->result(); m_update = task->result();

View File

@ -172,6 +172,7 @@ protected:
bool interaction_disabled = false; bool interaction_disabled = false;
QDir m_dir; QDir m_dir;
bool m_is_indexed; bool m_is_indexed;
bool m_first_folder_load = true;
QMap<QString, int> modsIndex; QMap<QString, int> modsIndex;
QMap<int, LocalModParseTask::ResultPtr> activeTickets; QMap<int, LocalModParseTask::ResultPtr> activeTickets;
int nextResolutionTicket = 0; int nextResolutionTicket = 0;

View File

@ -38,8 +38,8 @@
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed) ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan)
: m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result()) : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result())
{} {}
void ModFolderLoadTask::run() void ModFolderLoadTask::run()
@ -83,6 +83,19 @@ void ModFolderLoadTask::run()
} }
} }
// Remove orphan metadata to prevent issues
// See https://github.com/PolyMC/PolyMC/issues/996
if (m_clean_orphan) {
QMutableMapIterator<QString, Mod::Ptr> iter(m_result->mods);
while (iter.hasNext()) {
auto mod = iter.next().value();
if (mod->status() == ModStatus::NotInstalled) {
mod->destroy(m_index_dir, false);
iter.remove();
}
}
}
emit succeeded(); emit succeeded();
} }

View File

@ -56,7 +56,7 @@ public:
} }
public: public:
ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed); ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan = false);
void run(); void run();
signals: signals:
void succeeded(); void succeeded();
@ -67,5 +67,6 @@ private:
private: private:
QDir& m_mods_dir, m_index_dir; QDir& m_mods_dir, m_index_dir;
bool m_is_indexed; bool m_is_indexed;
bool m_clean_orphan;
ResultPtr m_result; ResultPtr m_result;
}; };

View File

@ -63,11 +63,12 @@ void FMLLibrariesTask::executeTask()
setStatus(tr("Downloading FML libraries...")); setStatus(tr("Downloading FML libraries..."));
auto dljob = new NetJob("FML libraries", APPLICATION->network()); auto dljob = new NetJob("FML libraries", APPLICATION->network());
auto metacache = APPLICATION->metacache(); auto metacache = APPLICATION->metacache();
Net::Download::Options options = Net::Download::Option::MakeEternal;
for (auto &lib : fmlLibsToProcess) for (auto &lib : fmlLibsToProcess)
{ {
auto entry = metacache->resolveEntry("fmllibs", lib.filename); auto entry = metacache->resolveEntry("fmllibs", lib.filename);
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename; QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options));
} }
connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);

View File

@ -3,81 +3,73 @@
#include <MurmurHash2.h> #include <MurmurHash2.h>
#include <QDebug> #include <QDebug>
#include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h" #include "modplatform/flame/FlameModIndex.h"
#include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h" #include "modplatform/modrinth/ModrinthPackIndex.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "tasks/MultipleOptionsTask.h"
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
static ModrinthAPI modrinth_api; static ModrinthAPI modrinth_api;
static FlameAPI flame_api; static FlameAPI flame_api;
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov) EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr)
{ {
auto hash = getHash(mod); auto hash_task = createNewHash(mod);
if (hash.isEmpty()) if (!hash_task)
emitFail(mod); return;
else connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
m_mods.insert(hash, mod); connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
hash_task->start();
} }
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::Provider prov) EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::Provider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{ {
m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10);
for (auto* mod : mods) { for (auto* mod : mods) {
if (!mod->valid()) { auto hash_task = createNewHash(mod);
emitFail(mod); if (!hash_task)
continue; continue;
} connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
auto hash = getHash(mod); m_hashing_task->addTask(hash_task);
if (hash.isEmpty()) {
emitFail(mod);
continue;
}
m_mods.insert(hash, mod);
} }
} }
QString EnsureMetadataTask::getHash(Mod* mod) Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod)
{ {
/* Here we create a mapping hash -> mod, because we need that relationship to parse the API routes */ if (!mod || !mod->valid() || mod->type() == Mod::MOD_FOLDER)
QByteArray jar_data; return nullptr;
try {
jar_data = FS::read(mod->fileinfo().absoluteFilePath());
} catch (FS::FileSystemException& e) {
qCritical() << QString("Failed to open / read JAR file of %1").arg(mod->name());
qCritical() << QString("Reason: ") << e.cause();
return {}; return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider);
}
QString EnsureMetadataTask::getExistingHash(Mod* mod)
{
// Check for already computed hashes
// (linear on the number of mods vs. linear on the size of the mod's JAR)
auto it = m_mods.keyValueBegin();
while (it != m_mods.keyValueEnd()) {
if ((*it).second == mod)
break;
it++;
} }
switch (m_provider) { // We already have the hash computed
case ModPlatform::Provider::MODRINTH: { if (it != m_mods.keyValueEnd()) {
auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); return (*it).first;
return QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, hash_type).toHex());
}
case ModPlatform::Provider::FLAME: {
QByteArray jar_data_treated;
for (char c : jar_data) {
// CF-specific
if (!(c == 9 || c == 10 || c == 13 || c == 32))
jar_data_treated.push_back(c);
}
return QString::number(MurmurHash2(jar_data_treated, jar_data_treated.length()));
}
} }
// No existing hash
return {}; return {};
} }
@ -127,11 +119,9 @@ void EnsureMetadataTask::executeTask()
} }
auto invalidade_leftover = [this] { auto invalidade_leftover = [this] {
QMutableHashIterator<QString, Mod*> mods_iter(m_mods); for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++)
while (mods_iter.hasNext()) { emitFail(mod.value(), mod.key(), RemoveFromList::No);
auto mod = mods_iter.next(); m_mods.clear();
emitFail(mod.value());
}
emitSucceeded(); emitSucceeded();
}; };
@ -178,20 +168,44 @@ void EnsureMetadataTask::executeTask()
version_task->start(); version_task->start();
} }
void EnsureMetadataTask::emitReady(Mod* m) void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove)
{ {
if (!m) {
qCritical() << "Tried to mark a null mod as ready.";
if (!key.isEmpty())
m_mods.remove(key);
return;
}
qDebug() << QString("Generated metadata for %1").arg(m->name()); qDebug() << QString("Generated metadata for %1").arg(m->name());
emit metadataReady(m); emit metadataReady(m);
m_mods.remove(getHash(m)); if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(m);
m_mods.remove(key);
}
} }
void EnsureMetadataTask::emitFail(Mod* m) void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
{ {
if (!m) {
qCritical() << "Tried to mark a null mod as failed.";
if (!key.isEmpty())
m_mods.remove(key);
return;
}
qDebug() << QString("Failed to generate metadata for %1").arg(m->name()); qDebug() << QString("Failed to generate metadata for %1").arg(m->name());
emit metadataFailed(m); emit metadataFailed(m);
m_mods.remove(getHash(m)); if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(m);
m_mods.remove(key);
}
} }
// Modrinth // Modrinth

View File

@ -1,12 +1,14 @@
#pragma once #pragma once
#include "ModIndex.h" #include "ModIndex.h"
#include "tasks/SequentialTask.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "modplatform/helpers/HashUtils.h"
#include "tasks/ConcurrentTask.h"
class Mod; class Mod;
class QDir; class QDir;
class MultipleOptionsTask;
class EnsureMetadataTask : public Task { class EnsureMetadataTask : public Task {
Q_OBJECT Q_OBJECT
@ -17,6 +19,8 @@ class EnsureMetadataTask : public Task {
~EnsureMetadataTask() = default; ~EnsureMetadataTask() = default;
Task::Ptr getHashingTask() { return m_hashing_task; }
public slots: public slots:
bool abort() override; bool abort() override;
protected slots: protected slots:
@ -31,10 +35,16 @@ class EnsureMetadataTask : public Task {
auto flameProjectsTask() -> NetJob::Ptr; auto flameProjectsTask() -> NetJob::Ptr;
// Helpers // Helpers
void emitReady(Mod*); enum class RemoveFromList {
void emitFail(Mod*); Yes,
No
};
void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
auto getHash(Mod*) -> QString; // Hashes and stuff
auto createNewHash(Mod*) -> Hashing::Hasher::Ptr;
auto getExistingHash(Mod*) -> QString;
private slots: private slots:
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
@ -50,5 +60,6 @@ class EnsureMetadataTask : public Task {
ModPlatform::Provider m_provider; ModPlatform::Provider m_provider;
QHash<QString, ModPlatform::IndexedVersion> m_temp_versions; QHash<QString, ModPlatform::IndexedVersion> m_temp_versions;
ConcurrentTask* m_hashing_task;
NetJob* m_current_task; NetJob* m_current_task;
}; };

View File

@ -19,6 +19,8 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QDebug>
#include <QIODevice>
namespace ModPlatform { namespace ModPlatform {
@ -53,34 +55,26 @@ auto ProviderCapabilities::hashType(Provider p) -> QStringList
} }
return {}; return {};
} }
auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> QByteArray
auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString
{ {
QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1;
switch (p) { switch (p) {
case Provider::MODRINTH: { case Provider::MODRINTH: {
// NOTE: Data is the result of reading the entire JAR file! algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512;
// If 'type' was specified, we use that
if (!type.isEmpty() && hashType(p).contains(type)) {
if (type == "sha512")
return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
else if (type == "sha1")
return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
}
return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
}
case Provider::FLAME:
// If 'type' was specified, we use that
if (!type.isEmpty() && hashType(p).contains(type)) {
if(type == "sha1")
return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
else if (type == "md5")
return QCryptographicHash::hash(data, QCryptographicHash::Md5);
}
break; break;
} }
return {}; case Provider::FLAME:
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5;
break;
}
QCryptographicHash hash(algo);
if(!hash.addData(device))
qCritical() << "Failed to read JAR to create hash!";
Q_ASSERT(hash.result().length() == hash.hashLength(algo));
return { hash.result().toHex() };
} }
} // namespace ModPlatform } // namespace ModPlatform

View File

@ -24,6 +24,8 @@
#include <QVariant> #include <QVariant>
#include <QVector> #include <QVector>
class QIODevice;
namespace ModPlatform { namespace ModPlatform {
enum class Provider { enum class Provider {
@ -36,7 +38,7 @@ class ProviderCapabilities {
auto name(Provider) -> const char*; auto name(Provider) -> const char*;
auto readableName(Provider) -> QString; auto readableName(Provider) -> QString;
auto hashType(Provider) -> QStringList; auto hashType(Provider) -> QStringList;
auto hash(Provider, QByteArray&, QString type = "") -> QByteArray; auto hash(Provider, QIODevice*, QString type = "") -> QString;
}; };
struct ModpackAuthor { struct ModpackAuthor {

View File

@ -46,7 +46,7 @@
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "meta/Version.h" #include "meta/Version.h"
#include <nonstd/optional> #include <optional>
namespace ATLauncher { namespace ATLauncher {
@ -131,8 +131,8 @@ private:
Meta::VersionPtr minecraftVersion; Meta::VersionPtr minecraftVersion;
QMap<QString, Meta::VersionPtr> componentsToInstall; QMap<QString, Meta::VersionPtr> componentsToInstall;
QFuture<nonstd::optional<QStringList>> m_extractFuture; QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
QFuture<bool> m_modExtractFuture; QFuture<bool> m_modExtractFuture;
QFutureWatcher<bool> m_modExtractFutureWatcher; QFutureWatcher<bool> m_modExtractFutureWatcher;

View File

@ -7,6 +7,13 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAcc
: m_network(network), m_toProcess(toProcess) : m_network(network), m_toProcess(toProcess)
{} {}
bool Flame::FileResolvingTask::abort()
{
if (m_dljob)
return m_dljob->abort();
return true;
}
void Flame::FileResolvingTask::executeTask() void Flame::FileResolvingTask::executeTask()
{ {
setStatus(tr("Resolving mod IDs...")); setStatus(tr("Resolving mod IDs..."));

View File

@ -13,6 +13,9 @@ public:
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess); explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
virtual ~FileResolvingTask() {}; virtual ~FileResolvingTask() {};
bool canAbort() const override { return true; }
bool abort() override;
const Flame::Manifest &getResults() const const Flame::Manifest &getResults() const
{ {
return m_toProcess; return m_toProcess;

View File

@ -0,0 +1,81 @@
#include "HashUtils.h"
#include <QDebug>
#include <QFile>
#include "FileSystem.h"
#include <MurmurHash2.h>
namespace Hashing {
static ModPlatform::ProviderCapabilities ProviderCaps;
Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider)
{
switch (provider) {
case ModPlatform::Provider::MODRINTH:
return createModrinthHasher(file_path);
case ModPlatform::Provider::FLAME:
return createFlameHasher(file_path);
default:
qCritical() << "[Hashing]"
<< "Unrecognized mod platform!";
return nullptr;
}
}
Hasher::Ptr createModrinthHasher(QString file_path)
{
return new ModrinthHasher(file_path);
}
Hasher::Ptr createFlameHasher(QString file_path)
{
return new FlameHasher(file_path);
}
void ModrinthHasher::executeTask()
{
QFile file(m_path);
try {
file.open(QFile::ReadOnly);
} catch (FS::FileSystemException& e) {
qCritical() << QString("Failed to open JAR file in %1").arg(m_path);
qCritical() << QString("Reason: ") << e.cause();
emitFailed("Failed to open file for hashing.");
return;
}
auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first();
m_hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type);
file.close();
if (m_hash.isEmpty()) {
emitFailed("Empty hash!");
} else {
emitSucceeded();
}
}
void FlameHasher::executeTask()
{
// CF-specific
auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); };
std::ifstream file_stream(m_path.toStdString(), std::ifstream::binary);
// TODO: This is very heavy work, but apparently QtConcurrent can't use move semantics, so we can't boop this to another thread.
// How do we make this non-blocking then?
m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out));
if (m_hash.isEmpty()) {
emitFailed("Empty hash!");
} else {
emitSucceeded();
}
}
} // namespace Hashing

View File

@ -0,0 +1,47 @@
#pragma once
#include <QString>
#include "modplatform/ModIndex.h"
#include "tasks/Task.h"
namespace Hashing {
class Hasher : public Task {
public:
using Ptr = shared_qobject_ptr<Hasher>;
Hasher(QString file_path) : m_path(std::move(file_path)) {}
/* We can't really abort this task, but we can say we aborted and finish our thing quickly :) */
bool abort() override { return true; }
void executeTask() override = 0;
QString getResult() const { return m_hash; };
QString getPath() const { return m_path; };
protected:
QString m_hash;
QString m_path;
};
class FlameHasher : public Hasher {
public:
FlameHasher(QString file_path) : Hasher(file_path) { setObjectName(QString("FlameHasher: %1").arg(file_path)); }
void executeTask() override;
};
class ModrinthHasher : public Hasher {
public:
ModrinthHasher(QString file_path) : Hasher(file_path) { setObjectName(QString("ModrinthHasher: %1").arg(file_path)); }
void executeTask() override;
};
Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider);
Hasher::Ptr createFlameHasher(QString file_path);
Hasher::Ptr createModrinthHasher(QString file_path);
} // namespace Hashing

View File

@ -10,7 +10,7 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include <nonstd/optional> #include <optional>
namespace LegacyFTB { namespace LegacyFTB {
@ -46,8 +46,8 @@ private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
bool abortable = false; bool abortable = false;
std::unique_ptr<QuaZip> m_packZip; std::unique_ptr<QuaZip> m_packZip;
QFuture<nonstd::optional<QStringList>> m_extractFuture; QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
NetJob::Ptr netJobContainer; NetJob::Ptr netJobContainer;
QString archivePath; QString archivePath;

View File

@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -40,103 +42,190 @@
#include "Json.h" #include "Json.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "modplatform/flame/PackManifest.h"
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "BuildConfig.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h"
#include "ui/dialogs/ScrollMessageBox.h"
namespace ModpacksCH { namespace ModpacksCH {
PackInstallTask::PackInstallTask(Modpack pack, QString version) PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent)
{ : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent)
m_pack = pack; {}
m_version_name = version;
}
bool PackInstallTask::abort() bool PackInstallTask::abort()
{ {
if(abortable) bool aborted = true;
{
return jobPtr->abort(); if (m_net_job)
} aborted &= m_net_job->abort();
return false; if (m_mod_id_resolver_task)
aborted &= m_mod_id_resolver_task->abort();
// FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet...
if (aborted)
emitFailed(tr("Aborted"));
return aborted;
} }
void PackInstallTask::executeTask() void PackInstallTask::executeTask()
{ {
setStatus(tr("Getting the manifest..."));
// Find pack version // Find pack version
bool found = false; auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(),
VersionInfo version; [this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; });
for(auto vInfo : m_pack.versions) { if (version_it == m_pack.versions.constEnd()) {
if (vInfo.name == m_version_name) {
found = true;
version = vInfo;
break;
}
}
if(!found) {
emitFailed(tr("Failed to find pack version %1").arg(m_version_name)); emitFailed(tr("Failed to find pack version %1").arg(m_version_name));
return; return;
} }
auto *netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); auto version = *version_it;
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); auto* netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network());
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response));
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded);
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed);
QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress);
m_net_job = netJob;
netJob->start();
} }
void PackInstallTask::onDownloadSucceeded() void PackInstallTask::onManifestDownloadSucceeded()
{ {
jobPtr.reset(); m_net_job.reset();
QJsonParseError parse_error; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset
qWarning() << response; << " reason: " << parse_error.errorString();
qWarning() << m_response;
return; return;
} }
auto obj = doc.object();
ModpacksCH::Version version; ModpacksCH::Version version;
try try {
{ auto obj = Json::requireObject(doc);
ModpacksCH::loadVersion(version, obj); ModpacksCH::loadVersion(version, obj);
} } catch (const JSONValidationError& e) {
catch (const JSONValidationError &e)
{
emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
return; return;
} }
m_version = version; m_version = version;
downloadPack(); resolveMods();
} }
void PackInstallTask::onDownloadFailed(QString reason) void PackInstallTask::resolveMods()
{ {
jobPtr.reset(); setStatus(tr("Resolving mods..."));
emitFailed(reason); setProgress(0, 100);
m_file_id_map.clear();
Flame::Manifest manifest;
int index = 0;
for (auto const& file : m_version.files) {
if (!file.serverOnly && file.url.isEmpty()) {
if (file.curseforge.file_id <= 0) {
emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name));
return;
}
Flame::File flame_file;
flame_file.projectId = file.curseforge.project_id;
flame_file.fileId = file.curseforge.file_id;
flame_file.hash = file.sha1;
manifest.files.insert(flame_file.fileId, flame_file);
m_file_id_map.append(flame_file.fileId);
} else {
m_file_id_map.append(-1);
}
index++;
}
m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest);
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded);
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed);
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress);
m_mod_id_resolver_task->start();
}
void PackInstallTask::onResolveModsSucceeded()
{
m_abortable = false;
QString text;
auto anyBlocked = false;
Flame::Manifest results = m_mod_id_resolver_task->getResults();
for (int index = 0; index < m_file_id_map.size(); index++) {
auto const file_id = m_file_id_map.at(index);
if (file_id < 0)
continue;
Flame::File results_file = results.files[file_id];
VersionFile& local_file = m_version.files[index];
// First check for blocked mods
if (!results_file.resolved || results_file.url.isEmpty()) {
QString type(local_file.type);
type[0] = type[0].toUpper();
text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl);
anyBlocked = true;
} else {
local_file.url = results_file.url.toString();
}
}
m_mod_id_resolver_task.reset();
if (anyBlocked) {
qDebug() << "Blocked files found, displaying file list";
auto message_dialog = new ScrollMessageBox(m_parent, tr("Blocked files found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
text);
if (message_dialog->exec() == QDialog::Accepted)
downloadPack();
else
abort();
} else {
downloadPack();
}
} }
void PackInstallTask::downloadPack() void PackInstallTask::downloadPack()
{ {
setStatus(tr("Downloading mods...")); setStatus(tr("Downloading mods..."));
jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network());
for(auto file : m_version.files) { for (auto const& file : m_version.files) {
if(file.serverOnly) continue; if (file.serverOnly || file.url.isEmpty())
continue;
QFileInfo fileName(file.name); QFileInfo file_info(file.name);
auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix(); auto cacheName = file_info.completeBaseName() + "-" + file.sha1 + "." + file_info.suffix();
auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName); auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName);
entry->setStale(true); entry->setStale(true);
@ -144,58 +233,64 @@ void PackInstallTask::downloadPack()
auto relpath = FS::PathCombine("minecraft", file.path, file.name); auto relpath = FS::PathCombine("minecraft", file.path, file.name);
auto path = FS::PathCombine(m_stagingPath, relpath); auto path = FS::PathCombine(m_stagingPath, relpath);
if (filesToCopy.contains(path)) { if (m_files_to_copy.contains(path)) {
qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading."; qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading.";
continue; continue;
} }
qDebug() << "Will download" << file.url << "to" << path; qDebug() << "Will download" << file.url << "to" << path;
filesToCopy[path] = entry->getFullPath(); m_files_to_copy[path] = entry->getFullPath();
auto dl = Net::Download::makeCached(file.url, entry); auto dl = Net::Download::makeCached(file.url, entry);
if (!file.sha1.isEmpty()) { if (!file.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1()); auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
} }
jobPtr->addNetAction(dl); jobPtr->addNetAction(dl);
} }
connect(jobPtr.get(), &NetJob::succeeded, this, [&]() connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded);
{ connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed);
abortable = false; connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress);
jobPtr.reset();
install();
});
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
{
abortable = false;
jobPtr.reset();
emitFailed(reason);
});
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
abortable = true;
setProgress(current, total);
});
m_net_job = jobPtr;
jobPtr->start(); jobPtr->start();
m_abortable = true;
}
void PackInstallTask::onModDownloadSucceeded()
{
m_net_job.reset();
install();
} }
void PackInstallTask::install() void PackInstallTask::install()
{ {
setStatus(tr("Copying modpack files")); setStatus(tr("Copying modpack files..."));
setProgress(0, m_files_to_copy.size());
QCoreApplication::processEvents();
for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) { m_abortable = false;
auto &to = iter.key();
auto &from = iter.value(); int i = 0;
for (auto iter = m_files_to_copy.constBegin(); iter != m_files_to_copy.constEnd(); iter++) {
auto& to = iter.key();
auto& from = iter.value();
FS::copy fileCopyOperation(from, to); FS::copy fileCopyOperation(from, to);
if(!fileCopyOperation()) { if (!fileCopyOperation()) {
qWarning() << "Failed to copy" << from << "to" << to; qWarning() << "Failed to copy" << from << "to" << to;
emitFailed(tr("Failed to copy files")); emitFailed(tr("Failed to copy files"));
return; return;
} }
setProgress(i++, m_files_to_copy.size());
QCoreApplication::processEvents();
} }
setStatus(tr("Installing modpack")); setStatus(tr("Installing modpack..."));
QCoreApplication::processEvents();
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
@ -205,20 +300,20 @@ void PackInstallTask::install()
auto components = instance.getPackProfile(); auto components = instance.getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
for(auto target : m_version.targets) { for (auto target : m_version.targets) {
if(target.type == "game" && target.name == "minecraft") { if (target.type == "game" && target.name == "minecraft") {
components->setComponentVersion("net.minecraft", target.version, true); components->setComponentVersion("net.minecraft", target.version, true);
break; break;
} }
} }
for(auto target : m_version.targets) { for (auto target : m_version.targets) {
if(target.type != "modloader") continue; if (target.type != "modloader")
continue;
if(target.name == "forge") { if (target.name == "forge") {
components->setComponentVersion("net.minecraftforge", target.version); components->setComponentVersion("net.minecraftforge", target.version);
} } else if (target.name == "fabric") {
else if(target.name == "fabric") {
components->setComponentVersion("net.fabricmc.fabric-loader", target.version); components->setComponentVersion("net.fabricmc.fabric-loader", target.version);
} }
} }
@ -245,4 +340,20 @@ void PackInstallTask::install()
emitSucceeded(); emitSucceeded();
} }
void PackInstallTask::onManifestDownloadFailed(QString reason)
{
m_net_job.reset();
emitFailed(reason);
} }
void PackInstallTask::onResolveModsFailed(QString reason)
{
m_net_job.reset();
emitFailed(reason);
}
void PackInstallTask::onModDownloadFailed(QString reason)
{
m_net_job.reset();
emitFailed(reason);
}
} // namespace ModpacksCH

View File

@ -1,4 +1,24 @@
// SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
* *
@ -20,44 +40,60 @@
#include "FTBPackManifest.h" #include "FTBPackManifest.h"
#include "InstanceTask.h" #include "InstanceTask.h"
#include "QObjectPtr.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include <QWidget>
namespace ModpacksCH { namespace ModpacksCH {
class PackInstallTask : public InstanceTask class PackInstallTask final : public InstanceTask
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit PackInstallTask(Modpack pack, QString version); explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr);
virtual ~PackInstallTask(){} ~PackInstallTask() override = default;
bool canAbort() const override { return true; } bool canAbort() const override { return m_abortable; }
bool abort() override; bool abort() override;
protected: protected:
virtual void executeTask() override; void executeTask() override;
private slots: private slots:
void onDownloadSucceeded(); void onManifestDownloadSucceeded();
void onDownloadFailed(QString reason); void onResolveModsSucceeded();
void onModDownloadSucceeded();
void onManifestDownloadFailed(QString reason);
void onResolveModsFailed(QString reason);
void onModDownloadFailed(QString reason);
private: private:
void resolveMods();
void downloadPack(); void downloadPack();
void install(); void install();
private: private:
bool abortable = false; bool m_abortable = true;
NetJob::Ptr jobPtr; NetJob::Ptr m_net_job = nullptr;
QByteArray response; shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver_task = nullptr;
QList<int> m_file_id_map;
QByteArray m_response;
Modpack m_pack; Modpack m_pack;
QString m_version_name; QString m_version_name;
Version m_version; Version m_version;
QMap<QString, QString> filesToCopy; QMap<QString, QString> m_files_to_copy;
//FIXME: nuke
QWidget* m_parent;
}; };
} }

View File

@ -1,4 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
* *
@ -127,13 +146,16 @@ static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj)
a.path = Json::requireString(obj, "path"); a.path = Json::requireString(obj, "path");
a.name = Json::requireString(obj, "name"); a.name = Json::requireString(obj, "name");
a.version = Json::requireString(obj, "version"); a.version = Json::requireString(obj, "version");
a.url = Json::requireString(obj, "url"); a.url = Json::ensureString(obj, "url"); // optional
a.sha1 = Json::requireString(obj, "sha1"); a.sha1 = Json::requireString(obj, "sha1");
a.size = Json::requireInteger(obj, "size"); a.size = Json::requireInteger(obj, "size");
a.clientOnly = Json::requireBoolean(obj, "clientonly"); a.clientOnly = Json::requireBoolean(obj, "clientonly");
a.serverOnly = Json::requireBoolean(obj, "serveronly"); a.serverOnly = Json::requireBoolean(obj, "serveronly");
a.optional = Json::requireBoolean(obj, "optional"); a.optional = Json::requireBoolean(obj, "optional");
a.updated = Json::requireInteger(obj, "updated"); a.updated = Json::requireInteger(obj, "updated");
auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional
a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project");
a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file");
} }
void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj) void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)

View File

@ -1,4 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright 2020 Petr Mrazek <peterix@gmail.com> * Copyright 2020 Petr Mrazek <peterix@gmail.com>
* *
@ -97,6 +116,12 @@ struct VersionTarget
int64_t updated; int64_t updated;
}; };
struct VersionFileCurseForge
{
int project_id;
int file_id;
};
struct VersionFile struct VersionFile
{ {
int id; int id;
@ -111,6 +136,7 @@ struct VersionFile
bool serverOnly; bool serverOnly;
bool optional; bool optional;
int64_t updated; int64_t updated;
VersionFileCurseForge curseforge;
}; };
struct Version struct Version

View File

@ -2,11 +2,14 @@
#include "ModrinthAPI.h" #include "ModrinthAPI.h"
#include "ModrinthPackIndex.h" #include "ModrinthPackIndex.h"
#include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "ModDownloadTask.h" #include "ModDownloadTask.h"
#include "modplatform/helpers/HashUtils.h"
#include "tasks/ConcurrentTask.h"
static ModrinthAPI api; static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
@ -32,6 +35,8 @@ void ModrinthCheckUpdate::executeTask()
// Create all hashes // Create all hashes
QStringList hashes; QStringList hashes;
auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first();
ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10);
for (auto* mod : m_mods) { for (auto* mod : m_mods) {
if (!mod->enabled()) { if (!mod->enabled()) {
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
@ -44,24 +49,24 @@ void ModrinthCheckUpdate::executeTask()
// need to generate a new hash if the current one is innadequate // need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all) // (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != best_hash_type) { if (mod->metadata()->hash_format != best_hash_type) {
QByteArray jar_data; auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath());
connect(hash_task.get(), &Task::succeeded, [&] {
try { QString hash (hash_task->getResult());
jar_data = FS::read(mod->fileinfo().absoluteFilePath()); hashes.append(hash);
} catch (FS::FileSystemException& e) { mappings.insert(hash, mod);
qCritical() << QString("Failed to open / read JAR file of %1").arg(mod->name()); });
qCritical() << QString("Reason: ") << e.cause(); connect(hash_task.get(), &Task::failed, [this, hash_task] { failed("Failed to generate hash"); });
hashing_task.addTask(hash_task);
failed(e.what()); } else {
return;
}
hash = QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, best_hash_type).toHex());
}
hashes.append(hash); hashes.append(hash);
mappings.insert(hash, mod); mappings.insert(hash, mod);
} }
}
QEventLoop loop;
connect(&hashing_task, &Task::finished, [&loop]{ loop.quit(); });
hashing_task.start();
loop.exec();
auto* response = new QByteArray(); auto* response = new QByteArray();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);

View File

@ -24,7 +24,7 @@
#include <QStringList> #include <QStringList>
#include <QUrl> #include <QUrl>
#include <nonstd/optional> #include <optional>
namespace Technic { namespace Technic {
@ -57,8 +57,8 @@ private:
QString m_archivePath; QString m_archivePath;
NetJob::Ptr m_filesNetJob; NetJob::Ptr m_filesNetJob;
std::unique_ptr<QuaZip> m_packZip; std::unique_ptr<QuaZip> m_packZip;
QFuture<nonstd::optional<QStringList>> m_extractFuture; QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
}; };
} // namespace Technic } // namespace Technic

View File

@ -60,7 +60,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
dl->m_url = url; dl->m_url = url;
dl->m_options = options; dl->m_options = options;
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
auto cachedNode = new MetaCacheSink(entry, md5Node); auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
dl->m_sink.reset(cachedNode); dl->m_sink.reset(cachedNode);
return dl; return dl;
} }

View File

@ -49,7 +49,7 @@ class Download : public NetAction {
public: public:
using Ptr = shared_qobject_ptr<class Download>; using Ptr = shared_qobject_ptr<class Download>;
enum class Option { NoOptions = 0, AcceptLocalFiles = 1 }; enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
Q_DECLARE_FLAGS(Options, Option) Q_DECLARE_FLAGS(Options, Option)
protected: protected:

View File

@ -121,6 +121,14 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
SaveEventually(); SaveEventually();
} }
// Get rid of old entries, to prevent cache problems
auto current_time = QDateTime::currentSecsSinceEpoch();
if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) {
qWarning() << "Removing cache entry because of old age!";
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
}
// entry passed all the checks we cared about. // entry passed all the checks we cared about.
entry->basePath = getBasePath(base); entry->basePath = getBasePath(base);
return entry; return entry;
@ -221,6 +229,13 @@ void HttpMetaCache::Load()
foo->etag = Json::ensureString(element_obj, "etag"); foo->etag = Json::ensureString(element_obj, "etag");
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp"); foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp"); foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
foo->makeEternal(Json::ensureBoolean(element_obj, "eternal", false));
if (!foo->isEternal()) {
foo->current_age = Json::ensureDouble(element_obj, "current_age");
foo->max_age = Json::ensureDouble(element_obj, "max_age");
}
// presumed innocent until closer examination // presumed innocent until closer examination
foo->stale = false; foo->stale = false;
@ -240,6 +255,8 @@ void HttpMetaCache::SaveNow()
if (m_index_file.isNull()) if (m_index_file.isNull())
return; return;
qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries";
QJsonObject toplevel; QJsonObject toplevel;
Json::writeString(toplevel, "version", "1"); Json::writeString(toplevel, "version", "1");
@ -259,6 +276,12 @@ void HttpMetaCache::SaveNow()
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp))); entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
if (!entry->remote_changed_timestamp.isEmpty()) if (!entry->remote_changed_timestamp.isEmpty())
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp)); entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
if (entry->isEternal()) {
entryObj.insert("eternal", true);
} else {
entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
}
entriesArr.append(entryObj); entriesArr.append(entryObj);
} }
} }

View File

@ -64,14 +64,31 @@ class MetaEntry {
auto getMD5Sum() -> QString { return md5sum; } auto getMD5Sum() -> QString { return md5sum; }
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; } void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
/* Whether the entry expires after some time (false) or not (true). */
void makeEternal(bool eternal) { is_eternal = eternal; }
[[nodiscard]] bool isEternal() const { return is_eternal; }
auto getCurrentAge() -> qint64 { return current_age; }
void setCurrentAge(qint64 age) { current_age = age; }
auto getMaximumAge() -> qint64 { return max_age; }
void setMaximumAge(qint64 age) { max_age = age; }
bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); };
protected: protected:
QString baseId; QString baseId;
QString basePath; QString basePath;
QString relativePath; QString relativePath;
QString md5sum; QString md5sum;
QString etag; QString etag;
qint64 local_changed_timestamp = 0; qint64 local_changed_timestamp = 0;
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
qint64 current_age = 0;
qint64 max_age = 0;
bool is_eternal = false;
bool stale = true; bool stale = true;
}; };

View File

@ -36,13 +36,18 @@
#include "MetaCacheSink.h" #include "MetaCacheSink.h"
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include "FileSystem.h"
#include "Application.h" #include "Application.h"
namespace Net { namespace Net {
MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum) /** Maximum time to hold a cache entry
:Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) * = 1 week in seconds
*/
#define MAX_TIME_TO_EXPIRE 1*7*24*60*60
MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum, bool is_eternal)
:Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum), m_is_eternal(is_eternal)
{ {
addValidator(md5sum); addValidator(md5sum);
} }
@ -88,6 +93,40 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
} }
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
{ // Cache lifetime
if (m_is_eternal) {
qDebug() << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath();
m_entry->makeEternal(true);
} else if (reply.hasRawHeader("Cache-Control")) {
auto cache_control_header = reply.rawHeader("Cache-Control");
// qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header;
QRegularExpression max_age_expr("max-age=([0-9]+)");
qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong();
m_entry->setMaximumAge(max_age);
} else if (reply.hasRawHeader("Expires")) {
auto expires_header = reply.rawHeader("Expires");
// qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header;
qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch();
m_entry->setMaximumAge(max_age);
} else {
m_entry->setMaximumAge(MAX_TIME_TO_EXPIRE);
}
if (reply.hasRawHeader("Age")) {
auto age_header = reply.rawHeader("Age");
// qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header;
qint64 current_age = age_header.toLongLong();
m_entry->setCurrentAge(current_age);
} else {
m_entry->setCurrentAge(0);
}
}
m_entry->setStale(false); m_entry->setStale(false);
APPLICATION->metacache()->updateEntry(m_entry); APPLICATION->metacache()->updateEntry(m_entry);

View File

@ -42,7 +42,7 @@
namespace Net { namespace Net {
class MetaCacheSink : public FileSink { class MetaCacheSink : public FileSink {
public: public:
MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum); MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum, bool is_eternal = false);
virtual ~MetaCacheSink() = default; virtual ~MetaCacheSink() = default;
auto hasLocalData() -> bool override; auto hasLocalData() -> bool override;
@ -54,5 +54,6 @@ class MetaCacheSink : public FileSink {
private: private:
MetaEntryPtr m_entry; MetaEntryPtr m_entry;
ChecksumValidator* m_md5Node; ChecksumValidator* m_md5Node;
bool m_is_eternal;
}; };
} // namespace Net } // namespace Net

View File

@ -1,10 +1,11 @@
#include "ConcurrentTask.h" #include "ConcurrentTask.h"
#include <QDebug> #include <QDebug>
#include <QCoreApplication>
ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent)
: Task(parent), m_name(task_name), m_total_max_size(max_concurrent) : Task(parent), m_name(task_name), m_total_max_size(max_concurrent)
{} { setObjectName(task_name); }
ConcurrentTask::~ConcurrentTask() ConcurrentTask::~ConcurrentTask()
{ {
@ -36,8 +37,9 @@ void ConcurrentTask::executeTask()
{ {
m_total_size = m_queue.size(); m_total_size = m_queue.size();
for (int i = 0; i < m_total_max_size; i++) for (int i = 0; i < m_total_max_size; i++) {
startNext(); QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
}
} }
bool ConcurrentTask::abort() bool ConcurrentTask::abort()
@ -91,6 +93,8 @@ void ConcurrentTask::startNext()
setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
updateState(); updateState();
QCoreApplication::processEvents();
next->start(); next->start();
} }

View File

@ -1445,6 +1445,7 @@ void MainWindow::updateNewsLabel()
{ {
newsLabel->setText(tr("Loading news...")); newsLabel->setText(tr("Loading news..."));
newsLabel->setEnabled(false); newsLabel->setEnabled(false);
ui->actionMoreNews->setVisible(false);
} }
else else
{ {
@ -1453,11 +1454,13 @@ void MainWindow::updateNewsLabel()
{ {
newsLabel->setText(entries[0]->title); newsLabel->setText(entries[0]->title);
newsLabel->setEnabled(true); newsLabel->setEnabled(true);
ui->actionMoreNews->setVisible(true);
} }
else else
{ {
newsLabel->setText(tr("No news available.")); newsLabel->setText(tr("No news available."));
newsLabel->setEnabled(false); newsLabel->setEnabled(false);
ui->actionMoreNews->setVisible(false);
} }
} }
} }

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>783</width> <width>573</width>
<height>843</height> <height>600</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">

View File

@ -121,9 +121,9 @@ QList<BasePage *> ModDownloadDialog::getPages()
{ {
QList<BasePage *> pages; QList<BasePage *> pages;
pages.append(new ModrinthModPage(this, m_instance)); pages.append(ModrinthModPage::create(this, m_instance));
if (APPLICATION->currentCapabilities() & Application::SupportsFlame) if (APPLICATION->currentCapabilities() & Application::SupportsFlame)
pages.append(new FlameModPage(this, m_instance)); pages.append(FlameModPage::create(this, m_instance));
return pages; return pages;
} }

View File

@ -270,6 +270,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH); onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH);
}); });
if (modrinth_task->getHashingTask())
seq.addTask(modrinth_task->getHashingTask());
seq.addTask(modrinth_task); seq.addTask(modrinth_task);
} }
@ -279,6 +283,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME); onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME);
}); });
if (flame_task->getHashingTask())
seq.addTask(flame_task->getHashingTask());
seq.addTask(flame_task); seq.addTask(flame_task);
} }

View File

@ -43,7 +43,7 @@ void ProgressDialog::setSkipButton(bool present, QString label)
void ProgressDialog::on_skipButton_clicked(bool checked) void ProgressDialog::on_skipButton_clicked(bool checked)
{ {
Q_UNUSED(checked); Q_UNUSED(checked);
task->abort(); if (task->abort())
QDialog::reject(); QDialog::reject();
} }

View File

@ -32,13 +32,13 @@ class SortProxy : public QSortFilterProxyModel {
const auto& mod = model->at(source_row); const auto& mod = model->at(source_row);
if (mod.name().contains(filterRegularExpression())) if (filterRegularExpression().match(mod.name()).hasMatch())
return true; return true;
if (mod.description().contains(filterRegularExpression())) if (filterRegularExpression().match(mod.description()).hasMatch())
return true; return true;
for (auto& author : mod.authors()) { for (auto& author : mod.authors()) {
if (author.contains(filterRegularExpression())) { if (filterRegularExpression().match(author).hasMatch()) {
return true; return true;
} }
} }
@ -101,7 +101,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
{ {
ui->setupUi(this); ui->setupUi(this);
runningStateChanged(m_instance && m_instance->isRunning()); ExternalResourcesPage::runningStateChanged(m_instance && m_instance->isRunning());
ui->actionsToolbar->insertSpacer(ui->actionViewConfigs); ui->actionsToolbar->insertSpacer(ui->actionViewConfigs);
@ -182,7 +182,7 @@ void ExternalResourcesPage::retranslate()
void ExternalResourcesPage::filterTextChanged(const QString& newContents) void ExternalResourcesPage::filterTextChanged(const QString& newContents)
{ {
m_viewFilter = newContents; m_viewFilter = newContents;
m_filterModel->setFilterFixedString(m_viewFilter); m_filterModel->setFilterRegularExpression(m_viewFilter);
} }
void ExternalResourcesPage::runningStateChanged(bool running) void ExternalResourcesPage::runningStateChanged(bool running)

View File

@ -46,7 +46,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
protected slots: protected slots:
void itemActivated(const QModelIndex& index); void itemActivated(const QModelIndex& index);
void filterTextChanged(const QString& newContents); void filterTextChanged(const QString& newContents);
void runningStateChanged(bool running); virtual void runningStateChanged(bool running);
virtual void addItem(); virtual void addItem();
virtual void removeItem(); virtual void removeItem();

View File

@ -349,7 +349,7 @@ void InstanceSettingsPage::loadSettings()
ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
#if !defined(Q_OS_LINUX) #if !defined(Q_OS_LINUX)
ui->perfomanceGroupBox->setVisible(false); ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false);
#endif #endif
// Miscellanous // Miscellanous

View File

@ -84,51 +84,46 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem); ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, auto check_allow_update = [this] {
[this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); }); return (!m_instance || !m_instance->isRunning()) &&
(ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
};
connect(mods.get(), &ModFolderModel::rowsInserted, this, connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
[this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); }); ui->actionUpdateItem->setEnabled(check_allow_update());
});
connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] { connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] {
ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); ui->actionUpdateItem->setEnabled(check_allow_update());
});
connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this, check_allow_update] {
ui->actionUpdateItem->setEnabled(check_allow_update());
});
connect(mods.get(), &ModFolderModel::updateFinished, this, [this, check_allow_update, mods] {
ui->actionUpdateItem->setEnabled(check_allow_update());
// Prevent a weird crash when trying to open the mods page twice in a session o.O // Prevent a weird crash when trying to open the mods page twice in a session o.O
disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0); disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0);
}); });
ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning());
} }
} }
CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent) void ModFolderPage::runningStateChanged(bool running)
: ModFolderPage(inst, mods, parent) {
{} ExternalResourcesPage::runningStateChanged(running);
ui->actionDownloadItem->setEnabled(!running);
ui->actionUpdateItem->setEnabled(!running);
}
bool ModFolderPage::shouldDisplay() const bool ModFolderPage::shouldDisplay() const
{ {
return true; return true;
} }
bool CoreModFolderPage::shouldDisplay() const
{
if (ModFolderPage::shouldDisplay()) {
auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
if (!inst)
return true;
auto version = inst->getPackProfile();
if (!version)
return true;
if (!version->getComponent("net.minecraftforge"))
return false;
if (!version->getComponent("net.minecraft"))
return false;
if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
return true;
}
return false;
}
void ModFolderPage::installMods() void ModFolderPage::installMods()
{ {
if (!m_controlsEnabled) if (!m_controlsEnabled)
@ -232,3 +227,28 @@ void ModFolderPage::updateMods()
m_model->update(); m_model->update();
} }
} }
CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ModFolderPage(inst, mods, parent)
{}
bool CoreModFolderPage::shouldDisplay() const
{
if (ModFolderPage::shouldDisplay()) {
auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
if (!inst)
return true;
auto version = inst->getPackProfile();
if (!version)
return true;
if (!version->getComponent("net.minecraftforge"))
return false;
if (!version->getComponent("net.minecraft"))
return false;
if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
return true;
}
return false;
}

View File

@ -53,6 +53,7 @@ class ModFolderPage : public ExternalResourcesPage {
virtual QString helpPage() const override { return "Loader-mods"; } virtual QString helpPage() const override { return "Loader-mods"; }
virtual bool shouldDisplay() const override; virtual bool shouldDisplay() const override;
void runningStateChanged(bool running) override;
private slots: private slots:
void installMods(); void installMods();
@ -63,5 +64,11 @@ class CoreModFolderPage : public ModFolderPage {
public: public:
explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0); explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
virtual ~CoreModFolderPage() = default; virtual ~CoreModFolderPage() = default;
virtual bool shouldDisplay() const;
virtual QString displayName() const override { return tr("Core mods"); }
virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); }
virtual QString id() const override { return "coremods"; }
virtual QString helpPage() const override { return "Core-mods"; }
virtual bool shouldDisplay() const override;
}; };

View File

@ -211,7 +211,7 @@ void WorldListPage::on_actionDatapacks_triggered()
return; return;
} }
if(!worldSafetyNagQuestion()) if(!worldSafetyNagQuestion(tr("Open World Datapacks Folder")))
return; return;
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
@ -269,7 +269,7 @@ void WorldListPage::on_actionMCEdit_triggered()
return; return;
} }
if(!worldSafetyNagQuestion()) if(!worldSafetyNagQuestion(tr("Open World in MCEdit")))
return; return;
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
@ -373,11 +373,11 @@ bool WorldListPage::isWorldSafe(QModelIndex)
return !m_inst->isRunning(); return !m_inst->isRunning();
} }
bool WorldListPage::worldSafetyNagQuestion() bool WorldListPage::worldSafetyNagQuestion(const QString &actionType)
{ {
if(!isWorldSafe(getSelectedWorld())) if(!isWorldSafe(getSelectedWorld()))
{ {
auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); auto result = QMessageBox::question(this, actionType, tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?"));
if(result == QMessageBox::No) if(result == QMessageBox::No)
{ {
return false; return false;
@ -395,7 +395,7 @@ void WorldListPage::on_actionCopy_triggered()
return; return;
} }
if(!worldSafetyNagQuestion()) if(!worldSafetyNagQuestion(tr("Copy World")))
return; return;
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
@ -417,7 +417,7 @@ void WorldListPage::on_actionRename_triggered()
return; return;
} }
if(!worldSafetyNagQuestion()) if(!worldSafetyNagQuestion(tr("Rename World")))
return; return;
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);

View File

@ -93,7 +93,7 @@ protected:
private: private:
QModelIndex getSelectedWorld(); QModelIndex getSelectedWorld();
bool isWorldSafe(QModelIndex index); bool isWorldSafe(QModelIndex index);
bool worldSafetyNagQuestion(); bool worldSafetyNagQuestion(const QString &actionType);
void mceditError(); void mceditError();
private: private:

View File

@ -44,12 +44,12 @@
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "ui/dialogs/ModDownloadDialog.h" #include "ui/dialogs/ModDownloadDialog.h"
ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
: QWidget(dialog) : QWidget(dialog)
, m_instance(instance) , m_instance(instance)
, ui(new Ui::ModPage) , ui(new Ui::ModPage)
, dialog(dialog) , dialog(dialog)
, filter_widget(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this)
, api(api) , api(api)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -59,18 +59,6 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
ui->gridLayout_3->addWidget(&filter_widget, 0, 0, 1, ui->gridLayout_3->columnCount());
filter_widget.setInstance(static_cast<MinecraftInstance*>(m_instance));
m_filter = filter_widget.getFilter();
connect(&filter_widget, &ModFilterWidget::filterChanged, this, [&]{
ui->searchButton->setStyleSheet("text-decoration: underline");
});
connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{
ui->searchButton->setStyleSheet("text-decoration: none");
});
} }
ModPage::~ModPage() ModPage::~ModPage()
@ -78,6 +66,26 @@ ModPage::~ModPage()
delete ui; delete ui;
} }
void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& widget)
{
if (m_filter_widget)
disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr);
m_filter_widget.swap(widget);
ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, ui->gridLayout_3->columnCount());
m_filter_widget->setInstance(static_cast<MinecraftInstance*>(m_instance));
m_filter = m_filter_widget->getFilter();
connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{
ui->searchButton->setStyleSheet("text-decoration: underline");
});
connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{
ui->searchButton->setStyleSheet("text-decoration: none");
});
}
/******** Qt things ********/ /******** Qt things ********/
@ -105,13 +113,13 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool
void ModPage::filterMods() void ModPage::filterMods()
{ {
filter_widget.setHidden(!filter_widget.isHidden()); m_filter_widget->setHidden(!m_filter_widget->isHidden());
} }
void ModPage::triggerSearch() void ModPage::triggerSearch()
{ {
auto changed = filter_widget.changed(); auto changed = m_filter_widget->changed();
m_filter = filter_widget.getFilter(); m_filter = m_filter_widget->getFilter();
if(changed){ if(changed){
ui->packView->clearSelection(); ui->packView->clearSelection();

View File

@ -20,7 +20,17 @@ class ModPage : public QWidget, public BasePage {
Q_OBJECT Q_OBJECT
public: public:
explicit ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api); template<typename T>
static T* create(ModDownloadDialog* dialog, BaseInstance* instance)
{
auto page = new T(dialog, instance);
auto filter_widget = ModFilterWidget::create(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), page);
page->setFilterWidget(filter_widget);
return page;
}
~ModPage() override; ~ModPage() override;
/* Affects what the user sees */ /* Affects what the user sees */
@ -45,6 +55,8 @@ class ModPage : public QWidget, public BasePage {
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
auto getDialog() const -> const ModDownloadDialog* { return dialog; } auto getDialog() const -> const ModDownloadDialog* { return dialog; }
void setFilterWidget(unique_qobject_ptr<ModFilterWidget>&);
auto getCurrent() -> ModPlatform::IndexedPack& { return current; } auto getCurrent() -> ModPlatform::IndexedPack& { return current; }
void updateModVersions(int prev_count = -1); void updateModVersions(int prev_count = -1);
@ -54,6 +66,7 @@ class ModPage : public QWidget, public BasePage {
BaseInstance* m_instance; BaseInstance* m_instance;
protected: protected:
ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api);
void updateSelectionButton(); void updateSelectionButton();
protected slots: protected slots:
@ -67,7 +80,7 @@ class ModPage : public QWidget, public BasePage {
Ui::ModPage* ui = nullptr; Ui::ModPage* ui = nullptr;
ModDownloadDialog* dialog = nullptr; ModDownloadDialog* dialog = nullptr;
ModFilterWidget filter_widget; unique_qobject_ptr<ModFilterWidget> m_filter_widget;
std::shared_ptr<ModFilterWidget::Filter> m_filter; std::shared_ptr<ModFilterWidget::Filter> m_filter;
ModPlatform::ListModel* listModel = nullptr; ModPlatform::ListModel* listModel = nullptr;

View File

@ -44,7 +44,12 @@ class FlameModPage : public ModPage {
Q_OBJECT Q_OBJECT
public: public:
explicit FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance); static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance* instance)
{
return ModPage::create<FlameModPage>(dialog, instance);
}
FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance);
~FlameModPage() override = default; ~FlameModPage() override = default;
inline auto displayName() const -> QString override { return "CurseForge"; } inline auto displayName() const -> QString override { return "CurseForge"; }

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -126,7 +127,7 @@ void FtbPage::suggestCurrent()
return; return;
} }
dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion)); dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this));
for(auto art : selected.art) { for(auto art : selected.art) {
if(art.type == "square") { if(art.type == "square") {
QString editedLogoName; QString editedLogoName;

View File

@ -35,7 +35,11 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QTextBrowser" name="publicPackDescription"/> <widget class="QTextBrowser" name="publicPackDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -45,7 +49,11 @@
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1"> <item row="0" column="1">
<widget class="QTextBrowser" name="thirdPartyPackDescription"/> <widget class="QTextBrowser" name="thirdPartyPackDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QTreeView" name="thirdPartyPackList"> <widget class="QTreeView" name="thirdPartyPackList">
@ -95,7 +103,11 @@
</widget> </widget>
</item> </item>
<item row="0" column="1" rowspan="3"> <item row="0" column="1" rowspan="3">
<widget class="QTextBrowser" name="privatePackDescription"/> <widget class="QTextBrowser" name="privatePackDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>

View File

@ -44,7 +44,12 @@ class ModrinthModPage : public ModPage {
Q_OBJECT Q_OBJECT
public: public:
explicit ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance* instance)
{
return ModPage::create<ModrinthModPage>(dialog, instance);
}
ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance);
~ModrinthModPage() override = default; ~ModrinthModPage() override = default;
inline auto displayName() const -> QString override { return "Modrinth"; } inline auto displayName() const -> QString override { return "Modrinth"; }

View File

@ -1,6 +1,39 @@
#include "ModFilterWidget.h" #include "ModFilterWidget.h"
#include "ui_ModFilterWidget.h" #include "ui_ModFilterWidget.h"
#include "Application.h"
unique_qobject_ptr<ModFilterWidget> ModFilterWidget::create(Version default_version, QWidget* parent)
{
auto filter_widget = new ModFilterWidget(default_version, parent);
if (!filter_widget->versionList()->isLoaded()) {
QEventLoop load_version_list_loop;
QTimer time_limit_for_list_load;
time_limit_for_list_load.setTimerType(Qt::TimerType::CoarseTimer);
time_limit_for_list_load.setSingleShot(true);
time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit);
time_limit_for_list_load.start(4000);
auto task = filter_widget->versionList()->getLoadTask();
connect(task.get(), &Task::failed, [filter_widget]{
filter_widget->disableVersionButton(VersionButtonID::Major, tr("failed to get version index"));
});
connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit);
if (!task->isRunning())
task->start();
load_version_list_loop.exec();
if (time_limit_for_list_load.isActive())
time_limit_for_list_load.stop();
}
return unique_qobject_ptr<ModFilterWidget>(filter_widget);
}
ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) ModFilterWidget::ModFilterWidget(Version def, QWidget* parent)
: QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget) : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget)
{ {
@ -16,6 +49,7 @@ ModFilterWidget::ModFilterWidget(Version def, QWidget* parent)
m_filter->versions.push_front(def); m_filter->versions.push_front(def);
m_version_list = APPLICATION->metadataIndex()->get("net.minecraft");
setHidden(true); setHidden(true);
} }
@ -51,24 +85,30 @@ auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter>
return m_filter; return m_filter;
} }
void ModFilterWidget::disableVersionButton(VersionButtonID id) void ModFilterWidget::disableVersionButton(VersionButtonID id, QString reason)
{ {
QAbstractButton* btn = nullptr;
switch(id){ switch(id){
case(VersionButtonID::Strict): case(VersionButtonID::Strict):
ui->strictVersionButton->setEnabled(false); btn = ui->strictVersionButton;
break; break;
case(VersionButtonID::Major): case(VersionButtonID::Major):
ui->majorVersionButton->setEnabled(false); btn = ui->majorVersionButton;
break; break;
case(VersionButtonID::All): case(VersionButtonID::All):
ui->allVersionsButton->setEnabled(false); btn = ui->allVersionsButton;
break; break;
case(VersionButtonID::Between): case(VersionButtonID::Between):
// ui->betweenVersionsButton->setEnabled(false);
break;
default: default:
break; break;
} }
if (btn) {
btn->setEnabled(false);
if (!reason.isEmpty())
btn->setText(btn->text() + QString(" (%1)").arg(reason));
}
} }
void ModFilterWidget::onVersionFilterChanged(int id) void ModFilterWidget::onVersionFilterChanged(int id)
@ -76,7 +116,7 @@ void ModFilterWidget::onVersionFilterChanged(int id)
//ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between); //ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between);
//ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between); //ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between);
int index = 0; int index = 1;
auto cast_id = (VersionButtonID) id; auto cast_id = (VersionButtonID) id;
if (cast_id != m_version_id) { if (cast_id != m_version_id) {
@ -93,10 +133,15 @@ void ModFilterWidget::onVersionFilterChanged(int id)
break; break;
case(VersionButtonID::Major): { case(VersionButtonID::Major): {
auto versionSplit = mcVersionStr().split("."); auto versionSplit = mcVersionStr().split(".");
for(auto i = Version(QString("%1.%2").arg(versionSplit[0], versionSplit[1])); i <= mcVersion(); index++){
m_filter->versions.push_front(i); auto major_version = QString("%1.%2").arg(versionSplit[0], versionSplit[1]);
i = Version(QString("%1.%2.%3").arg(versionSplit[0], versionSplit[1], QString("%1").arg(index))); QString version_str = major_version;
while (m_version_list->hasVersion(version_str)) {
m_filter->versions.emplace_back(version_str);
version_str = QString("%1.%2").arg(major_version, QString::number(index++));
} }
break; break;
} }
case(VersionButtonID::All): case(VersionButtonID::All):

View File

@ -4,6 +4,10 @@
#include <QButtonGroup> #include <QButtonGroup>
#include "Version.h" #include "Version.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
@ -34,18 +38,22 @@ public:
std::shared_ptr<Filter> m_filter; std::shared_ptr<Filter> m_filter;
public: public:
explicit ModFilterWidget(Version def, QWidget* parent = nullptr); static unique_qobject_ptr<ModFilterWidget> create(Version default_version, QWidget* parent = nullptr);
~ModFilterWidget(); ~ModFilterWidget();
void setInstance(MinecraftInstance* instance); void setInstance(MinecraftInstance* instance);
/// By default all buttons are enabled /// By default all buttons are enabled
void disableVersionButton(VersionButtonID); void disableVersionButton(VersionButtonID, QString reason = {});
auto getFilter() -> std::shared_ptr<Filter>; auto getFilter() -> std::shared_ptr<Filter>;
auto changed() const -> bool { return m_last_version_id != m_version_id; } auto changed() const -> bool { return m_last_version_id != m_version_id; }
Meta::VersionListPtr versionList() { return m_version_list; }
private: private:
ModFilterWidget(Version def, QWidget* parent = nullptr);
inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; } inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; }
inline auto mcVersion() const -> Version { return { mcVersionStr() }; } inline auto mcVersion() const -> Version { return { mcVersionStr() }; }
@ -61,8 +69,12 @@ private:
MinecraftInstance* m_instance = nullptr; MinecraftInstance* m_instance = nullptr;
/* Version stuff */
QButtonGroup m_mcVersion_buttons; QButtonGroup m_mcVersion_buttons;
Meta::VersionListPtr m_version_list;
/* Used to tell if the filter was changed since the last getFilter() call */ /* Used to tell if the filter was changed since the last getFilter() call */
VersionButtonID m_last_version_id = VersionButtonID::Strict; VersionButtonID m_last_version_id = VersionButtonID::Strict;
VersionButtonID m_version_id = VersionButtonID::Strict; VersionButtonID m_version_id = VersionButtonID::Strict;

View File

@ -15,7 +15,7 @@ A performance optimization daemon.
See [github repo](https://github.com/FeralInteractive/gamemode). See [github repo](https://github.com/FeralInteractive/gamemode).
BSD licensed BSD-3-Clause licensed
## hoedown ## hoedown
Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté. Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté.
@ -155,19 +155,11 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith
Public domain (the author disclaimed the copyright). Public domain (the author disclaimed the copyright).
## optional-bare
A simple single-file header-only version of a C++17-like optional for default-constructible, copyable types, for C++98 and later.
Imported from: https://github.com/martinmoene/optional-bare/commit/0bb1d183bcee1e854c4ea196b533252c51f98b81
Boost Software License - Version 1.0
## quazip ## quazip
A zip manipulation library, forked for MultiMC's use. A zip manipulation library.
LGPL 2.1 LGPL 2.1 with linking exception.
## rainbow ## rainbow
Color functions extracted from [KGuiAddons](https://inqlude.org/libraries/kguiaddons.html). Used for adaptive text coloring. Color functions extracted from [KGuiAddons](https://inqlude.org/libraries/kguiaddons.html). Used for adaptive text coloring.
@ -176,7 +168,7 @@ Available either under LGPL version 2.1 or later.
## systeminfo ## systeminfo
A MultiMC-specific library for probing system information. A PolyMC-specific library for probing system information.
Apache 2.0 Apache 2.0
@ -190,7 +182,7 @@ Licenced under the MIT licence.
## xz-embedded ## xz-embedded
Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth. Tiny implementation of LZMA2 de/compression. This format was only used by Forge to save bandwidth.
Public domain. Public domain.

View File

@ -1,86 +1,110 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// MurmurHash2 was written by Austin Appleby, and is placed in the public // MurmurHash2 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code. // domain. The author hereby disclaims copyright to this source code.
//
// Note - This code makes a few assumptions about how your machine behaves - // This was modified as to possibilitate it's usage incrementally.
// Those modifications are also placed in the public domain, and the author of
// 1. We can read a 4-byte value from any address without crashing // such modifications hereby disclaims copyright to this source code.
// 2. sizeof(int) == 4
// And it has a few limitations -
// 1. It will not work incrementally.
// 2. It will not produce the same results on little-endian and big-endian
// machines.
#include "MurmurHash2.h" #include "MurmurHash2.h"
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Platform-specific functions and macros
// Microsoft Visual Studio // 'm' and 'r' are mixing constants generated offline.
// They're not really 'magic', they just happen to work well.
const uint32_t m = 0x5bd1e995;
const int r = 24;
#if defined(_MSC_VER) uint32_t MurmurHash2(std::ifstream&& file_stream, std::size_t buffer_size, std::function<bool(char)> filter_out)
#define BIG_CONSTANT(x) (x)
// Other compilers
#else // defined(_MSC_VER)
#define BIG_CONSTANT(x) (x##LLU)
#endif // !defined(_MSC_VER)
//-----------------------------------------------------------------------------
uint64_t MurmurHash2 ( const void* key, int len, uint32_t seed )
{ {
// 'm' and 'r' are mixing constants generated offline. auto* buffer = new char[buffer_size];
// They're not really 'magic', they just happen to work well. char data[4];
const uint32_t m = 0x5bd1e995; int read = 0;
const int r = 24; uint32_t size = 0;
// Initialize the hash to a 'random' value // We need the size without the filtered out characters before actually calculating the hash,
// to setup the initial value for the hash.
do {
file_stream.read(buffer, buffer_size);
read = file_stream.gcount();
for (int i = 0; i < read; i++) {
if (!filter_out(buffer[i]))
size += 1;
}
} while (!file_stream.eof());
uint32_t h = seed ^ len; file_stream.clear();
file_stream.seekg(0, file_stream.beg);
int index = 0;
// This forces a seed of 1.
IncrementalHashInfo info{ (uint32_t)1 ^ size, (uint32_t)size };
do {
file_stream.read(buffer, buffer_size);
read = file_stream.gcount();
for (int i = 0; i < read; i++) {
char c = buffer[i];
if (filter_out(c))
continue;
data[index] = c;
index = (index + 1) % 4;
// Mix 4 bytes at a time into the hash // Mix 4 bytes at a time into the hash
const auto* data = (const unsigned char*) key; if (index == 0)
while(len >= 4) FourBytes_MurmurHash2((unsigned char*)&data, info);
{ }
} while (!file_stream.eof());
// Do one last bit shuffle in the hash
FourBytes_MurmurHash2((unsigned char*)&data, info);
delete[] buffer;
file_stream.close();
return info.h;
}
void FourBytes_MurmurHash2(const unsigned char* data, IncrementalHashInfo& prev)
{
if (prev.len >= 4) {
// Not the final mix
uint32_t k = *(uint32_t*)data; uint32_t k = *(uint32_t*)data;
k *= m; k *= m;
k ^= k >> r; k ^= k >> r;
k *= m; k *= m;
h *= m; prev.h *= m;
h ^= k; prev.h ^= k;
data += 4*sizeof(char); prev.len -= 4;
len -= 4; } else {
} // The final mix
// Handle the last few bytes of the input array // Handle the last few bytes of the input array
switch (prev.len) {
switch(len) case 3:
{ prev.h ^= data[2] << 16;
case 3: h ^= data[2] << 16; case 2:
case 2: h ^= data[1] << 8; prev.h ^= data[1] << 8;
case 1: h ^= data[0]; case 1:
h *= m; prev.h ^= data[0];
prev.h *= m;
}; };
// Do a few final mixes of the hash to ensure the last few // Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated. // bytes are well-incorporated.
h ^= h >> 13; prev.h ^= prev.h >> 13;
h *= m; prev.h *= m;
h ^= h >> 15; prev.h ^= prev.h >> 15;
return h; prev.len = 0;
}
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@ -1,30 +1,33 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// MurmurHash2 was written by Austin Appleby, and is placed in the public // The original MurmurHash2 was written by Austin Appleby, and is placed in the
// domain. The author hereby disclaims copyright to this source code. // public domain. The author hereby disclaims copyright to this source code.
//
// This was modified as to possibilitate it's usage incrementally.
// Those modifications are also placed in the public domain, and the author of
// such modifications hereby disclaims copyright to this source code.
#pragma once #pragma once
//----------------------------------------------------------------------------- #include <cstdint>
// Platform-specific functions and macros #include <fstream>
// Microsoft Visual Studio #include <functional>
#if defined(_MSC_VER) && (_MSC_VER < 1600)
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
typedef unsigned __int64 uint64_t;
// Other compilers
#else // defined(_MSC_VER)
#include <stdint.h>
#endif // !defined(_MSC_VER)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
uint64_t MurmurHash2 ( const void* key, int len, uint32_t seed = 1 ); #define KiB 1024
#define MiB 1024*KiB
uint32_t MurmurHash2(
std::ifstream&& file_stream,
std::size_t buffer_size = 4*MiB,
std::function<bool(char)> filter_out = [](char) { return false; });
struct IncrementalHashInfo {
uint32_t h;
uint32_t len;
};
void FourBytes_MurmurHash2(const unsigned char* data, IncrementalHashInfo& prev);
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@ -1,5 +0,0 @@
cmake_minimum_required(VERSION 3.9.4)
project(optional-bare)
add_library(optional-bare INTERFACE)
target_include_directories(optional-bare INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")

View File

@ -1,23 +0,0 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,5 +0,0 @@
# optional bare
A simple single-file header-only version of a C++17-like optional for default-constructible, copyable types, for C++98 and later.
Imported from: https://github.com/martinmoene/optional-bare/commit/0bb1d183bcee1e854c4ea196b533252c51f98b81

View File

@ -1,508 +0,0 @@
//
// Copyright 2017-2019 by Martin Moene
//
// https://github.com/martinmoene/optional-bare
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef NONSTD_OPTIONAL_BARE_HPP
#define NONSTD_OPTIONAL_BARE_HPP
#define optional_bare_MAJOR 1
#define optional_bare_MINOR 1
#define optional_bare_PATCH 0
#define optional_bare_VERSION optional_STRINGIFY(optional_bare_MAJOR) "." optional_STRINGIFY(optional_bare_MINOR) "." optional_STRINGIFY(optional_bare_PATCH)
#define optional_STRINGIFY( x ) optional_STRINGIFY_( x )
#define optional_STRINGIFY_( x ) #x
// optional-bare configuration:
#define optional_OPTIONAL_DEFAULT 0
#define optional_OPTIONAL_NONSTD 1
#define optional_OPTIONAL_STD 2
#if !defined( optional_CONFIG_SELECT_OPTIONAL )
# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD )
#endif
// Control presence of exception handling (try and auto discover):
#ifndef optional_CONFIG_NO_EXCEPTIONS
# if _MSC_VER
# include <cstddef> // for _HAS_EXCEPTIONS
# endif
# if _MSC_VER
# include <cstddef> // for _HAS_EXCEPTIONS
# endif
# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
# define optional_CONFIG_NO_EXCEPTIONS 0
# else
# define optional_CONFIG_NO_EXCEPTIONS 1
# endif
#endif
// C++ language version detection (C++20 is speculative):
// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
#ifndef optional_CPLUSPLUS
# if defined(_MSVC_LANG ) && !defined(__clang__)
# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
# else
# define optional_CPLUSPLUS __cplusplus
# endif
#endif
#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L )
#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L )
#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L )
#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L )
#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L )
// C++ language version (represent 98 as 3):
#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) )
// Use C++17 std::optional if available and requested:
#if optional_CPP17_OR_GREATER && defined(__has_include )
# if __has_include( <optional> )
# define optional_HAVE_STD_OPTIONAL 1
# else
# define optional_HAVE_STD_OPTIONAL 0
# endif
#else
# define optional_HAVE_STD_OPTIONAL 0
#endif
#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) )
//
// Using std::optional:
//
#if optional_USES_STD_OPTIONAL
#include <optional>
#include <utility>
namespace nonstd {
using std::in_place;
using std::in_place_type;
using std::in_place_index;
using std::in_place_t;
using std::in_place_type_t;
using std::in_place_index_t;
using std::optional;
using std::bad_optional_access;
using std::hash;
using std::nullopt;
using std::nullopt_t;
using std::operator==;
using std::operator!=;
using std::operator<;
using std::operator<=;
using std::operator>;
using std::operator>=;
using std::make_optional;
using std::swap;
}
#else // optional_USES_STD_OPTIONAL
#include <cassert>
#if ! optional_CONFIG_NO_EXCEPTIONS
# include <stdexcept>
#endif
namespace nonstd { namespace optional_bare {
// type for nullopt
struct nullopt_t
{
struct init{};
nullopt_t( init ) {}
};
// extra parenthesis to prevent the most vexing parse:
const nullopt_t nullopt(( nullopt_t::init() ));
// optional access error.
#if ! optional_CONFIG_NO_EXCEPTIONS
class bad_optional_access : public std::logic_error
{
public:
explicit bad_optional_access()
: logic_error( "bad optional access" ) {}
};
#endif // optional_CONFIG_NO_EXCEPTIONS
// Simplistic optional: requires T to be default constructible, copyable.
template< typename T >
class optional
{
private:
typedef void (optional::*safe_bool)() const;
public:
typedef T value_type;
optional()
: has_value_( false )
{}
optional( nullopt_t )
: has_value_( false )
{}
optional( T const & arg )
: has_value_( true )
, value_ ( arg )
{}
template< class U >
optional( optional<U> const & other )
: has_value_( other.has_value() )
, value_ ( other.value() )
{}
optional & operator=( nullopt_t )
{
reset();
return *this;
}
template< class U >
optional & operator=( optional<U> const & other )
{
has_value_ = other.has_value();
value_ = other.value();
return *this;
}
void swap( optional & rhs )
{
using std::swap;
if ( has_value() == true && rhs.has_value() == true ) { swap( **this, *rhs ); }
else if ( has_value() == false && rhs.has_value() == true ) { initialize( *rhs ); rhs.reset(); }
else if ( has_value() == true && rhs.has_value() == false ) { rhs.initialize( **this ); reset(); }
}
// observers
value_type const * operator->() const
{
return assert( has_value() ),
&value_;
}
value_type * operator->()
{
return assert( has_value() ),
&value_;
}
value_type const & operator*() const
{
return assert( has_value() ),
value_;
}
value_type & operator*()
{
return assert( has_value() ),
value_;
}
#if optional_CPP11_OR_GREATER
explicit operator bool() const
{
return has_value();
}
#else
operator safe_bool() const
{
return has_value() ? &optional::this_type_does_not_support_comparisons : 0;
}
#endif
bool has_value() const
{
return has_value_;
}
value_type const & value() const
{
#if optional_CONFIG_NO_EXCEPTIONS
assert( has_value() );
#else
if ( ! has_value() )
throw bad_optional_access();
#endif
return value_;
}
value_type & value()
{
#if optional_CONFIG_NO_EXCEPTIONS
assert( has_value() );
#else
if ( ! has_value() )
throw bad_optional_access();
#endif
return value_;
}
template< class U >
value_type value_or( U const & v ) const
{
return has_value() ? value() : static_cast<value_type>( v );
}
// modifiers
void reset()
{
has_value_ = false;
}
private:
void this_type_does_not_support_comparisons() const {}
template< typename V >
void initialize( V const & value )
{
assert( ! has_value() );
value_ = value;
has_value_ = true;
}
private:
bool has_value_;
value_type value_;
};
// Relational operators
template< typename T, typename U >
inline bool operator==( optional<T> const & x, optional<U> const & y )
{
return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y;
}
template< typename T, typename U >
inline bool operator!=( optional<T> const & x, optional<U> const & y )
{
return !(x == y);
}
template< typename T, typename U >
inline bool operator<( optional<T> const & x, optional<U> const & y )
{
return (!y) ? false : (!x) ? true : *x < *y;
}
template< typename T, typename U >
inline bool operator>( optional<T> const & x, optional<U> const & y )
{
return (y < x);
}
template< typename T, typename U >
inline bool operator<=( optional<T> const & x, optional<U> const & y )
{
return !(y < x);
}
template< typename T, typename U >
inline bool operator>=( optional<T> const & x, optional<U> const & y )
{
return !(x < y);
}
// Comparison with nullopt
template< typename T >
inline bool operator==( optional<T> const & x, nullopt_t )
{
return (!x);
}
template< typename T >
inline bool operator==( nullopt_t, optional<T> const & x )
{
return (!x);
}
template< typename T >
inline bool operator!=( optional<T> const & x, nullopt_t )
{
return bool(x);
}
template< typename T >
inline bool operator!=( nullopt_t, optional<T> const & x )
{
return bool(x);
}
template< typename T >
inline bool operator<( optional<T> const &, nullopt_t )
{
return false;
}
template< typename T >
inline bool operator<( nullopt_t, optional<T> const & x )
{
return bool(x);
}
template< typename T >
inline bool operator<=( optional<T> const & x, nullopt_t )
{
return (!x);
}
template< typename T >
inline bool operator<=( nullopt_t, optional<T> const & )
{
return true;
}
template< typename T >
inline bool operator>( optional<T> const & x, nullopt_t )
{
return bool(x);
}
template< typename T >
inline bool operator>( nullopt_t, optional<T> const & )
{
return false;
}
template< typename T >
inline bool operator>=( optional<T> const &, nullopt_t )
{
return true;
}
template< typename T >
inline bool operator>=( nullopt_t, optional<T> const & x )
{
return (!x);
}
// Comparison with T
template< typename T, typename U >
inline bool operator==( optional<T> const & x, U const & v )
{
return bool(x) ? *x == v : false;
}
template< typename T, typename U >
inline bool operator==( U const & v, optional<T> const & x )
{
return bool(x) ? v == *x : false;
}
template< typename T, typename U >
inline bool operator!=( optional<T> const & x, U const & v )
{
return bool(x) ? *x != v : true;
}
template< typename T, typename U >
inline bool operator!=( U const & v, optional<T> const & x )
{
return bool(x) ? v != *x : true;
}
template< typename T, typename U >
inline bool operator<( optional<T> const & x, U const & v )
{
return bool(x) ? *x < v : true;
}
template< typename T, typename U >
inline bool operator<( U const & v, optional<T> const & x )
{
return bool(x) ? v < *x : false;
}
template< typename T, typename U >
inline bool operator<=( optional<T> const & x, U const & v )
{
return bool(x) ? *x <= v : true;
}
template< typename T, typename U >
inline bool operator<=( U const & v, optional<T> const & x )
{
return bool(x) ? v <= *x : false;
}
template< typename T, typename U >
inline bool operator>( optional<T> const & x, U const & v )
{
return bool(x) ? *x > v : false;
}
template< typename T, typename U >
inline bool operator>( U const & v, optional<T> const & x )
{
return bool(x) ? v > *x : true;
}
template< typename T, typename U >
inline bool operator>=( optional<T> const & x, U const & v )
{
return bool(x) ? *x >= v : false;
}
template< typename T, typename U >
inline bool operator>=( U const & v, optional<T> const & x )
{
return bool(x) ? v >= *x : true;
}
// Specialized algorithms
template< typename T >
void swap( optional<T> & x, optional<T> & y )
{
x.swap( y );
}
// Convenience function to create an optional.
template< typename T >
inline optional<T> make_optional( T const & v )
{
return optional<T>( v );
}
} // namespace optional-bare
using namespace optional_bare;
} // namespace nonstd
#endif // optional_USES_STD_OPTIONAL
#endif // NONSTD_OPTIONAL_BARE_HPP

View File

@ -6,7 +6,7 @@
</provides> </provides>
<launchable type="desktop-id">org.polymc.PolyMC.desktop</launchable> <launchable type="desktop-id">org.polymc.PolyMC.desktop</launchable>
<name>PolyMC</name> <name>PolyMC</name>
<developer_name>PolyMC Team</developer_name> <developer_name>PolyMC</developer_name>
<summary>A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once</summary> <summary>A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once</summary>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license> <project_license>GPL-3.0-only</project_license>
@ -16,35 +16,39 @@
<p>PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.</p> <p>PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.</p>
<p>Features:</p> <p>Features:</p>
<ul> <ul>
<li>Easily install game modifications, such as Fabric or Forge</li> <li>Easily install game modifications, such as Fabric, Forge and Quilt</li>
<li>Control your java settings</li> <li>Control your java settings</li>
<li>Manage worlds and resource packs from the launcher</li> <li>Manage worlds and resource packs from the launcher</li>
<li>See logs and other details easily</li> <li>See logs and other details easily</li>
<li>Kill Minecraft in case of a crash/freeze</li> <li>Kill Minecraft in case of a crash/freeze</li>
<li>Isolate minecraft instances to keep everything clean</li> <li>Isolate minecraft instances to keep everything clean</li>
<li>Install mods directly from the launcher</li> <li>Install and update mods directly from the launcher</li>
</ul> </ul>
</description> </description>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<caption>The main PolyMC window</caption> <caption>The main PolyMC window</caption>
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherDark.png</image> <image type="source" width="578" height="452">https://polymc.org/img/screenshots/LauncherDark.png</image>
</screenshot> </screenshot>
<screenshot> <screenshot>
<caption>Modpack installation</caption> <caption>Modpack installation</caption>
<image type="source" width="860" height="848">https://polymc.org/img/screenshots/ModpackInstallDark.png</image> <image type="source" width="523" height="452">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
</screenshot> </screenshot>
<screenshot> <screenshot>
<caption>Mod installation</caption> <caption>Mod installation</caption>
<image type="source" width="1018" height="858">https://polymc.org/img/screenshots/ModInstallDark.png</image> <image type="source" width="654" height="452">https://polymc.org/img/screenshots/ModInstallDark.png</image>
</screenshot>
<screenshot>
<caption>Mod updating</caption>
<image type="source" width="490" height="452">https://polymc.org/img/screenshots/ModUpdateDark.png</image>
</screenshot> </screenshot>
<screenshot> <screenshot>
<caption>Instance management</caption> <caption>Instance management</caption>
<image type="source" width="777" height="693">https://polymc.org/img/screenshots/PropertiesDark.png</image> <image type="source" width="667" height="452">https://polymc.org/img/screenshots/PropertiesDark.png</image>
</screenshot> </screenshot>
<screenshot> <screenshot>
<caption>Cat :)</caption> <caption>Cat :)</caption>
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherCatDark.png</image> <image type="source" width="555" height="452">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
</screenshot> </screenshot>
</screenshots> </screenshots>
<releases> <releases>