Compare commits
97 Commits
ed3cf5d9bf
...
bc1c592f41
Author | SHA1 | Date | |
---|---|---|---|
bc1c592f41 | |||
|
ffcb0c3f41 | ||
|
02e6667761 | ||
|
3f54396705 | ||
|
b72d486e84 | ||
|
ba2fba10c2 | ||
|
aeac0eb9ba | ||
|
0a9801fb20 | ||
|
5e22009554 | ||
|
87ea20df5a | ||
|
0919630ed6 | ||
|
bce5a8df35 | ||
|
24c471de88 | ||
|
ff74d0d7ad | ||
|
94b01a5f5c | ||
|
385eaea709 | ||
|
6431265a73 | ||
|
f89a87e28a | ||
|
53a4395d5b | ||
|
290ccebc29 | ||
|
e2875ef9a5 | ||
|
d380c07d62 | ||
|
c4d68b7ccf | ||
|
c0ca8d21ca | ||
|
7dff3e5afc | ||
|
93d868230f | ||
|
9bc2873b89 | ||
|
1471d64d11 | ||
|
6a06bcc4ea | ||
|
b995074440 | ||
|
a6089296ce | ||
|
641f687043 | ||
|
19673a7d59 | ||
|
2358e6faa2 | ||
|
8e842d1b6e | ||
|
9cf31f2c9b | ||
|
eabd225b06 | ||
|
549cfa8833 | ||
|
9cb6200081 | ||
|
1f4d9cc12f | ||
|
4eb9085e71 | ||
|
2a9bb95e1f | ||
|
9b41a3bd48 | ||
|
e4211fea1a | ||
|
a7d8cc6605 | ||
|
9ee8f019e1 | ||
|
b4029553d4 | ||
|
3db09c5466 | ||
|
3f2af124e7 | ||
|
403f8a7d96 | ||
|
94e3b95b05 | ||
|
252da9a3dc | ||
|
4398cb5dc5 | ||
|
9f457e0ce6 | ||
|
dab0dc451c | ||
|
cdcc7d0f16 | ||
|
71d4e66c32 | ||
|
7b547c842c | ||
|
03552edbb7 | ||
|
01b52ea6be | ||
|
c16cc2d8bb | ||
|
20edcb1a2b | ||
|
67bb016623 | ||
|
fe2624bf1a | ||
|
eb390814d8 | ||
|
21e6093520 | ||
|
928ff7826c | ||
|
91c7a700a9 | ||
|
7839bd0e54 | ||
|
58bc61ccbd | ||
|
7f226eba9c | ||
|
3340d2c814 | ||
|
59c280309d | ||
|
3187b5c1c2 | ||
|
16a4a4f811 | ||
|
90bbde3631 | ||
|
211c423da1 | ||
|
51de23d83c | ||
|
cf317ca3d4 | ||
|
a3d1e88d44 | ||
|
4cccb693f2 | ||
|
6ff87f773b | ||
|
462e0ef56d | ||
|
de1de47940 | ||
|
22fb301d88 | ||
|
ddbc80e1ef | ||
|
cfe8f6ce9b | ||
|
78ec58dbf1 | ||
|
64aa817c80 | ||
|
75756a5b9e | ||
|
1b52829e6c | ||
|
b3fb52ce51 | ||
|
6b8e6774a0 | ||
|
c2a703b3d6 | ||
|
5d9ff3767a | ||
|
ce1a48be5d | ||
|
2a7c666932 |
15
.github/workflows/build.yml
vendored
@ -26,16 +26,16 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: linux
|
||||
qt_version: '6.2.4'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_modules: 'qt5compat qtimageformats qtcharts'
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-Legacy"
|
||||
msystem: mingw32
|
||||
msystem: mingw64
|
||||
qt_ver: 5
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows"
|
||||
msystem: mingw32
|
||||
msystem: mingw64
|
||||
qt_ver: 6
|
||||
|
||||
- os: macos-12
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_version: '6.3.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_modules: 'qt5compat qtimageformats qtcharts'
|
||||
|
||||
- os: macos-12
|
||||
name: macOS-Legacy
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
qt_ver: 5
|
||||
qt_host: mac
|
||||
qt_version: '5.15.2'
|
||||
qt_modules: ''
|
||||
qt_modules: 'qtcharts'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@ -97,6 +97,7 @@ jobs:
|
||||
qt${{ matrix.qt_ver }}-base:p
|
||||
qt${{ matrix.qt_ver }}-svg:p
|
||||
qt${{ matrix.qt_ver }}-imageformats:p
|
||||
qt${{ matrix.qt_ver }}-charts:p
|
||||
quazip-qt${{ matrix.qt_ver }}:p
|
||||
ccache:p
|
||||
nsis:p
|
||||
@ -154,7 +155,7 @@ jobs:
|
||||
- name: Install Qt (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
run: |
|
||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5charts5-dev
|
||||
|
||||
- name: Install Qt (macOS and AppImage)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS'
|
||||
@ -280,7 +281,7 @@ jobs:
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
if [ "${{ matrix.qt_ver }}" == "5" ]; then
|
||||
cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./
|
||||
cp /mingw64/bin/libcrypto-3-x64.dll /mingw64/bin/libssl-3-x64.dll ./
|
||||
fi
|
||||
|
||||
- name: Package (Windows, portable)
|
||||
|
@ -34,6 +34,13 @@ set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
||||
|
||||
# Increases the stack size to 8MB for Windows, only when not building in Debug mode
|
||||
# because we don't want random users being affected by stack overflows in release builds,
|
||||
# but it's fine in debug builds for finding and fixing them
|
||||
if((NOT CMAKE_BUILD_TYPE STREQUAL "Debug") AND WIN32)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
endif()
|
||||
|
||||
# Fix build with Qt 5.13
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
|
||||
|
||||
@ -76,7 +83,7 @@ set(Launcher_NEWS_OPEN_URL "https://multimc.org/posts.html" CACHE STRING "URL th
|
||||
set(Launcher_HELP_URL "" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
|
||||
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 5)
|
||||
set(Launcher_VERSION_MAJOR 6)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||
@ -120,9 +127,6 @@ execute_process(COMMAND ./id.sh OUTPUT_VARIABLE Launcher_MSA_CLIENT_ID)
|
||||
set(Launcher_CURSEFORGE_API_KEY "" CACHE STRING "API key for the CurseForge platform")
|
||||
set(Launcher_CURSEFORGE_API_KEY_API_URL "https://cf.polymc.org/api" CACHE STRING "URL to fetch the Curseforge API key from.")
|
||||
|
||||
# Curseforge API Key
|
||||
execute_process(COMMAND ./cf.sh OUTPUT_VARIABLE Launcher_CURSEFORGE_API_KEY)
|
||||
|
||||
#### Check the current Git commit and branch
|
||||
include(GetGitRevisionDescription)
|
||||
git_get_exact_tag(Launcher_GIT_TAG)
|
||||
@ -141,7 +145,8 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
|
||||
include(QtVersionlessBackport)
|
||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test
|
||||
Xml Charts)
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
find_package(QuaZip-Qt5 1.3 QUIET)
|
||||
@ -155,7 +160,8 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
|
||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml Core5Compat)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test
|
||||
Xml Charts Core5Compat)
|
||||
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
|
@ -567,6 +567,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
|
||||
// The cat
|
||||
m_settings->registerSetting("TheCat", false);
|
||||
m_settings->registerSetting("CatStyle", "BackgroundCat");
|
||||
m_settings->registerSetting("CatPosition", "top right");
|
||||
|
||||
m_settings->registerSetting("InstSortMode", "Name");
|
||||
m_settings->registerSetting("SelectedInstance", QString());
|
||||
@ -769,6 +771,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
||||
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
||||
m_metacache->addBase("meta", QDir("meta").absolutePath());
|
||||
m_metacache->addBase("authlibinjector", QDir("cache/authlibinjector").absolutePath());
|
||||
m_metacache->Load();
|
||||
qDebug() << "<> Cache initialized.";
|
||||
}
|
||||
@ -1119,9 +1122,9 @@ void Application::setApplicationTheme(const QString& name, bool initial)
|
||||
#ifdef Q_OS_WIN
|
||||
if (m_mainWindow) {
|
||||
if (QString::compare(theme->id(), "dark") == 0) {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||
WinDarkmode::setWindowDarkModeEnabled((HWND)m_mainWindow->winId(), true);
|
||||
} else {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||
WinDarkmode::setWindowDarkModeEnabled((HWND)m_mainWindow->winId(), false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1336,9 +1339,9 @@ MainWindow* Application::showMainWindow(bool minimized)
|
||||
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
|
||||
#ifdef Q_OS_WIN
|
||||
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||
WinDarkmode::setWindowDarkModeEnabled((HWND)m_mainWindow->winId(), true);
|
||||
} else {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||
WinDarkmode::setWindowDarkModeEnabled((HWND)m_mainWindow->winId(), false);
|
||||
}
|
||||
#endif
|
||||
if(minimized)
|
||||
|
@ -189,11 +189,15 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/auth/flows/AuthFlow.h
|
||||
minecraft/auth/flows/Mojang.cpp
|
||||
minecraft/auth/flows/Mojang.h
|
||||
minecraft/auth/flows/AuthlibInjector.cpp
|
||||
minecraft/auth/flows/AuthlibInjector.h
|
||||
minecraft/auth/flows/MSA.cpp
|
||||
minecraft/auth/flows/MSA.h
|
||||
minecraft/auth/flows/Offline.cpp
|
||||
minecraft/auth/flows/Offline.h
|
||||
|
||||
minecraft/auth/steps/AuthlibInjectorStep.cpp
|
||||
minecraft/auth/steps/AuthlibInjectorStep.h
|
||||
minecraft/auth/steps/OfflineStep.cpp
|
||||
minecraft/auth/steps/OfflineStep.h
|
||||
minecraft/auth/steps/EntitlementsStep.cpp
|
||||
@ -233,6 +237,8 @@ set(MINECRAFT_SOURCES
|
||||
|
||||
minecraft/launch/ClaimAccount.cpp
|
||||
minecraft/launch/ClaimAccount.h
|
||||
minecraft/launch/ConfigureAuthlibInjector.cpp
|
||||
minecraft/launch/ConfigureAuthlibInjector.h
|
||||
minecraft/launch/CreateGameFolders.cpp
|
||||
minecraft/launch/CreateGameFolders.h
|
||||
minecraft/launch/ModMinecraftJar.cpp
|
||||
@ -675,6 +681,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pages/instance/ServersPage.h
|
||||
ui/pages/instance/WorldListPage.cpp
|
||||
ui/pages/instance/WorldListPage.h
|
||||
ui/pages/instance/StoragePage.cpp
|
||||
ui/pages/instance/StoragePage.h
|
||||
|
||||
# GUI - global settings pages
|
||||
ui/pages/global/AccountListPage.cpp
|
||||
@ -889,6 +897,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/pages/instance/VersionPage.ui
|
||||
ui/pages/instance/WorldListPage.ui
|
||||
ui/pages/instance/ScreenshotsPage.ui
|
||||
ui/pages/instance/StoragePage.ui
|
||||
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
|
||||
ui/pages/modplatform/atlauncher/AtlPage.ui
|
||||
ui/pages/modplatform/VanillaPage.ui
|
||||
@ -971,6 +980,7 @@ target_link_libraries(Launcher_logic
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Charts
|
||||
${Launcher_QT_LIBS}
|
||||
)
|
||||
target_link_libraries(Launcher_logic
|
||||
|
@ -45,6 +45,8 @@
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
@ -174,7 +176,7 @@ bool copy::operator()(const QString& offset)
|
||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||
|
||||
std::error_code err;
|
||||
std::error_code err{};
|
||||
|
||||
fs::copy_options opt = copy_opts::none;
|
||||
|
||||
@ -182,28 +184,49 @@ bool copy::operator()(const QString& offset)
|
||||
if (!m_followSymlinks)
|
||||
opt |= copy_opts::copy_symlinks;
|
||||
|
||||
const auto testAndCopy = [opt, &err](const QString& s, const QString& d) {
|
||||
if (ensureFilePathExists(d)) {
|
||||
fs::copy(toStdString(s), toStdString(d), opt, err);
|
||||
} else {
|
||||
// mkpath failed which means the destination directory doesn't exist.
|
||||
err = std::make_error_code(std::errc::no_such_file_or_directory);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << s;
|
||||
qDebug() << "Destination file:" << d;
|
||||
}
|
||||
};
|
||||
|
||||
// We can't use copy_opts::recursive because we need to take into account the
|
||||
// blacklisted paths, so we iterate over the source directory, and if there's no blacklist
|
||||
// match, we copy the file.
|
||||
QDir src_dir(src);
|
||||
QDirIterator source_it(src, QDir::Filter::Files, QDirIterator::Subdirectories);
|
||||
if (QDir src_dir(src); src_dir.exists()) {
|
||||
QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
|
||||
|
||||
while (source_it.hasNext()) {
|
||||
auto src_path = source_it.next();
|
||||
auto relative_path = src_dir.relativeFilePath(src_path);
|
||||
while (source_it.hasNext()) {
|
||||
auto src_path = source_it.next();
|
||||
auto relative_path = src_dir.relativeFilePath(src_path);
|
||||
|
||||
if (m_blacklist && m_blacklist->matches(relative_path))
|
||||
continue;
|
||||
auto dst_path = PathCombine(dst, relative_path);
|
||||
|
||||
auto dst_path = PathCombine(dst, relative_path);
|
||||
ensureFilePathExists(dst_path);
|
||||
if (m_blacklist && m_blacklist->matches(relative_path)) {
|
||||
qDebug() << "Attempted to copy blacklisted file:";
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
continue;
|
||||
}
|
||||
|
||||
fs::copy(toStdString(src_path), toStdString(dst_path), opt, err);
|
||||
if (err) {
|
||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
testAndCopy(src_path, dst_path);
|
||||
}
|
||||
} else { // src_dir could still be a file, try to copy it directly.
|
||||
if (m_blacklist && m_blacklist->matches(src)){
|
||||
qDebug() << "Attempted to copy blacklisted file:";
|
||||
qDebug() << "Source file:" << src;
|
||||
qDebug() << "Destination file:" << dst;
|
||||
} else {
|
||||
testAndCopy(src, dst);
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,7 +363,7 @@ QString getDesktopDir()
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
|
||||
{
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
#if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
|
||||
location = PathCombine(location, name + ".desktop");
|
||||
|
||||
QFile f(location);
|
||||
@ -366,49 +389,35 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
|
||||
return true;
|
||||
#elif defined Q_OS_WIN
|
||||
// TODO: Fix
|
||||
// QFile file(PathCombine(location, name + ".lnk"));
|
||||
// WCHAR *file_w;
|
||||
// WCHAR *dest_w;
|
||||
// WCHAR *args_w;
|
||||
// file.fileName().toWCharArray(file_w);
|
||||
// dest.toWCharArray(dest_w);
|
||||
|
||||
// QString argStr;
|
||||
// for (int i = 0; i < args.count(); i++)
|
||||
// {
|
||||
// argStr.append(args[i]);
|
||||
// argStr.append(" ");
|
||||
// }
|
||||
// argStr.toWCharArray(args_w);
|
||||
|
||||
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
|
||||
return false;
|
||||
#else
|
||||
qWarning("Desktop Shortcuts not supported on your platform!");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool overrideFolder(QString overwritten_path, QString override_path)
|
||||
bool mergeFolders(QString dstpath, QString srcpath)
|
||||
{
|
||||
using copy_opts = fs::copy_options;
|
||||
|
||||
if (!FS::ensureFolderPathExists(overwritten_path))
|
||||
return false;
|
||||
|
||||
std::error_code err;
|
||||
fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing;
|
||||
|
||||
fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err);
|
||||
|
||||
if (err) {
|
||||
qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path);
|
||||
qCritical() << "Reason:" << QString::fromStdString(err.message());
|
||||
std::error_code ec;
|
||||
fs::path fullSrcPath = srcpath.toStdString();
|
||||
fs::path fullDstPath = dstpath.toStdString();
|
||||
for (auto& entry : fs::recursive_directory_iterator(fullSrcPath))
|
||||
{
|
||||
fs::path relativeChild = fs::relative(entry, fullSrcPath);
|
||||
if (entry.is_directory())
|
||||
if (!fs::exists(fullDstPath / relativeChild))
|
||||
fs::create_directory(fullDstPath / relativeChild);
|
||||
if (entry.is_regular_file())
|
||||
{
|
||||
fs::path childDst = fullDstPath / relativeChild;
|
||||
if (fs::exists(childDst))
|
||||
fs::remove(childDst);
|
||||
fs::copy(entry, childDst, fs::copy_options::none, ec);
|
||||
if (ec.value() != 0)
|
||||
qCritical() << QString("File copy failed with: %1. File was %2 -> %3").arg(QString::fromStdString(ec.message()), entry.path().c_str(), childDst.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return err.value() == 0;
|
||||
return ec.value() == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -154,5 +154,5 @@ QString getDesktopDir();
|
||||
|
||||
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
|
||||
// Equivalent to doing QDir::rename, but allowing for overrides
|
||||
bool overrideFolder(QString overwritten_path, QString override_path);
|
||||
bool mergeFolders(QString dstpath, QString srcpath);
|
||||
}
|
||||
|
@ -164,23 +164,21 @@ void InstanceImportTask::processZipPack()
|
||||
}
|
||||
else
|
||||
{
|
||||
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
|
||||
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
|
||||
|
||||
if (!mmcRoot.isNull())
|
||||
{
|
||||
// process as MultiMC instance/pack
|
||||
qDebug() << "MultiMC:" << mmcRoot;
|
||||
root = mmcRoot;
|
||||
m_modpackType = ModpackType::MultiMC;
|
||||
}
|
||||
else if(!flameRoot.isNull())
|
||||
auto [rootDirectory, fileName] = MMCZip::findFolderOfFileInZip(m_packZip.get(), {"manifest.json", "instance.cfg"});
|
||||
if(fileName == "manifest.json")
|
||||
{
|
||||
// process as Flame pack
|
||||
qDebug() << "Flame:" << flameRoot;
|
||||
root = flameRoot;
|
||||
qDebug() << "Flame:" << rootDirectory;
|
||||
root = rootDirectory;
|
||||
m_modpackType = ModpackType::Flame;
|
||||
}
|
||||
else if (fileName == "instance.cfg")
|
||||
{
|
||||
// process as MultiMC instance/pack
|
||||
qDebug() << "MultiMC:" << rootDirectory;
|
||||
root = rootDirectory;
|
||||
m_modpackType = ModpackType::MultiMC;
|
||||
}
|
||||
}
|
||||
if(m_modpackType == ModpackType::Unknown)
|
||||
{
|
||||
|
@ -904,7 +904,7 @@ bool InstanceList::commitStagedInstance(const QString& path, InstanceName const&
|
||||
QString destination = FS::PathCombine(m_instDir, instID);
|
||||
|
||||
if (should_override) {
|
||||
if (!FS::overrideFolder(destination, path)) {
|
||||
if (!FS::mergeFolders(destination, path)) {
|
||||
qWarning() << "Failed to override" << path << "to" << destination;
|
||||
return false;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "ui/pages/instance/WorldListPage.h"
|
||||
#include "ui/pages/instance/ServersPage.h"
|
||||
#include "ui/pages/instance/GameOptionsPage.h"
|
||||
#include "ui/pages/instance/StoragePage.h"
|
||||
|
||||
class InstancePageProvider : public QObject, public BasePageProvider
|
||||
{
|
||||
@ -46,6 +47,7 @@ public:
|
||||
// values.append(new GameOptionsPage(onesix.get()));
|
||||
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
||||
values.append(new InstanceSettingsPage(onesix.get()));
|
||||
values.append(new StoragePage(onesix.get()));
|
||||
auto logMatcher = inst->getLogFileMatcher();
|
||||
if(logMatcher)
|
||||
{
|
||||
|
@ -112,7 +112,18 @@ void LaunchController::decideAccount()
|
||||
}
|
||||
}
|
||||
|
||||
bool overrideAccount = m_instance->settings()->get("OverrideAccount").toBool();
|
||||
QString overrideAccountProfileId = m_instance->settings()->get("OverrideAccountProfileId").toString();
|
||||
|
||||
m_accountToUse = accounts->defaultAccount();
|
||||
|
||||
if (overrideAccount) {
|
||||
int overrideIndex = accounts->findAccountByProfileId(overrideAccountProfileId);
|
||||
if (overrideIndex != -1) {
|
||||
m_accountToUse = accounts->at(overrideIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_accountToUse)
|
||||
{
|
||||
// If no default account is set, ask the user which one to use.
|
||||
@ -179,7 +190,7 @@ void LaunchController::login() {
|
||||
switch(m_accountToUse->accountState()) {
|
||||
case AccountState::Offline: {
|
||||
m_session->wants_online = false;
|
||||
// NOTE: fallthrough is intentional
|
||||
[[fallthrough]];
|
||||
}
|
||||
case AccountState::Online: {
|
||||
if(!m_session->wants_online) {
|
||||
@ -212,7 +223,6 @@ void LaunchController::login() {
|
||||
APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
|
||||
}
|
||||
m_session->MakeOffline(usedname);
|
||||
// offline flavored game from here :3
|
||||
}
|
||||
if(m_accountToUse->ownsMinecraft()) {
|
||||
if(!m_accountToUse->hasProfile()) {
|
||||
@ -259,7 +269,7 @@ void LaunchController::login() {
|
||||
// This means some sort of soft error that we can fix with a refresh ... so let's refresh.
|
||||
case AccountState::Unchecked: {
|
||||
m_accountToUse->refresh();
|
||||
// NOTE: fallthrough intentional
|
||||
[[fallthrough]];
|
||||
}
|
||||
case AccountState::Working: {
|
||||
// refresh is in progress, we need to wait for it to finish to proceed.
|
||||
@ -272,12 +282,11 @@ void LaunchController::login() {
|
||||
progDialog.execWithTask(task.get());
|
||||
continue;
|
||||
}
|
||||
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
|
||||
/*
|
||||
case AccountState::Queued: {
|
||||
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
|
||||
qWarning() << "AccountState::Queued is not implemented";
|
||||
return;
|
||||
}
|
||||
*/
|
||||
case AccountState::Expired: {
|
||||
auto errorString = tr("The account has expired and needs to be logged into manually again.");
|
||||
QMessageBox::warning(
|
||||
@ -314,6 +323,9 @@ void LaunchController::login() {
|
||||
emitFailed(errorString);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
qWarning() << "Invalid AccountState enum";
|
||||
}
|
||||
}
|
||||
}
|
||||
emitFailed(tr("Failed to launch."));
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <deque>
|
||||
|
||||
// ours
|
||||
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const FilterFunction filter)
|
||||
@ -228,23 +229,27 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
}
|
||||
|
||||
// ours
|
||||
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
|
||||
std::pair<QString, QString> MMCZip::findFolderOfFileInZip(QuaZip * zip, QSet<const QString> what, const QString &root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
std::deque<QString> pathsToTraverse;
|
||||
pathsToTraverse.push_back(root);
|
||||
while (!pathsToTraverse.empty())
|
||||
{
|
||||
if(fileName == what)
|
||||
return root;
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
QString result = findFolderOfFileInZip(zip, what, root + fileName);
|
||||
if(!result.isEmpty())
|
||||
QString currentPath = pathsToTraverse.front();
|
||||
pathsToTraverse.pop_front();
|
||||
QuaZipDir rootDir(zip, currentPath);
|
||||
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
return result;
|
||||
if (what.contains(fileName))
|
||||
return {currentPath, fileName};
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
pathsToTraverse.push_back(rootDir.path() + fileName);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
return {QString(), QString()};
|
||||
}
|
||||
|
||||
// ours
|
||||
@ -292,10 +297,15 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
|
||||
do
|
||||
{
|
||||
QString name = zip->getCurrentFileName();
|
||||
if(!name.startsWith(subdir))
|
||||
if(!QDir::cleanPath(name).startsWith(subdir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (QDir::isAbsolutePath(name) || QDir::cleanPath(name).startsWith(".."))
|
||||
{
|
||||
qDebug() << "extractSubDir: Skipping file that tries to place itself in an absolute location or in a parent directory.";
|
||||
continue;
|
||||
}
|
||||
|
||||
name.remove(0, subdir.size());
|
||||
auto original_name = name;
|
||||
|
@ -78,11 +78,11 @@ namespace MMCZip
|
||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
|
||||
|
||||
/**
|
||||
* Find a single file in archive by file name (not path)
|
||||
* Breath-first find a single file in archive by a list of file names (not path)
|
||||
*
|
||||
* \return the path prefix where the file is
|
||||
* \return pair of {file parent directory, file name}
|
||||
*/
|
||||
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
|
||||
std::pair<QString, QString> findFolderOfFileInZip(QuaZip * zip, QSet<const QString> what, const QString &root = QString(""));
|
||||
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
|
@ -211,6 +211,7 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
|
||||
return tr("Latest");
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
default:
|
||||
{
|
||||
@ -254,6 +255,7 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
return pixmap;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
default:
|
||||
{
|
||||
|
@ -448,6 +448,16 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
scanJavaDir("/opt/jdks");
|
||||
// flatpak
|
||||
scanJavaDir("/app/jdk");
|
||||
|
||||
// Default SDKMAN directory can be overwritten via SDKMAN_DIR env var (default $HOME/.sdkman)
|
||||
// see https://sdkman.io/install
|
||||
auto sdkmanInstallPath = qEnvironmentVariable("SDKMAN_DIR", FS::PathCombine(QDir::homePath(), ".sdkman"));
|
||||
scanJavaDir(FS::PathCombine(sdkmanInstallPath, "candidates/java"));
|
||||
// Default ASDF directory can be overwritten via ASDF_DIR or ASDF_DATA_DIR env vars (default $HOME/.asdf)
|
||||
// see https://asdf-vm.com/manage/configuration.html#asdf-dir
|
||||
auto asdfDataPath = qEnvironmentVariable("ASDF_DATA_DIR", qEnvironmentVariable("ASDF_DIR", FS::PathCombine(QDir::homePath(), ".asdf")));
|
||||
scanJavaDir(FS::PathCombine(asdfDataPath, "installs/java"));
|
||||
|
||||
return addJavasFromEnv(javas);
|
||||
}
|
||||
#else
|
||||
|
@ -91,4 +91,5 @@ int main(int argc, char *argv[])
|
||||
case Application::Succeeded:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -45,11 +45,9 @@ QVariant Index::data(const QModelIndex &index, int role) const
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case 0: return list->humanReadable();
|
||||
default: break;
|
||||
}
|
||||
if (index.column() == 0)
|
||||
return list->humanReadable();
|
||||
break;
|
||||
case UidRole: return list->uid();
|
||||
case NameRole: return list->name();
|
||||
case ListPtrRole: return QVariant::fromValue(list);
|
||||
@ -70,10 +68,7 @@ QVariant Index::headerData(int section, Qt::Orientation orientation, int role) c
|
||||
{
|
||||
return tr("Name");
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool Index::hasUid(const QString &uid) const
|
||||
|
@ -56,10 +56,10 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
|
||||
version->setType(ensureString(obj, "type", QString()));
|
||||
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
|
||||
version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
|
||||
RequireSet requires, conflicts;
|
||||
parseRequires(obj, &requires, "requires");
|
||||
RequireSet required, conflicts;
|
||||
parseRequires(obj, &required, "requires");
|
||||
parseRequires(obj, &conflicts, "conflicts");
|
||||
version->setRequires(requires, conflicts);
|
||||
version->setRequires(required, conflicts);
|
||||
return version;
|
||||
}
|
||||
|
||||
@ -176,7 +176,6 @@ void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName
|
||||
{
|
||||
if(obj.contains(keyName))
|
||||
{
|
||||
QSet<QString> requires;
|
||||
auto reqArray = requireArray(obj, keyName);
|
||||
auto iter = reqArray.begin();
|
||||
while(iter != reqArray.end())
|
||||
|
@ -111,9 +111,9 @@ void Meta::Version::setTime(const qint64 time)
|
||||
emit timeChanged();
|
||||
}
|
||||
|
||||
void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts)
|
||||
void Meta::Version::setRequires(const Meta::RequireSet &required, const Meta::RequireSet &conflicts)
|
||||
{
|
||||
m_requires = requires;
|
||||
m_requires = required;
|
||||
m_conflicts = conflicts;
|
||||
emit requiresChanged();
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public: /* con/des */
|
||||
{
|
||||
return m_time;
|
||||
}
|
||||
const Meta::RequireSet &requires() const
|
||||
const Meta::RequireSet &required() const
|
||||
{
|
||||
return m_requires;
|
||||
}
|
||||
@ -87,7 +87,7 @@ public: /* con/des */
|
||||
public: // for usage by format parsers only
|
||||
void setType(const QString &type);
|
||||
void setTime(const qint64 time);
|
||||
void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts);
|
||||
void setRequires(const Meta::RequireSet &required, const Meta::RequireSet &conflicts);
|
||||
void setVolatile(bool volatile_);
|
||||
void setRecommended(bool recommended);
|
||||
void setProvidesRecommendations();
|
||||
|
@ -77,7 +77,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
|
||||
case ParentVersionRole:
|
||||
{
|
||||
// FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'.
|
||||
auto & reqs = version->requires();
|
||||
auto & reqs = version->required();
|
||||
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req)
|
||||
{
|
||||
return req.uid == "net.minecraft";
|
||||
@ -92,7 +92,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
|
||||
|
||||
case UidRole: return version->uid();
|
||||
case TimeRole: return version->time();
|
||||
case RequiresRole: return QVariant::fromValue(version->requires());
|
||||
case RequiresRole: return QVariant::fromValue(version->required());
|
||||
case SortRole: return version->rawTime();
|
||||
case VersionPtrRole: return QVariant::fromValue(version);
|
||||
case RecommendedRole: return version->isRecommended();
|
||||
|
@ -451,9 +451,9 @@ void Component::updateCachedData()
|
||||
m_cachedVolatile = file->m_volatile;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedRequires, file->requires))
|
||||
if(!deepCompare(m_cachedRequires, file->required))
|
||||
{
|
||||
m_cachedRequires = file->requires;
|
||||
m_cachedRequires = file->required;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedConflicts, file->conflicts))
|
||||
|
@ -59,6 +59,7 @@
|
||||
#include "launch/steps/QuitAfterGameStop.h"
|
||||
|
||||
#include "minecraft/launch/LauncherPartLaunch.h"
|
||||
#include "minecraft/launch/ConfigureAuthlibInjector.h"
|
||||
#include "minecraft/launch/DirectJavaLaunch.h"
|
||||
#include "minecraft/launch/ModMinecraftJar.h"
|
||||
#include "minecraft/launch/ClaimAccount.h"
|
||||
@ -188,6 +189,10 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerSetting("JoinServerOnLaunch", false);
|
||||
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
|
||||
|
||||
// Account override
|
||||
m_settings->registerSetting("OverrideAccount", false);
|
||||
m_settings->registerSetting("OverrideAccountProfileId", "");
|
||||
|
||||
qDebug() << "Instance-type specific settings were loaded!";
|
||||
|
||||
setSpecificSettingsLoaded(true);
|
||||
@ -256,7 +261,7 @@ QString MinecraftInstance::getLocalLibraryPath() const
|
||||
bool MinecraftInstance::supportsDemo() const
|
||||
{
|
||||
Version instance_ver { getPackProfile()->getComponentVersion("net.minecraft") };
|
||||
// Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
|
||||
// Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History
|
||||
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
|
||||
return instance_ver >= Version("1.3.1");
|
||||
}
|
||||
@ -384,6 +389,11 @@ QStringList MinecraftInstance::javaArguments()
|
||||
{
|
||||
QStringList args;
|
||||
|
||||
if (!m_authlibinjector_javaagent->isNull())
|
||||
{
|
||||
args.append(QString("-javaagent:%1").arg(*m_authlibinjector_javaagent));
|
||||
}
|
||||
|
||||
// custom args go first. we want to override them if we have our own here.
|
||||
args.append(extraArguments());
|
||||
|
||||
@ -987,6 +997,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
process->appendStep(step);
|
||||
}
|
||||
|
||||
*m_authlibinjector_javaagent = QString();
|
||||
if (!session->authlib_injector_base_url.isNull())
|
||||
{
|
||||
process->appendStep(new ConfigureAuthlibInjector(pptr, session->authlib_injector_base_url, m_authlibinjector_javaagent));
|
||||
}
|
||||
|
||||
// if we aren't in offline mode,.
|
||||
if(session->status != AuthSession::PlayableOffline)
|
||||
{
|
||||
|
@ -173,6 +173,7 @@ protected: // data
|
||||
mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list;
|
||||
mutable std::shared_ptr<WorldList> m_world_list;
|
||||
mutable std::shared_ptr<GameOptions> m_game_options;
|
||||
mutable std::shared_ptr<QString> m_authlibinjector_javaagent = std::make_shared<QString>();
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;
|
||||
|
@ -266,7 +266,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
|
||||
if (root.contains("requires"))
|
||||
{
|
||||
Meta::parseRequires(root, &out->requires);
|
||||
Meta::parseRequires(root, &out->required);
|
||||
}
|
||||
QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
|
||||
if(!dependsOnMinecraftVersion.isEmpty())
|
||||
@ -274,9 +274,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
Meta::Require mcReq;
|
||||
mcReq.uid = "net.minecraft";
|
||||
mcReq.equalsVersion = dependsOnMinecraftVersion;
|
||||
if (out->requires.count(mcReq) == 0)
|
||||
if (out->required.count(mcReq) == 0)
|
||||
{
|
||||
out->requires.insert(mcReq);
|
||||
out->required.insert(mcReq);
|
||||
}
|
||||
}
|
||||
if (root.contains("conflicts"))
|
||||
@ -368,9 +368,9 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
}
|
||||
root.insert("mods", array);
|
||||
}
|
||||
if(!patch->requires.empty())
|
||||
if(!patch->required.empty())
|
||||
{
|
||||
Meta::serializeRequires(root, &patch->requires, "requires");
|
||||
Meta::serializeRequires(root, &patch->required, "requires");
|
||||
}
|
||||
if(!patch->conflicts.empty())
|
||||
{
|
||||
|
@ -138,7 +138,7 @@ public: /* data */
|
||||
* SneedMC: set of packages this depends on
|
||||
* NOTE: this is shared with the meta format!!!
|
||||
*/
|
||||
Meta::RequireSet requires;
|
||||
Meta::RequireSet required;
|
||||
|
||||
/**
|
||||
* SneedMC: set of packages this conflicts with
|
||||
|
@ -285,7 +285,7 @@ void World::readFromZip(const QFileInfo &file)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
|
||||
auto [location, _] = MMCZip::findFolderOfFileInZip(&zip, {"level.dat"});
|
||||
is_valid = !location.isEmpty();
|
||||
if (!is_valid)
|
||||
{
|
||||
|
@ -350,6 +350,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
type = AccountType::MSA;
|
||||
} else if (typeS == "Mojang") {
|
||||
type = AccountType::Mojang;
|
||||
} else if (typeS == "Authlib-Injector") {
|
||||
type = AccountType::AuthlibInjector;
|
||||
} else if (typeS == "Offline") {
|
||||
type = AccountType::Offline;
|
||||
} else {
|
||||
@ -362,6 +364,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
|
||||
}
|
||||
|
||||
if(type == AccountType::AuthlibInjector) {
|
||||
authlibInjectorBaseUrl = data.value("authlibInjectorUrl").toString();
|
||||
}
|
||||
|
||||
if(type == AccountType::MSA) {
|
||||
auto clientIDV = data.value("msa-client-id");
|
||||
if (clientIDV.isString()) {
|
||||
@ -405,8 +411,10 @@ QJsonObject AccountData::saveState() const {
|
||||
tokenToJSONV3(output, userToken, "utoken");
|
||||
tokenToJSONV3(output, xboxApiToken, "xrp-main");
|
||||
tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
|
||||
}
|
||||
else if (type == AccountType::Offline) {
|
||||
} else if (type == AccountType::AuthlibInjector) {
|
||||
output["type"] = "Authlib-Injector";
|
||||
output["authlibInjectorUrl"] = authlibInjectorBaseUrl;
|
||||
} else if (type == AccountType::Offline) {
|
||||
output["type"] = "Offline";
|
||||
}
|
||||
|
||||
@ -428,14 +436,14 @@ QString AccountData::accessToken() const {
|
||||
}
|
||||
|
||||
QString AccountData::clientToken() const {
|
||||
if(type != AccountType::Mojang) {
|
||||
if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) {
|
||||
return QString();
|
||||
}
|
||||
return yggdrasilToken.extra["clientToken"].toString();
|
||||
}
|
||||
|
||||
void AccountData::setClientToken(QString clientToken) {
|
||||
if(type != AccountType::Mojang) {
|
||||
if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = clientToken;
|
||||
@ -449,7 +457,7 @@ void AccountData::generateClientTokenIfMissing() {
|
||||
}
|
||||
|
||||
void AccountData::invalidateClientToken() {
|
||||
if(type != AccountType::Mojang) {
|
||||
if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
|
||||
@ -470,6 +478,7 @@ QString AccountData::profileName() const {
|
||||
|
||||
QString AccountData::accountDisplayString() const {
|
||||
switch(type) {
|
||||
case AccountType::AuthlibInjector:
|
||||
case AccountType::Mojang: {
|
||||
return userName();
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ struct MinecraftProfile {
|
||||
enum class AccountType {
|
||||
MSA,
|
||||
Mojang,
|
||||
AuthlibInjector,
|
||||
Offline
|
||||
};
|
||||
|
||||
@ -85,6 +86,7 @@ enum class AccountState {
|
||||
Disabled,
|
||||
Errored,
|
||||
Expired,
|
||||
Queued,
|
||||
Gone
|
||||
};
|
||||
|
||||
@ -114,6 +116,9 @@ struct AccountData {
|
||||
QString lastError() const;
|
||||
|
||||
AccountType type = AccountType::MSA;
|
||||
QString authlibInjectorBaseUrl;
|
||||
QString authlibInjectorApiLocation;
|
||||
|
||||
bool legacy = false;
|
||||
bool canMigrateToMSA = false;
|
||||
|
||||
|
@ -328,6 +328,12 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
case AccountState::Queued: {
|
||||
qWarning() << "Unhandled account state Queued";
|
||||
[[fallthrough]];
|
||||
}
|
||||
default:
|
||||
return tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,8 +347,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
else {
|
||||
return tr("No", "Can Migrate?");
|
||||
}
|
||||
qWarning() << "Unhandled case in MigrationColumn";
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -359,7 +366,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
case ProfileNameColumn:
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
|
||||
[[fallthrough]];
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
@ -109,6 +109,10 @@ public:
|
||||
MinecraftAccountPtr defaultAccount() const;
|
||||
void setDefaultAccount(MinecraftAccountPtr profileId);
|
||||
bool anyAccountIsValid();
|
||||
bool drmCheck()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
|
@ -38,8 +38,12 @@ struct AuthSession
|
||||
QString player_name;
|
||||
// profile ID
|
||||
QString uuid;
|
||||
// 'legacy' or 'mojang', depending on account type
|
||||
// 'legacy' or 'mojang' or 'authlib-injector', depending on account type
|
||||
QString user_type;
|
||||
|
||||
// If not using authlib injector, this is blank.
|
||||
QString authlib_injector_base_url;
|
||||
|
||||
// Did the auth server reply?
|
||||
bool auth_server_online = false;
|
||||
// Did the user request online mode?
|
||||
|
@ -50,7 +50,39 @@
|
||||
|
||||
#include "flows/MSA.h"
|
||||
#include "flows/Mojang.h"
|
||||
#include "flows/AuthlibInjector.h"
|
||||
#include "flows/Offline.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
|
||||
// Basically the same as https://github.com/qt/qtbase/blob/5.12/src/corelib/plugin/quuid.cpp#L152C1-L173C2, but unfortunately they don't allow
|
||||
// us to specify a byte array for the namespace, we only get to specify a fixed length Uuid so I have to copy it and modify it ever so slightly.
|
||||
static QUuid createUuidFromName(const QByteArray &ns, const QByteArray &baseData, QCryptographicHash::Algorithm algorithm, int version)
|
||||
{
|
||||
QByteArray hashResult;
|
||||
|
||||
// create a scope so later resize won't reallocate
|
||||
{
|
||||
QCryptographicHash hash(algorithm);
|
||||
hash.addData(ns);
|
||||
hash.addData(baseData);
|
||||
hashResult = hash.result();
|
||||
}
|
||||
hashResult.resize(16); // Sha1 will be too long
|
||||
|
||||
QUuid result = QUuid::fromRfc4122(hashResult);
|
||||
|
||||
result.data3 &= 0x0FFF;
|
||||
result.data3 |= (version << 12);
|
||||
result.data4[0] &= 0x3F;
|
||||
result.data4[0] |= 0x80;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static QUuid createUuidV3(const QByteArray &ns, const QByteArray &baseData)
|
||||
{
|
||||
return createUuidFromName(ns, baseData, QCryptographicHash::Md5, 3);
|
||||
}
|
||||
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
|
||||
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
@ -82,6 +114,16 @@ MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username
|
||||
return account;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createAuthlibInjectorFromUsername(const QString &username, QString baseUrl)
|
||||
{
|
||||
MinecraftAccountPtr account = createFromUsername(username);
|
||||
account->data.type = AccountType::AuthlibInjector;
|
||||
account->data.authlibInjectorBaseUrl = baseUrl;
|
||||
account->data.minecraftEntitlement.ownsMinecraft = true;
|
||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||
return account;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createBlankMSA()
|
||||
{
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
@ -100,7 +142,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
|
||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftEntitlement.ownsMinecraft = true;
|
||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||
account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftProfile.id = createUuidV3("OfflinePlayer:", username.toUtf8()).toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftProfile.name = username;
|
||||
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
|
||||
return account;
|
||||
@ -132,7 +174,14 @@ QPixmap MinecraftAccount::getFace() const {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new MojangLogin(&data, password));
|
||||
if (data.type == AccountType::Mojang)
|
||||
{
|
||||
m_currentTask.reset(new MojangLogin(&data, password));
|
||||
}
|
||||
else if (data.type == AccountType::AuthlibInjector)
|
||||
{
|
||||
m_currentTask.reset(new AuthlibInjectorLogin(&data, password));
|
||||
}
|
||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
@ -173,6 +222,9 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
||||
else if(data.type == AccountType::Offline) {
|
||||
m_currentTask.reset(new OfflineRefresh(&data));
|
||||
}
|
||||
else if(data.type == AccountType::AuthlibInjector) {
|
||||
m_currentTask.reset(new AuthlibInjectorRefresh(&data));
|
||||
}
|
||||
else {
|
||||
m_currentTask.reset(new MojangRefresh(&data));
|
||||
}
|
||||
@ -300,8 +352,9 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
session->player_name = data.profileName();
|
||||
// profile ID
|
||||
session->uuid = data.profileId();
|
||||
// 'legacy' or 'mojang', depending on account type
|
||||
// 'legacy' or 'mojang', or 'authlib-injector' depending on account type
|
||||
session->user_type = typeString();
|
||||
session->authlib_injector_base_url = data.authlibInjectorBaseUrl;
|
||||
if (!session->access_token.isEmpty())
|
||||
{
|
||||
session->session = "token:" + data.accessToken() + ":" + data.profileId();
|
||||
|
@ -91,6 +91,8 @@ public: /* construction */
|
||||
|
||||
static MinecraftAccountPtr createFromUsername(const QString &username);
|
||||
|
||||
static MinecraftAccountPtr createAuthlibInjectorFromUsername(const QString &username, QString baseUrl);
|
||||
|
||||
static MinecraftAccountPtr createBlankMSA();
|
||||
|
||||
static MinecraftAccountPtr createOffline(const QString &username);
|
||||
@ -177,6 +179,10 @@ public: /* queries */
|
||||
return "msa";
|
||||
}
|
||||
break;
|
||||
case AccountType::AuthlibInjector: {
|
||||
return "authlib-injector";
|
||||
}
|
||||
break;
|
||||
case AccountType::Offline: {
|
||||
return "offline";
|
||||
}
|
||||
|
@ -27,6 +27,25 @@
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
|
||||
QString Yggdrasil::getBaseUrl()
|
||||
{
|
||||
switch (m_data->type)
|
||||
{
|
||||
case AccountType::Mojang: {
|
||||
return "https://authserver.mojang.com";
|
||||
}
|
||||
case AccountType::AuthlibInjector: {
|
||||
return m_data->authlibInjectorApiLocation + "/authserver";
|
||||
}
|
||||
// Silence warnings about unhandled enum values for values we know shouldn't be handled.
|
||||
case AccountType::MSA:
|
||||
case AccountType::Offline:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Yggdrasil::Yggdrasil(AccountData *data, QObject *parent)
|
||||
: AccountTask(data, parent)
|
||||
{
|
||||
@ -84,7 +103,7 @@ void Yggdrasil::refresh() {
|
||||
req.insert("requestUser", false);
|
||||
QJsonDocument doc(req);
|
||||
|
||||
QUrl reqUrl("https://authserver.mojang.com/refresh");
|
||||
QUrl reqUrl = getBaseUrl() + "/refresh";
|
||||
QByteArray requestData = doc.toJson();
|
||||
|
||||
sendRequest(reqUrl, requestData);
|
||||
@ -129,7 +148,8 @@ void Yggdrasil::login(QString password) {
|
||||
|
||||
QJsonDocument doc(req);
|
||||
|
||||
QUrl reqUrl("https://authserver.mojang.com/authenticate");
|
||||
QUrl reqUrl = getBaseUrl() + "/authenticate";
|
||||
qDebug() << "baseurl = " << getBaseUrl() << "requrl = " << reqUrl;
|
||||
QNetworkRequest netRequest(reqUrl);
|
||||
QByteArray requestData = doc.toJson();
|
||||
|
||||
@ -273,6 +293,7 @@ void Yggdrasil::processReply() {
|
||||
AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
changeState(
|
||||
|
@ -90,6 +90,7 @@ public slots:
|
||||
|
||||
private:
|
||||
void sendRequest(QUrl endpoint, QByteArray content);
|
||||
QString getBaseUrl();
|
||||
|
||||
protected:
|
||||
QNetworkReply *m_netReply = nullptr;
|
||||
|
29
launcher/minecraft/auth/flows/AuthlibInjector.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
#include "AuthlibInjector.h"
|
||||
|
||||
#include "minecraft/auth/steps/AuthlibInjectorStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
|
||||
AuthlibInjectorRefresh::AuthlibInjectorRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
m_steps.append(new AuthlibInjectorStep(m_data));
|
||||
m_steps.append(new YggdrasilStep(m_data, QString()));
|
||||
m_steps.append(new MinecraftProfileStepMojang(m_data));
|
||||
m_steps.append(new GetSkinStep(m_data));
|
||||
}
|
||||
|
||||
AuthlibInjectorLogin::AuthlibInjectorLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent
|
||||
): AuthFlow(data, parent), m_password(password) {
|
||||
m_steps.append(new AuthlibInjectorStep(m_data));
|
||||
m_steps.append(new YggdrasilStep(m_data, m_password));
|
||||
m_steps.append(new MinecraftProfileStepMojang(m_data));
|
||||
m_steps.append(new GetSkinStep(m_data));
|
||||
}
|
26
launcher/minecraft/auth/flows/AuthlibInjector.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class AuthlibInjectorRefresh : public AuthFlow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AuthlibInjectorRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
};
|
||||
|
||||
class AuthlibInjectorLogin : public AuthFlow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AuthlibInjectorLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent = 0
|
||||
);
|
||||
|
||||
private:
|
||||
QString m_password;
|
||||
};
|
58
launcher/minecraft/auth/steps/AuthlibInjectorStep.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "AuthlibInjectorStep.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QUuid>
|
||||
|
||||
AuthlibInjectorStep::AuthlibInjectorStep(AccountData* data) : AuthStep(data) {
|
||||
}
|
||||
|
||||
AuthlibInjectorStep::~AuthlibInjectorStep() noexcept = default;
|
||||
|
||||
QString AuthlibInjectorStep::describe() {
|
||||
return tr("Fetching authlib injector API URL");
|
||||
}
|
||||
|
||||
|
||||
void AuthlibInjectorStep::perform() {
|
||||
// Default to the same as the base URL
|
||||
QUrl url;
|
||||
url.setScheme("https");
|
||||
url.setAuthority(m_data->authlibInjectorBaseUrl);
|
||||
qDebug() << url << url.toString() << url.isLocalFile();
|
||||
m_data->authlibInjectorApiLocation = url.toString();
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
m_reply.reset( APPLICATION->network()->get(request));
|
||||
connect(m_reply.get(), &QNetworkReply::finished, this, &AuthlibInjectorStep::onRequestDone);
|
||||
qDebug() << "Fetching authlib injector API URL";
|
||||
}
|
||||
|
||||
void AuthlibInjectorStep::rehydrate() {
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void AuthlibInjectorStep::onRequestDone() {
|
||||
if (m_reply->hasRawHeader("x-authlib-injector-api-location"))
|
||||
{
|
||||
QString authlibInjectorApiLocationHeader = m_reply->rawHeader("x-authlib-injector-api-location");
|
||||
QUrl url = authlibInjectorApiLocationHeader;
|
||||
if (!url.isValid())
|
||||
{
|
||||
qDebug() << "Invalid Authlib Injector API URL specified by server: " << authlibInjectorApiLocationHeader;
|
||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Invalid authlib injector API URL"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data->authlibInjectorApiLocation = authlibInjectorApiLocationHeader;
|
||||
qDebug() << "Authlib injector API URL: " << m_data->authlibInjectorApiLocation;
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Fetched authlib injector API URL"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Authlib injector API URL not found";
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Authlib injector API URL not found, defaulting to the supplied base URL"));
|
||||
}
|
||||
}
|
24
launcher/minecraft/auth/steps/AuthlibInjectorStep.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class AuthlibInjectorStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AuthlibInjectorStep(AccountData *data);
|
||||
virtual ~AuthlibInjectorStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
void rehydrate() override;
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onRequestDone();
|
||||
private:
|
||||
std::unique_ptr<QNetworkReply> m_reply;
|
||||
};
|
@ -19,4 +19,7 @@ public:
|
||||
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
|
||||
private:
|
||||
QString baseUrl;
|
||||
};
|
||||
|
@ -6,8 +6,24 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
|
||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
QString MinecraftProfileStepMojang::getBaseUrl()
|
||||
{
|
||||
switch (m_data->type)
|
||||
{
|
||||
case AccountType::Mojang: {
|
||||
return "https://sessionserver.mojang.com";
|
||||
}
|
||||
case AccountType::AuthlibInjector: {
|
||||
return m_data->authlibInjectorApiLocation + "/sessionserver";
|
||||
}
|
||||
// Silence warnings about unhandled enum values for values we know shouldn't be handled.
|
||||
case AccountType::MSA:
|
||||
case AccountType::Offline:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
|
||||
@ -24,7 +40,7 @@ void MinecraftProfileStepMojang::perform() {
|
||||
}
|
||||
|
||||
// use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
|
||||
QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
|
||||
QUrl url = getBaseUrl() + "/session/minecraft/profile/" + m_data->minecraftProfile.id;
|
||||
QNetworkRequest req = QNetworkRequest(url);
|
||||
AuthRequest *request = new AuthRequest(this);
|
||||
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
|
||||
|
@ -19,4 +19,7 @@ public:
|
||||
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
|
||||
private:
|
||||
QString getBaseUrl();
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "YggdrasilStep.h"
|
||||
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
@ -15,7 +16,14 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat
|
||||
YggdrasilStep::~YggdrasilStep() noexcept = default;
|
||||
|
||||
QString YggdrasilStep::describe() {
|
||||
return tr("Logging in with Mojang account.");
|
||||
switch(m_data->type) {
|
||||
case(AccountType::Mojang):
|
||||
return tr("Logging in with Mojang account.");
|
||||
case AccountType::AuthlibInjector:
|
||||
return tr("Logging in with %1 account.").arg(m_data->authlibInjectorBaseUrl);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void YggdrasilStep::rehydrate() {
|
||||
@ -32,7 +40,9 @@ void YggdrasilStep::perform() {
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthSucceeded() {
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
|
||||
emit m_data->type == AccountType::Mojang
|
||||
? finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"))
|
||||
: finished(AccountTaskState::STATE_WORKING, tr("Logged in with %1").arg(m_data->authlibInjectorBaseUrl));
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthFailed() {
|
||||
@ -41,12 +51,32 @@ void YggdrasilStep::onAuthFailed() {
|
||||
// m_aborted = m_yggdrasil->m_aborted;
|
||||
|
||||
auto state = m_yggdrasil->taskState();
|
||||
QString errorMessage = tr("Mojang user authentication failed.");
|
||||
QString errorMessage = m_data->type == AccountType::Mojang
|
||||
? tr("Mojang user authentication failed.")
|
||||
: tr("%1 user authentication failed").arg(m_data->authlibInjectorBaseUrl);
|
||||
|
||||
// NOTE: soft error in the first step means 'offline'
|
||||
if(state == AccountTaskState::STATE_FAILED_SOFT) {
|
||||
state = AccountTaskState::STATE_OFFLINE;
|
||||
errorMessage = tr("Mojang user authentication ended with a network error.");
|
||||
switch(m_data->type) {
|
||||
case AccountType::Mojang:
|
||||
{
|
||||
errorMessage = tr("Mojang user authentication ended with a network error.");
|
||||
break;
|
||||
}
|
||||
case AccountType::AuthlibInjector:
|
||||
{
|
||||
if(m_data->authlibInjectorBaseUrl.isEmpty())
|
||||
{
|
||||
errorMessage = tr("User authentication ended with a network error, did specify a url?");
|
||||
} else {
|
||||
errorMessage = tr("%1 user authentication ended with a network error").arg(m_data->authlibInjectorBaseUrl);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit finished(state, errorMessage);
|
||||
}
|
||||
|
80
launcher/minecraft/launch/ConfigureAuthlibInjector.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "ConfigureAuthlibInjector.h"
|
||||
#include <launch/LaunchTask.h>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <Qt>
|
||||
|
||||
#include "Application.h"
|
||||
#include "minecraft/auth/AccountList.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "net/Download.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "net/NetAction.h"
|
||||
|
||||
ConfigureAuthlibInjector::ConfigureAuthlibInjector(LaunchTask* parent,
|
||||
QString authlibinjector_base_url,
|
||||
std::shared_ptr<QString> javaagent_arg)
|
||||
: LaunchStep(parent), m_javaagent_arg{ javaagent_arg }, m_authlibinjector_base_url{ authlibinjector_base_url }
|
||||
{}
|
||||
|
||||
void ConfigureAuthlibInjector::executeTask()
|
||||
{
|
||||
auto downloadFailed = [this] (QString reason) {
|
||||
return emitFailed(QString("Download failed: %1").arg(reason));
|
||||
};
|
||||
auto entry = APPLICATION->metacache()->resolveEntry("authlibinjector", "latest.json");
|
||||
|
||||
entry->setStale(true);
|
||||
m_job = std::make_unique<NetJob>("Download authlibinjector latest.json", APPLICATION->network());
|
||||
auto latestJsonDl =
|
||||
Net::Download::makeCached(QUrl("https://authlib-injector.yushi.moe/artifact/latest.json"), entry, Net::Download::Option::NoOptions);
|
||||
m_job->addNetAction(latestJsonDl);
|
||||
connect(m_job.get(), &NetJob::succeeded, this, [this, entry, downloadFailed] {
|
||||
QFile authlibInjectorLatestJson = entry->getFullPath();
|
||||
authlibInjectorLatestJson.open(QIODevice::ReadOnly);
|
||||
if (!authlibInjectorLatestJson.isOpen())
|
||||
return emitFailed(QString("Failed to open authlib-injector info json: %1").arg(authlibInjectorLatestJson.errorString()));
|
||||
|
||||
QJsonParseError json_parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(authlibInjectorLatestJson.readAll(), &json_parse_error);
|
||||
if (json_parse_error.error != QJsonParseError::NoError)
|
||||
return emitFailed(QString("Failed to parse authlib-injector info json: %1").arg(json_parse_error.errorString()));
|
||||
|
||||
if (!doc.isObject())
|
||||
return emitFailed(QString("Failed to parse authlib-injector info json: not a json object"));
|
||||
QJsonObject obj = doc.object();
|
||||
|
||||
QString authlibInjectorJarUrl = obj["download_url"].toString();
|
||||
if (authlibInjectorJarUrl.isNull())
|
||||
return emitFailed(QString("Failed to parse authlib-injector info json: download url missing"));
|
||||
|
||||
QString sha256Sum = obj["checksums"].toObject()["sha256"].toString();
|
||||
if (sha256Sum.isNull())
|
||||
return emitFailed("Failed to parse authlib-injector info json: sha256 checksum missing");
|
||||
|
||||
auto sha256SumRaw = QByteArray::fromHex(sha256Sum.toLatin1());
|
||||
|
||||
QString filename = QFileInfo(authlibInjectorJarUrl).fileName();
|
||||
auto javaAgentEntry = APPLICATION->metacache()->resolveEntry("authlibinjector", filename);
|
||||
m_job = std::make_unique<NetJob>("Download authlibinjector java agent", APPLICATION->network());
|
||||
auto javaAgentDl = Net::Download::makeCached(QUrl(authlibInjectorJarUrl), javaAgentEntry, Net::Download::Option::MakeEternal);
|
||||
javaAgentDl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha256, sha256SumRaw));
|
||||
m_job->addNetAction(javaAgentDl);
|
||||
connect(m_job.get(), &NetJob::succeeded, this, [this, javaAgentEntry] {
|
||||
auto path = javaAgentEntry->getFullPath();
|
||||
qDebug() << path;
|
||||
*m_javaagent_arg = QString("%1=%2").arg(path).arg(m_authlibinjector_base_url);
|
||||
emitSucceeded();
|
||||
});
|
||||
connect(m_job.get(), &NetJob::failed, this, downloadFailed);
|
||||
m_job->start();
|
||||
},
|
||||
// This slot can't run instantly because it needs to wait for the netjob's code to stop running
|
||||
// Since it will destroy the old netjob by reassigning the unique_ptr
|
||||
Qt::QueuedConnection);
|
||||
connect(m_job.get(), &NetJob::failed, this, downloadFailed);
|
||||
m_job->start();
|
||||
}
|
||||
|
||||
void ConfigureAuthlibInjector::finalize() {}
|
24
launcher/minecraft/launch/ConfigureAuthlibInjector.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <launch/LaunchStep.h>
|
||||
#include <minecraft/auth/MinecraftAccount.h>
|
||||
#include "net/NetJob.h"
|
||||
|
||||
class ConfigureAuthlibInjector: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConfigureAuthlibInjector(LaunchTask *parent, QString authlibinjector_base_url, std::shared_ptr<QString> javaagent_arg);
|
||||
virtual ~ConfigureAuthlibInjector() {};
|
||||
|
||||
void executeTask() override;
|
||||
void finalize() override;
|
||||
bool canAbort() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
std::unique_ptr<NetJob> m_job;
|
||||
std::shared_ptr<QString> m_javaagent_arg;
|
||||
QString m_authlibinjector_base_url;
|
||||
};
|
@ -39,6 +39,9 @@
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
|
||||
#undef major
|
||||
#undef minor
|
||||
|
||||
void VerifyJavaInstall::executeTask() {
|
||||
auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
||||
auto packProfile = instance->getPackProfile();
|
||||
|
@ -82,6 +82,8 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
// FIXME: Determine if this is a legitimate fallthrough
|
||||
[[fallthrough]];
|
||||
}
|
||||
case SortType::VERSION: {
|
||||
auto this_ver = Version(version());
|
||||
|
@ -66,6 +66,7 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
return { 1, type == SortType::ENABLED };
|
||||
if (!enabled() && other.enabled())
|
||||
return { -1, type == SortType::ENABLED };
|
||||
[[fallthrough]];
|
||||
case SortType::NAME: {
|
||||
QString this_name{ name() };
|
||||
QString other_name{ other.name() };
|
||||
@ -76,6 +77,7 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::NAME };
|
||||
[[fallthrough]];
|
||||
}
|
||||
case SortType::DATE:
|
||||
if (dateTimeChanged() > other.dateTimeChanged())
|
||||
|
@ -9,13 +9,20 @@
|
||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||
|
||||
// Values taken from:
|
||||
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
|
||||
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
|
||||
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
|
||||
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
|
||||
{ 1, { Version("1.6.1"), Version("1.8.9") } },
|
||||
{ 2, { Version("1.9"), Version("1.10.2") } },
|
||||
{ 3, { Version("1.11"), Version("1.12.2") } },
|
||||
{ 4, { Version("1.13"), Version("1.14.4") } },
|
||||
{ 5, { Version("1.15"), Version("1.16.1") } },
|
||||
{ 6, { Version("1.16.2"), Version("1.16.5") } },
|
||||
{ 7, { Version("1.17"), Version("1.17.1") } },
|
||||
{ 8, { Version("1.18"), Version("1.18.2") } },
|
||||
{ 9, { Version("1.19"), Version("1.19.2") } },
|
||||
{ 12, { Version("1.19.3"), Version("1.19.3") } },
|
||||
{ 13, { Version("1.19.4"), Version("1.19.4") } },
|
||||
{ 14, { Version("1.20"), Version("1.20") } }
|
||||
};
|
||||
|
||||
void ResourcePack::setPackFormat(int new_format_id)
|
||||
@ -85,6 +92,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
|
@ -49,7 +49,7 @@ class ResourcePack : public Resource {
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
|
||||
* See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
* See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
*/
|
||||
int m_pack_format = 0;
|
||||
|
||||
|
@ -115,7 +115,7 @@ void processZIP(ResourcePack& pack)
|
||||
zip.close();
|
||||
}
|
||||
|
||||
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
|
||||
{
|
||||
try {
|
||||
|
@ -42,12 +42,13 @@
|
||||
|
||||
QByteArray getVariant(SkinUpload::Model model) {
|
||||
switch (model) {
|
||||
default:
|
||||
qDebug() << "Unknown skin type!";
|
||||
case SkinUpload::STEVE:
|
||||
return "CLASSIC";
|
||||
case SkinUpload::ALEX:
|
||||
return "SLIM";
|
||||
default:
|
||||
qDebug() << "Unknown skin type!";
|
||||
return "CLASSIC";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,7 +405,8 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask()
|
||||
QHash<QString, QString> addonIds;
|
||||
for (auto const& hash : m_mods.keys()) {
|
||||
if (m_temp_versions.contains(hash)) {
|
||||
auto const& data = m_temp_versions.find(hash).value();
|
||||
auto const& dataObj = m_temp_versions.find(hash);
|
||||
auto const& data = dataObj.value();
|
||||
|
||||
auto id_str = data.addonId.toString();
|
||||
if (!id_str.isEmpty())
|
||||
|
@ -351,7 +351,7 @@ QString PackInstallTask::getVersionForLoader(QString uid)
|
||||
if(m_version.loader.recommended || m_version.loader.latest) {
|
||||
for (int i = 0; i < vlist->versions().size(); i++) {
|
||||
auto version = vlist->versions().at(i);
|
||||
auto reqs = version->requires();
|
||||
auto reqs = version->required();
|
||||
|
||||
// filter by minecraft version, if the loader depends on a certain version.
|
||||
// not all mod loaders depend on a given Minecraft version, so we won't do this
|
||||
|
@ -37,7 +37,6 @@ void Flame::FileResolvingTask::executeTask()
|
||||
void Flame::FileResolvingTask::netJobFinished()
|
||||
{
|
||||
setProgress(1, 3);
|
||||
int index = 0;
|
||||
// job to check modrinth for blocked projects
|
||||
auto job = new NetJob("Modrinth check", m_network);
|
||||
blockedProjects = QMap<File *,QByteArray *>();
|
||||
@ -73,7 +72,6 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
blockedProjects.insert(&out, output);
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
connect(job, &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
|
||||
|
||||
|
@ -158,7 +158,7 @@ void FlameCheckUpdate::executeTask()
|
||||
pack.addonId = mod->metadata()->project_id;
|
||||
pack.websiteUrl = mod->homeurl();
|
||||
for (auto& author : mod->authors())
|
||||
pack.authors.append({ author });
|
||||
pack.authors.append({ author, "" });
|
||||
pack.description = mod->description();
|
||||
pack.provider = ModPlatform::Provider::FLAME;
|
||||
|
||||
|
@ -420,7 +420,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
switch (result.type) {
|
||||
case Flame::File::Type::Folder: {
|
||||
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
|
||||
// fall-through intentional, we treat these as plain old mods and dump them wherever.
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Flame::File::Type::SingleFile:
|
||||
case Flame::File::Type::Mod: {
|
||||
|
@ -135,8 +135,6 @@ void PackInstallTask::resolveMods()
|
||||
m_file_id_map.clear();
|
||||
|
||||
Flame::Manifest manifest;
|
||||
int index = 0;
|
||||
|
||||
for (auto const& file : m_version.files) {
|
||||
if (!file.serverOnly && file.url.isEmpty()) {
|
||||
if (file.curseforge.file_id <= 0) {
|
||||
@ -154,8 +152,6 @@ void PackInstallTask::resolveMods()
|
||||
} else {
|
||||
m_file_id_map.append(-1);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest);
|
||||
|
@ -150,7 +150,7 @@ void ModrinthCheckUpdate::executeTask()
|
||||
pack.addonId = mod->metadata()->project_id;
|
||||
pack.websiteUrl = mod->homeurl();
|
||||
for (auto& author : mod->authors())
|
||||
pack.authors.append({ author });
|
||||
pack.authors.append({ author, "" });
|
||||
pack.description = mod->description();
|
||||
pack.provider = ModPlatform::Provider::MODRINTH;
|
||||
|
||||
|
@ -195,8 +195,8 @@ bool ModrinthCreationTask::createInstance()
|
||||
Override::createOverrides("client-overrides", parent_folder, client_override_path);
|
||||
|
||||
// Apply the overrides
|
||||
if (!FS::overrideFolder(mcPath, client_override_path)) {
|
||||
setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
|
||||
if (!FS::mergeFolders(mcPath, client_override_path)) {
|
||||
setError(tr("Could not overwrite / create new files:\n") + "client overrides");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -305,6 +305,11 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
||||
Modrinth::File file;
|
||||
file.path = Json::requireString(modInfo, "path");
|
||||
|
||||
if (QDir::isAbsolutePath(file.path) || QDir::cleanPath(file.path).startsWith("..")) {
|
||||
qDebug() << "Skipped file that tries to place itself in an absolute location or in a parent directory.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto env = Json::ensureObject(modInfo, "env");
|
||||
// 'env' field is optional
|
||||
if (!env.isEmpty()) {
|
||||
|
@ -32,6 +32,7 @@
|
||||
<file>scalable/status-bad.svg</file>
|
||||
<file>scalable/status-good.svg</file>
|
||||
<file>scalable/status-yellow.svg</file>
|
||||
<file>scalable/storage.svg</file>
|
||||
<file>scalable/viewfolder.svg</file>
|
||||
<file>scalable/worlds.svg</file>
|
||||
</qresource>
|
||||
|
50
launcher/resources/OSX/scalable/storage.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 24 24"
|
||||
enable-background="new 0 0 24 24"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs80" />
|
||||
<rect
|
||||
fill="none"
|
||||
width="24"
|
||||
height="24"
|
||||
id="rect66" />
|
||||
|
||||
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#FFFFFF"
|
||||
d="M18.5,5h-1.1c0-0.6-0.6-1-1.4-1c-0.8,0-1.4,0.4-1.4,1h-1.1v2h5V5z"
|
||||
id="path75" />
|
||||
<g
|
||||
id="g3784"
|
||||
transform="matrix(0.73845672,0,0,0.73845672,4.237417,0.430303)"
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#585858;stroke-opacity:1"><rect
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#585858;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1197"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.1934299"
|
||||
y="6.9381723"
|
||||
ry="2.1458371" /><rect
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#585858;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1774"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.1934299"
|
||||
y="17.748669"
|
||||
ry="1.7275306" /><circle
|
||||
style="fill:#585858;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1828"
|
||||
cx="21.224644"
|
||||
cy="21.072649"
|
||||
r="0.95399094" /></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1,8 +1,14 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource prefix="/backgrounds">
|
||||
<file alias="kitteh">catbgrnd2.png</file>
|
||||
<file alias="catmas">catmas.png</file>
|
||||
<file alias="cattiversary">cattiversary.png</file>
|
||||
<file alias="defaultCatmas">catmas.png</file>
|
||||
<file alias="defaultCattiversary">cattiversary.png</file>
|
||||
<file alias="defaultCat">catbackground.png</file>
|
||||
<file alias="jinxCat">jinxbg.png</file>
|
||||
<file alias="jinxCatmas">jinxmas.png</file>
|
||||
<file alias="jinxCattiversary">jinxversary.png</file>
|
||||
<file alias="floppaCat">floppabg.png</file>
|
||||
<file alias="floppaCatmas">floppaxmas.png</file>
|
||||
<file alias="floppaCattiversary">floppaversary.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
launcher/resources/backgrounds/catbackground.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
launcher/resources/backgrounds/floppabg.png
Normal file
After Width: | Height: | Size: 161 KiB |
BIN
launcher/resources/backgrounds/floppaversary.png
Normal file
After Width: | Height: | Size: 291 KiB |
BIN
launcher/resources/backgrounds/floppaxmas.png
Normal file
After Width: | Height: | Size: 316 KiB |
BIN
launcher/resources/backgrounds/jinxbg.png
Normal file
After Width: | Height: | Size: 130 KiB |
BIN
launcher/resources/backgrounds/jinxmas.png
Normal file
After Width: | Height: | Size: 407 KiB |
BIN
launcher/resources/backgrounds/jinxversary.png
Normal file
After Width: | Height: | Size: 236 KiB |
@ -39,6 +39,7 @@
|
||||
<file>scalable/status-good.svg</file>
|
||||
<file>scalable/status-running.svg</file>
|
||||
<file>scalable/status-yellow.svg</file>
|
||||
<file>scalable/storage.svg</file>
|
||||
<file>scalable/viewfolder.svg</file>
|
||||
<file>scalable/worlds.svg</file>
|
||||
</qresource>
|
||||
|
67
launcher/resources/flat/scalable/storage.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
fill="#757575"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg1594"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1598" />
|
||||
<g
|
||||
id="g3784-3"
|
||||
transform="matrix(1.0718486,0,0,1.0718486,-5.0666791,-4.7930816)"
|
||||
style="fill:#757575;fill-opacity:1;stroke:#757575;stroke-opacity:1">
|
||||
<rect
|
||||
style="fill:#757575;fill-opacity:1;stroke:#757575;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1197-6"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.1934299"
|
||||
y="6.9381723"
|
||||
ry="2.1458371" />
|
||||
<rect
|
||||
style="fill:#757575;fill-opacity:1;stroke:#757575;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1774-7"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.1934299"
|
||||
y="17.748669"
|
||||
ry="1.7275306" />
|
||||
<circle
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#757575;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1828-5"
|
||||
cx="21.224644"
|
||||
cy="21.072649"
|
||||
r="0.95399094" />
|
||||
</g>
|
||||
<g
|
||||
id="g3784"
|
||||
transform="matrix(0.95836194,0,0,0.95836194,-3.2596703,-3.0150411)"
|
||||
style="fill:#757575;fill-opacity:1;stroke:#ffffff;stroke-opacity:1">
|
||||
<rect
|
||||
style="fill:#757575;fill-opacity:1;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1197"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.1934299"
|
||||
y="6.9381723"
|
||||
ry="2.1458371" />
|
||||
<rect
|
||||
style="fill:#757575;fill-opacity:1;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1774"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.1934299"
|
||||
y="17.748669"
|
||||
ry="1.7275306" />
|
||||
<circle
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1828"
|
||||
cx="21.224644"
|
||||
cy="21.072649"
|
||||
r="0.95399094" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
@ -32,6 +32,7 @@
|
||||
<file>scalable/status-bad.svg</file>
|
||||
<file>scalable/status-good.svg</file>
|
||||
<file>scalable/status-yellow.svg</file>
|
||||
<file>scalable/storage.svg</file>
|
||||
<file>scalable/viewfolder.svg</file>
|
||||
<file>scalable/worlds.svg</file>
|
||||
</qresource>
|
||||
|
60
launcher/resources/iOS/scalable/storage.svg
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 32 32"
|
||||
enable-background="new 0 0 32 32"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="storage.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1540"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9939416"
|
||||
inkscape:cx="20.925355"
|
||||
inkscape:cy="15.618925"
|
||||
inkscape:window-width="909"
|
||||
inkscape:window-height="1064"
|
||||
inkscape:window-x="1001"
|
||||
inkscape:window-y="6"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Calque_1" /><defs
|
||||
id="defs5152" />
|
||||
|
||||
<g
|
||||
id="g3784"
|
||||
transform="matrix(1.4769134,0,0,1.4769134,-7.5163878,-7.1393945)"
|
||||
style="stroke:#3366cc;stroke-opacity:1"><rect
|
||||
style="fill:none;stroke:#3366cc;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1197"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.1934299"
|
||||
y="6.9381723"
|
||||
ry="2.1458371" /><rect
|
||||
style="fill:none;stroke:#3366cc;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1774"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.1934299"
|
||||
y="17.748669"
|
||||
ry="1.7275306" /><circle
|
||||
style="fill:#3366cc;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
|
||||
id="path1828"
|
||||
cx="21.224644"
|
||||
cy="21.072649"
|
||||
r="0.95399094" /></g></svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -32,6 +32,7 @@
|
||||
<file>scalable/status-bad.svg</file>
|
||||
<file>scalable/status-good.svg</file>
|
||||
<file>scalable/status-yellow.svg</file>
|
||||
<file>scalable/storage.svg</file>
|
||||
<file>scalable/viewfolder.svg</file>
|
||||
<file>scalable/worlds.svg</file>
|
||||
</qresource>
|
||||
|
95
launcher/resources/pe_blue/scalable/storage.svg
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 32 32"
|
||||
enable-background="new 0 0 32 32"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs4421" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#3366CC"
|
||||
d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z"
|
||||
id="path4374" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#DAEEFF"
|
||||
d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
|
||||
id="path4376" />
|
||||
|
||||
<g
|
||||
id="g4388">
|
||||
</g>
|
||||
<g
|
||||
id="g4390">
|
||||
</g>
|
||||
<g
|
||||
id="g4392">
|
||||
</g>
|
||||
<g
|
||||
id="g4394">
|
||||
</g>
|
||||
<g
|
||||
id="g4396">
|
||||
</g>
|
||||
<g
|
||||
id="g4398">
|
||||
</g>
|
||||
<g
|
||||
id="g4400">
|
||||
</g>
|
||||
<g
|
||||
id="g4402">
|
||||
</g>
|
||||
<g
|
||||
id="g4404">
|
||||
</g>
|
||||
<g
|
||||
id="g4406">
|
||||
</g>
|
||||
<g
|
||||
id="g4408">
|
||||
</g>
|
||||
<g
|
||||
id="g4410">
|
||||
</g>
|
||||
<g
|
||||
id="g4412">
|
||||
</g>
|
||||
<g
|
||||
id="g4414">
|
||||
</g>
|
||||
<g
|
||||
id="g4416">
|
||||
</g>
|
||||
<g
|
||||
id="g3784"
|
||||
transform="translate(0.07734108,0.33259869)"
|
||||
style="stroke:#666666;stroke-opacity:1"><rect
|
||||
style="fill:none;stroke:#666666;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1197"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.1934299"
|
||||
y="6.9381723"
|
||||
ry="2.1458371" /><rect
|
||||
style="fill:none;stroke:#666666;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1774"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.1934299"
|
||||
y="17.748669"
|
||||
ry="1.7275306" /><circle
|
||||
style="fill:#666666;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
|
||||
id="path1828"
|
||||
cx="21.224644"
|
||||
cy="21.072649"
|
||||
r="0.95399094" /></g></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -32,6 +32,7 @@
|
||||
<file>scalable/status-bad.svg</file>
|
||||
<file>scalable/status-good.svg</file>
|
||||
<file>scalable/status-yellow.svg</file>
|
||||
<file>scalable/storage.svg</file>
|
||||
<file>scalable/viewfolder.svg</file>
|
||||
<file>scalable/worlds.svg</file>
|
||||
</qresource>
|
||||
|
92
launcher/resources/pe_colored/scalable/storage.svg
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 32 32"
|
||||
enable-background="new 0 0 32 32"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="storage.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview271"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.9878833"
|
||||
inkscape:cx="15.468743"
|
||||
inkscape:cy="20.524869"
|
||||
inkscape:window-width="909"
|
||||
inkscape:window-height="1064"
|
||||
inkscape:window-x="1001"
|
||||
inkscape:window-y="6"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g3784" /><defs
|
||||
id="defs3662" />
|
||||
<g
|
||||
id="g3645">
|
||||
<rect
|
||||
x="6"
|
||||
y="0"
|
||||
fill="none"
|
||||
width="20"
|
||||
height="0"
|
||||
id="rect3635" />
|
||||
<polygon
|
||||
fill="none"
|
||||
points="0,6 0,6 0,26 0,26 0,9 "
|
||||
id="polygon3637" />
|
||||
<polygon
|
||||
fill="none"
|
||||
points="32,6 32,9 32,26 32,26 32,6 "
|
||||
id="polygon3639" />
|
||||
<path
|
||||
fill="#39B54A"
|
||||
d="M32,9V6c0-3.3-2.7-6-6-6H6C2.7,0,0,2.7,0,6v3H32z"
|
||||
id="path3641" />
|
||||
<path
|
||||
fill="#8C6239"
|
||||
d="M0,9v17c0,3.3,2.7,6,6,6h20c3.3,0,6-2.7,6-6V9H0z"
|
||||
id="path3643" />
|
||||
</g>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#F2F2F2"
|
||||
d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
|
||||
id="path3647" />
|
||||
|
||||
<g
|
||||
id="g3784"
|
||||
transform="translate(0.07734108,0.33259869)"
|
||||
style="stroke:#666666;stroke-opacity:1"><rect
|
||||
style="fill:none;stroke:#666666;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1197"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.1934299"
|
||||
y="6.9381723"
|
||||
ry="2.1458371" /><rect
|
||||
style="fill:none;stroke:#666666;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1774"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.1934299"
|
||||
y="17.748669"
|
||||
ry="1.7275306" /><circle
|
||||
style="fill:#666666;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
|
||||
id="path1828"
|
||||
cx="21.224644"
|
||||
cy="21.072649"
|
||||
r="0.95399094" /></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -32,6 +32,7 @@
|
||||
<file>scalable/status-bad.svg</file>
|
||||
<file>scalable/status-good.svg</file>
|
||||
<file>scalable/status-yellow.svg</file>
|
||||
<file>scalable/storage.svg</file>
|
||||
<file>scalable/viewfolder.svg</file>
|
||||
<file>scalable/worlds.svg</file>
|
||||
</qresource>
|
||||
|
94
launcher/resources/pe_dark/scalable/storage.svg
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 32 32"
|
||||
enable-background="new 0 0 32 32"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs2339" />
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="g2306">
|
||||
</g>
|
||||
<g
|
||||
id="g2308">
|
||||
</g>
|
||||
<g
|
||||
id="g2310">
|
||||
</g>
|
||||
<g
|
||||
id="g2312">
|
||||
</g>
|
||||
<g
|
||||
id="g2314">
|
||||
</g>
|
||||
<g
|
||||
id="g2316">
|
||||
</g>
|
||||
<g
|
||||
id="g2318">
|
||||
</g>
|
||||
<g
|
||||
id="g2320">
|
||||
</g>
|
||||
<g
|
||||
id="g2322">
|
||||
</g>
|
||||
<g
|
||||
id="g2324">
|
||||
</g>
|
||||
<g
|
||||
id="g2326">
|
||||
</g>
|
||||
<g
|
||||
id="g2328">
|
||||
</g>
|
||||
<g
|
||||
id="g2330">
|
||||
</g>
|
||||
<g
|
||||
id="g2332">
|
||||
</g>
|
||||
<g
|
||||
id="g2334">
|
||||
</g>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#f2f2f2"
|
||||
d="m 26.077341,32.020895 h -20 c -3.3,0 -6.00000002,-2.7 -6.00000002,-6 V 6.0208954 c 0,-3.3 2.70000002,-5.99999999 6.00000002,-5.99999999 h 20 c 3.3,0 6,2.69999999 6,5.99999999 V 26.020895 c 0,3.3 -2.7,6 -6,6 z"
|
||||
id="path659"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#4d4d4d"
|
||||
d="m 28.077341,6.0208954 c 0,-1.1 -0.9,-2 -2,-2 h -20 c -1.1,0 -2,0.9 -2,2 V 26.020895 c 0,1.1 0.9,2 2,2 h 20 c 1.1,0 2,-0.9 2,-2 z"
|
||||
id="path661"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke:none;stroke-opacity:1" /><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#666666;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1197"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.270771"
|
||||
y="6.9590669"
|
||||
ry="2.1458371" /><rect
|
||||
style="fill:none;stroke:#666666;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1774"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.270771"
|
||||
y="17.769564"
|
||||
ry="1.7275306" /><circle
|
||||
style="fill:#666666;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
|
||||
id="path1828"
|
||||
cx="21.301985"
|
||||
cy="21.093542"
|
||||
r="0.95399094" /></svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -32,6 +32,7 @@
|
||||
<file>scalable/status-bad.svg</file>
|
||||
<file>scalable/status-good.svg</file>
|
||||
<file>scalable/status-yellow.svg</file>
|
||||
<file>scalable/storage.svg</file>
|
||||
<file>scalable/viewfolder.svg</file>
|
||||
<file>scalable/worlds.svg</file>
|
||||
</qresource>
|
||||
|
92
launcher/resources/pe_light/scalable/storage.svg
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 32 32"
|
||||
enable-background="new 0 0 32 32"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs706" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#F2F2F2"
|
||||
d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z"
|
||||
id="path659" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#4D4D4D"
|
||||
d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
|
||||
id="path661" />
|
||||
|
||||
<g
|
||||
id="g673">
|
||||
</g>
|
||||
<g
|
||||
id="g675">
|
||||
</g>
|
||||
<g
|
||||
id="g677">
|
||||
</g>
|
||||
<g
|
||||
id="g679">
|
||||
</g>
|
||||
<g
|
||||
id="g681">
|
||||
</g>
|
||||
<g
|
||||
id="g683">
|
||||
</g>
|
||||
<g
|
||||
id="g685">
|
||||
</g>
|
||||
<g
|
||||
id="g687">
|
||||
</g>
|
||||
<g
|
||||
id="g689">
|
||||
</g>
|
||||
<g
|
||||
id="g691">
|
||||
</g>
|
||||
<g
|
||||
id="g693">
|
||||
</g>
|
||||
<g
|
||||
id="g695">
|
||||
</g>
|
||||
<g
|
||||
id="g697">
|
||||
</g>
|
||||
<g
|
||||
id="g699">
|
||||
</g>
|
||||
<g
|
||||
id="g701">
|
||||
</g>
|
||||
<rect
|
||||
style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none"
|
||||
id="rect1197"
|
||||
width="17.458458"
|
||||
height="17.458458"
|
||||
x="7.1934299"
|
||||
y="6.9381723"
|
||||
ry="2.1458371" /><rect
|
||||
style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none"
|
||||
id="rect1774"
|
||||
width="17.458458"
|
||||
height="6.6479607"
|
||||
x="7.1934299"
|
||||
y="17.748669"
|
||||
ry="1.7275306" /><circle
|
||||
style="fill:#ffffff;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:none"
|
||||
id="path1828"
|
||||
cx="21.224644"
|
||||
cy="21.072649"
|
||||
r="0.95399094" /></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -101,9 +101,8 @@ void ConcurrentTask::startNext()
|
||||
setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
|
||||
updateState();
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
next->start();
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=] { next->start(); }, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
|
||||
|
@ -226,7 +226,9 @@ void TranslationsModel::indexReceived()
|
||||
reloadLocalFiles();
|
||||
|
||||
auto language = d->m_system_locale;
|
||||
if (!findLanguage(language))
|
||||
auto languageIterator = findLanguage(language);
|
||||
|
||||
if (languageIterator == decltype(languageIterator){})
|
||||
{
|
||||
language = d->m_system_language;
|
||||
}
|
||||
@ -259,7 +261,6 @@ void readIndex(const QString & path, QMap<QString, Language>& languages)
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 1;
|
||||
try
|
||||
{
|
||||
auto toplevel_doc = Json::requireDocument(data);
|
||||
@ -292,7 +293,6 @@ void readIndex(const QString & path, QMap<QString, Language>& languages)
|
||||
lang.file_size = Json::requireInteger(langObj, "size");
|
||||
|
||||
languages.insert(lang.key, lang);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
catch (Json::JsonException & e)
|
||||
@ -418,8 +418,6 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const
|
||||
return QVariant();
|
||||
|
||||
int row = index.row();
|
||||
auto column = static_cast<Column>(index.column());
|
||||
|
||||
if (row < 0 || row >= d->m_languages.size())
|
||||
return QVariant();
|
||||
|
||||
@ -428,22 +426,19 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
auto column = static_cast<Column>(index.column());
|
||||
switch(column)
|
||||
{
|
||||
case Column::Language:
|
||||
{
|
||||
return lang.languageName();
|
||||
}
|
||||
case Column::Completeness:
|
||||
{
|
||||
return QString("%1%").arg(lang.percentTranslated(), 3, 'f', 1);
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
return tr("%1:\n%2 translated\n%3 fuzzy\n%4 total").arg(lang.key, QString::number(lang.translated), QString::number(lang.fuzzy), QString::number(lang.total));
|
||||
}
|
||||
case Qt::UserRole:
|
||||
return lang.key;
|
||||
default:
|
||||
@ -495,7 +490,7 @@ int TranslationsModel::columnCount(const QModelIndex& parent) const
|
||||
return 2;
|
||||
}
|
||||
|
||||
Language * TranslationsModel::findLanguage(const QString& key)
|
||||
QVector<Language>::iterator TranslationsModel::findLanguage(const QString& key)
|
||||
{
|
||||
auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang)
|
||||
{
|
||||
@ -503,7 +498,7 @@ Language * TranslationsModel::findLanguage(const QString& key)
|
||||
});
|
||||
if(found == d->m_languages.end())
|
||||
{
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -514,21 +509,21 @@ Language * TranslationsModel::findLanguage(const QString& key)
|
||||
bool TranslationsModel::selectLanguage(QString key)
|
||||
{
|
||||
QString &langCode = key;
|
||||
auto langPtr = findLanguage(key);
|
||||
auto langIterator = findLanguage(key);
|
||||
|
||||
if (langCode.isEmpty())
|
||||
{
|
||||
d->no_language_set = true;
|
||||
}
|
||||
|
||||
if(!langPtr)
|
||||
if (langIterator == decltype(langIterator){})
|
||||
{
|
||||
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
|
||||
langCode = defaultLangCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
langCode = langPtr->key;
|
||||
langCode = langIterator->key;
|
||||
}
|
||||
|
||||
// uninstall existing translators if there are any
|
||||
@ -580,7 +575,7 @@ bool TranslationsModel::selectLanguage(QString key)
|
||||
d->m_qt_translator.reset();
|
||||
}
|
||||
|
||||
if(langPtr->localFileType == FileType::PO)
|
||||
if(langIterator->localFileType == FileType::PO)
|
||||
{
|
||||
qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "...";
|
||||
auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po"));
|
||||
@ -603,7 +598,7 @@ bool TranslationsModel::selectLanguage(QString key)
|
||||
d->m_app_translator.reset();
|
||||
}
|
||||
}
|
||||
else if(langPtr->localFileType == FileType::QM)
|
||||
else if(langIterator->localFileType == FileType::QM)
|
||||
{
|
||||
d->m_app_translator.reset(new QTranslator());
|
||||
if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path()))
|
||||
@ -635,7 +630,7 @@ bool TranslationsModel::selectLanguage(QString key)
|
||||
QModelIndex TranslationsModel::selectedIndex()
|
||||
{
|
||||
auto found = findLanguage(d->m_selectedLanguage);
|
||||
if(found)
|
||||
if(found != decltype(found){})
|
||||
{
|
||||
// QVector iterator freely converts to pointer to contained type
|
||||
return index(found - d->m_languages.begin(), 0, QModelIndex());
|
||||
@ -673,7 +668,7 @@ void TranslationsModel::updateLanguage(QString key)
|
||||
return;
|
||||
}
|
||||
auto found = findLanguage(key);
|
||||
if(!found)
|
||||
if(found == decltype(found){})
|
||||
{
|
||||
qWarning() << "Cannot update invalid language" << key;
|
||||
return;
|
||||
@ -692,7 +687,7 @@ void TranslationsModel::downloadTranslation(QString key)
|
||||
return;
|
||||
}
|
||||
auto lang = findLanguage(key);
|
||||
if(!lang)
|
||||
if(lang == decltype(lang){})
|
||||
{
|
||||
qWarning() << "Will not download an unknown translation" << key;
|
||||
return;
|
||||
|
@ -40,7 +40,7 @@ public:
|
||||
void downloadIndex();
|
||||
|
||||
private:
|
||||
Language *findLanguage(const QString & key);
|
||||
QVector<Language>::iterator findLanguage(const QString & key);
|
||||
void reloadLocalFiles();
|
||||
void downloadTranslation(QString key);
|
||||
void downloadNext();
|
||||
|
@ -1483,27 +1483,40 @@ void MainWindow::setCatBackground(bool enabled)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0));
|
||||
QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0));
|
||||
QString cat;
|
||||
if(non_stupid_abs(now.daysTo(xmas)) <= 4) {
|
||||
cat = "catmas";
|
||||
QDateTime christmasStart(QDate(now.date().year(), 12, 25), QTime(0, 0));
|
||||
QDateTime christmasEnd(QDate(now.date().year(), 1, 7), QTime(0, 0)); //end at midnight of the 7th
|
||||
|
||||
QString cat = "default";
|
||||
QString catStyleOpt = APPLICATION->settings()->get("CatStyle").toString();
|
||||
if(catStyleOpt == "Floppa")
|
||||
cat = "floppa";
|
||||
else if(catStyleOpt == "Jinx")
|
||||
cat = "jinx";
|
||||
|
||||
if(christmasStart <= now || now < christmasEnd) {
|
||||
cat += "Catmas";
|
||||
}
|
||||
else if (non_stupid_abs(now.daysTo(birthday)) <= 12) {
|
||||
cat = "cattiversary";
|
||||
cat += "Cattiversary";
|
||||
}
|
||||
else {
|
||||
cat = "kitteh";
|
||||
cat += "Cat";
|
||||
}
|
||||
|
||||
auto cat_position = APPLICATION->settings()->get("CatPosition").toString().toLower().trimmed();
|
||||
if (cat_position != "top left" && cat_position != "bottom left" && cat_position != "bottom right" && cat_position != "top right")
|
||||
cat_position = "top right";
|
||||
|
||||
view->setStyleSheet(QString(R"(
|
||||
InstanceView
|
||||
{
|
||||
background-image: url(:/backgrounds/%1);
|
||||
background-attachment: fixed;
|
||||
background-clip: padding;
|
||||
background-position: top right;
|
||||
background-position: %2;
|
||||
background-repeat: none;
|
||||
background-color:palette(base);
|
||||
})").arg(cat));
|
||||
})").arg(cat, cat_position));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1569,8 +1582,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
|
||||
{
|
||||
view->updateGeometries();
|
||||
setSelectedInstanceById(inst->id());
|
||||
if (APPLICATION->accounts()->anyAccountIsValid())
|
||||
{
|
||||
if (APPLICATION->accounts()->drmCheck()) {
|
||||
ProgressDialog loadDialog(this);
|
||||
auto update = inst->createUpdateTask(Net::Mode::Online);
|
||||
connect(update.get(), &Task::failed, [this](QString reason)
|
||||
@ -1583,9 +1595,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
|
||||
loadDialog.setSkipButton(true, tr("Abort"));
|
||||
loadDialog.execWithTask(update.get());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Error"),
|
||||
@ -1803,6 +1813,7 @@ void MainWindow::globalSettingsClosed()
|
||||
updateMainToolBar();
|
||||
updateToolsMenu();
|
||||
updateStatusCenter();
|
||||
updateCat();
|
||||
// This needs to be done to prevent UI elements disappearing in the event the config is changed
|
||||
// but PolyMC exits abnormally, causing the window state to never be saved:
|
||||
APPLICATION->settings()->set("MainWindowState", saveState().toBase64());
|
||||
@ -2153,6 +2164,11 @@ void MainWindow::updateStatusCenter()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateCat()
|
||||
{
|
||||
setCatBackground(APPLICATION->settings()->get("TheCat").toBool());
|
||||
}
|
||||
|
||||
void MainWindow::refreshCurrentInstance(bool running)
|
||||
{
|
||||
auto current = view->selectionModel()->currentIndex();
|
||||
|
@ -195,6 +195,8 @@ private slots:
|
||||
|
||||
void updateNewsLabel();
|
||||
|
||||
void updateCat();
|
||||
|
||||
void konamiTriggered();
|
||||
|
||||
void globalSettingsClosed();
|
||||
|
@ -4,29 +4,102 @@
|
||||
|
||||
namespace WinDarkmode {
|
||||
|
||||
/* See https://github.com/statiolake/neovim-qt/commit/da8eaba7f0e38b6b51f3bacd02a8cc2d1f7a34d8 */
|
||||
void setDarkWinTitlebar(WId winid, bool darkmode)
|
||||
template<int syscall_id, typename... arglist> __attribute((naked)) uint32_t __fastcall WinSyscall([[maybe_unused]] arglist... args)
|
||||
{
|
||||
HWND hwnd = reinterpret_cast<HWND>(winid);
|
||||
BOOL dark = (BOOL) darkmode;
|
||||
asm volatile("mov %%rcx, %%r10; movl %0, %%eax; syscall; ret"
|
||||
:: "i"(syscall_id));
|
||||
}
|
||||
|
||||
HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
|
||||
fnAllowDarkModeForWindow AllowDarkModeForWindow
|
||||
= reinterpret_cast<fnAllowDarkModeForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)));
|
||||
fnSetPreferredAppMode SetPreferredAppMode
|
||||
= reinterpret_cast<fnSetPreferredAppMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)));
|
||||
fnSetWindowCompositionAttribute SetWindowCompositionAttribute
|
||||
= reinterpret_cast<fnSetWindowCompositionAttribute>(GetProcAddress(hUser32, "SetWindowCompositionAttribute"));
|
||||
VOID ApplyStringProp(HWND hWnd, LPCWSTR lpString, WORD Property)
|
||||
{
|
||||
WORD Prop = (uint16_t)(uint64_t)GetPropW(hWnd, (LPCWSTR)(uint64_t)Property);
|
||||
if (Prop)
|
||||
{
|
||||
DeleteAtom(Prop);
|
||||
RemovePropW(hWnd, (LPCWSTR)(uint64_t)Property);
|
||||
}
|
||||
if (lpString)
|
||||
{
|
||||
ATOM v = AddAtomW(lpString);
|
||||
if (v)
|
||||
SetPropW(hWnd, (LPCWSTR)(uint64_t)Property, (HANDLE)(uint64_t)v);
|
||||
}
|
||||
}
|
||||
|
||||
SetPreferredAppMode(AllowDark);
|
||||
AllowDarkModeForWindow(hwnd, dark);
|
||||
VOID AllowDarkModeForWindow(HWND hWnd, BOOL Enable)
|
||||
{
|
||||
if (hWnd)
|
||||
{
|
||||
ApplyStringProp(hWnd, Enable ? L"Enabled" : NULL, 0xA91E);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL IsWindows11()
|
||||
{
|
||||
HMODULE hKern32 = GetModuleHandleW(L"kernel32.dll");
|
||||
return GetProcAddress(hKern32, "Wow64SetThreadDefaultGuestMachine") != NULL; // Win11 21h2+
|
||||
}
|
||||
|
||||
BOOL IsWindows10_Only()
|
||||
{
|
||||
HMODULE hKern32 = GetModuleHandleW(L"kernel32.dll");
|
||||
HMODULE hNtuser = GetModuleHandleW(L"ntdll.dll");
|
||||
return GetProcAddress(hKern32, "SetThreadSelectedCpuSets") != NULL
|
||||
&& GetProcAddress(hNtuser, "ZwSetInformationCpuPartition") == NULL;
|
||||
}
|
||||
|
||||
BOOL IsWindows8_0_Only()
|
||||
{
|
||||
HMODULE hKern32 = GetModuleHandleW(L"kernel32.dll");
|
||||
return GetProcAddress(hKern32, "CreateFile2") != NULL // Export added in 6.2 (8)
|
||||
&& GetProcAddress(hKern32, "AppXFreeMemory") != NULL; // Export added in 6.2 (8), removed in 6.3 (8.1)
|
||||
}
|
||||
|
||||
BOOL IsWindows8_1_Only()
|
||||
{
|
||||
HMODULE hKern32 = GetModuleHandleW(L"kernel32.dll");
|
||||
return GetProcAddress(hKern32, "CalloutOnFiberStack") != NULL // Export added in 6.3 (8.1), Removed in 10.0.10586
|
||||
&& GetProcAddress(hKern32, "SetThreadSelectedCpuSets") == NULL; // Export added in 10.0 (10)
|
||||
}
|
||||
|
||||
void setWindowDarkModeEnabled(HWND hWnd, bool Enabled)
|
||||
{
|
||||
AllowDarkModeForWindow(hWnd, Enabled);
|
||||
BOOL DarkEnabled = (BOOL)Enabled;
|
||||
WINDOWCOMPOSITIONATTRIBDATA data = {
|
||||
WCA_USEDARKMODECOLORS,
|
||||
&dark,
|
||||
sizeof(dark)
|
||||
&DarkEnabled,
|
||||
sizeof(DarkEnabled)
|
||||
};
|
||||
SetWindowCompositionAttribute(hwnd, &data);
|
||||
|
||||
#ifdef _WIN64
|
||||
constexpr int NtUserSetWindowCompositionAttribute_NT6_2 = 0x13b4;
|
||||
constexpr int NtUserSetWindowCompositionAttribute_NT6_3 = 0x13e5;
|
||||
|
||||
if (IsWindows8_0_Only())
|
||||
WinSyscall<NtUserSetWindowCompositionAttribute_NT6_2>(hWnd, &data);
|
||||
else if (IsWindows8_1_Only())
|
||||
WinSyscall<NtUserSetWindowCompositionAttribute_NT6_3>(hWnd, &data);
|
||||
else if (IsWindows10_Only() || IsWindows11())
|
||||
{
|
||||
((fnSetWindowCompositionAttribute)(PVOID)GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowCompositionAttribute"))
|
||||
(hWnd, &data);
|
||||
// Verified this ordinal is the same through Win11 22H2 (5/8/2023)
|
||||
((fnSetPreferredAppMode)(PVOID)GetProcAddress(GetModuleHandleW(L"uxtheme.dll"), MAKEINTRESOURCEA(135)))
|
||||
(AppMode_AllowDark);
|
||||
}
|
||||
#else
|
||||
if (IsWindows10_Only())
|
||||
{
|
||||
((fnSetWindowCompositionAttribute)(PVOID)GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowCompositionAttribute"))
|
||||
(hWnd, &data);
|
||||
// Verified this ordinal is the same through Win11 22H2 (5/8/2023)
|
||||
((fnSetPreferredAppMode)(PVOID)GetProcAddress(GetModuleHandleW(L"uxtheme.dll"), MAKEINTRESOURCEA(135)))
|
||||
(AppMode_AllowDark);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,14 +6,14 @@
|
||||
|
||||
namespace WinDarkmode {
|
||||
|
||||
void setDarkWinTitlebar(WId winid, bool darkmode);
|
||||
void setWindowDarkModeEnabled(HWND hWnd, bool Enabled);
|
||||
|
||||
enum PreferredAppMode {
|
||||
Default,
|
||||
AllowDark,
|
||||
ForceDark,
|
||||
ForceLight,
|
||||
Max
|
||||
AppMode_Default,
|
||||
AppMode_AllowDark,
|
||||
AppMode_ForceDark,
|
||||
AppMode_ForceLight,
|
||||
AppMode_Max
|
||||
};
|
||||
|
||||
enum WINDOWCOMPOSITIONATTRIB {
|
||||
|
@ -20,9 +20,10 @@
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog)
|
||||
LoginDialog::LoginDialog(QWidget *parent, AccountType type) : QDialog(parent), ui(new Ui::LoginDialog), m_accountType{type}
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->authlibInjectorBaseTextBox->setVisible(m_accountType == AccountType::AuthlibInjector);
|
||||
ui->progressBar->setVisible(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
|
||||
@ -42,7 +43,14 @@ void LoginDialog::accept()
|
||||
ui->progressBar->setVisible(true);
|
||||
|
||||
// Setup the login task and start it
|
||||
m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
|
||||
if (m_accountType == AccountType::Mojang)
|
||||
{
|
||||
m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
|
||||
}
|
||||
else if (m_accountType == AccountType::AuthlibInjector)
|
||||
{
|
||||
m_account = MinecraftAccount::createAuthlibInjectorFromUsername(ui->userTextBox->text(), ui->authlibInjectorBaseTextBox->text());
|
||||
}
|
||||
m_loginTask = m_account->login(ui->passTextBox->text());
|
||||
connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
|
||||
connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded);
|
||||
@ -106,10 +114,9 @@ void LoginDialog::onTaskProgress(qint64 current, qint64 total)
|
||||
ui->progressBar->setValue(current);
|
||||
}
|
||||
|
||||
// Public interface
|
||||
MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
|
||||
MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg, AccountType type)
|
||||
{
|
||||
LoginDialog dlg(parent);
|
||||
LoginDialog dlg(parent, type);
|
||||
dlg.ui->label->setText(msg);
|
||||
if (dlg.exec() == QDialog::Accepted)
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtCore/QEventLoop>
|
||||
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/MinecraftAccount.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
@ -33,10 +34,13 @@ class LoginDialog : public QDialog
|
||||
public:
|
||||
~LoginDialog();
|
||||
|
||||
static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
|
||||
/*
|
||||
* @param type: Mojang or Authlib
|
||||
*/
|
||||
static MinecraftAccountPtr newAccount(QWidget *parent, QString message, AccountType type = AccountType::Mojang);
|
||||
|
||||
private:
|
||||
explicit LoginDialog(QWidget *parent = 0);
|
||||
explicit LoginDialog(QWidget *parent = 0, AccountType type = AccountType::Mojang);
|
||||
|
||||
void setUserInputsEnabled(bool enable);
|
||||
|
||||
@ -56,4 +60,5 @@ private:
|
||||
Ui::LoginDialog *ui;
|
||||
MinecraftAccountPtr m_account;
|
||||
Task::Ptr m_loginTask;
|
||||
AccountType m_accountType;
|
||||
};
|
||||
|
@ -33,6 +33,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="authlibInjectorBaseTextBox">
|
||||
<property name="placeholderText">
|
||||
<string>AuthlibInjector base URL (e.g. ely.by)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="userTextBox">
|
||||
<property name="placeholderText">
|
||||
|
@ -30,7 +30,8 @@ NewsDialog::~NewsDialog()
|
||||
|
||||
void NewsDialog::selectedArticleChanged(const QString& new_title)
|
||||
{
|
||||
auto const& article_entry = m_entries.constFind(new_title).value();
|
||||
auto const& article_entry_ptr = m_entries.constFind(new_title);
|
||||
auto const& article_entry = article_entry_ptr.value();
|
||||
|
||||
ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title));
|
||||
ui->currentArticleContentBrowser->setText(article_entry->content);
|
||||
|
@ -218,11 +218,11 @@ bool AccessibleInstanceView::selectRow(int row)
|
||||
if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) {
|
||||
view()->clearSelection();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << "Unhandled QAbstractItemView selection type!";
|
||||
break;
|
||||
}
|
||||
|
||||
view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
||||
@ -248,7 +248,7 @@ bool AccessibleInstanceView::selectColumn(int column)
|
||||
if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) {
|
||||
return false;
|
||||
}
|
||||
// fallthrough intentional
|
||||
[[fallthrough]];
|
||||
}
|
||||
case QAbstractItemView::ContiguousSelection: {
|
||||
if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
|
||||
@ -279,7 +279,7 @@ bool AccessibleInstanceView::unselectRow(int row)
|
||||
QItemSelection selection(index, index);
|
||||
auto selectionModel = view()->selectionModel();
|
||||
|
||||
switch (view()->selectionMode()) {
|
||||
switch (const auto selectionType = view()->selectionMode()) {
|
||||
case QAbstractItemView::SingleSelection:
|
||||
// no unselect
|
||||
if (selectedRowCount() == 1) {
|
||||
@ -298,8 +298,13 @@ bool AccessibleInstanceView::unselectRow(int row)
|
||||
//the ones which are down the current row will be deselected
|
||||
selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QAbstractItemView::NoSelection:
|
||||
break;
|
||||
default: {
|
||||
// FIXME: See if MultiSelection / ExtendedSelection need to be handled
|
||||
qWarning() << "Unhandled QAbstractItemView selection type!" << selectionType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -342,6 +347,7 @@ bool AccessibleInstanceView::unselectColumn(int column)
|
||||
//of the current row, the ones which are at the right will be deselected
|
||||
selection = QItemSelection(index, model->index(0, columnCount() - 1, view()->rootIndex()));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -40,10 +40,8 @@
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <QTabBar>
|
||||
#include <QValidator>
|
||||
#include <QVariant>
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
@ -82,9 +80,9 @@ APIPage::APIPage(QWidget *parent) :
|
||||
connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLPlaceholder);
|
||||
// This function needs to be called even when the ComboBox's index is still in its default state.
|
||||
updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());
|
||||
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
|
||||
ui->msaClientID->setValidator(new QRegularExpressionValidator(validMSAClientID, ui->msaClientID));
|
||||
ui->flameKey->setValidator(new QRegularExpressionValidator(validFlameKey, ui->flameKey));
|
||||
ui->baseURLEntry->setValidator(new TrimmedRegExValidator(validUrlRegExp, ui->baseURLEntry));
|
||||
ui->msaClientID->setValidator(new TrimmedRegExValidator(validMSAClientID, ui->msaClientID));
|
||||
ui->flameKey->setValidator(new TrimmedRegExValidator(validFlameKey, ui->flameKey));
|
||||
|
||||
ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
|
||||
ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT);
|
||||
|
@ -4,6 +4,7 @@
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (c) 2022 Lenny McLennington <lenny@sneed.church>
|
||||
* Copyright (c) 2022 jdp_ (https://github.com/jdpatdiscord)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -38,14 +39,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QValidator>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionValidator>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
namespace Ui {
|
||||
namespace Ui
|
||||
{
|
||||
class APIPage;
|
||||
}
|
||||
|
||||
class TrimmedRegExValidator : public QRegularExpressionValidator
|
||||
{
|
||||
using QRegularExpressionValidator::QRegularExpressionValidator;
|
||||
|
||||
virtual QValidator::State validate(QString& input, int& npos) const override
|
||||
{
|
||||
input = input.trimmed();
|
||||
return QRegularExpressionValidator::validate(input, npos);
|
||||
}
|
||||
};
|
||||
|
||||
class APIPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -157,6 +157,36 @@ void AccountListPage::on_actionAddMojang_triggered()
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAddAuthlibInjector_triggered()
|
||||
{
|
||||
if (!m_accounts->drmCheck()) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
tr("Error"),
|
||||
tr(
|
||||
"You must add a Microsoft or Mojang account that owns Minecraft before you can add an Authlib Injector account."
|
||||
"<br><br>"
|
||||
"If you have lost your account you can contact Microsoft for support."
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr account = LoginDialog::newAccount(
|
||||
this,
|
||||
tr("Please enter the AuthlibInjector base URL, and enter your account email and password to add your account."),
|
||||
AccountType::AuthlibInjector
|
||||
);
|
||||
|
||||
if (account)
|
||||
{
|
||||
m_accounts->addAccount(account);
|
||||
if (m_accounts->count() == 1) {
|
||||
m_accounts->setDefaultAccount(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAddMicrosoft_triggered()
|
||||
{
|
||||
if(BuildConfig.BUILD_PLATFORM == "osx64") {
|
||||
|
@ -83,6 +83,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void on_actionAddMojang_triggered();
|
||||
void on_actionAddAuthlibInjector_triggered();
|
||||
void on_actionAddMicrosoft_triggered();
|
||||
void on_actionAddOffline_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
|
@ -54,6 +54,7 @@
|
||||
</attribute>
|
||||
<addaction name="actionAddMicrosoft"/>
|
||||
<addaction name="actionAddMojang"/>
|
||||
<addaction name="actionAddAuthlibInjector"/>
|
||||
<addaction name="actionAddOffline"/>
|
||||
<addaction name="actionRefresh"/>
|
||||
<addaction name="actionRemove"/>
|
||||
@ -68,6 +69,11 @@
|
||||
<string>Add &Mojang</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAddAuthlibInjector">
|
||||
<property name="text">
|
||||
<string>Add Authlib-&Injector</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remo&ve</string>
|
||||
|