diff --git a/.clang-tidy b/.clang-tidy
index 436dcf244..ef5166da4 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,5 +1,23 @@
Checks:
- modernize-use-using
- readability-avoid-const-params-in-decls
+ - misc-unused-parameters,
+ - readability-identifier-naming
-SystemHeaders: false
+# ^ Without unused-parameters the readability-identifier-naming check doesn't cause any warnings.
+
+CheckOptions:
+ - { key: readability-identifier-naming.ClassCase, value: PascalCase }
+ - { key: readability-identifier-naming.EnumCase, value: PascalCase }
+ - { key: readability-identifier-naming.FunctionCase, value: camelCase }
+ - { key: readability-identifier-naming.GlobalVariableCase, value: camelCase }
+ - { key: readability-identifier-naming.GlobalFunctionCase, value: camelCase }
+ - { key: readability-identifier-naming.GlobalConstantCase, value: SCREAMING_SNAKE_CASE }
+ - { key: readability-identifier-naming.MacroDefinitionCase, value: SCREAMING_SNAKE_CASE }
+ - { key: readability-identifier-naming.ClassMemberCase, value: camelCase }
+ - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ }
+ - { key: readability-identifier-naming.ProtectedMemberPrefix, value: m_ }
+ - { key: readability-identifier-naming.PrivateStaticMemberPrefix, value: s_ }
+ - { key: readability-identifier-naming.ProtectedStaticMemberPrefix, value: s_ }
+ - { key: readability-identifier-naming.PublicStaticConstantCase, value: SCREAMING_SNAKE_CASE }
+ - { key: readability-identifier-naming.EnumConstantCase, value: SCREAMING_SNAKE_CASE }
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7bb6c0ad7..e1b07b2b1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -59,14 +59,14 @@ jobs:
qt_ver: 5
qt_host: linux
qt_arch: ""
- qt_version: "5.12.8"
+ qt_version: "5.15.2"
qt_modules: "qtnetworkauth"
- os: ubuntu-20.04
qt_ver: 6
qt_host: linux
qt_arch: ""
- qt_version: "6.2.4"
+ qt_version: "6.5.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022
@@ -173,7 +173,7 @@ jobs:
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
- uses: actions/cache@v4.1.0
+ uses: actions/cache@v4.2.0
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@@ -206,7 +206,7 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get -y update
- sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream
+ sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
- name: Install Dependencies (macOS)
if: runner.os == 'macOS'
@@ -380,11 +380,13 @@ jobs:
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
+ ENTITLEMENTS_FILE='../program_info/App.entitlements'
else
APPLE_CODESIGN_ID='-'
+ ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
fi
- sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
+ sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app"
- name: Notarize (macOS)
@@ -410,9 +412,8 @@ jobs:
if: matrix.name == 'macOS'
run: |
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
- brew install openssl@3
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
- signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
+ signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
rm ed25519-priv.pem
cat >> $GITHUB_STEP_SUMMARY << EOF
### Artifact Information :information_source:
@@ -634,19 +635,24 @@ jobs:
flatpak:
runs-on: ubuntu-latest
container:
- image: bilelmoussaoui/flatpak-github-actions:kde-6.7
+ image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
if: inputs.build_type == 'Debug'
with:
- submodules: "true"
+ submodules: true
+
+ - name: Set short version
+ shell: bash
+ run: echo "VERSION=${GITHUB_SHA::7}" >> $GITHUB_ENV
+
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
- bundle: "Prism Launcher.flatpak"
+ bundle: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
nix:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d70fe79b..dcf13c577 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -99,7 +99,7 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
- # using clang with clang-cl front end
+ # using clang with clang-cl front end
message(STATUS "Address Sanitizer available on Clang MSVC frontend")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
@@ -180,7 +180,7 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
######## Set version numbers ########
-set(Launcher_VERSION_MAJOR 9)
+set(Launcher_VERSION_MAJOR 10)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
@@ -225,7 +225,7 @@ set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build agains
# Java downloader
set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
-# Although we recommend enabling this, we cannot guarantee binary compatibility on
+# Although we recommend enabling this, we cannot guarantee binary compatibility on
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
# feature if they know it will work with their distribution.
if(UNIX AND NOT APPLE)
@@ -299,6 +299,8 @@ include(QtVersionlessBackport)
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(QT_VERSION_MAJOR 5)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
+ find_package(Qt5 COMPONENTS DBus)
+ list(APPEND Launcher_QT_DBUS Qt5::DBus)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 1.3 QUIET)
@@ -313,6 +315,8 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
+ find_package(Qt6 COMPONENTS DBus)
+ list(APPEND Launcher_QT_DBUS Qt6::DBus)
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
@@ -397,8 +401,8 @@ if(UNIX AND APPLE)
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
- set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive")
- set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive")
+ set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz" CACHE STRING "URL to Sparkle release archive")
+ set(MACOSX_SPARKLE_SHA256 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive")
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
# directories to look for dependencies
@@ -438,10 +442,10 @@ elseif(UNIX)
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
-
+
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
-
+
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
endif()
@@ -495,7 +499,7 @@ if(FORCE_BUNDLED_ZLIB)
set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
- # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
+ # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
check_include_file(unistd.h NEED_GENERATED_ZCONF)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
@@ -532,10 +536,12 @@ else()
endif()
if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark")
+ set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING})
set(BUILD_TESTING 0)
- set(BUILD_SHARED_LIBS 0)
+ set(BUILD_SHARED_LIBS 0)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark)
+ set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING})
else()
message(STATUS "Using system cmark")
endif()
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 072916772..5965f4d8e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,16 +2,59 @@
## Code formatting
-Try to follow the existing formatting.
-If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
+All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing!
-In general, in order of importance:
+Please also follow the project's conventions for C++:
-- 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.
+- Class and type names should be formatted as `PascalCase`: `MyClass`.
+- Private or protected class data members should be formatted as `camelCase` prefixed with `m_`: `m_myCounter`.
+- Private or protected `static` class data members should be formatted as `camelCase` prefixed with `s_`: `s_instance`.
+- Public class data members should be formatted as `camelCase` without the prefix: `dateOfBirth`.
+- Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`.
+- Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`.
+- Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`.
+- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`.
+- Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`.
+
+Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations.
+
+Here is what these conventions with the formatting configuration look like:
+
+```c++
+#define AWESOMENESS 10
+
+constexpr double PI = 3.14159;
+
+enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP };
+
+struct Person {
+ QString name;
+ QDateTime dateOfBirth;
+
+ long daysOld() const { return dateOfBirth.daysTo(QDateTime::currentDateTime()); }
+};
+
+class ImportantClass {
+ public:
+ void incrementCounter()
+ {
+ if (m_counter + 1 > MAX_COUNTER_VALUE)
+ throw std::runtime_error("Counter has reached limit!");
+
+ ++m_counter;
+ }
+
+ int counter() const { return m_counter; }
+
+ private:
+ static constexpr int MAX_COUNTER_VALUE = 100;
+ int m_counter;
+};
+
+ImportantClass importantClassInstance;
+```
+
+If you see any names which do not follow these conventions, it is preferred that you leave them be - renames increase the number of changes therefore make reviewing harder and make your PR more prone to conflicts. However, if you're refactoring a whole class anyway, it's fine.
## Signing your work
diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
index 6d3845dfc..3a8c8fbfe 100644
--- a/cmake/MacOSXBundleInfo.plist.in
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -8,6 +8,8 @@
A Minecraft mod wants to access your microphone.
NSDownloadsFolderUsageDescription
Prism uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Prism scans for downloaded mods in Settings or the prompt that appears.
+ NSLocalNetworkUsageDescription
+ Minecraft uses the local network to find and connect to LAN servers.
NSPrincipalClass
NSApplication
NSHighResolutionCapable
diff --git a/flake.lock b/flake.lock
index 6897c162d..c2f37cf0d 100644
--- a/flake.lock
+++ b/flake.lock
@@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
- "lastModified": 1696426674,
- "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "lastModified": 1733328505,
+ "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@@ -34,11 +34,11 @@
},
"nix-filter": {
"locked": {
- "lastModified": 1710156097,
- "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
+ "lastModified": 1731533336,
+ "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
- "rev": "3342559a24e85fc164b295c3444e8a139924675b",
+ "rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
@@ -49,11 +49,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1728018373,
- "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=",
+ "lastModified": 1735834308,
+ "narHash": "sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "bc947f541ae55e999ffdb4013441347d83b00feb",
+ "rev": "6df24922a1400241dae323af55f30e4318a6ca65",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index f4ca782ec..54add656d 100644
--- a/flake.nix
+++ b/flake.nix
@@ -85,24 +85,18 @@
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
- overlays.default =
- final: prev:
- let
- version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
- in
- {
- prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
- inherit
- libnbtplusplus
- nix-filter
- self
- version
- ;
- };
-
- prismlauncher = final.callPackage ./nix/wrapper.nix { };
+ overlays.default = final: prev: {
+ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
+ inherit
+ libnbtplusplus
+ nix-filter
+ self
+ ;
};
+ prismlauncher = final.callPackage ./nix/wrapper.nix { };
+ };
+
packages = forAllSystems (
system:
let
diff --git a/flatpak/flite.json b/flatpak/flite.json
new file mode 100644
index 000000000..1bf280af1
--- /dev/null
+++ b/flatpak/flite.json
@@ -0,0 +1,20 @@
+{
+ "name": "flite",
+ "config-opts": [
+ "--enable-shared",
+ "--with-audio=pulseaudio"
+ ],
+ "no-parallel-make": true,
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://github.com/festvox/flite.git",
+ "tag": "v2.2",
+ "commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88",
+ "x-checker-data": {
+ "type": "git",
+ "tag-pattern": "^v([\\d.]+)$"
+ }
+ }
+ ]
+}
diff --git a/flatpak/libdecor.json b/flatpak/libdecor.json
index 589310a35..1652a2f04 100644
--- a/flatpak/libdecor.json
+++ b/flatpak/libdecor.json
@@ -1,22 +1,18 @@
{
- "name": "libdecor",
- "buildsystem": "meson",
- "config-opts": [
- "-Ddemo=false"
- ],
- "sources": [
- {
- "type": "git",
- "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
- "commit": "73260393a97291c887e1074ab7f318e031be0ac6"
- },
- {
- "type": "patch",
- "path": "patches/weird_libdecor.patch"
- }
- ],
- "cleanup": [
- "/include",
- "/lib/pkgconfig"
- ]
+ "name": "libdecor",
+ "buildsystem": "meson",
+ "config-opts": [
+ "-Ddemo=false"
+ ],
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
+ "commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f"
+ }
+ ],
+ "cleanup": [
+ "/include",
+ "/lib/pkgconfig"
+ ]
}
diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml
index 71e6dd11e..136aef91a 100644
--- a/flatpak/org.prismlauncher.PrismLauncher.yml
+++ b/flatpak/org.prismlauncher.PrismLauncher.yml
@@ -1,11 +1,9 @@
id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform
-runtime-version: 6.7
+runtime-version: '6.8'
sdk: org.kde.Sdk
sdk-extensions:
- - org.freedesktop.Sdk.Extension.openjdk21
- org.freedesktop.Sdk.Extension.openjdk17
- - org.freedesktop.Sdk.Extension.openjdk8
command: prismlauncher
finish-args:
@@ -21,6 +19,12 @@ finish-args:
- --filesystem=xdg-download:ro
# FTBApp import
- --filesystem=~/.ftba:ro
+ # Userspace visibility for manual hugepages configuration
+ # Required for -XX:+UseLargePages
+ - --filesystem=/sys/kernel/mm/hugepages:ro
+ # Userspace visibility for transparent hugepages configuration
+ # Required for -XX:+UseTransparentHugePages
+ - --filesystem=/sys/kernel/mm/transparent_hugepage:ro
modules:
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
@@ -29,50 +33,39 @@ modules:
# Needed for proper Wayland support
- libdecor.json
+ # Text to Speech in the game
+ - flite.json
+
- name: prismlauncher
buildsystem: cmake-ninja
builddir: true
config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak
+ # This allows us to manage and update Java independently of this Flatpak
+ - -DLauncher_ENABLE_JAVA_DOWNLOADER=ON
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
build-options:
env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
+ run-tests: true
sources:
- type: dir
path: ../
- - name: openjdk
- buildsystem: simple
- build-commands:
- - mkdir -p /app/jdk/
- - /usr/lib/sdk/openjdk21/install.sh
- - mv /app/jre /app/jdk/21
- - /usr/lib/sdk/openjdk17/install.sh
- - mv /app/jre /app/jdk/17
- - /usr/lib/sdk/openjdk8/install.sh
- - mv /app/jre /app/jdk/8
- cleanup:
- - /jre
-
- name: glfw
buildsystem: cmake-ninja
config-opts:
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DBUILD_SHARED_LIBS:BOOL=ON
- - -DGLFW_USE_WAYLAND:BOOL=ON
+ - -DGLFW_BUILD_WAYLAND:BOOL=ON
- -DGLFW_BUILD_DOCS:BOOL=OFF
sources:
- type: git
url: https://github.com/glfw/glfw.git
- commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52
+ commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4
- type: patch
- path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
- - type: patch
- path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
- - type: patch
- path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
+ path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
cleanup:
- /include
- /lib/cmake
@@ -82,8 +75,8 @@ modules:
buildsystem: autotools
sources:
- type: archive
- url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
- sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
+ url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz
+ sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c
x-checker-data:
type: anitya
project-id: 14957
@@ -105,8 +98,8 @@ modules:
sources:
- type: archive
dest-filename: gamemode.tar.gz
- url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
- sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
+ url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2
+ sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60
x-checker-data:
type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
diff --git a/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch b/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
deleted file mode 100644
index 9130e856c..000000000
--- a/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-diff --git a/src/wl_window.c b/src/wl_window.c
-index 52d3b9eb..4ac4eb5d 100644
---- a/src/wl_window.c
-+++ b/src/wl_window.c
-@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title)
- void _glfwSetWindowIconWayland(_GLFWwindow* window,
- int count, const GLFWimage* images)
- {
-- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
-- "Wayland: The platform does not support setting the window icon");
-+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n");
- }
-
- void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
-@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window)
-
- void _glfwFocusWindowWayland(_GLFWwindow* window)
- {
-- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
-- "Wayland: The platform does not support setting the input focus");
-+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n");
- }
-
- void _glfwSetWindowMonitorWayland(_GLFWwindow* window,
diff --git a/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch b/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch
deleted file mode 100644
index b031d739f..000000000
--- a/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch
+++ /dev/null
@@ -1,17 +0,0 @@
-diff --git a/src/init.c b/src/init.c
-index 06dbb3f2..a7c6da86 100644
---- a/src/init.c
-+++ b/src/init.c
-@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void)
- _glfw.initialized = GLFW_TRUE;
-
- glfwDefaultWindowHints();
-+
-+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n"
-+ "!!! If any issues with the window, or some issues with rendering, occur, "
-+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n"
-+ "!!! Use outside Minecraft is untested, and things might break.\n");
-+
- return GLFW_TRUE;
- }
-
diff --git a/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch b/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
deleted file mode 100644
index 4eeb81309..000000000
--- a/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-diff --git a/src/platform.c b/src/platform.c
-index c5966ae7..3e7442f9 100644
---- a/src/platform.c
-+++ b/src/platform.c
-@@ -49,12 +49,12 @@ static const struct
- #if defined(_GLFW_COCOA)
- { GLFW_PLATFORM_COCOA, _glfwConnectCocoa },
- #endif
--#if defined(_GLFW_X11)
-- { GLFW_PLATFORM_X11, _glfwConnectX11 },
--#endif
- #if defined(_GLFW_WAYLAND)
- { GLFW_PLATFORM_WAYLAND, _glfwConnectWayland },
- #endif
-+#if defined(_GLFW_X11)
-+ { GLFW_PLATFORM_X11, _glfwConnectX11 },
-+#endif
- };
-
- GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform)
diff --git a/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
new file mode 100644
index 000000000..70cec9981
--- /dev/null
+++ b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
@@ -0,0 +1,59 @@
+From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001
+From: Dan Klishch
+Date: Sat, 30 Sep 2023 23:38:05 -0400
+Subject: Defer setting cursor position until the cursor is locked
+
+---
+ src/wl_platform.h | 3 +++
+ src/wl_window.c | 14 ++++++++++++--
+ 2 files changed, 15 insertions(+), 2 deletions(-)
+
+diff --git a/src/wl_platform.h b/src/wl_platform.h
+index ca34f66e..cd1f227f 100644
+--- a/src/wl_platform.h
++++ b/src/wl_platform.h
+@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland
+ int scaleSize;
+ int compositorPreferredScale;
+
++ double askedCursorPosX, askedCursorPosY;
++ GLFWbool didAskForSetCursorPos;
++
+ struct zwp_relative_pointer_v1* relativePointer;
+ struct zwp_locked_pointer_v1* lockedPointer;
+ struct zwp_confined_pointer_v1* confinedPointer;
+diff --git a/src/wl_window.c b/src/wl_window.c
+index 1de26558..0df16747 100644
+--- a/src/wl_window.c
++++ b/src/wl_window.c
+@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos)
+
+ void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y)
+ {
+- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
+- "Wayland: The platform does not support setting the cursor position");
++ window->wl.didAskForSetCursorPos = true;
++ window->wl.askedCursorPosX = x;
++ window->wl.askedCursorPosY = y;
+ }
+
+ void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode)
+@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener =
+ static void lockedPointerHandleLocked(void* userData,
+ struct zwp_locked_pointer_v1* lockedPointer)
+ {
++ _GLFWwindow* window = userData;
++
++ if (window->wl.didAskForSetCursorPos)
++ {
++ window->wl.didAskForSetCursorPos = false;
++ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer,
++ wl_fixed_from_double(window->wl.askedCursorPosX),
++ wl_fixed_from_double(window->wl.askedCursorPosY));
++ }
+ }
+
+ static void lockedPointerHandleUnlocked(void* userData,
+--
+2.42.0
+
diff --git a/flatpak/patches/weird_libdecor.patch b/flatpak/patches/weird_libdecor.patch
deleted file mode 100644
index 3a400b820..000000000
--- a/flatpak/patches/weird_libdecor.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-diff --git a/src/libdecor.c b/src/libdecor.c
-index a9c1106..1aa38b3 100644
---- a/src/libdecor.c
-+++ b/src/libdecor.c
-@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description)
- static bool
- check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description)
- {
-+ bool ret = true;
- char * const *symbol;
-+ void* main_prog = dlopen(NULL, RTLD_LAZY);
-+ if (!main_prog) {
-+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n",
-+ plugin_description->description, dlerror());
-+ return false;
-+ }
-+
-
- symbol = plugin_description->conflicting_symbols;
- while (*symbol) {
- dlerror();
-- dlsym (RTLD_DEFAULT, *symbol);
-+ dlsym (main_prog, *symbol);
- if (!dlerror()) {
- fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n",
- plugin_description->description, *symbol);
-- return false;
-+ ret = false;
-+ break;
- }
-
- symbol++;
- }
-
-- return true;
-+ dlclose(main_prog);
-+ return ret;
- }
-
- static struct plugin_loader *
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index b8dcc1099..b0ff14a6b 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -48,6 +48,7 @@
#include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h"
+#include "tasks/Task.h"
#include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
@@ -242,6 +243,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
+ { { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
@@ -257,6 +259,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile");
+ if (parser.isSet("offline")) {
+ m_offline = true;
+ m_offlineName = parser.value("offline");
+ }
m_liveCheck = parser.isSet("alive");
m_instanceIdToShowWindowOf = parser.value("show");
@@ -271,8 +277,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
// error if --launch is missing with --server or --profile
- if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
- std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
+ if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) &&
+ m_instanceIdToLaunch.isEmpty()) {
+ std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed;
return;
}
@@ -397,6 +404,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse;
}
+ if (m_offline) {
+ launch.args["offline_enabled"] = "true";
+ launch.args["offline_name"] = m_offlineName;
+ }
m_peerInstance->sendMessage(launch.serialize(), timeout);
}
m_status = Application::Succeeded;
@@ -605,6 +616,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IconsDir", "icons");
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
m_settings->registerSetting("DownloadsDirWatchRecursive", false);
+ m_settings->registerSetting("MoveModsFromDownloadsDir", false);
m_settings->registerSetting("SkinsDir", "skins");
m_settings->registerSetting("JavaDir", "java");
@@ -835,7 +847,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" };
m_icons.reset(new IconList(instFolders, setting->get().toString()));
connect(setting.get(), &Setting::SettingChanged,
- [&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
+ [this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
qDebug() << "<> Instance icons initialized.";
}
@@ -1070,8 +1082,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
bool Application::createSetupWizard()
{
- bool javaRequired = [&]() {
- bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
+ bool javaRequired = [this]() {
+ if (BuildConfig.JAVA_DOWNLOADER_ENABLED && settings()->get("AutomaticJavaDownload").toBool()) {
+ return false;
+ }
+ bool ignoreJavaWizard = settings()->get("IgnoreJavaWizard").toBool();
if (ignoreJavaWizard) {
return false;
}
@@ -1083,13 +1098,10 @@ bool Application::createSetupWizard()
}
QString currentJavaPath = settings()->get("JavaPath").toString();
QString actualPath = FS::ResolveExecutable(currentJavaPath);
- if (actualPath.isNull()) {
- return true;
- }
- return false;
+ return actualPath.isNull();
}();
- bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
- !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
+ bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !settings()->get("AutomaticJavaDownload").toBool() &&
+ !settings()->get("AutomaticJavaSwitch").toBool() && !settings()->get("UserAskedAboutAutomaticJavaDownload").toBool();
bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
@@ -1209,7 +1221,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
- launch(inst, true, false, targetToJoin, accountToUse);
+ launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
return;
}
}
@@ -1308,6 +1320,8 @@ void Application::messageReceived(const QByteArray& message)
QString server = received.args["server"];
QString world = received.args["world"];
QString profile = received.args["profile"];
+ bool offline = received.args["offline_enabled"] == "true";
+ QString offlineName = received.args["offline_name"];
InstancePtr instance;
if (!id.isEmpty()) {
@@ -1337,7 +1351,7 @@ void Application::messageReceived(const QByteArray& message)
}
}
- launch(instance, true, false, serverObject, accountObject);
+ launch(instance, !offline, false, serverObject, accountObject, offlineName);
} else {
qWarning() << "Received invalid message" << message;
}
@@ -1375,11 +1389,17 @@ bool Application::openJsonEditor(const QString& filename)
}
}
-bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
+bool Application::launch(InstancePtr instance,
+ bool online,
+ bool demo,
+ MinecraftTarget::Ptr targetToJoin,
+ MinecraftAccountPtr accountToUse,
+ const QString& offlineName)
{
if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) {
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()];
auto window = extras.window;
if (window) {
@@ -1395,6 +1415,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse);
+ controller->setOfflineName(offlineName);
if (window) {
controller->setParentWidget(window);
} else if (m_mainWindow) {
@@ -1404,7 +1425,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
addRunningInstance();
- controller->start();
+ QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection);
return true;
} else if (instance->isRunning()) {
showInstanceWindow(instance, "console");
@@ -1422,9 +1443,11 @@ bool Application::kill(InstancePtr instance)
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
return false;
}
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()];
// NOTE: copy of the shared pointer keeps it alive
auto controller = extras.controller;
+ locker.unlock();
if (controller) {
return controller->abort();
}
@@ -1478,12 +1501,14 @@ void Application::controllerSucceeded()
if (!controller)
return;
auto id = controller->id();
+
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id];
// on success, do...
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
if (extras.window) {
- extras.window->close();
+ QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection);
}
}
extras.controller.reset();
@@ -1503,6 +1528,7 @@ void Application::controllerFailed(const QString& error)
if (!controller)
return;
auto id = controller->id();
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id];
// on failure, do... nothing
@@ -1560,6 +1586,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
if (!instance)
return nullptr;
auto id = instance->id();
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id];
auto& window = extras.window;
@@ -1597,6 +1624,7 @@ void Application::on_windowClose()
m_openWindows--;
auto instWindow = qobject_cast(QObject::sender());
if (instWindow) {
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instWindow->instanceId()];
extras.window = nullptr;
if (extras.controller) {
@@ -1844,7 +1872,7 @@ bool Application::handleDataMigration(const QString& currentData,
matcher->add(std::make_shared("themes/"));
ProgressDialog diag;
- DataMigrationTask task(nullptr, oldData, currentData, matcher);
+ DataMigrationTask task(oldData, currentData, matcher);
if (diag.execWithTask(&task)) {
qDebug() << "<> Migration succeeded";
setDoNotMigrate();
@@ -1883,3 +1911,31 @@ const QString Application::javaPath()
{
return m_settings->get("JavaDir").toString();
}
+
+void Application::addQSavePath(QString path)
+{
+ QMutexLocker locker(&m_qsaveResourcesMutex);
+ m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1;
+}
+
+void Application::removeQSavePath(QString path)
+{
+ QMutexLocker locker(&m_qsaveResourcesMutex);
+ auto count = m_qsaveResources.value(path, 0) - 1;
+ if (count <= 0) {
+ m_qsaveResources.remove(path);
+ } else {
+ m_qsaveResources[path] = count;
+ }
+}
+
+bool Application::checkQSavePath(QString path)
+{
+ QMutexLocker locker(&m_qsaveResourcesMutex);
+ for (auto partialPath : m_qsaveResources.keys()) {
+ if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) {
+ return true;
+ }
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/launcher/Application.h b/launcher/Application.h
index 7432c9683..dad76d702 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -42,6 +42,7 @@
#include
#include
#include
+#include
#include
#include
@@ -81,6 +82,12 @@ class Index;
#endif
#define APPLICATION (static_cast(QCoreApplication::instance()))
+// Used for checking if is a test
+#if defined(APPLICATION_DYN)
+#undef APPLICATION_DYN
+#endif
+#define APPLICATION_DYN (dynamic_cast(QCoreApplication::instance()))
+
class Application : public QApplication {
// friends for the purpose of limiting access to deprecated stuff
Q_OBJECT
@@ -204,7 +211,8 @@ class Application : public QApplication {
bool online = true,
bool demo = false,
MinecraftTarget::Ptr targetToJoin = nullptr,
- MinecraftAccountPtr accountToUse = nullptr);
+ MinecraftAccountPtr accountToUse = nullptr,
+ const QString& offlineName = QString());
bool kill(InstancePtr instance);
void closeCurrentWindow();
@@ -272,6 +280,7 @@ class Application : public QApplication {
shared_qobject_ptr controller;
};
std::map m_instanceExtras;
+ mutable QMutex m_instanceExtrasMutex;
// main state variables
size_t m_openWindows = 0;
@@ -293,8 +302,19 @@ class Application : public QApplication {
QString m_serverToJoin;
QString m_worldToJoin;
QString m_profileToUse;
+ bool m_offline = false;
+ QString m_offlineName;
bool m_liveCheck = false;
QList m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr logFile;
+
+ public:
+ void addQSavePath(QString);
+ void removeQSavePath(QString);
+ bool checkQSavePath(QString);
+
+ private:
+ QHash m_qsaveResources;
+ mutable QMutex m_qsaveResourcesMutex;
};
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 69cf95e3c..ccfd0b847 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -411,3 +411,8 @@ void BaseInstance::updateRuntimeContext()
{
// NOOP
}
+
+bool BaseInstance::isLegacy()
+{
+ return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
+}
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 2be28d1ec..9827a08b4 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -269,6 +269,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this
-DataMigrationTask::DataMigrationTask(QObject* parent,
- const QString& sourcePath,
- const QString& targetPath,
- const IPathMatcher::Ptr pathMatcher)
- : Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
+DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
+ : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
{
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
}
@@ -27,7 +24,7 @@ void DataMigrationTask::executeTask()
// 1. Scan
// Check how many files we gotta copy
- m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
return m_copy(true); // dry run to collect amount of files
});
connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::dryRunFinished);
@@ -60,7 +57,7 @@ void DataMigrationTask::dryRunFinished()
setProgress(m_copy.totalCopied(), m_toCopy);
setStatus(tr("Copying %1…").arg(shortenedName));
});
- m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
return m_copy(false); // actually copy now
});
connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::copyFinished);
diff --git a/launcher/DataMigrationTask.h b/launcher/DataMigrationTask.h
index aba9f2399..fc613cd5e 100644
--- a/launcher/DataMigrationTask.h
+++ b/launcher/DataMigrationTask.h
@@ -18,7 +18,7 @@
class DataMigrationTask : public Task {
Q_OBJECT
public:
- explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
+ explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default;
protected:
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 7f38cff04..954e7936e 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -45,7 +45,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -54,6 +53,7 @@
#include
#include "DesktopServices.h"
+#include "PSaveFile.h"
#include "StringUtils.h"
#if defined Q_OS_WIN32
@@ -191,8 +191,8 @@ void ensureExists(const QDir& dir)
void write(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
- QSaveFile file(filename);
- if (!file.open(QSaveFile::WriteOnly)) {
+ PSaveFile file(filename);
+ if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (data.size() != file.write(data)) {
@@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data)
buffer = QByteArray();
}
buffer.append(data);
- QSaveFile file(filename);
- if (!file.open(QSaveFile::WriteOnly)) {
+ PSaveFile file(filename);
+ if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (buffer.size() != file.write(buffer)) {
@@ -341,7 +341,7 @@ bool copy::operator()(const QString& offset, bool dryRun)
opt |= copy_opts::overwrite_existing;
// Function that'll do the actual copying
- auto copy_file = [&](QString src_path, QString relative_dst_path) {
+ auto copy_file = [this, dryRun, src, dst, opt, &err](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;
@@ -428,7 +428,7 @@ void create_link::make_link_list(const QString& offset)
m_recursive = true;
// Function that'll do the actual linking
- auto link_file = [&](QString src_path, QString relative_dst_path) {
+ auto link_file = [this, dst](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
return;
@@ -523,7 +523,7 @@ void create_link::runPrivileged(const QString& offset)
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
- connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() {
+ connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() {
qDebug() << "Client connected, sending out pairs";
// construct block of data to send
QByteArray block;
@@ -605,7 +605,7 @@ void create_link::runPrivileged(const QString& offset)
}
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
- connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); });
+ connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
linkFileProcess->start();
@@ -971,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty())
argstring = " \"" + args.join("\" \"") + "\"";
- stream << "#!/bin/bash"
- << "\n";
+ stream << "#!/bin/bash" << "\n";
stream << "\"" << target << "\" " << argstring << "\n";
stream.flush();
@@ -1016,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty())
argstring = " '" + args.join("' '") + "'";
- stream << "[Desktop Entry]"
- << "\n";
- stream << "Type=Application"
- << "\n";
- stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
- << "\n";
+ stream << "[Desktop Entry]" << "\n";
+ stream << "Type=Application" << "\n";
+ stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty()) {
@@ -1299,7 +1295,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
std::error_code err;
// Function that'll do the actual cloneing
- auto cloneFile = [&](QString src_path, QString relative_dst_path) {
+ auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 0220a4144..d335b11c4 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -91,7 +91,7 @@ void InstanceCopyTask::executeTask()
QEventLoop loop;
bool got_priv_results = false;
- connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) {
+ connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](bool gotResults) {
if (!gotResults) {
qDebug() << "Privileged run exited without results!";
}
diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp
index 9c17dfc9f..bd3514798 100644
--- a/launcher/InstanceCreationTask.cpp
+++ b/launcher/InstanceCreationTask.cpp
@@ -38,22 +38,29 @@ void InstanceCreationTask::executeTask()
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
// put the instance in an invalid state.
if (shouldOverride()) {
+ bool deleteFailed = false;
+
setAbortable(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
- for (auto path : m_files_to_remove) {
+ for (const QString& path : m_files_to_remove) {
if (!QFile::exists(path))
continue;
+
qDebug() << "Removing" << path;
- if (!FS::deletePath(path)) {
- qCritical() << "Couldn't remove the old conflicting files.";
- emitFailed(tr("Failed to remove old conflicting files."));
- return;
+
+ if (!QFile::remove(path)) {
+ qCritical() << "Could not remove" << path;
+ deleteFailed = true;
}
}
- }
- emitSucceeded();
- return;
+ if (deleteFailed) {
+ emitFailed(tr("Failed to remove old conflicting files."));
+ return;
+ }
+ }
+ if (!m_abort)
+ emitSucceeded();
}
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 57cc77527..71630656d 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -69,9 +69,11 @@ bool InstanceImportTask::abort()
if (!canAbort())
return false;
- if (task)
- task->abort();
- return Task::abort();
+ bool wasAborted = false;
+ if (m_task)
+ wasAborted = m_task->abort();
+ Task::abort();
+ return wasAborted;
}
void InstanceImportTask::executeTask()
@@ -104,7 +106,7 @@ void InstanceImportTask::downloadFromUrl()
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
- task.reset(filesNetJob);
+ m_task.reset(filesNetJob);
filesNetJob->start();
}
@@ -193,7 +195,7 @@ void InstanceImportTask::processZipPack()
stepProgress(*progressStep);
});
- connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished);
+ connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection);
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
@@ -210,12 +212,13 @@ void InstanceImportTask::processZipPack()
progressStep->status = status;
stepProgress(*progressStep);
});
- task.reset(zipTask);
+ m_task.reset(zipTask);
zipTask->start();
}
void InstanceImportTask::extractFinished()
{
+ setAbortable(false);
QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files...";
@@ -289,8 +292,11 @@ void InstanceImportTask::processFlame()
inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
- connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] {
- setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
+ auto weak = inst_creation_task.toWeakRef();
+ connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
+ if (auto sp = weak.lock()) {
+ setOverride(sp->shouldOverride(), sp->originalInstanceID());
+ }
emitSucceeded();
});
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
@@ -299,11 +305,12 @@ void InstanceImportTask::processFlame()
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
- connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
- inst_creation_task->start();
+ m_task.reset(inst_creation_task);
+ setAbortable(true);
+ m_task->start();
}
void InstanceImportTask::processTechnic()
@@ -350,7 +357,7 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth()
{
- ModrinthCreationTask* inst_creation_task = nullptr;
+ shared_qobject_ptr inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
@@ -367,7 +374,7 @@ void InstanceImportTask::processModrinth()
original_instance_id = original_instance_id_it.value();
inst_creation_task =
- new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
+ makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
QString pack_id;
if (!m_sourceUrl.isEmpty()) {
@@ -376,7 +383,7 @@ void InstanceImportTask::processModrinth()
}
// FIXME: Find a way to get the ID in directly imported ZIPs
- inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
+ inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id);
}
inst_creation_task->setName(*this);
@@ -384,20 +391,23 @@ void InstanceImportTask::processModrinth()
inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
- connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
- setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
+ auto weak = inst_creation_task.toWeakRef();
+ connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
+ if (auto sp = weak.lock()) {
+ setOverride(sp->shouldOverride(), sp->originalInstanceID());
+ }
emitSucceeded();
});
- connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
- connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
- connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
- connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
- connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
- connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
+ connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
+ connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
+ connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
+ connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
+ connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
- connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
- connect(inst_creation_task, &Task::aborted, this, &Task::abort);
- connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
+ connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
+ connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
- inst_creation_task->start();
+ m_task.reset(inst_creation_task);
+ setAbortable(true);
+ m_task->start();
}
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index cf86af4ea..8884e0801 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -40,16 +40,13 @@
#include
#include "InstanceTask.h"
-#include
-#include
-
class QuaZip;
class InstanceImportTask : public InstanceTask {
Q_OBJECT
public:
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {});
-
+ virtual ~InstanceImportTask() = default;
bool abort() override;
protected:
@@ -70,7 +67,7 @@ class InstanceImportTask : public InstanceTask {
private: /* data */
QUrl m_sourceUrl;
QString m_archivePath;
- Task::Ptr task;
+ Task::Ptr m_task;
enum class ModpackType {
Unknown,
MultiMC,
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index e1fa755dd..918fa1073 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -487,7 +487,7 @@ InstanceList::InstListError InstanceList::loadList()
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
- auto removeNow = [&]() {
+ auto removeNow = [this, &front_bookmark, &back_bookmark, ¤tItem]() {
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();
diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp
index 3cbf9f9d5..188edb943 100644
--- a/launcher/JavaCommon.cpp
+++ b/launcher/JavaCommon.cpp
@@ -116,7 +116,7 @@ void JavaCommon::TestCheck::run()
emit finished();
return;
}
- checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this));
+ checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->start();
}
@@ -128,7 +128,7 @@ void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
emit finished();
return;
}
- checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this));
+ checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->start();
}
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index 687da1322..0aded4a95 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -43,6 +43,7 @@
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/MSALoginDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h"
@@ -61,7 +62,7 @@
#include "launch/steps/TextPrint.h"
#include "tasks/Task.h"
-LaunchController::LaunchController(QObject* parent) : Task(parent) {}
+LaunchController::LaunchController() : Task() {}
void LaunchController::executeTask()
{
@@ -234,10 +235,15 @@ void LaunchController::login()
if (!m_session->wants_online) {
// we ask the user for a player name
bool ok = false;
- auto name = askOfflineName(m_session->player_name, m_session->demo, ok);
- if (!ok) {
- tryagain = false;
- break;
+ QString name;
+ if (m_offlineName.isEmpty()) {
+ name = askOfflineName(m_session->player_name, m_session->demo, ok);
+ if (!ok) {
+ tryagain = false;
+ break;
+ }
+ } else {
+ name = m_offlineName;
}
m_session->MakeOffline(name);
// offline flavored game from here :3
@@ -287,10 +293,8 @@ void LaunchController::login()
continue;
}
case AccountState::Expired: {
- auto errorString = tr("The account has expired and needs to be logged into manually again.");
- QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
- QMessageBox::StandardButton::Ok);
- emitFailed(errorString);
+ if (reauthenticateCurrentAccount())
+ continue;
return;
}
case AccountState::Disabled: {
@@ -314,6 +318,33 @@ void LaunchController::login()
emitFailed(tr("Failed to launch."));
}
+bool LaunchController::reauthenticateCurrentAccount()
+{
+ auto button =
+ QMessageBox::warning(m_parentWidget, tr("Account refresh failed"),
+ tr("The account has expired and needs to be reauthenticated. Do you want to reauthenticate this account?"),
+ QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes);
+ if (button == QMessageBox::StandardButton::Yes) {
+ auto accounts = APPLICATION->accounts();
+ bool isDefault = accounts->defaultAccount() == m_accountToUse;
+ accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId())));
+ if (m_accountToUse->accountType() == AccountType::MSA) {
+ auto newAccount = MSALoginDialog::newAccount(m_parentWidget);
+ accounts->addAccount(newAccount);
+ if (isDefault) {
+ accounts->setDefaultAccount(newAccount);
+ }
+ m_accountToUse = nullptr;
+ decideAccount();
+ return true;
+ }
+ emitFailed(tr("Account expired and re-login attempt failed"));
+ } else {
+ emitFailed(tr("The account has expired and needs to be reauthenticated"));
+ }
+ return false;
+}
+
void LaunchController::launchInstance()
{
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h
index 6e2a94258..7e6a27d91 100644
--- a/launcher/LaunchController.h
+++ b/launcher/LaunchController.h
@@ -47,7 +47,7 @@ class LaunchController : public Task {
public:
void executeTask() override;
- LaunchController(QObject* parent = nullptr);
+ LaunchController();
virtual ~LaunchController() = default;
void setInstance(InstancePtr instance) { m_instance = instance; }
@@ -56,6 +56,8 @@ class LaunchController : public Task {
void setOnline(bool online) { m_online = online; }
+ void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; }
+
void setDemo(bool demo) { m_demo = demo; }
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
@@ -76,6 +78,7 @@ class LaunchController : public Task {
void decideAccount();
bool askPlayDemo();
QString askOfflineName(QString playerName, bool demo, bool& ok);
+ bool reauthenticateCurrentAccount();
private slots:
void readyForLaunch();
@@ -87,6 +90,7 @@ class LaunchController : public Task {
private:
BaseProfilerFactory* m_profiler = nullptr;
bool m_online = true;
+ QString m_offlineName;
bool m_demo = false;
InstancePtr m_instance;
QWidget* m_parentWidget = nullptr;
diff --git a/launcher/Launcher.in b/launcher/Launcher.in
index 1a23f2555..706d7022b 100755
--- a/launcher/Launcher.in
+++ b/launcher/Launcher.in
@@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then
# Just to be sure...
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
+ ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}")
+
+ if [ -f portable.txt ]; then
+ ARGS+=("-d" "${LAUNCHER_DIR}")
+ fi
+
+ ARGS+=("$@")
+
# Run the launcher
- exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
+ exec -a "${ARGS[@]}"
# Run the launcher in valgrind
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index fadd64e68..35ce4e0e5 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -39,7 +39,8 @@
#include
#include "MessageLevel.h"
-LoggedProcess::LoggedProcess(QObject* parent) : QProcess(parent)
+LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent)
+ : QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec)
{
// QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h
index 46bdaa830..75ba15dfd 100644
--- a/launcher/LoggedProcess.h
+++ b/launcher/LoggedProcess.h
@@ -49,7 +49,7 @@ class LoggedProcess : public QProcess {
enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted };
public:
- explicit LoggedProcess(QObject* parent = 0);
+ explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0);
virtual ~LoggedProcess();
State state() const;
@@ -80,8 +80,8 @@ class LoggedProcess : public QProcess {
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
private:
- QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
- QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
+ QTextDecoder m_err_decoder;
+ QTextDecoder m_out_decoder;
QString m_leftover_line;
bool m_killed = false;
State m_state = NotRunning;
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index dcf3d566f..b38aca17a 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -378,7 +378,7 @@ std::optional extractDir(QString fileCompressed, QString dir)
if (fileInfo.size() == 22) {
return QStringList();
}
- qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
+ qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
;
return std::nullopt;
}
@@ -395,7 +395,7 @@ std::optional extractDir(QString fileCompressed, QString subdir, QS
if (fileInfo.size() == 22) {
return QStringList();
}
- qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
+ qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
;
return std::nullopt;
}
@@ -412,7 +412,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
if (fileInfo.size() == 22) {
return true;
}
- qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
+ qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
return false;
}
return extractRelFile(&zip, file, target);
@@ -577,7 +577,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
auto original_name = relative_file_name;
- setStatus("Unziping: " + relative_file_name);
+ setStatus("Unpacking: " + relative_file_name);
// Fix subdirs/files ending with a / getting transformed into absolute paths
if (relative_file_name.startsWith('/'))
diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h
index b6bd13045..0ba9c5ac8 100644
--- a/launcher/MTPixmapCache.h
+++ b/launcher/MTPixmapCache.h
@@ -101,7 +101,7 @@ class PixmapCache final : public QObject {
*/
bool _markCacheMissByEviciton()
{
- static constexpr uint maxInt = static_cast(std::numeric_limits::max());
+ static constexpr uint maxCache = static_cast(std::numeric_limits::max()) / 4;
static constexpr uint step = 10240;
static constexpr int oneSecond = 1000;
@@ -118,8 +118,8 @@ class PixmapCache final : public QObject {
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
// increase the cache size
uint newSize = _cacheLimit() + step;
- if (newSize >= maxInt) { // increase it until you overflow :D
- newSize = maxInt;
+ if (newSize >= maxCache) { // increase it until you overflow :D
+ newSize = maxCache;
qDebug() << m_consecutive_fast_evicitons
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
} else {
diff --git a/launcher/PSaveFile.h b/launcher/PSaveFile.h
new file mode 100644
index 000000000..ba6154ad8
--- /dev/null
+++ b/launcher/PSaveFile.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#pragma once
+
+#include
+#include
+#include "Application.h"
+
+#if defined(LAUNCHER_APPLICATION)
+
+/* PSaveFile
+ * A class that mimics QSaveFile for Windows.
+ *
+ * When reading resources, we need to avoid accessing temporary files
+ * generated by QSaveFile. If we start reading such a file, we may
+ * inadvertently keep it open while QSaveFile is trying to remove it,
+ * or we might detect the file just before it is removed, leading to
+ * race conditions and errors.
+ *
+ * Unfortunately, QSaveFile doesn't provide a way to retrieve the
+ * temporary file name or to set a specific template for the temporary
+ * file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix
+ * to the original file name, where the `XXXXXX` part is dynamically
+ * generated to ensure uniqueness.
+ *
+ * This class acts like a lock by adding and removing the target file
+ * name into/from a global string set, helping to manage access to
+ * files during critical operations.
+ *
+ * Note: Please do not use the `setFileName` function directly, as it
+ * is not virtual and cannot be overridden.
+ */
+class PSaveFile : public QSaveFile {
+ public:
+ PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); }
+ PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); }
+ virtual ~PSaveFile()
+ {
+ if (auto app = APPLICATION_DYN) {
+ app->removeQSavePath(m_absoluteFilePath);
+ }
+ }
+
+ private:
+ void addPath(const QString& path)
+ {
+ m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only
+ if (auto app = APPLICATION_DYN) {
+ app->addQSavePath(m_absoluteFilePath);
+ }
+ }
+ QString m_absoluteFilePath;
+};
+#else
+#define PSaveFile QSaveFile
+#endif
\ No newline at end of file
diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h
index a1c64b433..88c17c0b2 100644
--- a/launcher/QObjectPtr.h
+++ b/launcher/QObjectPtr.h
@@ -33,7 +33,7 @@ class shared_qobject_ptr : public QSharedPointer {
{}
void reset() { QSharedPointer::reset(); }
- void reset(T*&& other)
+ void reset(T* other)
{
shared_qobject_ptr t(other);
this->swap(t);
diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp
index 6f5b9a189..0fe082ac4 100644
--- a/launcher/ResourceDownloadTask.cpp
+++ b/launcher/ResourceDownloadTask.cpp
@@ -35,9 +35,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
QString custom_target_folder)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
{
- if (auto model = dynamic_cast(m_pack_model.get()); model && is_indexed) {
- m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version));
- connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
+ if (is_indexed) {
+ m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
+ connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
addTask(m_update_task);
}
@@ -91,12 +91,8 @@ void ResourceDownloadTask::downloadSucceeded()
m_filesNetJob.reset();
auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete);
- if (!name.isEmpty() && filename != m_pack_version.fileName) {
- if (auto model = dynamic_cast(m_pack_model.get()); model)
- model->uninstallMod(filename, true);
- else
- m_pack_model->uninstallResource(filename);
- }
+ if (!name.isEmpty() && filename != m_pack_version.fileName)
+ m_pack_model->uninstallResource(filename, true);
}
void ResourceDownloadTask::downloadFailed(QString reason)
diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h
index f686e819a..a10e0ac2c 100644
--- a/launcher/ResourceDownloadTask.h
+++ b/launcher/ResourceDownloadTask.h
@@ -22,7 +22,7 @@
#include "net/NetJob.h"
#include "tasks/SequentialTask.h"
-#include "minecraft/mod/tasks/LocalModUpdateTask.h"
+#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/ModIndex.h"
class ResourceFolderModel;
@@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask {
QString m_custom_target_folder;
NetJob::Ptr m_filesNetJob;
- LocalModUpdateTask::Ptr m_update_task;
+ LocalResourceUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total);
void downloadFailed(QString reason);
diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp
index 0dfa74de7..cfcf63805 100644
--- a/launcher/SysInfo.cpp
+++ b/launcher/SysInfo.cpp
@@ -81,9 +81,9 @@ QString getSupportedJavaArchitecture()
if (arch == "arm64")
return "mac-os-arm64";
if (arch.contains("64"))
- return "mac-os-64";
+ return "mac-os-x64";
if (arch.contains("86"))
- return "mac-os-86";
+ return "mac-os-x86";
// Unknown, maybe something new, appending arch
return "mac-os-" + arch;
} else if (sys == "linux") {
diff --git a/launcher/Version.cpp b/launcher/Version.cpp
index 511aa9c35..03a16e8a0 100644
--- a/launcher/Version.cpp
+++ b/launcher/Version.cpp
@@ -79,7 +79,7 @@ void Version::parse()
if (m_string.isEmpty())
return;
- auto classChange = [&](QChar lastChar, QChar currentChar) {
+ auto classChange = [¤tSection](QChar lastChar, QChar currentChar) {
if (lastChar.isNull())
return false;
if (lastChar.isDigit() != currentChar.isDigit())
@@ -123,8 +123,7 @@ QDebug operator<<(QDebug debug, const Version& v)
first = false;
}
- debug.nospace() << " ]"
- << " }";
+ debug.nospace() << " ]" << " }";
return debug;
}
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index bdf173ebc..b641b41d5 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -104,11 +104,11 @@ void FileLinkApp::joinServer(QString server)
in.setDevice(&socket);
- connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; });
+ connect(&socket, &QLocalSocket::connected, this, []() { qDebug() << "connected to server"; });
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
- connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
+ connect(&socket, &QLocalSocket::errorOccurred, this, [this](QLocalSocket::LocalSocketError socketError) {
m_status = Failed;
switch (socketError) {
case QLocalSocket::ServerNotFoundError:
@@ -132,7 +132,7 @@ void FileLinkApp::joinServer(QString server)
}
});
- connect(&socket, &QLocalSocket::disconnected, this, [&]() {
+ connect(&socket, &QLocalSocket::disconnected, this, [this]() {
qDebug() << "disconnected from server, should exit";
m_status = Succeeded;
exit();
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index e4157ea2d..f4022e0fb 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -47,24 +47,24 @@
#define MAX_SIZE 1024
-IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent)
+IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject* parent) : QAbstractListModel(parent)
{
QSet builtinNames;
// add builtin icons
- for (auto& builtinPath : builtinPaths) {
- QDir instance_icons(builtinPath);
- auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
- for (auto file_info : file_info_list) {
- builtinNames.insert(file_info.completeBaseName());
+ for (const auto& builtinPath : builtinPaths) {
+ QDir instanceIcons(builtinPath);
+ auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name);
+ for (const auto& fileInfo : fileInfoList) {
+ builtinNames.insert(fileInfo.baseName());
}
}
- for (auto& builtinName : builtinNames) {
+ for (const auto& builtinName : builtinNames) {
addThemeIcon(builtinName);
}
m_watcher.reset(new QFileSystemWatcher());
- is_watching = false;
+ m_isWatching = false;
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
@@ -77,91 +77,131 @@ IconList::IconList(const QStringList& builtinPaths, QString path, QObject* paren
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
- std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
+ std::sort(m_icons.begin(), m_icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
+ bool aIsSubdir = a.m_key.contains(QDir::separator());
+ bool bIsSubdir = b.m_key.contains(QDir::separator());
+ if (aIsSubdir != bIsSubdir) {
+ return !aIsSubdir; // root-level icons come first
+ }
+ return a.m_key.localeAwareCompare(b.m_key) < 0;
+ });
reindex();
}
+// Helper function to add directories recursively
+bool IconList::addPathRecursively(const QString& path)
+{
+ QDir dir(path);
+ if (!dir.exists())
+ return false;
+
+ // Add the directory itself
+ bool watching = m_watcher->addPath(path);
+
+ // Add all subdirectories
+ QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QFileInfo& entry : entries) {
+ if (addPathRecursively(entry.absoluteFilePath())) {
+ watching = true;
+ }
+ }
+ return watching;
+}
+
+QStringList IconList::getIconFilePaths() const
+{
+ QStringList iconFiles{};
+ QStringList directories{ m_dir.absolutePath() };
+ while (!directories.isEmpty()) {
+ QString first = directories.takeFirst();
+ QDir dir(first);
+ for (QFileInfo& fileInfo : dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) {
+ if (fileInfo.isDir())
+ directories.push_back(fileInfo.absoluteFilePath());
+ else
+ iconFiles.push_back(fileInfo.absoluteFilePath());
+ }
+ }
+ return iconFiles;
+}
+
+QString formatName(const QDir& iconsDir, const QFileInfo& iconFile)
+{
+ if (iconFile.dir() == iconsDir)
+ return iconFile.baseName();
+
+ constexpr auto delimiter = " » ";
+ QString relativePathWithoutExtension = iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.baseName();
+ return relativePathWithoutExtension.replace(QDir::separator(), delimiter);
+}
+
+/// Split into a separate function because the preprocessing impedes readability
+QSet toStringSet(const QList& list)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QSet set(list.begin(), list.end());
+#else
+ QSet set = list.toSet();
+#endif
+ return set;
+}
+
void IconList::directoryChanged(const QString& path)
{
- QDir new_dir(path);
- if (m_dir.absolutePath() != new_dir.absolutePath()) {
- m_dir.setPath(path);
+ QDir newDir(path);
+ if (m_dir.absolutePath() != newDir.absolutePath()) {
+ if (!path.startsWith(m_dir.absolutePath()))
+ m_dir.setPath(path);
m_dir.refresh();
- if (is_watching)
+ if (m_isWatching)
stopWatching();
startWatching();
}
- if (!m_dir.exists())
- if (!FS::ensureFolderPathExists(m_dir.absolutePath()))
- return;
+ if (!m_dir.exists() && !FS::ensureFolderPathExists(m_dir.absolutePath()))
+ return;
m_dir.refresh();
- auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
- for (auto it = new_list.begin(); it != new_list.end(); it++) {
- QString& foo = (*it);
- foo = m_dir.filePath(foo);
- }
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- QSet new_set(new_list.begin(), new_list.end());
-#else
- auto new_set = new_list.toSet();
-#endif
- QList current_list;
- for (auto& it : icons) {
+ const QStringList newFileNamesList = getIconFilePaths();
+ const QSet newSet = toStringSet(newFileNamesList);
+ QSet currentSet;
+ for (const MMCIcon& it : m_icons) {
if (!it.has(IconType::FileBased))
continue;
- current_list.push_back(it.m_images[IconType::FileBased].filename);
+ currentSet.insert(it.m_images[IconType::FileBased].filename);
}
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- QSet current_set(current_list.begin(), current_list.end());
-#else
- QSet current_set = current_list.toSet();
-#endif
+ QSet toRemove = currentSet - newSet;
+ QSet toAdd = newSet - currentSet;
- QSet to_remove = current_set;
- to_remove -= new_set;
-
- QSet to_add = new_set;
- to_add -= current_set;
-
- for (auto remove : to_remove) {
- qDebug() << "Removing " << remove;
- QFileInfo rmfile(remove);
- QString key = rmfile.completeBaseName();
-
- QString suffix = rmfile.suffix();
- // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
- if (!IconUtils::isIconSuffix(suffix))
- key = rmfile.fileName();
+ for (const QString& removedPath : toRemove) {
+ qDebug() << "Removing icon " << removedPath;
+ QFileInfo removedFile(removedPath);
+ QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath());
int idx = getIconIndex(key);
if (idx == -1)
continue;
- icons[idx].remove(IconType::FileBased);
- if (icons[idx].type() == IconType::ToBeDeleted) {
+ m_icons[idx].remove(FileBased);
+ if (m_icons[idx].type() == ToBeDeleted) {
beginRemoveRows(QModelIndex(), idx, idx);
- icons.remove(idx);
+ m_icons.remove(idx);
reindex();
endRemoveRows();
} else {
dataChanged(index(idx), index(idx));
}
- m_watcher->removePath(remove);
+ m_watcher->removePath(removedPath);
emit iconUpdated(key);
}
- for (auto add : to_add) {
- qDebug() << "Adding " << add;
+ for (const QString& addedPath : toAdd) {
+ qDebug() << "Adding icon " << addedPath;
- QFileInfo addfile(add);
- QString key = addfile.completeBaseName();
+ QFileInfo addfile(addedPath);
+ QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath());
+ QString key = QFileInfo(relativePath).completeBaseName();
+ QString name = formatName(m_dir, addfile);
- QString suffix = addfile.suffix();
- // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
- if (!IconUtils::isIconSuffix(suffix))
- key = addfile.fileName();
-
- if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
- m_watcher->addPath(add);
+ if (addIcon(key, name, addfile.filePath(), IconType::FileBased)) {
+ m_watcher->addPath(addedPath);
emit iconUpdated(key);
}
}
@@ -171,24 +211,24 @@ void IconList::directoryChanged(const QString& path)
void IconList::fileChanged(const QString& path)
{
- qDebug() << "Checking " << path;
+ qDebug() << "Checking icon " << path;
QFileInfo checkfile(path);
if (!checkfile.exists())
return;
- QString key = checkfile.completeBaseName();
+ QString key = m_dir.relativeFilePath(checkfile.absoluteFilePath());
int idx = getIconIndex(key);
if (idx == -1)
return;
QIcon icon(path);
- if (!icon.availableSizes().size())
+ if (icon.availableSizes().empty())
return;
- icons[idx].m_images[IconType::FileBased].icon = icon;
+ m_icons[idx].m_images[IconType::FileBased].icon = icon;
dataChanged(index(idx), index(idx));
emit iconUpdated(key);
}
-void IconList::SettingChanged(const Setting& setting, QVariant value)
+void IconList::SettingChanged(const Setting& setting, const QVariant& value)
{
if (setting.id() != "IconsDir")
return;
@@ -200,8 +240,8 @@ void IconList::startWatching()
{
auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path);
- is_watching = m_watcher->addPath(abs_path);
- if (is_watching) {
+ m_isWatching = addPathRecursively(abs_path);
+ if (m_isWatching) {
qDebug() << "Started watching " << abs_path;
} else {
qDebug() << "Failed to start watching " << abs_path;
@@ -212,7 +252,7 @@ void IconList::stopWatching()
{
m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories());
- is_watching = false;
+ m_isWatching = false;
}
QStringList IconList::mimeTypes() const
@@ -242,7 +282,7 @@ bool IconList::dropMimeData(const QMimeData* data,
if (data->hasUrls()) {
auto urls = data->urls();
QStringList iconFiles;
- for (auto url : urls) {
+ for (const auto& url : urls) {
// only local files may be dropped...
if (!url.isLocalFile())
continue;
@@ -263,33 +303,33 @@ Qt::ItemFlags IconList::flags(const QModelIndex& index) const
QVariant IconList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
- return QVariant();
+ return {};
int row = index.row();
- if (row < 0 || row >= icons.size())
- return QVariant();
+ if (row < 0 || row >= m_icons.size())
+ return {};
switch (role) {
case Qt::DecorationRole:
- return icons[row].icon();
+ return m_icons[row].icon();
case Qt::DisplayRole:
- return icons[row].name();
+ return m_icons[row].name();
case Qt::UserRole:
- return icons[row].m_key;
+ return m_icons[row].m_key;
default:
- return QVariant();
+ return {};
}
}
int IconList::rowCount(const QModelIndex& parent) const
{
- return parent.isValid() ? 0 : icons.size();
+ return parent.isValid() ? 0 : m_icons.size();
}
void IconList::installIcons(const QStringList& iconFiles)
{
- for (QString file : iconFiles)
+ for (const QString& file : iconFiles)
installIcon(file, {});
}
@@ -312,12 +352,13 @@ bool IconList::iconFileExists(const QString& key) const
return iconEntry && iconEntry->has(IconType::FileBased);
}
+/// Returns the icon with the given key or nullptr if it doesn't exist.
const MMCIcon* IconList::icon(const QString& key) const
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return nullptr;
- return &icons[iconIdx];
+ return &m_icons[iconIdx];
}
bool IconList::deleteIcon(const QString& key)
@@ -332,22 +373,22 @@ bool IconList::trashIcon(const QString& key)
bool IconList::addThemeIcon(const QString& key)
{
- auto iter = name_index.find(key);
- if (iter != name_index.end()) {
- auto& oldOne = icons[*iter];
+ auto iter = m_nameIndex.find(key);
+ if (iter != m_nameIndex.end()) {
+ auto& oldOne = m_icons[*iter];
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
}
// add a new icon
- beginInsertRows(QModelIndex(), icons.size(), icons.size());
+ beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = key;
mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key);
- icons.push_back(mmc_icon);
- name_index[key] = icons.size() - 1;
+ m_icons.push_back(mmc_icon);
+ m_nameIndex[key] = m_icons.size() - 1;
}
endInsertRows();
return true;
@@ -359,22 +400,22 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
QIcon icon(path);
if (icon.isNull())
return false;
- auto iter = name_index.find(key);
- if (iter != name_index.end()) {
- auto& oldOne = icons[*iter];
+ auto iter = m_nameIndex.find(key);
+ if (iter != m_nameIndex.end()) {
+ auto& oldOne = m_icons[*iter];
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
}
// add a new icon
- beginInsertRows(QModelIndex(), icons.size(), icons.size());
+ beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = name;
mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path);
- icons.push_back(mmc_icon);
- name_index[key] = icons.size() - 1;
+ m_icons.push_back(mmc_icon);
+ m_nameIndex[key] = m_icons.size() - 1;
}
endInsertRows();
return true;
@@ -389,33 +430,32 @@ void IconList::saveIcon(const QString& key, const QString& path, const char* for
void IconList::reindex()
{
- name_index.clear();
- int i = 0;
- for (auto& iter : icons) {
- name_index[iter.m_key] = i;
- i++;
+ m_nameIndex.clear();
+ for (int i = 0; i < m_icons.size(); i++) {
+ m_nameIndex[m_icons[i].m_key] = i;
+ emit iconUpdated(m_icons[i].m_key); // prevents incorrect indices with proxy model
}
}
QIcon IconList::getIcon(const QString& key) const
{
- int icon_index = getIconIndex(key);
+ int iconIndex = getIconIndex(key);
- if (icon_index != -1)
- return icons[icon_index].icon();
+ if (iconIndex != -1)
+ return m_icons[iconIndex].icon();
- // Fallback for icons that don't exist.
- icon_index = getIconIndex("grass");
+ // Fallback for icons that don't exist.b
+ iconIndex = getIconIndex("grass");
- if (icon_index != -1)
- return icons[icon_index].icon();
- return QIcon();
+ if (iconIndex != -1)
+ return m_icons[iconIndex].icon();
+ return {};
}
int IconList::getIconIndex(const QString& key) const
{
- auto iter = name_index.find(key == "default" ? "grass" : key);
- if (iter != name_index.end())
+ auto iter = m_nameIndex.find(key == "default" ? "grass" : key);
+ if (iter != m_nameIndex.end())
return *iter;
return -1;
@@ -425,3 +465,15 @@ QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
+
+/// Returns the directory of the icon with the given key or the default directory if it's a builtin icon.
+QString IconList::iconDirectory(const QString& key) const
+{
+ for (const auto& mmcIcon : m_icons) {
+ if (mmcIcon.m_key == key && mmcIcon.has(IconType::FileBased)) {
+ QFileInfo iconFile(mmcIcon.getFilePath());
+ return iconFile.dir().path();
+ }
+ }
+ return getDirectory();
+}
diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h
index 553946c42..8936195c3 100644
--- a/launcher/icons/IconList.h
+++ b/launcher/icons/IconList.h
@@ -51,7 +51,7 @@ class QFileSystemWatcher;
class IconList : public QAbstractListModel {
Q_OBJECT
public:
- explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
+ explicit IconList(const QStringList& builtinPaths, const QString& path, QObject* parent = 0);
virtual ~IconList() {};
QIcon getIcon(const QString& key) const;
@@ -72,6 +72,7 @@ class IconList : public QAbstractListModel {
bool deleteIcon(const QString& key);
bool trashIcon(const QString& key);
bool iconFileExists(const QString& key) const;
+ QString iconDirectory(const QString& key) const;
void installIcons(const QStringList& iconFiles);
void installIcon(const QString& file, const QString& name);
@@ -91,18 +92,20 @@ class IconList : public QAbstractListModel {
IconList& operator=(const IconList&) = delete;
void reindex();
void sortIconList();
+ bool addPathRecursively(const QString& path);
+ QStringList getIconFilePaths() const;
public slots:
void directoryChanged(const QString& path);
protected slots:
void fileChanged(const QString& path);
- void SettingChanged(const Setting& setting, QVariant value);
+ void SettingChanged(const Setting& setting, const QVariant& value);
private:
shared_qobject_ptr m_watcher;
- bool is_watching;
- QMap name_index;
- QVector icons;
+ bool m_isWatching;
+ QMap m_nameIndex;
+ QVector m_icons;
QDir m_dir;
};
diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp
index c54a5b04b..772c90e42 100644
--- a/launcher/java/JavaChecker.cpp
+++ b/launcher/java/JavaChecker.cpp
@@ -44,8 +44,8 @@
#include "FileSystem.h"
#include "java/JavaUtils.h"
-JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent)
- : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
+JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id)
+ : Task(), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
{}
void JavaChecker::executeTask()
diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h
index 171a18b76..a04b68170 100644
--- a/launcher/java/JavaChecker.h
+++ b/launcher/java/JavaChecker.h
@@ -1,7 +1,6 @@
#pragma once
#include
#include
-#include
#include "JavaVersion.h"
#include "QObjectPtr.h"
@@ -26,7 +25,7 @@ class JavaChecker : public Task {
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
};
- explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
+ explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0);
signals:
void checkFinished(const Result& result);
diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp
index 569fda306..aa7fab8a0 100644
--- a/launcher/java/JavaInstallList.cpp
+++ b/launcher/java/JavaInstallList.cpp
@@ -163,7 +163,7 @@ void JavaListLoadTask::executeTask()
JavaUtils ju;
QList candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
- ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
+ ConcurrentTask::Ptr job(new ConcurrentTask("Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
m_job.reset(job);
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
@@ -171,7 +171,7 @@ void JavaListLoadTask::executeTask()
qDebug() << "Probing the following Java paths: ";
int id = 0;
for (QString candidate : candidate_paths) {
- auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this);
+ auto checker = new JavaChecker(candidate, "", 0, 0, 0, id);
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
job->addTask(Task::Ptr(checker));
id++;
diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp
index bc8026348..072cb1d16 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -102,6 +102,8 @@ QProcessEnvironment CleanEnviroment()
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
qDebug() << "Env: stripped" << key << value << "to" << newValue;
+
+ value = newValue;
}
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
// Strip IBus
@@ -403,7 +405,7 @@ QList JavaUtils::FindJavaPaths()
{
QList javas;
javas.append(this->GetDefaultJava()->path);
- auto scanJavaDir = [&](
+ auto scanJavaDir = [&javas](
const QString& dirPath,
const std::function& filter = [](const QFileInfo&) { return true; }) {
QDir dir(dirPath);
@@ -422,7 +424,7 @@ QList JavaUtils::FindJavaPaths()
};
// java installed in a snap is installed in the standard directory, but underneath $SNAP
auto snap = qEnvironmentVariable("SNAP");
- auto scanJavaDirs = [&](const QString& dirPath) {
+ auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath) {
scanJavaDir(dirPath);
if (!snap.isNull()) {
scanJavaDir(snap + dirPath);
@@ -440,9 +442,15 @@ QList JavaUtils::FindJavaPaths()
QString fileName = info.fileName();
return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
};
+ // AOSC OS's locations for openjdk
+ auto aoscFilter = [](const QFileInfo& info) {
+ QString fileName = info.fileName();
+ return fileName == "java" || fileName.startsWith("java-");
+ };
scanJavaDir("/usr/lib64", gentooFilter);
scanJavaDir("/usr/lib", gentooFilter);
scanJavaDir("/opt", gentooFilter);
+ scanJavaDir("/usr/lib", aoscFilter);
// javas stored in Prism Launcher's folder
scanJavaDirs("java");
// manually installed JDKs in /opt
@@ -544,12 +552,12 @@ QStringList getPrismJavaBundle()
{
QList javas;
- auto scanDir = [&](QString prefix) {
+ auto scanDir = [&javas](QString prefix) {
javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
};
- auto scanJavaDir = [&](const QString& dirPath) {
+ auto scanJavaDir = [scanDir](const QString& dirPath) {
QDir dir(dirPath);
if (!dir.exists())
return;
diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp
index 5e9700012..bca50f2c9 100644
--- a/launcher/java/JavaVersion.cpp
+++ b/launcher/java/JavaVersion.cpp
@@ -48,6 +48,12 @@ bool JavaVersion::requiresPermGen() const
return !m_parseable || m_major < 8;
}
+bool JavaVersion::defaultsToUtf8() const
+{
+ // starting from Java 18, UTF-8 is the default charset: https://openjdk.org/jeps/400
+ return m_parseable && m_major >= 18;
+}
+
bool JavaVersion::isModular() const
{
return m_parseable && m_major >= 9;
diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h
index dfb4770da..c070bdeec 100644
--- a/launcher/java/JavaVersion.h
+++ b/launcher/java/JavaVersion.h
@@ -25,7 +25,7 @@ class JavaVersion {
bool operator>(const JavaVersion& rhs);
bool requiresPermGen() const;
-
+ bool defaultsToUtf8() const;
bool isModular() const;
QString toString() const;
diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp
index 836afeaac..20b39e751 100644
--- a/launcher/java/download/ManifestDownloadTask.cpp
+++ b/launcher/java/download/ManifestDownloadTask.cpp
@@ -86,11 +86,10 @@ void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
if (type == "directory") {
FS::ensureFolderPathExists(file);
} else if (type == "link") {
- // this is linux only !
+ // this is *nix only !
auto path = Json::ensureString(meta, "target");
if (!path.isEmpty()) {
- auto target = FS::PathCombine(file, "../" + path);
- QFile(target).link(file);
+ QFile::link(path, file);
}
} else if (type == "file") {
// TODO download compressed version if it exists ?
diff --git a/launcher/launch/LaunchStep.cpp b/launcher/launch/LaunchStep.cpp
index f3e9dfce0..0b352ea9f 100644
--- a/launcher/launch/LaunchStep.cpp
+++ b/launcher/launch/LaunchStep.cpp
@@ -16,7 +16,7 @@
#include "LaunchStep.h"
#include "LaunchTask.h"
-LaunchStep::LaunchStep(LaunchTask* parent) : Task(parent), m_parent(parent)
+LaunchStep::LaunchStep(LaunchTask* parent) : Task(), m_parent(parent)
{
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp
index 4e4f5ead4..4b93d2077 100644
--- a/launcher/launch/LaunchTask.cpp
+++ b/launcher/launch/LaunchTask.cpp
@@ -51,14 +51,14 @@ void LaunchTask::init()
m_instance->setRunning(true);
}
-shared_qobject_ptr LaunchTask::create(InstancePtr inst)
+shared_qobject_ptr LaunchTask::create(MinecraftInstancePtr inst)
{
shared_qobject_ptr proc(new LaunchTask(inst));
proc->init();
return proc;
}
-LaunchTask::LaunchTask(InstancePtr instance) : m_instance(instance) {}
+LaunchTask::LaunchTask(MinecraftInstancePtr instance) : m_instance(instance) {}
void LaunchTask::appendStep(shared_qobject_ptr step)
{
@@ -254,20 +254,60 @@ void LaunchTask::emitFailed(QString reason)
Task::emitFailed(reason);
}
-void LaunchTask::substituteVariables(QStringList& args) const
+QString expandVariables(const QString& input, QProcessEnvironment dict)
{
- auto env = m_instance->createEnvironment();
+ QString result = input;
- for (auto key : env.keys()) {
- args.replaceInStrings("$" + key, env.value(key));
+ enum { base, maybeBrace, variable, brace } state = base;
+ int startIdx = -1;
+ for (int i = 0; i < result.length();) {
+ QChar c = result.at(i++);
+ switch (state) {
+ case base:
+ if (c == '$')
+ state = maybeBrace;
+ break;
+ case maybeBrace:
+ if (c == '{') {
+ state = brace;
+ startIdx = i;
+ } else if (c.isLetterOrNumber() || c == '_') {
+ state = variable;
+ startIdx = i - 1;
+ } else {
+ state = base;
+ }
+ break;
+ case brace:
+ if (c == '}') {
+ const auto res = dict.value(result.mid(startIdx, i - 1 - startIdx), "");
+ if (!res.isEmpty()) {
+ result.replace(startIdx - 2, i - startIdx + 2, res);
+ i = startIdx - 2 + res.length();
+ }
+ state = base;
+ }
+ break;
+ case variable:
+ if (!c.isLetterOrNumber() && c != '_') {
+ const auto res = dict.value(result.mid(startIdx, i - startIdx - 1), "");
+ if (!res.isEmpty()) {
+ result.replace(startIdx - 1, i - startIdx, res);
+ i = startIdx - 1 + res.length();
+ }
+ state = base;
+ }
+ break;
+ }
}
+ if (state == variable) {
+ if (const auto res = dict.value(result.mid(startIdx), ""); !res.isEmpty())
+ result.replace(startIdx - 1, result.length() - startIdx + 1, res);
+ }
+ return result;
}
-void LaunchTask::substituteVariables(QString& cmd) const
+QString LaunchTask::substituteVariables(QString& cmd, bool isLaunch) const
{
- auto env = m_instance->createEnvironment();
-
- for (auto key : env.keys()) {
- cmd.replace("$" + key, env.value(key));
- }
+ return expandVariables(cmd, isLaunch ? m_instance->createLaunchEnvironment() : m_instance->createEnvironment());
}
diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h
index 2fd8c78c7..2e87ece95 100644
--- a/launcher/launch/LaunchTask.h
+++ b/launcher/launch/LaunchTask.h
@@ -37,6 +37,7 @@
#pragma once
#include
+#include
#include
#include "BaseInstance.h"
#include "LaunchStep.h"
@@ -46,21 +47,21 @@
class LaunchTask : public Task {
Q_OBJECT
protected:
- explicit LaunchTask(InstancePtr instance);
+ explicit LaunchTask(MinecraftInstancePtr instance);
void init();
public:
enum State { NotStarted, Running, Waiting, Failed, Aborted, Finished };
public: /* methods */
- static shared_qobject_ptr create(InstancePtr inst);
+ static shared_qobject_ptr create(MinecraftInstancePtr inst);
virtual ~LaunchTask() = default;
void appendStep(shared_qobject_ptr step);
void prependStep(shared_qobject_ptr step);
void setCensorFilter(QMap filter);
- InstancePtr instance() { return m_instance; }
+ MinecraftInstancePtr instance() { return m_instance; }
void setPid(qint64 pid) { m_pid = pid; }
@@ -86,8 +87,7 @@ class LaunchTask : public Task {
shared_qobject_ptr getLogModel();
public:
- void substituteVariables(QStringList& args) const;
- void substituteVariables(QString& cmd) const;
+ QString substituteVariables(QString& cmd, bool isLaunch = false) const;
QString censorPrivateInfo(QString in);
protected: /* methods */
@@ -115,7 +115,7 @@ class LaunchTask : public Task {
void finalizeSteps(bool successful, const QString& error);
protected: /* data */
- InstancePtr m_instance;
+ MinecraftInstancePtr m_instance;
shared_qobject_ptr m_logModel;
QList> m_steps;
QMap m_censorFilter;
diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp
index 55d13b58c..0f8d27e94 100644
--- a/launcher/launch/steps/CheckJava.cpp
+++ b/launcher/launch/steps/CheckJava.cpp
@@ -94,7 +94,7 @@ void CheckJava::executeTask()
// if timestamps are not the same, or something is missing, check!
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
- m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this));
+ m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0));
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->start();
diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp
index 725101224..5d893c71f 100644
--- a/launcher/launch/steps/PostLaunchCommand.cpp
+++ b/launcher/launch/steps/PostLaunchCommand.cpp
@@ -47,25 +47,21 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
void PostLaunchCommand::executeTask()
{
- // FIXME: where to put this?
+ auto cmd = m_parent->substituteVariables(m_command);
+ emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
- auto args = QProcess::splitCommand(m_command);
- m_parent->substituteVariables(args);
+ auto args = QProcess::splitCommand(cmd);
- emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
const QString program = args.takeFirst();
m_process.start(program, args);
#else
- m_parent->substituteVariables(m_command);
-
- emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher);
- m_process.start(m_command);
+ m_process.start(cmd);
#endif
}
void PostLaunchCommand::on_state(LoggedProcess::State state)
{
- auto getError = [&]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
+ auto getError = [this]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
switch (state) {
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp
index 6d071a66e..318237e99 100644
--- a/launcher/launch/steps/PreLaunchCommand.cpp
+++ b/launcher/launch/steps/PreLaunchCommand.cpp
@@ -47,25 +47,20 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
void PreLaunchCommand::executeTask()
{
- // FIXME: where to put this?
+ auto cmd = m_parent->substituteVariables(m_command);
+ emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
- auto args = QProcess::splitCommand(m_command);
- m_parent->substituteVariables(args);
-
- emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
+ auto args = QProcess::splitCommand(cmd);
const QString program = args.takeFirst();
m_process.start(program, args);
#else
- m_parent->substituteVariables(m_command);
-
- emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher);
- m_process.start(m_command);
+ m_process.start(cmd);
#endif
}
void PreLaunchCommand::on_state(LoggedProcess::State state)
{
- auto getError = [&]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
+ auto getError = [this]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
switch (state) {
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp
index bd0745b6b..1707854be 100644
--- a/launcher/meta/Index.cpp
+++ b/launcher/meta/Index.cpp
@@ -140,8 +140,8 @@ Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mo
}
auto versionList = get(uid);
- auto loadTask = makeShared(
- this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
+ auto loadTask =
+ makeShared(tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
if (status() != BaseEntity::LoadStatus::Remote || force) {
loadTask->addTask(this->loadTask(mode));
}
diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp
index 6856b5f8d..1de4e7f36 100644
--- a/launcher/meta/VersionList.cpp
+++ b/launcher/meta/VersionList.cpp
@@ -34,8 +34,7 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
Task::Ptr VersionList::getLoadTask()
{
- auto loadTask =
- makeShared(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
+ auto loadTask = makeShared(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
loadTask->addTask(this->loadTask(Net::Mode::Online));
return loadTask;
@@ -159,8 +158,8 @@ Version::Ptr VersionList::getVersion(const QString& version)
bool VersionList::hasVersion(QString version) const
{
- auto ver =
- std::find_if(m_versions.constBegin(), m_versions.constEnd(), [&](Meta::Version::Ptr const& a) { return a->version() == version; });
+ auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
+ [version](Meta::Version::Ptr const& a) { return a->version() == version; });
return (ver != m_versions.constEnd());
}
diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp
index 1073ef324..ad7ef545c 100644
--- a/launcher/minecraft/Component.cpp
+++ b/launcher/minecraft/Component.cpp
@@ -222,11 +222,12 @@ bool Component::isMoveable()
return true;
}
-bool Component::isVersionChangeable()
+bool Component::isVersionChangeable(bool wait)
{
auto list = getVersionList();
if (list) {
- list->waitToLoad();
+ if (wait)
+ list->waitToLoad();
return list->count() != 0;
}
return false;
diff --git a/launcher/minecraft/Component.h b/launcher/minecraft/Component.h
index 7ff30889f..203cc2241 100644
--- a/launcher/minecraft/Component.h
+++ b/launcher/minecraft/Component.h
@@ -72,7 +72,7 @@ class Component : public QObject, public ProblemProvider {
bool isRevertible();
bool isRemovable();
bool isCustom();
- bool isVersionChangeable();
+ bool isVersionChangeable(bool wait = true);
bool isKnownModloader();
QStringList knownConflictingComponents();
diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp
index 6656a84f8..36a07ee72 100644
--- a/launcher/minecraft/ComponentUpdateTask.cpp
+++ b/launcher/minecraft/ComponentUpdateTask.cpp
@@ -38,7 +38,7 @@
* If the component list changes, start over.
*/
-ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent)
+ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list) : Task()
{
d.reset(new ComponentUpdateTaskData);
d->m_profile = list;
diff --git a/launcher/minecraft/ComponentUpdateTask.h b/launcher/minecraft/ComponentUpdateTask.h
index 484c0bedd..64c55877b 100644
--- a/launcher/minecraft/ComponentUpdateTask.h
+++ b/launcher/minecraft/ComponentUpdateTask.h
@@ -14,7 +14,7 @@ class ComponentUpdateTask : public Task {
enum class Mode { Launch, Resolution };
public:
- explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent = 0);
+ explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list);
virtual ~ComponentUpdateTask();
protected:
diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp
index 4f04f0eb9..0bc462474 100644
--- a/launcher/minecraft/Library.cpp
+++ b/launcher/minecraft/Library.cpp
@@ -65,7 +65,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
{
bool local = isLocal();
// Lambda function to get the absolute file path
- auto actualPath = [&](QString relPath) {
+ auto actualPath = [this, local, overridePath](QString relPath) {
relPath = FS::RemoveInvalidPathChars(relPath);
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
if (local && !overridePath.isEmpty()) {
@@ -115,7 +115,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC
bool local = isLocal();
// Lambda function to check if a local file exists
- auto check_local_file = [&](QString storage) {
+ auto check_local_file = [overridePath, &failedLocalFiles](QString storage) {
QFileInfo fileinfo(storage);
QString fileName = fileinfo.fileName();
auto fullPath = FS::PathCombine(overridePath, fileName);
@@ -128,7 +128,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC
};
// Lambda function to add a download request
- auto add_download = [&](QString storage, QString url, QString sha1) {
+ auto add_download = [this, local, check_local_file, cache, stale, &out](QString storage, QString url, QString sha1) {
if (local) {
return check_local_file(storage);
}
@@ -198,7 +198,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC
}
}
} else {
- auto raw_dl = [&]() {
+ auto raw_dl = [this, raw_storage]() {
if (!m_absoluteURL.isEmpty()) {
return m_absoluteURL;
}
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 98ac7647f..3ce563a4e 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -91,13 +91,56 @@
#include "tools/BaseProfiler.h"
#include
+#include
+#include
+#include
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
+#ifdef WITH_QTDBUS
+#include
+#endif
+
#define IBUS "@im=ibus"
+static bool switcherooSetupGPU(QProcessEnvironment& env)
+{
+#ifdef WITH_QTDBUS
+ if (!QDBusConnection::systemBus().isConnected())
+ return false;
+
+ QDBusInterface switcheroo("net.hadess.SwitcherooControl", "/net/hadess/SwitcherooControl", "org.freedesktop.DBus.Properties",
+ QDBusConnection::systemBus());
+
+ if (!switcheroo.isValid())
+ return false;
+
+ QDBusReply reply =
+ switcheroo.call(QStringLiteral("Get"), QStringLiteral("net.hadess.SwitcherooControl"), QStringLiteral("GPUs"));
+ if (!reply.isValid())
+ return false;
+
+ QDBusArgument arg = qvariant_cast(reply.value().variant());
+ QList gpus;
+ arg >> gpus;
+
+ for (const auto& gpu : gpus) {
+ QString name = qvariant_cast(gpu[QStringLiteral("Name")]);
+ bool defaultGpu = qvariant_cast(gpu[QStringLiteral("Default")]);
+ if (!defaultGpu) {
+ QStringList envList = qvariant_cast(gpu[QStringLiteral("Environment")]);
+ for (int i = 0; i + 1 < envList.size(); i += 2) {
+ env.insert(envList[i], envList[i + 1]);
+ }
+ return true;
+ }
+ }
+#endif
+ return false;
+}
+
// all of this because keeping things compatible with deprecated old settings
// if either of the settings {a, b} is true, this also resolves to true
class OrSetting : public Setting {
@@ -530,7 +573,7 @@ QStringList MinecraftInstance::javaArguments()
QString MinecraftInstance::getLauncher()
{
// use legacy launcher if the traits are set
- if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
+ if (isLegacy())
return "legacy";
return "standard";
@@ -551,6 +594,13 @@ QMap MinecraftInstance::getVariables()
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
out.insert("NO_COLOR", "1");
+#ifdef Q_OS_MACOS
+ // get library for Steam overlay support
+ QString steamDyldInsertLibraries = qEnvironmentVariable("STEAM_DYLD_INSERT_LIBRARIES");
+ if (!steamDyldInsertLibraries.isEmpty()) {
+ out.insert("DYLD_INSERT_LIBRARIES", steamDyldInsertLibraries);
+ }
+#endif
return out;
}
@@ -614,12 +664,14 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
}
if (settings()->get("UseDiscreteGpu").toBool()) {
- // Open Source Drivers
- env.insert("DRI_PRIME", "1");
- // Proprietary Nvidia Drivers
- env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
- env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
- env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
+ if (!switcherooSetupGPU(env)) {
+ // Open Source Drivers
+ env.insert("DRI_PRIME", "1");
+ // Proprietary Nvidia Drivers
+ env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
+ env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
+ env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
+ }
}
if (settings()->get("UseZink").toBool()) {
@@ -752,11 +804,34 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT
// window size, title and state, legacy
{
QString windowParams;
- if (settings()->get("LaunchMaximized").toBool())
- windowParams = "maximized";
- else
+ if (settings()->get("LaunchMaximized").toBool()) {
+ // FIXME doesn't support maximisation
+ if (!isLegacy()) {
+ auto screen = QGuiApplication::primaryScreen();
+ auto screenGeometry = screen->availableSize();
+
+ // small hack to get the widow decorations
+ for (auto w : QApplication::topLevelWidgets()) {
+ auto mainWindow = qobject_cast(w);
+ if (mainWindow) {
+ auto m = mainWindow->windowHandle()->frameMargins();
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
+ screenGeometry = screenGeometry.shrunkBy(m);
+#else
+ screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() };
+#endif
+ break;
+ }
+ }
+
+ windowParams = QString("%1x%2").arg(screenGeometry.width()).arg(screenGeometry.height());
+ } else {
+ windowParams = "maximized";
+ }
+ } else {
windowParams =
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
+ }
launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "windowParams " + windowParams + "\n";
}
@@ -828,7 +903,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Libraries:";
QStringList jars, nativeJars;
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
- auto printLibFile = [&](const QString& path) {
+ auto printLibFile = [&out](const QString& path) {
QFileInfo info(path);
if (info.exists()) {
out << " " + path;
@@ -848,7 +923,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
}
// mods and core mods
- auto printModList = [&](const QString& label, ModFolderModel& model) {
+ auto printModList = [&out](const QString& label, ModFolderModel& model) {
if (model.size()) {
out << QString("%1:").arg(label);
auto modList = model.allMods();
@@ -1083,17 +1158,10 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(step);
}
- // run pre-launch command if that's needed
- if (getPreLaunchCommand().size()) {
- auto step = makeShared(pptr);
- step->setWorkingDirectory(gameRoot());
- process->appendStep(step);
- }
-
// load meta
{
auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline;
- process->appendStep(makeShared(pptr, makeShared(this, mode, pptr)));
+ process->appendStep(makeShared(pptr, makeShared(this, mode)));
}
// check java
@@ -1102,6 +1170,13 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared(pptr));
}
+ // run pre-launch command if that's needed
+ if (getPreLaunchCommand().size()) {
+ auto step = makeShared(pptr);
+ step->setWorkingDirectory(gameRoot());
+ process->appendStep(step);
+ }
+
// if we aren't in offline mode,.
if (session->status != AuthSession::PlayableOffline) {
if (!session->demo) {
@@ -1177,7 +1252,7 @@ std::shared_ptr MinecraftInstance::loaderModList()
{
if (!m_loader_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
+ m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true));
}
return m_loader_mod_list;
}
@@ -1186,7 +1261,7 @@ std::shared_ptr MinecraftInstance::coreModList()
{
if (!m_core_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
+ m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true));
}
return m_core_mod_list;
}
@@ -1203,7 +1278,8 @@ std::shared_ptr MinecraftInstance::nilModList()
std::shared_ptr MinecraftInstance::resourcePackList()
{
if (!m_resource_pack_list) {
- m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
+ bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
+ m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true));
}
return m_resource_pack_list;
}
@@ -1211,7 +1287,8 @@ std::shared_ptr MinecraftInstance::resourcePackList()
std::shared_ptr MinecraftInstance::texturePackList()
{
if (!m_texture_pack_list) {
- m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
+ bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
+ m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true));
}
return m_texture_pack_list;
}
@@ -1219,11 +1296,17 @@ std::shared_ptr MinecraftInstance::texturePackList()
std::shared_ptr MinecraftInstance::shaderPackList()
{
if (!m_shader_pack_list) {
- m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
+ bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
+ m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true));
}
return m_shader_pack_list;
}
+QList> MinecraftInstance::resourceLists()
+{
+ return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() };
+}
+
std::shared_ptr MinecraftInstance::worldList()
{
if (!m_world_list) {
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index 75e97ae45..5d9bb45ef 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -116,6 +116,7 @@ class MinecraftInstance : public BaseInstance {
std::shared_ptr resourcePackList();
std::shared_ptr texturePackList();
std::shared_ptr shaderPackList();
+ QList> resourceLists();
std::shared_ptr worldList();
std::shared_ptr gameOptionsModel();
diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp
index a9dcdf067..c0a82e61e 100644
--- a/launcher/minecraft/MinecraftLoadAndCheck.cpp
+++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp
@@ -2,15 +2,16 @@
#include "MinecraftInstance.h"
#include "PackProfile.h"
-MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent)
- : Task(parent), m_inst(inst), m_netmode(netmode)
-{}
+MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode) : m_inst(inst), m_netmode(netmode) {}
void MinecraftLoadAndCheck::executeTask()
{
// add offline metadata load task
auto components = m_inst->getPackProfile();
- components->reload(m_netmode);
+ if (auto result = components->reload(m_netmode); !result) {
+ emitFailed(result.error);
+ return;
+ }
m_task = components->getCurrentTask();
if (!m_task) {
diff --git a/launcher/minecraft/MinecraftLoadAndCheck.h b/launcher/minecraft/MinecraftLoadAndCheck.h
index 72e9e0caa..c05698bca 100644
--- a/launcher/minecraft/MinecraftLoadAndCheck.h
+++ b/launcher/minecraft/MinecraftLoadAndCheck.h
@@ -23,7 +23,7 @@ class MinecraftInstance;
class MinecraftLoadAndCheck : public Task {
Q_OBJECT
public:
- explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent = nullptr);
+ explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode);
virtual ~MinecraftLoadAndCheck() = default;
void executeTask() override;
diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp
index bd587beb2..684869c8d 100644
--- a/launcher/minecraft/OneSixVersionFormat.cpp
+++ b/launcher/minecraft/OneSixVersionFormat.cpp
@@ -176,7 +176,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
}
}
- auto readLibs = [&](const char* which, QList& outList) {
+ auto readLibs = [&root, &out, &filename](const char* which, QList& outList) {
for (auto libVal : requireArray(root.value(which))) {
QJsonObject libObj = requireObject(libVal);
// parse the library
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index f1d2473c2..d6534b910 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -173,29 +173,32 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c
}
// Read the given file into component containers
-static bool loadPackProfile(PackProfile* parent,
- const QString& filename,
- const QString& componentJsonPattern,
- ComponentContainer& container)
+static PackProfile::Result loadPackProfile(PackProfile* parent,
+ const QString& filename,
+ const QString& componentJsonPattern,
+ ComponentContainer& container)
{
QFile componentsFile(filename);
if (!componentsFile.exists()) {
- qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen.";
- return false;
+ auto message = QObject::tr("Components file %1 doesn't exist. This should never happen.").arg(filename);
+ qCWarning(instanceProfileC) << message;
+ return PackProfile::Result::Error(message);
}
if (!componentsFile.open(QFile::ReadOnly)) {
- qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
+ auto message = QObject::tr("Couldn't open %1 for reading: %2").arg(componentsFile.fileName(), componentsFile.errorString());
+ qCCritical(instanceProfileC) << message;
qCWarning(instanceProfileC) << "Ignoring overridden order";
- return false;
+ return PackProfile::Result::Error(message);
}
// and it's valid JSON
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
- qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
+ auto message = QObject::tr("Couldn't parse %1 as json: %2").arg(componentsFile.fileName(), error.errorString());
+ qCCritical(instanceProfileC) << message;
qCWarning(instanceProfileC) << "Ignoring overridden order";
- return false;
+ return PackProfile::Result::Error(message);
}
// and then read it and process it if all above is true.
@@ -212,11 +215,13 @@ static bool loadPackProfile(PackProfile* parent,
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
}
} catch ([[maybe_unused]] const JSONValidationError& err) {
- qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
+ auto message = QObject::tr("Couldn't parse %1 : bad file format").arg(componentsFile.fileName());
+ qCCritical(instanceProfileC) << message;
+ qCWarning(instanceProfileC) << "error:" << err.what();
container.clear();
- return false;
+ return PackProfile::Result::Error(message);
}
- return true;
+ return PackProfile::Result::Success();
}
// END: component file format
@@ -283,44 +288,43 @@ void PackProfile::save_internal()
d->dirty = false;
}
-bool PackProfile::load()
+PackProfile::Result PackProfile::load()
{
auto filename = componentsFilePath();
// load the new component list and swap it with the current one...
ComponentContainer newComponents;
- if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) {
+ if (auto result = loadPackProfile(this, filename, patchesPattern(), newComponents); !result) {
qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
- return false;
- } else {
- // FIXME: actually use fine-grained updates, not this...
- beginResetModel();
- // disconnect all the old components
- for (auto component : d->components) {
- disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
- }
- d->components.clear();
- d->componentIndex.clear();
- for (auto component : newComponents) {
- if (d->componentIndex.contains(component->m_uid)) {
- qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
- continue;
- }
- connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
- d->components.append(component);
- d->componentIndex[component->m_uid] = component;
- }
- endResetModel();
- d->loaded = true;
- return true;
+ return result;
}
+ // FIXME: actually use fine-grained updates, not this...
+ beginResetModel();
+ // disconnect all the old components
+ for (auto component : d->components) {
+ disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
+ }
+ d->components.clear();
+ d->componentIndex.clear();
+ for (auto component : newComponents) {
+ if (d->componentIndex.contains(component->m_uid)) {
+ qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
+ continue;
+ }
+ connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
+ d->components.append(component);
+ d->componentIndex[component->m_uid] = component;
+ }
+ endResetModel();
+ d->loaded = true;
+ return Result::Success();
}
-void PackProfile::reload(Net::Mode netmode)
+PackProfile::Result PackProfile::reload(Net::Mode netmode)
{
// Do not reload when the update/resolve task is running. It is in control.
if (d->m_updateTask) {
- return;
+ return Result::Success();
}
// flush any scheduled saves to not lose state
@@ -329,9 +333,11 @@ void PackProfile::reload(Net::Mode netmode)
// FIXME: differentiate when a reapply is required by propagating state from components
invalidateLaunchProfile();
- if (load()) {
- resolve(netmode);
+ if (auto result = load(); !result) {
+ return result;
}
+ resolve(netmode);
+ return Result::Success();
}
Task::Ptr PackProfile::getCurrentTask()
@@ -746,7 +752,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
}
// FIXME: we need a generic way of removing local resources, not just jar mods...
- auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool {
+ auto preRemoveJarMod = [this](LibraryPtr jarMod) -> bool {
if (!jarMod->isLocal()) {
return true;
}
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index b2de26ea0..d812dfa48 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -62,6 +62,19 @@ class PackProfile : public QAbstractListModel {
public:
enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS };
+ struct Result {
+ bool success;
+ QString error;
+
+ // Implicit conversion to bool
+ operator bool() const { return success; }
+
+ // Factory methods for convenience
+ static Result Success() { return { true, "" }; }
+
+ static Result Error(const QString& errorMessage) { return { false, errorMessage }; }
+ };
+
explicit PackProfile(MinecraftInstance* instance);
virtual ~PackProfile();
@@ -102,7 +115,7 @@ class PackProfile : public QAbstractListModel {
bool revertToBase(int index);
/// reload the list, reload all components, resolve dependencies
- void reload(Net::Mode netmode);
+ Result reload(Net::Mode netmode);
// reload all components, resolve dependencies
void resolve(Net::Mode netmode);
@@ -169,7 +182,7 @@ class PackProfile : public QAbstractListModel {
void disableInteraction(bool disable);
private:
- bool load();
+ Result load();
bool installJarMods_internal(QStringList filepaths);
bool installCustomJar_internal(QString filepath);
bool installAgents_internal(QStringList filepaths);
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp
index 1eba148a5..bd28f9e9a 100644
--- a/launcher/minecraft/World.cpp
+++ b/launcher/minecraft/World.cpp
@@ -38,7 +38,6 @@
#include
#include
#include
-#include
#include
#include
@@ -57,6 +56,7 @@
#include
#include "FileSystem.h"
+#include "PSaveFile.h"
using std::nullopt;
using std::optional;
@@ -183,7 +183,7 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
if (fullFilePath.isNull()) {
return false;
}
- QSaveFile f(fullFilePath);
+ PSaveFile f(fullFilePath);
if (!f.open(QIODevice::WriteOnly)) {
return false;
}
diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp
index 45926206c..19fbe15dd 100644
--- a/launcher/minecraft/auth/AuthFlow.cpp
+++ b/launcher/minecraft/auth/AuthFlow.cpp
@@ -19,7 +19,7 @@
#include
-AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data)
+AuthFlow::AuthFlow(AccountData* data, Action action) : Task(), m_data(data)
{
if (data->type == AccountType::MSA) {
if (action == Action::DeviceCode) {
diff --git a/launcher/minecraft/auth/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h
index 4d18ac845..bff4c04e4 100644
--- a/launcher/minecraft/auth/AuthFlow.h
+++ b/launcher/minecraft/auth/AuthFlow.h
@@ -17,7 +17,7 @@ class AuthFlow : public Task {
public:
enum class Action { Refresh, Login, DeviceCode };
- explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0);
+ explicit AuthFlow(AccountData* data, Action action = Action::Refresh);
virtual ~AuthFlow() = default;
void executeTask() override;
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index 5b063604c..1ed39b5ca 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -121,7 +121,7 @@ shared_qobject_ptr MinecraftAccount::login(bool useDeviceCode)
{
Q_ASSERT(m_currentTask.get() == nullptr);
- m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this));
+ m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
@@ -135,7 +135,7 @@ shared_qobject_ptr MinecraftAccount::refresh()
return m_currentTask;
}
- m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this));
+ m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp
index 3aa458ace..f9d89baa2 100644
--- a/launcher/minecraft/auth/Parsers.cpp
+++ b/launcher/minecraft/auth/Parsers.cpp
@@ -180,6 +180,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
if (!getString(skinObj.value("url"), skinOut.url)) {
continue;
}
+ skinOut.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net");
if (!getString(skinObj.value("variant"), skinOut.variant)) {
continue;
}
@@ -221,9 +222,9 @@ namespace {
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
// they are needed because the session server doesn't return skin urls for default skins
static const QString SKIN_URL_STEVE =
- "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
+ "https://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
static const QString SKIN_URL_ALEX =
- "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
+ "https://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
bool isDefaultModelSteve(QString uuid)
{
diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp
index c283b153e..38ff90a47 100644
--- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp
+++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp
@@ -74,12 +74,12 @@ void MSADeviceCodeStep::perform()
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
- connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
+ connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAuthorizationFinished);
m_task->start();
}
-struct DeviceAutorizationResponse {
+struct DeviceAuthorizationResponse {
QString device_code;
QString user_code;
QString verification_uri;
@@ -90,17 +90,17 @@ struct DeviceAutorizationResponse {
QString error_description;
};
-DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data)
+DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& data)
{
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) {
- qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
+ qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
return {};
}
if (!doc.isObject()) {
- qWarning() << "Device autorization response is not an object";
+ qWarning() << "Device authorization response is not an object";
return {};
}
auto obj = doc.object();
@@ -111,9 +111,9 @@ DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& dat
};
}
-void MSADeviceCodeStep::deviceAutorizationFinished()
+void MSADeviceCodeStep::deviceAuthorizationFinished()
{
- auto rsp = parseDeviceAutorizationResponse(*m_response);
+ auto rsp = parseDeviceAuthorizationResponse(*m_response);
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
qWarning() << "Device authorization failed:" << rsp.error;
emit finished(AccountTaskState::STATE_FAILED_HARD,
@@ -208,12 +208,12 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) {
- qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
+ qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
return {};
}
if (!doc.isObject()) {
- qWarning() << "Device autorization response is not an object";
+ qWarning() << "Device authorization response is not an object";
return {};
}
auto obj = doc.object();
@@ -274,4 +274,4 @@ void MSADeviceCodeStep::authenticationFinished()
m_data->msaToken.refresh_token = rsp.refresh_token;
m_data->msaToken.token = rsp.access_token;
emit finished(AccountTaskState::STATE_WORKING, tr("Got"));
-}
\ No newline at end of file
+}
diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.h b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h
index 616008def..7f755563f 100644
--- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.h
+++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h
@@ -58,7 +58,7 @@ class MSADeviceCodeStep : public AuthStep {
void authorizeWithBrowser(QString url, QString code, int expiresIn);
private slots:
- void deviceAutorizationFinished();
+ void deviceAuthorizationFinished();
void startPoolTimer();
void authenticateUser();
void authenticationFinished();
diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp
index 74999414c..151ee4e68 100644
--- a/launcher/minecraft/auth/steps/MSAStep.cpp
+++ b/launcher/minecraft/auth/steps/MSAStep.cpp
@@ -85,8 +85,7 @@ class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler {
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
{
m_clientId = APPLICATION->getMSAClientID();
- if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") ||
- QFile::exists(FS::PathCombine(APPLICATION->root(), "portable.txt")) || !isSchemeHandlerRegistered())
+ if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || APPLICATION->isPortable() || !isSchemeHandlerRegistered())
{
auto replyHandler = new QOAuthHttpServerReplyHandler(this);
diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp
index 4fad6f15f..854590dd2 100644
--- a/launcher/minecraft/launch/AutoInstallJava.cpp
+++ b/launcher/minecraft/launch/AutoInstallJava.cpp
@@ -57,9 +57,7 @@
#include "tasks/SequentialTask.h"
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
- : LaunchStep(parent)
- , m_instance(std::dynamic_pointer_cast(m_parent->instance()))
- , m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
+ : LaunchStep(parent), m_instance(m_parent->instance()), m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
void AutoInstallJava::executeTask()
{
@@ -78,7 +76,7 @@ void AutoInstallJava::executeTask()
auto java = std::dynamic_pointer_cast(javas->at(i));
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
if (!java->is_64bit) {
- emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Info);
+ emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Launcher);
}
setJavaPath(java->path);
return;
@@ -136,7 +134,7 @@ void AutoInstallJava::setJavaPath(QString path)
settings->set("OverrideJavaLocation", true);
settings->set("JavaPath", path);
settings->set("AutomaticJava", true);
- emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Info);
+ emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Launcher);
emitSucceeded();
}
@@ -179,7 +177,7 @@ void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName)
return;
}
#if defined(Q_OS_MACOS)
- auto seq = makeShared(this, tr("Install Java"));
+ auto seq = makeShared(tr("Install Java"));
seq->addTask(m_current_task);
seq->addTask(makeShared(final_path));
m_current_task = seq;
diff --git a/launcher/minecraft/launch/CreateGameFolders.cpp b/launcher/minecraft/launch/CreateGameFolders.cpp
index 36f5e6407..07bdbb600 100644
--- a/launcher/minecraft/launch/CreateGameFolders.cpp
+++ b/launcher/minecraft/launch/CreateGameFolders.cpp
@@ -8,16 +8,15 @@ CreateGameFolders::CreateGameFolders(LaunchTask* parent) : LaunchStep(parent) {}
void CreateGameFolders::executeTask()
{
auto instance = m_parent->instance();
- std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance);
- if (!FS::ensureFolderPathExists(minecraftInstance->gameRoot())) {
+ if (!FS::ensureFolderPathExists(instance->gameRoot())) {
emit logLine("Couldn't create the main game folder", MessageLevel::Error);
emitFailed(tr("Couldn't create the main game folder"));
return;
}
// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created.
- if (!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) {
+ if (!FS::ensureFolderPathExists(FS::PathCombine(instance->gameRoot(), "server-resource-packs"))) {
emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error);
}
emitSucceeded();
diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp
index 405008f40..afe091758 100644
--- a/launcher/minecraft/launch/ExtractNatives.cpp
+++ b/launcher/minecraft/launch/ExtractNatives.cpp
@@ -70,17 +70,16 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
void ExtractNatives::executeTask()
{
auto instance = m_parent->instance();
- std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance);
- auto toExtract = minecraftInstance->getNativeJars();
+ auto toExtract = instance->getNativeJars();
if (toExtract.isEmpty()) {
emitSucceeded();
return;
}
- auto settings = minecraftInstance->settings();
+ auto settings = instance->settings();
- auto outputPath = minecraftInstance->getNativePath();
+ auto outputPath = instance->getNativePath();
FS::ensureFolderPathExists(outputPath);
- auto javaVersion = minecraftInstance->getJavaVersion();
+ auto javaVersion = instance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for (const auto& source : toExtract) {
if (!unzipNatives(source, outputPath, jniHackEnabled)) {
diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp
index 2b932ae47..7a086cf84 100644
--- a/launcher/minecraft/launch/LauncherPartLaunch.cpp
+++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp
@@ -48,18 +48,20 @@
#include "gamemode_client.h"
#endif
-LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent)
+LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent)
+ : LaunchStep(parent)
+ , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale())
{
- auto instance = parent->instance();
- if (instance->settings()->get("CloseAfterLaunch").toBool()) {
+ if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) {
std::shared_ptr connection{ new QMetaObject::Connection };
- *connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, [[maybe_unused]] MessageLevel::Enum level) {
- qDebug() << lines;
- if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
- APPLICATION->closeAllWindows();
- disconnect(*connection);
- }
- });
+ *connection = connect(
+ &m_process, &LoggedProcess::log, this, [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
+ qDebug() << lines;
+ if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
+ APPLICATION->closeAllWindows();
+ disconnect(*connection);
+ }
+ });
}
connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines);
@@ -77,10 +79,9 @@ void LauncherPartLaunch::executeTask()
}
auto instance = m_parent->instance();
- std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance);
QString legacyJarPath;
- if (minecraftInstance->getLauncher() == "legacy" || minecraftInstance->shouldApplyOnlineFixes()) {
+ if (instance->getLauncher() == "legacy" || instance->shouldApplyOnlineFixes()) {
legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar");
if (legacyJarPath.isEmpty()) {
const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation.");
@@ -90,8 +91,8 @@ void LauncherPartLaunch::executeTask()
}
}
- m_launchScript = minecraftInstance->createLaunchScript(m_session, m_targetToJoin);
- QStringList args = minecraftInstance->javaArguments();
+ m_launchScript = instance->createLaunchScript(m_session, m_targetToJoin);
+ QStringList args = instance->javaArguments();
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);
@@ -102,13 +103,13 @@ void LauncherPartLaunch::executeTask()
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
- auto classPath = minecraftInstance->getClassPath();
+ auto classPath = instance->getClassPath();
classPath.prepend(jarPath);
if (!legacyJarPath.isEmpty())
classPath.prepend(legacyJarPath);
- auto natPath = minecraftInstance->getNativePath();
+ auto natPath = instance->getNativePath();
#ifdef Q_OS_WIN
natPath = FS::getPathNameInLocal8bit(natPath);
#endif
@@ -130,6 +131,7 @@ void LauncherPartLaunch::executeTask()
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if (!wrapperCommandStr.isEmpty()) {
+ wrapperCommandStr = m_parent->substituteVariables(wrapperCommandStr);
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
diff --git a/launcher/minecraft/launch/ModMinecraftJar.cpp b/launcher/minecraft/launch/ModMinecraftJar.cpp
index 6e73333b1..e06080ba7 100644
--- a/launcher/minecraft/launch/ModMinecraftJar.cpp
+++ b/launcher/minecraft/launch/ModMinecraftJar.cpp
@@ -42,7 +42,7 @@
void ModMinecraftJar::executeTask()
{
- auto m_inst = std::dynamic_pointer_cast(m_parent->instance());
+ auto m_inst = m_parent->instance();
if (!m_inst->getJarMods().size()) {
emitSucceeded();
@@ -82,7 +82,7 @@ void ModMinecraftJar::finalize()
bool ModMinecraftJar::removeJar()
{
- auto m_inst = std::dynamic_pointer_cast(m_parent->instance());
+ auto m_inst = m_parent->instance();
auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
QFile finalJar(finalJarPath);
if (finalJar.exists()) {
diff --git a/launcher/minecraft/launch/ReconstructAssets.cpp b/launcher/minecraft/launch/ReconstructAssets.cpp
index 843ccc554..21ae395f0 100644
--- a/launcher/minecraft/launch/ReconstructAssets.cpp
+++ b/launcher/minecraft/launch/ReconstructAssets.cpp
@@ -22,12 +22,11 @@
void ReconstructAssets::executeTask()
{
auto instance = m_parent->instance();
- std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance);
- auto components = minecraftInstance->getPackProfile();
+ auto components = instance->getPackProfile();
auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
- if (!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) {
+ if (!AssetsUtils::reconstructAssets(assets->id, instance->resourcesDir())) {
emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error);
}
diff --git a/launcher/minecraft/launch/ScanModFolders.cpp b/launcher/minecraft/launch/ScanModFolders.cpp
index 7e08a4e36..1a2ddf194 100644
--- a/launcher/minecraft/launch/ScanModFolders.cpp
+++ b/launcher/minecraft/launch/ScanModFolders.cpp
@@ -42,7 +42,7 @@
void ScanModFolders::executeTask()
{
- auto m_inst = std::dynamic_pointer_cast(m_parent->instance());
+ auto m_inst = m_parent->instance();
auto loaders = m_inst->loaderModList();
connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp
index 1e7448089..bc950d673 100644
--- a/launcher/minecraft/launch/VerifyJavaInstall.cpp
+++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp
@@ -46,7 +46,7 @@
void VerifyJavaInstall::executeTask()
{
- auto instance = std::dynamic_pointer_cast(m_parent->instance());
+ auto instance = m_parent->instance();
auto packProfile = instance->getPackProfile();
auto settings = instance->settings();
auto storedVersion = settings->get("JavaVersion").toString();
diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h
index fb3a10133..0b8cb124d 100644
--- a/launcher/minecraft/mod/MetadataHandler.h
+++ b/launcher/minecraft/mod/MetadataHandler.h
@@ -26,33 +26,48 @@
// launcher/minecraft/mod/Mod.h
class Mod;
-/* Abstraction file for easily changing the way metadata is stored / handled
- * Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]]
- * */
-class Metadata {
- public:
- using ModStruct = Packwiz::V1::Mod;
- using ModSide = Packwiz::V1::Side;
+namespace Metadata {
+using ModStruct = Packwiz::V1::Mod;
+using ModSide = Packwiz::V1::Side;
- static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
- {
- return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
- }
+inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
+{
+ return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
+}
- static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
- {
- return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug);
- }
+inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
+{
+ return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug));
+}
- static void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); }
+inline void update(const QDir& index_dir, ModStruct& mod)
+{
+ Packwiz::V1::updateModIndex(index_dir, mod);
+}
- static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); }
+inline void remove(const QDir& index_dir, QString mod_slug)
+{
+ Packwiz::V1::deleteModIndex(index_dir, mod_slug);
+}
- static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); }
+inline void remove(const QDir& index_dir, QVariant& mod_id)
+{
+ Packwiz::V1::deleteModIndex(index_dir, mod_id);
+}
- static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); }
+inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct
+{
+ return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug));
+}
- static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); }
+inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct
+{
+ return Packwiz::V1::getIndexForMod(index_dir, mod_id);
+}
- static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); }
-};
+inline auto modSideToString(ModSide side) -> QString
+{
+ return Packwiz::V1::sideToString(side);
+}
+
+}; // namespace Metadata
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index 5442df7fe..50fb45d77 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -36,17 +36,17 @@
*/
#include "Mod.h"
+#include
-#include
#include
#include
#include
#include "MTPixmapCache.h"
#include "MetadataHandler.h"
+#include "Resource.h"
#include "Version.h"
#include "minecraft/mod/ModDetails.h"
-#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "modplatform/ModIndex.h"
@@ -55,24 +55,6 @@ Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
m_enabled = (file.suffix() != "disabled");
}
-Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename))
-{
- m_name = metadata.name;
- m_local_details.metadata = std::make_shared(std::move(metadata));
-}
-
-void Mod::setStatus(ModStatus status)
-{
- m_local_details.status = status;
-}
-void Mod::setMetadata(std::shared_ptr&& metadata)
-{
- if (status() == ModStatus::NoMetadata)
- setStatus(ModStatus::Installed);
-
- m_local_details.metadata = metadata;
-}
-
void Mod::setDetails(const ModDetails& details)
{
m_local_details = details;
@@ -100,33 +82,28 @@ int Mod::compare(const Resource& other, SortType type) const
return -1;
break;
}
- case SortType::PROVIDER: {
- return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
- }
case SortType::SIDE: {
- if (side() > cast_other->side())
- return 1;
- else if (side() < cast_other->side())
- return -1;
- break;
- }
- case SortType::LOADERS: {
- if (loaders() > cast_other->loaders())
- return 1;
- else if (loaders() < cast_other->loaders())
- return -1;
+ auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
+ if (compare_result != 0)
+ return compare_result;
break;
}
case SortType::MC_VERSIONS: {
- auto thisVersion = mcVersions().join(",");
- auto otherVersion = cast_other->mcVersions().join(",");
- return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
+ auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
+ if (compare_result != 0)
+ return compare_result;
+ break;
+ }
+ case SortType::LOADERS: {
+ auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive);
+ if (compare_result != 0)
+ return compare_result;
+ break;
}
case SortType::RELEASE_TYPE: {
- if (releaseType() > cast_other->releaseType())
- return 1;
- else if (releaseType() < cast_other->releaseType())
- return -1;
+ auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
+ if (compare_result != 0)
+ return compare_result;
break;
}
}
@@ -147,28 +124,6 @@ bool Mod::applyFilter(QRegularExpression filter) const
return Resource::applyFilter(filter);
}
-auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
-{
- if (!preserve_metadata) {
- qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
-
- destroyMetadata(index_dir);
- }
-
- return Resource::destroy(attempt_trash);
-}
-
-void Mod::destroyMetadata(QDir& index_dir)
-{
- if (metadata()) {
- Metadata::remove(index_dir, metadata()->slug);
- } else {
- auto n = name();
- Metadata::remove(index_dir, n);
- }
- m_local_details.metadata = nullptr;
-}
-
auto Mod::details() const -> const ModDetails&
{
return m_local_details;
@@ -180,10 +135,7 @@ auto Mod::name() const -> QString
if (!d_name.isEmpty())
return d_name;
- if (metadata())
- return metadata()->name;
-
- return m_name;
+ return Resource::name();
}
auto Mod::version() const -> QString
@@ -191,16 +143,52 @@ auto Mod::version() const -> QString
return details().version;
}
-auto Mod::homeurl() const -> QString
+auto Mod::homepage() const -> QString
{
- return details().homeurl;
+ QString metaUrl = Resource::homepage();
+
+ if (metaUrl.isEmpty())
+ return details().homeurl;
+ else
+ return metaUrl;
}
-auto Mod::metaurl() const -> QString
+auto Mod::loaders() const -> QString
{
- if (metadata() == nullptr)
- return homeurl();
- return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
+ if (metadata()) {
+ QStringList loaders;
+ auto modLoaders = metadata()->loaders;
+ for (auto loader : ModPlatform::modLoaderTypesToList(modLoaders)) {
+ loaders << getModLoaderAsString(loader);
+ }
+ return loaders.join(", ");
+ }
+
+ return {};
+}
+
+auto Mod::side() const -> QString
+{
+ if (metadata())
+ return Metadata::modSideToString(metadata()->side);
+
+ return Metadata::modSideToString(Metadata::ModSide::UniversalSide);
+}
+
+auto Mod::mcVersions() const -> QString
+{
+ if (metadata())
+ return metadata()->mcVersions.join(", ");
+
+ return {};
+}
+
+auto Mod::releaseType() const -> QString
+{
+ if (metadata())
+ return metadata()->releaseType.toString();
+
+ return ModPlatform::IndexedVersionType().toString();
}
auto Mod::description() const -> QString
@@ -213,73 +201,17 @@ auto Mod::authors() const -> QStringList
return details().authors;
}
-auto Mod::status() const -> ModStatus
-{
- return details().status;
-}
-
-auto Mod::metadata() -> std::shared_ptr
-{
- return m_local_details.metadata;
-}
-
-auto Mod::metadata() const -> const std::shared_ptr
-{
- return m_local_details.metadata;
-}
-
void Mod::finishResolvingWithDetails(ModDetails&& details)
{
m_is_resolving = false;
m_is_resolved = true;
- std::shared_ptr metadata = details.metadata;
- if (details.status == ModStatus::Unknown)
- details.status = m_local_details.status;
-
m_local_details = std::move(details);
- if (metadata)
- setMetadata(std::move(metadata));
if (!iconPath().isEmpty()) {
- m_pack_image_cache_key.was_read_attempt = false;
+ m_packImageCacheKey.wasReadAttempt = false;
}
}
-auto Mod::provider() const -> std::optional
-{
- if (metadata())
- return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
- return {};
-}
-
-auto Mod::side() const -> Metadata::ModSide
-{
- if (metadata())
- return metadata()->side;
- return Metadata::ModSide::UniversalSide;
-}
-
-auto Mod::releaseType() const -> ModPlatform::IndexedVersionType
-{
- if (metadata())
- return metadata()->releaseType;
- return ModPlatform::IndexedVersionType::VersionType::Unknown;
-}
-
-auto Mod::loaders() const -> ModPlatform::ModLoaderTypes
-{
- if (metadata())
- return metadata()->loaders;
- return {};
-}
-
-auto Mod::mcVersions() const -> QStringList
-{
- if (metadata())
- return metadata()->mcVersions;
- return {};
-}
-
auto Mod::licenses() const -> const QList&
{
return details().licenses;
@@ -290,45 +222,53 @@ auto Mod::issueTracker() const -> QString
return details().issue_tracker;
}
-void Mod::setIcon(QImage new_image) const
+QPixmap Mod::setIcon(QImage new_image) const
{
QMutexLocker locker(&m_data_lock);
Q_ASSERT(!new_image.isNull());
- if (m_pack_image_cache_key.key.isValid())
- PixmapCache::remove(m_pack_image_cache_key.key);
+ if (m_packImageCacheKey.key.isValid())
+ PixmapCache::remove(m_packImageCacheKey.key);
// scale the image to avoid flooding the pixmapcache
auto pixmap =
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
- m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
- m_pack_image_cache_key.was_ever_used = true;
- m_pack_image_cache_key.was_read_attempt = true;
+ m_packImageCacheKey.key = PixmapCache::insert(pixmap);
+ m_packImageCacheKey.wasEverUsed = true;
+ m_packImageCacheKey.wasReadAttempt = true;
+ return pixmap;
}
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
{
- QPixmap cached_image;
- if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
+ auto pixmap_transform = [&size, &mode](QPixmap pixmap) {
if (size.isNull())
- return cached_image;
- return cached_image.scaled(size, mode, Qt::SmoothTransformation);
+ return pixmap;
+ return pixmap.scaled(size, mode, Qt::SmoothTransformation);
+ };
+
+ QPixmap cached_image;
+ if (PixmapCache::find(m_packImageCacheKey.key, &cached_image)) {
+ return pixmap_transform(cached_image);
}
// No valid image we can get
- if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty())
+ if ((!m_packImageCacheKey.wasEverUsed && m_packImageCacheKey.wasReadAttempt) || iconPath().isEmpty())
return {};
- if (m_pack_image_cache_key.was_ever_used) {
+ if (m_packImageCacheKey.wasEverUsed) {
qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading...";
PixmapCache::markCacheMissByEviciton();
}
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
- m_pack_image_cache_key.was_read_attempt = true;
- ModUtils::loadIconFile(*this);
- return icon(size);
+ m_packImageCacheKey.wasReadAttempt = true;
+ if (ModUtils::loadIconFile(*this, &cached_image)) {
+ return pixmap_transform(cached_image);
+ }
+ // Image failed to load
+ return {};
}
bool Mod::valid() const
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 9bd76c2fd..8a352c66c 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -48,7 +48,6 @@
#include "ModDetails.h"
#include "Resource.h"
-#include "modplatform/ModIndex.h"
class Mod : public Resource {
Q_OBJECT
@@ -58,43 +57,33 @@ class Mod : public Resource {
Mod() = default;
Mod(const QFileInfo& file);
- Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
auto details() const -> const ModDetails&;
auto name() const -> QString override;
auto version() const -> QString;
- auto homeurl() const -> QString;
+ auto homepage() const -> QString override;
auto description() const -> QString;
auto authors() const -> QStringList;
- auto status() const -> ModStatus;
- auto provider() const -> std::optional;
auto licenses() const -> const QList&;
auto issueTracker() const -> QString;
- auto metaurl() const -> QString;
- auto side() const -> Metadata::ModSide;
- auto loaders() const -> ModPlatform::ModLoaderTypes;
- auto mcVersions() const -> QStringList;
- auto releaseType() const -> ModPlatform::IndexedVersionType;
+ auto side() const -> QString;
+ auto loaders() const -> QString;
+ auto mcVersions() const -> QString;
+ auto releaseType() const -> QString;
/** Get the intneral path to the mod's icon file*/
QString iconPath() const { return m_local_details.icon_file; }
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */
- void setIcon(QImage new_image) const;
+ QPixmap setIcon(QImage new_image) const;
- auto metadata() -> std::shared_ptr;
- auto metadata() const -> const std::shared_ptr;
-
- void setStatus(ModStatus status);
- void setMetadata(std::shared_ptr&& metadata);
- void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); }
void setDetails(const ModDetails& details);
bool valid() const override;
- [[nodiscard]] int compare(Resource const& other, SortType type) const override;
+ [[nodiscard]] int compare(const Resource & other, SortType type) const override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
// Delete all the files of this mod
@@ -111,7 +100,7 @@ class Mod : public Resource {
struct {
QPixmapCache::Key key;
- bool was_ever_used = false;
- bool was_read_attempt = false;
- } mutable m_pack_image_cache_key;
+ bool wasEverUsed = false;
+ bool wasReadAttempt = false;
+ } mutable m_packImageCacheKey;
};
diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h
index a00d5a24b..9195c0368 100644
--- a/launcher/minecraft/mod/ModDetails.h
+++ b/launcher/minecraft/mod/ModDetails.h
@@ -43,13 +43,6 @@
#include "minecraft/mod/MetadataHandler.h"
-enum class ModStatus {
- Installed, // Both JAR and Metadata are present
- NotInstalled, // Only the Metadata is present
- NoMetadata, // Only the JAR is present
- Unknown, // Default status
-};
-
struct ModLicense {
QString name = {};
QString id = {};
@@ -149,12 +142,6 @@ struct ModDetails {
/* Path of mod logo */
QString icon_file = {};
- /* Installation status of the mod */
- ModStatus status = ModStatus::Unknown;
-
- /* Metadata information, if any */
- std::shared_ptr metadata = nullptr;
-
ModDetails() = default;
/** Metadata should be handled manually to properly set the mod status. */
@@ -169,40 +156,9 @@ struct ModDetails {
, issue_tracker(other.issue_tracker)
, licenses(other.licenses)
, icon_file(other.icon_file)
- , status(other.status)
{}
- ModDetails& operator=(const ModDetails& other)
- {
- this->mod_id = other.mod_id;
- this->name = other.name;
- this->version = other.version;
- this->mcversion = other.mcversion;
- this->homeurl = other.homeurl;
- this->description = other.description;
- this->authors = other.authors;
- this->issue_tracker = other.issue_tracker;
- this->licenses = other.licenses;
- this->icon_file = other.icon_file;
- this->status = other.status;
+ ModDetails& operator=(const ModDetails& other) = default;
- return *this;
- }
-
- ModDetails& operator=(const ModDetails&& other)
- {
- this->mod_id = other.mod_id;
- this->name = other.name;
- this->version = other.version;
- this->mcversion = other.mcversion;
- this->homeurl = other.homeurl;
- this->description = other.description;
- this->authors = other.authors;
- this->issue_tracker = other.issue_tracker;
- this->licenses = other.licenses;
- this->icon_file = other.icon_file;
- this->status = other.status;
-
- return *this;
- }
+ ModDetails& operator=(ModDetails&& other) = default;
};
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 5e4fe7f11..027f3d4ca 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -51,18 +51,10 @@
#include "Application.h"
-#include "Json.h"
-#include "minecraft/mod/MetadataHandler.h"
-#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
-#include "minecraft/mod/tasks/LocalModUpdateTask.h"
-#include "minecraft/mod/tasks/ModFolderLoadTask.h"
-#include "modplatform/ModIndex.h"
-#include "modplatform/flame/FlameAPI.h"
-#include "modplatform/flame/FlameModIndex.h"
-ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
- : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
+ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
+ : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Minecraft Versions", "Release Type" });
@@ -92,7 +84,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case NameColumn:
return m_resources[row]->name();
case VersionColumn: {
- switch (m_resources[row]->type()) {
+ switch (at(row).type()) {
case ResourceType::FOLDER:
return tr("Folder");
case ResourceType::SINGLEFILE:
@@ -100,64 +92,50 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
default:
break;
}
- return at(row)->version();
+ return at(row).version();
}
case DateColumn:
- return m_resources[row]->dateTimeChanged();
+ return at(row).dateTimeChanged();
case ProviderColumn: {
- auto provider = at(row)->provider();
- if (!provider.has_value()) {
- //: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
- return tr("Unknown");
- }
-
- return provider.value();
+ return at(row).provider();
}
case SideColumn: {
- return Metadata::modSideToString(at(row)->side());
+ return at(row).side();
}
case LoadersColumn: {
- QStringList loaders;
- auto modLoaders = at(row)->loaders();
- for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader,
- ModPlatform::Fabric, ModPlatform::Quilt }) {
- if (modLoaders & loader) {
- loaders << getModLoaderAsString(loader);
- }
- }
- return loaders.join(", ");
+ return at(row).loaders();
}
case McVersionsColumn: {
- return at(row)->mcVersions().join(", ");
+ return at(row).mcVersions();
}
case ReleaseTypeColumn: {
- return at(row)->releaseType().toString();
+ return at(row).releaseType();
}
case SizeColumn:
- return m_resources[row]->sizeStr();
+ return at(row).sizeStr();
default:
return QVariant();
}
case Qt::ToolTipRole:
if (column == NameColumn) {
- if (at(row)->isSymLinkUnder(instDirPath())) {
+ if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
- .arg(at(row)->fileinfo().canonicalFilePath());
+ .arg(at(row).fileinfo().canonicalFilePath());
}
- if (at(row)->isMoreThanOneHardLink()) {
+ if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
- if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
+ if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
- return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
+ return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@@ -169,7 +147,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
- return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
+ return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
@@ -210,7 +188,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
case ProviderColumn:
- return tr("Where the mod was downloaded from.");
+ return tr("The source provider of the mod.");
case SideColumn:
return tr("On what environment the mod is running.");
case LoadersColumn:
@@ -235,133 +213,16 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const
return parent.isValid() ? 0 : NUM_COLUMNS;
}
-Task* ModFolderModel::createUpdateTask()
-{
- auto index_dir = indexDir();
- auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
- m_first_folder_load = false;
- return task;
-}
-
Task* ModFolderModel::createParseTask(Resource& resource)
{
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
}
-bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
-{
- for (auto mod : allMods()) {
- if (mod->getOriginalFileName() == filename) {
- auto index_dir = indexDir();
- mod->destroy(index_dir, preserve_metadata, false);
-
- update();
-
- return true;
- }
- }
-
- return false;
-}
-
-bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
-{
- if (indexes.isEmpty())
- return true;
-
- for (auto i : indexes) {
- if (i.column() != 0) {
- continue;
- }
- auto m = at(i.row());
- auto index_dir = indexDir();
- m->destroy(index_dir);
- }
-
- update();
-
- return true;
-}
-
-bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
-{
- if (indexes.isEmpty())
- return true;
-
- for (auto i : indexes) {
- if (i.column() != 0) {
- continue;
- }
- auto m = at(i.row());
- auto index_dir = indexDir();
- m->destroyMetadata(index_dir);
- }
-
- update();
-
- return true;
-}
-
bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
}
-bool ModFolderModel::startWatching()
-{
- // Remove orphaned metadata next time
- m_first_folder_load = true;
- return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
-}
-
-bool ModFolderModel::stopWatching()
-{
- return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
-}
-
-auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList
-{
- QList selected_resources;
- for (auto i : indexes) {
- if (i.column() != 0)
- continue;
-
- selected_resources.push_back(at(i.row()));
- }
- return selected_resources;
-}
-
-auto ModFolderModel::allMods() -> QList
-{
- QList mods;
-
- for (auto& res : qAsConst(m_resources)) {
- mods.append(static_cast(res.get()));
- }
-
- return mods;
-}
-
-void ModFolderModel::onUpdateSucceeded()
-{
- auto update_results = static_cast(m_current_update_task.get())->result();
-
- auto& new_mods = update_results->mods;
-
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- auto current_list = m_resources_index.keys();
- QSet current_set(current_list.begin(), current_list.end());
-
- auto new_list = new_mods.keys();
- QSet new_set(new_list.begin(), new_list.end());
-#else
- QSet current_set(m_resources_index.keys().toSet());
- QSet new_set(new_mods.keys().toSet());
-#endif
-
- applyUpdates(current_set, new_set, new_mods);
-}
-
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
{
auto iter = m_active_parse_tasks.constFind(ticket);
@@ -379,51 +240,7 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
auto result = cast_task->result();
if (result && resource)
- resource->finishResolvingWithDetails(std::move(result->details));
+ static_cast(resource.get())->finishResolvingWithDetails(std::move(result->details));
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
-
-static const FlameAPI flameAPI;
-bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
-{
- if (vers.addonId.isValid()) {
- ModPlatform::IndexedPack pack{
- vers.addonId,
- ModPlatform::ResourceProvider::FLAME,
- };
-
- QEventLoop loop;
-
- auto response = std::make_shared();
- auto job = flameAPI.getProject(vers.addonId.toString(), response);
-
- QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
- QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
- QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
- QJsonParseError parse_error{};
- QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
- if (parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
- << " reason: " << parse_error.errorString();
- qDebug() << *response;
- return;
- }
- try {
- auto obj = Json::requireObject(Json::requireObject(doc), "data");
- FlameMod::loadIndexedPack(pack, obj);
- } catch (const JSONValidationError& e) {
- qDebug() << doc;
- qWarning() << "Error while reading mod info: " << e.cause();
- }
- LocalModUpdateTask update_metadata(indexDir(), pack, vers);
- QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
- update_metadata.start();
- });
-
- job->start();
-
- loop.exec();
- }
- return ResourceFolderModel::installResource(file_path);
-}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index db5edf66e..42868dc91 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -47,11 +47,6 @@
#include "Mod.h"
#include "ResourceFolderModel.h"
-#include "minecraft/mod/tasks/LocalModParseTask.h"
-#include "minecraft/mod/tasks/ModFolderLoadTask.h"
-#include "modplatform/ModIndex.h"
-
-class LegacyInstance;
class BaseInstance;
class QFileSystemWatcher;
@@ -76,8 +71,7 @@ class ModFolderModel : public ResourceFolderModel {
ReleaseTypeColumn,
NUM_COLUMNS
};
- enum ModStatusAction { Disable, Enable, Toggle };
- ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
+ ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
virtual QString id() const override { return "mods"; }
@@ -86,34 +80,13 @@ class ModFolderModel : public ResourceFolderModel {
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex& parent) const override;
- [[nodiscard]] Task* createUpdateTask() override;
+ [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); }
[[nodiscard]] Task* createParseTask(Resource&) override;
- bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
- bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
- bool uninstallMod(const QString& filename, bool preserve_metadata = false);
-
- /// Deletes all the selected mods
- bool deleteMods(const QModelIndexList& indexes);
- bool deleteModsMetadata(const QModelIndexList& indexes);
-
bool isValid();
- bool startWatching() override;
- bool stopWatching() override;
-
- QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
-
- auto selectedMods(QModelIndexList& indexes) -> QList;
- auto allMods() -> QList;
-
RESOURCE_HELPERS(Mod)
private slots:
- void onUpdateSucceeded() override;
void onParseSucceeded(int ticket, QString resource_id) override;
-
- protected:
- bool m_is_indexed;
- bool m_first_folder_load = true;
};
diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp
index c52d76f1f..d1a7b8f9c 100644
--- a/launcher/minecraft/mod/Resource.cpp
+++ b/launcher/minecraft/mod/Resource.cpp
@@ -21,7 +21,7 @@ void Resource::setFile(QFileInfo file_info)
parseFile();
}
-std::tuple calculateFileSize(const QFileInfo& file)
+static std::tuple calculateFileSize(const QFileInfo& file)
{
if (file.isDir()) {
auto dir = QDir(file.absoluteFilePath());
@@ -72,6 +72,14 @@ void Resource::parseFile()
m_changed_date_time = m_file_info.lastModified();
}
+auto Resource::name() const -> QString
+{
+ if (metadata())
+ return metadata()->name;
+
+ return m_name;
+}
+
static void removeThePrefix(QString& string)
{
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
@@ -79,6 +87,30 @@ static void removeThePrefix(QString& string)
string = string.trimmed();
}
+auto Resource::provider() const -> QString
+{
+ if (metadata())
+ return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
+
+ return tr("Unknown");
+}
+
+auto Resource::homepage() const -> QString
+{
+ if (metadata())
+ return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
+
+ return {};
+}
+
+void Resource::setMetadata(std::shared_ptr&& metadata)
+{
+ if (status() == ResourceStatus::NO_METADATA)
+ setStatus(ResourceStatus::INSTALLED);
+
+ m_metadata = metadata;
+}
+
int Resource::compare(const Resource& other, SortType type) const
{
switch (type) {
@@ -93,6 +125,7 @@ int Resource::compare(const Resource& other, SortType type) const
QString this_name{ name() };
QString other_name{ other.name() };
+ // TODO do we need this? it could result in 0 being returned
removeThePrefix(this_name);
removeThePrefix(other_name);
@@ -118,6 +151,12 @@ int Resource::compare(const Resource& other, SortType type) const
return -1;
break;
}
+ case SortType::PROVIDER: {
+ auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
+ if (compare_result != 0)
+ return compare_result;
+ break;
+ }
}
return 0;
@@ -174,10 +213,27 @@ bool Resource::enable(EnableAction action)
return true;
}
-bool Resource::destroy(bool attemptTrash)
+auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{
m_type = ResourceType::UNKNOWN;
- return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
+
+ if (!preserve_metadata) {
+ qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
+ destroyMetadata(index_dir);
+ }
+
+ return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
+}
+
+auto Resource::destroyMetadata(const QDir& index_dir) -> void
+{
+ if (metadata()) {
+ Metadata::remove(index_dir, metadata()->slug);
+ } else {
+ auto n = name();
+ Metadata::remove(index_dir, n);
+ }
+ m_metadata = nullptr;
}
bool Resource::isSymLinkUnder(const QString& instPath) const
diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h
index c99dab8db..42463fe8f 100644
--- a/launcher/minecraft/mod/Resource.h
+++ b/launcher/minecraft/mod/Resource.h
@@ -40,6 +40,7 @@
#include
#include
+#include "MetadataHandler.h"
#include "QObjectPtr.h"
enum class ResourceType {
@@ -50,7 +51,14 @@ enum class ResourceType {
LITEMOD, //!< The resource is a litemod
};
-enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE };
+enum class ResourceStatus {
+ INSTALLED, // Both JAR and Metadata are present
+ NOT_INSTALLED, // Only the Metadata is present
+ NO_METADATA, // Only the JAR is present
+ UNKNOWN, // Default status
+};
+
+enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE };
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
@@ -84,9 +92,19 @@ class Resource : public QObject {
[[nodiscard]] QString sizeStr() const { return m_size_str; }
[[nodiscard]] qint64 sizeInfo() const { return m_size_info; }
- [[nodiscard]] virtual auto name() const -> QString { return m_name; }
+ [[nodiscard]] virtual auto name() const -> QString;
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
+ [[nodiscard]] auto status() const -> ResourceStatus { return m_status; };
+ [[nodiscard]] auto metadata() -> std::shared_ptr { return m_metadata; }
+ [[nodiscard]] auto metadata() const -> std::shared_ptr { return m_metadata; }
+ [[nodiscard]] auto provider() const -> QString;
+ [[nodiscard]] virtual auto homepage() const -> QString;
+
+ void setStatus(ResourceStatus status) { m_status = status; }
+ void setMetadata(std::shared_ptr&& metadata);
+ void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); }
+
/** Compares two Resources, for sorting purposes, considering a ascending order, returning:
* > 0: 'this' comes after 'other'
* = 0: 'this' is equal to 'other'
@@ -117,7 +135,9 @@ class Resource : public QObject {
}
// Delete all files of this resource.
- bool destroy(bool attemptTrash = true);
+ auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
+ // Delete the metadata only.
+ auto destroyMetadata(const QDir& index_dir) -> void;
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
@@ -146,6 +166,11 @@ class Resource : public QObject {
/* The type of file we're dealing with. */
ResourceType m_type = ResourceType::UNKNOWN;
+ /* Installation status of the resource. */
+ ResourceStatus m_status = ResourceStatus::UNKNOWN;
+
+ std::shared_ptr m_metadata = nullptr;
+
/* Whether the resource is enabled (e.g. shows up in the game) or not. */
bool m_enabled = true;
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index 941e7ce58..adeb2e422 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -11,20 +11,23 @@
#include
#include
#include
+#include
#include "Application.h"
#include "FileSystem.h"
-#include "QVariantUtils.h"
-#include "StringUtils.h"
-#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
+#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
+#include "Json.h"
+#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
#include "settings/Setting.h"
#include "tasks/Task.h"
#include "ui/dialogs/CustomMessageBox.h"
-ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
- : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
+ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
+ : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed)
{
if (create_dir) {
FS::ensureFolderPathExists(m_dir.absolutePath());
@@ -35,10 +38,9 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); });
-#ifndef LAUNCHER_TEST
- // in tests the application macro doesn't work
- m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
-#endif
+ if (APPLICATION_DYN) { // in tests the application macro doesn't work
+ m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
+ }
}
ResourceFolderModel::~ResourceFolderModel()
@@ -49,6 +51,9 @@ ResourceFolderModel::~ResourceFolderModel()
bool ResourceFolderModel::startWatching(const QStringList& paths)
{
+ // Remove orphaned metadata next time
+ m_first_folder_load = true;
+
if (m_is_watching)
return false;
@@ -159,11 +164,55 @@ bool ResourceFolderModel::installResource(QString original_path)
return false;
}
-bool ResourceFolderModel::uninstallResource(QString file_name)
+bool ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers)
+{
+ if (vers.addonId.isValid()) {
+ ModPlatform::IndexedPack pack{
+ vers.addonId,
+ ModPlatform::ResourceProvider::FLAME,
+ };
+
+ QEventLoop loop;
+
+ auto response = std::make_shared();
+ auto job = FlameAPI().getProject(vers.addonId.toString(), response);
+
+ QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
+ QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
+ QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qDebug() << *response;
+ return;
+ }
+ try {
+ auto obj = Json::requireObject(Json::requireObject(doc), "data");
+ FlameMod::loadIndexedPack(pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading mod info: " << e.cause();
+ }
+ LocalResourceUpdateTask update_metadata(indexDir(), pack, vers);
+ QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
+ update_metadata.start();
+ });
+
+ job->start();
+
+ loop.exec();
+ }
+
+ return installResource(std::move(path));
+}
+
+bool ResourceFolderModel::uninstallResource(QString file_name, bool preserve_metadata)
{
for (auto& resource : m_resources) {
if (resource->fileinfo().fileName() == file_name) {
- auto res = resource->destroy(false);
+ auto res = resource->destroy(indexDir(), preserve_metadata, false);
update();
@@ -179,13 +228,11 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
for (auto i : indexes) {
- if (i.column() != 0) {
+ if (i.column() != 0)
continue;
- }
auto& resource = m_resources.at(i.row());
-
- resource->destroy();
+ resource->destroy(indexDir());
}
update();
@@ -193,6 +240,22 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
}
+void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
+{
+ if (indexes.isEmpty())
+ return;
+
+ for (auto i : indexes) {
+ if (i.column() != 0)
+ continue;
+
+ auto& resource = m_resources.at(i.row());
+ resource->destroyMetadata(indexDir());
+ }
+
+ update();
+}
+
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
{
if (indexes.isEmpty())
@@ -246,7 +309,7 @@ bool ResourceFolderModel::update()
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
connect(
m_current_update_task.get(), &Task::finished, this,
- [=] {
+ [this] {
m_current_update_task.reset();
if (m_scheduled_update) {
m_scheduled_update = false;
@@ -262,7 +325,7 @@ bool ResourceFolderModel::update()
return true;
}
-void ResourceFolderModel::resolveResource(Resource* res)
+void ResourceFolderModel::resolveResource(Resource::Ptr res)
{
if (!res->shouldResolve()) {
return;
@@ -278,11 +341,14 @@ void ResourceFolderModel::resolveResource(Resource* res)
m_active_parse_tasks.insert(ticket, task);
connect(
- task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
- connect(task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
+ task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); },
+ Qt::ConnectionType::QueuedConnection);
+ connect(
+ task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); },
+ Qt::ConnectionType::QueuedConnection);
connect(
task.get(), &Task::finished, this,
- [=] {
+ [this, ticket] {
m_active_parse_tasks.remove(ticket);
emit parseFinished();
},
@@ -297,7 +363,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
void ResourceFolderModel::onUpdateSucceeded()
{
- auto update_results = static_cast(m_current_update_task.get())->result();
+ auto update_results = static_cast(m_current_update_task.get())->result();
auto& new_resources = update_results->resources;
@@ -318,7 +384,7 @@ void ResourceFolderModel::onUpdateSucceeded()
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
{
auto iter = m_active_parse_tasks.constFind(ticket);
- if (iter == m_active_parse_tasks.constEnd())
+ if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
return;
int row = m_resources_index[resource_id];
@@ -327,7 +393,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
Task* ResourceFolderModel::createUpdateTask()
{
- return new BasicFolderLoadTask(m_dir);
+ auto index_dir = indexDir();
+ auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load,
+ [this](const QFileInfo& file) { return createResource(file); });
+ m_first_folder_load = false;
+ return task;
}
bool ResourceFolderModel::hasPendingParseTasks() const
@@ -417,6 +487,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->name();
case DateColumn:
return m_resources[row]->dateTimeChanged();
+ case ProviderColumn:
+ return m_resources[row]->provider();
case SizeColumn:
return m_resources[row]->sizeStr();
default:
@@ -488,22 +560,23 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
case ActiveColumn:
case NameColumn:
case DateColumn:
+ case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
return {};
}
case Qt::ToolTipRole: {
+ //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
switch (section) {
case ActiveColumn:
- //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("Is the resource enabled?");
case NameColumn:
- //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The name of the resource.");
case DateColumn:
- //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The date and time this resource was last changed (or added).");
+ case ProviderColumn:
+ return tr("The source provider of the resource.");
case SizeColumn:
return tr("The size of the resource.");
default:
@@ -630,7 +703,7 @@ QString ResourceFolderModel::instDirPath() const
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
{
auto iter = m_active_parse_tasks.constFind(ticket);
- if (iter == m_active_parse_tasks.constEnd())
+ if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
return;
auto removed_index = m_resources_index[resource_id];
@@ -649,3 +722,126 @@ void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
}
endRemoveRows();
}
+
+void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources)
+{
+ // see if the kept resources changed in some way
+ {
+ QSet kept_set = current_set;
+ kept_set.intersect(new_set);
+
+ for (auto const& kept : kept_set) {
+ auto row_it = m_resources_index.constFind(kept);
+ Q_ASSERT(row_it != m_resources_index.constEnd());
+ auto row = row_it.value();
+
+ auto& new_resource = new_resources[kept];
+ auto const& current_resource = m_resources.at(row);
+
+ if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
+ // no significant change, ignore...
+ continue;
+ }
+
+ // If the resource is resolving, but something about it changed, we don't want to
+ // continue the resolving.
+ if (current_resource->isResolving()) {
+ auto ticket = current_resource->resolutionTicket();
+ if (m_active_parse_tasks.contains(ticket)) {
+ auto task = (*m_active_parse_tasks.find(ticket)).get();
+ task->abort();
+ }
+ }
+
+ m_resources[row].reset(new_resource);
+ resolveResource(m_resources.at(row));
+ emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
+ }
+ }
+
+ // remove resources no longer present
+ {
+ QSet removed_set = current_set;
+ removed_set.subtract(new_set);
+
+ QList removed_rows;
+ for (auto& removed : removed_set)
+ removed_rows.append(m_resources_index[removed]);
+
+ std::sort(removed_rows.begin(), removed_rows.end(), std::greater());
+
+ for (auto& removed_index : removed_rows) {
+ auto removed_it = m_resources.begin() + removed_index;
+
+ Q_ASSERT(removed_it != m_resources.end());
+
+ if ((*removed_it)->isResolving()) {
+ auto ticket = (*removed_it)->resolutionTicket();
+ if (m_active_parse_tasks.contains(ticket)) {
+ auto task = (*m_active_parse_tasks.find(ticket)).get();
+ task->abort();
+ }
+ }
+
+ beginRemoveRows(QModelIndex(), removed_index, removed_index);
+ m_resources.erase(removed_it);
+ endRemoveRows();
+ }
+ }
+
+ // add new resources to the end
+ {
+ QSet added_set = new_set;
+ added_set.subtract(current_set);
+
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (added_set.size() > 0) {
+ beginInsertRows(QModelIndex(), static_cast(m_resources.size()),
+ static_cast(m_resources.size() + added_set.size() - 1));
+
+ for (auto& added : added_set) {
+ auto res = new_resources[added];
+ m_resources.append(res);
+ resolveResource(m_resources.last());
+ }
+
+ endInsertRows();
+ }
+ }
+
+ // update index
+ {
+ m_resources_index.clear();
+ int idx = 0;
+ for (auto const& mod : qAsConst(m_resources)) {
+ m_resources_index[mod->internal_id()] = idx;
+ idx++;
+ }
+ }
+}
+Resource::Ptr ResourceFolderModel::find(QString id)
+{
+ auto iter =
+ std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; });
+ if (iter == m_resources.constEnd())
+ return nullptr;
+ return *iter;
+}
+QList ResourceFolderModel::allResources()
+{
+ QList result;
+ result.reserve(m_resources.size());
+ for (const Resource ::Ptr& resource : m_resources)
+ result.append((resource.get()));
+ return result;
+}
+QList ResourceFolderModel::selectedResources(const QModelIndexList& indexes)
+{
+ QList result;
+ for (const QModelIndex& index : indexes) {
+ if (index.column() != 0)
+ continue;
+ result.append(&at(index.row()));
+ }
+ return result;
+}
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index ca919d3e3..ee26a74bc 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -19,6 +19,38 @@
class QSortFilterProxyModel;
+/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
+#define RESOURCE_HELPERS(T) \
+ [[nodiscard]] T& at(int index) \
+ { \
+ return *static_cast(m_resources[index].get()); \
+ } \
+ [[nodiscard]] const T& at(int index) const \
+ { \
+ return *static_cast(m_resources.at(index).get()); \
+ } \
+ QList selected##T##s(const QModelIndexList& indexes) \
+ { \
+ QList result; \
+ for (const QModelIndex& index : indexes) { \
+ if (index.column() != 0) \
+ continue; \
+ \
+ result.append(&at(index.row())); \
+ } \
+ return result; \
+ } \
+ QList all##T##s() \
+ { \
+ QList result; \
+ result.reserve(m_resources.size()); \
+ \
+ for (const Resource::Ptr& resource : m_resources) \
+ result.append(static_cast(resource.get())); \
+ \
+ return result; \
+ }
+
/** A basic model for external resources.
*
* This model manages a list of resources. As such, external users of such resources do not own them,
@@ -29,7 +61,7 @@ class QSortFilterProxyModel;
class ResourceFolderModel : public QAbstractListModel {
Q_OBJECT
public:
- ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
+ ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
~ResourceFolderModel() override;
virtual QString id() const { return "resource"; }
@@ -49,8 +81,10 @@ class ResourceFolderModel : public QAbstractListModel {
bool stopWatching(const QStringList& paths);
/* Helper methods for subclasses, using a predetermined list of paths. */
- virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
- virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
+ virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
+ virtual bool stopWatching() { return stopWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
+
+ QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
/** Given a path in the system, install that resource, moving it to its place in the
* instance file hierarchy.
@@ -59,12 +93,15 @@ class ResourceFolderModel : public QAbstractListModel {
*/
virtual bool installResource(QString path);
+ virtual bool installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers);
+
/** Uninstall (i.e. remove all data about it) a resource, given its file name.
*
* Returns whether the removal was successful.
*/
- virtual bool uninstallResource(QString file_name);
+ virtual bool uninstallResource(QString file_name, bool preserve_metadata = false);
virtual bool deleteResources(const QModelIndexList&);
+ virtual void deleteMetadata(const QModelIndexList&);
/** Applies the given 'action' to the resources in 'indexes'.
*
@@ -76,13 +113,17 @@ class ResourceFolderModel : public QAbstractListModel {
virtual bool update();
/** Creates a new parse task, if needed, for 'res' and start it.*/
- virtual void resolveResource(Resource* res);
+ virtual void resolveResource(Resource::Ptr res);
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
[[nodiscard]] bool empty() const { return size() == 0; }
- [[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
- [[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
- [[nodiscard]] QList const& all() const { return m_resources; }
+
+ [[nodiscard]] Resource& at(int index) { return *m_resources[index].get(); }
+ [[nodiscard]] const Resource& at(int index) const { return *m_resources.at(index).get(); }
+ QList selectedResources(const QModelIndexList& indexes);
+ QList allResources();
+
+ [[nodiscard]] Resource::Ptr find(QString id);
[[nodiscard]] QDir const& dir() const { return m_dir; }
@@ -96,7 +137,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Qt behavior */
/* Basic columns */
- enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
+ enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
+
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); }
@@ -153,7 +195,9 @@ class ResourceFolderModel : public QAbstractListModel {
* This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
* If such work is needed, try using it in the Task create by createParseTask() instead!
*/
- [[nodiscard]] virtual Task* createUpdateTask();
+ [[nodiscard]] Task* createUpdateTask();
+
+ [[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); }
/** This creates a new parse task to be executed by onUpdateSucceeded().
*
@@ -167,10 +211,8 @@ class ResourceFolderModel : public QAbstractListModel {
* It uses set operations to find differences between the current state and the updated state,
* to act only on those disparities.
*
- * The implementation is at the end of this header.
*/
- template
- void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources);
+ void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources);
protected slots:
void directoryChanged(QString);
@@ -195,19 +237,22 @@ class ResourceFolderModel : public QAbstractListModel {
protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
// As such, the order in with they appear is very important!
- QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE };
- QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" };
- QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") };
+ QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
+ QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" };
+ QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") };
QList