Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e65cd4405 | |||
98fbb3613d | |||
2666beafb1 | |||
cf8a76be6b | |||
e96f482544 | |||
dc32f0671b | |||
d6d7794cd0 | |||
94d95d45d4 | |||
5e767a91d9 | |||
c89f8b4657 | |||
d7fc0f53d3 | |||
e81cf8a845 | |||
c116885ab1 | |||
d68d5ca23f | |||
6d94338a56 | |||
1e1a1cef05 | |||
34bab3e1b2 | |||
be6d6501e8 | |||
10a70732ce | |||
a725dc82a7 | |||
97ce8a94e9 | |||
85f0904872 | |||
cb0a5e42df | |||
a0c7fa30c0 | |||
b7490b479c | |||
0d35edbbf3 |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
macosx_deployment_target: 10.14
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_version: '6.3.1'
|
||||
qt_version: '6.3.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_path: /Users/runner/work/PolyMC/Qt
|
||||
|
||||
@ -314,6 +314,9 @@ jobs:
|
||||
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
|
||||
|
||||
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/jvm/java-8-openjdk/lib/amd64/server"
|
||||
|
@ -29,10 +29,10 @@ set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
|
||||
######## Set compiler flags ########
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
||||
set(CMAKE_C_STANDARD_REQUIRED true)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
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)
|
||||
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
@ -81,7 +81,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 1)
|
||||
set(Launcher_VERSION_MINOR 4)
|
||||
set(Launcher_VERSION_HOTFIX 0)
|
||||
set(Launcher_VERSION_HOTFIX 2)
|
||||
|
||||
# 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/LocalPeer) # fork of a library from Qt solutions
|
||||
add_subdirectory(libraries/classparser) # class parser library
|
||||
add_subdirectory(libraries/optional-bare)
|
||||
add_subdirectory(libraries/tomlc99) # toml parser
|
||||
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
||||
add_subdirectory(libraries/gamemode)
|
||||
|
111
COPYING.md
111
COPYING.md
@ -32,27 +32,47 @@
|
||||
See the License for the specific language governing permissions and
|
||||
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
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
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:
|
||||
This license has been certified as open source. It has also been designated
|
||||
as GPL compatible by the Free Software Foundation (FSF).
|
||||
|
||||
The above copyright notice, this permission notice and the below disclaimer
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
1. Redistributions in source code must retain the accompanying copyright
|
||||
notice, this list of conditions, and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the accompanying
|
||||
copyright notice, this list of conditions, and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
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
|
||||
|
||||
@ -295,34 +315,6 @@
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
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
|
||||
|
||||
MIT License
|
||||
@ -373,3 +365,32 @@
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
@ -321,7 +321,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
|
||||
{
|
||||
// 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
|
||||
m_rootPath = foo.absolutePath();
|
||||
#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("TechnicPacks", QDir("cache/TechnicPacks").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("root", QDir::currentPath());
|
||||
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::failed, this, &Application::controllerFailed);
|
||||
connect(controller.get(), &LaunchController::aborted, this, [this] {
|
||||
controllerFailed(tr("Aborted"));
|
||||
});
|
||||
addRunningInstance();
|
||||
controller->start();
|
||||
return true;
|
||||
|
@ -494,6 +494,8 @@ set(API_SOURCES
|
||||
modplatform/modrinth/ModrinthAPI.cpp
|
||||
modplatform/helpers/NetworkModAPI.h
|
||||
modplatform/helpers/NetworkModAPI.cpp
|
||||
modplatform/helpers/HashUtils.h
|
||||
modplatform/helpers/HashUtils.cpp
|
||||
)
|
||||
|
||||
set(FTB_SOURCES
|
||||
@ -988,7 +990,6 @@ target_link_libraries(Launcher_logic
|
||||
Launcher_murmur2
|
||||
nbt++
|
||||
${ZLIB_LIBRARIES}
|
||||
optional-bare
|
||||
tomlc99
|
||||
BuildConfig
|
||||
Katabasis
|
||||
|
@ -516,6 +516,7 @@ bool overrideFolder(QString overwritten_path, QString override_path)
|
||||
for (auto file : listFolderPaths(root_override)) {
|
||||
QString destination = file;
|
||||
destination.replace(override_path, overwritten_path);
|
||||
ensureFilePathExists(destination);
|
||||
|
||||
qDebug() << QString("Applying override %1 in %2").arg(file, destination);
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "modplatform/flame/PackManifest.h"
|
||||
|
||||
#include <nonstd/optional>
|
||||
#include <optional>
|
||||
|
||||
class QuaZip;
|
||||
namespace Flame
|
||||
@ -90,8 +90,8 @@ private: /* data */
|
||||
QString m_archivePath;
|
||||
bool m_downloadRequired = false;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||
QVector<Flame::File> m_blockedMods;
|
||||
enum class ModpackType{
|
||||
Unknown,
|
||||
|
@ -145,16 +145,26 @@ void LaunchController::login() {
|
||||
return;
|
||||
}
|
||||
|
||||
// we try empty password first :)
|
||||
QString password;
|
||||
// we loop until the user succeeds in logging in or gives up
|
||||
bool tryagain = true;
|
||||
// the failure. the default failure.
|
||||
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;
|
||||
unsigned int tries = 0;
|
||||
|
||||
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->wants_online = m_online;
|
||||
m_accountToUse->fillSession(m_session);
|
||||
|
@ -34,8 +34,9 @@
|
||||
*/
|
||||
|
||||
#include "LoggedProcess.h"
|
||||
#include "MessageLevel.h"
|
||||
#include <QDebug>
|
||||
#include <QTextDecoder>
|
||||
#include "MessageLevel.h"
|
||||
|
||||
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);
|
||||
|
||||
str.remove('\r');
|
||||
QStringList lines = str.split("\n");
|
||||
leftover = lines.takeLast();
|
||||
auto str = decoder.toUnicode(data);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
|
||||
#else
|
||||
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
|
||||
#endif
|
||||
return lines;
|
||||
}
|
||||
|
||||
void LoggedProcess::on_stdErr()
|
||||
{
|
||||
auto lines = reprocess(readAllStandardError(), m_err_leftover);
|
||||
auto lines = reprocess(readAllStandardError(), m_err_decoder);
|
||||
emit log(lines, MessageLevel::StdErr);
|
||||
}
|
||||
|
||||
void LoggedProcess::on_stdOut()
|
||||
{
|
||||
auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
|
||||
auto lines = reprocess(readAllStandardOutput(), m_out_decoder);
|
||||
emit log(lines, MessageLevel::StdOut);
|
||||
}
|
||||
|
||||
@ -86,18 +88,6 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
|
||||
// save the 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
|
||||
if (!m_is_aborting)
|
||||
{
|
||||
|
@ -36,6 +36,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QProcess>
|
||||
#include <QTextDecoder>
|
||||
#include "MessageLevel.h"
|
||||
|
||||
/*
|
||||
@ -88,8 +89,8 @@ private:
|
||||
void changeState(LoggedProcess::State state);
|
||||
|
||||
private:
|
||||
QString m_err_leftover;
|
||||
QString m_out_leftover;
|
||||
QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
|
||||
QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
|
||||
bool m_killed = false;
|
||||
State m_state = NotRunning;
|
||||
int m_exit_code = 0;
|
||||
|
@ -268,7 +268,7 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
|
||||
|
||||
|
||||
// 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);
|
||||
QStringList extracted;
|
||||
@ -277,7 +277,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
||||
auto numEntries = zip->getEntriesCount();
|
||||
if(numEntries < 0) {
|
||||
qWarning() << "Failed to enumerate files in archive";
|
||||
return nonstd::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
else if(numEntries == 0) {
|
||||
qDebug() << "Extracting empty archives seems odd...";
|
||||
@ -286,7 +286,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
||||
else if (!zip->goToFirstFile())
|
||||
{
|
||||
qWarning() << "Failed to seek to first file in zip";
|
||||
return nonstd::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
do
|
||||
@ -323,7 +323,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
||||
{
|
||||
qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
|
||||
JlCompress::removeFile(extracted);
|
||||
return nonstd::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
extracted.append(absFilePath);
|
||||
@ -341,7 +341,7 @@ bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &tar
|
||||
}
|
||||
|
||||
// ours
|
||||
nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
{
|
||||
QuaZip zip(fileCompressed);
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
@ -352,13 +352,13 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
|
||||
return QStringList();
|
||||
}
|
||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
||||
return nonstd::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
return MMCZip::extractSubDir(&zip, "", dir);
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
@ -369,7 +369,7 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
|
||||
return QStringList();
|
||||
}
|
||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
||||
return nonstd::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
return MMCZip::extractSubDir(&zip, subdir, dir);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@
|
||||
#include <functional>
|
||||
|
||||
#include <quazip/JlCompress.h>
|
||||
#include <nonstd/optional>
|
||||
#include <optional>
|
||||
|
||||
namespace MMCZip
|
||||
{
|
||||
@ -95,7 +95,7 @@ namespace MMCZip
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@ -106,7 +106,7 @@ namespace MMCZip
|
||||
* \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.
|
||||
*/
|
||||
nonstd::optional<QStringList> extractDir(QString fileCompressed, QString dir);
|
||||
std::optional<QStringList> extractDir(QString fileCompressed, QString dir);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* \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
|
||||
|
@ -140,6 +140,13 @@ VersionPtr VersionList::getVersion(const QString &version)
|
||||
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)
|
||||
{
|
||||
m_name = name;
|
||||
|
@ -66,6 +66,7 @@ public:
|
||||
QString humanReadable() const;
|
||||
|
||||
VersionPtr getVersion(const QString &version);
|
||||
bool hasVersion(QString version) const;
|
||||
|
||||
QVector<VersionPtr> versions() const
|
||||
{
|
||||
|
@ -88,6 +88,9 @@ QList<NetAction::Ptr> Library::getDownloads(
|
||||
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())
|
||||
{
|
||||
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
||||
|
@ -53,12 +53,12 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <nonstd/optional>
|
||||
#include <optional>
|
||||
|
||||
using nonstd::optional;
|
||||
using nonstd::nullopt;
|
||||
using std::optional;
|
||||
using std::nullopt;
|
||||
|
||||
GameType::GameType(nonstd::optional<int> original):
|
||||
GameType::GameType(std::optional<int> original):
|
||||
original(original)
|
||||
{
|
||||
if(!original) {
|
||||
|
@ -16,11 +16,11 @@
|
||||
#pragma once
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <nonstd/optional>
|
||||
#include <optional>
|
||||
|
||||
struct GameType {
|
||||
GameType() = default;
|
||||
GameType (nonstd::optional<int> original);
|
||||
GameType (std::optional<int> original);
|
||||
|
||||
QString toTranslatedString() const;
|
||||
QString toLogString() const;
|
||||
@ -33,7 +33,7 @@ struct GameType {
|
||||
Adventure,
|
||||
Spectator
|
||||
} type = Unknown;
|
||||
nonstd::optional<int> original;
|
||||
std::optional<int> original;
|
||||
};
|
||||
|
||||
class World
|
||||
|
@ -63,6 +63,9 @@ void ModFolderModel::startWatching()
|
||||
if(is_watching)
|
||||
return;
|
||||
|
||||
// Remove orphaned metadata next time
|
||||
m_first_folder_load = true;
|
||||
|
||||
update();
|
||||
|
||||
// Watch the mods folder
|
||||
@ -113,7 +116,8 @@ bool ModFolderModel::update()
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
|
@ -172,6 +172,7 @@ protected:
|
||||
bool interaction_disabled = false;
|
||||
QDir m_dir;
|
||||
bool m_is_indexed;
|
||||
bool m_first_folder_load = true;
|
||||
QMap<QString, int> modsIndex;
|
||||
QMap<int, LocalModParseTask::ResultPtr> activeTickets;
|
||||
int nextResolutionTicket = 0;
|
||||
|
@ -38,8 +38,8 @@
|
||||
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
||||
ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)
|
||||
: m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())
|
||||
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_clean_orphan(clean_orphan), m_result(new Result())
|
||||
{}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ 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();
|
||||
signals:
|
||||
void succeeded();
|
||||
@ -67,5 +67,6 @@ private:
|
||||
private:
|
||||
QDir& m_mods_dir, m_index_dir;
|
||||
bool m_is_indexed;
|
||||
bool m_clean_orphan;
|
||||
ResultPtr m_result;
|
||||
};
|
||||
|
@ -63,11 +63,12 @@ void FMLLibrariesTask::executeTask()
|
||||
setStatus(tr("Downloading FML libraries..."));
|
||||
auto dljob = new NetJob("FML libraries", APPLICATION->network());
|
||||
auto metacache = APPLICATION->metacache();
|
||||
Net::Download::Options options = Net::Download::Option::MakeEternal;
|
||||
for (auto &lib : fmlLibsToProcess)
|
||||
{
|
||||
auto entry = metacache->resolveEntry("fmllibs", 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);
|
||||
|
@ -3,81 +3,73 @@
|
||||
#include <MurmurHash2.h>
|
||||
#include <QDebug>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
|
||||
|
||||
#include "net/NetJob.h"
|
||||
#include "tasks/MultipleOptionsTask.h"
|
||||
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
|
||||
static ModrinthAPI modrinth_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);
|
||||
if (hash.isEmpty())
|
||||
emitFail(mod);
|
||||
else
|
||||
m_mods.insert(hash, mod);
|
||||
auto hash_task = createNewHash(mod);
|
||||
if (!hash_task)
|
||||
return;
|
||||
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); });
|
||||
hash_task->start();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!mod->valid()) {
|
||||
emitFail(mod);
|
||||
auto hash_task = createNewHash(mod);
|
||||
if (!hash_task)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto hash = getHash(mod);
|
||||
if (hash.isEmpty()) {
|
||||
emitFail(mod);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_mods.insert(hash, mod);
|
||||
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); });
|
||||
m_hashing_task->addTask(hash_task);
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
QByteArray jar_data;
|
||||
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();
|
||||
if (!mod || !mod->valid() || mod->type() == Mod::MOD_FOLDER)
|
||||
return nullptr;
|
||||
|
||||
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) {
|
||||
case ModPlatform::Provider::MODRINTH: {
|
||||
auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).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()));
|
||||
}
|
||||
// We already have the hash computed
|
||||
if (it != m_mods.keyValueEnd()) {
|
||||
return (*it).first;
|
||||
}
|
||||
|
||||
// No existing hash
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -127,11 +119,9 @@ void EnsureMetadataTask::executeTask()
|
||||
}
|
||||
|
||||
auto invalidade_leftover = [this] {
|
||||
QMutableHashIterator<QString, Mod*> mods_iter(m_mods);
|
||||
while (mods_iter.hasNext()) {
|
||||
auto mod = mods_iter.next();
|
||||
emitFail(mod.value());
|
||||
}
|
||||
for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++)
|
||||
emitFail(mod.value(), mod.key(), RemoveFromList::No);
|
||||
m_mods.clear();
|
||||
|
||||
emitSucceeded();
|
||||
};
|
||||
@ -178,20 +168,44 @@ void EnsureMetadataTask::executeTask()
|
||||
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());
|
||||
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());
|
||||
emit metadataFailed(m);
|
||||
|
||||
m_mods.remove(getHash(m));
|
||||
if (remove == RemoveFromList::Yes) {
|
||||
if (key.isEmpty())
|
||||
key = getExistingHash(m);
|
||||
m_mods.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Modrinth
|
||||
|
@ -1,12 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModIndex.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "modplatform/helpers/HashUtils.h"
|
||||
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
class Mod;
|
||||
class QDir;
|
||||
class MultipleOptionsTask;
|
||||
|
||||
class EnsureMetadataTask : public Task {
|
||||
Q_OBJECT
|
||||
@ -17,6 +19,8 @@ class EnsureMetadataTask : public Task {
|
||||
|
||||
~EnsureMetadataTask() = default;
|
||||
|
||||
Task::Ptr getHashingTask() { return m_hashing_task; }
|
||||
|
||||
public slots:
|
||||
bool abort() override;
|
||||
protected slots:
|
||||
@ -31,10 +35,16 @@ class EnsureMetadataTask : public Task {
|
||||
auto flameProjectsTask() -> NetJob::Ptr;
|
||||
|
||||
// Helpers
|
||||
void emitReady(Mod*);
|
||||
void emitFail(Mod*);
|
||||
enum class RemoveFromList {
|
||||
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:
|
||||
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
|
||||
@ -50,5 +60,6 @@ class EnsureMetadataTask : public Task {
|
||||
ModPlatform::Provider m_provider;
|
||||
|
||||
QHash<QString, ModPlatform::IndexedVersion> m_temp_versions;
|
||||
ConcurrentTask* m_hashing_task;
|
||||
NetJob* m_current_task;
|
||||
};
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QIODevice>
|
||||
|
||||
namespace ModPlatform {
|
||||
|
||||
@ -53,34 +55,26 @@ auto ProviderCapabilities::hashType(Provider p) -> QStringList
|
||||
}
|
||||
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) {
|
||||
case Provider::MODRINTH: {
|
||||
// NOTE: Data is the result of reading the entire JAR file!
|
||||
|
||||
// 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);
|
||||
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5;
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
|
||||
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
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
|
||||
class QIODevice;
|
||||
|
||||
namespace ModPlatform {
|
||||
|
||||
enum class Provider {
|
||||
@ -36,7 +38,7 @@ class ProviderCapabilities {
|
||||
auto name(Provider) -> const char*;
|
||||
auto readableName(Provider) -> QString;
|
||||
auto hashType(Provider) -> QStringList;
|
||||
auto hash(Provider, QByteArray&, QString type = "") -> QByteArray;
|
||||
auto hash(Provider, QIODevice*, QString type = "") -> QString;
|
||||
};
|
||||
|
||||
struct ModpackAuthor {
|
||||
|
@ -46,7 +46,7 @@
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "meta/Version.h"
|
||||
|
||||
#include <nonstd/optional>
|
||||
#include <optional>
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
@ -131,8 +131,8 @@ private:
|
||||
Meta::VersionPtr minecraftVersion;
|
||||
QMap<QString, Meta::VersionPtr> componentsToInstall;
|
||||
|
||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||
|
||||
QFuture<bool> m_modExtractFuture;
|
||||
QFutureWatcher<bool> m_modExtractFutureWatcher;
|
||||
|
81
launcher/modplatform/helpers/HashUtils.cpp
Normal file
81
launcher/modplatform/helpers/HashUtils.cpp
Normal 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
|
47
launcher/modplatform/helpers/HashUtils.h
Normal file
47
launcher/modplatform/helpers/HashUtils.h
Normal 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
|
@ -10,7 +10,7 @@
|
||||
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include <nonstd/optional>
|
||||
#include <optional>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
@ -46,8 +46,8 @@ private: /* data */
|
||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||
bool abortable = false;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||
NetJob::Ptr netJobContainer;
|
||||
QString archivePath;
|
||||
|
||||
|
@ -2,11 +2,14 @@
|
||||
#include "ModrinthAPI.h"
|
||||
#include "ModrinthPackIndex.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "ModDownloadTask.h"
|
||||
|
||||
#include "modplatform/helpers/HashUtils.h"
|
||||
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
static ModrinthAPI api;
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
|
||||
@ -32,6 +35,8 @@ void ModrinthCheckUpdate::executeTask()
|
||||
// Create all hashes
|
||||
QStringList hashes;
|
||||
auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first();
|
||||
|
||||
ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10);
|
||||
for (auto* mod : m_mods) {
|
||||
if (!mod->enabled()) {
|
||||
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
|
||||
@ -44,25 +49,25 @@ void ModrinthCheckUpdate::executeTask()
|
||||
// need to generate a new hash if the current one is innadequate
|
||||
// (though it will rarely happen, if at all)
|
||||
if (mod->metadata()->hash_format != best_hash_type) {
|
||||
QByteArray jar_data;
|
||||
|
||||
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();
|
||||
|
||||
failed(e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
hash = QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, best_hash_type).toHex());
|
||||
auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath());
|
||||
connect(hash_task.get(), &Task::succeeded, [&] {
|
||||
QString hash (hash_task->getResult());
|
||||
hashes.append(hash);
|
||||
mappings.insert(hash, mod);
|
||||
});
|
||||
connect(hash_task.get(), &Task::failed, [this, hash_task] { failed("Failed to generate hash"); });
|
||||
hashing_task.addTask(hash_task);
|
||||
} else {
|
||||
hashes.append(hash);
|
||||
mappings.insert(hash, mod);
|
||||
}
|
||||
|
||||
hashes.append(hash);
|
||||
mappings.insert(hash, mod);
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
connect(&hashing_task, &Task::finished, [&loop]{ loop.quit(); });
|
||||
hashing_task.start();
|
||||
loop.exec();
|
||||
|
||||
auto* response = new QByteArray();
|
||||
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include <nonstd/optional>
|
||||
#include <optional>
|
||||
|
||||
namespace Technic {
|
||||
|
||||
@ -57,8 +57,8 @@ private:
|
||||
QString m_archivePath;
|
||||
NetJob::Ptr m_filesNetJob;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||
};
|
||||
|
||||
} // namespace Technic
|
||||
|
@ -60,7 +60,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
|
||||
dl->m_url = url;
|
||||
dl->m_options = options;
|
||||
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);
|
||||
return dl;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class Download : public NetAction {
|
||||
|
||||
public:
|
||||
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)
|
||||
|
||||
protected:
|
||||
|
@ -121,6 +121,14 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
||||
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->basePath = getBasePath(base);
|
||||
return entry;
|
||||
@ -221,6 +229,13 @@ void HttpMetaCache::Load()
|
||||
foo->etag = Json::ensureString(element_obj, "etag");
|
||||
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_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
|
||||
foo->stale = false;
|
||||
|
||||
@ -240,6 +255,8 @@ void HttpMetaCache::SaveNow()
|
||||
if (m_index_file.isNull())
|
||||
return;
|
||||
|
||||
qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries";
|
||||
|
||||
QJsonObject toplevel;
|
||||
Json::writeString(toplevel, "version", "1");
|
||||
|
||||
@ -259,6 +276,12 @@ void HttpMetaCache::SaveNow()
|
||||
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
|
||||
if (!entry->remote_changed_timestamp.isEmpty())
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -64,14 +64,31 @@ class MetaEntry {
|
||||
auto getMD5Sum() -> QString { return 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:
|
||||
QString baseId;
|
||||
QString basePath;
|
||||
QString relativePath;
|
||||
QString md5sum;
|
||||
QString etag;
|
||||
|
||||
qint64 local_changed_timestamp = 0;
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -36,13 +36,18 @@
|
||||
#include "MetaCacheSink.h"
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include "FileSystem.h"
|
||||
#include "Application.h"
|
||||
|
||||
namespace Net {
|
||||
|
||||
MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum)
|
||||
:Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum)
|
||||
/** Maximum time to hold a cache entry
|
||||
* = 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);
|
||||
}
|
||||
@ -88,6 +93,40 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
|
||||
}
|
||||
|
||||
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);
|
||||
APPLICATION->metacache()->updateEntry(m_entry);
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
||||
namespace Net {
|
||||
class MetaCacheSink : public FileSink {
|
||||
public:
|
||||
MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum);
|
||||
MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum, bool is_eternal = false);
|
||||
virtual ~MetaCacheSink() = default;
|
||||
|
||||
auto hasLocalData() -> bool override;
|
||||
@ -54,5 +54,6 @@ class MetaCacheSink : public FileSink {
|
||||
private:
|
||||
MetaEntryPtr m_entry;
|
||||
ChecksumValidator* m_md5Node;
|
||||
bool m_is_eternal;
|
||||
};
|
||||
} // namespace Net
|
||||
|
@ -1,10 +1,11 @@
|
||||
#include "ConcurrentTask.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
|
||||
ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent)
|
||||
: Task(parent), m_name(task_name), m_total_max_size(max_concurrent)
|
||||
{}
|
||||
{ setObjectName(task_name); }
|
||||
|
||||
ConcurrentTask::~ConcurrentTask()
|
||||
{
|
||||
@ -36,8 +37,9 @@ void ConcurrentTask::executeTask()
|
||||
{
|
||||
m_total_size = m_queue.size();
|
||||
|
||||
for (int i = 0; i < m_total_max_size; i++)
|
||||
startNext();
|
||||
for (int i = 0; i < m_total_max_size; i++) {
|
||||
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConcurrentTask::abort()
|
||||
@ -91,6 +93,8 @@ void ConcurrentTask::startNext()
|
||||
setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
|
||||
updateState();
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
next->start();
|
||||
}
|
||||
|
||||
|
@ -1445,6 +1445,7 @@ void MainWindow::updateNewsLabel()
|
||||
{
|
||||
newsLabel->setText(tr("Loading news..."));
|
||||
newsLabel->setEnabled(false);
|
||||
ui->actionMoreNews->setVisible(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1453,11 +1454,13 @@ void MainWindow::updateNewsLabel()
|
||||
{
|
||||
newsLabel->setText(entries[0]->title);
|
||||
newsLabel->setEnabled(true);
|
||||
ui->actionMoreNews->setVisible(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
newsLabel->setText(tr("No news available."));
|
||||
newsLabel->setEnabled(false);
|
||||
ui->actionMoreNews->setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>783</width>
|
||||
<height>843</height>
|
||||
<width>573</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
|
@ -121,9 +121,9 @@ QList<BasePage *> ModDownloadDialog::getPages()
|
||||
{
|
||||
QList<BasePage *> pages;
|
||||
|
||||
pages.append(new ModrinthModPage(this, m_instance));
|
||||
pages.append(ModrinthModPage::create(this, m_instance));
|
||||
if (APPLICATION->currentCapabilities() & Application::SupportsFlame)
|
||||
pages.append(new FlameModPage(this, m_instance));
|
||||
pages.append(FlameModPage::create(this, m_instance));
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
@ -270,6 +270,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool
|
||||
connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -279,6 +283,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool
|
||||
connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
runningStateChanged(m_instance && m_instance->isRunning());
|
||||
ExternalResourcesPage::runningStateChanged(m_instance && m_instance->isRunning());
|
||||
|
||||
ui->actionsToolbar->insertSpacer(ui->actionViewConfigs);
|
||||
|
||||
|
@ -46,7 +46,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
|
||||
protected slots:
|
||||
void itemActivated(const QModelIndex& index);
|
||||
void filterTextChanged(const QString& newContents);
|
||||
void runningStateChanged(bool running);
|
||||
virtual void runningStateChanged(bool running);
|
||||
|
||||
virtual void addItem();
|
||||
virtual void removeItem();
|
||||
|
@ -84,51 +84,46 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
|
||||
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
|
||||
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
[this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
|
||||
auto check_allow_update = [this] {
|
||||
return (!m_instance || !m_instance->isRunning()) &&
|
||||
(ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
|
||||
};
|
||||
|
||||
connect(mods.get(), &ModFolderModel::rowsInserted, this,
|
||||
[this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
|
||||
|
||||
connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] {
|
||||
ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
|
||||
ui->actionUpdateItem->setEnabled(check_allow_update());
|
||||
});
|
||||
|
||||
connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] {
|
||||
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
|
||||
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)
|
||||
: ModFolderPage(inst, mods, parent)
|
||||
{}
|
||||
void ModFolderPage::runningStateChanged(bool running)
|
||||
{
|
||||
ExternalResourcesPage::runningStateChanged(running);
|
||||
ui->actionDownloadItem->setEnabled(!running);
|
||||
ui->actionUpdateItem->setEnabled(!running);
|
||||
}
|
||||
|
||||
bool ModFolderPage::shouldDisplay() const
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
@ -232,3 +227,28 @@ void ModFolderPage::updateMods()
|
||||
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;
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ class ModFolderPage : public ExternalResourcesPage {
|
||||
virtual QString helpPage() const override { return "Loader-mods"; }
|
||||
|
||||
virtual bool shouldDisplay() const override;
|
||||
void runningStateChanged(bool running) override;
|
||||
|
||||
private slots:
|
||||
void installMods();
|
||||
@ -63,5 +64,11 @@ class CoreModFolderPage : public ModFolderPage {
|
||||
public:
|
||||
explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
|
||||
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;
|
||||
};
|
||||
|
@ -211,7 +211,7 @@ void WorldListPage::on_actionDatapacks_triggered()
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
if(!worldSafetyNagQuestion(tr("Open World Datapacks Folder")))
|
||||
return;
|
||||
|
||||
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
|
||||
@ -269,7 +269,7 @@ void WorldListPage::on_actionMCEdit_triggered()
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
if(!worldSafetyNagQuestion(tr("Open World in MCEdit")))
|
||||
return;
|
||||
|
||||
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
|
||||
@ -373,11 +373,11 @@ bool WorldListPage::isWorldSafe(QModelIndex)
|
||||
return !m_inst->isRunning();
|
||||
}
|
||||
|
||||
bool WorldListPage::worldSafetyNagQuestion()
|
||||
bool WorldListPage::worldSafetyNagQuestion(const QString &actionType)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
@ -395,7 +395,7 @@ void WorldListPage::on_actionCopy_triggered()
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
if(!worldSafetyNagQuestion(tr("Copy World")))
|
||||
return;
|
||||
|
||||
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
|
||||
@ -417,7 +417,7 @@ void WorldListPage::on_actionRename_triggered()
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
if(!worldSafetyNagQuestion(tr("Rename World")))
|
||||
return;
|
||||
|
||||
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
|
||||
|
@ -93,7 +93,7 @@ protected:
|
||||
private:
|
||||
QModelIndex getSelectedWorld();
|
||||
bool isWorldSafe(QModelIndex index);
|
||||
bool worldSafetyNagQuestion();
|
||||
bool worldSafetyNagQuestion(const QString &actionType);
|
||||
void mceditError();
|
||||
|
||||
private:
|
||||
|
@ -44,12 +44,12 @@
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "ui/dialogs/ModDownloadDialog.h"
|
||||
|
||||
|
||||
ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
|
||||
: QWidget(dialog)
|
||||
, m_instance(instance)
|
||||
, ui(new Ui::ModPage)
|
||||
, dialog(dialog)
|
||||
, filter_widget(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this)
|
||||
, api(api)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@ -59,18 +59,6 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
|
||||
|
||||
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
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()
|
||||
@ -78,6 +66,26 @@ ModPage::~ModPage()
|
||||
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 ********/
|
||||
|
||||
@ -105,13 +113,13 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool
|
||||
|
||||
void ModPage::filterMods()
|
||||
{
|
||||
filter_widget.setHidden(!filter_widget.isHidden());
|
||||
m_filter_widget->setHidden(!m_filter_widget->isHidden());
|
||||
}
|
||||
|
||||
void ModPage::triggerSearch()
|
||||
{
|
||||
auto changed = filter_widget.changed();
|
||||
m_filter = filter_widget.getFilter();
|
||||
auto changed = m_filter_widget->changed();
|
||||
m_filter = m_filter_widget->getFilter();
|
||||
|
||||
if(changed){
|
||||
ui->packView->clearSelection();
|
||||
|
@ -20,7 +20,17 @@ class ModPage : public QWidget, public BasePage {
|
||||
Q_OBJECT
|
||||
|
||||
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;
|
||||
|
||||
/* 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 getDialog() const -> const ModDownloadDialog* { return dialog; }
|
||||
|
||||
void setFilterWidget(unique_qobject_ptr<ModFilterWidget>&);
|
||||
|
||||
auto getCurrent() -> ModPlatform::IndexedPack& { return current; }
|
||||
void updateModVersions(int prev_count = -1);
|
||||
|
||||
@ -54,6 +66,7 @@ class ModPage : public QWidget, public BasePage {
|
||||
BaseInstance* m_instance;
|
||||
|
||||
protected:
|
||||
ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api);
|
||||
void updateSelectionButton();
|
||||
|
||||
protected slots:
|
||||
@ -67,7 +80,7 @@ class ModPage : public QWidget, public BasePage {
|
||||
Ui::ModPage* ui = nullptr;
|
||||
ModDownloadDialog* dialog = nullptr;
|
||||
|
||||
ModFilterWidget filter_widget;
|
||||
unique_qobject_ptr<ModFilterWidget> m_filter_widget;
|
||||
std::shared_ptr<ModFilterWidget::Filter> m_filter;
|
||||
|
||||
ModPlatform::ListModel* listModel = nullptr;
|
||||
|
@ -44,7 +44,12 @@ class FlameModPage : public ModPage {
|
||||
Q_OBJECT
|
||||
|
||||
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;
|
||||
|
||||
inline auto displayName() const -> QString override { return "CurseForge"; }
|
||||
|
@ -35,7 +35,11 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextBrowser" name="publicPackDescription"/>
|
||||
<widget class="QTextBrowser" name="publicPackDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -45,7 +49,11 @@
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<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 row="0" column="0">
|
||||
<widget class="QTreeView" name="thirdPartyPackList">
|
||||
@ -95,7 +103,11 @@
|
||||
</widget>
|
||||
</item>
|
||||
<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>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -44,7 +44,12 @@ class ModrinthModPage : public ModPage {
|
||||
Q_OBJECT
|
||||
|
||||
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;
|
||||
|
||||
inline auto displayName() const -> QString override { return "Modrinth"; }
|
||||
|
@ -1,6 +1,39 @@
|
||||
#include "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)
|
||||
: 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_version_list = APPLICATION->metadataIndex()->get("net.minecraft");
|
||||
setHidden(true);
|
||||
}
|
||||
|
||||
@ -51,24 +85,30 @@ auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter>
|
||||
return m_filter;
|
||||
}
|
||||
|
||||
void ModFilterWidget::disableVersionButton(VersionButtonID id)
|
||||
void ModFilterWidget::disableVersionButton(VersionButtonID id, QString reason)
|
||||
{
|
||||
QAbstractButton* btn = nullptr;
|
||||
|
||||
switch(id){
|
||||
case(VersionButtonID::Strict):
|
||||
ui->strictVersionButton->setEnabled(false);
|
||||
btn = ui->strictVersionButton;
|
||||
break;
|
||||
case(VersionButtonID::Major):
|
||||
ui->majorVersionButton->setEnabled(false);
|
||||
btn = ui->majorVersionButton;
|
||||
break;
|
||||
case(VersionButtonID::All):
|
||||
ui->allVersionsButton->setEnabled(false);
|
||||
btn = ui->allVersionsButton;
|
||||
break;
|
||||
case(VersionButtonID::Between):
|
||||
// ui->betweenVersionsButton->setEnabled(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (btn) {
|
||||
btn->setEnabled(false);
|
||||
if (!reason.isEmpty())
|
||||
btn->setText(btn->text() + QString(" (%1)").arg(reason));
|
||||
}
|
||||
}
|
||||
|
||||
void ModFilterWidget::onVersionFilterChanged(int id)
|
||||
@ -76,7 +116,7 @@ void ModFilterWidget::onVersionFilterChanged(int id)
|
||||
//ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between);
|
||||
//ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between);
|
||||
|
||||
int index = 0;
|
||||
int index = 1;
|
||||
|
||||
auto cast_id = (VersionButtonID) id;
|
||||
if (cast_id != m_version_id) {
|
||||
@ -93,10 +133,15 @@ void ModFilterWidget::onVersionFilterChanged(int id)
|
||||
break;
|
||||
case(VersionButtonID::Major): {
|
||||
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);
|
||||
i = Version(QString("%1.%2.%3").arg(versionSplit[0], versionSplit[1], QString("%1").arg(index)));
|
||||
|
||||
auto major_version = QString("%1.%2").arg(versionSplit[0], versionSplit[1]);
|
||||
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;
|
||||
}
|
||||
case(VersionButtonID::All):
|
||||
|
@ -4,6 +4,10 @@
|
||||
#include <QButtonGroup>
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
|
||||
@ -34,18 +38,22 @@ public:
|
||||
std::shared_ptr<Filter> m_filter;
|
||||
|
||||
public:
|
||||
explicit ModFilterWidget(Version def, QWidget* parent = nullptr);
|
||||
static unique_qobject_ptr<ModFilterWidget> create(Version default_version, QWidget* parent = nullptr);
|
||||
~ModFilterWidget();
|
||||
|
||||
void setInstance(MinecraftInstance* instance);
|
||||
|
||||
/// By default all buttons are enabled
|
||||
void disableVersionButton(VersionButtonID);
|
||||
void disableVersionButton(VersionButtonID, QString reason = {});
|
||||
|
||||
auto getFilter() -> std::shared_ptr<Filter>;
|
||||
auto changed() const -> bool { return m_last_version_id != m_version_id; }
|
||||
|
||||
Meta::VersionListPtr versionList() { return m_version_list; }
|
||||
|
||||
private:
|
||||
ModFilterWidget(Version def, QWidget* parent = nullptr);
|
||||
|
||||
inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; }
|
||||
inline auto mcVersion() const -> Version { return { mcVersionStr() }; }
|
||||
|
||||
@ -61,8 +69,12 @@ private:
|
||||
|
||||
MinecraftInstance* m_instance = nullptr;
|
||||
|
||||
|
||||
/* Version stuff */
|
||||
QButtonGroup m_mcVersion_buttons;
|
||||
|
||||
Meta::VersionListPtr m_version_list;
|
||||
|
||||
/* Used to tell if the filter was changed since the last getFilter() call */
|
||||
VersionButtonID m_last_version_id = VersionButtonID::Strict;
|
||||
VersionButtonID m_version_id = VersionButtonID::Strict;
|
||||
|
@ -15,7 +15,7 @@ A performance optimization daemon.
|
||||
|
||||
See [github repo](https://github.com/FeralInteractive/gamemode).
|
||||
|
||||
BSD licensed
|
||||
BSD-3-Clause licensed
|
||||
|
||||
## hoedown
|
||||
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).
|
||||
|
||||
## 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
|
||||
|
||||
A zip manipulation library, forked for MultiMC's use.
|
||||
A zip manipulation library.
|
||||
|
||||
LGPL 2.1
|
||||
LGPL 2.1 with linking exception.
|
||||
|
||||
## rainbow
|
||||
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
|
||||
|
||||
A MultiMC-specific library for probing system information.
|
||||
A PolyMC-specific library for probing system information.
|
||||
|
||||
Apache 2.0
|
||||
|
||||
@ -190,7 +182,7 @@ Licenced under the MIT licence.
|
||||
|
||||
## 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.
|
||||
|
||||
|
@ -1,86 +1,110 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// MurmurHash2 was written by Austin Appleby, and is placed in the public
|
||||
// domain. The author hereby disclaims copyright to this source code.
|
||||
|
||||
// Note - This code makes a few assumptions about how your machine behaves -
|
||||
|
||||
// 1. We can read a 4-byte value from any address without crashing
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#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)
|
||||
|
||||
#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 )
|
||||
uint32_t MurmurHash2(std::ifstream&& file_stream, std::size_t buffer_size, std::function<bool(char)> filter_out)
|
||||
{
|
||||
// 'm' and 'r' are mixing constants generated offline.
|
||||
// They're not really 'magic', they just happen to work well.
|
||||
auto* buffer = new char[buffer_size];
|
||||
char data[4];
|
||||
|
||||
const uint32_t m = 0x5bd1e995;
|
||||
const int r = 24;
|
||||
int read = 0;
|
||||
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);
|
||||
|
||||
// Mix 4 bytes at a time into the hash
|
||||
const auto* data = (const unsigned char*) key;
|
||||
while(len >= 4)
|
||||
{
|
||||
uint32_t k = *(uint32_t*)data;
|
||||
int index = 0;
|
||||
|
||||
k *= m;
|
||||
k ^= k >> r;
|
||||
k *= m;
|
||||
// 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];
|
||||
|
||||
h *= m;
|
||||
h ^= k;
|
||||
if (filter_out(c))
|
||||
continue;
|
||||
|
||||
data += 4*sizeof(char);
|
||||
len -= 4;
|
||||
}
|
||||
data[index] = c;
|
||||
index = (index + 1) % 4;
|
||||
|
||||
// Handle the last few bytes of the input array
|
||||
// Mix 4 bytes at a time into the hash
|
||||
if (index == 0)
|
||||
FourBytes_MurmurHash2((unsigned char*)&data, info);
|
||||
}
|
||||
} while (!file_stream.eof());
|
||||
|
||||
switch(len)
|
||||
{
|
||||
case 3: h ^= data[2] << 16;
|
||||
case 2: h ^= data[1] << 8;
|
||||
case 1: h ^= data[0];
|
||||
h *= m;
|
||||
};
|
||||
// Do one last bit shuffle in the hash
|
||||
FourBytes_MurmurHash2((unsigned char*)&data, info);
|
||||
|
||||
// Do a few final mixes of the hash to ensure the last few
|
||||
// bytes are well-incorporated.
|
||||
delete[] buffer;
|
||||
|
||||
h ^= h >> 13;
|
||||
h *= m;
|
||||
h ^= h >> 15;
|
||||
file_stream.close();
|
||||
return info.h;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
void FourBytes_MurmurHash2(const unsigned char* data, IncrementalHashInfo& prev)
|
||||
{
|
||||
if (prev.len >= 4) {
|
||||
// Not the final mix
|
||||
uint32_t k = *(uint32_t*)data;
|
||||
|
||||
k *= m;
|
||||
k ^= k >> r;
|
||||
k *= m;
|
||||
|
||||
prev.h *= m;
|
||||
prev.h ^= k;
|
||||
|
||||
prev.len -= 4;
|
||||
} else {
|
||||
// The final mix
|
||||
|
||||
// Handle the last few bytes of the input array
|
||||
switch (prev.len) {
|
||||
case 3:
|
||||
prev.h ^= data[2] << 16;
|
||||
case 2:
|
||||
prev.h ^= data[1] << 8;
|
||||
case 1:
|
||||
prev.h ^= data[0];
|
||||
prev.h *= m;
|
||||
};
|
||||
|
||||
// Do a few final mixes of the hash to ensure the last few
|
||||
// bytes are well-incorporated.
|
||||
|
||||
prev.h ^= prev.h >> 13;
|
||||
prev.h *= m;
|
||||
prev.h ^= prev.h >> 15;
|
||||
|
||||
prev.len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -1,30 +1,33 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// MurmurHash2 was written by Austin Appleby, and is placed in the public
|
||||
// domain. The author hereby disclaims copyright to this source code.
|
||||
// The original MurmurHash2 was written by Austin Appleby, and is placed in the
|
||||
// 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
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Platform-specific functions and macros
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
// Microsoft Visual Studio
|
||||
|
||||
#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)
|
||||
#include <functional>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -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")
|
@ -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.
|
@ -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
|
@ -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
|
@ -6,7 +6,7 @@
|
||||
</provides>
|
||||
<launchable type="desktop-id">org.polymc.PolyMC.desktop</launchable>
|
||||
<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>
|
||||
<metadata_license>CC0-1.0</metadata_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>Features:</p>
|
||||
<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>Manage worlds and resource packs from the launcher</li>
|
||||
<li>See logs and other details easily</li>
|
||||
<li>Kill Minecraft in case of a crash/freeze</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>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</screenshots>
|
||||
<releases>
|
||||
|
Reference in New Issue
Block a user