diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000000..717fbb6851
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,51 @@
+
+
+System Information
+-----------------------------
+MultiMC version:
+
+Operating System:
+
+Summary of the issue or suggestion:
+----------------------------------------------
+
+
+What should happen:
+------------------------------
+
+
+Steps to reproduce the issue (Add more if needed):
+-------------------------------------------------------------
+1.
+
+2.
+
+3.
+
+Suspected cause:
+---------------------------
+
+
+Logs/Screenshots:
+----------------------------
+[//]: # (Please refer to https://github.com/Ponywka/MultiMC5-with-offline/wiki/Log-Upload for instructions on how to attach your logs.)
+
+
+Additional Info:
+---------------------------
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 7e894785ba..5b8d858e1c 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,7 +1,6 @@
name: Bug Report
description: File a bug report
labels: [bug, needs-triage]
-issue_body: false
body:
- type: markdown
attributes:
diff --git a/.github/ISSUE_TEMPLATE/suggestion.yml b/.github/ISSUE_TEMPLATE/suggestion.yml
index ab2449a0fe..88bf66cf60 100644
--- a/.github/ISSUE_TEMPLATE/suggestion.yml
+++ b/.github/ISSUE_TEMPLATE/suggestion.yml
@@ -1,7 +1,6 @@
name: Suggestion
description: Make a suggestion
labels: [idea, needs-triage]
-issue_body: true
body:
- type: markdown
attributes:
@@ -34,8 +33,6 @@ body:
options:
- label: I have searched the issue tracker and did not find an issue describing my suggestion, especially not one that has been rejected.
required: true
-- type: markdown
+- type: textarea
attributes:
- value: |
- ### You may use the editor below to elaborate further.
-# The issue_body: true up there makes the standard WYSIWYG editor for issues show up down here.
+ label: You may use the editor below to elaborate further.
diff --git a/.github/workflows/lin64.yml b/.github/workflows/lin64.yml
new file mode 100644
index 0000000000..2aa60fcc4c
--- /dev/null
+++ b/.github/workflows/lin64.yml
@@ -0,0 +1,30 @@
+name: lin64
+
+on: [push, pull_request]
+
+env:
+ BUILD_TYPE: Release
+
+jobs:
+ build:
+ runs-on: ubuntu-18.04
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: jurplel/install-qt-action@v2
+ with:
+ version: 5.11.3
+
+ - name: Sub projects
+ run: git submodule init && git submodule update
+
+ - name: CMake
+ run: cmake -DCMAKE_INSTALL_PREFIX=./install .
+
+ - name: Build
+ run: mkdir install && make -j8 install
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: lin64
+ path: install/
diff --git a/.gitignore b/.gitignore
index 496c382e3f..6b716252f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ CMakeLists.txt.user.*
/.settings
/.idea
cmake-build-*/
+Debug
# Build dirs
build
@@ -29,3 +30,6 @@ tags
#OSX Stuff
.DS_Store
+
+branding
+secrets
diff --git a/BUILD.md b/BUILD.md
index 4360fddea4..c1efb96972 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -12,7 +12,7 @@ Build Instructions
# Note
MultiMC is a portable application and is not supposed to be installed into any system folders.
-That would be anything outside your home folder. Before runing `make install`, make sure
+That would be anything outside your home folder. Before running `make install`, make sure
you set the install path to something you have write access to. Never build this under
an administrator/root level account. Don't use `sudo`. It won't work and it's not supposed to work.
@@ -22,7 +22,7 @@ an administrator/root level account. Don't use `sudo`. It won't work and it's no
Clone the source code using git and grab all the submodules:
```
-git clone git@github.com:MultiMC/MultiMC5.git
+git clone git@github.com:Ponywka/MultiMC5-with-offline.git
git submodule init
git submodule update
```
@@ -50,7 +50,7 @@ mkdir ~/MultiMC && cd ~/MultiMC
mkdir build
mkdir install
# clone the complete source
-git clone --recursive https://github.com/MultiMC/MultiMC5.git src
+git clone --recursive https://github.com/Ponywka/MultiMC5-with-offline.git src
# configure the project
cd build
cmake -DCMAKE_INSTALL_PREFIX=../install ../src
@@ -165,7 +165,7 @@ zlib1.dll
**These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
### Compile from command line on Windows
1. If you installed Qt with the web installer, there should be a shortcut called `Qt 5.4 for Desktop (MinGW 4.9 32-bit)` in the Start menu on Windows 7 and 10. Best way to find it is to search for it. Do note you cannot just use cmd.exe, you have to use the shortcut, otherwise the proper MinGW software will not be on the PATH.
-2. Once that is open, change into your user directory, and clone MultiMC by doing `git clone --recursive https://github.com/MultiMC/MultiMC5.git`, and change directory to the folder you cloned to.
+2. Once that is open, change into your user directory, and clone MultiMC by doing `git clone --recursive https://github.com/Ponywka/MultiMC5-with-offline.git`, and change directory to the folder you cloned to.
3. Make a build directory, and change directory to the directory and do `cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:\Path\that\makes\sense\for\you`. By default, it will install to C:\Program Files (x86), which you might not want, if you want a local installation. If you want to install it to that directory, make sure to run the command window as administrator.
3. Do `mingw32-make -jX`, where X is the number of cores your CPU has plus one.
4. Now to wait for it to compile. This could take some time. Hopefully it compiles properly.
@@ -185,7 +185,10 @@ zlib1.dll
Pick an installation path - this is where the final `.app` will be constructed when you run `make install`. Supply it as the `CMAKE_INSTALL_PREFIX` argument during CMake configuration.
```
-git clone --recursive https://github.com/MultiMC/MultiMC5.git
+git clone https://github.com/Ponywka/MultiMC5-with-offline.git
+cd MultiMC5
+git submodule init
+git submodule update
cd MultiMC5
mkdir build
cd build
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1c93e7a9c0..44028f7656 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -55,7 +55,7 @@ set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetc
######## Set version numbers ########
set(MultiMC_VERSION_MAJOR 0)
set(MultiMC_VERSION_MINOR 6)
-set(MultiMC_VERSION_HOTFIX 12)
+set(MultiMC_VERSION_HOTFIX 13)
# Build number
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@@ -64,7 +64,7 @@ set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.
set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
# Channel list URL
-set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
+set(MultiMC_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
# Notification URL
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
@@ -75,9 +75,24 @@ set(MultiMC_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch M
# paste.ee API key
set(MultiMC_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
+# Imgur API Client ID
+set(MultiMC_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
+
# Google analytics ID
set(MultiMC_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
+# Bug tracker URL
+set(MultiMC_BUG_TRACKER_URL "" CACHE STRING "URL for the bug tracker.")
+
+# Discord URL
+set(MultiMC_DISCORD_URL "" CACHE STRING "URL for the Discord guild.")
+
+# Subreddit URL
+set(MultiMC_SUBREDDIT_URL "" CACHE STRING "URL for the subreddit.")
+
+
+option(MultiMC_EMBED_SECRETS "Determines whether to embed secrets. Secrets are separate and non-public." OFF)
+
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
@@ -163,7 +178,7 @@ if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
set(INSTALL_BUNDLE "full")
# Add the icon
- install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
+ install(FILES launcher/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
set(BINARY_DEST_DIR "bin")
@@ -186,7 +201,7 @@ elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
- install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
+ install(PROGRAMS launcher/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
set(BINARY_DEST_DIR "bin")
@@ -203,7 +218,7 @@ elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
- install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
+ install(PROGRAMS launcher/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary")
@@ -265,12 +280,19 @@ add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # google analytics library
add_subdirectory(libraries/optional-bare)
+add_subdirectory(libraries/tomlc99) # toml parser
+add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
############################### Built Artifacts ###############################
add_subdirectory(buildconfig)
-add_subdirectory(api/logic)
-add_subdirectory(api/gui)
+
+if(MultiMC_EMBED_SECRETS)
+ add_subdirectory(secrets)
+else()
+ add_subdirectory(notsecrets)
+endif()
+
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
-add_subdirectory(application)
+add_subdirectory(launcher)
diff --git a/COPYING.md b/COPYING.md
index c0c986066a..4c19bbc22f 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -251,3 +251,54 @@
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+
+# tomlc99
+
+ MIT License
+
+ Copyright (c) 2017 CK Tan
+ https://github.com/cktan/tomlc99
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+# O2 (Katabasis fork)
+
+ Copyright (c) 2012, Akos Polster
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index ede8f88f02..0133e58a78 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,28 @@
-
-
-
+**This is a "cracked" version of a popular Minecraft launcher that lets you play the game without a Mojang account.**
+This software is not related to MultiMC developers and provided without any warranty. Please don't bomb MultiMC developers if something gets wrong using this launcher.
+
+Offline mode based by this code:
+https://github.com/MultiMC/MultiMC5/commit/6ede3c13b2bcda315e65dd78f2bfd729bc8b699b
+
+Rewrited for use license and offline accounts at the same time
+
+Details about the original launcher below:
MultiMC 5
=========
-MultiMC is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. It also allows you to easily install and remove mods by simply dragging and dropping. Here are the current [features](https://github.com/MultiMC/MultiMC5/wiki#features) of MultiMC.
-
+MultiMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
## Development
-The project uses C++ and Qt5 as the language and base framework. This might seem odd in the Minecraft community, but allows using 25MB of RAM, where other tools use an excessive amount of resources for no reason.
+If you want to contribute, talk to us on [Discord](https://discord.gg/multimc) first.
-We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
+While blindly submitting PRs is definitely possible, they're not necessarily going to get accepted.
-If you want to contribute, either talk to us on [Discord](https://discord.gg/multimc), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from the github issues [workflowy](https://github.com/MultiMC/MultiMC5/issues) - there is always plenty of ideas around.
+<<<<<<< HEAD
+We aren't looking for flashy features, but expanding upon the existing feature set without distruption or endangering future viability of the project is OK.
+=======
+If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from the github issues [workflowy](https://github.com/Ponywka/MultiMC5-with-offline/issues) - there is always plenty of ideas around.
+>>>>>>> 8f2bcfc5 (Offline patch)
### Building
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
@@ -21,19 +30,21 @@ If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build inst
### Code formatting
Just follow the existing formatting.
-In general:
-* Indent with 4 space unless it's in a submodule
-* Keep lists (of arguments, parameters, initializators...) as lists, not paragraphs.
+In general, in order of importance:
+* Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
* Prefer readability over dogma.
+* Keep to the existing formatting.
+* Indent with 4 space unless it's in a submodule.
+* Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both.
## Translations
-Translations can be done [on crowdin](https://translate.multimc.org).
+Translations can be done [on crowdin](https://translate.multimc.org). Please avoid making direct pull requests to the translations repository.
-## Forking/Redistributing
+## Forking/Redistributing/Custom builds policy
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
-Part of the reason for using the Apache license is we don't want people using the "MultiMC" name when redistributing the project. This means people must take the time to go through the source code and remove all references to "MultiMC", including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).
+Part of the reason for using the Apache license is that we don't want people using the "MultiMC" name when redistributing the project. This means people must take the time to go through the source code and remove all references to "MultiMC", including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).
Apache covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork *without* implying that you have our blessing.
diff --git a/api/gui/CMakeLists.txt b/api/gui/CMakeLists.txt
deleted file mode 100644
index ad116a43da..0000000000
--- a/api/gui/CMakeLists.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-project(MultiMC_gui LANGUAGES CXX)
-
-set(GUI_SOURCES
- DesktopServices.h
- DesktopServices.cpp
-
- # Icons
- icons/MMCIcon.h
- icons/MMCIcon.cpp
- icons/IconList.h
- icons/IconList.cpp
-
- SkinUtils.cpp
- SkinUtils.h
-)
-################################ COMPILE ################################
-
-add_library(MultiMC_gui SHARED ${GUI_SOURCES})
-set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
-
-generate_export_header(MultiMC_gui)
-
-# Link
-target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui)
-
-# Mark and export headers
-target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
-
-# Install it
-install(
- TARGETS MultiMC_gui
- RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
- LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
-)
\ No newline at end of file
diff --git a/api/gui/DesktopServices.h b/api/gui/DesktopServices.h
deleted file mode 100644
index 606fa52cbf..0000000000
--- a/api/gui/DesktopServices.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#pragma once
-
-#include
-#include
-#include "multimc_gui_export.h"
-
-/**
- * This wraps around QDesktopServices and adds workarounds where needed
- * Use this instead of QDesktopServices!
- */
-namespace DesktopServices
-{
- /**
- * Open a file in whatever application is applicable
- */
- MULTIMC_GUI_EXPORT bool openFile(const QString &path);
-
- /**
- * Open a file in the specified application
- */
- MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
-
- /**
- * Run an application
- */
- MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
-
- /**
- * Open a directory
- */
- MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
-
- /**
- * Open the URL, most likely in a browser. Maybe.
- */
- MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
-}
diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt
deleted file mode 100644
index 3193d813ab..0000000000
--- a/api/logic/CMakeLists.txt
+++ /dev/null
@@ -1,552 +0,0 @@
-project(MultiMC_logic)
-
-include (UnitTest)
-
-set(CORE_SOURCES
- # LOGIC - Base classes and infrastructure
- BaseInstaller.h
- BaseInstaller.cpp
- BaseVersionList.h
- BaseVersionList.cpp
- InstanceList.h
- InstanceList.cpp
- InstanceTask.h
- InstanceTask.cpp
- LoggedProcess.h
- LoggedProcess.cpp
- MessageLevel.cpp
- MessageLevel.h
- BaseVersion.h
- BaseInstance.h
- BaseInstance.cpp
- NullInstance.h
- MMCZip.h
- MMCZip.cpp
- MMCStrings.h
- MMCStrings.cpp
-
- # Basic instance manipulation tasks (derived from InstanceTask)
- InstanceCreationTask.h
- InstanceCreationTask.cpp
- InstanceCopyTask.h
- InstanceCopyTask.cpp
- InstanceImportTask.h
- InstanceImportTask.cpp
-
- # Use tracking separate from memory management
- Usable.h
-
- # Prefix tree where node names are strings between separators
- SeparatorPrefixTree.h
-
- # WARNING: globals live here
- Env.h
- Env.cpp
-
- # String filters
- Filter.h
- Filter.cpp
-
- # JSON parsing helpers
- Json.h
- Json.cpp
-
- FileSystem.h
- FileSystem.cpp
-
- Exception.h
-
- # RW lock protected map
- RWStorage.h
-
- # A variable that has an implicit default value and keeps track of changes
- DefaultVariable.h
-
- # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
- QObjectPtr.h
-
- # Compression support
- GZip.h
- GZip.cpp
-
- # Command line parameter parsing
- Commandline.h
- Commandline.cpp
-
- # Version number string support
- Version.h
- Version.cpp
-
- # A Recursive file system watcher
- RecursiveFileSystemWatcher.h
- RecursiveFileSystemWatcher.cpp
-)
-
-add_unit_test(FileSystem
- SOURCES FileSystem_test.cpp
- LIBS MultiMC_logic
- DATA testdata
- )
-
-add_unit_test(GZip
- SOURCES GZip_test.cpp
- LIBS MultiMC_logic
- )
-
-set(PATHMATCHER_SOURCES
- # Path matchers
- pathmatcher/FSTreeMatcher.h
- pathmatcher/IPathMatcher.h
- pathmatcher/MultiMatcher.h
- pathmatcher/RegexpMatcher.h
-)
-
-set(NET_SOURCES
- # network stuffs
- net/ByteArraySink.h
- net/ChecksumValidator.h
- net/Download.cpp
- net/Download.h
- net/FileSink.cpp
- net/FileSink.h
- net/HttpMetaCache.cpp
- net/HttpMetaCache.h
- net/MetaCacheSink.cpp
- net/MetaCacheSink.h
- net/NetAction.h
- net/NetJob.cpp
- net/NetJob.h
- net/PasteUpload.cpp
- net/PasteUpload.h
- net/Sink.h
- net/Validator.h
-)
-
-# Game launch logic
-set(LAUNCH_SOURCES
- launch/steps/PostLaunchCommand.cpp
- launch/steps/PostLaunchCommand.h
- launch/steps/PreLaunchCommand.cpp
- launch/steps/PreLaunchCommand.h
- launch/steps/TextPrint.cpp
- launch/steps/TextPrint.h
- launch/steps/Update.cpp
- launch/steps/Update.h
- launch/LaunchStep.cpp
- launch/LaunchStep.h
- launch/LaunchTask.cpp
- launch/LaunchTask.h
- launch/LogModel.cpp
- launch/LogModel.h
-)
-
-# Old update system
-set(UPDATE_SOURCES
- updater/GoUpdate.h
- updater/GoUpdate.cpp
- updater/UpdateChecker.h
- updater/UpdateChecker.cpp
- updater/DownloadTask.h
- updater/DownloadTask.cpp
-)
-
-add_unit_test(UpdateChecker
- SOURCES updater/UpdateChecker_test.cpp
- LIBS MultiMC_logic
- DATA updater/testdata
- )
-
-add_unit_test(DownloadTask
- SOURCES updater/DownloadTask_test.cpp
- LIBS MultiMC_logic
- DATA updater/testdata
- )
-
-# Rarely used notifications
-set(NOTIFICATIONS_SOURCES
- # Notifications - short warning messages
- notifications/NotificationChecker.h
- notifications/NotificationChecker.cpp
-)
-
-# Backend for the news bar... there's usually no news.
-set(NEWS_SOURCES
- # News System
- news/NewsChecker.h
- news/NewsChecker.cpp
- news/NewsEntry.h
- news/NewsEntry.cpp
-)
-
-# Icon interface
-set(ICONS_SOURCES
- # Icons System and related code
- icons/IIconList.h
- icons/IIconList.cpp
- icons/IconUtils.h
- icons/IconUtils.cpp
-)
-
-# Minecraft services status checker
-set(STATUS_SOURCES
- # Status system
- status/StatusChecker.h
- status/StatusChecker.cpp
-)
-
-# Support for Minecraft instances and launch
-set(MINECRAFT_SOURCES
- # Minecraft support
- minecraft/auth/AuthSession.h
- minecraft/auth/AuthSession.cpp
- minecraft/auth/MojangAccountList.h
- minecraft/auth/MojangAccountList.cpp
- minecraft/auth/MojangAccount.h
- minecraft/auth/MojangAccount.cpp
- minecraft/auth/YggdrasilTask.h
- minecraft/auth/YggdrasilTask.cpp
- minecraft/auth/flows/AuthenticateTask.h
- minecraft/auth/flows/AuthenticateTask.cpp
- minecraft/auth/flows/RefreshTask.cpp
- minecraft/auth/flows/RefreshTask.cpp
- minecraft/auth/flows/ValidateTask.h
- minecraft/auth/flows/ValidateTask.cpp
-
- minecraft/gameoptions/GameOptions.h
- minecraft/gameoptions/GameOptions.cpp
-
- minecraft/update/AssetUpdateTask.h
- minecraft/update/AssetUpdateTask.cpp
- minecraft/update/FMLLibrariesTask.cpp
- minecraft/update/FMLLibrariesTask.h
- minecraft/update/FoldersTask.cpp
- minecraft/update/FoldersTask.h
- minecraft/update/LibrariesTask.cpp
- minecraft/update/LibrariesTask.h
-
- minecraft/launch/ClaimAccount.cpp
- minecraft/launch/ClaimAccount.h
- minecraft/launch/CreateGameFolders.cpp
- minecraft/launch/CreateGameFolders.h
- minecraft/launch/ModMinecraftJar.cpp
- minecraft/launch/ModMinecraftJar.h
- minecraft/launch/DirectJavaLaunch.cpp
- minecraft/launch/DirectJavaLaunch.h
- minecraft/launch/ExtractNatives.cpp
- minecraft/launch/ExtractNatives.h
- minecraft/launch/LauncherPartLaunch.cpp
- minecraft/launch/LauncherPartLaunch.h
- minecraft/launch/PrintInstanceInfo.cpp
- minecraft/launch/PrintInstanceInfo.h
- minecraft/launch/ReconstructAssets.cpp
- minecraft/launch/ReconstructAssets.h
- minecraft/launch/ScanModFolders.cpp
- minecraft/launch/ScanModFolders.h
-
- minecraft/legacy/LegacyModList.h
- minecraft/legacy/LegacyModList.cpp
- minecraft/legacy/LegacyInstance.h
- minecraft/legacy/LegacyInstance.cpp
- minecraft/legacy/LegacyUpgradeTask.h
- minecraft/legacy/LegacyUpgradeTask.cpp
-
- minecraft/GradleSpecifier.h
- minecraft/MinecraftInstance.cpp
- minecraft/MinecraftInstance.h
- minecraft/LaunchProfile.cpp
- minecraft/LaunchProfile.h
- minecraft/Component.cpp
- minecraft/Component.h
- minecraft/PackProfile.cpp
- minecraft/PackProfile.h
- minecraft/ComponentUpdateTask.cpp
- minecraft/ComponentUpdateTask.h
- minecraft/MinecraftLoadAndCheck.h
- minecraft/MinecraftLoadAndCheck.cpp
- minecraft/MinecraftUpdate.h
- minecraft/MinecraftUpdate.cpp
- minecraft/MojangVersionFormat.cpp
- minecraft/MojangVersionFormat.h
- minecraft/Rule.cpp
- minecraft/Rule.h
- minecraft/OneSixVersionFormat.cpp
- minecraft/OneSixVersionFormat.h
- minecraft/OpSys.cpp
- minecraft/OpSys.h
- minecraft/ParseUtils.cpp
- minecraft/ParseUtils.h
- minecraft/ProfileUtils.cpp
- minecraft/ProfileUtils.h
- minecraft/Library.cpp
- minecraft/Library.h
- minecraft/MojangDownloadInfo.h
- minecraft/VersionFile.cpp
- minecraft/VersionFile.h
- minecraft/VersionFilterData.h
- minecraft/VersionFilterData.cpp
- minecraft/World.h
- minecraft/World.cpp
- minecraft/WorldList.h
- minecraft/WorldList.cpp
-
- minecraft/mod/Mod.h
- minecraft/mod/Mod.cpp
- minecraft/mod/ModDetails.h
- minecraft/mod/ModFolderModel.h
- minecraft/mod/ModFolderModel.cpp
- minecraft/mod/ModFolderLoadTask.h
- minecraft/mod/ModFolderLoadTask.cpp
- minecraft/mod/LocalModParseTask.h
- minecraft/mod/LocalModParseTask.cpp
-
- # Assets
- minecraft/AssetsUtils.h
- minecraft/AssetsUtils.cpp
-
- # Minecraft services
- minecraft/services/SkinUpload.cpp
- minecraft/services/SkinUpload.h
- minecraft/services/SkinDelete.cpp
- minecraft/services/SkinDelete.h
-
- mojang/PackageManifest.h
- mojang/PackageManifest.cpp
- )
-
-add_unit_test(GradleSpecifier
- SOURCES minecraft/GradleSpecifier_test.cpp
- LIBS MultiMC_logic
- )
-
-add_executable(PackageManifest
- mojang/PackageManifest_test.cpp
-)
-target_link_libraries(PackageManifest
- MultiMC_logic
- Qt5::Test
-)
-target_include_directories(PackageManifest
- PRIVATE ../../cmake/UnitTest/
-)
-add_test(
- NAME PackageManifest
- COMMAND PackageManifest
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
-)
-
-add_unit_test(MojangVersionFormat
- SOURCES minecraft/MojangVersionFormat_test.cpp
- LIBS MultiMC_logic
- DATA minecraft/testdata
- )
-
-add_unit_test(Library
- SOURCES minecraft/Library_test.cpp
- LIBS MultiMC_logic
- )
-
-# FIXME: shares data with FileSystem test
-add_unit_test(ModFolderModel
- SOURCES minecraft/mod/ModFolderModel_test.cpp
- DATA testdata
- LIBS MultiMC_logic
- )
-
-add_unit_test(ParseUtils
- SOURCES minecraft/ParseUtils_test.cpp
- LIBS MultiMC_logic
- )
-
-# the screenshots feature
-set(SCREENSHOTS_SOURCES
- screenshots/Screenshot.h
- screenshots/ImgurUpload.h
- screenshots/ImgurUpload.cpp
- screenshots/ImgurAlbumCreation.h
- screenshots/ImgurAlbumCreation.cpp
-)
-
-set(TASKS_SOURCES
- # Tasks
- tasks/Task.h
- tasks/Task.cpp
- tasks/SequentialTask.h
- tasks/SequentialTask.cpp
-)
-
-set(SETTINGS_SOURCES
- # Settings
- settings/INIFile.cpp
- settings/INIFile.h
- settings/INISettingsObject.cpp
- settings/INISettingsObject.h
- settings/OverrideSetting.cpp
- settings/OverrideSetting.h
- settings/PassthroughSetting.cpp
- settings/PassthroughSetting.h
- settings/Setting.cpp
- settings/Setting.h
- settings/SettingsObject.cpp
- settings/SettingsObject.h
-)
-
-add_unit_test(INIFile
- SOURCES settings/INIFile_test.cpp
- LIBS MultiMC_logic
- )
-
-set(JAVA_SOURCES
- # Java related code
- java/launch/CheckJava.cpp
- java/launch/CheckJava.h
- java/JavaChecker.h
- java/JavaChecker.cpp
- java/JavaCheckerJob.h
- java/JavaCheckerJob.cpp
- java/JavaInstall.h
- java/JavaInstall.cpp
- java/JavaInstallList.h
- java/JavaInstallList.cpp
- java/JavaUtils.h
- java/JavaUtils.cpp
- java/JavaVersion.h
- java/JavaVersion.cpp
-)
-
-add_unit_test(JavaVersion
- SOURCES java/JavaVersion_test.cpp
- LIBS MultiMC_logic
- )
-
-set(TRANSLATIONS_SOURCES
- translations/TranslationsModel.h
- translations/TranslationsModel.cpp
- translations/POTranslator.h
- translations/POTranslator.cpp
-)
-
-set(TOOLS_SOURCES
- # Tools
- tools/BaseExternalTool.cpp
- tools/BaseExternalTool.h
- tools/BaseProfiler.cpp
- tools/BaseProfiler.h
- tools/JProfiler.cpp
- tools/JProfiler.h
- tools/JVisualVM.cpp
- tools/JVisualVM.h
- tools/MCEditTool.cpp
- tools/MCEditTool.h
-)
-
-set(META_SOURCES
- # Metadata sources
- meta/JsonFormat.cpp
- meta/JsonFormat.h
- meta/BaseEntity.cpp
- meta/BaseEntity.h
- meta/VersionList.cpp
- meta/VersionList.h
- meta/Version.cpp
- meta/Version.h
- meta/Index.cpp
- meta/Index.h
-)
-
-set(FTB_SOURCES
- modplatform/legacy_ftb/PackFetchTask.h
- modplatform/legacy_ftb/PackFetchTask.cpp
- modplatform/legacy_ftb/PackInstallTask.h
- modplatform/legacy_ftb/PackInstallTask.cpp
- modplatform/legacy_ftb/PrivatePackManager.h
- modplatform/legacy_ftb/PrivatePackManager.cpp
-
- modplatform/legacy_ftb/PackHelpers.h
-)
-
-set(FLAME_SOURCES
- # Flame
- modplatform/flame/PackManifest.h
- modplatform/flame/PackManifest.cpp
- modplatform/flame/FileResolvingTask.h
- modplatform/flame/FileResolvingTask.cpp
-)
-
-set(MODPACKSCH_SOURCES
- modplatform/modpacksch/FTBPackInstallTask.h
- modplatform/modpacksch/FTBPackInstallTask.cpp
- modplatform/modpacksch/FTBPackManifest.h
- modplatform/modpacksch/FTBPackManifest.cpp
-)
-
-set(TECHNIC_SOURCES
- modplatform/technic/SingleZipPackInstallTask.h
- modplatform/technic/SingleZipPackInstallTask.cpp
- modplatform/technic/SolderPackInstallTask.h
- modplatform/technic/SolderPackInstallTask.cpp
- modplatform/technic/TechnicPackProcessor.h
- modplatform/technic/TechnicPackProcessor.cpp
-)
-
-set(ATLAUNCHER_SOURCES
- modplatform/atlauncher/ATLPackIndex.cpp
- modplatform/atlauncher/ATLPackIndex.h
- modplatform/atlauncher/ATLPackInstallTask.cpp
- modplatform/atlauncher/ATLPackInstallTask.h
- modplatform/atlauncher/ATLPackManifest.cpp
- modplatform/atlauncher/ATLPackManifest.h
-)
-
-add_unit_test(Index
- SOURCES meta/Index_test.cpp
- LIBS MultiMC_logic
- )
-
-################################ COMPILE ################################
-
-# we need zlib
-find_package(ZLIB REQUIRED)
-
-set(LOGIC_SOURCES
- ${CORE_SOURCES}
- ${PATHMATCHER_SOURCES}
- ${NET_SOURCES}
- ${LAUNCH_SOURCES}
- ${UPDATE_SOURCES}
- ${NOTIFICATIONS_SOURCES}
- ${NEWS_SOURCES}
- ${STATUS_SOURCES}
- ${MINECRAFT_SOURCES}
- ${SCREENSHOTS_SOURCES}
- ${TASKS_SOURCES}
- ${SETTINGS_SOURCES}
- ${JAVA_SOURCES}
- ${TRANSLATIONS_SOURCES}
- ${TOOLS_SOURCES}
- ${META_SOURCES}
- ${ICONS_SOURCES}
- ${FTB_SOURCES}
- ${FLAME_SOURCES}
- ${MODPACKSCH_SOURCES}
- ${TECHNIC_SOURCES}
- ${ATLAUNCHER_SOURCES}
-)
-
-add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
-set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
-
-generate_export_header(MultiMC_logic)
-
-# Link
-target_link_libraries(MultiMC_logic systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES} optional-bare BuildConfig)
-target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
-
-# Mark and export headers
-target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
-
-# Install it
-install(
- TARGETS MultiMC_logic
- RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
- LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
-)
diff --git a/api/logic/MMCStrings.h b/api/logic/MMCStrings.h
deleted file mode 100644
index 493ba3d2fc..0000000000
--- a/api/logic/MMCStrings.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include
-
-#include "multimc_logic_export.h"
-
-namespace Strings
-{
- int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
-}
diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp
deleted file mode 100644
index f5853fe335..0000000000
--- a/api/logic/minecraft/auth/MojangAccount.cpp
+++ /dev/null
@@ -1,315 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Authors: Orochimarufan
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "MojangAccount.h"
-#include "flows/RefreshTask.h"
-#include "flows/AuthenticateTask.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
-{
- // The JSON object must at least have a username for it to be valid.
- if (!object.value("username").isString())
- {
- qCritical() << "Can't load Mojang account info from JSON object. Username field is "
- "missing or of the wrong type.";
- return nullptr;
- }
-
- QString username = object.value("username").toString("");
- QString clientToken = object.value("clientToken").toString("");
- QString accessToken = object.value("accessToken").toString("");
-
- QJsonArray profileArray = object.value("profiles").toArray();
- if (profileArray.size() < 1)
- {
- qCritical() << "Can't load Mojang account with username \"" << username
- << "\". No profiles found.";
- return nullptr;
- }
-
- QList profiles;
- for (QJsonValue profileVal : profileArray)
- {
- QJsonObject profileObject = profileVal.toObject();
- QString id = profileObject.value("id").toString("");
- QString name = profileObject.value("name").toString("");
- bool legacy = profileObject.value("legacy").toBool(false);
- if (id.isEmpty() || name.isEmpty())
- {
- qWarning() << "Unable to load a profile because it was missing an ID or a name.";
- continue;
- }
- profiles.append({id, name, legacy});
- }
-
- MojangAccountPtr account(new MojangAccount());
- if (object.value("user").isObject())
- {
- User u;
- QJsonObject userStructure = object.value("user").toObject();
- u.id = userStructure.value("id").toString();
- /*
- QJsonObject propMap = userStructure.value("properties").toObject();
- for(auto key: propMap.keys())
- {
- auto values = propMap.operator[](key).toArray();
- for(auto value: values)
- u.properties.insert(key, value.toString());
- }
- */
- account->m_user = u;
- }
- account->m_username = username;
- account->m_clientToken = clientToken;
- account->m_accessToken = accessToken;
- account->m_profiles = profiles;
-
- // Get the currently selected profile.
- QString currentProfile = object.value("activeProfile").toString("");
- if (!currentProfile.isEmpty())
- account->setCurrentProfile(currentProfile);
-
- return account;
-}
-
-MojangAccountPtr MojangAccount::createFromUsername(const QString &username)
-{
- MojangAccountPtr account(new MojangAccount());
- account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
- account->m_username = username;
- return account;
-}
-
-QJsonObject MojangAccount::saveToJson() const
-{
- QJsonObject json;
- json.insert("username", m_username);
- json.insert("clientToken", m_clientToken);
- json.insert("accessToken", m_accessToken);
-
- QJsonArray profileArray;
- for (AccountProfile profile : m_profiles)
- {
- QJsonObject profileObj;
- profileObj.insert("id", profile.id);
- profileObj.insert("name", profile.name);
- profileObj.insert("legacy", profile.legacy);
- profileArray.append(profileObj);
- }
- json.insert("profiles", profileArray);
-
- QJsonObject userStructure;
- {
- userStructure.insert("id", m_user.id);
- /*
- QJsonObject userAttrs;
- for(auto key: m_user.properties.keys())
- {
- auto array = QJsonArray::fromStringList(m_user.properties.values(key));
- userAttrs.insert(key, array);
- }
- userStructure.insert("properties", userAttrs);
- */
- }
- json.insert("user", userStructure);
-
- if (m_currentProfile != -1)
- json.insert("activeProfile", currentProfile()->id);
-
- return json;
-}
-
-bool MojangAccount::setCurrentProfile(const QString &profileId)
-{
- for (int i = 0; i < m_profiles.length(); i++)
- {
- if (m_profiles[i].id == profileId)
- {
- m_currentProfile = i;
- return true;
- }
- }
- return false;
-}
-
-const AccountProfile *MojangAccount::currentProfile() const
-{
- if (m_currentProfile == -1)
- return nullptr;
- return &m_profiles[m_currentProfile];
-}
-
-AccountStatus MojangAccount::accountStatus() const
-{
- if (m_accessToken.isEmpty())
- return NotVerified;
- else
- return Verified;
-}
-
-std::shared_ptr MojangAccount::login(AuthSessionPtr session, QString password)
-{
- Q_ASSERT(m_currentTask.get() == nullptr);
-
- // take care of the true offline status
- if (accountStatus() == NotVerified && password.isEmpty())
- {
- if (session)
- {
- session->status = AuthSession::RequiresPassword;
- fillSession(session);
- }
- return nullptr;
- }
-
- if(accountStatus() == Verified && !session->wants_online)
- {
- session->status = AuthSession::PlayableOffline;
- session->auth_server_online = false;
- fillSession(session);
- return nullptr;
- }
- else
- {
- if (password.isEmpty())
- {
- m_currentTask.reset(new RefreshTask(this));
- }
- else
- {
- m_currentTask.reset(new AuthenticateTask(this, password));
- }
- m_currentTask->assignSession(session);
-
- connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
- connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
- }
- return m_currentTask;
-}
-
-void MojangAccount::authSucceeded()
-{
- auto session = m_currentTask->getAssignedSession();
- if (session)
- {
- session->status =
- session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline;
- fillSession(session);
- session->auth_server_online = true;
- }
- m_currentTask.reset();
- emit changed();
-}
-
-void MojangAccount::authFailed(QString reason)
-{
- auto session = m_currentTask->getAssignedSession();
- // This is emitted when the yggdrasil tasks time out or are cancelled.
- // -> we treat the error as no-op
- if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT)
- {
- if (session)
- {
- session->status = accountStatus() == Verified ? AuthSession::PlayableOffline
- : AuthSession::RequiresPassword;
- session->auth_server_online = false;
- fillSession(session);
- }
- }
- else
- {
- m_accessToken = QString();
- emit changed();
- if (session)
- {
- session->status = AuthSession::RequiresPassword;
- session->auth_server_online = true;
- fillSession(session);
- }
- }
- m_currentTask.reset();
-}
-
-void MojangAccount::fillSession(AuthSessionPtr session)
-{
- // the user name. you have to have an user name
- session->username = m_username;
- // volatile auth token
- session->access_token = m_accessToken;
- // the semi-permanent client token
- session->client_token = m_clientToken;
- if (currentProfile())
- {
- // profile name
- session->player_name = currentProfile()->name;
- // profile ID
- session->uuid = currentProfile()->id;
- // 'legacy' or 'mojang', depending on account type
- session->user_type = currentProfile()->legacy ? "legacy" : "mojang";
- if (!session->access_token.isEmpty())
- {
- session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id;
- }
- else
- {
- session->session = "-";
- }
- }
- else
- {
- session->player_name = "Player";
- session->session = "-";
- }
- session->u = user();
- session->m_accountPtr = shared_from_this();
-}
-
-void MojangAccount::decrementUses()
-{
- Usable::decrementUses();
- if(!isInUse())
- {
- emit changed();
- qWarning() << "Account" << m_username << "is no longer in use.";
- }
-}
-
-void MojangAccount::incrementUses()
-{
- bool wasInUse = isInUse();
- Usable::incrementUses();
- if(!wasInUse)
- {
- emit changed();
- qWarning() << "Account" << m_username << "is now in use.";
- }
-}
-
-void MojangAccount::invalidateClientToken()
-{
- m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
- emit changed();
-}
diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h
deleted file mode 100644
index cc3a61a277..0000000000
--- a/api/logic/minecraft/auth/MojangAccountList.h
+++ /dev/null
@@ -1,201 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "MojangAccount.h"
-
-#include
-#include
-#include
-#include
-
-#include "multimc_logic_export.h"
-
-/*!
- * \brief List of available Mojang accounts.
- * This should be loaded in the background by MultiMC on startup.
- *
- * This class also inherits from QAbstractListModel. Methods from that
- * class determine how this list shows up in a list view. Said methods
- * all have a default implementation, but they can be overridden by subclasses to
- * change the behavior of the list.
- */
-class MULTIMC_LOGIC_EXPORT MojangAccountList : public QAbstractListModel
-{
- Q_OBJECT
-public:
- enum ModelRoles
- {
- PointerRole = 0x34B1CB48
- };
-
- enum VListColumns
- {
- // TODO: Add icon column.
-
- // First column - Active?
- ActiveColumn = 0,
-
- // Second column - Name
- NameColumn,
- };
-
- explicit MojangAccountList(QObject *parent = 0);
-
- //! Gets the account at the given index.
- virtual const MojangAccountPtr at(int i) const;
-
- //! Returns the number of accounts in the list.
- virtual int count() const;
-
- //////// List Model Functions ////////
- virtual QVariant data(const QModelIndex &index, int role) const;
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
- virtual int rowCount(const QModelIndex &parent) const;
- virtual int columnCount(const QModelIndex &parent) const;
- virtual Qt::ItemFlags flags(const QModelIndex &index) const;
- virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
-
- /*!
- * Adds a the given Mojang account to the account list.
- */
- virtual void addAccount(const MojangAccountPtr account);
-
- /*!
- * Removes the mojang account with the given username from the account list.
- */
- virtual void removeAccount(const QString &username);
-
- /*!
- * Removes the account at the given QModelIndex.
- */
- virtual void removeAccount(QModelIndex index);
-
- /*!
- * \brief Finds an account by its username.
- * \param The username of the account to find.
- * \return A const pointer to the account with the given username. NULL if
- * one doesn't exist.
- */
- virtual MojangAccountPtr findAccount(const QString &username) const;
-
- /*!
- * Sets the default path to save the list file to.
- * If autosave is true, this list will automatically save to the given path whenever it changes.
- * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately
- * after calling this function to ensure an autosaved change doesn't overwrite the list you intended
- * to load.
- */
- virtual void setListFilePath(QString path, bool autosave = false);
-
- /*!
- * \brief Loads the account list from the given file path.
- * If the given file is an empty string (default), will load from the default account list file.
- * \return True if successful, otherwise false.
- */
- virtual bool loadList(const QString &file = "");
-
- /*!
- * \brief Saves the account list to the given file.
- * If the given file is an empty string (default), will save from the default account list file.
- * \return True if successful, otherwise false.
- */
- virtual bool saveList(const QString &file = "");
-
- /*!
- * \brief Gets a pointer to the account that the user has selected as their "active" account.
- * Which account is active can be overridden on a per-instance basis, but this will return the one that
- * is set as active globally.
- * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer.
- */
- virtual MojangAccountPtr activeAccount() const;
-
- /*!
- * Sets the given account as the current active account.
- * If the username given is an empty string, sets the active account to nothing.
- */
- virtual void setActiveAccount(const QString &username);
-
- /*!
- * Returns true if any of the account is at least Validated
- */
- bool anyAccountIsValid();
-
-signals:
- /*!
- * Signal emitted to indicate that the account list has changed.
- * This will also fire if the value of an element in the list changes (will be implemented
- * later).
- */
- void listChanged();
-
- /*!
- * Signal emitted to indicate that the active account has changed.
- */
- void activeAccountChanged();
-
-public
-slots:
- /**
- * This is called when one of the accounts changes and the list needs to be updated
- */
- void accountChanged();
-
-protected:
- /*!
- * Called whenever the list changes.
- * This emits the listChanged() signal and autosaves the list (if autosave is enabled).
- */
- void onListChanged();
-
- /*!
- * Called whenever the active account changes.
- * Emits the activeAccountChanged() signal and autosaves the list if enabled.
- */
- void onActiveChanged();
-
- QList m_accounts;
-
- /*!
- * Account that is currently active.
- */
- MojangAccountPtr m_activeAccount;
-
- //! Path to the account list file. Empty string if there isn't one.
- QString m_listFilePath;
-
- /*!
- * If true, the account list will automatically save to the account list path when it changes.
- * Ignored if m_listFilePath is blank.
- */
- bool m_autosave = false;
-
-protected
-slots:
- /*!
- * Updates this list with the given list of accounts.
- * This is done by copying each account in the given list and inserting it
- * into this one.
- * We need to do this so that we can set the parents of the accounts are set to this
- * account list. This can't be done in the load task, because the accounts the load
- * task creates are on the load task's thread and Qt won't allow their parents
- * to be set to something created on another thread.
- * To get around that problem, we invoke this method on the GUI thread, which
- * then copies the accounts and sets their parents correctly.
- * \param accounts List of accounts whose parents should be set.
- */
- virtual void updateListData(QList versions);
-};
diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp
deleted file mode 100644
index 0857b46bd3..0000000000
--- a/api/logic/minecraft/auth/YggdrasilTask.cpp
+++ /dev/null
@@ -1,255 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "YggdrasilTask.h"
-#include "MojangAccount.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-#include
-
-#include
-
-YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent)
- : Task(parent), m_account(account)
-{
- changeState(STATE_CREATED);
-}
-
-void YggdrasilTask::executeTask()
-{
- changeState(STATE_SENDING_REQUEST);
-
- // Get the content of the request we're going to send to the server.
- QJsonDocument doc(getRequestContent());
-
- QUrl reqUrl(BuildConfig.AUTH_BASE + getEndpoint());
- QNetworkRequest netRequest(reqUrl);
- netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
-
- QByteArray requestData = doc.toJson();
- m_netReply = ENV.qnam().post(netRequest, requestData);
- connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
- connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
- connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
- connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors);
- timeout_keeper.setSingleShot(true);
- timeout_keeper.start(timeout_max);
- counter.setSingleShot(false);
- counter.start(time_step);
- progress(0, timeout_max);
- connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout);
- connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat);
-}
-
-void YggdrasilTask::refreshTimers(qint64, qint64)
-{
- timeout_keeper.stop();
- timeout_keeper.start(timeout_max);
- progress(count = 0, timeout_max);
-}
-void YggdrasilTask::heartbeat()
-{
- count += time_step;
- progress(count, timeout_max);
-}
-
-bool YggdrasilTask::abort()
-{
- progress(timeout_max, timeout_max);
- // TODO: actually use this in a meaningful way
- m_aborted = YggdrasilTask::BY_USER;
- m_netReply->abort();
- return true;
-}
-
-void YggdrasilTask::abortByTimeout()
-{
- progress(timeout_max, timeout_max);
- // TODO: actually use this in a meaningful way
- m_aborted = YggdrasilTask::BY_TIMEOUT;
- m_netReply->abort();
-}
-
-void YggdrasilTask::sslErrors(QList errors)
-{
- int i = 1;
- for (auto error : errors)
- {
- qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
- auto cert = error.certificate();
- qCritical() << "Certificate in question:\n" << cert.toText();
- i++;
- }
-}
-
-void YggdrasilTask::processReply()
-{
- changeState(STATE_PROCESSING_RESPONSE);
-
- switch (m_netReply->error())
- {
- case QNetworkReply::NoError:
- break;
- case QNetworkReply::TimeoutError:
- changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out."));
- return;
- case QNetworkReply::OperationCanceledError:
- changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
- return;
- case QNetworkReply::SslHandshakeFailedError:
- changeState(
- STATE_FAILED_SOFT,
- tr("SSL Handshake failed. There might be a few causes for it: "
- ""
- "You use Windows XP and need to update "
- "your root certificates "
- "Some device on your network is interfering with SSL traffic. In that case, "
- "you have bigger worries than Minecraft not starting. "
- "Possibly something else. Check the MultiMC log file for details "
- " "));
- return;
- // used for invalid credentials and similar errors. Fall through.
- case QNetworkReply::ContentAccessDenied:
- case QNetworkReply::ContentOperationNotPermittedError:
- break;
- default:
- changeState(STATE_FAILED_SOFT,
- tr("Authentication operation failed due to a network error: %1 (%2)")
- .arg(m_netReply->errorString()).arg(m_netReply->error()));
- return;
- }
-
- // Try to parse the response regardless of the response code.
- // Sometimes the auth server will give more information and an error code.
- QJsonParseError jsonError;
- QByteArray replyData = m_netReply->readAll();
- QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
- // Check the response code.
- int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (responseCode == 200)
- {
- // If the response code was 200, then there shouldn't be an error. Make sure
- // anyways.
- // Also, sometimes an empty reply indicates success. If there was no data received,
- // pass an empty json object to the processResponse function.
- if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
- {
- processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
- return;
- }
- else
- {
- changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response "
- "JSON response: %1 at offset %2.")
- .arg(jsonError.errorString())
- .arg(jsonError.offset));
- qCritical() << replyData;
- }
- return;
- }
-
- // If the response code was not 200, then Yggdrasil may have given us information
- // about the error.
- // If we can parse the response, then get information from it. Otherwise just say
- // there was an unknown error.
- if (jsonError.error == QJsonParseError::NoError)
- {
- // We were able to parse the server's response. Woo!
- // Call processError. If a subclass has overridden it then they'll handle their
- // stuff there.
- qDebug() << "The request failed, but the server gave us an error message. "
- "Processing error.";
- processError(doc.object());
- }
- else
- {
- // The server didn't say anything regarding the error. Give the user an unknown
- // error.
- qDebug()
- << "The request failed and the server gave no error message. Unknown error.";
- changeState(STATE_FAILED_SOFT,
- tr("An unknown error occurred when trying to communicate with the "
- "authentication server: %1").arg(m_netReply->errorString()));
- }
-}
-
-void YggdrasilTask::processError(QJsonObject responseData)
-{
- QJsonValue errorVal = responseData.value("error");
- QJsonValue errorMessageValue = responseData.value("errorMessage");
- QJsonValue causeVal = responseData.value("cause");
-
- if (errorVal.isString() && errorMessageValue.isString())
- {
- m_error = std::shared_ptr(new Error{
- errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")});
- changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
- }
- else
- {
- // Error is not in standard format. Don't set m_error and return unknown error.
- changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
- }
-}
-
-QString YggdrasilTask::getStateMessage() const
-{
- switch (m_state)
- {
- case STATE_CREATED:
- return "Waiting...";
- case STATE_SENDING_REQUEST:
- return tr("Sending request to auth servers...");
- case STATE_PROCESSING_RESPONSE:
- return tr("Processing response from servers...");
- case STATE_SUCCEEDED:
- return tr("Authentication task succeeded.");
- case STATE_FAILED_SOFT:
- return tr("Failed to contact the authentication server.");
- case STATE_FAILED_HARD:
- return tr("Failed to authenticate.");
- default:
- return tr("...");
- }
-}
-
-void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason)
-{
- m_state = newState;
- setStatus(getStateMessage());
- if (newState == STATE_SUCCEEDED)
- {
- emitSucceeded();
- }
- else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT)
- {
- emitFailed(reason);
- }
-}
-
-YggdrasilTask::State YggdrasilTask::state()
-{
- return m_state;
-}
diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
deleted file mode 100644
index 2e8dc8595b..0000000000
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "AuthenticateTask.h"
-#include "../MojangAccount.h"
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-
-AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password,
- QObject *parent)
- : YggdrasilTask(account, parent), m_password(password)
-{
-}
-
-QJsonObject AuthenticateTask::getRequestContent() const
-{
- /*
- * {
- * "agent": { // optional
- * "name": "Minecraft", // So far this is the only encountered value
- * "version": 1 // This number might be increased
- * // by the vanilla client in the future
- * },
- * "username": "mojang account name", // Can be an email address or player name for
- // unmigrated accounts
- * "password": "mojang account password",
- * "clientToken": "client identifier" // optional
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
-
- {
- QJsonObject agent;
- // C++ makes string literals void* for some stupid reason, so we have to tell it
- // QString... Thanks Obama.
- agent.insert("name", QString("Minecraft"));
- agent.insert("version", 1);
- req.insert("agent", agent);
- }
-
- req.insert("username", m_account->username());
- req.insert("password", m_password);
- req.insert("requestUser", true);
-
- // If we already have a client token, give it to the server.
- // Otherwise, let the server give us one.
-
- if(m_account->m_clientToken.isEmpty())
- {
- auto uuid = QUuid::createUuid();
- auto uuidString = uuid.toString().remove('{').remove('-').remove('}');
- m_account->m_clientToken = uuidString;
- }
- req.insert("clientToken", m_account->m_clientToken);
-
- return req;
-}
-
-void AuthenticateTask::processResponse(QJsonObject responseData)
-{
- // Read the response data. We need to get the client token, access token, and the selected
- // profile.
- qDebug() << "Processing authentication response.";
- // qDebug() << responseData;
- // If we already have a client token, make sure the one the server gave us matches our
- // existing one.
- qDebug() << "Getting client token.";
- QString clientToken = responseData.value("clientToken").toString("");
- if (clientToken.isEmpty())
- {
- // Fail if the server gave us an empty client token
- changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
- return;
- }
- if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
- {
- changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
- return;
- }
- // Set the client token.
- m_account->m_clientToken = clientToken;
-
- // Now, we set the access token.
- qDebug() << "Getting access token.";
- QString accessToken = responseData.value("accessToken").toString("");
- if (accessToken.isEmpty())
- {
- // Fail if the server didn't give us an access token.
- changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
- return;
- }
- // Set the access token.
- m_account->m_accessToken = accessToken;
-
- // Now we load the list of available profiles.
- // Mojang hasn't yet implemented the profile system,
- // but we might as well support what's there so we
- // don't have trouble implementing it later.
- qDebug() << "Loading profile list.";
- QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
- QList loadedProfiles;
- for (auto iter : availableProfiles)
- {
- QJsonObject profile = iter.toObject();
- // Profiles are easy, we just need their ID and name.
- QString id = profile.value("id").toString("");
- QString name = profile.value("name").toString("");
- bool legacy = profile.value("legacy").toBool(false);
-
- if (id.isEmpty() || name.isEmpty())
- {
- // This should never happen, but we might as well
- // warn about it if it does so we can debug it easily.
- // You never know when Mojang might do something truly derpy.
- qWarning() << "Found entry in available profiles list with missing ID or name "
- "field. Ignoring it.";
- }
-
- // Now, add a new AccountProfile entry to the list.
- loadedProfiles.append({id, name, legacy});
- }
- // Put the list of profiles we loaded into the MojangAccount object.
- m_account->m_profiles = loadedProfiles;
-
- // Finally, we set the current profile to the correct value. This is pretty simple.
- // We do need to make sure that the current profile that the server gave us
- // is actually in the available profiles list.
- // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know).
- qDebug() << "Setting current profile.";
- QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
- QString currentProfileId = currentProfile.value("id").toString("");
- if (currentProfileId.isEmpty())
- {
- changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium."));
- return;
- }
- if (!m_account->setCurrentProfile(currentProfileId))
- {
- changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list."));
- return;
- }
-
- // this is what the vanilla launcher passes to the userProperties launch param
- if (responseData.contains("user"))
- {
- User u;
- auto obj = responseData.value("user").toObject();
- u.id = obj.value("id").toString();
- auto propArray = obj.value("properties").toArray();
- for (auto prop : propArray)
- {
- auto propTuple = prop.toObject();
- auto name = propTuple.value("name").toString();
- auto value = propTuple.value("value").toString();
- u.properties.insert(name, value);
- }
- m_account->m_user = u;
- }
-
- // We've made it through the minefield of possible errors. Return true to indicate that
- // we've succeeded.
- qDebug() << "Finished reading authentication response.";
- changeState(STATE_SUCCEEDED);
-}
-
-QString AuthenticateTask::getEndpoint() const
-{
- return "authenticate";
-}
-
-QString AuthenticateTask::getStateMessage() const
-{
- switch (m_state)
- {
- case STATE_SENDING_REQUEST:
- return tr("Authenticating: Sending request...");
- case STATE_PROCESSING_RESPONSE:
- return tr("Authenticating: Processing response...");
- default:
- return YggdrasilTask::getStateMessage();
- }
-}
diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h
deleted file mode 100644
index 4c14eec7a9..0000000000
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "../YggdrasilTask.h"
-
-#include
-#include
-#include
-
-/**
- * The authenticate task takes a MojangAccount with no access token and password and attempts to
- * authenticate with Mojang's servers.
- * If successful, it will set the MojangAccount's access token.
- */
-class AuthenticateTask : public YggdrasilTask
-{
- Q_OBJECT
-public:
- AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0);
-
-protected:
- virtual QJsonObject getRequestContent() const override;
-
- virtual QString getEndpoint() const override;
-
- virtual void processResponse(QJsonObject responseData) override;
-
- virtual QString getStateMessage() const override;
-
-private:
- QString m_password;
-};
diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp
deleted file mode 100644
index ecba178da5..0000000000
--- a/api/logic/minecraft/auth/flows/RefreshTask.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "RefreshTask.h"
-#include "../MojangAccount.h"
-
-#include
-#include
-#include
-#include
-
-#include
-
-RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account)
-{
-}
-
-QJsonObject RefreshTask::getRequestContent() const
-{
- /*
- * {
- * "clientToken": "client identifier"
- * "accessToken": "current access token to be refreshed"
- * "selectedProfile": // specifying this causes errors
- * {
- * "id": "profile ID"
- * "name": "profile name"
- * }
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
- req.insert("clientToken", m_account->m_clientToken);
- req.insert("accessToken", m_account->m_accessToken);
- /*
- {
- auto currentProfile = m_account->currentProfile();
- QJsonObject profile;
- profile.insert("id", currentProfile->id());
- profile.insert("name", currentProfile->name());
- req.insert("selectedProfile", profile);
- }
- */
- req.insert("requestUser", true);
-
- return req;
-}
-
-void RefreshTask::processResponse(QJsonObject responseData)
-{
- // Read the response data. We need to get the client token, access token, and the selected
- // profile.
- qDebug() << "Processing authentication response.";
-
- // qDebug() << responseData;
- // If we already have a client token, make sure the one the server gave us matches our
- // existing one.
- QString clientToken = responseData.value("clientToken").toString("");
- if (clientToken.isEmpty())
- {
- // Fail if the server gave us an empty client token
- changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
- return;
- }
- if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
- {
- changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
- return;
- }
-
- // Now, we set the access token.
- qDebug() << "Getting new access token.";
- QString accessToken = responseData.value("accessToken").toString("");
- if (accessToken.isEmpty())
- {
- // Fail if the server didn't give us an access token.
- changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
- return;
- }
-
- // we validate that the server responded right. (our current profile = returned current
- // profile)
- QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
- QString currentProfileId = currentProfile.value("id").toString("");
- if (m_account->currentProfile()->id != currentProfileId)
- {
- changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected."));
- return;
- }
-
- // this is what the vanilla launcher passes to the userProperties launch param
- if (responseData.contains("user"))
- {
- User u;
- auto obj = responseData.value("user").toObject();
- u.id = obj.value("id").toString();
- auto propArray = obj.value("properties").toArray();
- for (auto prop : propArray)
- {
- auto propTuple = prop.toObject();
- auto name = propTuple.value("name").toString();
- auto value = propTuple.value("value").toString();
- u.properties.insert(name, value);
- }
- m_account->m_user = u;
- }
-
- // We've made it through the minefield of possible errors. Return true to indicate that
- // we've succeeded.
- qDebug() << "Finished reading refresh response.";
- // Reset the access token.
- m_account->m_accessToken = accessToken;
- changeState(STATE_SUCCEEDED);
-}
-
-QString RefreshTask::getEndpoint() const
-{
- return "refresh";
-}
-
-QString RefreshTask::getStateMessage() const
-{
- switch (m_state)
- {
- case STATE_SENDING_REQUEST:
- return tr("Refreshing login token...");
- case STATE_PROCESSING_RESPONSE:
- return tr("Refreshing login token: Processing response...");
- default:
- return YggdrasilTask::getStateMessage();
- }
-}
diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h
deleted file mode 100644
index f0840ddaf3..0000000000
--- a/api/logic/minecraft/auth/flows/RefreshTask.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "../YggdrasilTask.h"
-
-#include
-#include
-#include
-
-/**
- * The authenticate task takes a MojangAccount with a possibly timed-out access token
- * and attempts to authenticate with Mojang's servers.
- * If successful, it will set the new access token. The token is considered validated.
- */
-class RefreshTask : public YggdrasilTask
-{
- Q_OBJECT
-public:
- RefreshTask(MojangAccount * account);
-
-protected:
- virtual QJsonObject getRequestContent() const override;
-
- virtual QString getEndpoint() const override;
-
- virtual void processResponse(QJsonObject responseData) override;
-
- virtual QString getStateMessage() const override;
-};
-
diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp
deleted file mode 100644
index 6b3f0a65ca..0000000000
--- a/api/logic/minecraft/auth/flows/ValidateTask.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ValidateTask.h"
-#include "../MojangAccount.h"
-
-#include
-#include
-#include
-#include
-
-#include
-
-ValidateTask::ValidateTask(MojangAccount * account, QObject *parent)
- : YggdrasilTask(account, parent)
-{
-}
-
-QJsonObject ValidateTask::getRequestContent() const
-{
- QJsonObject req;
- req.insert("accessToken", m_account->m_accessToken);
- return req;
-}
-
-void ValidateTask::processResponse(QJsonObject responseData)
-{
- // Assume that if processError wasn't called, then the request was successful.
- changeState(YggdrasilTask::STATE_SUCCEEDED);
-}
-
-QString ValidateTask::getEndpoint() const
-{
- return "validate";
-}
-
-QString ValidateTask::getStateMessage() const
-{
- switch (m_state)
- {
- case YggdrasilTask::STATE_SENDING_REQUEST:
- return tr("Validating access token: Sending request...");
- case YggdrasilTask::STATE_PROCESSING_RESPONSE:
- return tr("Validating access token: Processing response...");
- default:
- return YggdrasilTask::getStateMessage();
- }
-}
diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt
deleted file mode 100644
index a81327e340..0000000000
--- a/application/CMakeLists.txt
+++ /dev/null
@@ -1,414 +0,0 @@
-project(application)
-
-################################ FILES ################################
-
-######## Sources and headers ########
-SET(MULTIMC_SOURCES
- # Application base
- main.cpp
- MultiMC.h
- MultiMC.cpp
- UpdateController.cpp
- UpdateController.h
-
- # GUI - general utilities
- GuiUtil.h
- GuiUtil.cpp
- ColumnResizer.h
- ColumnResizer.cpp
- InstanceProxyModel.h
- InstanceProxyModel.cpp
- VersionProxyModel.h
- VersionProxyModel.cpp
- ColorCache.h
- ColorCache.cpp
- HoeDown.h
-
- # Super secret!
- KonamiCode.h
- KonamiCode.cpp
-
- # GUI - windows
- MainWindow.h
- MainWindow.cpp
- InstanceWindow.h
- InstanceWindow.cpp
-
- # GUI - setup wizard
- setupwizard/SetupWizard.h
- setupwizard/SetupWizard.cpp
- setupwizard/AnalyticsWizardPage.cpp
- setupwizard/AnalyticsWizardPage.h
- setupwizard/BaseWizardPage.h
- setupwizard/JavaWizardPage.cpp
- setupwizard/JavaWizardPage.h
- setupwizard/LanguageWizardPage.cpp
- setupwizard/LanguageWizardPage.h
-
- # GUI - themes
- themes/FusionTheme.cpp
- themes/FusionTheme.h
- themes/BrightTheme.cpp
- themes/BrightTheme.h
- themes/CustomTheme.cpp
- themes/CustomTheme.h
- themes/DarkTheme.cpp
- themes/DarkTheme.h
- themes/ITheme.cpp
- themes/ITheme.h
- themes/SystemTheme.cpp
- themes/SystemTheme.h
-
- # Processes
- LaunchController.h
- LaunchController.cpp
-
- # page provider for instances
- InstancePageProvider.h
-
- # Common java checking UI
- JavaCommon.h
- JavaCommon.cpp
-
- # GUI - paged dialog base
- pages/BasePage.h
- pages/BasePageContainer.h
- pages/BasePageProvider.h
-
- # GUI - instance pages
- pages/instance/GameOptionsPage.cpp
- pages/instance/GameOptionsPage.h
- pages/instance/VersionPage.cpp
- pages/instance/VersionPage.h
- pages/instance/TexturePackPage.h
- pages/instance/ResourcePackPage.h
- pages/instance/ModFolderPage.cpp
- pages/instance/ModFolderPage.h
- pages/instance/NotesPage.cpp
- pages/instance/NotesPage.h
- pages/instance/LogPage.cpp
- pages/instance/LogPage.h
- pages/instance/InstanceSettingsPage.cpp
- pages/instance/InstanceSettingsPage.h
- pages/instance/ScreenshotsPage.cpp
- pages/instance/ScreenshotsPage.h
- pages/instance/OtherLogsPage.cpp
- pages/instance/OtherLogsPage.h
- pages/instance/ServersPage.cpp
- pages/instance/ServersPage.h
- pages/instance/LegacyUpgradePage.cpp
- pages/instance/LegacyUpgradePage.h
- pages/instance/WorldListPage.cpp
- pages/instance/WorldListPage.h
-
- # GUI - global settings pages
- pages/global/AccountListPage.cpp
- pages/global/AccountListPage.h
- pages/global/CustomCommandsPage.cpp
- pages/global/CustomCommandsPage.h
- pages/global/ExternalToolsPage.cpp
- pages/global/ExternalToolsPage.h
- pages/global/JavaPage.cpp
- pages/global/JavaPage.h
- pages/global/LanguagePage.cpp
- pages/global/LanguagePage.h
- pages/global/MinecraftPage.cpp
- pages/global/MinecraftPage.h
- pages/global/MultiMCPage.cpp
- pages/global/MultiMCPage.h
- pages/global/ProxyPage.cpp
- pages/global/ProxyPage.h
- pages/global/PasteEEPage.cpp
- pages/global/PasteEEPage.h
-
- # GUI - platform pages
- pages/modplatform/VanillaPage.cpp
- pages/modplatform/VanillaPage.h
-
- pages/modplatform/atlauncher/AtlModel.cpp
- pages/modplatform/atlauncher/AtlModel.h
- pages/modplatform/atlauncher/AtlFilterModel.cpp
- pages/modplatform/atlauncher/AtlFilterModel.h
- pages/modplatform/atlauncher/AtlPage.cpp
- pages/modplatform/atlauncher/AtlPage.h
- pages/modplatform/atlauncher/AtlPage.h
-
- pages/modplatform/ftb/FtbFilterModel.cpp
- pages/modplatform/ftb/FtbFilterModel.h
- pages/modplatform/ftb/FtbListModel.cpp
- pages/modplatform/ftb/FtbListModel.h
- pages/modplatform/ftb/FtbPage.cpp
- pages/modplatform/ftb/FtbPage.h
-
- pages/modplatform/legacy_ftb/Page.cpp
- pages/modplatform/legacy_ftb/Page.h
- pages/modplatform/legacy_ftb/ListModel.h
- pages/modplatform/legacy_ftb/ListModel.cpp
-
- pages/modplatform/twitch/TwitchData.h
- pages/modplatform/twitch/TwitchModel.cpp
- pages/modplatform/twitch/TwitchModel.h
- pages/modplatform/twitch/TwitchPage.cpp
- pages/modplatform/twitch/TwitchPage.h
-
- pages/modplatform/technic/TechnicModel.cpp
- pages/modplatform/technic/TechnicModel.h
- pages/modplatform/technic/TechnicPage.cpp
- pages/modplatform/technic/TechnicPage.h
-
- pages/modplatform/ImportPage.cpp
- pages/modplatform/ImportPage.h
-
- # GUI - dialogs
- dialogs/AboutDialog.cpp
- dialogs/AboutDialog.h
- dialogs/ProfileSelectDialog.cpp
- dialogs/ProfileSelectDialog.h
- dialogs/CopyInstanceDialog.cpp
- dialogs/CopyInstanceDialog.h
- dialogs/CustomMessageBox.cpp
- dialogs/CustomMessageBox.h
- dialogs/EditAccountDialog.cpp
- dialogs/EditAccountDialog.h
- dialogs/ExportInstanceDialog.cpp
- dialogs/ExportInstanceDialog.h
- dialogs/IconPickerDialog.cpp
- dialogs/IconPickerDialog.h
- dialogs/LoginDialog.cpp
- dialogs/LoginDialog.h
- dialogs/NewComponentDialog.cpp
- dialogs/NewComponentDialog.h
- dialogs/NewInstanceDialog.cpp
- dialogs/NewInstanceDialog.h
- dialogs/NotificationDialog.cpp
- dialogs/NotificationDialog.h
- pagedialog/PageDialog.cpp
- pagedialog/PageDialog.h
- dialogs/ProgressDialog.cpp
- dialogs/ProgressDialog.h
- dialogs/UpdateDialog.cpp
- dialogs/UpdateDialog.h
- dialogs/VersionSelectDialog.cpp
- dialogs/VersionSelectDialog.h
- dialogs/SkinUploadDialog.cpp
- dialogs/SkinUploadDialog.h
-
-
- # GUI - widgets
- widgets/Common.cpp
- widgets/Common.h
- widgets/CustomCommands.cpp
- widgets/CustomCommands.h
- widgets/DropLabel.cpp
- widgets/DropLabel.h
- widgets/FocusLineEdit.cpp
- widgets/FocusLineEdit.h
- widgets/IconLabel.cpp
- widgets/IconLabel.h
- widgets/JavaSettingsWidget.cpp
- widgets/JavaSettingsWidget.h
- widgets/LabeledToolButton.cpp
- widgets/LabeledToolButton.h
- widgets/LanguageSelectionWidget.cpp
- widgets/LanguageSelectionWidget.h
- widgets/LineSeparator.cpp
- widgets/LineSeparator.h
- widgets/LogView.cpp
- widgets/LogView.h
- widgets/MCModInfoFrame.cpp
- widgets/MCModInfoFrame.h
- widgets/ModListView.cpp
- widgets/ModListView.h
- widgets/PageContainer.cpp
- widgets/PageContainer.h
- widgets/PageContainer_p.h
- widgets/ServerStatus.cpp
- widgets/ServerStatus.h
- widgets/VersionListView.cpp
- widgets/VersionListView.h
- widgets/VersionSelectWidget.cpp
- widgets/VersionSelectWidget.h
- widgets/ProgressWidget.h
- widgets/ProgressWidget.cpp
- widgets/WideBar.h
- widgets/WideBar.cpp
-
- # GUI - instance group view
- groupview/GroupedProxyModel.cpp
- groupview/GroupedProxyModel.h
- groupview/AccessibleGroupView.cpp
- groupview/AccessibleGroupView.h
- groupview/AccessibleGroupView_p.h
- groupview/GroupView.cpp
- groupview/GroupView.h
- groupview/InstanceDelegate.cpp
- groupview/InstanceDelegate.h
- groupview/VisualGroup.cpp
- groupview/VisualGroup.h
- )
-
-######## UIs ########
-SET(MULTIMC_UIS
- # Instance pages
- pages/instance/GameOptionsPage.ui
- pages/instance/VersionPage.ui
- pages/instance/ModFolderPage.ui
- pages/instance/LogPage.ui
- pages/instance/InstanceSettingsPage.ui
- pages/instance/NotesPage.ui
- pages/instance/ScreenshotsPage.ui
- pages/instance/OtherLogsPage.ui
- pages/instance/LegacyUpgradePage.ui
- pages/instance/ServersPage.ui
- pages/instance/WorldListPage.ui
-
- # Global settings pages
- pages/global/AccountListPage.ui
- pages/global/ExternalToolsPage.ui
- pages/global/JavaPage.ui
- pages/global/MinecraftPage.ui
- pages/global/MultiMCPage.ui
- pages/global/ProxyPage.ui
- pages/global/PasteEEPage.ui
-
- # Platform pages
- pages/modplatform/VanillaPage.ui
- pages/modplatform/atlauncher/AtlPage.ui
- pages/modplatform/ftb/FtbPage.ui
- pages/modplatform/legacy_ftb/Page.ui
- pages/modplatform/twitch/TwitchPage.ui
- pages/modplatform/technic/TechnicPage.ui
- pages/modplatform/ImportPage.ui
-
- # Dialogs
- dialogs/CopyInstanceDialog.ui
- dialogs/NewComponentDialog.ui
- dialogs/NewInstanceDialog.ui
- dialogs/AboutDialog.ui
- dialogs/ProgressDialog.ui
- dialogs/IconPickerDialog.ui
- dialogs/ProfileSelectDialog.ui
- dialogs/EditAccountDialog.ui
- dialogs/ExportInstanceDialog.ui
- dialogs/LoginDialog.ui
- dialogs/UpdateDialog.ui
- dialogs/NotificationDialog.ui
- dialogs/SkinUploadDialog.ui
-
- # Widgets/other
- widgets/CustomCommands.ui
- widgets/MCModInfoFrame.ui
-)
-
-set(MULTIMC_QRCS
- resources/backgrounds/backgrounds.qrc
- resources/multimc/multimc.qrc
- resources/pe_dark/pe_dark.qrc
- resources/pe_light/pe_light.qrc
- resources/pe_colored/pe_colored.qrc
- resources/pe_blue/pe_blue.qrc
- resources/OSX/OSX.qrc
- resources/iOS/iOS.qrc
- resources/flat/flat.qrc
- resources/documents/documents.qrc
-)
-
-######## Windows resource files ########
-if(WIN32)
- set(MULTIMC_RCS resources/multimc.rc)
-endif()
-
-# Qt 5 stuff
-qt5_wrap_ui(MULTIMC_UI ${MULTIMC_UIS})
-qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
-
-# Add executable
-add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES} ${MULTIMC_RCS})
-target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer ganalytics)
-if(DEFINED MultiMC_APP_BINARY_NAME)
- set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}")
-endif()
-if(DEFINED MultiMC_BINARY_RPATH)
- SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}")
-endif()
-if(DEFINED MultiMC_APP_BINARY_DEFS)
- target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS})
-endif()
-
-install(TARGETS MultiMC
- BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime
- LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
- RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
-)
-
-#### The MultiMC bundle mess! ####
-# Bundle utilities are used to complete the portable packages - they add all the libraries that would otherwise be missing on the target system.
-# NOTE: it seems that this absolutely has to be here, and nowhere else.
-if(INSTALL_BUNDLE STREQUAL "full")
- # Add qt.conf - this makes Qt stop looking for things outside the bundle
- install(
- CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")"
- COMPONENT Runtime
- )
- # Bundle plugins
- if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
- # Image formats
- install(
- DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
- DESTINATION ${PLUGIN_DEST_DIR}
- COMPONENT Runtime
- REGEX "tga|tiff|mng|webp" EXCLUDE
- )
- # Icon engines
- install(
- DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
- DESTINATION ${PLUGIN_DEST_DIR}
- COMPONENT Runtime
- REGEX "fontawesome" EXCLUDE
- )
- # Platform plugins
- install(
- DIRECTORY "${QT_PLUGINS_DIR}/platforms"
- DESTINATION ${PLUGIN_DEST_DIR}
- COMPONENT Runtime
- REGEX "minimal|linuxfb|offscreen" EXCLUDE
- )
- else()
- # Image formats
- install(
- DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
- DESTINATION ${PLUGIN_DEST_DIR}
- COMPONENT Runtime
- REGEX "tga|tiff|mng|webp" EXCLUDE
- REGEX "d\\." EXCLUDE
- REGEX "_debug\\." EXCLUDE
- REGEX "\\.dSYM" EXCLUDE
- )
- # Icon engines
- install(
- DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
- DESTINATION ${PLUGIN_DEST_DIR}
- COMPONENT Runtime
- REGEX "fontawesome" EXCLUDE
- REGEX "d\\." EXCLUDE
- REGEX "_debug\\." EXCLUDE
- REGEX "\\.dSYM" EXCLUDE
- )
- # Platform plugins
- install(
- DIRECTORY "${QT_PLUGINS_DIR}/platforms"
- DESTINATION ${PLUGIN_DEST_DIR}
- COMPONENT Runtime
- REGEX "minimal|linuxfb|offscreen" EXCLUDE
- REGEX "d\\." EXCLUDE
- REGEX "_debug\\." EXCLUDE
- REGEX "\\.dSYM" EXCLUDE
- )
- endif()
- configure_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
- "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
- @ONLY
- )
- install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime)
-endif()
diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp
deleted file mode 100644
index bebc3db1b8..0000000000
--- a/application/LaunchController.cpp
+++ /dev/null
@@ -1,311 +0,0 @@
-#include "LaunchController.h"
-#include "MainWindow.h"
-#include
-#include "MultiMC.h"
-#include "dialogs/CustomMessageBox.h"
-#include "dialogs/ProfileSelectDialog.h"
-#include "dialogs/ProgressDialog.h"
-#include "dialogs/EditAccountDialog.h"
-#include "InstanceWindow.h"
-#include "BuildConfig.h"
-#include "JavaCommon.h"
-#include
-#include
-#include
-#include
-#include
-#include
-
-LaunchController::LaunchController(QObject *parent) : Task(parent)
-{
-}
-
-void LaunchController::executeTask()
-{
- if (!m_instance)
- {
- emitFailed(tr("No instance specified!"));
- return;
- }
-
- login();
-}
-
-// FIXME: minecraft specific
-void LaunchController::login()
-{
- JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget);
-
- // Find an account to use.
- std::shared_ptr accounts = MMC->accounts();
- MojangAccountPtr account = accounts->activeAccount();
- if (accounts->count() <= 0)
- {
- // Tell the user they need to log in at least one account in order to play.
- auto reply = CustomMessageBox::selectable(
- m_parentWidget, tr("No Accounts"),
- tr("In order to play Minecraft, you must have at least one Mojang or Minecraft "
- "account logged in to MultiMC."
- "Would you like to open the account manager to add an account now?"),
- QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec();
-
- if (reply == QMessageBox::Yes)
- {
- // Open the account manager.
- MMC->ShowGlobalSettings(m_parentWidget, "accounts");
- }
- }
- else if (account.get() == nullptr)
- {
- // If no default account is set, ask the user which one to use.
- ProfileSelectDialog selectDialog(tr("Which profile would you like to use?"),
- ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget);
-
- selectDialog.exec();
-
- // Launch the instance with the selected account.
- account = selectDialog.selectedAccount();
-
- // If the user said to use the account as default, do that.
- if (selectDialog.useAsGlobalDefault() && account.get() != nullptr)
- accounts->setActiveAccount(account->username());
- }
-
- // if no account is selected, we bail
- if (!account.get())
- {
- emitFailed(tr("No account selected for launch."));
- return;
- }
-
- // we try empty password first :)
- QString password;
- // we loop until the user succeeds in logging in or gives up
- bool tryagain = true;
- // the failure. the default failure.
- const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. This could be caused by a password change.");
- QString failReason = needLoginAgain;
-
- while (tryagain)
- {
- m_session = std::make_shared();
- m_session->wants_online = m_online;
- auto task = account->login(m_session, password);
- if (task)
- {
- // We'll need to validate the access token to make sure the account
- // is still logged in.
- ProgressDialog progDialog(m_parentWidget);
- if (m_online)
- {
- progDialog.setSkipButton(true, tr("Play Offline"));
- }
- progDialog.execWithTask(task.get());
- if (!task->wasSuccessful())
- {
- auto failReasonNew = task->failReason();
- if(failReasonNew == "Invalid token.")
- {
- account->invalidateClientToken();
- failReason = needLoginAgain;
- }
- else failReason = failReasonNew;
- }
- }
- switch (m_session->status)
- {
- case AuthSession::Undetermined:
- {
- qCritical() << "Received undetermined session status during login. Bye.";
- tryagain = false;
- emitFailed(tr("Received undetermined session status during login."));
- break;
- }
- case AuthSession::RequiresPassword:
- {
- EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField);
- auto username = m_session->username;
- auto chopN = [](QString toChop, int N) -> QString
- {
- if(toChop.size() > N)
- {
- auto left = toChop.left(N);
- left += QString("\u25CF").repeated(toChop.size() - N);
- return left;
- }
- return toChop;
- };
-
- if(username.contains('@'))
- {
- auto parts = username.split('@');
- auto mailbox = chopN(parts[0],3);
- QString domain = chopN(parts[1], 3);
- username = mailbox + '@' + domain;
- }
- passDialog.setUsername(username);
- if (passDialog.exec() == QDialog::Accepted)
- {
- password = passDialog.password();
- }
- else
- {
- tryagain = false;
- }
- break;
- }
- case AuthSession::PlayableOffline:
- {
- // we ask the user for a player name
- bool ok = false;
- QString usedname = m_session->player_name;
- QString name = QInputDialog::getText(m_parentWidget, tr("Player name"),
- tr("Choose your offline mode player name."),
- QLineEdit::Normal, m_session->player_name, &ok);
- if (!ok)
- {
- tryagain = false;
- break;
- }
- if (name.length())
- {
- usedname = name;
- }
- m_session->MakeOffline(usedname);
- // offline flavored game from here :3
- }
- case AuthSession::PlayableOnline:
- {
- launchInstance();
- tryagain = false;
- return;
- }
- }
- }
- emitFailed(tr("Failed to launch."));
-}
-
-void LaunchController::launchInstance()
-{
- Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
- Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
-
- if(!m_instance->reloadSettings())
- {
- QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile."));
- emitFailed(tr("Couldn't load the instance profile."));
- return;
- }
-
- m_launcher = m_instance->createLaunchTask(m_session);
- if (!m_launcher)
- {
- emitFailed(tr("Couldn't instantiate a launcher."));
- return;
- }
-
- auto console = qobject_cast(m_parentWidget);
- auto showConsole = m_instance->settings()->get("ShowConsole").toBool();
- if(!console && showConsole)
- {
- MMC->showInstanceWindow(m_instance);
- }
- connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
- connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded);
- connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed);
- connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested);
-
-
- m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC));
- m_launcher->start();
-}
-
-void LaunchController::readyForLaunch()
-{
- if (!m_profiler)
- {
- m_launcher->proceed();
- return;
- }
-
- QString error;
- if (!m_profiler->check(&error))
- {
- m_launcher->abort();
- QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
- emitFailed("Profiler startup failed!");
- return;
- }
- BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
-
- connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message)
- {
- QMessageBox msg;
- msg.setText(tr("The game launch is delayed until you press the "
- "button. This is the right time to setup the profiler, as the "
- "profiler server is running now.\n\n%1").arg(message));
- msg.setWindowTitle(tr("Waiting."));
- msg.setIcon(QMessageBox::Information);
- msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
- msg.setModal(true);
- msg.exec();
- m_launcher->proceed();
- });
- connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message)
- {
- QMessageBox msg;
- msg.setText(tr("Couldn't start the profiler: %1").arg(message));
- msg.setWindowTitle(tr("Error"));
- msg.setIcon(QMessageBox::Critical);
- msg.addButton(QMessageBox::Ok);
- msg.setModal(true);
- msg.exec();
- m_launcher->abort();
- emitFailed("Profiler startup failed!");
- });
- profilerInstance->beginProfiling(m_launcher);
-}
-
-void LaunchController::onSucceeded()
-{
- emitSucceeded();
-}
-
-void LaunchController::onFailed(QString reason)
-{
- if(m_instance->settings()->get("ShowConsoleOnError").toBool())
- {
- MMC->showInstanceWindow(m_instance, "console");
- }
- emitFailed(reason);
-}
-
-void LaunchController::onProgressRequested(Task* task)
-{
- ProgressDialog progDialog(m_parentWidget);
- progDialog.setSkipButton(true, tr("Abort"));
- m_launcher->proceed();
- progDialog.execWithTask(task);
-}
-
-bool LaunchController::abort()
-{
- if(!m_launcher)
- {
- return true;
- }
- if(!m_launcher->canAbort())
- {
- return false;
- }
- auto response = CustomMessageBox::selectable(
- m_parentWidget, tr("Kill Minecraft?"),
- tr("This can cause the instance to get corrupted and should only be used if Minecraft "
- "is frozen for some reason"),
- QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec();
- if (response == QMessageBox::Yes)
- {
- return m_launcher->abort();
- }
- return false;
-}
diff --git a/application/dialogs/SkinUploadDialog.ui b/application/dialogs/SkinUploadDialog.ui
deleted file mode 100644
index 6f5307e343..0000000000
--- a/application/dialogs/SkinUploadDialog.ui
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
- SkinUploadDialog
-
-
-
- 0
- 0
- 413
- 300
-
-
-
- Skin Upload
-
-
- -
-
-
- Skin File
-
-
- -
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 28
- 16777215
-
-
-
- ...
-
-
-
-
-
-
- -
-
-
- Player Model
-
-
- -
-
-
- Steve Model
-
-
- true
-
-
-
- -
-
-
- Alex Model
-
-
-
-
-
-
- -
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
-
-
-
-
-
diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp
deleted file mode 100644
index 1e9f9dbbf8..0000000000
--- a/application/pages/modplatform/twitch/TwitchPage.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-#include "TwitchPage.h"
-#include "ui_TwitchPage.h"
-
-#include "MultiMC.h"
-#include "dialogs/NewInstanceDialog.h"
-#include
-#include "TwitchModel.h"
-#include
-
-TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog)
-{
- ui->setupUi(this);
- connect(ui->searchButton, &QPushButton::clicked, this, &TwitchPage::triggerSearch);
- ui->searchEdit->installEventFilter(this);
- model = new Twitch::ListModel(this);
- ui->packView->setModel(model);
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TwitchPage::onSelectionChanged);
-}
-
-TwitchPage::~TwitchPage()
-{
- delete ui;
-}
-
-bool TwitchPage::eventFilter(QObject* watched, QEvent* event)
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- QKeyEvent* keyEvent = static_cast(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- }
- }
- return QWidget::eventFilter(watched, event);
-}
-
-bool TwitchPage::shouldDisplay() const
-{
- return true;
-}
-
-void TwitchPage::openedImpl()
-{
- suggestCurrent();
-}
-
-void TwitchPage::triggerSearch()
-{
- model->searchWithTerm(ui->searchEdit->text());
-}
-
-void TwitchPage::onSelectionChanged(QModelIndex first, QModelIndex second)
-{
- if(!first.isValid())
- {
- if(isOpened)
- {
- dialog->setSuggestedPack();
- }
- ui->frame->clear();
- return;
- }
-
- current = model->data(first, Qt::UserRole).value();
- QString text = "";
- QString name = current.name;
-
- if (current.websiteUrl.isEmpty())
- text = name;
- else
- text = "" + name + " ";
- if (!current.authors.empty()) {
- auto authorToStr = [](Twitch::ModpackAuthor & author) {
- if(author.url.isEmpty()) {
- return author.name;
- }
- return QString("%2 ").arg(author.url, author.name);
- };
- QStringList authorStrs;
- for(auto & author: current.authors) {
- authorStrs.push_back(authorToStr(author));
- }
- text += tr(" by ") + authorStrs.join(", ");
- }
-
- ui->frame->setModText(text);
- ui->frame->setModDescription(current.description);
- suggestCurrent();
-}
-
-void TwitchPage::suggestCurrent()
-{
- if(!isOpened)
- {
- return;
- }
- if(current.broken)
- {
- dialog->setSuggestedPack();
- }
-
- dialog->setSuggestedPack(current.name, new InstanceImportTask(current.latestFile.downloadUrl));
- QString editedLogoName;
- editedLogoName = "twitch_" + current.logoName.section(".", 0, 0);
- model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
-}
diff --git a/application/pages/modplatform/twitch/TwitchPage.ui b/application/pages/modplatform/twitch/TwitchPage.ui
deleted file mode 100644
index c78d8ce024..0000000000
--- a/application/pages/modplatform/twitch/TwitchPage.ui
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
- TwitchPage
-
-
-
- 0
- 0
- 875
- 745
-
-
-
- -
-
-
- -
-
-
- Search
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- QFrame::StyledPanel
-
-
- QFrame::Raised
-
-
-
- -
-
-
- Qt::ScrollBarAlwaysOff
-
-
- true
-
-
-
- 48
- 48
-
-
-
-
-
-
-
-
- MCModInfoFrame
- QFrame
-
- 1
-
-
-
- searchEdit
- searchButton
- packView
-
-
-
-
diff --git a/application/resources/MultiMC.ico b/application/resources/MultiMC.ico
deleted file mode 100644
index 1846964e96..0000000000
Binary files a/application/resources/MultiMC.ico and /dev/null differ
diff --git a/application/resources/multimc/16x16/patreon.png b/application/resources/multimc/16x16/patreon.png
deleted file mode 100644
index cde2b326d1..0000000000
Binary files a/application/resources/multimc/16x16/patreon.png and /dev/null differ
diff --git a/application/resources/multimc/22x22/patreon.png b/application/resources/multimc/22x22/patreon.png
deleted file mode 100644
index b6235ad2df..0000000000
Binary files a/application/resources/multimc/22x22/patreon.png and /dev/null differ
diff --git a/application/resources/multimc/24x24/patreon.png b/application/resources/multimc/24x24/patreon.png
deleted file mode 100644
index c1da080f83..0000000000
Binary files a/application/resources/multimc/24x24/patreon.png and /dev/null differ
diff --git a/application/resources/multimc/32x32/instances/brick.png b/application/resources/multimc/32x32/instances/brick.png
deleted file mode 100644
index 0b534366bd..0000000000
Binary files a/application/resources/multimc/32x32/instances/brick.png and /dev/null differ
diff --git a/application/resources/multimc/32x32/instances/diamond.png b/application/resources/multimc/32x32/instances/diamond.png
deleted file mode 100644
index 376ab901aa..0000000000
Binary files a/application/resources/multimc/32x32/instances/diamond.png and /dev/null differ
diff --git a/application/resources/multimc/32x32/instances/gold.png b/application/resources/multimc/32x32/instances/gold.png
deleted file mode 100644
index 9bedda168a..0000000000
Binary files a/application/resources/multimc/32x32/instances/gold.png and /dev/null differ
diff --git a/application/resources/multimc/32x32/instances/iron.png b/application/resources/multimc/32x32/instances/iron.png
deleted file mode 100644
index 28960782ee..0000000000
Binary files a/application/resources/multimc/32x32/instances/iron.png and /dev/null differ
diff --git a/application/resources/multimc/32x32/instances/planks.png b/application/resources/multimc/32x32/instances/planks.png
deleted file mode 100644
index 7fcf8467c7..0000000000
Binary files a/application/resources/multimc/32x32/instances/planks.png and /dev/null differ
diff --git a/application/resources/multimc/32x32/instances/stone.png b/application/resources/multimc/32x32/instances/stone.png
deleted file mode 100644
index 34f9a751e4..0000000000
Binary files a/application/resources/multimc/32x32/instances/stone.png and /dev/null differ
diff --git a/application/resources/multimc/32x32/patreon.png b/application/resources/multimc/32x32/patreon.png
deleted file mode 100644
index f5ae8a5e4e..0000000000
Binary files a/application/resources/multimc/32x32/patreon.png and /dev/null differ
diff --git a/application/resources/multimc/48x48/patreon.png b/application/resources/multimc/48x48/patreon.png
deleted file mode 100644
index 2708a85a65..0000000000
Binary files a/application/resources/multimc/48x48/patreon.png and /dev/null differ
diff --git a/application/resources/multimc/64x64/patreon.png b/application/resources/multimc/64x64/patreon.png
deleted file mode 100644
index 7b4814ec6d..0000000000
Binary files a/application/resources/multimc/64x64/patreon.png and /dev/null differ
diff --git a/application/resources/multimc/scalable/twitch.svg b/application/resources/multimc/scalable/twitch.svg
deleted file mode 100644
index 8099938040..0000000000
--- a/application/resources/multimc/scalable/twitch.svg
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
- image/svg+xml
-
- Glitch
-
-
-
-
-
-
-
- Glitch
-
-
diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index 86ea83ee37..d9f4d1f1c3 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -12,14 +12,14 @@ Config::Config()
VERSION_BUILD = @MultiMC_VERSION_BUILD@;
BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@";
- CHANLIST_URL = "@MultiMC_CHANLIST_URL@";
+ UPDATER_BASE = "@MultiMC_UPDATER_BASE@";
ANALYTICS_ID = "@MultiMC_ANALYTICS_ID@";
NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@";
FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@";
GIT_COMMIT = "@MultiMC_GIT_COMMIT@";
GIT_REFSPEC = "@MultiMC_GIT_REFSPEC@";
- if(GIT_REFSPEC.startsWith("refs/heads/") && !CHANLIST_URL.isEmpty() && VERSION_BUILD >= 0)
+ if(GIT_REFSPEC.startsWith("refs/heads/") && !UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty() && VERSION_BUILD >= 0)
{
VERSION_CHANNEL = GIT_REFSPEC;
VERSION_CHANNEL.remove("refs/heads/");
@@ -33,7 +33,12 @@ Config::Config()
VERSION_STR = "@MultiMC_VERSION_STRING@";
NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@";
PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@";
+ IMGUR_CLIENT_ID = "@MultiMC_IMGUR_CLIENT_ID@";
META_URL = "@MultiMC_META_URL@";
+
+ BUG_TRACKER_URL = "@MultiMC_BUG_TRACKER_URL@";
+ DISCORD_URL = "@MultiMC_DISCORD_URL@";
+ SUBREDDIT_URL = "@MultiMC_SUBREDDIT_URL@";
}
QString Config::printableVersionString() const
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index 02a9297a9f..6a35d1b352 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -29,7 +29,12 @@ class Config
QString BUILD_PLATFORM;
/// URL for the updater's channel
- QString CHANLIST_URL;
+ QString UPDATER_BASE;
+
+ /// User-Agent to use.
+ QString USER_AGENT = "MultiMC/5.0";
+ /// User-Agent to use for uncached requests.
+ QString USER_AGENT_UNCACHED = "MultiMC/5.0 (Uncached)";
/// Google analytics ID
QString ANALYTICS_ID;
@@ -60,19 +65,26 @@ class Config
*/
QString PASTE_EE_KEY;
+ /**
+ * Client ID you can get from Imgur when you register an application
+ */
+ QString IMGUR_CLIENT_ID;
+
/**
* MultiMC Metadata repository URL prefix
*/
QString META_URL;
+ QString BUG_TRACKER_URL;
+ QString DISCORD_URL;
+ QString SUBREDDIT_URL;
+
QString RESOURCE_BASE = "https://resources.download.minecraft.net/";
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
- QString SKINS_BASE = "https://crafatar.com/skins/";
QString AUTH_BASE = "https://authserver.mojang.com/";
QString MOJANG_STATUS_URL = "https://status.mojang.com/check";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
- QString FMLLIBS_OUR_BASE_URL = "https://files.multimc.org/fmllibs/";
- QString FMLLIBS_FORGE_BASE_URL = "https://files.minecraftforge.net/fmllibs/";
+ QString FMLLIBS_BASE_URL = "https://files.multimc.org/fmllibs/";
QString TRANSLATIONS_BASE_URL = "https://files.multimc.org/translations/";
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
diff --git a/changelog.md b/changelog.md
index 31b99a6b45..97454d7f62 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,10 +1,135 @@
-# MultiMC 0.6.12
+# MultiMC 0.6.13
+
+This release brings initial support for Microsoft accounts, along with a nice pile of modpack platform support changes and improved Java runtime detection.
+
+Java runtimes still need an overhaul, so we're staying on the 0.6 version for a little longer.
+
+Next release should also tackle the current Forge 1.17.x issues in a systematic way.
+
+### Microsoft accounts
+
+This is the first release with Microsoft accounts in.
+
+Implementation is loosely based on documentation available from [wiki.vg](https://wiki.vg/Microsoft_Authentication_Scheme) with some notable changes:
+
+- More complete implementation including getting and displaying GamerTags [(see XR-046)](https://docs.microsoft.com/en-us/gaming/gdk/_content/gc/policies/pc/live-policies-pc#xr-046-display-name-and-gamerpic-).
+
+- Using the OAuth Device Flow instead of closely integrating with a browser engine.
+
+ MultiMC asks you to open a Microsoft login web page and put in a code that lets MultiMC authenticate.
+
+ This lets you authenticate on a completely separate device like your phone, leaving code we ship and the computer you may not even trust out of the picture.
+
+As part of this, the skin fetching no longer uses a third party service and instead gets skins directly from Mojang.
+
+Capes can also be selected in MultiMC now. With how many people will now get one for migrating their accounts, it only makes sense.
+
+### macOS update
+
+Because of issues with the Microsoft accounts, we now have two builds on macOS:
+
+- The old build with Qt 5.6 that does not work with Microsoft accounts, but can run on macOS older than 10.13.
+
+- A new build with Qt 5.15.2 that does work with Microsoft accounts, can use the new macOS dark theme and highlight colors, but requires at least macOS 10.13.
+
+MultiMC will update to the 5.15.2 builds when it detects that this is possible. **It may look like it is updating twice, just let it do its thing.**
+
+Similar approach got attempted on Windows, aiming to fix various display scaling and theming issues, but it ran into too many problems and will be attempted later, with more caution.
+
+### Modpack platforms
+
+In general, the modpack platform pages have been made more consistent with each other (GH-3118, GH-3720, GH-3731).
+
+- FTB improvements:
+
+ - Modpack file downloads are now checked with checksums and cached.
+
+ - GH-1949: Allow Legacy FTB and FTB pack downloads to be aborted.
+
+- CurseForge improvements:
+
+ - CurseForge modpack platform is now presented as CurseForge, not Twitch.
+
+ - UI has been updated to match other platforms
+
+ - Added sorting
+
+ - GH-3667: Added version selection
+
+ - GH-3611: Added ability to install beta versions
+
+ - GH-3633: When a CurseForge pack is available for multiple Minecraft versions, we assume the latest one.
+
+- ATLauncher improvements:
+
+ - Handling latest/custom/recommended mod loader versions.
+
+ - Fabric loader packs should now work.
+
+ - GH-3764: Only client mods are installed now for ATL packs.
+
+ - Improved error handling
+
+ - Optional mods are supported.
+
+ - GH-1949: Allow ATLauncher pack downloads to be aborted
+
+
+- Fixed bugs in FTB platform search.
+
+### Other changes
+
+- Forge installation is disabled on Minecraft 1.17+ because of incompatible/unresolved changes on the Forge side.
+
+ We're going to aim for fixing it in time for 1.18. Thankfully, 1.17 is more of a in-between release, so go play some 1.16.x packs!
+
+- GH-2529: On macOS, MultiMC will ask to move all the instance data to a new `Data` folder in order to fix long load times caused by macOS checking all files.
+
+- Detection of a large amount of various Java runtime flavors have been added.
+
+- It is now possible to join servers when starting an instance:
+
+ - From command line via the `--launch` and `--server` arguments.
+
+ - Or by setting this up in the instance settings page.
+
+ This may not work correctly in some cases, because it is a rarely used feature and modders do not test with it.
+
+- MultiMC now prints resolved IP addresses of Minecraft services into the game log for diagnostic purposes.
+
+- Updated instance icons based on Minecraft textures.
+
+- Forge `mods.toml` files are now used for displaying mods in the UI.
+
+- Datapack button is now disabled when no world is selected.
+
+- MultiMC warns about GLFW and OpenAL workarounds being enabled in the game log.
+
+- Languages in the translations list are now sorted by their two/three letter key
+
+- GH-3450: Displaying and recording gameplay time is now optional and can be turned off.
+
+- GH-3930: MultiMC can now track the gameplay time of the last session.
+
+- GH-3033: The version pages of instances now have a filter bar.
+
+- GH-2971: UI descriptions of texture and resource packs no longer mention mods.
+
+- Quick and dirty minimum Java runtime versions checks have been added. This needs to be expanded in the future.
+
+### Technical changes
+
+- The codebase continues to move towards being debranded and harder to build as 'MultiMC' for third parties.
+
+# Previous releases
+
+## MultiMC 0.6.12
After roughly one year of maintenance and development work by various contributors, we're just calling it a good time to release.
What got added since the last time? Quite a bit! But in general, this is more of a spring cleaning before the major changes that we need to make come in.
-### Modpack platforms
+#### Modpack platforms
We've added a whole bunch of new modpack platforms to pick from right into the new instance dialog. If you run into any unusual issues with the imported packs, report them on the bug tracker.
@@ -18,7 +143,7 @@ We've added a whole bunch of new modpack platforms to pick from right into the n
- GH-405: Added a ATLauncher pack browser
-### Other changes
+#### Other changes
- Added the option to not use OpenAL and/or GLFW libraries bundled with the game.
@@ -46,7 +171,7 @@ We've added a whole bunch of new modpack platforms to pick from right into the n
- GH-3602: Pre-launch commands could fail on first launch of the instance because the .minecraft folder has not been created yet.
-### Technical changes
+#### Technical changes
- GH-3234: At build time, the meta URL can be changed.
@@ -58,8 +183,6 @@ We've added a whole bunch of new modpack platforms to pick from right into the n
- Compatibility with unusual build environments has been increased
-# Previous releases
-
## MultiMC 0.6.11
This adds Forge 1.13+ support using [ForgeWrapper](https://github.com/ZekerZhayard/ForgeWrapper) by ZekerZhayard.
diff --git a/doc/multimc.1.txt b/doc/multimc.1.txt
new file mode 100644
index 0000000000..c2d938804b
--- /dev/null
+++ b/doc/multimc.1.txt
@@ -0,0 +1,62 @@
+MULTIMC(1)
+==========
+:doctype: manpage
+
+
+NAME
+----
+multimc - a launcher and instance manager for Minecraft.
+
+
+SYNOPSIS
+--------
+*multimc* ['OPTIONS']
+
+
+DESCRIPTION
+-----------
+MultiMC is a custom launcher for Minecraft that allows you to easily manage
+multiple installations of Minecraft at once. It also allows you to easily
+install and remove mods by simply dragging and dropping.
+Here are the current features of MultiMC.
+
+OPTIONS
+-------
+*-d, --dir*='DIRECTORY'::
+ Use 'DIRECTORY' as the MultiMC root.
+
+*-l, --launch*='INSTANCE_ID'::
+ Launch the instance specified by 'INSTANCE_ID'.
+
+*--alive*::
+ Write a small 'live.check' file after MultiMC starts.
+
+*-h, --help*::
+ Display help text and exit.
+
+*-v, --version*::
+ Display program version and exit.
+
+EXIT STATUS
+-----------
+*0*::
+ Success
+
+*1*::
+ Failure (syntax or usage error; configuration error; unexpected error).
+
+BUGS
+----
+
+
+RESOURCES
+---------
+GitHub:
+
+Main website:
+
+AUTHORS
+-------
+peterix
+
+// vim: syntax=asciidoc
diff --git a/api/logic/BaseInstaller.cpp b/launcher/BaseInstaller.cpp
similarity index 100%
rename from api/logic/BaseInstaller.cpp
rename to launcher/BaseInstaller.cpp
diff --git a/api/logic/BaseInstaller.h b/launcher/BaseInstaller.h
similarity index 94%
rename from api/logic/BaseInstaller.h
rename to launcher/BaseInstaller.h
index 3e40b3554b..b2e6a14d65 100644
--- a/api/logic/BaseInstaller.h
+++ b/launcher/BaseInstaller.h
@@ -17,8 +17,6 @@
#include
-#include "multimc_logic_export.h"
-
class MinecraftInstance;
class QDir;
class QString;
@@ -27,7 +25,7 @@ class Task;
class BaseVersion;
typedef std::shared_ptr BaseVersionPtr;
-class MULTIMC_LOGIC_EXPORT BaseInstaller
+class BaseInstaller
{
public:
BaseInstaller();
diff --git a/api/logic/BaseInstance.cpp b/launcher/BaseInstance.cpp
similarity index 92%
rename from api/logic/BaseInstance.cpp
rename to launcher/BaseInstance.cpp
index d08ceb2b86..46b458271b 100644
--- a/api/logic/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -37,6 +37,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
+ m_settings->registerSetting("lastTimePlayed", 0);
// Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
@@ -134,15 +135,24 @@ void BaseInstance::setRunning(bool running)
m_isRunning = running;
+ if(!m_settings->get("RecordGameTime").toBool())
+ {
+ emit runningStatusChanged(running);
+ return;
+ }
+
if(running)
{
m_timeStarted = QDateTime::currentDateTime();
}
else
{
- qint64 current = settings()->get("totalTimePlayed").toLongLong();
QDateTime timeEnded = QDateTime::currentDateTime();
+
+ qint64 current = settings()->get("totalTimePlayed").toLongLong();
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
+ settings()->set("lastTimePlayed", m_timeStarted.secsTo(timeEnded));
+
emit propertiesChanged(this);
}
@@ -160,9 +170,20 @@ int64_t BaseInstance::totalTimePlayed() const
return current;
}
+int64_t BaseInstance::lastTimePlayed() const
+{
+ if(m_isRunning)
+ {
+ QDateTime timeNow = QDateTime::currentDateTime();
+ return m_timeStarted.secsTo(timeNow);
+ }
+ return settings()->get("lastTimePlayed").toLongLong();
+}
+
void BaseInstance::resetTimePlayed()
{
settings()->reset("totalTimePlayed");
+ settings()->reset("lastTimePlayed");
}
QString BaseInstance::instanceType() const
diff --git a/api/logic/BaseInstance.h b/launcher/BaseInstance.h
similarity index 93%
rename from api/logic/BaseInstance.h
rename to launcher/BaseInstance.h
index bbeabb4179..8c08dc05c3 100644
--- a/api/logic/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -26,13 +26,13 @@
#include "settings/INIFile.h"
#include "BaseVersionList.h"
-#include "minecraft/auth/MojangAccount.h"
+#include "minecraft/auth/MinecraftAccount.h"
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "net/Mode.h"
-#include "multimc_logic_export.h"
+#include "minecraft/launch/MinecraftServerTarget.h"
class QDir;
class Task;
@@ -50,7 +50,7 @@ typedef std::shared_ptr InstancePtr;
* To create a new instance type, create a new class inheriting from this class
* and implement the pure virtual functions.
*/
-class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this
+class BaseInstance : public QObject, public std::enable_shared_from_this
{
Q_OBJECT
protected:
@@ -85,6 +85,7 @@ class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_sha
void setRunning(bool running);
bool isRunning() const;
int64_t totalTimePlayed() const;
+ int64_t lastTimePlayed() const;
void resetTimePlayed();
/// get the type of this instance
@@ -145,7 +146,8 @@ class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_sha
virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
- virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account) = 0;
+ virtual shared_qobject_ptr createLaunchTask(
+ AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
/// returns the current launch task (if any)
shared_qobject_ptr getLaunchTask();
@@ -221,9 +223,9 @@ class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_sha
bool reloadSettings();
/**
- * 'print' a verbose desription of the instance into a QStringList
+ * 'print' a verbose description of the instance into a QStringList
*/
- virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
+ virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0;
Status currentStatus() const;
diff --git a/api/logic/BaseVersion.h b/launcher/BaseVersion.h
similarity index 100%
rename from api/logic/BaseVersion.h
rename to launcher/BaseVersion.h
diff --git a/api/logic/BaseVersionList.cpp b/launcher/BaseVersionList.cpp
similarity index 100%
rename from api/logic/BaseVersionList.cpp
rename to launcher/BaseVersionList.cpp
diff --git a/api/logic/BaseVersionList.h b/launcher/BaseVersionList.h
similarity index 97%
rename from api/logic/BaseVersionList.h
rename to launcher/BaseVersionList.h
index 29e21bdb11..ce7abce195 100644
--- a/api/logic/BaseVersionList.h
+++ b/launcher/BaseVersionList.h
@@ -21,7 +21,6 @@
#include "BaseVersion.h"
#include "tasks/Task.h"
-#include "multimc_logic_export.h"
#include "QObjectPtr.h"
/*!
@@ -36,7 +35,7 @@
* all have a default implementation, but they can be overridden by plugins to
* change the behavior of the list.
*/
-class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel
+class BaseVersionList : public QAbstractListModel
{
Q_OBJECT
public:
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
new file mode 100644
index 0000000000..c29ee3e1ab
--- /dev/null
+++ b/launcher/CMakeLists.txt
@@ -0,0 +1,1043 @@
+project(application)
+
+################################ FILES ################################
+
+######## Sources and headers ########
+
+include (UnitTest)
+
+set(CORE_SOURCES
+ # LOGIC - Base classes and infrastructure
+ BaseInstaller.h
+ BaseInstaller.cpp
+ BaseVersionList.h
+ BaseVersionList.cpp
+ InstanceList.h
+ InstanceList.cpp
+ InstanceTask.h
+ InstanceTask.cpp
+ LoggedProcess.h
+ LoggedProcess.cpp
+ MessageLevel.cpp
+ MessageLevel.h
+ BaseVersion.h
+ BaseInstance.h
+ BaseInstance.cpp
+ NullInstance.h
+ MMCZip.h
+ MMCZip.cpp
+ MMCStrings.h
+ MMCStrings.cpp
+
+ # Basic instance manipulation tasks (derived from InstanceTask)
+ InstanceCreationTask.h
+ InstanceCreationTask.cpp
+ InstanceCopyTask.h
+ InstanceCopyTask.cpp
+ InstanceImportTask.h
+ InstanceImportTask.cpp
+
+ # Use tracking separate from memory management
+ Usable.h
+
+ # Prefix tree where node names are strings between separators
+ SeparatorPrefixTree.h
+
+ # WARNING: globals live here
+ Env.h
+ Env.cpp
+
+ # String filters
+ Filter.h
+ Filter.cpp
+
+ # JSON parsing helpers
+ Json.h
+ Json.cpp
+
+ FileSystem.h
+ FileSystem.cpp
+
+ Exception.h
+
+ # RW lock protected map
+ RWStorage.h
+
+ # A variable that has an implicit default value and keeps track of changes
+ DefaultVariable.h
+
+ # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
+ QObjectPtr.h
+
+ # Compression support
+ GZip.h
+ GZip.cpp
+
+ # Command line parameter parsing
+ Commandline.h
+ Commandline.cpp
+
+ # Version number string support
+ Version.h
+ Version.cpp
+
+ # A Recursive file system watcher
+ RecursiveFileSystemWatcher.h
+ RecursiveFileSystemWatcher.cpp
+)
+
+add_unit_test(FileSystem
+ SOURCES FileSystem_test.cpp
+ LIBS MultiMC_logic
+ DATA testdata
+ )
+
+add_unit_test(GZip
+ SOURCES GZip_test.cpp
+ LIBS MultiMC_logic
+ )
+
+set(PATHMATCHER_SOURCES
+ # Path matchers
+ pathmatcher/FSTreeMatcher.h
+ pathmatcher/IPathMatcher.h
+ pathmatcher/MultiMatcher.h
+ pathmatcher/RegexpMatcher.h
+)
+
+set(NET_SOURCES
+ # network stuffs
+ net/ByteArraySink.h
+ net/ChecksumValidator.h
+ net/Download.cpp
+ net/Download.h
+ net/FileSink.cpp
+ net/FileSink.h
+ net/HttpMetaCache.cpp
+ net/HttpMetaCache.h
+ net/MetaCacheSink.cpp
+ net/MetaCacheSink.h
+ net/NetAction.h
+ net/NetJob.cpp
+ net/NetJob.h
+ net/PasteUpload.cpp
+ net/PasteUpload.h
+ net/Sink.h
+ net/Validator.h
+)
+
+# Game launch logic
+set(LAUNCH_SOURCES
+ launch/steps/LookupServerAddress.cpp
+ launch/steps/LookupServerAddress.h
+ launch/steps/PostLaunchCommand.cpp
+ launch/steps/PostLaunchCommand.h
+ launch/steps/PreLaunchCommand.cpp
+ launch/steps/PreLaunchCommand.h
+ launch/steps/TextPrint.cpp
+ launch/steps/TextPrint.h
+ launch/steps/Update.cpp
+ launch/steps/Update.h
+ launch/LaunchStep.cpp
+ launch/LaunchStep.h
+ launch/LaunchTask.cpp
+ launch/LaunchTask.h
+ launch/LogModel.cpp
+ launch/LogModel.h
+)
+
+# Old update system
+set(UPDATE_SOURCES
+ updater/GoUpdate.h
+ updater/GoUpdate.cpp
+ updater/UpdateChecker.h
+ updater/UpdateChecker.cpp
+ updater/DownloadTask.h
+ updater/DownloadTask.cpp
+)
+
+add_unit_test(UpdateChecker
+ SOURCES updater/UpdateChecker_test.cpp
+ LIBS MultiMC_logic
+ DATA updater/testdata
+ )
+
+add_unit_test(DownloadTask
+ SOURCES updater/DownloadTask_test.cpp
+ LIBS MultiMC_logic
+ DATA updater/testdata
+ )
+
+# Rarely used notifications
+set(NOTIFICATIONS_SOURCES
+ # Notifications - short warning messages
+ notifications/NotificationChecker.h
+ notifications/NotificationChecker.cpp
+)
+
+# Backend for the news bar... there's usually no news.
+set(NEWS_SOURCES
+ # News System
+ news/NewsChecker.h
+ news/NewsChecker.cpp
+ news/NewsEntry.h
+ news/NewsEntry.cpp
+)
+
+# Icon interface
+set(ICONS_SOURCES
+ # Icons System and related code
+ icons/IIconList.h
+ icons/IIconList.cpp
+ icons/IconUtils.h
+ icons/IconUtils.cpp
+)
+
+# Minecraft services status checker
+set(STATUS_SOURCES
+ # Status system
+ status/StatusChecker.h
+ status/StatusChecker.cpp
+)
+
+# Support for Minecraft instances and launch
+set(MINECRAFT_SOURCES
+ # Minecraft support
+ minecraft/auth/AccountData.h
+ minecraft/auth/AccountData.cpp
+ minecraft/auth/AccountTask.h
+ minecraft/auth/AccountTask.cpp
+ minecraft/auth/AuthSession.h
+ minecraft/auth/AuthSession.cpp
+ minecraft/auth/AccountList.h
+ minecraft/auth/AccountList.cpp
+ minecraft/auth/MinecraftAccount.h
+ minecraft/auth/MinecraftAccount.cpp
+ minecraft/auth/flows/AuthContext.h
+ minecraft/auth/flows/AuthContext.cpp
+ minecraft/auth/flows/AuthRequest.h
+ minecraft/auth/flows/AuthRequest.cpp
+
+ minecraft/auth/flows/MSAInteractive.h
+ minecraft/auth/flows/MSAInteractive.cpp
+ minecraft/auth/flows/MSASilent.h
+ minecraft/auth/flows/MSASilent.cpp
+
+ minecraft/auth/flows/MojangLogin.h
+ minecraft/auth/flows/MojangLogin.cpp
+ minecraft/auth/flows/MojangRefresh.h
+ minecraft/auth/flows/MojangRefresh.cpp
+
+ minecraft/auth/flows/Yggdrasil.h
+ minecraft/auth/flows/Yggdrasil.cpp
+
+ minecraft/gameoptions/GameOptions.h
+ minecraft/gameoptions/GameOptions.cpp
+
+ minecraft/update/AssetUpdateTask.h
+ minecraft/update/AssetUpdateTask.cpp
+ minecraft/update/FMLLibrariesTask.cpp
+ minecraft/update/FMLLibrariesTask.h
+ minecraft/update/FoldersTask.cpp
+ minecraft/update/FoldersTask.h
+ minecraft/update/LibrariesTask.cpp
+ minecraft/update/LibrariesTask.h
+
+ minecraft/launch/ClaimAccount.cpp
+ minecraft/launch/ClaimAccount.h
+ minecraft/launch/CreateGameFolders.cpp
+ minecraft/launch/CreateGameFolders.h
+ minecraft/launch/ModMinecraftJar.cpp
+ minecraft/launch/ModMinecraftJar.h
+ minecraft/launch/DirectJavaLaunch.cpp
+ minecraft/launch/DirectJavaLaunch.h
+ minecraft/launch/ExtractNatives.cpp
+ minecraft/launch/ExtractNatives.h
+ minecraft/launch/LauncherPartLaunch.cpp
+ minecraft/launch/LauncherPartLaunch.h
+ minecraft/launch/MinecraftServerTarget.cpp
+ minecraft/launch/MinecraftServerTarget.h
+ minecraft/launch/PrintInstanceInfo.cpp
+ minecraft/launch/PrintInstanceInfo.h
+ minecraft/launch/ReconstructAssets.cpp
+ minecraft/launch/ReconstructAssets.h
+ minecraft/launch/ScanModFolders.cpp
+ minecraft/launch/ScanModFolders.h
+ minecraft/launch/VerifyJavaInstall.cpp
+ minecraft/launch/VerifyJavaInstall.h
+
+ minecraft/legacy/LegacyModList.h
+ minecraft/legacy/LegacyModList.cpp
+ minecraft/legacy/LegacyInstance.h
+ minecraft/legacy/LegacyInstance.cpp
+ minecraft/legacy/LegacyUpgradeTask.h
+ minecraft/legacy/LegacyUpgradeTask.cpp
+
+ minecraft/GradleSpecifier.h
+ minecraft/MinecraftInstance.cpp
+ minecraft/MinecraftInstance.h
+ minecraft/LaunchProfile.cpp
+ minecraft/LaunchProfile.h
+ minecraft/Component.cpp
+ minecraft/Component.h
+ minecraft/PackProfile.cpp
+ minecraft/PackProfile.h
+ minecraft/ComponentUpdateTask.cpp
+ minecraft/ComponentUpdateTask.h
+ minecraft/MinecraftLoadAndCheck.h
+ minecraft/MinecraftLoadAndCheck.cpp
+ minecraft/MinecraftUpdate.h
+ minecraft/MinecraftUpdate.cpp
+ minecraft/MojangVersionFormat.cpp
+ minecraft/MojangVersionFormat.h
+ minecraft/Rule.cpp
+ minecraft/Rule.h
+ minecraft/OneSixVersionFormat.cpp
+ minecraft/OneSixVersionFormat.h
+ minecraft/OpSys.cpp
+ minecraft/OpSys.h
+ minecraft/ParseUtils.cpp
+ minecraft/ParseUtils.h
+ minecraft/ProfileUtils.cpp
+ minecraft/ProfileUtils.h
+ minecraft/Library.cpp
+ minecraft/Library.h
+ minecraft/MojangDownloadInfo.h
+ minecraft/VersionFile.cpp
+ minecraft/VersionFile.h
+ minecraft/VersionFilterData.h
+ minecraft/VersionFilterData.cpp
+ minecraft/World.h
+ minecraft/World.cpp
+ minecraft/WorldList.h
+ minecraft/WorldList.cpp
+
+ minecraft/mod/Mod.h
+ minecraft/mod/Mod.cpp
+ minecraft/mod/ModDetails.h
+ minecraft/mod/ModFolderModel.h
+ minecraft/mod/ModFolderModel.cpp
+ minecraft/mod/ModFolderLoadTask.h
+ minecraft/mod/ModFolderLoadTask.cpp
+ minecraft/mod/LocalModParseTask.h
+ minecraft/mod/LocalModParseTask.cpp
+ minecraft/mod/ResourcePackFolderModel.h
+ minecraft/mod/ResourcePackFolderModel.cpp
+ minecraft/mod/TexturePackFolderModel.h
+ minecraft/mod/TexturePackFolderModel.cpp
+
+ # Assets
+ minecraft/AssetsUtils.h
+ minecraft/AssetsUtils.cpp
+
+ # Minecraft services
+ minecraft/services/CapeChange.cpp
+ minecraft/services/CapeChange.h
+ minecraft/services/SkinUpload.cpp
+ minecraft/services/SkinUpload.h
+ minecraft/services/SkinDelete.cpp
+ minecraft/services/SkinDelete.h
+
+ mojang/PackageManifest.h
+ mojang/PackageManifest.cpp
+ )
+
+add_unit_test(GradleSpecifier
+ SOURCES minecraft/GradleSpecifier_test.cpp
+ LIBS MultiMC_logic
+ )
+
+add_executable(PackageManifest
+ mojang/PackageManifest_test.cpp
+)
+target_link_libraries(PackageManifest
+ MultiMC_logic
+ Qt5::Test
+)
+target_include_directories(PackageManifest
+ PRIVATE ../cmake/UnitTest/
+)
+add_test(
+ NAME PackageManifest
+ COMMAND PackageManifest
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+add_unit_test(MojangVersionFormat
+ SOURCES minecraft/MojangVersionFormat_test.cpp
+ LIBS MultiMC_logic
+ DATA minecraft/testdata
+ )
+
+add_unit_test(Library
+ SOURCES minecraft/Library_test.cpp
+ LIBS MultiMC_logic
+ )
+
+# FIXME: shares data with FileSystem test
+add_unit_test(ModFolderModel
+ SOURCES minecraft/mod/ModFolderModel_test.cpp
+ DATA testdata
+ LIBS MultiMC_logic
+ )
+
+add_unit_test(ParseUtils
+ SOURCES minecraft/ParseUtils_test.cpp
+ LIBS MultiMC_logic
+ )
+
+# the screenshots feature
+set(SCREENSHOTS_SOURCES
+ screenshots/Screenshot.h
+ screenshots/ImgurUpload.h
+ screenshots/ImgurUpload.cpp
+ screenshots/ImgurAlbumCreation.h
+ screenshots/ImgurAlbumCreation.cpp
+)
+
+set(TASKS_SOURCES
+ # Tasks
+ tasks/Task.h
+ tasks/Task.cpp
+ tasks/SequentialTask.h
+ tasks/SequentialTask.cpp
+)
+
+set(SETTINGS_SOURCES
+ # Settings
+ settings/INIFile.cpp
+ settings/INIFile.h
+ settings/INISettingsObject.cpp
+ settings/INISettingsObject.h
+ settings/OverrideSetting.cpp
+ settings/OverrideSetting.h
+ settings/PassthroughSetting.cpp
+ settings/PassthroughSetting.h
+ settings/Setting.cpp
+ settings/Setting.h
+ settings/SettingsObject.cpp
+ settings/SettingsObject.h
+)
+
+add_unit_test(INIFile
+ SOURCES settings/INIFile_test.cpp
+ LIBS MultiMC_logic
+ )
+
+set(JAVA_SOURCES
+ # Java related code
+ java/launch/CheckJava.cpp
+ java/launch/CheckJava.h
+ java/JavaChecker.h
+ java/JavaChecker.cpp
+ java/JavaCheckerJob.h
+ java/JavaCheckerJob.cpp
+ java/JavaInstall.h
+ java/JavaInstall.cpp
+ java/JavaInstallList.h
+ java/JavaInstallList.cpp
+ java/JavaUtils.h
+ java/JavaUtils.cpp
+ java/JavaVersion.h
+ java/JavaVersion.cpp
+)
+
+add_unit_test(JavaVersion
+ SOURCES java/JavaVersion_test.cpp
+ LIBS MultiMC_logic
+ )
+
+set(TRANSLATIONS_SOURCES
+ translations/TranslationsModel.h
+ translations/TranslationsModel.cpp
+ translations/POTranslator.h
+ translations/POTranslator.cpp
+)
+
+set(TOOLS_SOURCES
+ # Tools
+ tools/BaseExternalTool.cpp
+ tools/BaseExternalTool.h
+ tools/BaseProfiler.cpp
+ tools/BaseProfiler.h
+ tools/JProfiler.cpp
+ tools/JProfiler.h
+ tools/JVisualVM.cpp
+ tools/JVisualVM.h
+ tools/MCEditTool.cpp
+ tools/MCEditTool.h
+)
+
+set(META_SOURCES
+ # Metadata sources
+ meta/JsonFormat.cpp
+ meta/JsonFormat.h
+ meta/BaseEntity.cpp
+ meta/BaseEntity.h
+ meta/VersionList.cpp
+ meta/VersionList.h
+ meta/Version.cpp
+ meta/Version.h
+ meta/Index.cpp
+ meta/Index.h
+)
+
+set(FTB_SOURCES
+ modplatform/legacy_ftb/PackFetchTask.h
+ modplatform/legacy_ftb/PackFetchTask.cpp
+ modplatform/legacy_ftb/PackInstallTask.h
+ modplatform/legacy_ftb/PackInstallTask.cpp
+ modplatform/legacy_ftb/PrivatePackManager.h
+ modplatform/legacy_ftb/PrivatePackManager.cpp
+
+ modplatform/legacy_ftb/PackHelpers.h
+)
+
+set(FLAME_SOURCES
+ # Flame
+ modplatform/flame/FlamePackIndex.cpp
+ modplatform/flame/FlamePackIndex.h
+ modplatform/flame/PackManifest.h
+ modplatform/flame/PackManifest.cpp
+ modplatform/flame/FileResolvingTask.h
+ modplatform/flame/FileResolvingTask.cpp
+)
+
+set(MODPACKSCH_SOURCES
+ modplatform/modpacksch/FTBPackInstallTask.h
+ modplatform/modpacksch/FTBPackInstallTask.cpp
+ modplatform/modpacksch/FTBPackManifest.h
+ modplatform/modpacksch/FTBPackManifest.cpp
+)
+
+set(TECHNIC_SOURCES
+ modplatform/technic/SingleZipPackInstallTask.h
+ modplatform/technic/SingleZipPackInstallTask.cpp
+ modplatform/technic/SolderPackInstallTask.h
+ modplatform/technic/SolderPackInstallTask.cpp
+ modplatform/technic/TechnicPackProcessor.h
+ modplatform/technic/TechnicPackProcessor.cpp
+)
+
+set(ATLAUNCHER_SOURCES
+ modplatform/atlauncher/ATLPackIndex.cpp
+ modplatform/atlauncher/ATLPackIndex.h
+ modplatform/atlauncher/ATLPackInstallTask.cpp
+ modplatform/atlauncher/ATLPackInstallTask.h
+ modplatform/atlauncher/ATLPackManifest.cpp
+ modplatform/atlauncher/ATLPackManifest.h
+)
+
+add_unit_test(Index
+ SOURCES meta/Index_test.cpp
+ LIBS MultiMC_logic
+ )
+
+################################ COMPILE ################################
+
+# we need zlib
+find_package(ZLIB REQUIRED)
+
+set(LOGIC_SOURCES
+ ${CORE_SOURCES}
+ ${PATHMATCHER_SOURCES}
+ ${NET_SOURCES}
+ ${LAUNCH_SOURCES}
+ ${UPDATE_SOURCES}
+ ${NOTIFICATIONS_SOURCES}
+ ${NEWS_SOURCES}
+ ${STATUS_SOURCES}
+ ${MINECRAFT_SOURCES}
+ ${SCREENSHOTS_SOURCES}
+ ${TASKS_SOURCES}
+ ${SETTINGS_SOURCES}
+ ${JAVA_SOURCES}
+ ${TRANSLATIONS_SOURCES}
+ ${TOOLS_SOURCES}
+ ${META_SOURCES}
+ ${ICONS_SOURCES}
+ ${FTB_SOURCES}
+ ${FLAME_SOURCES}
+ ${MODPACKSCH_SOURCES}
+ ${TECHNIC_SOURCES}
+ ${ATLAUNCHER_SOURCES}
+)
+
+SET(MULTIMC_SOURCES
+ # Application base
+ MultiMC.h
+ MultiMC.cpp
+ UpdateController.cpp
+ UpdateController.h
+
+ # GUI - general utilities
+ DesktopServices.h
+ DesktopServices.cpp
+ GuiUtil.h
+ GuiUtil.cpp
+ ColumnResizer.h
+ ColumnResizer.cpp
+ InstanceProxyModel.h
+ InstanceProxyModel.cpp
+ VersionProxyModel.h
+ VersionProxyModel.cpp
+ ColorCache.h
+ ColorCache.cpp
+ HoeDown.h
+
+ # Super secret!
+ KonamiCode.h
+ KonamiCode.cpp
+
+ # Icons
+ icons/MMCIcon.h
+ icons/MMCIcon.cpp
+ icons/IconList.h
+ icons/IconList.cpp
+
+ # GUI - windows
+ MainWindow.h
+ MainWindow.cpp
+ InstanceWindow.h
+ InstanceWindow.cpp
+
+ # FIXME: maybe find a better home for this.
+ SkinUtils.cpp
+ SkinUtils.h
+
+ # GUI - setup wizard
+ setupwizard/SetupWizard.h
+ setupwizard/SetupWizard.cpp
+ setupwizard/AnalyticsWizardPage.cpp
+ setupwizard/AnalyticsWizardPage.h
+ setupwizard/BaseWizardPage.h
+ setupwizard/JavaWizardPage.cpp
+ setupwizard/JavaWizardPage.h
+ setupwizard/LanguageWizardPage.cpp
+ setupwizard/LanguageWizardPage.h
+
+ # GUI - themes
+ themes/FusionTheme.cpp
+ themes/FusionTheme.h
+ themes/BrightTheme.cpp
+ themes/BrightTheme.h
+ themes/CustomTheme.cpp
+ themes/CustomTheme.h
+ themes/DarkTheme.cpp
+ themes/DarkTheme.h
+ themes/ITheme.cpp
+ themes/ITheme.h
+ themes/SystemTheme.cpp
+ themes/SystemTheme.h
+
+ # Processes
+ LaunchController.h
+ LaunchController.cpp
+
+ # page provider for instances
+ InstancePageProvider.h
+
+ # Common java checking UI
+ JavaCommon.h
+ JavaCommon.cpp
+
+ # GUI - paged dialog base
+ pages/BasePage.h
+ pages/BasePageContainer.h
+ pages/BasePageProvider.h
+
+ # GUI - instance pages
+ pages/instance/GameOptionsPage.cpp
+ pages/instance/GameOptionsPage.h
+ pages/instance/VersionPage.cpp
+ pages/instance/VersionPage.h
+ pages/instance/TexturePackPage.h
+ pages/instance/ResourcePackPage.h
+ pages/instance/ModFolderPage.cpp
+ pages/instance/ModFolderPage.h
+ pages/instance/NotesPage.cpp
+ pages/instance/NotesPage.h
+ pages/instance/LogPage.cpp
+ pages/instance/LogPage.h
+ pages/instance/InstanceSettingsPage.cpp
+ pages/instance/InstanceSettingsPage.h
+ pages/instance/ScreenshotsPage.cpp
+ pages/instance/ScreenshotsPage.h
+ pages/instance/OtherLogsPage.cpp
+ pages/instance/OtherLogsPage.h
+ pages/instance/ServersPage.cpp
+ pages/instance/ServersPage.h
+ pages/instance/LegacyUpgradePage.cpp
+ pages/instance/LegacyUpgradePage.h
+ pages/instance/WorldListPage.cpp
+ pages/instance/WorldListPage.h
+
+ # GUI - global settings pages
+ pages/global/AccountListPage.cpp
+ pages/global/AccountListPage.h
+ pages/global/CustomCommandsPage.cpp
+ pages/global/CustomCommandsPage.h
+ pages/global/ExternalToolsPage.cpp
+ pages/global/ExternalToolsPage.h
+ pages/global/JavaPage.cpp
+ pages/global/JavaPage.h
+ pages/global/LanguagePage.cpp
+ pages/global/LanguagePage.h
+ pages/global/MinecraftPage.cpp
+ pages/global/MinecraftPage.h
+ pages/global/MultiMCPage.cpp
+ pages/global/MultiMCPage.h
+ pages/global/ProxyPage.cpp
+ pages/global/ProxyPage.h
+ pages/global/PasteEEPage.cpp
+ pages/global/PasteEEPage.h
+
+ # GUI - platform pages
+ pages/modplatform/VanillaPage.cpp
+ pages/modplatform/VanillaPage.h
+
+ pages/modplatform/atlauncher/AtlFilterModel.cpp
+ pages/modplatform/atlauncher/AtlFilterModel.h
+ pages/modplatform/atlauncher/AtlListModel.cpp
+ pages/modplatform/atlauncher/AtlListModel.h
+ pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+ pages/modplatform/atlauncher/AtlOptionalModDialog.h
+ pages/modplatform/atlauncher/AtlPage.cpp
+ pages/modplatform/atlauncher/AtlPage.h
+
+ pages/modplatform/ftb/FtbFilterModel.cpp
+ pages/modplatform/ftb/FtbFilterModel.h
+ pages/modplatform/ftb/FtbListModel.cpp
+ pages/modplatform/ftb/FtbListModel.h
+ pages/modplatform/ftb/FtbPage.cpp
+ pages/modplatform/ftb/FtbPage.h
+
+ pages/modplatform/legacy_ftb/Page.cpp
+ pages/modplatform/legacy_ftb/Page.h
+ pages/modplatform/legacy_ftb/ListModel.h
+ pages/modplatform/legacy_ftb/ListModel.cpp
+
+ pages/modplatform/flame/FlameModel.cpp
+ pages/modplatform/flame/FlameModel.h
+ pages/modplatform/flame/FlamePage.cpp
+ pages/modplatform/flame/FlamePage.h
+
+ pages/modplatform/technic/TechnicModel.cpp
+ pages/modplatform/technic/TechnicModel.h
+ pages/modplatform/technic/TechnicPage.cpp
+ pages/modplatform/technic/TechnicPage.h
+
+ pages/modplatform/ImportPage.cpp
+ pages/modplatform/ImportPage.h
+
+ # GUI - dialogs
+ dialogs/AboutDialog.cpp
+ dialogs/AboutDialog.h
+ dialogs/ProfileSelectDialog.cpp
+ dialogs/ProfileSelectDialog.h
+ dialogs/CopyInstanceDialog.cpp
+ dialogs/CopyInstanceDialog.h
+ dialogs/CustomMessageBox.cpp
+ dialogs/CustomMessageBox.h
+ dialogs/EditAccountDialog.cpp
+ dialogs/EditAccountDialog.h
+ dialogs/ExportInstanceDialog.cpp
+ dialogs/ExportInstanceDialog.h
+ dialogs/IconPickerDialog.cpp
+ dialogs/IconPickerDialog.h
+ dialogs/LoginDialog.cpp
+ dialogs/LoginDialog.h
+ dialogs/MSALoginDialog.cpp
+ dialogs/MSALoginDialog.h
+ dialogs/NewComponentDialog.cpp
+ dialogs/NewComponentDialog.h
+ dialogs/NewInstanceDialog.cpp
+ dialogs/NewInstanceDialog.h
+ dialogs/NotificationDialog.cpp
+ dialogs/NotificationDialog.h
+ pagedialog/PageDialog.cpp
+ pagedialog/PageDialog.h
+ dialogs/ProgressDialog.cpp
+ dialogs/ProgressDialog.h
+ dialogs/UpdateDialog.cpp
+ dialogs/UpdateDialog.h
+ dialogs/VersionSelectDialog.cpp
+ dialogs/VersionSelectDialog.h
+ dialogs/SkinUploadDialog.cpp
+ dialogs/SkinUploadDialog.h
+
+
+ # GUI - widgets
+ widgets/Common.cpp
+ widgets/Common.h
+ widgets/CustomCommands.cpp
+ widgets/CustomCommands.h
+ widgets/DropLabel.cpp
+ widgets/DropLabel.h
+ widgets/FocusLineEdit.cpp
+ widgets/FocusLineEdit.h
+ widgets/IconLabel.cpp
+ widgets/IconLabel.h
+ widgets/JavaSettingsWidget.cpp
+ widgets/JavaSettingsWidget.h
+ widgets/LabeledToolButton.cpp
+ widgets/LabeledToolButton.h
+ widgets/LanguageSelectionWidget.cpp
+ widgets/LanguageSelectionWidget.h
+ widgets/LineSeparator.cpp
+ widgets/LineSeparator.h
+ widgets/LogView.cpp
+ widgets/LogView.h
+ widgets/MCModInfoFrame.cpp
+ widgets/MCModInfoFrame.h
+ widgets/ModListView.cpp
+ widgets/ModListView.h
+ widgets/PageContainer.cpp
+ widgets/PageContainer.h
+ widgets/PageContainer_p.h
+ widgets/ServerStatus.cpp
+ widgets/ServerStatus.h
+ widgets/VersionListView.cpp
+ widgets/VersionListView.h
+ widgets/VersionSelectWidget.cpp
+ widgets/VersionSelectWidget.h
+ widgets/ProgressWidget.h
+ widgets/ProgressWidget.cpp
+ widgets/WideBar.h
+ widgets/WideBar.cpp
+
+ # GUI - instance group view
+ groupview/GroupedProxyModel.cpp
+ groupview/GroupedProxyModel.h
+ groupview/AccessibleGroupView.cpp
+ groupview/AccessibleGroupView.h
+ groupview/AccessibleGroupView_p.h
+ groupview/GroupView.cpp
+ groupview/GroupView.h
+ groupview/InstanceDelegate.cpp
+ groupview/InstanceDelegate.h
+ groupview/VisualGroup.cpp
+ groupview/VisualGroup.h
+ )
+
+######## UIs ########
+SET(MULTIMC_UIS
+ # Instance pages
+ pages/instance/GameOptionsPage.ui
+ pages/instance/VersionPage.ui
+ pages/instance/ModFolderPage.ui
+ pages/instance/LogPage.ui
+ pages/instance/InstanceSettingsPage.ui
+ pages/instance/NotesPage.ui
+ pages/instance/ScreenshotsPage.ui
+ pages/instance/OtherLogsPage.ui
+ pages/instance/LegacyUpgradePage.ui
+ pages/instance/ServersPage.ui
+ pages/instance/WorldListPage.ui
+
+ # Global settings pages
+ pages/global/AccountListPage.ui
+ pages/global/ExternalToolsPage.ui
+ pages/global/JavaPage.ui
+ pages/global/MinecraftPage.ui
+ pages/global/MultiMCPage.ui
+ pages/global/ProxyPage.ui
+ pages/global/PasteEEPage.ui
+
+ # Platform pages
+ pages/modplatform/VanillaPage.ui
+ pages/modplatform/atlauncher/AtlPage.ui
+ pages/modplatform/ftb/FtbPage.ui
+ pages/modplatform/legacy_ftb/Page.ui
+ pages/modplatform/flame/FlamePage.ui
+ pages/modplatform/technic/TechnicPage.ui
+ pages/modplatform/ImportPage.ui
+
+ # Platform Dialogs
+ pages/modplatform/atlauncher/AtlOptionalModDialog.ui
+
+ # Dialogs
+ dialogs/CopyInstanceDialog.ui
+ dialogs/NewComponentDialog.ui
+ dialogs/NewInstanceDialog.ui
+ dialogs/AboutDialog.ui
+ dialogs/ProgressDialog.ui
+ dialogs/IconPickerDialog.ui
+ dialogs/ProfileSelectDialog.ui
+ dialogs/EditAccountDialog.ui
+ dialogs/ExportInstanceDialog.ui
+ dialogs/LoginDialog.ui
+ dialogs/MSALoginDialog.ui
+ dialogs/UpdateDialog.ui
+ dialogs/NotificationDialog.ui
+ dialogs/SkinUploadDialog.ui
+
+ # Widgets/other
+ widgets/CustomCommands.ui
+ widgets/MCModInfoFrame.ui
+)
+
+set(MULTIMC_QRCS
+ resources/backgrounds/backgrounds.qrc
+ resources/multimc/multimc.qrc
+ resources/pe_dark/pe_dark.qrc
+ resources/pe_light/pe_light.qrc
+ resources/pe_colored/pe_colored.qrc
+ resources/pe_blue/pe_blue.qrc
+ resources/OSX/OSX.qrc
+ resources/iOS/iOS.qrc
+ resources/flat/flat.qrc
+ resources/documents/documents.qrc
+)
+
+######## Windows resource files ########
+if(WIN32)
+ set(MULTIMC_RCS resources/multimc.rc)
+endif()
+
+# Qt 5 stuff
+qt5_wrap_ui(MULTIMC_UI ${MULTIMC_UIS})
+qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
+
+# Add executable
+add_library(MultiMC_logic STATIC ${LOGIC_SOURCES} ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES})
+target_link_libraries(MultiMC_logic
+ systeminfo
+ MultiMC_quazip
+ MultiMC_classparser
+ ${NBT_NAME}
+ ${ZLIB_LIBRARIES}
+ optional-bare
+ tomlc99
+ BuildConfig
+ Katabasis
+)
+target_link_libraries(MultiMC_logic
+ Qt5::Core
+ Qt5::Xml
+ Qt5::Network
+ Qt5::Concurrent
+ Qt5::Gui
+)
+target_link_libraries(MultiMC_logic
+ MultiMC_iconfix
+ ${QUAZIP_LIBRARIES}
+ hoedown
+ MultiMC_rainbow
+ LocalPeer
+ ganalytics
+)
+
+add_executable(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
+target_link_libraries(MultiMC MultiMC_logic)
+
+if(DEFINED MultiMC_APP_BINARY_NAME)
+ set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}")
+endif()
+if(DEFINED MultiMC_BINARY_RPATH)
+ SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}")
+endif()
+
+if(DEFINED MultiMC_APP_BINARY_DEFS)
+ target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS})
+ target_compile_definitions(MultiMC_logic PRIVATE ${MultiMC_APP_BINARY_DEFS})
+endif()
+
+install(TARGETS MultiMC
+ BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
+ RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
+)
+
+target_link_libraries(MultiMC_logic secrets)
+
+#### The MultiMC bundle mess! ####
+# Bundle utilities are used to complete the portable packages - they add all the libraries that would otherwise be missing on the target system.
+# NOTE: it seems that this absolutely has to be here, and nowhere else.
+if(INSTALL_BUNDLE STREQUAL "full")
+ # Add qt.conf - this makes Qt stop looking for things outside the bundle
+ install(
+ CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")"
+ COMPONENT Runtime
+ )
+ # Bundle plugins
+ if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
+ # Image formats
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "tga|tiff|mng|webp" EXCLUDE
+ )
+ # Icon engines
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "fontawesome" EXCLUDE
+ )
+ # Platform plugins
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/platforms"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "minimal|linuxfb|offscreen" EXCLUDE
+ )
+ # Style plugins
+ if(EXISTS "${QT_PLUGINS_DIR}/styles")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/styles"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ )
+ endif()
+ else()
+ # Image formats
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "tga|tiff|mng|webp" EXCLUDE
+ REGEX "d\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ # Icon engines
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "fontawesome" EXCLUDE
+ REGEX "d\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ # Platform plugins
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/platforms"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "minimal|linuxfb|offscreen" EXCLUDE
+ REGEX "d\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ # Style plugins
+ if(EXISTS "${QT_PLUGINS_DIR}/styles")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/styles"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "d\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ endif()
+ endif()
+ configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
+ @ONLY
+ )
+ install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime)
+endif()
diff --git a/application/ColorCache.cpp b/launcher/ColorCache.cpp
similarity index 100%
rename from application/ColorCache.cpp
rename to launcher/ColorCache.cpp
diff --git a/application/ColorCache.h b/launcher/ColorCache.h
similarity index 100%
rename from application/ColorCache.h
rename to launcher/ColorCache.h
diff --git a/application/ColumnResizer.cpp b/launcher/ColumnResizer.cpp
similarity index 100%
rename from application/ColumnResizer.cpp
rename to launcher/ColumnResizer.cpp
diff --git a/application/ColumnResizer.h b/launcher/ColumnResizer.h
similarity index 100%
rename from application/ColumnResizer.h
rename to launcher/ColumnResizer.h
diff --git a/api/logic/Commandline.cpp b/launcher/Commandline.cpp
similarity index 100%
rename from api/logic/Commandline.cpp
rename to launcher/Commandline.cpp
diff --git a/api/logic/Commandline.h b/launcher/Commandline.h
similarity index 96%
rename from api/logic/Commandline.h
rename to launcher/Commandline.h
index 09c1707e56..a4e7aa6110 100644
--- a/api/logic/Commandline.h
+++ b/launcher/Commandline.h
@@ -25,8 +25,6 @@
#include
#include
-#include "multimc_logic_export.h"
-
/**
* @file libutil/include/cmdutils.h
* @brief commandline parsing and processing utilities
@@ -40,7 +38,7 @@ namespace Commandline
* @param args the argument string
* @return a QStringList containing all arguments
*/
-MULTIMC_LOGIC_EXPORT QStringList splitArgs(QString args);
+QStringList splitArgs(QString args);
/**
* @brief The FlagStyle enum
@@ -83,7 +81,7 @@ enum Enum
/**
* @brief The ParsingError class
*/
-class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
+class ParsingError : public std::runtime_error
{
public:
ParsingError(const QString &what);
@@ -92,7 +90,7 @@ class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
/**
* @brief The Parser class
*/
-class MULTIMC_LOGIC_EXPORT Parser
+class Parser
{
public:
/**
diff --git a/api/logic/DefaultVariable.h b/launcher/DefaultVariable.h
similarity index 100%
rename from api/logic/DefaultVariable.h
rename to launcher/DefaultVariable.h
diff --git a/api/gui/DesktopServices.cpp b/launcher/DesktopServices.cpp
similarity index 100%
rename from api/gui/DesktopServices.cpp
rename to launcher/DesktopServices.cpp
diff --git a/launcher/DesktopServices.h b/launcher/DesktopServices.h
new file mode 100644
index 0000000000..1c081da412
--- /dev/null
+++ b/launcher/DesktopServices.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include
+#include
+
+/**
+ * This wraps around QDesktopServices and adds workarounds where needed
+ * Use this instead of QDesktopServices!
+ */
+namespace DesktopServices
+{
+ /**
+ * Open a file in whatever application is applicable
+ */
+ bool openFile(const QString &path);
+
+ /**
+ * Open a file in the specified application
+ */
+ bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
+
+ /**
+ * Run an application
+ */
+ bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
+
+ /**
+ * Open a directory
+ */
+ bool openDirectory(const QString &path, bool ensureExists = false);
+
+ /**
+ * Open the URL, most likely in a browser. Maybe.
+ */
+ bool openUrl(const QUrl &url);
+}
diff --git a/api/logic/Env.cpp b/launcher/Env.cpp
similarity index 97%
rename from api/logic/Env.cpp
rename to launcher/Env.cpp
index 42a1cff7af..abf9f58cc2 100644
--- a/api/logic/Env.cpp
+++ b/launcher/Env.cpp
@@ -100,8 +100,7 @@ void Env::initHttpMetaCache()
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
- m_metacache->addBase("TwitchPacks", QDir("cache/TwitchPacks").absolutePath());
- m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
+ m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
diff --git a/api/logic/Env.h b/launcher/Env.h
similarity index 95%
rename from api/logic/Env.h
rename to launcher/Env.h
index 8b9b827e58..7d1a8bc9ae 100644
--- a/api/logic/Env.h
+++ b/launcher/Env.h
@@ -5,8 +5,6 @@
#include
#include
-#include "multimc_logic_export.h"
-
#include "QObjectPtr.h"
class QNetworkAccessManager;
@@ -25,7 +23,7 @@ class Index;
#define ENV (Env::getInstance())
-class MULTIMC_LOGIC_EXPORT Env
+class Env
{
friend class MultiMC;
private:
diff --git a/api/logic/Exception.h b/launcher/Exception.h
similarity index 86%
rename from api/logic/Exception.h
rename to launcher/Exception.h
index 9400b3f86a..fe0b86b5f8 100644
--- a/api/logic/Exception.h
+++ b/launcher/Exception.h
@@ -6,9 +6,7 @@
#include
#include
-#include "multimc_logic_export.h"
-
-class MULTIMC_LOGIC_EXPORT Exception : public std::exception
+class Exception : public std::exception
{
public:
Exception(const QString &message) : std::exception(), m_message(message)
diff --git a/api/logic/ExponentialSeries.h b/launcher/ExponentialSeries.h
similarity index 100%
rename from api/logic/ExponentialSeries.h
rename to launcher/ExponentialSeries.h
diff --git a/api/logic/FileSystem.cpp b/launcher/FileSystem.cpp
similarity index 100%
rename from api/logic/FileSystem.cpp
rename to launcher/FileSystem.cpp
diff --git a/api/logic/FileSystem.h b/launcher/FileSystem.h
similarity index 58%
rename from api/logic/FileSystem.h
rename to launcher/FileSystem.h
index 55ec6a58f5..8f6e8b4898 100644
--- a/api/logic/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -5,14 +5,13 @@
#include "Exception.h"
#include "pathmatcher/IPathMatcher.h"
-#include "multimc_logic_export.h"
#include
#include
namespace FS
{
-class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
+class FileSystemException : public ::Exception
{
public:
FileSystemException(const QString &message) : Exception(message) {}
@@ -21,31 +20,31 @@ class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
/**
* write data to a file safely
*/
-MULTIMC_LOGIC_EXPORT void write(const QString &filename, const QByteArray &data);
+void write(const QString &filename, const QByteArray &data);
/**
* read data from a file safely\
*/
-MULTIMC_LOGIC_EXPORT QByteArray read(const QString &filename);
+QByteArray read(const QString &filename);
/**
* Update the last changed timestamp of an existing file
*/
-MULTIMC_LOGIC_EXPORT bool updateTimestamp(const QString & filename);
+bool updateTimestamp(const QString & filename);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a file name and is ignored!
*/
-MULTIMC_LOGIC_EXPORT bool ensureFilePathExists(QString filenamepath);
+bool ensureFilePathExists(QString filenamepath);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
-MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath);
+bool ensureFolderPathExists(QString filenamepath);
-class MULTIMC_LOGIC_EXPORT copy
+class copy
{
public:
copy(const QString & src, const QString & dst)
@@ -81,13 +80,13 @@ class MULTIMC_LOGIC_EXPORT copy
/**
* Delete a folder recursively
*/
-MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
+bool deletePath(QString path);
-MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2);
-MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
-MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
+QString PathCombine(const QString &path1, const QString &path2);
+QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
+QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
-MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
+QString AbsolutePath(QString path);
/**
* Resolve an executable
@@ -99,7 +98,7 @@ MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
*
* @return absolute path to executable or null string
*/
-MULTIMC_LOGIC_EXPORT QString ResolveExecutable(QString path);
+QString ResolveExecutable(QString path);
/**
* Normalize path
@@ -109,20 +108,20 @@ MULTIMC_LOGIC_EXPORT QString ResolveExecutable(QString path);
*
* Returns false if the path logic somehow filed (and normalizedPath in invalid)
*/
-MULTIMC_LOGIC_EXPORT QString NormalizePath(QString path);
+QString NormalizePath(QString path);
-MULTIMC_LOGIC_EXPORT QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
+QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
-MULTIMC_LOGIC_EXPORT QString DirNameFromString(QString string, QString inDir = ".");
+QString DirNameFromString(QString string, QString inDir = ".");
/// Checks if the a given Path contains "!"
-MULTIMC_LOGIC_EXPORT bool checkProblemticPathJava(QDir folder);
+bool checkProblemticPathJava(QDir folder);
// Get the Directory representing the User's Desktop
-MULTIMC_LOGIC_EXPORT QString getDesktopDir();
+QString getDesktopDir();
// Create a shortcut at *location*, pointing to *dest* called with the arguments *args*
// call it *name* and assign it the icon *icon*
// return true if operation succeeded
-MULTIMC_LOGIC_EXPORT bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
+bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
}
diff --git a/api/logic/FileSystem_test.cpp b/launcher/FileSystem_test.cpp
similarity index 100%
rename from api/logic/FileSystem_test.cpp
rename to launcher/FileSystem_test.cpp
diff --git a/api/logic/Filter.cpp b/launcher/Filter.cpp
similarity index 100%
rename from api/logic/Filter.cpp
rename to launcher/Filter.cpp
diff --git a/api/logic/Filter.h b/launcher/Filter.h
similarity index 74%
rename from api/logic/Filter.h
rename to launcher/Filter.h
index 1ba48e482a..b55067acb1 100644
--- a/api/logic/Filter.h
+++ b/launcher/Filter.h
@@ -3,16 +3,14 @@
#include
#include
-#include "multimc_logic_export.h"
-
-class MULTIMC_LOGIC_EXPORT Filter
+class Filter
{
public:
virtual ~Filter();
virtual bool accepts(const QString & value) = 0;
};
-class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter
+class ContainsFilter: public Filter
{
public:
ContainsFilter(const QString &pattern);
@@ -22,7 +20,7 @@ class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter
QString pattern;
};
-class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter
+class ExactFilter: public Filter
{
public:
ExactFilter(const QString &pattern);
@@ -32,7 +30,7 @@ class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter
QString pattern;
};
-class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter
+class RegexpFilter: public Filter
{
public:
RegexpFilter(const QString ®exp, bool invert);
diff --git a/api/logic/GZip.cpp b/launcher/GZip.cpp
similarity index 100%
rename from api/logic/GZip.cpp
rename to launcher/GZip.cpp
diff --git a/api/logic/GZip.h b/launcher/GZip.h
similarity index 77%
rename from api/logic/GZip.h
rename to launcher/GZip.h
index c7eddbb300..7d4b1c3323 100644
--- a/api/logic/GZip.h
+++ b/launcher/GZip.h
@@ -1,9 +1,7 @@
#pragma once
#include
-#include "multimc_logic_export.h"
-
-class MULTIMC_LOGIC_EXPORT GZip
+class GZip
{
public:
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
diff --git a/api/logic/GZip_test.cpp b/launcher/GZip_test.cpp
similarity index 100%
rename from api/logic/GZip_test.cpp
rename to launcher/GZip_test.cpp
diff --git a/application/GuiUtil.cpp b/launcher/GuiUtil.cpp
similarity index 100%
rename from application/GuiUtil.cpp
rename to launcher/GuiUtil.cpp
diff --git a/application/GuiUtil.h b/launcher/GuiUtil.h
similarity index 100%
rename from application/GuiUtil.h
rename to launcher/GuiUtil.h
diff --git a/application/HoeDown.h b/launcher/HoeDown.h
similarity index 100%
rename from application/HoeDown.h
rename to launcher/HoeDown.h
diff --git a/api/logic/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
similarity index 100%
rename from api/logic/InstanceCopyTask.cpp
rename to launcher/InstanceCopyTask.cpp
diff --git a/api/logic/InstanceCopyTask.h b/launcher/InstanceCopyTask.h
similarity index 87%
rename from api/logic/InstanceCopyTask.h
rename to launcher/InstanceCopyTask.h
index 6465e92d8f..8290173265 100644
--- a/api/logic/InstanceCopyTask.h
+++ b/launcher/InstanceCopyTask.h
@@ -1,7 +1,6 @@
#pragma once
#include "tasks/Task.h"
-#include "multimc_logic_export.h"
#include "net/NetJob.h"
#include
#include
@@ -11,7 +10,7 @@
#include "BaseInstance.h"
#include "InstanceTask.h"
-class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
+class InstanceCopyTask : public InstanceTask
{
Q_OBJECT
public:
diff --git a/api/logic/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp
similarity index 100%
rename from api/logic/InstanceCreationTask.cpp
rename to launcher/InstanceCreationTask.cpp
diff --git a/api/logic/InstanceCreationTask.h b/launcher/InstanceCreationTask.h
similarity index 78%
rename from api/logic/InstanceCreationTask.h
rename to launcher/InstanceCreationTask.h
index 154a854f8c..549971164b 100644
--- a/api/logic/InstanceCreationTask.h
+++ b/launcher/InstanceCreationTask.h
@@ -1,14 +1,13 @@
#pragma once
#include "tasks/Task.h"
-#include "multimc_logic_export.h"
#include "net/NetJob.h"
#include
#include "settings/SettingsObject.h"
#include "BaseVersion.h"
#include "InstanceTask.h"
-class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask
+class InstanceCreationTask : public InstanceTask
{
Q_OBJECT
public:
diff --git a/api/logic/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
similarity index 98%
rename from api/logic/InstanceImportTask.cpp
rename to launcher/InstanceImportTask.cpp
index fe2cdd7533..3eac4d5794 100644
--- a/api/logic/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -238,6 +238,7 @@ void InstanceImportTask::processFlame()
}
QString forgeVersion;
+ QString fabricVersion;
for(auto &loader: pack.minecraft.modLoaders)
{
auto id = loader.id;
@@ -247,6 +248,12 @@ void InstanceImportTask::processFlame()
forgeVersion = id;
continue;
}
+ if(id.startsWith("fabric-"))
+ {
+ id.remove("fabric-");
+ fabricVersion = id;
+ continue;
+ }
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
}
@@ -281,6 +288,10 @@ void InstanceImportTask::processFlame()
}
components->setComponentVersion("net.minecraftforge", forgeVersion);
}
+ if(!fabricVersion.isEmpty())
+ {
+ components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
+ }
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
diff --git a/api/logic/InstanceImportTask.h b/launcher/InstanceImportTask.h
similarity index 94%
rename from api/logic/InstanceImportTask.h
rename to launcher/InstanceImportTask.h
index 7291324d3c..72ae6851a8 100644
--- a/api/logic/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -16,7 +16,6 @@
#pragma once
#include "InstanceTask.h"
-#include "multimc_logic_export.h"
#include "net/NetJob.h"
#include
#include
@@ -32,7 +31,7 @@ namespace Flame
class FileResolvingTask;
}
-class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask
+class InstanceImportTask : public InstanceTask
{
Q_OBJECT
public:
diff --git a/api/logic/InstanceList.cpp b/launcher/InstanceList.cpp
similarity index 98%
rename from api/logic/InstanceList.cpp
rename to launcher/InstanceList.cpp
index 02fae6ac53..cb38853b55 100644
--- a/api/logic/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -387,9 +387,19 @@ InstanceList::InstListError InstanceList::loadList()
add(newList);
}
m_dirty = false;
+ updateTotalPlayTime();
return NoError;
}
+void InstanceList::updateTotalPlayTime()
+{
+ totalPlayTime = 0;
+ for(auto const& itr : m_instances)
+ {
+ totalPlayTime += itr.get()->totalTimePlayed();
+ }
+}
+
void InstanceList::saveNow()
{
for(auto & item: m_instances)
@@ -475,6 +485,7 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
if (i != -1)
{
emit dataChanged(index(i), index(i));
+ updateTotalPlayTime();
}
}
@@ -848,4 +859,9 @@ bool InstanceList::destroyStagingPath(const QString& keyPath)
return FS::deletePath(keyPath);
}
-#include "InstanceList.moc"
\ No newline at end of file
+int InstanceList::getTotalPlayTime() {
+ updateTotalPlayTime();
+ return totalPlayTime;
+}
+
+#include "InstanceList.moc"
diff --git a/api/logic/InstanceList.h b/launcher/InstanceList.h
similarity index 97%
rename from api/logic/InstanceList.h
rename to launcher/InstanceList.h
index 8215cb665e..4d2dc1f60d 100644
--- a/api/logic/InstanceList.h
+++ b/launcher/InstanceList.h
@@ -22,8 +22,6 @@
#include "BaseInstance.h"
-#include "multimc_logic_export.h"
-
#include "QObjectPtr.h"
class QFileSystemWatcher;
@@ -49,7 +47,7 @@ enum class GroupsState
};
-class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
+class InstanceList : public QAbstractListModel
{
Q_OBJECT
@@ -128,6 +126,8 @@ class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
*/
bool destroyStagingPath(const QString & keyPath);
+ int getTotalPlayTime();
+
signals:
void dataIsInvalid();
void instancesChanged();
@@ -145,6 +145,7 @@ private slots:
private:
int getInstIndex(BaseInstance *inst) const;
+ void updateTotalPlayTime();
void suspendWatch();
void resumeWatch();
void add(const QList &list);
@@ -155,6 +156,7 @@ private slots:
private:
int m_watchLevel = 0;
+ int totalPlayTime = 0;
bool m_dirty = false;
QList m_instances;
QSet m_groupNameCache;
diff --git a/application/InstancePageProvider.h b/launcher/InstancePageProvider.h
similarity index 98%
rename from application/InstancePageProvider.h
rename to launcher/InstancePageProvider.h
index dde36aefb9..3cb723c403 100644
--- a/application/InstancePageProvider.h
+++ b/launcher/InstancePageProvider.h
@@ -46,7 +46,7 @@ class InstancePageProvider : public QObject, public BasePageProvider
values.append(new TexturePackPage(onesix.get()));
values.append(new NotesPage(onesix.get()));
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
- values.append(new ServersPage(onesix.get()));
+ values.append(new ServersPage(onesix));
// values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
values.append(new InstanceSettingsPage(onesix.get()));
diff --git a/application/InstanceProxyModel.cpp b/launcher/InstanceProxyModel.cpp
similarity index 100%
rename from application/InstanceProxyModel.cpp
rename to launcher/InstanceProxyModel.cpp
diff --git a/application/InstanceProxyModel.h b/launcher/InstanceProxyModel.h
similarity index 100%
rename from application/InstanceProxyModel.h
rename to launcher/InstanceProxyModel.h
diff --git a/api/logic/InstanceTask.cpp b/launcher/InstanceTask.cpp
similarity index 100%
rename from api/logic/InstanceTask.cpp
rename to launcher/InstanceTask.cpp
diff --git a/api/logic/InstanceTask.h b/launcher/InstanceTask.h
similarity index 91%
rename from api/logic/InstanceTask.h
rename to launcher/InstanceTask.h
index c5f6c7fd36..82e23f1114 100644
--- a/api/logic/InstanceTask.h
+++ b/launcher/InstanceTask.h
@@ -1,10 +1,9 @@
#pragma once
#include "tasks/Task.h"
-#include "multimc_logic_export.h"
#include "settings/SettingsObject.h"
-class MULTIMC_LOGIC_EXPORT InstanceTask : public Task
+class InstanceTask : public Task
{
Q_OBJECT
public:
diff --git a/application/InstanceWindow.cpp b/launcher/InstanceWindow.cpp
similarity index 100%
rename from application/InstanceWindow.cpp
rename to launcher/InstanceWindow.cpp
diff --git a/application/InstanceWindow.h b/launcher/InstanceWindow.h
similarity index 100%
rename from application/InstanceWindow.h
rename to launcher/InstanceWindow.h
diff --git a/application/JavaCommon.cpp b/launcher/JavaCommon.cpp
similarity index 100%
rename from application/JavaCommon.cpp
rename to launcher/JavaCommon.cpp
diff --git a/application/JavaCommon.h b/launcher/JavaCommon.h
similarity index 100%
rename from application/JavaCommon.h
rename to launcher/JavaCommon.h
diff --git a/api/logic/Json.cpp b/launcher/Json.cpp
similarity index 100%
rename from api/logic/Json.cpp
rename to launcher/Json.cpp
diff --git a/api/logic/Json.h b/launcher/Json.h
similarity index 72%
rename from api/logic/Json.h
rename to launcher/Json.h
index 34ff6fe2b3..f2e68f0c28 100644
--- a/api/logic/Json.h
+++ b/launcher/Json.h
@@ -16,32 +16,32 @@
namespace Json
{
-class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception
+class JsonException : public ::Exception
{
public:
JsonException(const QString &message) : Exception(message) {}
};
/// @throw FileSystemException
-MULTIMC_LOGIC_EXPORT void write(const QJsonDocument &doc, const QString &filename);
+void write(const QJsonDocument &doc, const QString &filename);
/// @throw FileSystemException
-MULTIMC_LOGIC_EXPORT void write(const QJsonObject &object, const QString &filename);
+void write(const QJsonObject &object, const QString &filename);
/// @throw FileSystemException
-MULTIMC_LOGIC_EXPORT void write(const QJsonArray &array, const QString &filename);
+void write(const QJsonArray &array, const QString &filename);
-MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonObject &obj);
-MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonArray &array);
-MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonObject &obj);
-MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonArray &array);
+QByteArray toBinary(const QJsonObject &obj);
+QByteArray toBinary(const QJsonArray &array);
+QByteArray toText(const QJsonObject &obj);
+QByteArray toText(const QJsonArray &array);
/// @throw JsonException
-MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
+QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
/// @throw JsonException
-MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
+QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
/// @throw JsonException
-MULTIMC_LOGIC_EXPORT QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
+QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
/// @throw JsonException
-MULTIMC_LOGIC_EXPORT QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
+QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
/////////////////// WRITING ////////////////////
@@ -84,31 +84,31 @@ template
T requireIsType(const QJsonValue &value, const QString &what = "Value");
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT double requireIsType(const QJsonValue &value, const QString &what);
+template<> double requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT bool requireIsType(const QJsonValue &value, const QString &what);
+template<> bool requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT int requireIsType(const QJsonValue &value, const QString &what);
+template<> int requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QJsonObject requireIsType(const QJsonValue &value, const QString &what);
+template<> QJsonObject requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QJsonArray requireIsType(const QJsonValue &value, const QString &what);
+template<> QJsonArray requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QJsonValue requireIsType(const QJsonValue &value, const QString &what);
+template<> QJsonValue requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QByteArray requireIsType(const QJsonValue &value, const QString &what);
+template<> QByteArray requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QDateTime requireIsType(const QJsonValue &value, const QString &what);
+template<> QDateTime requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QVariant requireIsType(const QJsonValue &value, const QString &what);
+template<> QVariant requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QString requireIsType(const QJsonValue &value, const QString &what);
+template<> QString requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QUuid requireIsType(const QJsonValue &value, const QString &what);
+template<> QUuid requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QDir requireIsType(const QJsonValue &value, const QString &what);
+template<> QDir requireIsType(const QJsonValue &value, const QString &what);
/// @throw JsonException
-template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType(const QJsonValue &value, const QString &what);
+template<> QUrl requireIsType(const QJsonValue &value, const QString &what);
// the following functions are higher level functions, that make use of the above functions for
// type conversion
diff --git a/application/KonamiCode.cpp b/launcher/KonamiCode.cpp
similarity index 92%
rename from application/KonamiCode.cpp
rename to launcher/KonamiCode.cpp
index 4c5af83713..46a2a0b2e7 100644
--- a/application/KonamiCode.cpp
+++ b/launcher/KonamiCode.cpp
@@ -35,7 +35,7 @@ void KonamiCode::input(QEvent* event)
{
m_progress = 0;
}
- if(m_progress == konamiCode.size())
+ if(m_progress == static_cast(konamiCode.size()))
{
m_progress = 0;
emit triggered();
diff --git a/application/KonamiCode.h b/launcher/KonamiCode.h
similarity index 100%
rename from application/KonamiCode.h
rename to launcher/KonamiCode.h
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
new file mode 100644
index 0000000000..833bfc59bf
--- /dev/null
+++ b/launcher/LaunchController.cpp
@@ -0,0 +1,399 @@
+#include "LaunchController.h"
+#include "MainWindow.h"
+#include
+#include "MultiMC.h"
+#include "dialogs/CustomMessageBox.h"
+#include "dialogs/ProfileSelectDialog.h"
+#include "dialogs/ProgressDialog.h"
+#include "dialogs/EditAccountDialog.h"
+#include "InstanceWindow.h"
+#include "BuildConfig.h"
+#include "JavaCommon.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+LaunchController::LaunchController(QObject *parent) : Task(parent)
+{
+}
+
+void LaunchController::executeTask()
+{
+ if (!m_instance)
+ {
+ emitFailed(tr("No instance specified!"));
+ return;
+ }
+
+ login();
+}
+
+// FIXME: minecraft specific
+void LaunchController::login() {
+ JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget);
+
+ // Find an account to use.
+ std::shared_ptr accounts = MMC->accounts();
+ if (accounts->count() <= 0)
+ {
+ // Tell the user they need to log in at least one account in order to play.
+ auto reply = CustomMessageBox::selectable(
+ m_parentWidget,
+ tr("No Accounts"),
+ tr("In order to play Minecraft, you must have at least one Mojang or Minecraft "
+ "account logged in to MultiMC."
+ "Would you like to open the account manager to add an account now?"),
+ QMessageBox::Information,
+ QMessageBox::Yes | QMessageBox::No
+ )->exec();
+
+ if (reply == QMessageBox::Yes)
+ {
+ // Open the account manager.
+ MMC->ShowGlobalSettings(m_parentWidget, "accounts");
+ }
+ }
+
+ MinecraftAccountPtr account = accounts->activeAccount();
+ if (account.get() == nullptr)
+ {
+ // If no default account is set, ask the user which one to use.
+ ProfileSelectDialog selectDialog(
+ tr("Which account would you like to use?"),
+ ProfileSelectDialog::GlobalDefaultCheckbox,
+ m_parentWidget
+ );
+
+ selectDialog.exec();
+
+ // Launch the instance with the selected account.
+ account = selectDialog.selectedAccount();
+
+ // If the user said to use the account as default, do that.
+ if (selectDialog.useAsGlobalDefault() && account.get() != nullptr) {
+ accounts->setActiveAccount(account->profileId());
+ }
+ }
+
+ // if no account is selected, we bail
+ if (!account.get())
+ {
+ emitFailed(tr("No account selected for launch."));
+ return;
+ }
+
+ if(account->accessToken() != "ff64ff64ff64ff64ff64ff64ff64ff64") {
+ // Online
+ // we try empty password first :)
+ QString password;
+ // we loop until the user succeeds in logging in or gives up
+ bool tryagain = true;
+ // the failure. the default failure.
+ const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. This could be caused by a password change.");
+ QString failReason = needLoginAgain;
+
+ while (tryagain)
+ {
+ m_session = std::make_shared();
+ m_session->wants_online = m_online;
+ auto task = account->login(m_session, password);
+ // We'll need to validate the access token to make sure the account
+ // is still logged in.
+ ProgressDialog progDialog(m_parentWidget);
+ if (task)
+ {
+ progDialog.setSkipButton(true, tr("Play Offline"));
+ }
+ progDialog.execWithTask(task.get());
+ if (!task->wasSuccessful())
+ {
+ auto failReasonNew = task->failReason();
+ if(failReasonNew == "Invalid token." || failReasonNew == "Invalid Signature")
+ {
+ // account->invalidateClientToken();
+ failReason = needLoginAgain;
+ }
+ else failReason = failReasonNew;
+ }
+ }
+ switch (m_session->status)
+ {
+ case AuthSession::Undetermined: {
+ qCritical() << "Received undetermined session status during login. Bye.";
+ tryagain = false;
+ emitFailed(tr("Received undetermined session status during login."));
+ return;
+ }
+ case AuthSession::RequiresPassword: {
+ // FIXME: this needs to understand MSA
+ EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField);
+ auto username = m_session->username;
+ auto chopN = [](QString toChop, int N) -> QString
+ {
+ if(toChop.size() > N)
+ {
+ auto left = toChop.left(N);
+ left += QString("\u25CF").repeated(toChop.size() - N);
+ return left;
+ }
+ return toChop;
+ };
+
+ if(username.contains('@'))
+ {
+ auto parts = username.split('@');
+ auto mailbox = chopN(parts[0],3);
+ QString domain = chopN(parts[1], 3);
+ username = mailbox + '@' + domain;
+ }
+ passDialog.setUsername(username);
+ if (passDialog.exec() == QDialog::Accepted)
+ {
+ password = passDialog.password();
+ }
+ else
+ {
+ tryagain = false;
+ emitFailed(tr("Received undetermined session status during login."));
+ }
+ break;
+ }
+ case AuthSession::RequiresOAuth: {
+ auto errorString = tr("Microsoft account has expired and needs to be logged into manually again.");
+ QMessageBox::warning(
+ nullptr,
+ tr("Microsoft Account refresh failed"),
+ errorString,
+ QMessageBox::StandardButton::Ok,
+ QMessageBox::StandardButton::Ok
+ );
+ tryagain = false;
+ emitFailed(errorString);
+ return;
+ }
+ case AuthSession::GoneOrMigrated: {
+ auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to.");
+ QMessageBox::warning(
+ nullptr,
+ tr("Account gone"),
+ errorString,
+ QMessageBox::StandardButton::Ok,
+ QMessageBox::StandardButton::Ok
+ );
+ tryagain = false;
+ emitFailed(errorString);
+ return;
+ }
+ case AuthSession::PlayableOffline: {
+ // we ask the user for a player name
+ bool ok = false;
+ QString usedname = m_session->player_name;
+ QString name = QInputDialog::getText(
+ m_parentWidget,
+ tr("Player name"),
+ tr("Choose your offline mode player name."),
+ QLineEdit::Normal,
+ m_session->player_name,
+ &ok
+ );
+ if (!ok)
+ {
+ tryagain = false;
+ break;
+ }
+ if (name.length())
+ {
+ usedname = name;
+ }
+ m_session->MakeOffline(usedname);
+ // offline flavored game from here :3
+ }
+ case AuthSession::PlayableOnline:
+ {
+ launchInstance();
+ tryagain = false;
+ return;
+ }
+ }
+ emitFailed(tr("Failed to launch."));
+ }else{
+ // Offline
+ m_session = std::make_shared();
+ m_session->client_token = account->accountData()->clientToken();
+ m_session->access_token = account->accessToken();
+ m_session->uuid = account->accountData()->minecraftProfile.id;
+ m_session->status = AuthSession::PlayableOffline;
+ m_session->MakeOffline(account->accountData()->userName());
+ launchInstance();
+ }
+}
+
+void LaunchController::launchInstance()
+{
+ Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
+ Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
+
+ if(!m_instance->reloadSettings())
+ {
+ QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile."));
+ emitFailed(tr("Couldn't load the instance profile."));
+ return;
+ }
+
+ m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin);
+ if (!m_launcher)
+ {
+ emitFailed(tr("Couldn't instantiate a launcher."));
+ return;
+ }
+
+ auto console = qobject_cast(m_parentWidget);
+ auto showConsole = m_instance->settings()->get("ShowConsole").toBool();
+ if(!console && showConsole)
+ {
+ MMC->showInstanceWindow(m_instance);
+ }
+ connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
+ connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded);
+ connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed);
+ connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested);
+
+ // Prepend Online and Auth Status
+ QString online_mode;
+ if(m_session->wants_online) {
+ online_mode = "online";
+
+ // Prepend Server Status
+ QStringList servers = {"authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com"};
+ QString resolved_servers = "";
+ QHostInfo host_info;
+
+ for(QString server : servers) {
+ host_info = QHostInfo::fromName(server);
+ resolved_servers = resolved_servers + server + " resolves to:\n [";
+ if(!host_info.addresses().isEmpty()) {
+ for(QHostAddress address : host_info.addresses()) {
+ resolved_servers = resolved_servers + address.toString();
+ if(!host_info.addresses().endsWith(address)) {
+ resolved_servers = resolved_servers + ", ";
+ }
+ }
+ } else {
+ resolved_servers = resolved_servers + "N/A";
+ }
+ resolved_servers = resolved_servers + "]\n\n";
+ }
+ m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::MultiMC));
+ } else {
+ online_mode = "offline";
+ }
+
+ QString auth_server_status;
+ if(m_session->auth_server_online) {
+ auth_server_status = "online";
+ } else {
+ auth_server_status = "offline";
+ }
+
+ m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\nAuthentication server is " + auth_server_status + "\n", MessageLevel::MultiMC));
+
+ // Prepend Version
+ m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC));
+ m_launcher->start();
+}
+
+void LaunchController::readyForLaunch()
+{
+ if (!m_profiler)
+ {
+ m_launcher->proceed();
+ return;
+ }
+
+ QString error;
+ if (!m_profiler->check(&error))
+ {
+ m_launcher->abort();
+ QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
+ emitFailed("Profiler startup failed!");
+ return;
+ }
+ BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
+
+ connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message)
+ {
+ QMessageBox msg;
+ msg.setText(tr("The game launch is delayed until you press the "
+ "button. This is the right time to setup the profiler, as the "
+ "profiler server is running now.\n\n%1").arg(message));
+ msg.setWindowTitle(tr("Waiting."));
+ msg.setIcon(QMessageBox::Information);
+ msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
+ msg.setModal(true);
+ msg.exec();
+ m_launcher->proceed();
+ });
+ connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message)
+ {
+ QMessageBox msg;
+ msg.setText(tr("Couldn't start the profiler: %1").arg(message));
+ msg.setWindowTitle(tr("Error"));
+ msg.setIcon(QMessageBox::Critical);
+ msg.addButton(QMessageBox::Ok);
+ msg.setModal(true);
+ msg.exec();
+ m_launcher->abort();
+ emitFailed("Profiler startup failed!");
+ });
+ profilerInstance->beginProfiling(m_launcher);
+}
+
+void LaunchController::onSucceeded()
+{
+ emitSucceeded();
+}
+
+void LaunchController::onFailed(QString reason)
+{
+ if(m_instance->settings()->get("ShowConsoleOnError").toBool())
+ {
+ MMC->showInstanceWindow(m_instance, "console");
+ }
+ emitFailed(reason);
+}
+
+void LaunchController::onProgressRequested(Task* task)
+{
+ ProgressDialog progDialog(m_parentWidget);
+ progDialog.setSkipButton(true, tr("Abort"));
+ m_launcher->proceed();
+ progDialog.execWithTask(task);
+}
+
+bool LaunchController::abort()
+{
+ if(!m_launcher)
+ {
+ return true;
+ }
+ if(!m_launcher->canAbort())
+ {
+ return false;
+ }
+ auto response = CustomMessageBox::selectable(
+ m_parentWidget, tr("Kill Minecraft?"),
+ tr("This can cause the instance to get corrupted and should only be used if Minecraft "
+ "is frozen for some reason"),
+ QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec();
+ if (response == QMessageBox::Yes)
+ {
+ return m_launcher->abort();
+ }
+ return false;
+}
diff --git a/application/LaunchController.h b/launcher/LaunchController.h
similarity index 84%
rename from application/LaunchController.h
rename to launcher/LaunchController.h
index 1d879028d9..5f177e0065 100644
--- a/application/LaunchController.h
+++ b/launcher/LaunchController.h
@@ -3,6 +3,8 @@
#include
#include
+#include "minecraft/launch/MinecraftServerTarget.h"
+
class InstanceWindow;
class LaunchController: public Task
{
@@ -33,6 +35,10 @@ class LaunchController: public Task
{
m_parentWidget = widget;
}
+ void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
+ {
+ m_serverToJoin = std::move(serverToJoin);
+ }
QString id()
{
return m_instance->id();
@@ -58,4 +64,5 @@ private slots:
InstanceWindow *m_console = nullptr;
AuthSessionPtr m_session;
shared_qobject_ptr m_launcher;
+ MinecraftServerTargetPtr m_serverToJoin;
};
diff --git a/api/logic/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
similarity index 100%
rename from api/logic/LoggedProcess.cpp
rename to launcher/LoggedProcess.cpp
diff --git a/api/logic/LoggedProcess.h b/launcher/LoggedProcess.h
similarity index 95%
rename from api/logic/LoggedProcess.h
rename to launcher/LoggedProcess.h
index 327cdc6ac7..e52b8a7ba9 100644
--- a/api/logic/LoggedProcess.h
+++ b/launcher/LoggedProcess.h
@@ -17,13 +17,12 @@
#include
#include "MessageLevel.h"
-#include "multimc_logic_export.h"
/*
* This is a basic process.
* It has line-based logging support and hides some of the nasty bits.
*/
-class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess
+class LoggedProcess : public QProcess
{
Q_OBJECT
public:
diff --git a/api/logic/MMCStrings.cpp b/launcher/MMCStrings.cpp
similarity index 100%
rename from api/logic/MMCStrings.cpp
rename to launcher/MMCStrings.cpp
diff --git a/launcher/MMCStrings.h b/launcher/MMCStrings.h
new file mode 100644
index 0000000000..48052a00e1
--- /dev/null
+++ b/launcher/MMCStrings.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include
+
+namespace Strings
+{
+ int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
+}
diff --git a/api/logic/MMCZip.cpp b/launcher/MMCZip.cpp
similarity index 100%
rename from api/logic/MMCZip.cpp
rename to launcher/MMCZip.cpp
diff --git a/api/logic/MMCZip.h b/launcher/MMCZip.h
similarity index 69%
rename from api/logic/MMCZip.h
rename to launcher/MMCZip.h
index 98d9cd5bdd..9c47fa11f2 100644
--- a/api/logic/MMCZip.h
+++ b/launcher/MMCZip.h
@@ -21,8 +21,6 @@
#include "minecraft/mod/Mod.h"
#include
-#include "multimc_logic_export.h"
-
#include
#include
@@ -32,20 +30,20 @@ namespace MMCZip
/**
* Merge two zip files, using a filter function
*/
- bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained,
+ bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained,
const JlCompress::FilterFunction filter = nullptr);
/**
* take a source jar, add mods to it, resulting in target jar
*/
- bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods);
+ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods);
/**
* Find a single file in archive by file name (not path)
*
* \return the path prefix where the file is
*/
- QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
+ QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
/**
* Find a multiple files of the same name in archive by file name
@@ -53,14 +51,14 @@ namespace MMCZip
*
* \return true if anything was found
*/
- bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
+ bool findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/**
* Extract a subdirectory from an archive
*/
- nonstd::optional MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
+ nonstd::optional extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
- bool MULTIMC_LOGIC_EXPORT extractRelFile(QuaZip *zip, const QString & file, const QString &target);
+ bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
/**
* Extract a whole archive.
@@ -69,7 +67,7 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
- nonstd::optional MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
+ nonstd::optional extractDir(QString fileCompressed, QString dir);
/**
* Extract a subdirectory from an archive
@@ -79,7 +77,7 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
- nonstd::optional MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString subdir, QString dir);
+ nonstd::optional extractDir(QString fileCompressed, QString subdir, QString dir);
/**
* Extract a single file from an archive into a directory
@@ -89,6 +87,6 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty.
* \return true for success or false for failure
*/
- bool MULTIMC_LOGIC_EXPORT extractFile(QString fileCompressed, QString file, QString dir);
+ bool extractFile(QString fileCompressed, QString file, QString dir);
}
diff --git a/application/MainWindow.cpp b/launcher/MainWindow.cpp
similarity index 92%
rename from application/MainWindow.cpp
rename to launcher/MainWindow.cpp
index 1286007d50..182b22e99f 100644
--- a/application/MainWindow.cpp
+++ b/launcher/MainWindow.cpp
@@ -54,7 +54,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -90,6 +90,20 @@
#include "KonamiCode.h"
#include
+namespace {
+QString profileInUseFilter(const QString & profile, bool used)
+{
+ if(used)
+ {
+ return QObject::tr("%1 (in use)").arg(profile);
+ }
+ else
+ {
+ return profile;
+ }
+}
+}
+
// WHY: to hold the pre-translation strings together with the T pointer, so it can be retranslated without a lot of ugly code
template
class Translated
@@ -306,29 +320,35 @@ class MainWindow::Ui
helpMenu = new QMenu(MainWindow);
helpMenu->setToolTipsVisible(true);
- actionReportBug = TranslatedAction(MainWindow);
- actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
- actionReportBug->setIcon(MMC->getThemedIcon("bug"));
- actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug"));
- actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC."));
- all_actions.append(&actionReportBug);
- helpMenu->addAction(actionReportBug);
-
- actionDISCORD = TranslatedAction(MainWindow);
- actionDISCORD->setObjectName(QStringLiteral("actionDISCORD"));
- actionDISCORD->setIcon(MMC->getThemedIcon("discord"));
- actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord"));
- actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat."));
- all_actions.append(&actionDISCORD);
- helpMenu->addAction(actionDISCORD);
-
- actionREDDIT = TranslatedAction(MainWindow);
- actionREDDIT->setObjectName(QStringLiteral("actionREDDIT"));
- actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien"));
- actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit"));
- actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit."));
- all_actions.append(&actionREDDIT);
- helpMenu->addAction(actionREDDIT);
+ if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
+ actionReportBug = TranslatedAction(MainWindow);
+ actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
+ actionReportBug->setIcon(MMC->getThemedIcon("bug"));
+ actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug"));
+ actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC."));
+ all_actions.append(&actionReportBug);
+ helpMenu->addAction(actionReportBug);
+ }
+
+ if (!BuildConfig.DISCORD_URL.isEmpty()) {
+ actionDISCORD = TranslatedAction(MainWindow);
+ actionDISCORD->setObjectName(QStringLiteral("actionDISCORD"));
+ actionDISCORD->setIcon(MMC->getThemedIcon("discord"));
+ actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord"));
+ actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat."));
+ all_actions.append(&actionDISCORD);
+ helpMenu->addAction(actionDISCORD);
+ }
+
+ if (!BuildConfig.SUBREDDIT_URL.isEmpty()) {
+ actionREDDIT = TranslatedAction(MainWindow);
+ actionREDDIT->setObjectName(QStringLiteral("actionREDDIT"));
+ actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien"));
+ actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit"));
+ actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit."));
+ all_actions.append(&actionREDDIT);
+ helpMenu->addAction(actionREDDIT);
+ }
actionAbout = TranslatedAction(MainWindow);
actionAbout->setObjectName(QStringLiteral("actionAbout"));
@@ -718,8 +738,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed);
m_statusLeft = new QLabel(tr("No instance selected"), this);
+ m_statusCenter = new QLabel(tr("Total playtime: 0s."), this);
m_statusRight = new ServerStatus(this);
statusBar()->addPermanentWidget(m_statusLeft, 1);
+ statusBar()->addPermanentWidget(m_statusCenter, 1);
statusBar()->addPermanentWidget(m_statusRight, 0);
// Add "manage accounts" button, right align
@@ -732,7 +754,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
repopulateAccountsMenu();
accountMenuButton = new QToolButton(this);
- accountMenuButton->setText(tr("Profiles"));
accountMenuButton->setMenu(accountMenu);
accountMenuButton->setPopupMode(QToolButton::InstantPopup);
accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
@@ -746,49 +767,27 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
// Update the menu when the active account changes.
// Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
// Template hell sucks...
- connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this]
- {
- activeAccountChanged();
- });
- connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this]
- {
- repopulateAccountsMenu();
- });
+ connect(
+ MMC->accounts().get(),
+ &AccountList::activeAccountChanged,
+ [this] {
+ activeAccountChanged();
+ }
+ );
+ connect(
+ MMC->accounts().get(),
+ &AccountList::listChanged,
+ [this]
+ {
+ repopulateAccountsMenu();
+ }
+ );
// Show initial account
activeAccountChanged();
- auto accounts = MMC->accounts();
-
- QList skin_dls;
- for (int i = 0; i < accounts->count(); i++)
- {
- auto account = accounts->at(i);
- if (!account)
- {
- qWarning() << "Null account at index" << i;
- continue;
- }
- for (auto profile : account->profiles())
- {
- auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png");
- auto action = Net::Download::makeCached(QUrl(BuildConfig.SKINS_BASE + profile.id + ".png"), meta);
- skin_dls.append(action);
- meta->setStale(true);
- }
- }
- if (!skin_dls.isEmpty())
- {
- auto job = new NetJob("Startup player skins download");
- connect(job, &NetJob::succeeded, this, &MainWindow::skinJobFinished);
- connect(job, &NetJob::failed, this, &MainWindow::skinJobFinished);
- for (auto action : skin_dls)
- {
- job->addNetAction(action);
- }
- skin_download_job.reset(job);
- job->start();
- }
+ // TODO: refresh accounts here?
+ // auto accounts = MMC->accounts();
// load the news
{
@@ -831,6 +830,29 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
// removing this looks stupid
view->setFocus();
+
+ retranslateUi();
+}
+
+void MainWindow::retranslateUi()
+{
+ std::shared_ptr accounts = MMC->accounts();
+ MinecraftAccountPtr active_account = accounts->activeAccount();
+ if(active_account) {
+ auto profileLabel = profileInUseFilter(active_account->profileName(), active_account->isInUse());
+ accountMenuButton->setText(profileLabel);
+ }
+ else {
+ accountMenuButton->setText(tr("Profiles"));
+ }
+
+ if (m_selectedInstance) {
+ m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
+ } else {
+ m_statusLeft->setText(tr("No instance selected"));
+ }
+
+ ui->retranslateUi(this);
}
MainWindow::~MainWindow()
@@ -850,12 +872,6 @@ void MainWindow::konamiTriggered()
qDebug() << "Super Secret Mode ACTIVATED!";
}
-void MainWindow::skinJobFinished()
-{
- activeAccountChanged();
- skin_download_job.reset();
-}
-
void MainWindow::showInstanceContextMenu(const QPoint &pos)
{
QList actions;
@@ -996,34 +1012,21 @@ void MainWindow::updateToolsMenu()
ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu);
}
-QString profileInUseFilter(const QString & profile, bool used)
-{
- if(used)
- {
- return profile + QObject::tr(" (in use)");
- }
- else
- {
- return profile;
- }
-}
-
void MainWindow::repopulateAccountsMenu()
{
accountMenu->clear();
- std::shared_ptr accounts = MMC->accounts();
- MojangAccountPtr active_account = accounts->activeAccount();
+ std::shared_ptr accounts = MMC->accounts();
+ MinecraftAccountPtr active_account = accounts->activeAccount();
- QString active_username = "";
+ QString active_profileId = "";
if (active_account != nullptr)
{
- active_username = active_account->username();
- const AccountProfile *profile = active_account->currentProfile();
+ active_profileId = active_account->profileId();
// this can be called before accountMenuButton exists
- if (profile != nullptr && accountMenuButton)
+ if (accountMenuButton)
{
- auto profileLabel = profileInUseFilter(profile->name, active_account->isInUse());
+ auto profileLabel = profileInUseFilter(active_account->profileName(), active_account->isInUse());
accountMenuButton->setText(profileLabel);
}
}
@@ -1039,22 +1042,19 @@ void MainWindow::repopulateAccountsMenu()
// TODO: Nicer way to iterate?
for (int i = 0; i < accounts->count(); i++)
{
- MojangAccountPtr account = accounts->at(i);
- for (auto profile : account->profiles())
+ MinecraftAccountPtr account = accounts->at(i);
+ auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse());
+ QAction *action = new QAction(profileLabel, this);
+ action->setData(account->profileId());
+ action->setCheckable(true);
+ if (active_profileId == account->profileId())
{
- auto profileLabel = profileInUseFilter(profile.name, account->isInUse());
- QAction *action = new QAction(profileLabel, this);
- action->setData(account->username());
- action->setCheckable(true);
- if (active_username == account->username())
- {
- action->setChecked(true);
- }
-
- action->setIcon(SkinUtils::getFaceFromCache(profile.id));
- accountMenu->addAction(action);
- connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
+ action->setChecked(true);
}
+
+ action->setIcon(account->getFace());
+ accountMenu->addAction(action);
+ connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
}
}
@@ -1064,8 +1064,7 @@ void MainWindow::repopulateAccountsMenu()
action->setCheckable(true);
action->setIcon(MMC->getThemedIcon("noaccount"));
action->setData("");
- if (active_username.isEmpty())
- {
+ if (active_profileId.isEmpty()) {
action->setChecked(true);
}
@@ -1112,18 +1111,15 @@ void MainWindow::activeAccountChanged()
{
repopulateAccountsMenu();
- MojangAccountPtr account = MMC->accounts()->activeAccount();
+ MinecraftAccountPtr account = MMC->accounts()->activeAccount();
- if (account != nullptr && account->username() != "")
+ // FIXME: this needs adjustment for MSA
+ if (account != nullptr && account->profileName() != "")
{
- const AccountProfile *profile = account->currentProfile();
- if (profile != nullptr)
- {
- auto profileLabel = profileInUseFilter(profile->name, account->isInUse());
- accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->id));
- accountMenuButton->setText(profileLabel);
- return;
- }
+ auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse());
+ accountMenuButton->setText(profileLabel);
+ accountMenuButton->setIcon(account->getFace());
+ return;
}
// Set the icon to the "no account" icon.
@@ -1307,7 +1303,6 @@ void MainWindow::setCatBackground(bool enabled)
{
QDateTime now = QDateTime::currentDateTime();
QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0));
- ;
QString cat = (non_stupid_abs(now.daysTo(xmas)) <= 4) ? "catmas" : "kitteh";
view->setStyleSheet(QString(R"(
GroupView
@@ -1455,12 +1450,12 @@ void MainWindow::droppedURLs(QList urls)
void MainWindow::on_actionREDDIT_triggered()
{
- DesktopServices::openUrl(QUrl("https://www.reddit.com/r/MultiMC/"));
+ DesktopServices::openUrl(QUrl(BuildConfig.SUBREDDIT_URL));
}
void MainWindow::on_actionDISCORD_triggered()
{
- DesktopServices::openUrl(QUrl("https://discord.gg/multimc"));
+ DesktopServices::openUrl(QUrl(BuildConfig.DISCORD_URL));
}
void MainWindow::on_actionChangeInstIcon_triggered()
@@ -1506,6 +1501,7 @@ void MainWindow::setSelectedInstanceById(const QString &id)
{
QModelIndex selectionIndex = proxymodel->mapFromSource(index);
view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect);
+ updateStatusCenter();
}
}
@@ -1638,7 +1634,7 @@ void MainWindow::on_actionManageAccounts_triggered()
void MainWindow::on_actionReportBug_triggered()
{
- DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/issues"));
+ DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL));
}
void MainWindow::on_actionPatreon_triggered()
@@ -1745,7 +1741,7 @@ void MainWindow::changeEvent(QEvent* event)
{
if (event->type() == QEvent::LanguageChange)
{
- ui->retranslateUi(this);
+ retranslateUi();
}
QMainWindow::changeEvent(event);
}
@@ -1834,6 +1830,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
ui->renameButton->setText(m_selectedInstance->name());
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
+ updateStatusCenter();
updateInstanceToolIcon(m_selectedInstance->iconKey());
updateToolsMenu();
@@ -1912,3 +1909,18 @@ void MainWindow::checkInstancePathForProblems()
warning.exec();
}
}
+
+void MainWindow::updateStatusCenter()
+{
+ int timeplayed = MMC->instances()->getTotalPlayTime();
+ int minutesTotal = timeplayed / 60;
+ int seconds = timeplayed % 60;
+ int minutes = minutesTotal % 60;
+ int hours = minutesTotal / 60;
+ if(hours != 0)
+ m_statusCenter->setText(tr("Total playtime: %1h %2m %3s").arg(hours).arg(minutes).arg(seconds));
+ else if(minutes != 0)
+ m_statusCenter->setText(tr("Total playtime: %1m %2s").arg(minutes).arg(seconds));
+ else if(seconds != 0)
+ m_statusCenter->setText(tr("Total playtime: %1s").arg(seconds));
+}
diff --git a/application/MainWindow.h b/launcher/MainWindow.h
similarity index 97%
rename from application/MainWindow.h
rename to launcher/MainWindow.h
index 3d4114de9f..67dec8cfd4 100644
--- a/application/MainWindow.h
+++ b/launcher/MainWindow.h
@@ -22,7 +22,7 @@
#include
#include "BaseInstance.h"
-#include "minecraft/auth/MojangAccount.h"
+#include "minecraft/auth/MinecraftAccount.h"
#include "net/NetJob.h"
#include "updater/GoUpdate.h"
@@ -149,8 +149,6 @@ private slots:
void updateToolsMenu();
- void skinJobFinished();
-
void instanceActivated(QModelIndex);
void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous);
@@ -187,11 +185,14 @@ private slots:
void globalSettingsClosed();
private:
+ void retranslateUi();
+
void addInstance(QString url = QString());
void activateInstance(InstancePtr instance);
void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);
void setSelectedInstanceById(const QString &id);
+ void updateStatusCenter();
void runModalTask(Task *task);
void instanceFromInstanceTask(InstanceTask *task);
@@ -205,12 +206,12 @@ private slots:
InstanceProxyModel *proxymodel = nullptr;
QToolButton *newsLabel = nullptr;
QLabel *m_statusLeft = nullptr;
+ QLabel *m_statusCenter = nullptr;
ServerStatus *m_statusRight = nullptr;
QMenu *accountMenu = nullptr;
QToolButton *accountMenuButton = nullptr;
KonamiCode * secretEventFilter = nullptr;
- unique_qobject_ptr skin_download_job;
unique_qobject_ptr m_newsChecker;
unique_qobject_ptr m_notificationChecker;
diff --git a/api/logic/MessageLevel.cpp b/launcher/MessageLevel.cpp
similarity index 100%
rename from api/logic/MessageLevel.cpp
rename to launcher/MessageLevel.cpp
diff --git a/api/logic/MessageLevel.h b/launcher/MessageLevel.h
similarity index 100%
rename from api/logic/MessageLevel.h
rename to launcher/MessageLevel.h
diff --git a/application/MultiMC.cpp b/launcher/MultiMC.cpp
similarity index 84%
rename from application/MultiMC.cpp
rename to launcher/MultiMC.cpp
index 22946e08c2..c532ce8264 100644
--- a/application/MultiMC.cpp
+++ b/launcher/MultiMC.cpp
@@ -42,7 +42,7 @@
#include "dialogs/CustomMessageBox.h"
#include "InstanceList.h"
-#include
+#include
#include "icons/IconList.h"
#include "net/HttpMetaCache.h"
#include "Env.h"
@@ -91,7 +91,8 @@ using namespace Commandline;
"This usually fixes the problem and you can move the application elsewhere afterwards.\n"\
"\n"
-static void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+namespace {
+void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
const char *levels = "DWCFIS";
const QString format("%1 %2 %3\n");
@@ -111,6 +112,47 @@ static void appDebugOutput(QtMsgType type, const QMessageLogContext &context, co
fflush(stderr);
}
+QString getIdealPlatform(QString currentPlatform) {
+ auto info = Sys::getKernelInfo();
+ switch(info.kernelType) {
+ case Sys::KernelType::Darwin: {
+ if(info.kernelMajor >= 17) {
+ // macOS 10.13 or newer
+ return "osx64-5.15.2";
+ }
+ else {
+ // macOS 10.12 or older
+ return "osx64";
+ }
+ }
+ case Sys::KernelType::Windows: {
+ // FIXME: 5.15.2 is not stable on Windows, due to a large number of completely unpredictable and hard to reproduce issues
+ break;
+/*
+ if(info.kernelMajor == 6 && info.kernelMinor >= 1) {
+ // Windows 7
+ return "win32-5.15.2";
+ }
+ else if (info.kernelMajor > 6) {
+ // Above Windows 7
+ return "win32-5.15.2";
+ }
+ else {
+ // Below Windows 7
+ return "win32";
+ }
+*/
+ }
+ case Sys::KernelType::Undetermined:
+ case Sys::KernelType::Linux: {
+ break;
+ }
+ }
+ return currentPlatform;
+}
+
+}
+
MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
@@ -191,6 +233,11 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
parser.addOption("launch");
parser.addShortOpt("launch", 'l');
parser.addDocumentation("launch", "Launch the specified instance (by instance ID)");
+ // --server
+ parser.addOption("server");
+ parser.addShortOpt("server", 's');
+ parser.addDocumentation("server", "Join the specified server on launch "
+ "(only valid in combination with --launch)");
// --alive
parser.addSwitch("alive");
parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts");
@@ -232,6 +279,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
}
m_instanceIdToLaunch = args["launch"].toString();
+ m_serverToJoin = args["server"].toString();
m_liveCheck = args["alive"].toBool();
m_zipToImport = args["import"].toUrl();
@@ -256,6 +304,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
dataPath = xdgDataHome + "/multimc";
adjustedBy += "XDG standard " + dataPath;
+#elif defined(Q_OS_MAC)
+ QDir foo(FS::PathCombine(applicationDirPath(), "../../Data"));
+ dataPath = foo.absolutePath();
+ adjustedBy += "Fallback to special Mac location " + dataPath;
#else
dataPath = applicationDirPath();
adjustedBy += "Fallback to binary path " + dataPath;
@@ -293,6 +345,70 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
return;
}
+ if(m_instanceIdToLaunch.isEmpty() && !m_serverToJoin.isEmpty())
+ {
+ std::cerr << "--server can only be used in combination with --launch!" << std::endl;
+ m_status = MultiMC::Failed;
+ return;
+ }
+
+#if defined(Q_OS_MAC)
+ // move user data to new location if on macOS and it still exists in Contents/MacOS
+ QDir fi(applicationDirPath());
+ QString originalData = fi.absolutePath();
+ // if the config file exists in Contents/MacOS, then user data is still there and needs to moved
+ if (QFileInfo::exists(FS::PathCombine(originalData, "multimc.cfg")))
+ {
+ if (!QFileInfo::exists(FS::PathCombine(originalData, "dontmovemacdata")))
+ {
+ QMessageBox::StandardButton askMoveDialogue;
+ askMoveDialogue = QMessageBox::question(nullptr, "MultiMC 5", "Would you like to move application data to a new data location? It will improve MultiMC's performance, but if you switch to older versions it will look like instances have disappeared. If you select no, you can migrate later in settings. You should select yes unless you're commonly switching between different versions of MultiMC (eg. develop and stable).", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+ if (askMoveDialogue == QMessageBox::Yes)
+ {
+ qDebug() << "On macOS and found config file in old location, moving user data...";
+ QDir dir;
+ QStringList dataFiles {
+ "*.log", // MultiMC-@.log
+ "accounts.json",
+ "accounts",
+ "assets",
+ "cache",
+ "icons",
+ "instances",
+ "libraries",
+ "meta",
+ "metacache",
+ "mods",
+ "multimc.cfg",
+ "themes",
+ "translations"
+ };
+ QDirIterator files(originalData, dataFiles);
+ while (files.hasNext()) {
+ QString filePath(files.next());
+ QString fileName(files.fileName());
+ if (!dir.rename(filePath, FS::PathCombine(dataPath, fileName)))
+ {
+ qWarning() << "Failed to move " << fileName;
+ }
+ }
+ }
+ else
+ {
+ dataPath = originalData;
+ QDir::setCurrent(dataPath);
+ QFile file(originalData + "/dontmovemacdata");
+ file.open(QIODevice::WriteOnly);
+ }
+ }
+ else
+ {
+ dataPath = originalData;
+ QDir::setCurrent(dataPath);
+ }
+ }
+#endif
+
/*
* Establish the mechanism for communication with an already running MultiMC that uses the same data path.
* If there is one, tell it what the user actually wanted to do and exit.
@@ -318,7 +434,15 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
else
{
- m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout);
+ if(!m_serverToJoin.isEmpty())
+ {
+ m_peerInstance->sendMessage(
+ "launch-with-server " + m_instanceIdToLaunch + " " + m_serverToJoin, timeout);
+ }
+ else
+ {
+ m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout);
+ }
}
m_status = MultiMC::Succeeded;
return;
@@ -399,6 +523,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
{
qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
}
+ if(!m_serverToJoin.isEmpty())
+ {
+ qDebug() << "Address of server to join :" << m_serverToJoin;
+ }
qDebug() << "<> Paths set.";
}
@@ -514,6 +642,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("UseNativeGLFW", false);
+ // Game time
+ m_settings->registerSetting("ShowGameTime", true);
+ m_settings->registerSetting("RecordGameTime", true);
+
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
@@ -588,7 +720,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
// initialize the updater
if(BuildConfig.UPDATER_ENABLED)
{
- m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
+ auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
+ auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
+ qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
+ m_updateChecker.reset(new UpdateChecker(channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
qDebug() << "<> Updater started.";
}
@@ -655,7 +790,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
// and accounts
{
- m_accounts.reset(new MojangAccountList(this));
+ m_accounts.reset(new AccountList(this));
qDebug() << "Loading accounts...";
m_accounts->setListFilePath("accounts.json", true);
m_accounts->loadList();
@@ -840,8 +975,19 @@ void MultiMC::performMainStartupAction()
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if(inst)
{
- qDebug() << "<> Instance launching:" << m_instanceIdToLaunch;
- launch(inst, true, nullptr);
+ MinecraftServerTargetPtr serverToJoin = nullptr;
+
+ if(!m_serverToJoin.isEmpty())
+ {
+ serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin)));
+ qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching with server" << m_serverToJoin;
+ }
+ else
+ {
+ qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
+ }
+
+ launch(inst, true, nullptr, serverToJoin);
return;
}
}
@@ -923,6 +1069,31 @@ void MultiMC::messageReceived(const QString& message)
launch(inst, true, nullptr);
}
}
+ else if(command == "launch-with-server")
+ {
+ QString instanceID = message.section(' ', 1, 1);
+ QString serverToJoin = message.section(' ', 2, 2);
+ if(instanceID.isEmpty())
+ {
+ qWarning() << "Received" << command << "message without an instance ID.";
+ return;
+ }
+ if(serverToJoin.isEmpty())
+ {
+ qWarning() << "Received" << command << "message without a server to join.";
+ return;
+ }
+ auto inst = instances()->getInstanceById(instanceID);
+ if(inst)
+ {
+ launch(
+ inst,
+ true,
+ nullptr,
+ std::make_shared(MinecraftServerTarget::parse(serverToJoin))
+ );
+ }
+ }
else
{
qWarning() << "Received invalid message" << message;
@@ -1010,8 +1181,12 @@ bool MultiMC::openJsonEditor(const QString &filename)
}
}
-bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler)
-{
+bool MultiMC::launch(
+ InstancePtr instance,
+ bool online,
+ BaseProfilerFactory *profiler,
+ MinecraftServerTargetPtr serverToJoin
+) {
if(m_updateRunning)
{
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@@ -1032,6 +1207,7 @@ bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *pro
controller->setInstance(instance);
controller->setOnline(online);
controller->setProfiler(profiler);
+ controller->setServerToJoin(serverToJoin);
if(window)
{
controller->setParentWidget(window);
diff --git a/application/MultiMC.h b/launcher/MultiMC.h
similarity index 93%
rename from application/MultiMC.h
rename to launcher/MultiMC.h
index e6588a1470..59fd7345f0 100644
--- a/application/MultiMC.h
+++ b/launcher/MultiMC.h
@@ -11,6 +11,8 @@
#include
+#include "minecraft/launch/MinecraftServerTarget.h"
+
class LaunchController;
class LocalPeer;
class InstanceWindow;
@@ -22,7 +24,7 @@ class QFile;
class HttpMetaCache;
class SettingsObject;
class InstanceList;
-class MojangAccountList;
+class AccountList;
class IconList;
class QNetworkAccessManager;
class JavaInstallList;
@@ -109,7 +111,7 @@ class MultiMC : public QApplication
return m_mcedit.get();
}
- std::shared_ptr accounts() const
+ std::shared_ptr accounts() const
{
return m_accounts;
}
@@ -150,7 +152,12 @@ class MultiMC : public QApplication
void globalSettingsClosed();
public slots:
- bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
+ bool launch(
+ InstancePtr instance,
+ bool online = true,
+ BaseProfilerFactory *profiler = nullptr,
+ MinecraftServerTargetPtr serverToJoin = nullptr
+ );
bool kill(InstancePtr instance);
private slots:
@@ -181,7 +188,7 @@ private slots:
FolderInstanceProvider * m_instanceFolder = nullptr;
std::shared_ptr m_icons;
std::shared_ptr m_updateChecker;
- std::shared_ptr m_accounts;
+ std::shared_ptr m_accounts;
std::shared_ptr m_javalist;
std::shared_ptr m_translations;
std::shared_ptr m_globalSettingsProvider;
@@ -221,6 +228,7 @@ private slots:
SetupWizard * m_setupWizard = nullptr;
public:
QString m_instanceIdToLaunch;
+ QString m_serverToJoin;
bool m_liveCheck = false;
QUrl m_zipToImport;
std::unique_ptr logFile;
diff --git a/api/logic/NullInstance.h b/launcher/NullInstance.h
similarity index 91%
rename from api/logic/NullInstance.h
rename to launcher/NullInstance.h
index e9ba1a13c0..94ed6c3a9c 100644
--- a/api/logic/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -27,7 +27,7 @@ class NullInstance: public BaseInstance
{
return instanceRoot();
};
- shared_qobject_ptr createLaunchTask(AuthSessionPtr) override
+ shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override
{
return nullptr;
}
@@ -67,7 +67,7 @@ class NullInstance: public BaseInstance
{
return false;
}
- QStringList verboseDescription(AuthSessionPtr session) override
+ QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
{
QStringList out;
out << "Null instance - placeholder.";
diff --git a/api/logic/ProblemProvider.h b/launcher/ProblemProvider.h
similarity index 86%
rename from api/logic/ProblemProvider.h
rename to launcher/ProblemProvider.h
index 33c9d3649c..cd4745fa14 100644
--- a/api/logic/ProblemProvider.h
+++ b/launcher/ProblemProvider.h
@@ -1,7 +1,5 @@
#pragma once
-#include "multimc_logic_export.h"
-
enum class ProblemSeverity
{
None,
@@ -15,7 +13,7 @@ struct PatchProblem
QString m_description;
};
-class MULTIMC_LOGIC_EXPORT ProblemProvider
+class ProblemProvider
{
public:
virtual ~ProblemProvider() {};
@@ -23,7 +21,7 @@ class MULTIMC_LOGIC_EXPORT ProblemProvider
virtual ProblemSeverity getProblemSeverity() const = 0;
};
-class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
+class ProblemContainer : public ProblemProvider
{
public:
const QList getProblems() const override
diff --git a/api/logic/QObjectPtr.h b/launcher/QObjectPtr.h
similarity index 100%
rename from api/logic/QObjectPtr.h
rename to launcher/QObjectPtr.h
diff --git a/api/logic/RWStorage.h b/launcher/RWStorage.h
similarity index 97%
rename from api/logic/RWStorage.h
rename to launcher/RWStorage.h
index 5d7923676e..3028388e25 100644
--- a/api/logic/RWStorage.h
+++ b/launcher/RWStorage.h
@@ -1,6 +1,9 @@
#pragma once
#include
#include
+#include
+#include
+
template
class RWStorage
{
diff --git a/api/logic/RecursiveFileSystemWatcher.cpp b/launcher/RecursiveFileSystemWatcher.cpp
similarity index 100%
rename from api/logic/RecursiveFileSystemWatcher.cpp
rename to launcher/RecursiveFileSystemWatcher.cpp
diff --git a/api/logic/RecursiveFileSystemWatcher.h b/launcher/RecursiveFileSystemWatcher.h
similarity index 91%
rename from api/logic/RecursiveFileSystemWatcher.h
rename to launcher/RecursiveFileSystemWatcher.h
index c9c39f49ce..cc837d6034 100644
--- a/api/logic/RecursiveFileSystemWatcher.h
+++ b/launcher/RecursiveFileSystemWatcher.h
@@ -4,9 +4,7 @@
#include
#include "pathmatcher/IPathMatcher.h"
-#include "multimc_logic_export.h"
-
-class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject
+class RecursiveFileSystemWatcher : public QObject
{
Q_OBJECT
public:
diff --git a/api/logic/SeparatorPrefixTree.h b/launcher/SeparatorPrefixTree.h
similarity index 100%
rename from api/logic/SeparatorPrefixTree.h
rename to launcher/SeparatorPrefixTree.h
diff --git a/api/gui/SkinUtils.cpp b/launcher/SkinUtils.cpp
similarity index 91%
rename from api/gui/SkinUtils.cpp
rename to launcher/SkinUtils.cpp
index ec969889f8..a196173eb7 100644
--- a/api/gui/SkinUtils.cpp
+++ b/launcher/SkinUtils.cpp
@@ -30,9 +30,7 @@ namespace SkinUtils
*/
QPixmap getFaceFromCache(QString username, int height, int width)
{
- QFile fskin(ENV.metacache()
- ->resolveEntry("skins", username + ".png")
- ->getFullPath());
+ QFile fskin(ENV.metacache()->resolveEntry("skins", username + ".png")->getFullPath());
if (fskin.exists())
{
diff --git a/api/gui/SkinUtils.h b/launcher/SkinUtils.h
similarity index 84%
rename from api/gui/SkinUtils.h
rename to launcher/SkinUtils.h
index b44f422836..c1f437ab0b 100644
--- a/api/gui/SkinUtils.h
+++ b/launcher/SkinUtils.h
@@ -17,9 +17,7 @@
#include
-#include "multimc_gui_export.h"
-
namespace SkinUtils
{
-QPixmap MULTIMC_GUI_EXPORT getFaceFromCache(QString id, int height = 64, int width = 64);
+QPixmap getFaceFromCache(QString id, int height = 64, int width = 64);
}
diff --git a/application/UpdateController.cpp b/launcher/UpdateController.cpp
similarity index 100%
rename from application/UpdateController.cpp
rename to launcher/UpdateController.cpp
diff --git a/application/UpdateController.h b/launcher/UpdateController.h
similarity index 100%
rename from application/UpdateController.h
rename to launcher/UpdateController.h
diff --git a/api/logic/Usable.h b/launcher/Usable.h
similarity index 100%
rename from api/logic/Usable.h
rename to launcher/Usable.h
diff --git a/api/logic/Version.cpp b/launcher/Version.cpp
similarity index 100%
rename from api/logic/Version.cpp
rename to launcher/Version.cpp
diff --git a/api/logic/Version.h b/launcher/Version.h
similarity index 97%
rename from api/logic/Version.h
rename to launcher/Version.h
index c5d9308150..9fe12d6dad 100644
--- a/api/logic/Version.h
+++ b/launcher/Version.h
@@ -3,11 +3,9 @@
#include
#include
-#include "multimc_logic_export.h"
-
class QUrl;
-class MULTIMC_LOGIC_EXPORT Version
+class Version
{
public:
Version(const QString &str);
diff --git a/application/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
similarity index 100%
rename from application/VersionProxyModel.cpp
rename to launcher/VersionProxyModel.cpp
diff --git a/application/VersionProxyModel.h b/launcher/VersionProxyModel.h
similarity index 100%
rename from application/VersionProxyModel.h
rename to launcher/VersionProxyModel.h
diff --git a/api/logic/Version_test.cpp b/launcher/Version_test.cpp
similarity index 100%
rename from api/logic/Version_test.cpp
rename to launcher/Version_test.cpp
diff --git a/api/logic/WatchLock.h b/launcher/WatchLock.h
similarity index 100%
rename from api/logic/WatchLock.h
rename to launcher/WatchLock.h
diff --git a/application/dialogs/AboutDialog.cpp b/launcher/dialogs/AboutDialog.cpp
similarity index 100%
rename from application/dialogs/AboutDialog.cpp
rename to launcher/dialogs/AboutDialog.cpp
diff --git a/application/dialogs/AboutDialog.h b/launcher/dialogs/AboutDialog.h
similarity index 100%
rename from application/dialogs/AboutDialog.h
rename to launcher/dialogs/AboutDialog.h
diff --git a/application/dialogs/AboutDialog.ui b/launcher/dialogs/AboutDialog.ui
similarity index 100%
rename from application/dialogs/AboutDialog.ui
rename to launcher/dialogs/AboutDialog.ui
diff --git a/application/dialogs/CopyInstanceDialog.cpp b/launcher/dialogs/CopyInstanceDialog.cpp
similarity index 100%
rename from application/dialogs/CopyInstanceDialog.cpp
rename to launcher/dialogs/CopyInstanceDialog.cpp
diff --git a/application/dialogs/CopyInstanceDialog.h b/launcher/dialogs/CopyInstanceDialog.h
similarity index 100%
rename from application/dialogs/CopyInstanceDialog.h
rename to launcher/dialogs/CopyInstanceDialog.h
diff --git a/application/dialogs/CopyInstanceDialog.ui b/launcher/dialogs/CopyInstanceDialog.ui
similarity index 100%
rename from application/dialogs/CopyInstanceDialog.ui
rename to launcher/dialogs/CopyInstanceDialog.ui
diff --git a/application/dialogs/CustomMessageBox.cpp b/launcher/dialogs/CustomMessageBox.cpp
similarity index 100%
rename from application/dialogs/CustomMessageBox.cpp
rename to launcher/dialogs/CustomMessageBox.cpp
diff --git a/application/dialogs/CustomMessageBox.h b/launcher/dialogs/CustomMessageBox.h
similarity index 100%
rename from application/dialogs/CustomMessageBox.h
rename to launcher/dialogs/CustomMessageBox.h
diff --git a/application/dialogs/EditAccountDialog.cpp b/launcher/dialogs/EditAccountDialog.cpp
similarity index 100%
rename from application/dialogs/EditAccountDialog.cpp
rename to launcher/dialogs/EditAccountDialog.cpp
diff --git a/application/dialogs/EditAccountDialog.h b/launcher/dialogs/EditAccountDialog.h
similarity index 100%
rename from application/dialogs/EditAccountDialog.h
rename to launcher/dialogs/EditAccountDialog.h
diff --git a/application/dialogs/EditAccountDialog.ui b/launcher/dialogs/EditAccountDialog.ui
similarity index 100%
rename from application/dialogs/EditAccountDialog.ui
rename to launcher/dialogs/EditAccountDialog.ui
diff --git a/application/dialogs/ExportInstanceDialog.cpp b/launcher/dialogs/ExportInstanceDialog.cpp
similarity index 100%
rename from application/dialogs/ExportInstanceDialog.cpp
rename to launcher/dialogs/ExportInstanceDialog.cpp
diff --git a/application/dialogs/ExportInstanceDialog.h b/launcher/dialogs/ExportInstanceDialog.h
similarity index 100%
rename from application/dialogs/ExportInstanceDialog.h
rename to launcher/dialogs/ExportInstanceDialog.h
diff --git a/application/dialogs/ExportInstanceDialog.ui b/launcher/dialogs/ExportInstanceDialog.ui
similarity index 100%
rename from application/dialogs/ExportInstanceDialog.ui
rename to launcher/dialogs/ExportInstanceDialog.ui
diff --git a/application/dialogs/IconPickerDialog.cpp b/launcher/dialogs/IconPickerDialog.cpp
similarity index 100%
rename from application/dialogs/IconPickerDialog.cpp
rename to launcher/dialogs/IconPickerDialog.cpp
diff --git a/application/dialogs/IconPickerDialog.h b/launcher/dialogs/IconPickerDialog.h
similarity index 100%
rename from application/dialogs/IconPickerDialog.h
rename to launcher/dialogs/IconPickerDialog.h
diff --git a/application/dialogs/IconPickerDialog.ui b/launcher/dialogs/IconPickerDialog.ui
similarity index 100%
rename from application/dialogs/IconPickerDialog.ui
rename to launcher/dialogs/IconPickerDialog.ui
diff --git a/application/dialogs/LoginDialog.cpp b/launcher/dialogs/LoginDialog.cpp
similarity index 64%
rename from application/dialogs/LoginDialog.cpp
rename to launcher/dialogs/LoginDialog.cpp
index 32f8a48fd8..befd87a510 100644
--- a/application/dialogs/LoginDialog.cpp
+++ b/launcher/dialogs/LoginDialog.cpp
@@ -16,7 +16,7 @@
#include "LoginDialog.h"
#include "ui_LoginDialog.h"
-#include "minecraft/auth/YggdrasilTask.h"
+#include "minecraft/auth/AccountTask.h"
#include
@@ -41,15 +41,22 @@ void LoginDialog::accept()
setUserInputsEnabled(false);
ui->progressBar->setVisible(true);
- // Setup the login task and start it
- m_account = MojangAccount::createFromUsername(ui->userTextBox->text());
- m_loginTask = m_account->login(nullptr, ui->passTextBox->text());
- connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
- connect(m_loginTask.get(), &Task::succeeded, this,
- &LoginDialog::onTaskSucceeded);
- connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus);
- connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress);
- m_loginTask->start();
+ if(!ui->passTextBox->text().isEmpty()){
+ // Online mode
+ // Setup the login task and start it
+ m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
+ m_loginTask = m_account->login(nullptr, ui->passTextBox->text());
+ connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
+ connect(m_loginTask.get(), &Task::succeeded, this,
+ &LoginDialog::onTaskSucceeded);
+ connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus);
+ connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress);
+ m_loginTask->start();
+ }else{
+ // Offline mode
+ m_account = MinecraftAccount::createFromUsernameOffline(ui->userTextBox->text());
+ QDialog::accept();
+ }
}
void LoginDialog::setUserInputsEnabled(bool enable)
@@ -63,18 +70,28 @@ void LoginDialog::setUserInputsEnabled(bool enable)
void LoginDialog::on_userTextBox_textEdited(const QString &newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
- ->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty());
+ ->setEnabled(!newText.isEmpty());
}
void LoginDialog::on_passTextBox_textEdited(const QString &newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
- ->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty());
+ ->setEnabled(!ui->userTextBox->text().isEmpty());
}
void LoginDialog::onTaskFailed(const QString &reason)
{
// Set message
- ui->label->setText("" + reason + " ");
+ auto lines = reason.split('\n');
+ QString processed;
+ for(auto line: lines) {
+ if(line.size()) {
+ processed += "" + line + " ";
+ }
+ else {
+ processed += " ";
+ }
+ }
+ ui->label->setText(processed);
// Re-enable user-interaction
setUserInputsEnabled(true);
@@ -98,7 +115,7 @@ void LoginDialog::onTaskProgress(qint64 current, qint64 total)
}
// Public interface
-MojangAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
+MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
{
LoginDialog dlg(parent);
dlg.ui->label->setText(msg);
diff --git a/application/dialogs/LoginDialog.h b/launcher/dialogs/LoginDialog.h
similarity index 89%
rename from application/dialogs/LoginDialog.h
rename to launcher/dialogs/LoginDialog.h
index 16bdddfbf2..134636407d 100644
--- a/application/dialogs/LoginDialog.h
+++ b/launcher/dialogs/LoginDialog.h
@@ -18,7 +18,7 @@
#include
#include
-#include "minecraft/auth/MojangAccount.h"
+#include "minecraft/auth/MinecraftAccount.h"
namespace Ui
{
@@ -32,7 +32,7 @@ class LoginDialog : public QDialog
public:
~LoginDialog();
- static MojangAccountPtr newAccount(QWidget *parent, QString message);
+ static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
private:
explicit LoginDialog(QWidget *parent = 0);
@@ -53,6 +53,6 @@ protected
private:
Ui::LoginDialog *ui;
- MojangAccountPtr m_account;
+ MinecraftAccountPtr m_account;
std::shared_ptr m_loginTask;
};
diff --git a/application/dialogs/LoginDialog.ui b/launcher/dialogs/LoginDialog.ui
similarity index 80%
rename from application/dialogs/LoginDialog.ui
rename to launcher/dialogs/LoginDialog.ui
index d92fbae391..60c866ccaa 100644
--- a/application/dialogs/LoginDialog.ui
+++ b/launcher/dialogs/LoginDialog.ui
@@ -6,8 +6,8 @@
0
0
- 400
- 162
+ 421
+ 198
@@ -33,6 +33,22 @@
+ -
+
+
+
+ 75
+ true
+
+
+
+ Note: For offline mode use "Email" field for username and "Password" field leave blank
+
+
+ Qt::RichText
+
+
+
-
diff --git a/launcher/dialogs/MSALoginDialog.cpp b/launcher/dialogs/MSALoginDialog.cpp
new file mode 100644
index 0000000000..15c04061f4
--- /dev/null
+++ b/launcher/dialogs/MSALoginDialog.cpp
@@ -0,0 +1,141 @@
+/* Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MSALoginDialog.h"
+#include "ui_MSALoginDialog.h"
+
+#include "minecraft/auth/AccountTask.h"
+
+#include
+#include
+
+MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
+{
+ ui->setupUi(this);
+ ui->progressBar->setVisible(false);
+ // ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
+
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+int MSALoginDialog::exec() {
+ setUserInputsEnabled(false);
+ ui->progressBar->setVisible(true);
+
+ // Setup the login task and start it
+ m_account = MinecraftAccount::createBlankMSA();
+ m_loginTask = m_account->loginMSA(nullptr);
+ connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
+ connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
+ connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
+ connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress);
+ connect(m_loginTask.get(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
+ connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
+ connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
+ m_loginTask->start();
+
+ return QDialog::exec();
+}
+
+
+MSALoginDialog::~MSALoginDialog()
+{
+ delete ui;
+}
+
+void MSALoginDialog::externalLoginTick() {
+ m_externalLoginElapsed++;
+ ui->progressBar->setValue(m_externalLoginElapsed);
+ ui->progressBar->repaint();
+
+ if(m_externalLoginElapsed >= m_externalLoginTimeout) {
+ m_externalLoginTimer.stop();
+ }
+}
+
+
+void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn) {
+ m_externalLoginElapsed = 0;
+ m_externalLoginTimeout = expiresIn;
+
+ m_externalLoginTimer.setInterval(1000);
+ m_externalLoginTimer.setSingleShot(false);
+ m_externalLoginTimer.start();
+
+ ui->progressBar->setMaximum(expiresIn);
+ ui->progressBar->setValue(m_externalLoginElapsed);
+
+ QString urlString = uri.toString();
+ QString linkString = QString("%2 ").arg(urlString, urlString);
+ ui->label->setText(tr("Please open up %1 in a browser and put in the code %2 to proceed with login.
").arg(linkString, code));
+}
+
+void MSALoginDialog::hideVerificationUriAndCode() {
+ m_externalLoginTimer.stop();
+}
+
+void MSALoginDialog::setUserInputsEnabled(bool enable)
+{
+ ui->buttonBox->setEnabled(enable);
+}
+
+void MSALoginDialog::onTaskFailed(const QString &reason)
+{
+ // Set message
+ auto lines = reason.split('\n');
+ QString processed;
+ for(auto line: lines) {
+ if(line.size()) {
+ processed += "" + line + " ";
+ }
+ else {
+ processed += " ";
+ }
+ }
+ ui->label->setText(processed);
+
+ // Re-enable user-interaction
+ setUserInputsEnabled(true);
+ ui->progressBar->setVisible(false);
+}
+
+void MSALoginDialog::onTaskSucceeded()
+{
+ QDialog::accept();
+}
+
+void MSALoginDialog::onTaskStatus(const QString &status)
+{
+ ui->label->setText(status);
+}
+
+void MSALoginDialog::onTaskProgress(qint64 current, qint64 total)
+{
+ ui->progressBar->setMaximum(total);
+ ui->progressBar->setValue(current);
+}
+
+// Public interface
+MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg)
+{
+ MSALoginDialog dlg(parent);
+ dlg.ui->label->setText(msg);
+ if (dlg.exec() == QDialog::Accepted)
+ {
+ return dlg.m_account;
+ }
+ return 0;
+}
diff --git a/launcher/dialogs/MSALoginDialog.h b/launcher/dialogs/MSALoginDialog.h
new file mode 100644
index 0000000000..3d26a0dd6e
--- /dev/null
+++ b/launcher/dialogs/MSALoginDialog.h
@@ -0,0 +1,63 @@
+/* Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "minecraft/auth/MinecraftAccount.h"
+
+namespace Ui
+{
+class MSALoginDialog;
+}
+
+class MSALoginDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ ~MSALoginDialog();
+
+ static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
+ int exec() override;
+
+private:
+ explicit MSALoginDialog(QWidget *parent = 0);
+
+ void setUserInputsEnabled(bool enable);
+
+protected
+slots:
+ void onTaskFailed(const QString &reason);
+ void onTaskSucceeded();
+ void onTaskStatus(const QString &status);
+ void onTaskProgress(qint64 current, qint64 total);
+ void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
+ void hideVerificationUriAndCode();
+
+ void externalLoginTick();
+
+private:
+ Ui::MSALoginDialog *ui;
+ MinecraftAccountPtr m_account;
+ std::shared_ptr m_loginTask;
+ QTimer m_externalLoginTimer;
+ int m_externalLoginElapsed = 0;
+ int m_externalLoginTimeout = 0;
+};
+
diff --git a/launcher/dialogs/MSALoginDialog.ui b/launcher/dialogs/MSALoginDialog.ui
new file mode 100644
index 0000000000..78cbfb269f
--- /dev/null
+++ b/launcher/dialogs/MSALoginDialog.ui
@@ -0,0 +1,65 @@
+
+
+ MSALoginDialog
+
+
+
+ 0
+ 0
+ 491
+ 143
+
+
+
+
+ 0
+ 0
+
+
+
+ Add Microsoft Account
+
+
+ -
+
+
+ Message label placeholder.
+
+aaaaa
+
+
+ Qt::RichText
+
+
+ true
+
+
+ Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+
+ -
+
+
+ 24
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel
+
+
+
+
+
+
+
+
diff --git a/application/dialogs/NewComponentDialog.cpp b/launcher/dialogs/NewComponentDialog.cpp
similarity index 100%
rename from application/dialogs/NewComponentDialog.cpp
rename to launcher/dialogs/NewComponentDialog.cpp
diff --git a/application/dialogs/NewComponentDialog.h b/launcher/dialogs/NewComponentDialog.h
similarity index 100%
rename from application/dialogs/NewComponentDialog.h
rename to launcher/dialogs/NewComponentDialog.h
diff --git a/application/dialogs/NewComponentDialog.ui b/launcher/dialogs/NewComponentDialog.ui
similarity index 100%
rename from application/dialogs/NewComponentDialog.ui
rename to launcher/dialogs/NewComponentDialog.ui
diff --git a/application/dialogs/NewInstanceDialog.cpp b/launcher/dialogs/NewInstanceDialog.cpp
similarity index 95%
rename from application/dialogs/NewInstanceDialog.cpp
rename to launcher/dialogs/NewInstanceDialog.cpp
index d70cbffed7..86963149fd 100644
--- a/application/dialogs/NewInstanceDialog.cpp
+++ b/launcher/dialogs/NewInstanceDialog.cpp
@@ -37,7 +37,7 @@
#include
#include
#include
-#include
+#include
#include
#include
@@ -124,17 +124,17 @@ void NewInstanceDialog::accept()
QList NewInstanceDialog::getPages()
{
importPage = new ImportPage(this);
- twitchPage = new TwitchPage(this);
+ flamePage = new FlamePage(this);
auto technicPage = new TechnicPage(this);
return
{
new VanillaPage(this),
importPage,
new AtlPage(this),
+ flamePage,
new FtbPage(this),
new LegacyFTB::Page(this),
- technicPage,
- twitchPage
+ technicPage
};
}
@@ -173,6 +173,14 @@ void NewInstanceDialog::setSuggestedIconFromFile(const QString &path, const QStr
ui->iconButton->setIcon(QIcon(path));
}
+void NewInstanceDialog::setSuggestedIcon(const QString &key)
+{
+ auto icon = MMC->icons()->getIcon(key);
+ importIcon = false;
+
+ ui->iconButton->setIcon(icon);
+}
+
InstanceTask * NewInstanceDialog::extractTask()
{
InstanceTask * extracted = creationTask.get();
diff --git a/application/dialogs/NewInstanceDialog.h b/launcher/dialogs/NewInstanceDialog.h
similarity index 95%
rename from application/dialogs/NewInstanceDialog.h
rename to launcher/dialogs/NewInstanceDialog.h
index 88ed00e58d..53abf8cfbd 100644
--- a/application/dialogs/NewInstanceDialog.h
+++ b/launcher/dialogs/NewInstanceDialog.h
@@ -29,7 +29,7 @@ class NewInstanceDialog;
class PageContainer;
class QDialogButtonBox;
class ImportPage;
-class TwitchPage;
+class FlamePage;
class NewInstanceDialog : public QDialog, public BasePageProvider
{
@@ -43,6 +43,7 @@ class NewInstanceDialog : public QDialog, public BasePageProvider
void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr);
void setSuggestedIconFromFile(const QString &path, const QString &name);
+ void setSuggestedIcon(const QString &key);
InstanceTask * extractTask();
@@ -68,7 +69,7 @@ private slots:
QString InstIconKey;
ImportPage *importPage = nullptr;
- TwitchPage *twitchPage = nullptr;
+ FlamePage *flamePage = nullptr;
std::unique_ptr creationTask;
bool importIcon = false;
diff --git a/application/dialogs/NewInstanceDialog.ui b/launcher/dialogs/NewInstanceDialog.ui
similarity index 100%
rename from application/dialogs/NewInstanceDialog.ui
rename to launcher/dialogs/NewInstanceDialog.ui
diff --git a/application/dialogs/NotificationDialog.cpp b/launcher/dialogs/NotificationDialog.cpp
similarity index 100%
rename from application/dialogs/NotificationDialog.cpp
rename to launcher/dialogs/NotificationDialog.cpp
diff --git a/application/dialogs/NotificationDialog.h b/launcher/dialogs/NotificationDialog.h
similarity index 100%
rename from application/dialogs/NotificationDialog.h
rename to launcher/dialogs/NotificationDialog.h
diff --git a/application/dialogs/NotificationDialog.ui b/launcher/dialogs/NotificationDialog.ui
similarity index 100%
rename from application/dialogs/NotificationDialog.ui
rename to launcher/dialogs/NotificationDialog.ui
diff --git a/application/dialogs/ProfileSelectDialog.cpp b/launcher/dialogs/ProfileSelectDialog.cpp
similarity index 76%
rename from application/dialogs/ProfileSelectDialog.cpp
rename to launcher/dialogs/ProfileSelectDialog.cpp
index ae34709f2a..e2ad73e4ff 100644
--- a/application/dialogs/ProfileSelectDialog.cpp
+++ b/launcher/dialogs/ProfileSelectDialog.cpp
@@ -33,9 +33,10 @@ ProfileSelectDialog::ProfileSelectDialog(const QString &message, int flags, QWid
m_accounts = MMC->accounts();
auto view = ui->listView;
//view->setModel(m_accounts.get());
- //view->hideColumn(MojangAccountList::ActiveColumn);
+ //view->hideColumn(AccountList::ActiveColumn);
view->setColumnCount(1);
view->setRootIsDecorated(false);
+ // FIXME: use a real model, not this
if(QTreeWidgetItem* header = view->headerItem())
{
header->setText(0, tr("Name"));
@@ -47,20 +48,19 @@ ProfileSelectDialog::ProfileSelectDialog(const QString &message, int flags, QWid
QList items;
for (int i = 0; i < m_accounts->count(); i++)
{
- MojangAccountPtr account = m_accounts->at(i);
- for (auto profile : account->profiles())
- {
- auto profileLabel = profile.name;
- if(account->isInUse())
- {
- profileLabel += tr(" (in use)");
- }
- auto item = new QTreeWidgetItem(view);
- item->setText(0, profileLabel);
- item->setIcon(0, SkinUtils::getFaceFromCache(profile.id));
- item->setData(0, MojangAccountList::PointerRole, QVariant::fromValue(account));
- items.append(item);
+ MinecraftAccountPtr account = m_accounts->at(i);
+ QString profileLabel;
+ if(account->isInUse()) {
+ profileLabel = tr("%1 (in use)").arg(account->profileName());
}
+ else {
+ profileLabel = account->profileName();
+ }
+ auto item = new QTreeWidgetItem(view);
+ item->setText(0, profileLabel);
+ item->setIcon(0, account->getFace());
+ item->setData(0, AccountList::PointerRole, QVariant::fromValue(account));
+ items.append(item);
}
view->addTopLevelItems(items);
@@ -84,7 +84,7 @@ ProfileSelectDialog::~ProfileSelectDialog()
delete ui;
}
-MojangAccountPtr ProfileSelectDialog::selectedAccount() const
+MinecraftAccountPtr ProfileSelectDialog::selectedAccount() const
{
return m_selected;
}
@@ -105,7 +105,7 @@ void ProfileSelectDialog::on_buttonBox_accepted()
if (selection.size() > 0)
{
QModelIndex selected = selection.first();
- m_selected = selected.data(MojangAccountList::PointerRole).value();
+ m_selected = selected.data(AccountList::PointerRole).value();
}
close();
}
diff --git a/application/dialogs/ProfileSelectDialog.h b/launcher/dialogs/ProfileSelectDialog.h
similarity index 93%
rename from application/dialogs/ProfileSelectDialog.h
rename to launcher/dialogs/ProfileSelectDialog.h
index 9f95830c15..a4acd9a1b4 100644
--- a/application/dialogs/ProfileSelectDialog.h
+++ b/launcher/dialogs/ProfileSelectDialog.h
@@ -19,7 +19,7 @@
#include
-#include "minecraft/auth/MojangAccountList.h"
+#include "minecraft/auth/AccountList.h"
namespace Ui
{
@@ -59,7 +59,7 @@ class ProfileSelectDialog : public QDialog
* Gets a pointer to the account that the user selected.
* This is null if the user clicked cancel or hasn't clicked OK yet.
*/
- MojangAccountPtr selectedAccount() const;
+ MinecraftAccountPtr selectedAccount() const;
/*!
* Returns true if the user checked the "use as global default" checkbox.
@@ -80,10 +80,10 @@ public
void on_buttonBox_rejected();
protected:
- std::shared_ptr m_accounts;
+ std::shared_ptr m_accounts;
//! The account that was selected when the user clicked OK.
- MojangAccountPtr m_selected;
+ MinecraftAccountPtr m_selected;
private:
Ui::ProfileSelectDialog *ui;
diff --git a/application/dialogs/ProfileSelectDialog.ui b/launcher/dialogs/ProfileSelectDialog.ui
similarity index 100%
rename from application/dialogs/ProfileSelectDialog.ui
rename to launcher/dialogs/ProfileSelectDialog.ui
diff --git a/application/dialogs/ProgressDialog.cpp b/launcher/dialogs/ProgressDialog.cpp
similarity index 100%
rename from application/dialogs/ProgressDialog.cpp
rename to launcher/dialogs/ProgressDialog.cpp
diff --git a/application/dialogs/ProgressDialog.h b/launcher/dialogs/ProgressDialog.h
similarity index 100%
rename from application/dialogs/ProgressDialog.h
rename to launcher/dialogs/ProgressDialog.h
diff --git a/application/dialogs/ProgressDialog.ui b/launcher/dialogs/ProgressDialog.ui
similarity index 100%
rename from application/dialogs/ProgressDialog.ui
rename to launcher/dialogs/ProgressDialog.ui
diff --git a/application/dialogs/SkinUploadDialog.cpp b/launcher/dialogs/SkinUploadDialog.cpp
similarity index 64%
rename from application/dialogs/SkinUploadDialog.cpp
rename to launcher/dialogs/SkinUploadDialog.cpp
index 56133529bd..97478f4b18 100644
--- a/application/dialogs/SkinUploadDialog.cpp
+++ b/launcher/dialogs/SkinUploadDialog.cpp
@@ -1,11 +1,16 @@
#include
#include
+#include
+
#include
#include
+#include
+
#include "SkinUploadDialog.h"
#include "ui_SkinUploadDialog.h"
#include "ProgressDialog.h"
#include "CustomMessageBox.h"
+#include
void SkinUploadDialog::on_buttonBox_rejected()
{
@@ -15,7 +20,7 @@ void SkinUploadDialog::on_buttonBox_rejected()
void SkinUploadDialog::on_buttonBox_accepted()
{
AuthSessionPtr session = std::make_shared();
- auto login = m_acct->login(session);
+ auto login = m_acct->refresh(session);
ProgressDialog prog(this);
if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted)
{
@@ -85,8 +90,13 @@ void SkinUploadDialog::on_buttonBox_accepted()
{
model = SkinUpload::ALEX;
}
- SkinUploadPtr upload = std::make_shared(this, session, FS::read(fileName), model);
- if (prog.execWithTask((Task*)upload.get()) != QDialog::Accepted)
+ SequentialTask skinUpload;
+ skinUpload.addTask(std::make_shared(this, session, FS::read(fileName), model));
+ auto selectedCape = ui->capeCombo->currentData().toString();
+ if(selectedCape != session->m_accountPtr->accountData()->minecraftProfile.currentCape) {
+ skinUpload.addTask(std::make_shared(this, session, selectedCape));
+ }
+ if (prog.execWithTask(&skinUpload) != QDialog::Accepted)
{
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec();
close();
@@ -107,8 +117,38 @@ void SkinUploadDialog::on_skinBrowseBtn_clicked()
ui->skinPathTextBox->setText(cooked_path);
}
-SkinUploadDialog::SkinUploadDialog(MojangAccountPtr acct, QWidget *parent)
+SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr acct, QWidget *parent)
:QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog)
{
ui->setupUi(this);
+
+ // FIXME: add a model for this, download/refresh the capes on demand
+ auto &data = *acct->accountData();
+ int index = 0;
+ ui->capeCombo->addItem(tr("No Cape"), QVariant());
+ auto currentCape = data.minecraftProfile.currentCape;
+ if(currentCape.isEmpty()) {
+ ui->capeCombo->setCurrentIndex(index);
+ }
+
+ for(auto & cape: data.minecraftProfile.capes) {
+ index++;
+ if(cape.data.size()) {
+ QPixmap capeImage;
+ if(capeImage.loadFromData(cape.data, "PNG")) {
+ QPixmap preview = QPixmap(10, 16);
+ QPainter painter(&preview);
+ painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16));
+ ui->capeCombo->addItem(capeImage, cape.alias, cape.id);
+ if(currentCape == cape.id) {
+ ui->capeCombo->setCurrentIndex(index);
+ }
+ continue;
+ }
+ }
+ ui->capeCombo->addItem(cape.alias, cape.id);
+ if(currentCape == cape.id) {
+ ui->capeCombo->setCurrentIndex(index);
+ }
+ }
}
diff --git a/application/dialogs/SkinUploadDialog.h b/launcher/dialogs/SkinUploadDialog.h
similarity index 69%
rename from application/dialogs/SkinUploadDialog.h
rename to launcher/dialogs/SkinUploadDialog.h
index deb44eac0d..84d17dc6fb 100644
--- a/application/dialogs/SkinUploadDialog.h
+++ b/launcher/dialogs/SkinUploadDialog.h
@@ -1,7 +1,7 @@
#pragma once
#include
-#include
+#include
namespace Ui
{
@@ -11,7 +11,7 @@ namespace Ui
class SkinUploadDialog : public QDialog {
Q_OBJECT
public:
- explicit SkinUploadDialog(MojangAccountPtr acct, QWidget *parent = 0);
+ explicit SkinUploadDialog(MinecraftAccountPtr acct, QWidget *parent = 0);
virtual ~SkinUploadDialog() {};
public slots:
@@ -22,7 +22,7 @@ public slots:
void on_skinBrowseBtn_clicked();
protected:
- MojangAccountPtr m_acct;
+ MinecraftAccountPtr m_acct;
private:
Ui::SkinUploadDialog *ui;
diff --git a/launcher/dialogs/SkinUploadDialog.ui b/launcher/dialogs/SkinUploadDialog.ui
new file mode 100644
index 0000000000..f4b0ed0aa7
--- /dev/null
+++ b/launcher/dialogs/SkinUploadDialog.ui
@@ -0,0 +1,97 @@
+
+
+ SkinUploadDialog
+
+
+
+