Merge branch 'release-5.x' of https://github.com/PrismLauncher/PrismLauncher into prism

This commit is contained in:
fn2006 2022-10-24 16:24:36 +01:00
commit 23e5773094
188 changed files with 3353 additions and 10183 deletions

3
.github/codeql/codeql-config.yml vendored Normal file
View File

@ -0,0 +1,3 @@
query-filters:
- exclude:
id: cpp/fixme-comment

View File

@ -39,12 +39,21 @@ jobs:
qt_ver: 6 qt_ver: 6
- os: macos-12 - os: macos-12
name: macOS
macosx_deployment_target: 10.15 macosx_deployment_target: 10.15
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_version: '6.3.0' qt_version: '6.3.0'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
- os: macos-12
name: macOS-Legacy
macosx_deployment_target: 10.13
qt_ver: 5
qt_host: mac
qt_version: '5.15.2'
qt_modules: ''
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
@ -64,6 +73,14 @@ jobs:
with: with:
submodules: 'true' submodules: 'true'
- name: Initialize CodeQL
if: runner.os == 'Linux' && matrix.qt_ver == 6
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality
languages: cpp, java
- name: 'Setup MSYS2' - name: 'Setup MSYS2'
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
@ -140,7 +157,7 @@ jobs:
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Install Qt (macOS and AppImage) - name: Install Qt (macOS and AppImage)
if: matrix.qt_ver == 6 && runner.os != 'Windows' if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
version: ${{ matrix.qt_version }} version: ${{ matrix.qt_version }}
@ -164,9 +181,14 @@ jobs:
## ##
- name: Configure CMake (macOS) - name: Configure CMake (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS' && matrix.qt_ver == 6
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
- name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
- name: Configure CMake (Windows) - name: Configure CMake (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
@ -209,6 +231,14 @@ jobs:
run: | run: |
ctest --test-dir build --output-on-failure ctest --test-dir build --output-on-failure
##
# CODE SCAN
##
- name: Perform CodeQL Analysis
if: runner.os == 'Linux' && matrix.qt_ver == 6
uses: github/codeql-action/analyze@v2
## ##
# PACKAGE BUILDS # PACKAGE BUILDS
## ##
@ -224,7 +254,7 @@ jobs:
tar -czf ../PollyMC.tar.gz * tar -czf ../PollyMC.tar.gz *
- name: Make Sparkle signature (macOS) - name: Make Sparkle signature (macOS)
if: runner.os == 'macOS' if: matrix.name == 'macOS'
run: | run: |
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
brew install openssl@3 brew install openssl@3
@ -323,7 +353,7 @@ jobs:
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: PollyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} name: PollyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PollyMC.tar.gz path: PollyMC.tar.gz
- name: Upload binary zip (Windows) - name: Upload binary zip (Windows)

View File

@ -40,6 +40,7 @@ jobs:
mv PollyMC-Linux-Portable*/PollyMC-portable.tar.gz PollyMC-Linux-Portable-${{ env.VERSION }}.tar.gz mv PollyMC-Linux-Portable*/PollyMC-portable.tar.gz PollyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
mv PollyMC-Linux*/PollyMC.tar.gz PollyMC-Linux-${{ env.VERSION }}.tar.gz mv PollyMC-Linux*/PollyMC.tar.gz PollyMC-Linux-${{ env.VERSION }}.tar.gz
mv PollyMC-*.AppImage/PollyMC-*.AppImage PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage mv PollyMC-*.AppImage/PollyMC-*.AppImage PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
mv PollyMC-macOS-Legacy*/PollyMC.tar.gz PollyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz
mv PollyMC-macOS*/PollyMC.tar.gz PollyMC-macOS-${{ env.VERSION }}.tar.gz mv PollyMC-macOS*/PollyMC.tar.gz PollyMC-macOS-${{ env.VERSION }}.tar.gz
tar -czf PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }} tar -czf PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }}
@ -80,4 +81,5 @@ jobs:
PollyMC-Windows-Portable-${{ env.VERSION }}.zip PollyMC-Windows-Portable-${{ env.VERSION }}.zip
PollyMC-Windows-Setup-${{ env.VERSION }}.exe PollyMC-Windows-Setup-${{ env.VERSION }}.exe
PollyMC-macOS-${{ env.VERSION }}.tar.gz PollyMC-macOS-${{ env.VERSION }}.tar.gz
PollyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz
PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }}.tar.gz

View File

@ -7,8 +7,9 @@ jobs:
publish: publish:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: vedantmgoyal2009/winget-releaser@latest - uses: vedantmgoyal2009/winget-releaser@v1
with: with:
identifier: PolyMC.PolyMC identifier: PrismLauncher.PrismLauncher
installers-regex: 'PolyMC-Windows-Setup-.+\.exe$' version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-Setup-.+\.exe$'
token: ${{ secrets.WINGET_TOKEN }} token: ${{ secrets.WINGET_TOKEN }}

14
.gitmodules vendored
View File

@ -1,8 +1,12 @@
[submodule "depends/libnbtplusplus"]
path = libraries/libnbtplusplus
url = https://github.com/PolyMC/libnbtplusplus.git
pushurl = git@github.com:PolyMC/libnbtplusplus.git
[submodule "libraries/quazip"] [submodule "libraries/quazip"]
path = libraries/quazip path = libraries/quazip
url = https://github.com/stachenov/quazip.git url = https://github.com/stachenov/quazip.git
[submodule "libraries/tomlplusplus"]
path = libraries/tomlplusplus
url = https://github.com/marzer/tomlplusplus.git
[submodule "libraries/filesystem"]
path = libraries/filesystem
url = https://github.com/gulrak/filesystem
[submodule "libraries/libnbtplusplus"]
path = libraries/libnbtplusplus
url = https://github.com/PrismLauncher/libnbtplusplus.git

View File

@ -1,5 +1,53 @@
# Build Instructions # Build Instructions
Build instructions are available on [the website](https://polymc.org/wiki/development/build-instructions/). Full build instructions will be available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).
If you would like to contribute or fix an issue with the Build instructions you will be able to do so [here](https://github.com/PrismLauncher/website/blob/master/src/wiki/development/build-instructions.md).
## Getting the source
Clone the source code using git, and grab all the submodules. This is generic for all platforms you want to build on.
```
git clone --recursive https://github.com/PrismLauncher/PrismLauncher
cd PrismLauncher
```
## Linux
This guide will mostly mention dependant packages by their Debian naming and commands are done by a user in the sudoers file.
### Dependencies
- A C++ compiler capable of building C++17 code (can be found in the package `build-essential`).
- Qt Development tools 5.12 or newer (on Debian 11 or Debian-based distributions, `qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5`).
- `cmake` 3.15 or newer.
- `extra-cmake-modules`.
- zlib (`zlib1g-dev` on Debian 11 or Debian-based distributions).
- Java Development Kit (Java JDK) (`openjdk-17-jdk` on Debian 11 or Debian-based distributions).
- Mesa GL headers (`libgl1-mesa-dev` on Debian 11 or Debian-based distributions).
- (Optional) `scdoc` to generate man pages.
In conclusion, to check if all you need is installed (including optional):
```
sudo apt install build-essential qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 cmake extra-cmake-modules zlib1g-dev openjdk-17-jdk libgl1-mesa-dev scdoc
```
### Compiling
#### Building and installing on the system
This is usually the suggested way to build the client.
```
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_LTO=ON
cmake --build build -j$(nproc)
sudo cmake --install build
```
#### Building a portable binary
```
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
cmake --build build -j$(nproc)
cmake --install build
cmake --install build --component portable
```
If you would like to contribute or fix an issue with the Build instructions you can do so [here](https://github.com/PolyMC/polymc.github.io/blob/master/src/wiki/development/build-instructions.md).

View File

@ -33,9 +33,6 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) include(GenerateExportHeader)
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
# Fix build with Qt 5.13 # Fix build with Qt 5.13
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
@ -74,9 +71,9 @@ endif()
##################################### Set Application options ##################################### ##################################### Set Application options #####################################
######## Set URLs ######## ######## Set URLs ########
set(Launcher_NEWS_RSS_URL "https://polymc.org/feed/feed.xml" CACHE STRING "URL to fetch PolyMC's news RSS feed from.") set(Launcher_NEWS_RSS_URL "https://prismlauncher.org/feed/feed.xml" CACHE STRING "URL to fetch PrismLauncher's news RSS feed from.")
set(Launcher_NEWS_OPEN_URL "https://polymc.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'") set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help") set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 5) set(Launcher_VERSION_MAJOR 5)
@ -93,7 +90,7 @@ set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the plat
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
# The metadata server # The metadata server
set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
# Imgur API Client ID # Imgur API Client ID
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application") set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
@ -102,7 +99,7 @@ set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can g
set(Launcher_BUG_TRACKER_URL "https://github.com/fn2006/PollyMC/issues" CACHE STRING "URL for the bug tracker.") set(Launcher_BUG_TRACKER_URL "https://github.com/fn2006/PollyMC/issues" CACHE STRING "URL for the bug tracker.")
# Translations Platform URL # Translations Platform URL
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/polymc/polymc/" CACHE STRING "URL for the translations platform.") set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
# Matrix Space # Matrix Space
set(Launcher_MATRIX_URL "" CACHE STRING "URL to the Matrix Space") set(Launcher_MATRIX_URL "" CACHE STRING "URL to the Matrix Space")
@ -192,6 +189,14 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON) SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif() endif()
if(NOT Launcher_FORCE_BUNDLED_LIBS)
# Find toml++
find_package(tomlplusplus 3.2.0 QUIET)
# Find ghc_filesystem
find_package(ghc_filesystem QUIET)
endif()
####################################### Program Info ####################################### ####################################### Program Info #######################################
set(Launcher_APP_BINARY_NAME "pollymc" CACHE STRING "Name of the Launcher binary") set(Launcher_APP_BINARY_NAME "pollymc" CACHE STRING "Name of the Launcher binary")
@ -218,14 +223,14 @@ if(UNIX AND APPLE)
# Mac bundle settings # Mac bundle settings
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}") set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}")
set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.polymc.${Launcher_Name}") set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.prismlauncher.${Launcher_Name}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}") set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "idALcUIazingvKSSsEa9U7coDVxZVx/ORpOEE/QtJfg=") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://polymc.org/feed/appcast.xml") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive") set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive") set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
@ -300,7 +305,6 @@ add_subdirectory(libraries/systeminfo) # system information library
add_subdirectory(libraries/hoedown) # markdown parser add_subdirectory(libraries/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker add_subdirectory(libraries/javacheck) # java compatibility checker
add_subdirectory(libraries/xz-embedded) # xz compression
if (FORCE_BUNDLED_QUAZIP) if (FORCE_BUNDLED_QUAZIP)
message(STATUS "Using bundled QuaZip") message(STATUS "Using bundled QuaZip")
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.
@ -311,11 +315,23 @@ else()
endif() endif()
add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # class parser library if(NOT tomlplusplus_FOUND)
add_subdirectory(libraries/tomlc99) # toml parser message(STATUS "Using bundled tomlplusplus")
add_subdirectory(libraries/tomlplusplus) # toml parser
else()
message(STATUS "Using system tomlplusplus")
endif()
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
add_subdirectory(libraries/gamemode) add_subdirectory(libraries/gamemode)
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
if (NOT ghc_filesystem_FOUND)
message(STATUS "Using bundled ghc_filesystem")
set(GHC_FILESYSTEM_WITH_INSTALL OFF) # Workaround ghc::filesystem bug
add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS
add_library(ghcFilesystem::ghc_filesystem ALIAS ghc_filesystem)
else()
message(STATUS "Using system ghc_filesystem")
endif()
############################### Built Artifacts ############################### ############################### Built Artifacts ###############################

View File

@ -63,7 +63,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement via email at reported to the community leaders responsible for enforcement via email at
[polymc-enforcement@scrumplex.net](mailto:polymc-enforcement@scrumplex.net) (Email [coc@scrumplex.net](mailto:coc@scrumplex.net) (Email
address subject to change). address subject to change).
All complaints will be reviewed and investigated promptly and fairly. All complaints will be reviewed and investigated promptly and fairly.

View File

@ -1,3 +1,37 @@
## Prism Launcher
Prism Launcher - Minecraft Launcher
Copyright (C) 2022 Prism Launcher Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
This file incorporates work covered by the following copyright and
permission notice:
Copyright 2013-2021 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## PolyMC ## PolyMC
PolyMC - Minecraft Launcher PolyMC - Minecraft Launcher
@ -191,52 +225,6 @@
See COPYING file for the full LGPL text. See COPYING file for the full LGPL text.
## xz-minidec
XZ decompressor
Authors: Lasse Collin <lasse.collin@tukaani.org>
Igor Pavlov <http://7-zip.org/>
This file has been put into the public domain.
You can do whatever you want with this file.
## ColumnResizer
Copyright (c) 2011-2016 Aurélien Gâteau and contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted (subject to the limitations in the
disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
* The name of the contributors may not be used to endorse or
promote products derived from this software without specific prior
written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## launcher (`libraries/launcher`) ## launcher (`libraries/launcher`)
PolyMC - Minecraft Launcher PolyMC - Minecraft Launcher
@ -315,30 +303,24 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
## tomlc99 ## tomlplusplus
MIT License MIT License
Copyright (c) 2017 CK Tan Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
of this software and associated documentation files (the "Software"), to deal documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
in the Software without restriction, including without limitation the rights rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell permit persons to whom the Software is furnished to do so, subject to the following conditions:
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
copies or substantial portions of the Software. Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## O2 (Katabasis fork) ## O2 (Katabasis fork)
@ -394,3 +376,25 @@
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
## gulrak/filesystem
Copyright (c) 2018, Steffen Schümann <s.schuemann@pobox.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,17 +1,14 @@
# One of PolyMC's developers has gone rouge and kicked out all of the other developers
### For the time being use I would use `https://meta.prismlauncher.org/v1/` as your metaserver
### If you trust him then you can continue using the default metaserver but I take no responsibility for anything that happens while using it
<p align="center"> <p align="center">
<img src="./program_info/pollymc-header-black.svg#gh-light-mode-only" alt="PollyMC logo" width="60%"/> <img src="./program_info/pollymc-header-black.svg#gh-light-mode-only" alt="PollyMC logo" width="60%"/>
<img src="./program_info/pollymc-header.svg#gh-dark-mode-only" alt="PollyMC logo" width="60%"/> <img src="./program_info/pollymc-header.svg#gh-dark-mode-only" alt="PollyMC logo" width="60%"/>
</p> </p>
PollyMC is a **fork** of PolyMC and is not endorsed by or affiliated with the PolyMC project. PollyMC is a **fork** of Prism Launcher that adds support for Ely.by accounts and allows you to play offline mode without an account
If you have any problems open an issue here, do not bug the PolyMC maintainers. PollyMC is not endorsed by or affiliated with the Prism Launcher project.
If you have any problems open an issue here, do not bug the Prism Launcher maintainers.
Binaries can be found in releases. Binaries can be found in releases.
Workaround for downloading from CurseForge and FTB not working [here](https://github.com/fn2006/PollyMC/wiki/CurseForge-Workaround). Workaround for downloading from CurseForge and FTB not working [here](https://github.com/fn2006/PollyMC/wiki/CurseForge-Workaround).
To build this yourself, follow the instructions on the PolyMC website but clone this repo instead. To build this yourself, follow the instructions on the Prism Launcher website but clone this repo instead.

View File

@ -140,8 +140,8 @@ class Config {
QString LIBRARY_BASE = "https://libraries.minecraft.net/"; QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString AUTH_BASE = "https://authserver.mojang.com/"; QString AUTH_BASE = "https://authserver.mojang.com/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/"; QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
QString FMLLIBS_BASE_URL = "https://files.polymc.org/fmllibs/"; QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists
QString TRANSLATIONS_BASE_URL = "https://i18n.polymc.org/"; QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/"; QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";

29
flake.lock generated
View File

@ -21,24 +21,24 @@
"locked": { "locked": {
"lastModified": 1650031308, "lastModified": 1650031308,
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=", "narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
"owner": "PolyMC", "owner": "PrismLauncher",
"repo": "libnbtplusplus", "repo": "libnbtplusplus",
"rev": "2203af7eeb48c45398139b583615134efd8d407f", "rev": "2203af7eeb48c45398139b583615134efd8d407f",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "PolyMC", "owner": "PrismLauncher",
"repo": "libnbtplusplus", "repo": "libnbtplusplus",
"type": "github" "type": "github"
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1658119717, "lastModified": 1666057921,
"narHash": "sha256-4upOZIQQ7Bc4CprqnHsKnqYfw+arJeAuU+QcpjYBXW0=", "narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9eb60f25aff0d2218c848dd4574a0ab5e296cabe", "rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -52,7 +52,24 @@
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus", "libnbtplusplus": "libnbtplusplus",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs",
"tomlplusplus": "tomlplusplus"
}
},
"tomlplusplus": {
"flake": false,
"locked": {
"lastModified": 1666091090,
"narHash": "sha256-djpMCFPvkJcfynV8WnsYdtwLq+J7jpV1iM4C6TojiyM=",
"owner": "marzer",
"repo": "tomlplusplus",
"rev": "1e4a3833d013aee08f58c5b31c69f709afc69f73",
"type": "github"
},
"original": {
"owner": "marzer",
"repo": "tomlplusplus",
"type": "github"
} }
} }
}, },

View File

@ -4,10 +4,11 @@
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
libnbtplusplus = { url = "github:PolyMC/libnbtplusplus"; flake = false; }; libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; };
}; };
outputs = { self, nixpkgs, libnbtplusplus, ... }: outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }:
let let
# User-friendly version number. # User-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate; version = builtins.substring 0 8 self.lastModifiedDate;
@ -22,14 +23,14 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
packagesFn = pkgs: rec { packagesFn = pkgs: rec {
polymc = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
polymc-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; }; prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
}; };
in in
{ {
packages = forAllSystems (system: packages = forAllSystems (system:
let packages = packagesFn pkgs.${system}; in let packages = packagesFn pkgs.${system}; in
packages // { default = packages.polymc; } packages // { default = packages.prismlauncher; }
); );
overlay = final: packagesFn; overlay = final: packagesFn;

View File

@ -78,6 +78,7 @@
#include <iostream> #include <iostream>
#include <QAccessible> #include <QAccessible>
#include <QCommandLineParser>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
@ -110,7 +111,6 @@
#include "translations/TranslationsModel.h" #include "translations/TranslationsModel.h"
#include "meta/Index.h" #include "meta/Index.h"
#include <Commandline.h>
#include <FileSystem.h> #include <FileSystem.h>
#include <DesktopServices.h> #include <DesktopServices.h>
#include <LocalPeer.h> #include <LocalPeer.h>
@ -136,12 +136,6 @@
static const QLatin1String liveCheckFile("live.check"); static const QLatin1String liveCheckFile("live.check");
using namespace Commandline;
#define MACOS_HINT "If you are on macOS Sierra, you might have to move the app to your /Applications or ~/Applications folder. "\
"This usually fixes the problem and you can move the application elsewhere afterwards.\n"\
"\n"
namespace { namespace {
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{ {
@ -242,80 +236,27 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
this->setQuitOnLastWindowClosed(false); this->setQuitOnLastWindowClosed(false);
// Commandline parsing // Commandline parsing
QHash<QString, QVariant> args; QCommandLineParser parser;
{ parser.setApplicationDescription(BuildConfig.LAUNCHER_DISPLAYNAME);
Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals);
// --help parser.addOptions({
parser.addSwitch("help"); {{"d", "dir"}, "Use a custom path as application root (use '.' for current directory)", "directory"},
parser.addShortOpt("help", 'h'); {{"l", "launch"}, "Launch the specified instance (by instance ID)", "instance"},
parser.addDocumentation("help", "Display this help and exit."); {{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"},
// --version {{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"},
parser.addSwitch("version"); {"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"},
parser.addShortOpt("version", 'V'); {{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"}
parser.addDocumentation("version", "Display program version and exit."); });
// --dir parser.addHelpOption();
parser.addOption("dir"); parser.addVersionOption();
parser.addShortOpt("dir", 'd');
parser.addDocumentation("dir", "Use the supplied folder as application root instead of the binary location (use '.' for current)");
// --launch
parser.addOption("launch");
parser.addShortOpt("launch", 'l');
parser.addDocumentation("launch", "Launch the specified instance (by instance ID)");
// --server
parser.addOption("server");
parser.addShortOpt("server", 's');
parser.addDocumentation("server", "Join the specified server on launch (only valid in combination with --launch)");
// --profile
parser.addOption("profile");
parser.addShortOpt("profile", 'a');
parser.addDocumentation("profile", "Use the account specified by its profile name (only valid in combination with --launch)");
// --alive
parser.addSwitch("alive");
parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after the launcher starts");
// --import
parser.addOption("import");
parser.addShortOpt("import", 'I');
parser.addDocumentation("import", "Import instance from specified zip (local path or URL)");
// parse the arguments parser.process(arguments());
try
{
args = parser.parse(arguments());
}
catch (const ParsingError &e)
{
std::cerr << "CommandLineError: " << e.what() << std::endl;
if(argc > 0)
std::cerr << "Try '" << argv[0] << " -h' to get help on command line parameters."
<< std::endl;
m_status = Application::Failed;
return;
}
// display help and exit m_instanceIdToLaunch = parser.value("launch");
if (args["help"].toBool()) m_serverToJoin = parser.value("server");
{ m_profileToUse = parser.value("profile");
std::cout << qPrintable(parser.compileHelp(arguments()[0])); m_liveCheck = parser.isSet("alive");
m_status = Application::Succeeded; m_zipToImport = parser.value("import");
return;
}
// display version and exit
if (args["version"].toBool())
{
std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl;
std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl;
m_status = Application::Succeeded;
return;
}
}
m_instanceIdToLaunch = args["launch"].toString();
m_serverToJoin = args["server"].toString();
m_profileToUse = args["profile"].toString();
m_liveCheck = args["alive"].toBool();
m_zipToImport = args["import"].toUrl();
// error if --launch is missing with --server or --profile // error if --launch is missing with --server or --profile
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
@ -346,7 +287,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString adjustedBy; QString adjustedBy;
QString dataPath; QString dataPath;
// change folder // change folder
QString dirParam = args["dir"].toString(); QString dirParam = parser.value("dir");
if (!dirParam.isEmpty()) if (!dirParam.isEmpty())
{ {
// the dir param. it makes multimc data path point to whatever the user specified // the dir param. it makes multimc data path point to whatever the user specified
@ -360,6 +301,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
dataPath = foo.absolutePath(); dataPath = foo.absolutePath();
adjustedBy = "Persistent data path"; adjustedBy = "Persistent data path";
QDir polymcData(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "PolyMC"));
if (polymcData.exists()) {
dataPath = polymcData.absolutePath();
adjustedBy = "PolyMC data path";
}
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
// TODO: this should be removed in a future version // TODO: this should be removed in a future version
// TODO: provide a migration path similar to macOS migration // TODO: provide a migration path similar to macOS migration
@ -385,9 +332,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString( QString(
"The launcher data folder could not be created.\n" "The launcher data folder could not be created.\n"
"\n" "\n"
#if defined(Q_OS_MAC)
MACOS_HINT
#endif
"Make sure you have the right permissions to the launcher data folder and any folder needed to access it.\n" "Make sure you have the right permissions to the launcher data folder and any folder needed to access it.\n"
"(%1)\n" "(%1)\n"
"\n" "\n"
@ -403,9 +347,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString( QString(
"The launcher data folder could not be opened.\n" "The launcher data folder could not be opened.\n"
"\n" "\n"
#if defined(Q_OS_MAC)
MACOS_HINT
#endif
"Make sure you have the right permissions to the launcher data folder.\n" "Make sure you have the right permissions to the launcher data folder.\n"
"(%1)\n" "(%1)\n"
"\n" "\n"
@ -486,9 +427,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString( QString(
"The launcher couldn't create a log file - the data folder is not writable.\n" "The launcher couldn't create a log file - the data folder is not writable.\n"
"\n" "\n"
#if defined(Q_OS_MAC)
MACOS_HINT
#endif
"Make sure you have write permissions to the data folder.\n" "Make sure you have write permissions to the data folder.\n"
"(%1)\n" "(%1)\n"
"\n" "\n"
@ -550,7 +488,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Initialize application settings // Initialize application settings
{ {
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this)); // Provide a fallback for migration from PolyMC
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
// Updates // Updates
// Multiple channels are separated by spaces // Multiple channels are separated by spaces
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL); m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
@ -877,6 +816,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath()); m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath()); m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath()); m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
@ -928,12 +868,13 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Application theme set."; qDebug() << "<> Application theme set.";
} }
updateCapabilities();
if(createSetupWizard()) if(createSetupWizard())
{ {
return; return;
} }
updateCapabilities();
performMainStartupAction(); performMainStartupAction();
} }

View File

@ -355,7 +355,7 @@ QString BaseInstance::name() const
QString BaseInstance::windowTitle() const QString BaseInstance::windowTitle() const
{ {
return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegularExpression("\\s+"), " "); return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
} }
// FIXME: why is this here? move it to MinecraftInstance!!! // FIXME: why is this here? move it to MinecraftInstance!!!
@ -368,3 +368,8 @@ shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
{ {
return m_launchProcess; return m_launchProcess;
} }
void BaseInstance::updateRuntimeContext()
{
// NOOP
}

View File

@ -54,6 +54,7 @@
#include "net/Mode.h" #include "net/Mode.h"
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftServerTarget.h"
#include "RuntimeContext.h"
class QDir; class QDir;
class Task; class Task;
@ -220,6 +221,12 @@ public:
virtual QString typeName() const = 0; virtual QString typeName() const = 0;
void updateRuntimeContext();
RuntimeContext runtimeContext() const
{
return m_runtimeContext;
}
bool hasVersionBroken() const bool hasVersionBroken() const
{ {
return m_hasBrokenVersion; return m_hasBrokenVersion;
@ -305,6 +312,7 @@ protected: /* data */
bool m_isRunning = false; bool m_isRunning = false;
shared_qobject_ptr<LaunchTask> m_launchProcess; shared_qobject_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted; QDateTime m_timeStarted;
RuntimeContext m_runtimeContext;
private: /* data */ private: /* data */
Status m_status = Status::Present; Status m_status = Status::Present;

View File

@ -26,6 +26,7 @@ set(CORE_SOURCES
MMCZip.cpp MMCZip.cpp
MMCStrings.h MMCStrings.h
MMCStrings.cpp MMCStrings.cpp
RuntimeContext.h
# Basic instance manipulation tasks (derived from InstanceTask) # Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h InstanceCreationTask.h
@ -296,8 +297,6 @@ set(MINECRAFT_SOURCES
minecraft/Rule.h minecraft/Rule.h
minecraft/OneSixVersionFormat.cpp minecraft/OneSixVersionFormat.cpp
minecraft/OneSixVersionFormat.h minecraft/OneSixVersionFormat.h
minecraft/OpSys.cpp
minecraft/OpSys.h
minecraft/ParseUtils.cpp minecraft/ParseUtils.cpp
minecraft/ParseUtils.h minecraft/ParseUtils.h
minecraft/ProfileUtils.cpp minecraft/ProfileUtils.cpp
@ -865,6 +864,10 @@ SET(LAUNCHER_SOURCES
ui/widgets/PageContainer.cpp ui/widgets/PageContainer.cpp
ui/widgets/PageContainer.h ui/widgets/PageContainer.h
ui/widgets/PageContainer_p.h ui/widgets/PageContainer_p.h
ui/widgets/ProjectDescriptionPage.h
ui/widgets/ProjectDescriptionPage.cpp
ui/widgets/VariableSizedImageObject.h
ui/widgets/VariableSizedImageObject.cpp
ui/widgets/ProjectItem.h ui/widgets/ProjectItem.h
ui/widgets/ProjectItem.cpp ui/widgets/ProjectItem.cpp
ui/widgets/VersionListView.cpp ui/widgets/VersionListView.cpp
@ -980,14 +983,14 @@ add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHE
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
systeminfo systeminfo
Launcher_classparser
Launcher_murmur2 Launcher_murmur2
nbt++ nbt++
${ZLIB_LIBRARIES} ${ZLIB_LIBRARIES}
tomlc99 tomlplusplus::tomlplusplus
BuildConfig BuildConfig
Katabasis Katabasis
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
) )
if (UNIX AND NOT CYGWIN AND NOT APPLE) if (UNIX AND NOT CYGWIN AND NOT APPLE)

View File

@ -92,412 +92,4 @@ QStringList splitArgs(QString args)
argv << current; argv << current;
return argv; return argv;
} }
Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle)
{
m_flagStyle = flagStyle;
m_argStyle = argStyle;
}
// styles setter/getter
void Parser::setArgumentStyle(ArgumentStyle::Enum style)
{
m_argStyle = style;
}
ArgumentStyle::Enum Parser::argumentStyle()
{
return m_argStyle;
}
void Parser::setFlagStyle(FlagStyle::Enum style)
{
m_flagStyle = style;
}
FlagStyle::Enum Parser::flagStyle()
{
return m_flagStyle;
}
// setup methods
void Parser::addSwitch(QString name, bool def)
{
if (m_params.contains(name))
throw "Name not unique";
OptionDef *param = new OptionDef;
param->type = otSwitch;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
m_options[name] = param;
m_params[name] = (CommonDef *)param;
m_optionList.append(param);
}
void Parser::addOption(QString name, QVariant def)
{
if (m_params.contains(name))
throw "Name not unique";
OptionDef *param = new OptionDef;
param->type = otOption;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
m_options[name] = param;
m_params[name] = (CommonDef *)param;
m_optionList.append(param);
}
void Parser::addArgument(QString name, bool required, QVariant def)
{
if (m_params.contains(name))
throw "Name not unique";
PositionalDef *param = new PositionalDef;
param->name = name;
param->def = def;
param->required = required;
param->metavar = name;
m_positionals.append(param);
m_params[name] = (CommonDef *)param;
}
void Parser::addDocumentation(QString name, QString doc, QString metavar)
{
if (!m_params.contains(name))
throw "Name does not exist";
CommonDef *param = m_params[name];
param->doc = doc;
if (!metavar.isNull())
param->metavar = metavar;
}
void Parser::addShortOpt(QString name, QChar flag)
{
if (!m_params.contains(name))
throw "Name does not exist";
if (!m_options.contains(name))
throw "Name is not an Option or Swtich";
OptionDef *param = m_options[name];
m_flags[flag] = param;
param->flag = flag;
}
// help methods
QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags)
{
QStringList help;
help << compileUsage(progName, useFlags) << "\r\n";
// positionals
if (!m_positionals.isEmpty())
{
help << "\r\n";
help << "Positional arguments:\r\n";
QListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext())
{
PositionalDef *param = it2.next();
help << " " << param->metavar;
help << " " << QString(helpIndent - param->metavar.length() - 1, ' ');
help << param->doc << "\r\n";
}
}
// Options
if (!m_optionList.isEmpty())
{
help << "\r\n";
QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix);
help << "Options & Switches:\r\n";
QListIterator<OptionDef *> it(m_optionList);
while (it.hasNext())
{
OptionDef *option = it.next();
help << " ";
int nameLength = optPrefix.length() + option->name.length();
if (!option->flag.isNull())
{
nameLength += 3 + flagPrefix.length();
help << flagPrefix << option->flag << ", ";
}
help << optPrefix << option->name;
if (option->type == otOption)
{
QString arg = QString("%1%2").arg(
((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
nameLength += arg.length();
help << arg;
}
help << " " << QString(helpIndent - nameLength - 1, ' ');
help << option->doc << "\r\n";
}
}
return help.join("");
}
QString Parser::compileUsage(QString progName, bool useFlags)
{
QStringList usage;
usage << "Usage: " << progName;
QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix);
// options
QListIterator<OptionDef *> it(m_optionList);
while (it.hasNext())
{
OptionDef *option = it.next();
usage << " [";
if (!option->flag.isNull() && useFlags)
usage << flagPrefix << option->flag;
else
usage << optPrefix << option->name;
if (option->type == otOption)
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
usage << "]";
}
// arguments
QListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext())
{
PositionalDef *param = it2.next();
usage << " " << (param->required ? "<" : "[");
usage << param->metavar;
usage << (param->required ? ">" : "]");
}
return usage.join("");
}
// parsing
QHash<QString, QVariant> Parser::parse(QStringList argv)
{
QHash<QString, QVariant> map;
QStringListIterator it(argv);
QString programName = it.next();
QString optionPrefix;
QString flagPrefix;
QListIterator<PositionalDef *> positionals(m_positionals);
QStringList expecting;
getPrefix(optionPrefix, flagPrefix);
while (it.hasNext())
{
QString arg = it.next();
if (!expecting.isEmpty())
// we were expecting an argument
{
QString name = expecting.first();
/*
if (map.contains(name))
throw ParsingError(
QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
*/
map[name] = QVariant(arg);
expecting.removeFirst();
continue;
}
if (arg.startsWith(optionPrefix))
// we have an option
{
// qDebug("Found option %s", qPrintable(arg));
QString name = arg.mid(optionPrefix.length());
QString equals;
if ((m_argStyle == ArgumentStyle::Equals ||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
name.contains("="))
{
int i = name.indexOf("=");
equals = name.mid(i + 1);
name = name.left(i);
}
if (m_options.contains(name))
{
/*
if (map.contains(name))
throw ParsingError(QString("Option %2%1 was given multiple times")
.arg(name, optionPrefix));
*/
OptionDef *option = m_options[name];
if (option->type == otSwitch)
map[name] = true;
else // if (option->type == otOption)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(name);
else if (!equals.isNull())
map[name] = equals;
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(name);
else
throw ParsingError(QString("Option %2%1 reqires an argument.")
.arg(name, optionPrefix));
}
continue;
}
throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix));
}
if (arg.startsWith(flagPrefix))
// we have (a) flag(s)
{
// qDebug("Found flags %s", qPrintable(arg));
QString flags = arg.mid(flagPrefix.length());
QString equals;
if ((m_argStyle == ArgumentStyle::Equals ||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
flags.contains("="))
{
int i = flags.indexOf("=");
equals = flags.mid(i + 1);
flags = flags.left(i);
}
for (int i = 0; i < flags.length(); i++)
{
QChar flag = flags.at(i);
if (!m_flags.contains(flag))
throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix));
OptionDef *option = m_flags[flag];
/*
if (map.contains(option->name))
throw ParsingError(QString("Option %2%1 was given multiple times")
.arg(option->name, optionPrefix));
*/
if (option->type == otSwitch)
map[option->name] = true;
else // if (option->type == otOption)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(option->name);
else if (!equals.isNull())
if (i == flags.length() - 1)
map[option->name] = equals;
else
throw ParsingError(QString("Flag %4%2 of Argument-requiring Option "
"%1 not last flag in %4%3")
.arg(option->name, flag, flags, flagPrefix));
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(option->name);
else
throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)")
.arg(option->name, flag, flagPrefix));
}
}
continue;
}
// must be a positional argument
if (!positionals.hasNext())
throw ParsingError(QString("Don't know what to do with '%1'").arg(arg));
PositionalDef *param = positionals.next();
map[param->name] = arg;
}
// check if we're missing something
if (!expecting.isEmpty())
throw ParsingError(QString("Was still expecting arguments for %2%1").arg(
expecting.join(QString(", ") + optionPrefix), optionPrefix));
while (positionals.hasNext())
{
PositionalDef *param = positionals.next();
if (param->required)
throw ParsingError(
QString("Missing required positional argument '%1'").arg(param->name));
else
map[param->name] = param->def;
}
// fill out gaps
QListIterator<OptionDef *> iter(m_optionList);
while (iter.hasNext())
{
OptionDef *option = iter.next();
if (!map.contains(option->name))
map[option->name] = option->def;
}
return map;
}
// clear defs
void Parser::clear()
{
m_flags.clear();
m_params.clear();
m_options.clear();
QMutableListIterator<OptionDef *> it(m_optionList);
while (it.hasNext())
{
OptionDef *option = it.next();
it.remove();
delete option;
}
QMutableListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext())
{
PositionalDef *arg = it2.next();
it2.remove();
delete arg;
}
}
// Destructor
Parser::~Parser()
{
clear();
}
// getPrefix
void Parser::getPrefix(QString &opt, QString &flag)
{
if (m_flagStyle == FlagStyle::Windows)
opt = flag = "/";
else if (m_flagStyle == FlagStyle::Unix)
opt = flag = "-";
// else if (m_flagStyle == FlagStyle::GNU)
else
{
opt = "--";
flag = "-";
}
}
// ParsingError
ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString())
{
}
} }

View File

@ -17,12 +17,7 @@
#pragma once #pragma once
#include <exception>
#include <stdexcept>
#include <QString> #include <QString>
#include <QVariant>
#include <QHash>
#include <QStringList> #include <QStringList>
/** /**
@ -39,212 +34,4 @@ namespace Commandline
* @return a QStringList containing all arguments * @return a QStringList containing all arguments
*/ */
QStringList splitArgs(QString args); QStringList splitArgs(QString args);
/**
* @brief The FlagStyle enum
* Specifies how flags are decorated
*/
namespace FlagStyle
{
enum Enum
{
GNU, /**< --option and -o (GNU Style) */
Unix, /**< -option and -o (Unix Style) */
Windows, /**< /option and /o (Windows Style) */
#ifdef Q_OS_WIN32
Default = Windows
#else
Default = GNU
#endif
};
}
/**
* @brief The ArgumentStyle enum
*/
namespace ArgumentStyle
{
enum Enum
{
Space, /**< --option value */
Equals, /**< --option=value */
SpaceAndEquals, /**< --option[= ]value */
#ifdef Q_OS_WIN32
Default = Equals
#else
Default = SpaceAndEquals
#endif
};
}
/**
* @brief The ParsingError class
*/
class ParsingError : public std::runtime_error
{
public:
ParsingError(const QString &what);
};
/**
* @brief The Parser class
*/
class Parser
{
public:
/**
* @brief Parser constructor
* @param flagStyle the FlagStyle to use in this Parser
* @param argStyle the ArgumentStyle to use in this Parser
*/
Parser(FlagStyle::Enum flagStyle = FlagStyle::Default,
ArgumentStyle::Enum argStyle = ArgumentStyle::Default);
/**
* @brief set the flag style
* @param style
*/
void setFlagStyle(FlagStyle::Enum style);
/**
* @brief get the flag style
* @return
*/
FlagStyle::Enum flagStyle();
/**
* @brief set the argument style
* @param style
*/
void setArgumentStyle(ArgumentStyle::Enum style);
/**
* @brief get the argument style
* @return
*/
ArgumentStyle::Enum argumentStyle();
/**
* @brief define a boolean switch
* @param name the parameter name
* @param def the default value
*/
void addSwitch(QString name, bool def = false);
/**
* @brief define an option that takes an additional argument
* @param name the parameter name
* @param def the default value
*/
void addOption(QString name, QVariant def = QVariant());
/**
* @brief define a positional argument
* @param name the parameter name
* @param required wether this argument is required
* @param def the default value
*/
void addArgument(QString name, bool required = true, QVariant def = QVariant());
/**
* @brief adds a flag to an existing parameter
* @param name the (existing) parameter name
* @param flag the flag character
* @see addSwitch addArgument addOption
* Note: any one parameter can only have one flag
*/
void addShortOpt(QString name, QChar flag);
/**
* @brief adds documentation to a Parameter
* @param name the parameter name
* @param metavar a string to be displayed as placeholder for the value
* @param doc a QString containing the documentation
* Note: on positional arguments, metavar replaces the name as displayed.
* on options , metavar replaces the value placeholder
*/
void addDocumentation(QString name, QString doc, QString metavar = QString());
/**
* @brief generate a help message
* @param progName the program name to use in the help message
* @param helpIndent how much the parameter documentation should be indented
* @param flagsInUsage whether we should use flags instead of options in the usage
* @return a help message
*/
QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true);
/**
* @brief generate a short usage message
* @param progName the program name to use in the usage message
* @param useFlags whether we should use flags instead of options
* @return a usage message
*/
QString compileUsage(QString progName, bool useFlags = true);
/**
* @brief parse
* @param argv a QStringList containing the program ARGV
* @return a QHash mapping argument names to their values
*/
QHash<QString, QVariant> parse(QStringList argv);
/**
* @brief clear all definitions
*/
void clear();
~Parser();
private:
FlagStyle::Enum m_flagStyle;
ArgumentStyle::Enum m_argStyle;
enum OptionType
{
otSwitch,
otOption
};
// Important: the common part MUST BE COMMON ON ALL THREE structs
struct CommonDef
{
QString name;
QString doc;
QString metavar;
QVariant def;
};
struct OptionDef
{
// common
QString name;
QString doc;
QString metavar;
QVariant def;
// option
OptionType type;
QChar flag;
};
struct PositionalDef
{
// common
QString name;
QString doc;
QString metavar;
QVariant def;
// positional
bool required;
};
QHash<QString, OptionDef *> m_options;
QHash<QChar, OptionDef *> m_flags;
QHash<QString, CommonDef *> m_params;
QList<PositionalDef *> m_positionals;
QList<OptionDef *> m_optionList;
void getPrefix(QString &opt, QString &flag);
};
} }

View File

@ -59,7 +59,24 @@
#include <utime.h> #include <utime.h>
#endif #endif
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
#ifdef __APPLE__
#include <Availability.h> // for deployment target to support pre-catalina targets without std::fs
#endif // __APPLE__
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
#if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
#define GHC_USE_STD_FS
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem;
#endif // MacOS min version check
#endif // Other OSes version check
#ifndef GHC_USE_STD_FS
#include <ghc/filesystem.hpp>
namespace fs = ghc::filesystem;
#endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@ -147,7 +164,7 @@ bool ensureFolderPathExists(QString foldernamepath)
bool copy::operator()(const QString& offset) bool copy::operator()(const QString& offset)
{ {
using copy_opts = std::filesystem::copy_options; using copy_opts = fs::copy_options;
// NOTE always deep copy on windows. the alternatives are too messy. // NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@ -159,7 +176,7 @@ bool copy::operator()(const QString& offset)
std::error_code err; std::error_code err;
std::filesystem::copy_options opt = copy_opts::none; fs::copy_options opt = copy_opts::none;
// The default behavior is to follow symlinks // The default behavior is to follow symlinks
if (!m_followSymlinks) if (!m_followSymlinks)
@ -170,7 +187,7 @@ bool copy::operator()(const QString& offset)
// blacklisted paths, so we iterate over the source directory, and if there's no blacklist // blacklisted paths, so we iterate over the source directory, and if there's no blacklist
// match, we copy the file. // match, we copy the file.
QDir src_dir(src); QDir src_dir(src);
QDirIterator source_it(src, QDir::Filter::Files, QDirIterator::Subdirectories); QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
while (source_it.hasNext()) { while (source_it.hasNext()) {
auto src_path = source_it.next(); auto src_path = source_it.next();
@ -182,7 +199,7 @@ bool copy::operator()(const QString& offset)
auto dst_path = PathCombine(dst, relative_path); auto dst_path = PathCombine(dst, relative_path);
ensureFilePathExists(dst_path); ensureFilePathExists(dst_path);
std::filesystem::copy(toStdString(src_path), toStdString(dst_path), opt, err); fs::copy(toStdString(src_path), toStdString(dst_path), opt, err);
if (err) { if (err) {
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << src_path; qDebug() << "Source file:" << src_path;
@ -197,7 +214,7 @@ bool deletePath(QString path)
{ {
std::error_code err; std::error_code err;
std::filesystem::remove_all(toStdString(path), err); fs::remove_all(toStdString(path), err);
if (err) { if (err) {
qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); qWarning() << "Failed to remove files:" << QString::fromStdString(err.message());
@ -376,15 +393,15 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
bool overrideFolder(QString overwritten_path, QString override_path) bool overrideFolder(QString overwritten_path, QString override_path)
{ {
using copy_opts = std::filesystem::copy_options; using copy_opts = fs::copy_options;
if (!FS::ensureFolderPathExists(overwritten_path)) if (!FS::ensureFolderPathExists(overwritten_path))
return false; return false;
std::error_code err; std::error_code err;
std::filesystem::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing;
std::filesystem::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err);
if (err) { if (err) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path);

View File

@ -93,8 +93,8 @@ void LaunchController::decideAccount()
auto reply = CustomMessageBox::selectable( auto reply = CustomMessageBox::selectable(
m_parentWidget, m_parentWidget,
tr("No Accounts"), tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Mojang or Microsoft " tr("In order to play Minecraft, you must have at least one Microsoft or Mojang "
"account logged in. " "account logged in. Mojang accounts can only be used offline. "
"Would you like to open the account manager to add an account now?"), "Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Information,
QMessageBox::Yes | QMessageBox::No QMessageBox::Yes | QMessageBox::No
@ -382,7 +382,7 @@ void LaunchController::launchInstance()
m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
// Prepend Version // Prepend Version
m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_NAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher)); m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
m_launcher->start(); m_launcher->start();
} }

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include "BaseInstance.h" #include "BaseInstance.h"
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
@ -84,4 +119,8 @@ public:
QString modsRoot() const override { QString modsRoot() const override {
return QString(); return QString();
} }
void updateRuntimeContext()
{
// NOOP
}
}; };

88
launcher/RuntimeContext.h Normal file
View File

@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QSet>
#include <QString>
#include "settings/SettingsObject.h"
struct RuntimeContext {
QString javaArchitecture;
QString javaRealArchitecture;
QString javaPath;
QString system;
QString mappedJavaRealArchitecture() const
{
if (javaRealArchitecture == "amd64")
return "x86_64";
if (javaRealArchitecture == "i386" || javaRealArchitecture == "i686")
return "x86";
if (javaRealArchitecture == "aarch64")
return "arm64";
if (javaRealArchitecture == "arm" || javaRealArchitecture == "armhf")
return "arm32";
return javaRealArchitecture;
}
void updateFromInstanceSettings(SettingsObjectPtr instanceSettings)
{
javaArchitecture = instanceSettings->get("JavaArchitecture").toString();
javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString();
javaPath = instanceSettings->get("JavaPath").toString();
system = currentSystem();
}
QString getClassifier() const { return system + "-" + mappedJavaRealArchitecture(); }
// "Legacy" refers to the fact that Mojang assumed that these are the only two architectures
bool isLegacyArch() const
{
const QString mapped = mappedJavaRealArchitecture();
return mapped == "x86_64" || mapped == "x86";
}
bool classifierMatches(QString target) const
{
// try to match precise classifier "[os]-[arch]"
bool x = target == getClassifier();
// try to match imprecise classifier on legacy architectures "[os]"
if (!x && isLegacyArch())
x = target == system;
return x;
}
static QString currentSystem()
{
#if defined(Q_OS_LINUX)
return "linux";
#elif defined(Q_OS_MACOS)
return "osx";
#elif defined(Q_OS_WINDOWS)
return "windows";
#elif defined(Q_OS_FREEBSD)
return "freebsd";
#elif defined(Q_OS_OPENBSD)
return "openbsd";
#else
return "unknown";
#endif
}
};

View File

@ -358,7 +358,7 @@ void UpdateController::fail()
msg = QObject::tr( msg = QObject::tr(
"Couldn't replace file %1. Changes will be reverted.\n" "Couldn't replace file %1. Changes will be reverted.\n"
"See the %2 log file for details." "See the %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_NAME); ).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
doRollback = true; doRollback = true;
QMessageBox::critical(m_parent, failTitle, msg); QMessageBox::critical(m_parent, failTitle, msg);
break; break;
@ -368,7 +368,7 @@ void UpdateController::fail()
msg = QObject::tr( msg = QObject::tr(
"Couldn't remove file %1. Changes will be reverted.\n" "Couldn't remove file %1. Changes will be reverted.\n"
"See the %2 log file for details." "See the %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_NAME); ).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
doRollback = true; doRollback = true;
QMessageBox::critical(m_parent, failTitle, msg); QMessageBox::critical(m_parent, failTitle, msg);
break; break;
@ -399,7 +399,7 @@ void UpdateController::fail()
{ {
msg = QObject::tr("The rollback failed too.\n" msg = QObject::tr("The rollback failed too.\n"
"You will have to repair %1 manually.\n" "You will have to repair %1 manually.\n"
"Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_NAME); "Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_DISPLAYNAME);
QMessageBox::critical(m_parent, rollFailTitle, msg); QMessageBox::critical(m_parent, rollFailTitle, msg);
qApp->quit(); qApp->quit();
} }

View File

@ -174,7 +174,7 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
QStringList addJavasFromEnv(QList<QString> javas) QStringList addJavasFromEnv(QList<QString> javas)
{ {
auto env = qEnvironmentVariable("POLYMC_JAVA_PATHS"); auto env = qEnvironmentVariable("PRISMLAUNCHER_JAVA_PATHS"); // FIXME: use launcher name from buildconfig
#if defined(Q_OS_WIN32) #if defined(Q_OS_WIN32)
QList<QString> javaPaths = env.replace("\\", "/").split(QLatin1String(";")); QList<QString> javaPaths = env.replace("\\", "/").split(QLatin1String(";"));
@ -441,7 +441,7 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDir("/usr/lib/jvm"); scanJavaDir("/usr/lib/jvm");
scanJavaDir("/usr/lib64/jvm"); scanJavaDir("/usr/lib64/jvm");
scanJavaDir("/usr/lib32/jvm"); scanJavaDir("/usr/lib32/jvm");
// javas stored in PolyMC's folder // javas stored in Prism Launcher's folder
scanJavaDir("java"); scanJavaDir("java");
// manually installed JDKs in /opt // manually installed JDKs in /opt
scanJavaDir("/opt/jdk"); scanJavaDir("/opt/jdk");

View File

@ -121,7 +121,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
emit logLine(QString("Could not start java:"), MessageLevel::Error); emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine(QString("\nCheck your Java settings."), MessageLevel::Launcher); emit logLine(QString("\nCheck your Java settings."), MessageLevel::Launcher);
printSystemInfo(false, false);
emitFailed(QString("Could not start java!")); emitFailed(QString("Could not start java!"));
return; return;
} }
@ -130,7 +129,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error); emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning); emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher); emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
printSystemInfo(false, false);
emitSucceeded(); emitSucceeded();
return; return;
} }
@ -138,7 +136,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
{ {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor); printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
printSystemInfo(true, result.is_64bit);
instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform); instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaRealArchitecture", result.realPlatform); instance->settings()->set("JavaRealArchitecture", result.realPlatform);
@ -155,20 +152,3 @@ void CheckJava::printJavaInfo(const QString& version, const QString& architectur
emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n") emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n")
.arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher); .arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher);
} }
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
{
auto cpu64 = Sys::isCPU64bit();
auto system64 = Sys::isSystem64bit();
if(cpu64 != system64)
{
emit logLine(QString("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
}
if(javaIsKnown)
{
if(javaIs64bit != system64)
{
emit logLine(QString("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
}
}
}

View File

@ -75,7 +75,7 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(multimc); Q_INIT_RESOURCE(multimc);
Q_INIT_RESOURCE(backgrounds); Q_INIT_RESOURCE(backgrounds);
Q_INIT_RESOURCE(documents); Q_INIT_RESOURCE(documents);
Q_INIT_RESOURCE(polymc); Q_INIT_RESOURCE(prismlauncher);
Q_INIT_RESOURCE(pe_dark); Q_INIT_RESOURCE(pe_dark);
Q_INIT_RESOURCE(pe_light); Q_INIT_RESOURCE(pe_light);

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <meta/VersionList.h> #include <meta/VersionList.h>
#include <meta/Index.h> #include <meta/Index.h>
#include "Component.h" #include "Component.h"
@ -60,7 +95,7 @@ void Component::applyTo(LaunchProfile* profile)
auto vfile = getVersionFile(); auto vfile = getVersionFile();
if(vfile) if(vfile)
{ {
vfile->applyTo(profile); vfile->applyTo(profile, m_parent->runtimeContext());
} }
else else
{ {

View File

@ -173,9 +173,9 @@ void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor)
m_compatibleJavaMajors.append(javaMajor); m_compatibleJavaMajors.append(javaMajor);
} }
void LaunchProfile::applyLibrary(LibraryPtr library) void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext)
{ {
if(!library->isActive()) if(!library->isActive(runtimeContext))
{ {
return; return;
} }
@ -205,9 +205,9 @@ void LaunchProfile::applyLibrary(LibraryPtr library)
} }
} }
void LaunchProfile::applyMavenFile(LibraryPtr mavenFile) void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext & runtimeContext)
{ {
if(!mavenFile->isActive()) if(!mavenFile->isActive(runtimeContext))
{ {
return; return;
} }
@ -221,10 +221,10 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile)
m_mavenFiles.append(Library::limitedCopy(mavenFile)); m_mavenFiles.append(Library::limitedCopy(mavenFile));
} }
void LaunchProfile::applyAgent(AgentPtr agent) void LaunchProfile::applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext)
{ {
auto lib = agent->library(); auto lib = agent->library();
if(!lib->isActive()) if(!lib->isActive(runtimeContext))
{ {
return; return;
} }
@ -354,7 +354,7 @@ const QList<int> & LaunchProfile::getCompatibleJavaMajors() const
} }
void LaunchProfile::getLibraryFiles( void LaunchProfile::getLibraryFiles(
const QString& architecture, const RuntimeContext & runtimeContext,
QStringList& jars, QStringList& jars,
QStringList& nativeJars, QStringList& nativeJars,
const QString& overridePath, const QString& overridePath,
@ -366,7 +366,7 @@ void LaunchProfile::getLibraryFiles(
nativeJars.clear(); nativeJars.clear();
for (auto lib : getLibraries()) for (auto lib : getLibraries())
{ {
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
} }
// NOTE: order is important here, add main jar last to the lists // NOTE: order is important here, add main jar last to the lists
if(m_mainJar) if(m_mainJar)
@ -379,18 +379,18 @@ void LaunchProfile::getLibraryFiles(
} }
else else
{ {
m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); m_mainJar->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
} }
} }
for (auto lib : getNativeLibraries()) for (auto lib : getNativeLibraries())
{ {
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
} }
if(architecture == "32") if(runtimeContext.javaArchitecture == "32")
{ {
nativeJars.append(native32); nativeJars.append(native32);
} }
else if(architecture == "64") else if(runtimeContext.javaArchitecture == "64")
{ {
nativeJars.append(native64); nativeJars.append(native64);
} }

View File

@ -56,9 +56,9 @@ public: /* application of profile variables from patches */
void applyTweakers(const QStringList &tweakers); void applyTweakers(const QStringList &tweakers);
void applyJarMods(const QList<LibraryPtr> &jarMods); void applyJarMods(const QList<LibraryPtr> &jarMods);
void applyMods(const QList<LibraryPtr> &jarMods); void applyMods(const QList<LibraryPtr> &jarMods);
void applyLibrary(LibraryPtr library); void applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext);
void applyMavenFile(LibraryPtr library); void applyMavenFile(LibraryPtr library, const RuntimeContext & runtimeContext);
void applyAgent(AgentPtr agent); void applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext);
void applyCompatibleJavaMajors(QList<int>& javaMajor); void applyCompatibleJavaMajors(QList<int>& javaMajor);
void applyMainJar(LibraryPtr jar); void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity); void applyProblemSeverity(ProblemSeverity severity);
@ -83,7 +83,7 @@ public: /* getters for profile variables */
const QList<int> & getCompatibleJavaMajors() const; const QList<int> & getCompatibleJavaMajors() const;
const LibraryPtr getMainJar() const; const LibraryPtr getMainJar() const;
void getLibraryFiles( void getLibraryFiles(
const QString & architecture, const RuntimeContext & runtimeContext,
QStringList & jars, QStringList & jars,
QStringList & nativeJars, QStringList & nativeJars,
const QString & overridePath, const QString & overridePath,

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Library.h" #include "Library.h"
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
@ -7,7 +42,7 @@
#include <BuildConfig.h> #include <BuildConfig.h>
void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, void Library::getApplicableFiles(const RuntimeContext & runtimeContext, QStringList& jar, QStringList& native, QStringList& native32,
QStringList& native64, const QString &overridePath) const QStringList& native64, const QString &overridePath) const
{ {
bool local = isLocal(); bool local = isLocal();
@ -21,7 +56,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
} }
return out.absoluteFilePath(); return out.absoluteFilePath();
}; };
QString raw_storage = storageSuffix(system); QString raw_storage = storageSuffix(runtimeContext);
if(isNative()) if(isNative())
{ {
if (raw_storage.contains("${arch}")) if (raw_storage.contains("${arch}"))
@ -45,7 +80,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
} }
QList<NetAction::Ptr> Library::getDownloads( QList<NetAction::Ptr> Library::getDownloads(
OpSys system, const RuntimeContext & runtimeContext,
class HttpMetaCache* cache, class HttpMetaCache* cache,
QStringList& failedLocalFiles, QStringList& failedLocalFiles,
const QString & overridePath const QString & overridePath
@ -107,14 +142,14 @@ QList<NetAction::Ptr> Library::getDownloads(
return true; return true;
}; };
QString raw_storage = storageSuffix(system); QString raw_storage = storageSuffix(runtimeContext);
if(m_mojangDownloads) if(m_mojangDownloads)
{ {
if(isNative()) if(isNative())
{ {
if(m_nativeClassifiers.contains(system)) auto nativeClassifier = getCompatibleNative(runtimeContext);
if(!nativeClassifier.isNull())
{ {
auto nativeClassifier = m_nativeClassifiers[system];
if(nativeClassifier.contains("${arch}")) if(nativeClassifier.contains("${arch}"))
{ {
auto nat32Classifier = nativeClassifier; auto nat32Classifier = nativeClassifier;
@ -203,7 +238,7 @@ QList<NetAction::Ptr> Library::getDownloads(
return out; return out;
} }
bool Library::isActive() const bool Library::isActive(const RuntimeContext & runtimeContext) const
{ {
bool result = true; bool result = true;
if (m_rules.empty()) if (m_rules.empty())
@ -215,7 +250,7 @@ bool Library::isActive() const
RuleAction ruleResult = Disallow; RuleAction ruleResult = Disallow;
for (auto rule : m_rules) for (auto rule : m_rules)
{ {
RuleAction temp = rule->apply(this); RuleAction temp = rule->apply(this, runtimeContext);
if (temp != Defer) if (temp != Defer)
ruleResult = temp; ruleResult = temp;
} }
@ -223,7 +258,7 @@ bool Library::isActive() const
} }
if (isNative()) if (isNative())
{ {
result = result && m_nativeClassifiers.contains(currentSystem); result = result && !getCompatibleNative(runtimeContext).isNull();
} }
return result; return result;
} }
@ -238,6 +273,19 @@ bool Library::isAlwaysStale() const
return m_hint == "always-stale"; return m_hint == "always-stale";
} }
QString Library::getCompatibleNative(const RuntimeContext & runtimeContext) const {
// try to match precise classifier "[os]-[arch]"
auto entry = m_nativeClassifiers.constFind(runtimeContext.getClassifier());
// try to match imprecise classifier on legacy architectures "[os]"
if (entry == m_nativeClassifiers.constEnd() && runtimeContext.isLegacyArch())
entry = m_nativeClassifiers.constFind(runtimeContext.system);
if (entry == m_nativeClassifiers.constEnd())
return QString();
return entry.value();
}
void Library::setStoragePrefix(QString prefix) void Library::setStoragePrefix(QString prefix)
{ {
m_storagePrefix = prefix; m_storagePrefix = prefix;
@ -257,7 +305,7 @@ QString Library::storagePrefix() const
return m_storagePrefix; return m_storagePrefix;
} }
QString Library::filename(OpSys system) const QString Library::filename(const RuntimeContext & runtimeContext) const
{ {
if(!m_filename.isEmpty()) if(!m_filename.isEmpty())
{ {
@ -271,9 +319,10 @@ QString Library::filename(OpSys system) const
// otherwise native, override classifiers. Mojang HACK! // otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name; GradleSpecifier nativeSpec = m_name;
if (m_nativeClassifiers.contains(system)) QString nativeClassifier = getCompatibleNative(runtimeContext);
if (!nativeClassifier.isNull())
{ {
nativeSpec.setClassifier(m_nativeClassifiers[system]); nativeSpec.setClassifier(nativeClassifier);
} }
else else
{ {
@ -282,14 +331,14 @@ QString Library::filename(OpSys system) const
return nativeSpec.getFileName(); return nativeSpec.getFileName();
} }
QString Library::displayName(OpSys system) const QString Library::displayName(const RuntimeContext & runtimeContext) const
{ {
if(!m_displayname.isEmpty()) if(!m_displayname.isEmpty())
return m_displayname; return m_displayname;
return filename(system); return filename(runtimeContext);
} }
QString Library::storageSuffix(OpSys system) const QString Library::storageSuffix(const RuntimeContext & runtimeContext) const
{ {
// non-native? use only the gradle specifier // non-native? use only the gradle specifier
if (!isNative()) if (!isNative())
@ -299,9 +348,10 @@ QString Library::storageSuffix(OpSys system) const
// otherwise native, override classifiers. Mojang HACK! // otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name; GradleSpecifier nativeSpec = m_name;
if (m_nativeClassifiers.contains(system)) QString nativeClassifier = getCompatibleNative(runtimeContext);
if (!nativeClassifier.isNull())
{ {
nativeSpec.setClassifier(m_nativeClassifiers[system]); nativeSpec.setClassifier(nativeClassifier);
} }
else else
{ {

View File

@ -1,8 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QString> #include <QString>
#include <net/NetAction.h> #include <net/NetAction.h>
#include <QPair> #include <QPair>
#include <QList> #include <QList>
#include <QString>
#include <QStringList> #include <QStringList>
#include <QMap> #include <QMap>
#include <QDir> #include <QDir>
@ -10,9 +46,9 @@
#include <memory> #include <memory>
#include "Rule.h" #include "Rule.h"
#include "minecraft/OpSys.h"
#include "GradleSpecifier.h" #include "GradleSpecifier.h"
#include "MojangDownloadInfo.h" #include "MojangDownloadInfo.h"
#include "RuntimeContext.h"
class Library; class Library;
class MinecraftInstance; class MinecraftInstance;
@ -98,7 +134,7 @@ public: /* methods */
m_repositoryURL = base_url; m_repositoryURL = base_url;
} }
void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, void getApplicableFiles(const RuntimeContext & runtimeContext, QStringList & jar, QStringList & native,
QStringList & native32, QStringList & native64, const QString & overridePath) const; QStringList & native32, QStringList & native64, const QString & overridePath) const;
void setAbsoluteUrl(const QString &absolute_url) void setAbsoluteUrl(const QString &absolute_url)
@ -112,7 +148,7 @@ public: /* methods */
} }
/// Get the file name of the library /// Get the file name of the library
QString filename(OpSys system) const; QString filename(const RuntimeContext & runtimeContext) const;
// DEPRECATED: set a display name, used by jar mods only // DEPRECATED: set a display name, used by jar mods only
void setDisplayName(const QString & displayName) void setDisplayName(const QString & displayName)
@ -121,7 +157,7 @@ public: /* methods */
} }
/// Get the file name of the library /// Get the file name of the library
QString displayName(OpSys system) const; QString displayName(const RuntimeContext & runtimeContext) const;
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info)
{ {
@ -140,7 +176,7 @@ public: /* methods */
} }
/// Returns true if the library should be loaded (or extracted, in case of natives) /// Returns true if the library should be loaded (or extracted, in case of natives)
bool isActive() const; bool isActive(const RuntimeContext & runtimeContext) const;
/// Returns true if the library is contained in an instance and false if it is shared /// Returns true if the library is contained in an instance and false if it is shared
bool isLocal() const; bool isLocal() const;
@ -152,18 +188,20 @@ public: /* methods */
bool isForge() const; bool isForge() const;
// Get a list of downloads for this library // Get a list of downloads for this library
QList<NetAction::Ptr> getDownloads(OpSys system, class HttpMetaCache * cache, QList<NetAction::Ptr> getDownloads(const RuntimeContext & runtimeContext, class HttpMetaCache * cache,
QStringList & failedLocalFiles, const QString & overridePath) const; QStringList & failedLocalFiles, const QString & overridePath) const;
QString getCompatibleNative(const RuntimeContext & runtimeContext) const;
private: /* methods */ private: /* methods */
/// the default storage prefix used by PolyMC /// the default storage prefix used by Prism Launcher
static QString defaultStoragePrefix(); static QString defaultStoragePrefix();
/// Get the prefix - root of the storage to be used /// Get the prefix - root of the storage to be used
QString storagePrefix() const; QString storagePrefix() const;
/// Get the relative file path where the library should be saved /// Get the relative file path where the library should be saved
QString storageSuffix(OpSys system) const; QString storageSuffix(const RuntimeContext & runtimeContext) const;
QString hint() const QString hint() const
{ {
@ -177,23 +215,23 @@ protected: /* data */
/// DEPRECATED URL prefix of the maven repo where the file can be downloaded /// DEPRECATED URL prefix of the maven repo where the file can be downloaded
QString m_repositoryURL; QString m_repositoryURL;
/// DEPRECATED: PolyMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined /// DEPRECATED: Prism Launcher-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
QString m_absoluteURL; QString m_absoluteURL;
/// PolyMC extension - filename override /// Prism Launcher extension - filename override
QString m_filename; QString m_filename;
/// DEPRECATED PolyMC extension - display name /// DEPRECATED Prism Launcher extension - display name
QString m_displayname; QString m_displayname;
/** /**
* PolyMC-specific type hint - modifies how the library is treated * Prism Launcher-specific type hint - modifies how the library is treated
*/ */
QString m_hint; QString m_hint;
/** /**
* storage - by default the local libraries folder in polymc, but could be elsewhere * storage - by default the local libraries folder in Prism Launcher, but could be elsewhere
* PolyMC specific, because of FTB. * Prism Launcher specific, because of FTB.
*/ */
QString m_storagePrefix; QString m_storagePrefix;
@ -204,7 +242,7 @@ protected: /* data */
QStringList m_extractExcludes; QStringList m_extractExcludes;
/// native suffixes per OS /// native suffixes per OS
QMap<OpSys, QString> m_nativeClassifiers; QMap<QString, QString> m_nativeClassifiers;
/// true if the library had a rules section (even empty) /// true if the library had a rules section (even empty)
bool applyRules = false; bool applyRules = false;

View File

@ -147,6 +147,7 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
// Window Size // Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false); auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
@ -190,6 +191,13 @@ void MinecraftInstance::loadSpecificSettings()
qDebug() << "Instance-type specific settings were loaded!"; qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true); setSpecificSettingsLoaded(true);
updateRuntimeContext();
}
void MinecraftInstance::updateRuntimeContext()
{
m_runtimeContext.updateFromInstanceSettings(m_settings);
} }
QString MinecraftInstance::typeName() const QString MinecraftInstance::typeName() const
@ -327,9 +335,8 @@ QDir MinecraftInstance::versionsPath() const
QStringList MinecraftInstance::getClassPath() QStringList MinecraftInstance::getClassPath()
{ {
QStringList jars, nativeJars; QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
auto profile = m_components->getProfile(); auto profile = m_components->getProfile();
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
return jars; return jars;
} }
@ -342,9 +349,8 @@ QString MinecraftInstance::getMainClass() const
QStringList MinecraftInstance::getNativeJars() QStringList MinecraftInstance::getNativeJars()
{ {
QStringList jars, nativeJars; QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
auto profile = m_components->getProfile(); auto profile = m_components->getProfile();
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
return nativeJars; return nativeJars;
} }
@ -369,7 +375,7 @@ QStringList MinecraftInstance::extraArguments()
for (auto agent : agents) for (auto agent : agents)
{ {
QStringList jar, temp1, temp2, temp3; QStringList jar, temp1, temp2, temp3;
agent->library()->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, getLocalLibraryPath()); agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument())); list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
} }
@ -633,8 +639,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
// libraries and class path. // libraries and class path.
{ {
QStringList jars, nativeJars; QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString(); profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
for(auto file: jars) for(auto file: jars)
{ {
launchScript += "cp " + file + "\n"; launchScript += "cp " + file + "\n";
@ -690,8 +695,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
{ {
out << "Libraries:"; out << "Libraries:";
QStringList jars, nativeJars; QStringList jars, nativeJars;
auto javaArchitecture = settings->get("JavaArchitecture").toString(); profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
auto printLibFile = [&](const QString & path) auto printLibFile = [&](const QString & path)
{ {
QFileInfo info(path); QFileInfo info(path);
@ -756,8 +760,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Jar Mods:"; out << "Jar Mods:";
for(auto & jarmod: jarMods) for(auto & jarmod: jarMods)
{ {
auto displayname = jarmod->displayName(currentSystem); auto displayname = jarmod->displayName(runtimeContext());
auto realname = jarmod->filename(currentSystem); auto realname = jarmod->filename(runtimeContext());
if(displayname != realname) if(displayname != realname)
{ {
out << " " + displayname + " (" + realname + ")"; out << " " + displayname + " (" + realname + ")";
@ -919,6 +923,7 @@ QString MinecraftInstance::getStatusbarDescription()
Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode) Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
{ {
updateRuntimeContext();
switch (mode) switch (mode)
{ {
case Net::Mode::Offline: case Net::Mode::Offline:
@ -935,6 +940,7 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{ {
updateRuntimeContext();
// FIXME: get rid of shared_from_this ... // FIXME: get rid of shared_from_this ...
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this())); auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
auto pptr = process.get(); auto pptr = process.get();
@ -1172,7 +1178,7 @@ QList<Mod*> MinecraftInstance::getJarMods() const
for (auto jarmod : profile->getJarMods()) for (auto jarmod : profile->getJarMods())
{ {
QStringList jar, temp1, temp2, temp3; QStringList jar, temp1, temp2, temp3;
jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); jarmod->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
// QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
mods.push_back(new Mod(QFileInfo(jar[0]))); mods.push_back(new Mod(QFileInfo(jar[0])));
} }

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include "BaseInstance.h" #include "BaseInstance.h"
#include <java/JavaVersion.h> #include <java/JavaVersion.h>
@ -73,6 +108,8 @@ public:
/** Returns whether the instance, with its version, has support for demo mode. */ /** Returns whether the instance, with its version, has support for demo mode. */
[[nodiscard]] bool supportsDemo() const; [[nodiscard]] bool supportsDemo() const;
void updateRuntimeContext();
////// Profile management ////// ////// Profile management //////
std::shared_ptr<PackProfile> getPackProfile() const; std::shared_ptr<PackProfile> getPackProfile() const;

View File

@ -214,7 +214,7 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFi
QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by %3 (%2). It might not work properly!") QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by %3 (%2). It might not work properly!")
.arg(out->minimumLauncherVersion) .arg(out->minimumLauncherVersion)
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION) .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)
.arg(BuildConfig.LAUNCHER_NAME) .arg(BuildConfig.LAUNCHER_DISPLAYNAME)
); );
} }
} }
@ -362,11 +362,8 @@ LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, con
{ {
qWarning() << filename << "contains an invalid native (skipping)"; qWarning() << filename << "contains an invalid native (skipping)";
} }
OpSys opSys = OpSys_fromString(it.key()); // FIXME: Skip unknown platforms
if (opSys != Os_Other) out->m_nativeClassifiers[it.key()] = it.value().toString();
{
out->m_nativeClassifiers[opSys] = it.value().toString();
}
} }
} }
if (libObj.contains("rules")) if (libObj.contains("rules"))
@ -395,7 +392,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
auto iter = library->m_nativeClassifiers.begin(); auto iter = library->m_nativeClassifiers.begin();
while (iter != library->m_nativeClassifiers.end()) while (iter != library->m_nativeClassifiers.end())
{ {
nativeList.insert(OpSys_toString(iter.key()), iter.value()); nativeList.insert(iter.key(), iter.value());
iter++; iter++;
} }
libRoot.insert("natives", nativeList); libRoot.insert("natives", nativeList);

View File

@ -1,46 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "OpSys.h"
OpSys OpSys_fromString(QString name)
{
if (name == "freebsd")
return Os_FreeBSD;
if (name == "linux")
return Os_Linux;
if (name == "windows")
return Os_Windows;
if (name == "osx")
return Os_OSX;
return Os_Other;
}
QString OpSys_toString(OpSys name)
{
switch (name)
{
case Os_FreeBSD:
return "freebsd";
case Os_Linux:
return "linux";
case Os_OSX:
return "osx";
case Os_Windows:
return "windows";
default:
return "other";
}
}

View File

@ -1,38 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QString>
enum OpSys
{
Os_Windows,
Os_FreeBSD,
Os_Linux,
Os_OSX,
Os_Other
};
OpSys OpSys_fromString(QString);
QString OpSys_toString(OpSys);
#ifdef Q_OS_WIN32
#define currentSystem Os_Windows
#elif defined Q_OS_MAC
#define currentSystem Os_OSX
#elif defined Q_OS_FREEBSD
#define currentSystem Os_FreeBSD
#else
#define currentSystem Os_Linux
#endif

View File

@ -273,6 +273,11 @@ void PackProfile::scheduleSave()
d->m_saveTimer.start(); d->m_saveTimer.start();
} }
RuntimeContext PackProfile::runtimeContext()
{
return d->m_instance->runtimeContext();
}
QString PackProfile::componentsFilePath() const QString PackProfile::componentsFilePath() const
{ {
return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
@ -784,7 +789,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
return true; return true;
} }
QStringList jar, temp1, temp2, temp3; QStringList jar, temp1, temp2, temp3;
jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); jarMod->getApplicableFiles(d->m_instance->runtimeContext(), jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
QFileInfo finfo (jar[0]); QFileInfo finfo (jar[0]);
if(finfo.exists()) if(finfo.exists())
{ {

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -104,6 +124,9 @@ public:
/// if there is a save scheduled, do it now. /// if there is a save scheduled, do it now.
void saveNow(); void saveNow();
/// helper method, returns RuntimeContext of instance
RuntimeContext runtimeContext();
signals: signals:
void minecraftChanged(); void minecraftChanged();

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -60,10 +80,10 @@ QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
auto osNameVal = osObj.value("name"); auto osNameVal = osObj.value("name");
if (!osNameVal.isString()) if (!osNameVal.isString())
continue; continue;
OpSys requiredOs = OpSys_fromString(osNameVal.toString()); QString osName = osNameVal.toString();
QString versionRegex = osObj.value("version").toString(); QString versionRegex = osObj.value("version").toString();
// add a new OS rule // add a new OS rule
rules.append(OsRule::create(action, requiredOs, versionRegex)); rules.append(OsRule::create(action, osName, versionRegex));
} }
return rules; return rules;
} }
@ -81,7 +101,7 @@ QJsonObject OsRule::toJson()
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
QJsonObject osObj; QJsonObject osObj;
{ {
osObj.insert("name", OpSys_toString(m_system)); osObj.insert("name", m_system);
if(!m_version_regexp.isEmpty()) if(!m_version_regexp.isEmpty())
{ {
osObj.insert("version", m_version_regexp); osObj.insert("version", m_version_regexp);

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,7 +39,7 @@
#include <QList> #include <QList>
#include <QJsonObject> #include <QJsonObject>
#include <memory> #include <memory>
#include "OpSys.h" #include "RuntimeContext.h"
class Library; class Library;
class Rule; class Rule;
@ -37,7 +57,7 @@ class Rule
{ {
protected: protected:
RuleAction m_result; RuleAction m_result;
virtual bool applies(const Library *parent) = 0; virtual bool applies(const Library *parent, const RuntimeContext & runtimeContext) = 0;
public: public:
Rule(RuleAction result) : m_result(result) Rule(RuleAction result) : m_result(result)
@ -45,9 +65,9 @@ public:
} }
virtual ~Rule() {}; virtual ~Rule() {};
virtual QJsonObject toJson() = 0; virtual QJsonObject toJson() = 0;
RuleAction apply(const Library *parent) RuleAction apply(const Library *parent, const RuntimeContext & runtimeContext)
{ {
if (applies(parent)) if (applies(parent, runtimeContext))
return m_result; return m_result;
else else
return Defer; return Defer;
@ -58,23 +78,23 @@ class OsRule : public Rule
{ {
private: private:
// the OS // the OS
OpSys m_system; QString m_system;
// the OS version regexp // the OS version regexp
QString m_version_regexp; QString m_version_regexp;
protected: protected:
virtual bool applies(const Library *) virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
{ {
return (m_system == currentSystem); return runtimeContext.classifierMatches(m_system);
} }
OsRule(RuleAction result, OpSys system, QString version_regexp) OsRule(RuleAction result, QString system, QString version_regexp)
: Rule(result), m_system(system), m_version_regexp(version_regexp) : Rule(result), m_system(system), m_version_regexp(version_regexp)
{ {
} }
public: public:
virtual QJsonObject toJson(); virtual QJsonObject toJson();
static std::shared_ptr<OsRule> create(RuleAction result, OpSys system, static std::shared_ptr<OsRule> create(RuleAction result, QString system,
QString version_regexp) QString version_regexp)
{ {
return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp)); return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp));
@ -84,7 +104,7 @@ public:
class ImplicitRule : public Rule class ImplicitRule : public Rule
{ {
protected: protected:
virtual bool applies(const Library *) virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
{ {
return true; return true;
} }

View File

@ -51,7 +51,7 @@ static bool isMinecraftVersion(const QString &uid)
return uid == "net.minecraft"; return uid == "net.minecraft";
} }
void VersionFile::applyTo(LaunchProfile *profile) void VersionFile::applyTo(LaunchProfile *profile, const RuntimeContext & runtimeContext)
{ {
// Only real Minecraft can set those. Don't let anything override them. // Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid)) if (isMinecraftVersion(uid))
@ -77,15 +77,15 @@ void VersionFile::applyTo(LaunchProfile *profile)
for (auto library : libraries) for (auto library : libraries)
{ {
profile->applyLibrary(library); profile->applyLibrary(library, runtimeContext);
} }
for (auto mavenFile : mavenFiles) for (auto mavenFile : mavenFiles)
{ {
profile->applyMavenFile(mavenFile); profile->applyMavenFile(mavenFile, runtimeContext);
} }
for (auto agent : agents) for (auto agent : agents)
{ {
profile->applyAgent(agent); profile->applyAgent(agent, runtimeContext);
} }
profile->applyProblemSeverity(getProblemSeverity()); profile->applyProblemSeverity(getProblemSeverity());
} }

View File

@ -41,7 +41,6 @@
#include <QSet> #include <QSet>
#include <memory> #include <memory>
#include "minecraft/OpSys.h"
#include "minecraft/Rule.h" #include "minecraft/Rule.h"
#include "ProblemProvider.h" #include "ProblemProvider.h"
#include "Library.h" #include "Library.h"
@ -60,22 +59,22 @@ class VersionFile : public ProblemContainer
friend class MojangVersionFormat; friend class MojangVersionFormat;
friend class OneSixVersionFormat; friend class OneSixVersionFormat;
public: /* methods */ public: /* methods */
void applyTo(LaunchProfile* profile); void applyTo(LaunchProfile* profile, const RuntimeContext & runtimeContext);
public: /* data */ public: /* data */
/// PolyMC: order hint for this version file if no explicit order is set /// Prism Launcher: order hint for this version file if no explicit order is set
int order = 0; int order = 0;
/// PolyMC: human readable name of this package /// Prism Launcher: human readable name of this package
QString name; QString name;
/// PolyMC: package ID of this package /// Prism Launcher: package ID of this package
QString uid; QString uid;
/// PolyMC: version of this package /// Prism Launcher: version of this package
QString version; QString version;
/// PolyMC: DEPRECATED dependency on a Minecraft version /// Prism Launcher: DEPRECATED dependency on a Minecraft version
QString dependsOnMinecraftVersion; QString dependsOnMinecraftVersion;
/// Mojang: DEPRECATED used to version the Mojang version format /// Mojang: DEPRECATED used to version the Mojang version format
@ -87,13 +86,13 @@ public: /* data */
/// Mojang: class to launch Minecraft with /// Mojang: class to launch Minecraft with
QString mainClass; QString mainClass;
/// PolyMC: class to launch legacy Minecraft with (embed in a custom window) /// Prism Launcher: class to launch legacy Minecraft with (embed in a custom window)
QString appletClass; QString appletClass;
/// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution)
QString minecraftArguments; QString minecraftArguments;
/// PolyMC: Additional JVM launch arguments /// Prism Launcher: Additional JVM launch arguments
QStringList addnJvmArguments; QStringList addnJvmArguments;
/// Mojang: list of compatible java majors /// Mojang: list of compatible java majors
@ -111,38 +110,38 @@ public: /* data */
/// Mojang: DEPRECATED asset group to be used with Minecraft /// Mojang: DEPRECATED asset group to be used with Minecraft
QString assets; QString assets;
/// PolyMC: list of tweaker mod arguments for launchwrapper /// Prism Launcher: list of tweaker mod arguments for launchwrapper
QStringList addTweakers; QStringList addTweakers;
/// Mojang: list of libraries to add to the version /// Mojang: list of libraries to add to the version
QList<LibraryPtr> libraries; QList<LibraryPtr> libraries;
/// PolyMC: list of maven files to put in the libraries folder, but not in classpath /// Prism Launcher: list of maven files to put in the libraries folder, but not in classpath
QList<LibraryPtr> mavenFiles; QList<LibraryPtr> mavenFiles;
/// PolyMC: list of agents to add to JVM arguments /// Prism Launcher: list of agents to add to JVM arguments
QList<AgentPtr> agents; QList<AgentPtr> agents;
/// The main jar (Minecraft version library, normally) /// The main jar (Minecraft version library, normally)
LibraryPtr mainJar; LibraryPtr mainJar;
/// PolyMC: list of attached traits of this version file - used to enable features /// Prism Launcher: list of attached traits of this version file - used to enable features
QSet<QString> traits; QSet<QString> traits;
/// PolyMC: list of jar mods added to this version /// Prism Launcher: list of jar mods added to this version
QList<LibraryPtr> jarMods; QList<LibraryPtr> jarMods;
/// PolyMC: list of mods added to this version /// Prism Launcher: list of mods added to this version
QList<LibraryPtr> mods; QList<LibraryPtr> mods;
/** /**
* PolyMC: set of packages this depends on * Prism Launcher: set of packages this depends on
* NOTE: this is shared with the meta format!!! * NOTE: this is shared with the meta format!!!
*/ */
Meta::RequireSet requires; Meta::RequireSet requires;
/** /**
* PolyMC: set of packages this conflicts with * Prism Launcher: set of packages this conflicts with
* NOTE: this is shared with the meta format!!! * NOTE: this is shared with the meta format!!!
*/ */
Meta::RequireSet conflicts; Meta::RequireSet conflicts;

View File

@ -44,7 +44,7 @@
/*! /*!
* List of available Mojang accounts. * List of available Mojang accounts.
* This should be loaded in the background by PolyMC on startup. * This should be loaded in the background by Prism Launcher on startup.
*/ */
class AccountList : public QAbstractListModel class AccountList : public QAbstractListModel
{ {

View File

@ -61,7 +61,7 @@ Q_DECLARE_METATYPE(MinecraftAccountPtr)
* A profile within someone's Mojang account. * A profile within someone's Mojang account.
* *
* Currently, the profile system has not been implemented by Mojang yet, * Currently, the profile system has not been implemented by Mojang yet,
* but we might as well add some things for it in PolyMC right now so * but we might as well add some things for it in Prism Launcher right now so
* we don't have to rip the code to pieces to add it later. * we don't have to rip the code to pieces to add it later.
*/ */
struct AccountProfile struct AccountProfile

View File

@ -154,7 +154,7 @@ void LauncherPartLaunch::executeTask()
#else #else
args << classPath.join(':'); args << classPath.join(':');
#endif #endif
args << "org.polymc.EntryPoint"; args << "org.prismlauncher.EntryPoint";
qDebug() << args.join(' '); qDebug() << args.join(' ');

View File

@ -23,7 +23,7 @@ MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) {
// The logic below replicates the exact logic minecraft uses for parsing server addresses. // The logic below replicates the exact logic minecraft uses for parsing server addresses.
// While the conversion is not lossless and eats errors, it ensures the same behavior // While the conversion is not lossless and eats errors, it ensures the same behavior
// within Minecraft and PolyMC when entering server addresses. // within Minecraft and Prism Launcher when entering server addresses.
if (fullAddress.startsWith("[")) if (fullAddress.startsWith("["))
{ {
int bracket = fullAddress.indexOf("]"); int bracket = fullAddress.indexOf("]");

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,7 +36,6 @@
#include "ModMinecraftJar.h" #include "ModMinecraftJar.h"
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
#include "MMCZip.h" #include "MMCZip.h"
#include "minecraft/OpSys.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
@ -50,7 +69,7 @@ void ModMinecraftJar::executeTask()
{ {
auto mainJar = profile->getMainJar(); auto mainJar = profile->getMainJar();
QStringList jars, temp1, temp2, temp3, temp4; QStringList jars, temp1, temp2, temp3, temp4;
mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); mainJar->getApplicableFiles(m_inst->runtimeContext(), jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath());
auto sourceJarPath = jars[0]; auto sourceJarPath = jars[0];
if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods))
{ {

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,7 +36,6 @@
#include "ScanModFolders.h" #include "ScanModFolders.h"
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
#include "MMCZip.h" #include "MMCZip.h"
#include "minecraft/OpSys.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"

View File

@ -1,17 +1,17 @@
#include "LocalModParseTask.h" #include "LocalModParseTask.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QString>
#include <quazip/quazip.h> #include <quazip/quazip.h>
#include <quazip/quazipfile.h> #include <quazip/quazipfile.h>
#include <toml.h> #include <toml++/toml.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QString>
#include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "settings/INIFile.h" #include "settings/INIFile.h"
#include "FileSystem.h"
namespace { namespace {
@ -22,8 +22,7 @@ namespace {
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
ModDetails ReadMCModInfo(QByteArray contents) ModDetails ReadMCModInfo(QByteArray contents)
{ {
auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails {
{
if (!arr.at(0).isObject()) { if (!arr.at(0).isObject()) {
return {}; return {};
} }
@ -32,16 +31,14 @@ ModDetails ReadMCModInfo(QByteArray contents)
details.mod_id = firstObj.value("modid").toString(); details.mod_id = firstObj.value("modid").toString();
auto name = firstObj.value("name").toString(); auto name = firstObj.value("name").toString();
// NOTE: ignore stupid example mods copies where the author didn't even bother to change the name // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name
if(name != "Example Mod") { if (name != "Example Mod") {
details.name = name; details.name = name;
} }
details.version = firstObj.value("version").toString(); details.version = firstObj.value("version").toString();
auto homeurl = firstObj.value("url").toString().trimmed(); auto homeurl = firstObj.value("url").toString().trimmed();
if(!homeurl.isEmpty()) if (!homeurl.isEmpty()) {
{
// fix up url. // fix up url.
if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) {
{
homeurl.prepend("http://"); homeurl.prepend("http://");
} }
} }
@ -53,8 +50,7 @@ ModDetails ReadMCModInfo(QByteArray contents)
authors = firstObj.value("authors").toArray(); authors = firstObj.value("authors").toArray();
} }
for (auto author: authors) for (auto author : authors) {
{
details.authors.append(author.toString()); details.authors.append(author.toString());
} }
return details; return details;
@ -62,14 +58,11 @@ ModDetails ReadMCModInfo(QByteArray contents)
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
// this is the very old format that had just the array // this is the very old format that had just the array
if (jsonDoc.isArray()) if (jsonDoc.isArray()) {
{
return getInfoFromArray(jsonDoc.array()); return getInfoFromArray(jsonDoc.array());
} } else if (jsonDoc.isObject()) {
else if (jsonDoc.isObject())
{
auto val = jsonDoc.object().value("modinfoversion"); auto val = jsonDoc.object().value("modinfoversion");
if(val.isUndefined()) { if (val.isUndefined()) {
val = jsonDoc.object().value("modListVersion"); val = jsonDoc.object().value("modListVersion");
} }
@ -79,18 +72,16 @@ ModDetails ReadMCModInfo(QByteArray contents)
if (version < 0) if (version < 0)
version = Json::ensureString(val, "").toInt(); version = Json::ensureString(val, "").toInt();
if (version != 2) if (version != 2) {
{
qCritical() << "BAD stuff happened to mod json:"; qCritical() << "BAD stuff happened to mod json:";
qCritical() << contents; qCritical() << contents;
return {}; return {};
} }
auto arrVal = jsonDoc.object().value("modlist"); auto arrVal = jsonDoc.object().value("modlist");
if(arrVal.isUndefined()) { if (arrVal.isUndefined()) {
arrVal = jsonDoc.object().value("modList"); arrVal = jsonDoc.object().value("modList");
} }
if (arrVal.isArray()) if (arrVal.isArray()) {
{
return getInfoFromArray(arrVal.toArray()); return getInfoFromArray(arrVal.toArray());
} }
} }
@ -102,109 +93,76 @@ ModDetails ReadMCModTOML(QByteArray contents)
{ {
ModDetails details; ModDetails details;
char errbuf[200]; toml::table tomlData;
// top-level table #if TOML_EXCEPTIONS
toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf)); try {
tomlData = toml::parse(contents.toStdString());
if(!tomlData) } catch (const toml::parse_error& err) {
{
return {}; return {};
} }
#else
tomlData = toml::parse(contents.toStdString());
if (!tomlData) {
return {};
}
#endif
// array defined by [[mods]] // array defined by [[mods]]
toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods"); auto tomlModsArr = tomlData["mods"].as_array();
if(!tomlModsArr) if (!tomlModsArr) {
{
qWarning() << "Corrupted mods.toml? Couldn't find [[mods]] array!"; qWarning() << "Corrupted mods.toml? Couldn't find [[mods]] array!";
return {}; return {};
} }
// we only really care about the first element, since multiple mods in one file is not supported by us at the moment // we only really care about the first element, since multiple mods in one file is not supported by us at the moment
toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0); auto tomlModsTable0 = tomlModsArr->get(0);
if(!tomlModsTable0) if (!tomlModsTable0) {
{
qWarning() << "Corrupted mods.toml? [[mods]] didn't have an element at index 0!"; qWarning() << "Corrupted mods.toml? [[mods]] didn't have an element at index 0!";
return {}; return {};
} }
auto modsTable = tomlModsTable0->as_table();
if (!tomlModsTable0) {
qWarning() << "Corrupted mods.toml? [[mods]] was not a table!";
return {};
}
// mandatory properties - always in [[mods]] // mandatory properties - always in [[mods]]
toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId"); if (auto modIdDatum = (*modsTable)["modId"].as_string()) {
if(modIdDatum.ok) details.mod_id = QString::fromStdString(modIdDatum->get());
{
details.mod_id = modIdDatum.u.s;
// library says this is required for strings
free(modIdDatum.u.s);
} }
toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version"); if (auto versionDatum = (*modsTable)["version"].as_string()) {
if(versionDatum.ok) details.version = QString::fromStdString(versionDatum->get());
{
details.version = versionDatum.u.s;
free(versionDatum.u.s);
} }
toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName"); if (auto displayNameDatum = (*modsTable)["displayName"].as_string()) {
if(displayNameDatum.ok) details.name = QString::fromStdString(displayNameDatum->get());
{
details.name = displayNameDatum.u.s;
free(displayNameDatum.u.s);
} }
toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description"); if (auto descriptionDatum = (*modsTable)["description"].as_string()) {
if(descriptionDatum.ok) details.description = QString::fromStdString(descriptionDatum->get());
{
details.description = descriptionDatum.u.s;
free(descriptionDatum.u.s);
} }
// optional properties - can be in the root table or [[mods]] // optional properties - can be in the root table or [[mods]]
toml_datum_t authorsDatum = toml_string_in(tomlData, "authors");
QString authors = ""; QString authors = "";
if(authorsDatum.ok) if (auto authorsDatum = tomlData["authors"].as_string()) {
{ authors = QString::fromStdString(authorsDatum->get());
authors = authorsDatum.u.s; } else if (auto authorsDatum = (*modsTable)["authors"].as_string()) {
free(authorsDatum.u.s); authors = QString::fromStdString(authorsDatum->get());
} }
else if (!authors.isEmpty()) {
{
authorsDatum = toml_string_in(tomlModsTable0, "authors");
if(authorsDatum.ok)
{
authors = authorsDatum.u.s;
free(authorsDatum.u.s);
}
}
if(!authors.isEmpty())
{
details.authors.append(authors); details.authors.append(authors);
} }
toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
QString homeurl = ""; QString homeurl = "";
if(homeurlDatum.ok) if (auto homeurlDatum = tomlData["displayURL"].as_string()) {
{ homeurl = QString::fromStdString(homeurlDatum->get());
homeurl = homeurlDatum.u.s; } else if (auto homeurlDatum = (*modsTable)["displayURL"].as_string()) {
free(homeurlDatum.u.s); homeurl = QString::fromStdString(homeurlDatum->get());
} }
else
{
homeurlDatum = toml_string_in(tomlModsTable0, "displayURL");
if(homeurlDatum.ok)
{
homeurl = homeurlDatum.u.s;
free(homeurlDatum.u.s);
}
}
if(!homeurl.isEmpty())
{
// fix up url. // fix up url.
if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) if (!homeurl.isEmpty() && !homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) {
{
homeurl.prepend("http://"); homeurl.prepend("http://");
} }
}
details.homeurl = homeurl; details.homeurl = homeurl;
// this seems to be recursive, so it should free everything
toml_free(tomlData);
return details; return details;
} }
@ -224,25 +182,20 @@ ModDetails ReadFabricModInfo(QByteArray contents)
details.name = object.contains("name") ? object.value("name").toString() : details.mod_id; details.name = object.contains("name") ? object.value("name").toString() : details.mod_id;
details.description = object.value("description").toString(); details.description = object.value("description").toString();
if (schemaVersion >= 1) if (schemaVersion >= 1) {
{
QJsonArray authors = object.value("authors").toArray(); QJsonArray authors = object.value("authors").toArray();
for (auto author: authors) for (auto author : authors) {
{ if (author.isObject()) {
if(author.isObject()) {
details.authors.append(author.toObject().value("name").toString()); details.authors.append(author.toObject().value("name").toString());
} } else {
else {
details.authors.append(author.toString()); details.authors.append(author.toString());
} }
} }
if (object.contains("contact")) if (object.contains("contact")) {
{
QJsonObject contact = object.value("contact").toObject(); QJsonObject contact = object.value("contact").toObject();
if (contact.contains("homepage")) if (contact.contains("homepage")) {
{
details.homeurl = contact.value("homepage").toString(); details.homeurl = contact.value("homepage").toString();
} }
} }
@ -261,8 +214,7 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
ModDetails details; ModDetails details;
// https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md
if (schemaVersion == 1) if (schemaVersion == 1) {
{
auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info");
details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID");
@ -280,8 +232,7 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
auto modContact = Json::ensureObject(modMetadata.value("contact")); auto modContact = Json::ensureObject(modMetadata.value("contact"));
if (modContact.contains("homepage")) if (modContact.contains("homepage")) {
{
details.homeurl = Json::requireString(modContact.value("homepage")); details.homeurl = Json::requireString(modContact.value("homepage"));
} }
} }
@ -314,21 +265,17 @@ ModDetails ReadLiteModInfo(QByteArray contents)
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = jsonDoc.object(); auto object = jsonDoc.object();
if (object.contains("name")) if (object.contains("name")) {
{
details.mod_id = details.name = object.value("name").toString(); details.mod_id = details.name = object.value("name").toString();
} }
if (object.contains("version")) if (object.contains("version")) {
{
details.version = object.value("version").toString(""); details.version = object.value("version").toString("");
} } else {
else
{
details.version = object.value("revision").toString(""); details.version = object.value("revision").toString("");
} }
details.mcversion = object.value("mcversion").toString(); details.mcversion = object.value("mcversion").toString();
auto author = object.value("author").toString(); auto author = object.value("author").toString();
if(!author.isEmpty()) { if (!author.isEmpty()) {
details.authors.append(author); details.authors.append(author);
} }
details.description = object.value("description").toString(); details.description = object.value("description").toString();
@ -336,14 +283,10 @@ ModDetails ReadLiteModInfo(QByteArray contents)
return details; return details;
} }
} } // namespace
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile): LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
Task(nullptr, false), : Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
m_token(token),
m_type(type),
m_modFile(modFile),
m_result(new Result())
{} {}
void LocalModParseTask::processAsZip() void LocalModParseTask::processAsZip()
@ -354,10 +297,8 @@ void LocalModParseTask::processAsZip()
QuaZipFile file(&zip); QuaZipFile file(&zip);
if (zip.setCurrentFile("META-INF/mods.toml")) if (zip.setCurrentFile("META-INF/mods.toml")) {
{ if (!file.open(QIODevice::ReadOnly)) {
if (!file.open(QIODevice::ReadOnly))
{
zip.close(); zip.close();
return; return;
} }
@ -366,12 +307,9 @@ void LocalModParseTask::processAsZip()
file.close(); file.close();
// to replace ${file.jarVersion} with the actual version, as needed // to replace ${file.jarVersion} with the actual version, as needed
if (m_result->details.version == "${file.jarVersion}") if (m_result->details.version == "${file.jarVersion}") {
{ if (zip.setCurrentFile("META-INF/MANIFEST.MF")) {
if (zip.setCurrentFile("META-INF/MANIFEST.MF")) if (!file.open(QIODevice::ReadOnly)) {
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close(); zip.close();
return; return;
} }
@ -379,10 +317,8 @@ void LocalModParseTask::processAsZip()
// quick and dirty line-by-line parser // quick and dirty line-by-line parser
auto manifestLines = file.readAll().split('\n'); auto manifestLines = file.readAll().split('\n');
QString manifestVersion = ""; QString manifestVersion = "";
for (auto &line : manifestLines) for (auto& line : manifestLines) {
{ if (QString(line).startsWith("Implementation-Version: ")) {
if (QString(line).startsWith("Implementation-Version: "))
{
manifestVersion = QString(line).remove("Implementation-Version: "); manifestVersion = QString(line).remove("Implementation-Version: ");
break; break;
} }
@ -390,8 +326,7 @@ void LocalModParseTask::processAsZip()
// some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF // some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF
// also keep with forge's behavior of setting the version to "NONE" if none is found // also keep with forge's behavior of setting the version to "NONE" if none is found
if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") {
{
manifestVersion = "NONE"; manifestVersion = "NONE";
} }
@ -403,11 +338,8 @@ void LocalModParseTask::processAsZip()
zip.close(); zip.close();
return; return;
} } else if (zip.setCurrentFile("mcmod.info")) {
else if (zip.setCurrentFile("mcmod.info")) if (!file.open(QIODevice::ReadOnly)) {
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close(); zip.close();
return; return;
} }
@ -416,11 +348,8 @@ void LocalModParseTask::processAsZip()
file.close(); file.close();
zip.close(); zip.close();
return; return;
} } else if (zip.setCurrentFile("quilt.mod.json")) {
else if (zip.setCurrentFile("quilt.mod.json")) if (!file.open(QIODevice::ReadOnly)) {
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close(); zip.close();
return; return;
} }
@ -429,11 +358,8 @@ void LocalModParseTask::processAsZip()
file.close(); file.close();
zip.close(); zip.close();
return; return;
} } else if (zip.setCurrentFile("fabric.mod.json")) {
else if (zip.setCurrentFile("fabric.mod.json")) if (!file.open(QIODevice::ReadOnly)) {
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close(); zip.close();
return; return;
} }
@ -442,11 +368,8 @@ void LocalModParseTask::processAsZip()
file.close(); file.close();
zip.close(); zip.close();
return; return;
} } else if (zip.setCurrentFile("forgeversion.properties")) {
else if (zip.setCurrentFile("forgeversion.properties")) if (!file.open(QIODevice::ReadOnly)) {
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close(); zip.close();
return; return;
} }
@ -463,8 +386,7 @@ void LocalModParseTask::processAsZip()
void LocalModParseTask::processAsFolder() void LocalModParseTask::processAsFolder()
{ {
QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info")); QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info"));
if (mcmod_info.isFile()) if (mcmod_info.isFile()) {
{
QFile mcmod(mcmod_info.filePath()); QFile mcmod(mcmod_info.filePath());
if (!mcmod.open(QIODevice::ReadOnly)) if (!mcmod.open(QIODevice::ReadOnly))
return; return;
@ -483,10 +405,8 @@ void LocalModParseTask::processAsLitemod()
QuaZipFile file(&zip); QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json")) if (zip.setCurrentFile("litemod.json")) {
{ if (!file.open(QIODevice::ReadOnly)) {
if (!file.open(QIODevice::ReadOnly))
{
zip.close(); zip.close();
return; return;
} }
@ -505,8 +425,7 @@ bool LocalModParseTask::abort()
void LocalModParseTask::executeTask() void LocalModParseTask::executeTask()
{ {
switch(m_type) switch (m_type) {
{
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
processAsZip(); processAsZip();
break; break;

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FoldersTask.h" #include "FoldersTask.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include <QDir> #include <QDir>

View File

@ -34,7 +34,7 @@ void LibrariesTask::executeTask()
emitFailed(tr("Null jar is specified in the metadata, aborting.")); emitFailed(tr("Null jar is specified in the metadata, aborting."));
return false; return false;
} }
auto dls = lib->getDownloads(currentSystem, metacache.get(), errors, localPath); auto dls = lib->getDownloads(inst->runtimeContext(), metacache.get(), errors, localPath);
for(auto dl : dls) for(auto dl : dls)
{ {
downloadJob->addNetAction(dl); downloadJob->addNetAction(dl);

View File

@ -9,9 +9,10 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAcc
bool Flame::FileResolvingTask::abort() bool Flame::FileResolvingTask::abort()
{ {
bool aborted = true;
if (m_dljob) if (m_dljob)
return m_dljob->abort(); aborted &= m_dljob->abort();
return true; return aborted ? Task::abort() : false;
} }
void Flame::FileResolvingTask::executeTask() void Flame::FileResolvingTask::executeTask()

View File

@ -65,48 +65,42 @@ void PackInstallTask::executeTask()
void PackInstallTask::downloadPack() void PackInstallTask::downloadPack()
{ {
setStatus(tr("Downloading zip for %1").arg(m_pack.name)); setStatus(tr("Downloading zip for %1").arg(m_pack.name));
setAbortable(false);
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
auto packoffset = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
auto entry = APPLICATION->metacache()->resolveEntry("FTBPacks", packoffset);
netJobContainer = new NetJob("Download FTB Pack", m_network); netJobContainer = new NetJob("Download FTB Pack", m_network);
entry->setStale(true);
QString url; QString url;
if(m_pack.type == PackType::Private) if (m_pack.type == PackType::Private) {
{ url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(archivePath);
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset); } else {
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
} }
else netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath));
{
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset);
}
netJobContainer->addNetAction(Net::Download::makeCached(url, entry));
archivePath = entry->getFullPath();
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
netJobContainer->start(); netJobContainer->start();
setAbortable(true);
progress(1, 4); progress(1, 4);
} }
void PackInstallTask::onDownloadSucceeded() void PackInstallTask::onDownloadSucceeded()
{ {
abortable = false;
unzip(); unzip();
} }
void PackInstallTask::onDownloadFailed(QString reason) void PackInstallTask::onDownloadFailed(QString reason)
{ {
abortable = false;
emitFailed(reason); emitFailed(reason);
} }
void PackInstallTask::onDownloadProgress(qint64 current, qint64 total) void PackInstallTask::onDownloadProgress(qint64 current, qint64 total)
{ {
abortable = true;
progress(current, total * 4); progress(current, total * 4);
setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10));
} }
@ -118,8 +112,10 @@ void PackInstallTask::onDownloadAborted()
void PackInstallTask::unzip() void PackInstallTask::unzip()
{ {
progress(2, 4);
setStatus(tr("Extracting modpack")); setStatus(tr("Extracting modpack"));
setAbortable(false);
progress(2, 4);
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
m_packZip.reset(new QuaZip(archivePath)); m_packZip.reset(new QuaZip(archivePath));
@ -151,8 +147,8 @@ void PackInstallTask::onUnzipCanceled()
void PackInstallTask::install() void PackInstallTask::install()
{ {
progress(3, 4);
setStatus(tr("Installing modpack")); setStatus(tr("Installing modpack"));
progress(3, 4);
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
if(unzipMcDir.exists()) if(unzipMcDir.exists())
{ {
@ -247,11 +243,12 @@ void PackInstallTask::install()
bool PackInstallTask::abort() bool PackInstallTask::abort()
{ {
if(abortable) if (!canAbort()) {
{
return netJobContainer->abort();
}
return false; return false;
}
netJobContainer->abort();
return InstanceTask::abort();
} }
} }

View File

@ -58,6 +58,9 @@ PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent)
bool PackInstallTask::abort() bool PackInstallTask::abort()
{ {
if (!canAbort())
return false;
bool aborted = true; bool aborted = true;
if (m_net_job) if (m_net_job)
@ -65,15 +68,13 @@ bool PackInstallTask::abort()
if (m_mod_id_resolver_task) if (m_mod_id_resolver_task)
aborted &= m_mod_id_resolver_task->abort(); aborted &= m_mod_id_resolver_task->abort();
if (aborted) return aborted ? InstanceTask::abort() : false;
emitAborted();
return aborted;
} }
void PackInstallTask::executeTask() void PackInstallTask::executeTask()
{ {
setStatus(tr("Getting the manifest...")); setStatus(tr("Getting the manifest..."));
setAbortable(false);
// Find pack version // Find pack version
auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(), auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(),
@ -93,10 +94,12 @@ void PackInstallTask::executeTask()
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded); QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded);
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed);
QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::abort);
QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress); QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress);
m_net_job = netJob; m_net_job = netJob;
setAbortable(true);
netJob->start(); netJob->start();
} }
@ -130,6 +133,7 @@ void PackInstallTask::onManifestDownloadSucceeded()
void PackInstallTask::resolveMods() void PackInstallTask::resolveMods()
{ {
setStatus(tr("Resolving mods...")); setStatus(tr("Resolving mods..."));
setAbortable(false);
setProgress(0, 100); setProgress(0, 100);
m_file_id_map.clear(); m_file_id_map.clear();
@ -162,15 +166,16 @@ void PackInstallTask::resolveMods()
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded); connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded);
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed); connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed);
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::aborted, this, &PackInstallTask::abort);
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress);
setAbortable(true);
m_mod_id_resolver_task->start(); m_mod_id_resolver_task->start();
} }
void PackInstallTask::onResolveModsSucceeded() void PackInstallTask::onResolveModsSucceeded()
{ {
m_abortable = false;
QString text; QString text;
QList<QUrl> urls; QList<QUrl> urls;
auto anyBlocked = false; auto anyBlocked = false;
@ -209,94 +214,23 @@ void PackInstallTask::onResolveModsSucceeded()
urls); urls);
if (message_dialog->exec() == QDialog::Accepted) if (message_dialog->exec() == QDialog::Accepted)
downloadPack(); createInstance();
else else
abort(); abort();
} else { } else {
downloadPack(); createInstance();
} }
} }
void PackInstallTask::downloadPack() void PackInstallTask::createInstance()
{ {
setStatus(tr("Downloading mods...")); setAbortable(false);
auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); setStatus(tr("Creating the instance..."));
for (auto const& file : m_version.files) {
if (file.serverOnly || file.url.isEmpty())
continue;
QFileInfo file_info(file.name);
auto cacheName = file_info.completeBaseName() + "-" + file.sha1 + "." + file_info.suffix();
auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName);
entry->setStale(true);
auto relpath = FS::PathCombine("minecraft", file.path, file.name);
auto path = FS::PathCombine(m_stagingPath, relpath);
if (m_files_to_copy.contains(path)) {
qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading.";
continue;
}
qDebug() << "Will download" << file.url << "to" << path;
m_files_to_copy[path] = entry->getFullPath();
auto dl = Net::Download::makeCached(file.url, entry);
if (!file.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
}
jobPtr->addNetAction(dl);
}
connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded);
connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed);
connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress);
m_net_job = jobPtr;
jobPtr->start();
m_abortable = true;
}
void PackInstallTask::onModDownloadSucceeded()
{
m_net_job.reset();
install();
}
void PackInstallTask::install()
{
setStatus(tr("Copying modpack files..."));
setProgress(0, m_files_to_copy.size());
QCoreApplication::processEvents();
m_abortable = false;
int i = 0;
for (auto iter = m_files_to_copy.constBegin(); iter != m_files_to_copy.constEnd(); iter++) {
auto& to = iter.key();
auto& from = iter.value();
FS::copy fileCopyOperation(from, to);
if (!fileCopyOperation()) {
qWarning() << "Failed to copy" << from << "to" << to;
emitFailed(tr("Failed to copy files"));
return;
}
setProgress(i++, m_files_to_copy.size());
QCoreApplication::processEvents();
}
setStatus(tr("Installing modpack..."));
QCoreApplication::processEvents(); QCoreApplication::processEvents();
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
instanceSettings->suspendSave();
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto components = instance.getPackProfile(); auto components = instance.getPackProfile();
@ -337,8 +271,55 @@ void PackInstallTask::install()
instance.setName(name()); instance.setName(name());
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name); instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);
instanceSettings->resumeSave();
instance.saveNow();
onCreateInstanceSucceeded();
}
void PackInstallTask::onCreateInstanceSucceeded()
{
downloadPack();
}
void PackInstallTask::downloadPack()
{
setStatus(tr("Downloading mods..."));
setAbortable(false);
auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network());
for (auto const& file : m_version.files) {
if (file.serverOnly || file.url.isEmpty())
continue;
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path, file.name);
qDebug() << "Will try to download" << file.url << "to" << path;
QFileInfo file_info(file.name);
auto dl = Net::Download::makeFile(file.url, path);
if (!file.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
}
jobPtr->addNetAction(dl);
}
connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded);
connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed);
connect(jobPtr, &NetJob::aborted, this, &PackInstallTask::abort);
connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress);
m_net_job = jobPtr;
setAbortable(true);
jobPtr->start();
}
void PackInstallTask::onModDownloadSucceeded()
{
m_net_job.reset();
emitSucceeded(); emitSucceeded();
} }
@ -352,6 +333,10 @@ void PackInstallTask::onResolveModsFailed(QString reason)
m_net_job.reset(); m_net_job.reset();
emitFailed(reason); emitFailed(reason);
} }
void PackInstallTask::onCreateInstanceFailed(QString reason)
{
emitFailed(reason);
}
void PackInstallTask::onModDownloadFailed(QString reason) void PackInstallTask::onModDownloadFailed(QString reason)
{ {
m_net_job.reset(); m_net_job.reset();

View File

@ -56,7 +56,6 @@ public:
explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr); explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr);
~PackInstallTask() override = default; ~PackInstallTask() override = default;
bool canAbort() const override { return m_abortable; }
bool abort() override; bool abort() override;
protected: protected:
@ -65,20 +64,20 @@ protected:
private slots: private slots:
void onManifestDownloadSucceeded(); void onManifestDownloadSucceeded();
void onResolveModsSucceeded(); void onResolveModsSucceeded();
void onCreateInstanceSucceeded();
void onModDownloadSucceeded(); void onModDownloadSucceeded();
void onManifestDownloadFailed(QString reason); void onManifestDownloadFailed(QString reason);
void onResolveModsFailed(QString reason); void onResolveModsFailed(QString reason);
void onCreateInstanceFailed(QString reason);
void onModDownloadFailed(QString reason); void onModDownloadFailed(QString reason);
private: private:
void resolveMods(); void resolveMods();
void createInstance();
void downloadPack(); void downloadPack();
void install();
private: private:
bool m_abortable = true;
NetJob::Ptr m_net_job = nullptr; NetJob::Ptr m_net_job = nullptr;
shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver_task = nullptr; shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver_task = nullptr;

View File

@ -1,20 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3. * the Free Software Foundation, version 3.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "Packwiz.h" #include "Packwiz.h"
@ -22,9 +22,7 @@
#include <QDir> #include <QDir>
#include <QObject> #include <QObject>
#include "toml.h" #include <toml++/toml.h>
#include "FileSystem.h"
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
@ -44,7 +42,7 @@ auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_fin
} }
} }
if(should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)){ if (should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)) {
qCritical() << "Could not find a match for a valid metadata file!"; qCritical() << "Could not find a match for a valid metadata file!";
qCritical() << "File: " << normalized_fname; qCritical() << "File: " << normalized_fname;
return {}; return {};
@ -57,7 +55,7 @@ auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_fin
// Helpers // Helpers
static inline auto indexFileName(QString const& mod_slug) -> QString static inline auto indexFileName(QString const& mod_slug) -> QString
{ {
if(mod_slug.endsWith(".pw.toml")) if (mod_slug.endsWith(".pw.toml"))
return mod_slug; return mod_slug;
return QString("%1.pw.toml").arg(mod_slug); return QString("%1.pw.toml").arg(mod_slug);
} }
@ -65,32 +63,28 @@ static inline auto indexFileName(QString const& mod_slug) -> QString
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
// Helper functions for extracting data from the TOML file // Helper functions for extracting data from the TOML file
auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString auto stringEntry(toml::table table, const std::string entry_name) -> QString
{ {
toml_datum_t var = toml_string_in(parent, entry_name); auto node = table[entry_name];
if (!var.ok) { if (!node) {
qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name); qCritical() << QString::fromStdString("Failed to read str property '" + entry_name + "' in mod metadata.");
return {}; return {};
} }
QString tmp = var.u.s; return QString::fromStdString(node.value_or(""));
free(var.u.s);
return tmp;
} }
auto intEntry(toml_table_t* parent, const char* entry_name) -> int auto intEntry(toml::table table, const std::string entry_name) -> int
{ {
toml_datum_t var = toml_int_in(parent, entry_name); auto node = table[entry_name];
if (!var.ok) { if (!node) {
qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name); qCritical() << QString::fromStdString("Failed to read int property '" + entry_name + "' in mod metadata.");
return {}; return {};
} }
return var.u.i; return node.value_or(0);
} }
auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod
{ {
Mod mod; Mod mod;
@ -99,10 +93,9 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo
mod.name = mod_pack.name; mod.name = mod_pack.name;
mod.filename = mod_version.fileName; mod.filename = mod_version.fileName;
if(mod_pack.provider == ModPlatform::Provider::FLAME){ if (mod_pack.provider == ModPlatform::Provider::FLAME) {
mod.mode = "metadata:curseforge"; mod.mode = "metadata:curseforge";
} } else {
else {
mod.mode = "url"; mod.mode = "url";
mod.url = mod_version.downloadUrl; mod.url = mod_version.downloadUrl;
} }
@ -120,8 +113,8 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo
auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod
{ {
// Try getting metadata if it exists // Try getting metadata if it exists
Mod mod { getIndexForMod(index_dir, slug) }; Mod mod{ getIndexForMod(index_dir, slug) };
if(mod.isValid()) if (mod.isValid())
return mod; return mod;
qWarning() << QString("Tried to create mod metadata with a Mod without metadata!"); qWarning() << QString("Tried to create mod metadata with a Mod without metadata!");
@ -131,7 +124,7 @@ auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) ->
void V1::updateModIndex(QDir& index_dir, Mod& mod) void V1::updateModIndex(QDir& index_dir, Mod& mod)
{ {
if(!mod.isValid()){ if (!mod.isValid()) {
qCritical() << QString("Tried to update metadata of an invalid mod!"); qCritical() << QString("Tried to update metadata of an invalid mod!");
return; return;
} }
@ -150,7 +143,9 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
// TODO: We should do more stuff here, as the user is likely trying to // TODO: We should do more stuff here, as the user is likely trying to
// override a file. In this case, check versions and ask the user what // override a file. In this case, check versions and ask the user what
// they want to do! // they want to do!
if (index_file.exists()) { index_file.remove(); } if (index_file.exists()) {
index_file.remove();
}
if (!index_file.open(QIODevice::ReadWrite)) { if (!index_file.open(QIODevice::ReadWrite)) {
qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name)); qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name));
@ -174,12 +169,12 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
in_stream << QString("\n[update]\n"); in_stream << QString("\n[update]\n");
in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider)); in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider));
switch(mod.provider){ switch (mod.provider) {
case(ModPlatform::Provider::FLAME): case (ModPlatform::Provider::FLAME):
in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); in_stream << QString("file-id = %1\n").arg(mod.file_id.toString());
in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); in_stream << QString("project-id = %1\n").arg(mod.project_id.toString());
break; break;
case(ModPlatform::Provider::MODRINTH): case (ModPlatform::Provider::MODRINTH):
addToStream("mod-id", mod.mod_id().toString()); addToStream("mod-id", mod.mod_id().toString());
addToStream("version", mod.version().toString()); addToStream("version", mod.version().toString());
break; break;
@ -230,27 +225,25 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
if (real_fname.isEmpty()) if (real_fname.isEmpty())
return {}; return {};
QFile index_file(index_dir.absoluteFilePath(real_fname)); toml::table table;
#if TOML_EXCEPTIONS
if (!index_file.open(QIODevice::ReadOnly)) { try {
qWarning() << QString("Failed to open mod metadata for %1").arg(slug); table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString());
} catch (const toml::parse_error& err) {
qWarning() << QString("Could not open file %1!").arg(normalized_fname);
qWarning() << "Reason: " << QString(err.what());
return {}; return {};
} }
#else
toml_table_t* table = nullptr; table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString());
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
char errbuf[200];
auto file_bytearray = index_file.readAll();
table = toml_parse(file_bytearray.data(), errbuf, sizeof(errbuf));
index_file.close();
if (!table) { if (!table) {
qWarning() << QString("Could not open file %1!").arg(normalized_fname); qWarning() << QString("Could not open file %1!").arg(normalized_fname);
qWarning() << "Reason: " << QString(errbuf); qWarning() << "Reason: " << QString(table.error().what());
return {}; return {};
} }
#endif
// index_file.close();
mod.slug = slug; mod.slug = slug;
@ -261,45 +254,42 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
} }
{ // [download] info { // [download] info
toml_table_t* download_table = toml_table_in(table, "download"); auto download_table = table["download"].as_table();
if (!download_table) { if (!download_table) {
qCritical() << QString("No [download] section found on mod metadata!"); qCritical() << QString("No [download] section found on mod metadata!");
return {}; return {};
} }
mod.mode = stringEntry(download_table, "mode"); mod.mode = stringEntry(*download_table, "mode");
mod.url = stringEntry(download_table, "url"); mod.url = stringEntry(*download_table, "url");
mod.hash_format = stringEntry(download_table, "hash-format"); mod.hash_format = stringEntry(*download_table, "hash-format");
mod.hash = stringEntry(download_table, "hash"); mod.hash = stringEntry(*download_table, "hash");
} }
{ // [update] info { // [update] info
using Provider = ModPlatform::Provider; using Provider = ModPlatform::Provider;
toml_table_t* update_table = toml_table_in(table, "update"); auto update_table = table["update"];
if (!update_table) { if (!update_table || !update_table.is_table()) {
qCritical() << QString("No [update] section found on mod metadata!"); qCritical() << QString("No [update] section found on mod metadata!");
return {}; return {};
} }
toml_table_t* mod_provider_table = nullptr; toml::table* mod_provider_table = nullptr;
if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::FLAME)))) { if ((mod_provider_table = update_table[ProviderCaps.name(Provider::FLAME)].as_table())) {
mod.provider = Provider::FLAME; mod.provider = Provider::FLAME;
mod.file_id = intEntry(mod_provider_table, "file-id"); mod.file_id = intEntry(*mod_provider_table, "file-id");
mod.project_id = intEntry(mod_provider_table, "project-id"); mod.project_id = intEntry(*mod_provider_table, "project-id");
} else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::MODRINTH)))) { } else if ((mod_provider_table = update_table[ProviderCaps.name(Provider::MODRINTH)].as_table())) {
mod.provider = Provider::MODRINTH; mod.provider = Provider::MODRINTH;
mod.mod_id() = stringEntry(mod_provider_table, "mod-id"); mod.mod_id() = stringEntry(*mod_provider_table, "mod-id");
mod.version() = stringEntry(mod_provider_table, "version"); mod.version() = stringEntry(*mod_provider_table, "version");
} else { } else {
qCritical() << QString("No mod provider on mod metadata!"); qCritical() << QString("No mod provider on mod metadata!");
return {}; return {};
} }
} }
toml_free(table);
return mod; return mod;
} }

View File

@ -162,6 +162,18 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
return true; return true;
} }
void HttpMetaCache::evictAll()
{
for (QString& base : m_entries.keys()) {
EntryMap& map = m_entries[base];
qDebug() << "Evicting base" << base;
for (MetaEntryPtr entry : map.entry_list) {
if (!evictEntry(entry))
qWarning() << "Unexpected missing cache entry" << entry->basePath;
}
}
}
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
{ {
auto foo = new MetaEntry(); auto foo = new MetaEntry();

View File

@ -113,6 +113,7 @@ class HttpMetaCache : public QObject {
// evict selected entry from cache // evict selected entry from cache
auto evictEntry(MetaEntryPtr entry) -> bool; auto evictEntry(MetaEntryPtr entry) -> bool;
void evictAll();
void addBase(QString base, QString base_root); void addBase(QString base, QString base_root);

View File

@ -311,6 +311,6 @@
<file>scalable/instances/fox.svg</file> <file>scalable/instances/fox.svg</file>
<file>scalable/instances/bee.svg</file> <file>scalable/instances/bee.svg</file>
<file>scalable/instances/polymc.svg</file> <file>scalable/instances/prismlauncher.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="linearGradient84726" x1="4.4979" x2="12.435" y1="3.8011" y2="9.5681" gradientUnits="userSpaceOnUse">
<stop stop-color="#88b858" offset="0"/>
<stop stop-color="#72b147" offset=".5"/>
<stop stop-color="#5a9a30" offset="1"/>
</linearGradient>
</defs>
<g>
<path d="m3.561 16.016s0-3.5642 4.9056-3.5642c4.9069 0 4.9056 3.5642 4.9056 3.5642z" fill="#765338"/>
<path d="m8.4667 12.452-4.9056 3.5642-3.0319-9.3311z" fill="#b7835a"/>
<path d="m8.4667 12.452 7.9375-5.7669-3.0319 9.3311z" fill="#5b422d"/>
<path d="m8.8308 12.716-0.36417 0.26458-0.36417-0.26458c0-0.26458 0.36417-0.26458 0.36417-0.26458s0.36417 0 0.36417 0.26458z" fill="#72b147"/>
<path d="m8.4667 12.452s-2e-7 -5.7669 7.9375-5.7669l-0.22507 0.69269-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819z" fill="#5a9a30"/>
<path d="m8.1025 12.716-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.22507-0.69269c7.9375 1e-7 7.9375 5.7669 7.9375 5.7669z" fill="#88b858"/>
<path d="m0.52917 6.6846 7.9375 5.7669 7.9375-5.7669-7.9375-5.7669z" fill="url(#linearGradient84726)"/>
</g>
<path d="m0.75424 7.3773-0.22507-0.69269 7.9375 5.7669 7.9375-5.7669-0.22507 0.69269-7.7124 5.6034z" fill-opacity="0"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="512" height="512" version="1.1" viewBox="0 0 135.47 135.47" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1.3386 0 0 1.3386 16.155 10.174)">
<path d="m18.78 50.126c0 1.342 0.356 2.6345 1.0008 3.758l-18.279 10.617c-0.96723-1.6842-1.5023-3.6252-1.5023-5.6404v-31.721c0-2.0152 0.53511-3.9517 1.5001-5.6382l18.281 10.614c-0.6448 1.1235-1.0008 2.416-1.0008 3.7602z" fill="#7a573b"/>
<path d="m77.065 27.139v15.861h-18.78v-7.1243c0-1.342-0.3559-2.6367-1.003-3.7602l18.279-10.614c0.9694 1.6843 1.5023 3.6252 1.5023 5.6404z" fill="#f3db6c"/>
<path d="m77.065 43v15.861c0 2.0152-0.5351 3.9562-1.5023 5.6404l-17.278-10.031-1.003-0.5832c0.6471-1.1236 1.003-2.4183 1.003-3.7603v-7.1265z" fill="#7ab392"/>
<path d="m75.563 64.501c-0.9695 1.6843-2.3711 3.1208-4.1062 4.1296l-13.658 7.9303-9.3924-16.356 6.1392-3.5644c1.1553-0.6733 2.089-1.628 2.736-2.7516l18.279 10.614z" fill="#4b7cbc"/>
<path d="m57.799 76.559-13.658 7.9303c-1.7352 1.0065-3.6719 1.5109-5.6086 1.5109v-21.226c1.2896 0 2.5792-0.3355 3.739-1.0088l6.1348-3.5644 9.3924 16.356z" fill="#6f488c"/>
<path d="m57.799 9.4412-9.3924 16.356-6.1348-3.5644c-1.1598-0.6732-2.4494-1.0065-3.739-1.0065v-21.226c1.9367 0 3.8734 0.50437 5.6086 1.5109z" fill="#df6277"/>
<path d="m38.532 0v21.226c-1.2896 0-2.5793 0.3333-3.7391 1.0065l-12.274 7.1288c-1.1576 0.671-2.0912 1.6279-2.7383 2.7538l-18.281-10.614c0.96947-1.6865 2.3733-3.1208 4.1085-4.1295l27.315-15.861c1.7352-1.0065 3.6719-1.5109 5.6086-1.5109z" fill="#99cd61"/>
<path d="m75.563 21.501-18.279 10.614c-0.647-1.1236-1.5807-2.0806-2.736-2.7516l-6.1392-3.5644 9.3924-16.356 13.658 7.9303c1.7352 1.0065 3.1368 2.4431 4.1062 4.1296z" fill="#fb9168"/>
<path d="m38.532 64.776v21.226c-1.9367 0-3.8733-0.5044-5.6085-1.5109l-27.315-15.863c-1.7352-1.0087-3.1368-2.443-4.1062-4.1295l18.279-10.614c0.647 1.1236 1.5807 2.0783 2.736 2.7516l12.274 7.1287c1.1598 0.6733 2.4495 1.0088 3.7391 1.0088z" fill="#4d3f33"/>
<path d="m58.285 35.876v14.251c0 2.6885-1.424 5.1698-3.7391 6.5118l-12.274 7.1288c-1.1597 0.6732-2.4494 1.0087-3.739 1.0087-1.2897 0-2.5793-0.3355-3.7391-1.0087l-12.274-7.1288c-2.3151-1.342-3.7391-3.8233-3.7391-6.5118v-14.251c0-2.6884 1.424-5.1698 3.7391-6.5118l12.274-7.1287c1.1598-0.6733 2.4494-1.0065 3.7391-1.0065 1.2896 0 2.5793 0.3355 3.739 1.0065l6.1348 3.5643 6.1392 3.5644c1.1553 0.6733 2.089 1.628 2.736 2.7516 0.6471 1.1235 1.0031 2.4182 1.0031 3.7602z" fill="#fff"/>
<path d="m58.285 35.876v14.251c0 1.342-0.356 2.6367-1.0031 3.7603s-1.5807 2.0783-2.736 2.7515l-6.1392 3.5644-6.1348 3.5644c-1.1598 0.6732-2.4494 1.0087-3.739 1.0087v-21.774l14.728-8.5495 4.0234-2.335c0.6471 1.1236 1.0031 2.4183 1.0031 3.7603z" fill="#dfdfdf"/>
<path d="m38.532 43v21.774c-1.2897 0-2.5793-0.3355-3.7391-1.0088l-12.274-7.1287c-1.1553-0.6733-2.089-1.628-2.7361-2.7516-0.647-1.1235-1.003-2.4182-1.003-3.7602v-14.251c0-1.342 0.356-2.6367 1.003-3.7603l18.751 10.884z" fill="#d6d2d2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -16,7 +16,30 @@
#include "INISettingsObject.h" #include "INISettingsObject.h"
#include "Setting.h" #include "Setting.h"
INISettingsObject::INISettingsObject(const QString &path, QObject *parent) #include <QDebug>
#include <QFile>
INISettingsObject::INISettingsObject(QStringList paths, QObject *parent)
: SettingsObject(parent)
{
auto first_path = paths.constFirst();
for (auto path : paths) {
if (!QFile::exists(path))
continue;
if (path != first_path && QFile::exists(path)) {
// Copy the fallback to the preferred path.
QFile::copy(path, first_path);
qDebug() << "Copied settings from" << path << "to" << first_path;
break;
}
}
m_filePath = first_path;
m_ini.loadFile(first_path);
}
INISettingsObject::INISettingsObject(QString path, QObject* parent)
: SettingsObject(parent) : SettingsObject(parent)
{ {
m_filePath = path; m_filePath = path;

View File

@ -28,7 +28,10 @@ class INISettingsObject : public SettingsObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit INISettingsObject(const QString &path, QObject *parent = 0); /** 'paths' is a list of INI files to try, in order, for fallback support. */
explicit INISettingsObject(QStringList paths, QObject* parent = nullptr);
explicit INISettingsObject(QString path, QObject* parent = nullptr);
/*! /*!
* \brief Gets the path to the INI file. * \brief Gets the path to the INI file.

View File

@ -33,7 +33,7 @@ public:
* Construct a Setting * Construct a Setting
* *
* Synonyms are all the possible names used in the settings object, in order of preference. * Synonyms are all the possible names used in the settings object, in order of preference.
* First synonym is the ID, which identifies the setting in PolyMC. * First synonym is the ID, which identifies the setting in Prism Launcher.
* *
* defVal is the default value that will be returned when the settings object * defVal is the default value that will be returned when the settings object
* doesn't have any value for this setting. * doesn't have any value for this setting.

View File

@ -161,7 +161,7 @@ public:
QString result; QString result;
result = QApplication::translate("MainWindow", m_text); result = QApplication::translate("MainWindow", m_text);
if(result.contains("%1")) { if(result.contains("%1")) {
result = result.arg(BuildConfig.LAUNCHER_NAME); result = result.arg(BuildConfig.LAUNCHER_DISPLAYNAME);
} }
m_contained->setText(result); m_contained->setText(result);
} }
@ -170,7 +170,7 @@ public:
QString result; QString result;
result = QApplication::translate("MainWindow", m_tooltip); result = QApplication::translate("MainWindow", m_tooltip);
if(result.contains("%1")) { if(result.contains("%1")) {
result = result.arg(BuildConfig.LAUNCHER_NAME); result = result.arg(BuildConfig.LAUNCHER_DISPLAYNAME);
} }
m_contained->setToolTip(result); m_contained->setToolTip(result);
} }
@ -229,19 +229,13 @@ public:
TranslatedAction actionRenameInstance; TranslatedAction actionRenameInstance;
TranslatedAction actionChangeInstGroup; TranslatedAction actionChangeInstGroup;
TranslatedAction actionChangeInstIcon; TranslatedAction actionChangeInstIcon;
TranslatedAction actionEditInstNotes;
TranslatedAction actionEditInstance; TranslatedAction actionEditInstance;
TranslatedAction actionWorlds;
TranslatedAction actionMods;
TranslatedAction actionViewSelectedInstFolder; TranslatedAction actionViewSelectedInstFolder;
TranslatedAction actionViewSelectedMCFolder;
TranslatedAction actionDeleteInstance; TranslatedAction actionDeleteInstance;
TranslatedAction actionConfig_Folder;
TranslatedAction actionCAT; TranslatedAction actionCAT;
TranslatedAction actionCopyInstance; TranslatedAction actionCopyInstance;
TranslatedAction actionLaunchInstanceOffline; TranslatedAction actionLaunchInstanceOffline;
TranslatedAction actionLaunchInstanceDemo; TranslatedAction actionLaunchInstanceDemo;
TranslatedAction actionScreenshots;
TranslatedAction actionExportInstance; TranslatedAction actionExportInstance;
QVector<TranslatedAction *> all_actions; QVector<TranslatedAction *> all_actions;
@ -258,6 +252,7 @@ public:
QMenu * helpMenu = nullptr; QMenu * helpMenu = nullptr;
TranslatedToolButton helpMenuButton; TranslatedToolButton helpMenuButton;
TranslatedAction actionClearMetadata;
TranslatedAction actionReportBug; TranslatedAction actionReportBug;
TranslatedAction actionDISCORD; TranslatedAction actionDISCORD;
TranslatedAction actionMATRIX; TranslatedAction actionMATRIX;
@ -347,6 +342,13 @@ public:
actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z")); actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z"));
all_actions.append(&actionUndoTrashInstance); all_actions.append(&actionUndoTrashInstance);
actionClearMetadata = TranslatedAction(MainWindow);
actionClearMetadata->setObjectName(QStringLiteral("actionClearMetadata"));
actionClearMetadata->setIcon(APPLICATION->getThemedIcon("refresh"));
actionClearMetadata.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Clear Metadata Cache"));
actionClearMetadata.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Clear cached metadata"));
all_actions.append(&actionClearMetadata);
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
actionReportBug = TranslatedAction(MainWindow); actionReportBug = TranslatedAction(MainWindow);
actionReportBug->setObjectName(QStringLiteral("actionReportBug")); actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
@ -445,6 +447,8 @@ public:
helpMenu = new QMenu(MainWindow); helpMenu = new QMenu(MainWindow);
helpMenu->setToolTipsVisible(true); helpMenu->setToolTipsVisible(true);
helpMenu->addAction(actionClearMetadata);
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
helpMenu->addAction(actionReportBug); helpMenu->addAction(actionReportBug);
} }
@ -505,16 +509,8 @@ public:
fileMenu->addAction(actionCloseWindow); fileMenu->addAction(actionCloseWindow);
fileMenu->addSeparator(); fileMenu->addSeparator();
fileMenu->addAction(actionEditInstance); fileMenu->addAction(actionEditInstance);
fileMenu->addAction(actionEditInstNotes);
fileMenu->addAction(actionMods);
fileMenu->addAction(actionWorlds);
fileMenu->addAction(actionScreenshots);
fileMenu->addAction(actionChangeInstGroup); fileMenu->addAction(actionChangeInstGroup);
fileMenu->addSeparator();
fileMenu->addAction(actionViewSelectedMCFolder);
fileMenu->addAction(actionConfig_Folder);
fileMenu->addAction(actionViewSelectedInstFolder); fileMenu->addAction(actionViewSelectedInstFolder);
fileMenu->addSeparator();
fileMenu->addAction(actionExportInstance); fileMenu->addAction(actionExportInstance);
fileMenu->addAction(actionDeleteInstance); fileMenu->addAction(actionDeleteInstance);
fileMenu->addAction(actionCopyInstance); fileMenu->addAction(actionCopyInstance);
@ -537,6 +533,8 @@ public:
helpMenu = menuBar->addMenu(tr("&Help")); helpMenu = menuBar->addMenu(tr("&Help"));
helpMenu->setSeparatorsCollapsible(false); helpMenu->setSeparatorsCollapsible(false);
helpMenu->addAction(actionClearMetadata);
helpMenu->addSeparator();
helpMenu->addAction(actionAbout); helpMenu->addAction(actionAbout);
helpMenu->addAction(actionOpenWiki); helpMenu->addAction(actionOpenWiki);
helpMenu->addAction(actionNewsMenuBar); helpMenu->addAction(actionNewsMenuBar);
@ -586,13 +584,7 @@ public:
void setInstanceActionsEnabled(bool enabled) void setInstanceActionsEnabled(bool enabled)
{ {
actionEditInstance->setEnabled(enabled); actionEditInstance->setEnabled(enabled);
actionEditInstNotes->setEnabled(enabled);
actionMods->setEnabled(enabled);
actionWorlds->setEnabled(enabled);
actionScreenshots->setEnabled(enabled);
actionChangeInstGroup->setEnabled(enabled); actionChangeInstGroup->setEnabled(enabled);
actionViewSelectedMCFolder->setEnabled(enabled);
actionConfig_Folder->setEnabled(enabled);
actionViewSelectedInstFolder->setEnabled(enabled); actionViewSelectedInstFolder->setEnabled(enabled);
actionExportInstance->setEnabled(enabled); actionExportInstance->setEnabled(enabled);
actionDeleteInstance->setEnabled(enabled); actionDeleteInstance->setEnabled(enabled);
@ -687,35 +679,11 @@ public:
actionEditInstance = TranslatedAction(MainWindow); actionEditInstance = TranslatedAction(MainWindow);
actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); actionEditInstance->setObjectName(QStringLiteral("actionEditInstance"));
actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Inst&ance...")); actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Edit..."));
actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions.")); actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions."));
actionEditInstance->setShortcut(QKeySequence(tr("Ctrl+I"))); actionEditInstance->setShortcut(QKeySequence(tr("Ctrl+I")));
all_actions.append(&actionEditInstance); all_actions.append(&actionEditInstance);
actionEditInstNotes = TranslatedAction(MainWindow);
actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes"));
actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "E&dit Notes..."));
actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Edit the notes for the selected instance."));
all_actions.append(&actionEditInstNotes);
actionMods = TranslatedAction(MainWindow);
actionMods->setObjectName(QStringLiteral("actionMods"));
actionMods.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View &Mods"));
actionMods.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the mods of this instance."));
all_actions.append(&actionMods);
actionWorlds = TranslatedAction(MainWindow);
actionWorlds->setObjectName(QStringLiteral("actionWorlds"));
actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&View Worlds"));
actionWorlds.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the worlds of this instance."));
all_actions.append(&actionWorlds);
actionScreenshots = TranslatedAction(MainWindow);
actionScreenshots->setObjectName(QStringLiteral("actionScreenshots"));
actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage &Screenshots"));
actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View and upload screenshots for this instance."));
all_actions.append(&actionScreenshots);
actionChangeInstGroup = TranslatedAction(MainWindow); actionChangeInstGroup = TranslatedAction(MainWindow);
actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup")); actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup"));
actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Change Group...")); actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Change Group..."));
@ -723,38 +691,22 @@ public:
actionChangeInstGroup->setShortcut(QKeySequence(tr("Ctrl+G"))); actionChangeInstGroup->setShortcut(QKeySequence(tr("Ctrl+G")));
all_actions.append(&actionChangeInstGroup); all_actions.append(&actionChangeInstGroup);
actionViewSelectedMCFolder = TranslatedAction(MainWindow);
actionViewSelectedMCFolder->setObjectName(QStringLiteral("actionViewSelectedMCFolder"));
actionViewSelectedMCFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Minec&raft Folder"));
actionViewSelectedMCFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's Minecraft folder in a file browser."));
actionViewSelectedMCFolder->setShortcut(QKeySequence(tr("Ctrl+M")));
all_actions.append(&actionViewSelectedMCFolder);
actionConfig_Folder = TranslatedAction(MainWindow);
actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder"));
actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Confi&g Folder"));
actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance's config folder."));
// Qt on macOS is "smart" and will eat up this action when added to the menu bar because it starts with the word "config"...
// Docs: https://doc.qt.io/qt-5/qmenubar.html#qmenubar-as-a-global-menu-bar
actionConfig_Folder->setMenuRole(QAction::NoRole);
all_actions.append(&actionConfig_Folder);
actionViewSelectedInstFolder = TranslatedAction(MainWindow); actionViewSelectedInstFolder = TranslatedAction(MainWindow);
actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder"));
actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Instance Folder")); actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Folder"));
actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser.")); actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser."));
all_actions.append(&actionViewSelectedInstFolder); all_actions.append(&actionViewSelectedInstFolder);
actionExportInstance = TranslatedAction(MainWindow); actionExportInstance = TranslatedAction(MainWindow);
actionExportInstance->setObjectName(QStringLiteral("actionExportInstance")); actionExportInstance->setObjectName(QStringLiteral("actionExportInstance"));
actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "E&xport Instance...")); actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "E&xport..."));
actionExportInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Export the selected instance as a zip file.")); actionExportInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Export the selected instance as a zip file."));
actionExportInstance->setShortcut(QKeySequence(tr("Ctrl+E"))); actionExportInstance->setShortcut(QKeySequence(tr("Ctrl+E")));
all_actions.append(&actionExportInstance); all_actions.append(&actionExportInstance);
actionDeleteInstance = TranslatedAction(MainWindow); actionDeleteInstance = TranslatedAction(MainWindow);
actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance")); actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te"));
actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete}); actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete});
actionDeleteInstance->setAutoRepeat(false); actionDeleteInstance->setAutoRepeat(false);
@ -763,7 +715,7 @@ public:
actionCopyInstance = TranslatedAction(MainWindow); actionCopyInstance = TranslatedAction(MainWindow);
actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance"));
actionCopyInstance->setIcon(APPLICATION->getThemedIcon("copy")); actionCopyInstance->setIcon(APPLICATION->getThemedIcon("copy"));
actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Cop&y Instance...")); actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Cop&y..."));
actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance.")); actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance."));
actionCopyInstance->setShortcut(QKeySequence(tr("Ctrl+D"))); actionCopyInstance->setShortcut(QKeySequence(tr("Ctrl+D")));
all_actions.append(&actionCopyInstance); all_actions.append(&actionCopyInstance);
@ -792,26 +744,15 @@ public:
instanceToolBar->addSeparator(); instanceToolBar->addSeparator();
instanceToolBar->addAction(actionLaunchInstance); instanceToolBar->addAction(actionLaunchInstance);
instanceToolBar->addAction(actionLaunchInstanceOffline);
instanceToolBar->addAction(actionKillInstance); instanceToolBar->addAction(actionKillInstance);
instanceToolBar->addSeparator(); instanceToolBar->addSeparator();
instanceToolBar->addAction(actionEditInstance); instanceToolBar->addAction(actionEditInstance);
instanceToolBar->addAction(actionEditInstNotes);
instanceToolBar->addAction(actionMods);
instanceToolBar->addAction(actionWorlds);
instanceToolBar->addAction(actionScreenshots);
instanceToolBar->addAction(actionChangeInstGroup); instanceToolBar->addAction(actionChangeInstGroup);
instanceToolBar->addSeparator();
instanceToolBar->addAction(actionViewSelectedMCFolder);
instanceToolBar->addAction(actionConfig_Folder);
instanceToolBar->addAction(actionViewSelectedInstFolder); instanceToolBar->addAction(actionViewSelectedInstFolder);
instanceToolBar->addSeparator();
instanceToolBar->addAction(actionExportInstance); instanceToolBar->addAction(actionExportInstance);
instanceToolBar->addAction(actionDeleteInstance); instanceToolBar->addAction(actionDeleteInstance);
instanceToolBar->addAction(actionCopyInstance); instanceToolBar->addAction(actionCopyInstance);
@ -830,7 +771,7 @@ public:
MainWindow->setWindowIcon(APPLICATION->getThemedIcon("logo")); MainWindow->setWindowIcon(APPLICATION->getThemedIcon("logo"));
MainWindow->setWindowTitle(APPLICATION->applicationDisplayName()); MainWindow->setWindowTitle(APPLICATION->applicationDisplayName());
#ifndef QT_NO_ACCESSIBILITY #ifndef QT_NO_ACCESSIBILITY
MainWindow->setAccessibleName(BuildConfig.LAUNCHER_NAME); MainWindow->setAccessibleName(BuildConfig.LAUNCHER_DISPLAYNAME);
#endif #endif
createMainToolbarActions(MainWindow); createMainToolbarActions(MainWindow);
@ -1147,7 +1088,7 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
{ {
auto group = view->groupNameAt(pos); auto group = view->groupNameAt(pos);
QAction *actionVoid = new QAction(BuildConfig.LAUNCHER_NAME, this); QAction *actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this);
actionVoid->setEnabled(false); actionVoid->setEnabled(false);
QAction *actionCreateInstance = new QAction(tr("Create instance"), this); QAction *actionCreateInstance = new QAction(tr("Create instance"), this);
@ -1197,7 +1138,6 @@ void MainWindow::updateMainToolBar()
void MainWindow::updateToolsMenu() void MainWindow::updateToolsMenu()
{ {
QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance));
QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline));
bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning();
@ -1206,9 +1146,7 @@ void MainWindow::updateToolsMenu()
ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning);
QMenu *launchMenu = ui->actionLaunchInstance->menu(); QMenu *launchMenu = ui->actionLaunchInstance->menu();
QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu();
launchButton->setPopupMode(QToolButton::MenuButtonPopup); launchButton->setPopupMode(QToolButton::MenuButtonPopup);
launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup);
if (launchMenu) if (launchMenu)
{ {
launchMenu->clear(); launchMenu->clear();
@ -1217,19 +1155,12 @@ void MainWindow::updateToolsMenu()
{ {
launchMenu = new QMenu(this); launchMenu = new QMenu(this);
} }
if (launchOfflineMenu) {
launchOfflineMenu->clear();
}
else
{
launchOfflineMenu = new QMenu(this);
}
QAction *normalLaunch = launchMenu->addAction(tr("Launch")); QAction *normalLaunch = launchMenu->addAction(tr("Launch"));
normalLaunch->setShortcut(QKeySequence::Open); normalLaunch->setShortcut(QKeySequence::Open);
QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); QAction *normalLaunchOffline = launchMenu->addAction(tr("Launch Offline"));
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
QAction *normalLaunchDemo = launchOfflineMenu->addAction(tr("Launch Demo")); QAction *normalLaunchDemo = launchMenu->addAction(tr("Launch Demo"));
normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
if (m_selectedInstance) if (m_selectedInstance)
{ {
@ -1262,11 +1193,10 @@ void MainWindow::updateToolsMenu()
QString profilersTitle = tr("Profilers"); QString profilersTitle = tr("Profilers");
launchMenu->addSeparator()->setText(profilersTitle); launchMenu->addSeparator()->setText(profilersTitle);
launchOfflineMenu->addSeparator()->setText(profilersTitle);
for (auto profiler : APPLICATION->profilers().values()) for (auto profiler : APPLICATION->profilers().values())
{ {
QAction *profilerAction = launchMenu->addAction(profiler->name()); QAction *profilerAction = launchMenu->addAction(profiler->name());
QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); QAction *profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name()));
QString error; QString error;
if (!profiler->check(&error)) if (!profiler->check(&error))
{ {
@ -1297,7 +1227,6 @@ void MainWindow::updateToolsMenu()
} }
} }
ui->actionLaunchInstance->setMenu(launchMenu); ui->actionLaunchInstance->setMenu(launchMenu);
ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu);
} }
void MainWindow::repopulateAccountsMenu() void MainWindow::repopulateAccountsMenu()
@ -1903,15 +1832,6 @@ void MainWindow::on_actionViewCentralModsFolder_triggered()
DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true); DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true);
} }
void MainWindow::on_actionConfig_Folder_triggered()
{
if (m_selectedInstance)
{
QString str = m_selectedInstance->instanceConfigFolder();
DesktopServices::openDirectory(QDir(str).absolutePath());
}
}
void MainWindow::checkForUpdates() void MainWindow::checkForUpdates()
{ {
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
@ -1940,36 +1860,16 @@ void MainWindow::globalSettingsClosed()
updateToolsMenu(); updateToolsMenu();
updateStatusCenter(); updateStatusCenter();
// This needs to be done to prevent UI elements disappearing in the event the config is changed // This needs to be done to prevent UI elements disappearing in the event the config is changed
// but PolyMC exits abnormally, causing the window state to never be saved: // but Prism Launcher exits abnormally, causing the window state to never be saved:
APPLICATION->settings()->set("MainWindowState", saveState().toBase64()); APPLICATION->settings()->set("MainWindowState", saveState().toBase64());
update(); update();
} }
void MainWindow::on_actionEditInstNotes_triggered()
{
APPLICATION->showInstanceWindow(m_selectedInstance, "notes");
}
void MainWindow::on_actionWorlds_triggered()
{
APPLICATION->showInstanceWindow(m_selectedInstance, "worlds");
}
void MainWindow::on_actionMods_triggered()
{
APPLICATION->showInstanceWindow(m_selectedInstance, "mods");
}
void MainWindow::on_actionEditInstance_triggered() void MainWindow::on_actionEditInstance_triggered()
{ {
APPLICATION->showInstanceWindow(m_selectedInstance); APPLICATION->showInstanceWindow(m_selectedInstance);
} }
void MainWindow::on_actionScreenshots_triggered()
{
APPLICATION->showInstanceWindow(m_selectedInstance, "screenshots");
}
void MainWindow::on_actionManageAccounts_triggered() void MainWindow::on_actionManageAccounts_triggered()
{ {
APPLICATION->ShowGlobalSettings(this, "accounts"); APPLICATION->ShowGlobalSettings(this, "accounts");
@ -1980,6 +1880,11 @@ void MainWindow::on_actionReportBug_triggered()
DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL)); DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL));
} }
void MainWindow::on_actionClearMetadata_triggered()
{
APPLICATION->metacache()->evictAll();
}
void MainWindow::on_actionOpenWiki_triggered() void MainWindow::on_actionOpenWiki_triggered()
{ {
DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(""))); DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("")));
@ -2058,20 +1963,6 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered()
} }
} }
void MainWindow::on_actionViewSelectedMCFolder_triggered()
{
if (m_selectedInstance)
{
QString str = m_selectedInstance->gameRoot();
if (!FS::ensureFilePathExists(str))
{
// TODO: report error
return;
}
DesktopServices::openDirectory(QDir(str).absolutePath());
}
}
void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::closeEvent(QCloseEvent *event)
{ {
// Save the window state and geometry. // Save the window state and geometry.
@ -2252,7 +2143,7 @@ void MainWindow::checkInstancePathForProblems()
"You have now two options: <br/>" "You have now two options: <br/>"
" - change the instance folder in the settings <br/>" " - change the instance folder in the settings <br/>"
" - move this installation of %1 to a different folder" " - move this installation of %1 to a different folder"
).arg(BuildConfig.LAUNCHER_NAME) ).arg(BuildConfig.LAUNCHER_DISPLAYNAME)
); );
warning.setDefaultButton(QMessageBox::Ok); warning.setDefaultButton(QMessageBox::Ok);
warning.exec(); warning.exec();

View File

@ -112,12 +112,8 @@ private slots:
void on_actionViewInstanceFolder_triggered(); void on_actionViewInstanceFolder_triggered();
void on_actionConfig_Folder_triggered();
void on_actionViewSelectedInstFolder_triggered(); void on_actionViewSelectedInstFolder_triggered();
void on_actionViewSelectedMCFolder_triggered();
void refreshInstances(); void refreshInstances();
void on_actionViewCentralModsFolder_triggered(); void on_actionViewCentralModsFolder_triggered();
@ -130,6 +126,8 @@ private slots:
void on_actionReportBug_triggered(); void on_actionReportBug_triggered();
void on_actionClearMetadata_triggered();
void on_actionOpenWiki_triggered(); void on_actionOpenWiki_triggered();
void on_actionMoreNews_triggered(); void on_actionMoreNews_triggered();
@ -159,14 +157,6 @@ private slots:
void on_actionEditInstance_triggered(); void on_actionEditInstance_triggered();
void on_actionEditInstNotes_triggered();
void on_actionMods_triggered();
void on_actionWorlds_triggered();
void on_actionScreenshots_triggered();
void taskEnd(); void taskEnd();
/** /**

View File

@ -69,20 +69,19 @@ QString getCreditsHtml()
#endif #endif
stream << "<center>\n"; stream << "<center>\n";
//: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers" //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers"
stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_NAME) << "</h3>\n"; stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n";
stream << QString("<p>fn2006 %1</p>\n") .arg(getGitHub("fn2006")); stream << QString("<p>fn2006 %1</p>\n") .arg(getGitHub("fn2006"));
stream << "<br />\n"; stream << "<br />\n";
//: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Contributors" //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Contributors"
stream << "<h3>" << QObject::tr("%1 Contributors", "About Credits").arg(BuildConfig.LAUNCHER_NAME) << "</h3>\n"; stream << "<h3>" << QObject::tr("%1 Contributors", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n";
stream << QString("<p>anoraktrend %1</p>\n") .arg(getGitHub("anoraktrend")); stream << QString("<p>anoraktrend %1</p>\n") .arg(getGitHub("anoraktrend"));
stream << QString("<p>Emma Tebibyte %1</p>\n") .arg(getWebsite("https://tebibyte.media/")); stream << QString("<p>Emma Tebibyte %1</p>\n") .arg(getWebsite("https://tebibyte.media/"));
stream << "<br />\n"; stream << "<br />\n";
//: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers" //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers"
stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg("PolyMC") << "</h3>\n"; stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg("Prism Launcher") << "</h3>\n";
stream << QString("<p>LennyMcLennington %1</p>\n") .arg(getGitHub("LennyMcLennington"));
stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net")); stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net"));
stream << QString("<p>dada513 %1</p>\n") .arg(getGitHub("dada513")); stream << QString("<p>dada513 %1</p>\n") .arg(getGitHub("dada513"));
stream << QString("<p>txtsd %1</p>\n") .arg(getGitHub("txtsd")); stream << QString("<p>txtsd %1</p>\n") .arg(getGitHub("txtsd"));
@ -91,15 +90,15 @@ QString getCreditsHtml()
stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism")); stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism"));
stream << "<br />\n"; stream << "<br />\n";
//: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Contributors" //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Contributors"
stream << "<h3>" << QObject::tr("%1 Contributors", "About Credits").arg("PolyMC") << "</h3>\n"; stream << "<h3>" << QObject::tr("%1 Contributors", "About Credits").arg("Prism Launcher") << "</h3>\n";
stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio")); stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio"));
stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln")); stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln"));
stream << QString("<p>swirl %1</p>\n") .arg(getWebsite("https://swurl.xyz/")); stream << QString("<p>swirl %1</p>\n") .arg(getWebsite("https://swurl.xyz/"));
stream << "<br />\n"; stream << "<br />\n";
// TODO: possibly retrieve from git history at build time? // TODO: possibly retrieve from git history at build time?
//: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers" //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers"
stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg("MultiMC") << "</h3>\n"; stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg("MultiMC") << "</h3>\n";
stream << "<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>\n"; stream << "<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>\n";
stream << QString("<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>\n"); stream << QString("<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>\n");
@ -109,12 +108,20 @@ QString getCreditsHtml()
stream << "<br />\n"; stream << "<br />\n";
stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n"; stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n";
stream << QString("<p>Boba %1</p>\n") .arg(getWebsite("https://cmdplusv.neocities.org/"));
stream << QString("<p>Davi Rafael %1</p>\n") .arg(getWebsite("https://auti.one/"));
stream << QString("<p>Fulmine %1</p>\n") .arg(getWebsite("https://www.fulmine.xyz/"));
stream << QString("<p>ely %1</p>\n") .arg(getGitHub("elyrodso"));
stream << QString("<p>gon sawa %1</p>\n") .arg(getGitHub("gonsawa"));
stream << QString("<p>Pankakes</p>\n");
stream << QString("<p>tobimori %1</p>\n") .arg(getGitHub("tobimori"));
stream << "<p>Orochimarufan &lt;<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>&gt;</p>\n"; stream << "<p>Orochimarufan &lt;<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>&gt;</p>\n";
stream << "<p>TakSuyu &lt;<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>&gt;</p>\n"; stream << "<p>TakSuyu &lt;<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>&gt;</p>\n";
stream << "<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>\n"; stream << "<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>\n";
stream << "<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt;</p>\n"; stream << "<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt;</p>\n";
stream << "<p>Zeker Zhayard &lt;<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>&gt;</p>\n"; stream << "<p>Zeker Zhayard &lt;<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>&gt;</p>\n";
stream << "<p>Everyone else who <a href='https://github.com/PolyMC/PolyMC/graphs/contributors'>contributed</a>!</p>\n"; stream << "<p>Everyone who helped establish our branding!</p>\n";
stream << "<p>And everyone else who <a href='https://github.com/PrismLauncher/PrismLauncher/graphs/contributors'>contributed</a>!</p>\n";
stream << "<br />\n"; stream << "<br />\n";
stream << "</center>\n"; stream << "</center>\n";
@ -136,7 +143,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
{ {
ui->setupUi(this); ui->setupUi(this);
QString launcherName = BuildConfig.LAUNCHER_NAME; QString launcherName = BuildConfig.LAUNCHER_DISPLAYNAME;
setWindowTitle(tr("About %1").arg(launcherName)); setWindowTitle(tr("About %1").arg(launcherName));

View File

@ -139,6 +139,10 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
void NewInstanceDialog::reject() void NewInstanceDialog::reject()
{ {
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
// This is just so that the pages get the close() call and can react to it, if needed.
m_container->prepareToClose();
QDialog::reject(); QDialog::reject();
} }
@ -146,6 +150,10 @@ void NewInstanceDialog::accept()
{ {
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
importIconNow(); importIconNow();
// This is just so that the pages get the close() call and can react to it, if needed.
m_container->prepareToClose();
QDialog::accept(); QDialog::accept();
} }

View File

@ -20,7 +20,9 @@ NewsDialog::NewsDialog(QList<NewsEntryPtr> entries, QWidget* parent) : QDialog(p
auto article_entry = m_entries.constFind(first_item->text()).value(); auto article_entry = m_entries.constFind(first_item->text()).value();
ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, first_item->text())); ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, first_item->text()));
ui->currentArticleContentBrowser->setText(article_entry->content); ui->currentArticleContentBrowser->setText(article_entry->content);
ui->currentArticleContentBrowser->flush();
} }
NewsDialog::~NewsDialog() NewsDialog::~NewsDialog()
@ -33,7 +35,9 @@ void NewsDialog::selectedArticleChanged(const QString& new_title)
auto const& article_entry = m_entries.constFind(new_title).value(); auto const& article_entry = m_entries.constFind(new_title).value();
ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title)); ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title));
ui->currentArticleContentBrowser->setText(article_entry->content); ui->currentArticleContentBrowser->setText(article_entry->content);
ui->currentArticleContentBrowser->flush();
} }
void NewsDialog::toggleArticleList() void NewsDialog::toggleArticleList()

View File

@ -49,7 +49,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTextBrowser" name="currentArticleContentBrowser"> <widget class="ProjectDescriptionPage" name="currentArticleContentBrowser">
<property name="textInteractionFlags"> <property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property> </property>
@ -91,6 +91,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>ProjectDescriptionPage</class>
<extends>QTextBrowser</extends>
<header>ui/widgets/ProjectDescriptionPage.h</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>

View File

@ -25,6 +25,7 @@ ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Pr
{ {
ui->setupUi(this); ui->setupUi(this);
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true);
setSkipButton(false); setSkipButton(false);
changeProgress(0, 100); changeProgress(0, 100);
} }
@ -67,7 +68,7 @@ int ProgressDialog::execWithTask(Task* task)
return QDialog::DialogCode::Accepted; return QDialog::DialogCode::Accepted;
} }
QDialog::DialogCode result; QDialog::DialogCode result {};
if (handleImmediateResult(result)) { if (handleImmediateResult(result)) {
return result; return result;
} }
@ -80,7 +81,7 @@ int ProgressDialog::execWithTask(Task* task)
connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus);
connect(task, &Task::progress, this, &ProgressDialog::changeProgress); connect(task, &Task::progress, this, &ProgressDialog::changeProgress);
connect(task, &Task::aborted, [this] { QDialog::reject(); }); connect(task, &Task::aborted, this, &ProgressDialog::hide);
connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled);
m_is_multi_step = task->isMultiStep(); m_is_multi_step = task->isMultiStep();

View File

@ -73,12 +73,12 @@ void UpdateDialog::loadChangelog()
QString url; QString url;
if(channel == "stable") if(channel == "stable")
{ {
url = QString("https://raw.githubusercontent.com/PolyMC/PolyMC/%1/changelog.md").arg(channel); url = QString("https://raw.githubusercontent.com/PrismLauncher/PrismLauncher/%1/changelog.md").arg(channel);
m_changelogType = CHANGELOG_MARKDOWN; m_changelogType = CHANGELOG_MARKDOWN;
} }
else else
{ {
url = QString("https://api.github.com/repos/PolyMC/PolyMC/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); url = QString("https://api.github.com/repos/PrismLauncher/PrismLauncher/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel);
m_changelogType = CHANGELOG_COMMITS; m_changelogType = CHANGELOG_COMMITS;
} }
dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData));
@ -93,7 +93,7 @@ QString reprocessMarkdown(QByteArray markdown)
QString output = hoedown.process(markdown); QString output = hoedown.process(markdown);
// HACK: easier than customizing hoedown // HACK: easier than customizing hoedown
output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/issues/\\1\">GH-\\1</a>"); output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/PrismLauncher/PrismLauncher/issues/\\1\">GH-\\1</a>");
qDebug() << output; qDebug() << output;
return output; return output;
} }
@ -135,7 +135,7 @@ QString reprocessCommits(QByteArray json)
result += "<tr><td>"; result += "<tr><td>";
if(issuenr.length()) if(issuenr.length())
{ {
result += QString("<a href=\"https://github.com/PolyMC/PolyMC/issues/%1\">GH-%2</a>").arg(issuenr, issuenr); result += QString("<a href=\"https://github.com/PrismLauncher/PrismLauncher/issues/%1\">GH-%2</a>").arg(issuenr, issuenr);
} }
else if(prefix.length()) else if(prefix.length())
{ {

View File

@ -168,7 +168,7 @@ void AccountListPage::on_actionAddMicrosoft_triggered()
tr( tr(
"Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated %1.\n\n" "Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated %1.\n\n"
"Please update both your operating system and %1." "Please update both your operating system and %1."
).arg(BuildConfig.LAUNCHER_NAME), ).arg(BuildConfig.LAUNCHER_DISPLAYNAME),
QMessageBox::Warning QMessageBox::Warning
)->exec(); )->exec();
return; return;

View File

@ -151,7 +151,7 @@ void LauncherPage::on_instDirBrowseBtn_clicked()
"This is known to cause problems. " "This is known to cause problems. "
"After a restart the launcher might break, " "After a restart the launcher might break, "
"because it will no longer have access to that directory.\n\n" "because it will no longer have access to that directory.\n\n"
"Granting PolyMC access to it via Flatseal is recommended.")); "Granting %1 access to it via Flatseal is recommended.").arg(BuildConfig.LAUNCHER_DISPLAYNAME));
warning.setInformativeText( warning.setInformativeText(
tr("Do you want to proceed anyway?")); tr("Do you want to proceed anyway?"));
warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);

View File

@ -176,7 +176,7 @@
<item> <item>
<widget class="QLabel" name="metadataWarningLabel"> <widget class="QLabel" name="metadataWarningLabel">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; color:#f5c211;&quot;&gt;Warning&lt;/span&gt;&lt;span style=&quot; color:#f5c211;&quot;&gt;: Disabling mod metadata may also disable some upcoming QoL features, such as mod updating!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; color:#f5c211;&quot;&gt;Warning&lt;/span&gt;&lt;span style=&quot; color:#f5c211;&quot;&gt;: Disabling mod metadata may also disable some QoL features, such as mod updating!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@ -103,10 +103,6 @@ void ExternalResourcesPage::runningStateChanged(bool running)
return; return;
m_controlsEnabled = !running; m_controlsEnabled = !running;
ui->actionAddItem->setEnabled(m_controlsEnabled);
ui->actionDisableItem->setEnabled(m_controlsEnabled);
ui->actionEnableItem->setEnabled(m_controlsEnabled);
ui->actionRemoveItem->setEnabled(m_controlsEnabled);
} }
bool ExternalResourcesPage::shouldDisplay() const bool ExternalResourcesPage::shouldDisplay() const

View File

@ -274,6 +274,9 @@ void InstanceSettingsPage::applySettings()
{ {
m_settings->reset("JoinServerOnLaunchAddress"); m_settings->reset("JoinServerOnLaunchAddress");
} }
// FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext();
} }
void InstanceSettingsPage::loadSettings() void InstanceSettingsPage::loadSettings()

View File

@ -279,7 +279,7 @@ void LogPage::on_btnPaste_clicked()
MessageLevel::Launcher, MessageLevel::Launcher,
QString("%2: Log upload triggered at: %1").arg( QString("%2: Log upload triggered at: %1").arg(
QDateTime::currentDateTime().toString(Qt::RFC2822Date), QDateTime::currentDateTime().toString(Qt::RFC2822Date),
BuildConfig.LAUNCHER_NAME BuildConfig.LAUNCHER_DISPLAYNAME
) )
); );
auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this); auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this);
@ -289,7 +289,7 @@ void LogPage::on_btnPaste_clicked()
MessageLevel::Launcher, MessageLevel::Launcher,
QString("%2: Log uploaded to: %1").arg( QString("%2: Log uploaded to: %1").arg(
url, url,
BuildConfig.LAUNCHER_NAME BuildConfig.LAUNCHER_DISPLAYNAME
) )
); );
} }
@ -297,7 +297,7 @@ void LogPage::on_btnPaste_clicked()
{ {
m_model->append( m_model->append(
MessageLevel::Error, MessageLevel::Error,
QString("%1: Log upload failed!").arg(BuildConfig.LAUNCHER_NAME) QString("%1: Log upload failed!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)
); );
} }
} }

View File

@ -117,6 +117,10 @@ void ModFolderPage::runningStateChanged(bool running)
ExternalResourcesPage::runningStateChanged(running); ExternalResourcesPage::runningStateChanged(running);
ui->actionDownloadItem->setEnabled(!running); ui->actionDownloadItem->setEnabled(!running);
ui->actionUpdateItem->setEnabled(!running); ui->actionUpdateItem->setEnabled(!running);
ui->actionAddItem->setEnabled(!running);
ui->actionEnableItem->setEnabled(!running);
ui->actionDisableItem->setEnabled(!running);
ui->actionRemoveItem->setEnabled(!running);
} }
bool ModFolderPage::shouldDisplay() const bool ModFolderPage::shouldDisplay() const

View File

@ -60,7 +60,7 @@
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>- PolyMC / MultiMC exported instances (ZIP)</string> <string>- Prism Launcher, PolyMC or MultiMC exported instances (ZIP)</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>

View File

@ -62,11 +62,7 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
} }
case Qt::DecorationRole: { case Qt::DecorationRole: {
if (m_logoMap.contains(pack.logoName)) { if (m_logoMap.contains(pack.logoName)) {
auto icon = m_logoMap.value(pack.logoName); return m_logoMap.value(pack.logoName);
// FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;(
auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
return icon_scaled;
} }
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
// un-const-ify this // un-const-ify this
@ -175,7 +171,7 @@ void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallbac
void ListModel::requestLogo(QString logo, QString url) void ListModel::requestLogo(QString logo, QString url)
{ {
if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) {
return; return;
} }
@ -269,7 +265,7 @@ void ListModel::searchRequestFailed(QString reason)
//: %1 refers to the launcher itself //: %1 refers to the launcher itself
QString("%1 %2") QString("%1 %2")
.arg(m_parent->displayName()) .arg(m_parent->displayName())
.arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)));
} }
jobPtr.reset(); jobPtr.reset();

View File

@ -265,7 +265,9 @@ void ModPage::updateModVersions(int prev_count)
break; break;
} }
} }
if(valid || m_filter->versions.size() == 0)
// Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out
if ((valid || m_filter->versions.empty()) && !optedOut(version))
ui->versionSelectionBox->addItem(version.version, QVariant(i)); ui->versionSelectionBox->addItem(version.version, QVariant(i));
} }
if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { if (ui->versionSelectionBox->count() == 0 && prev_count != 0) {
@ -350,4 +352,5 @@ void ModPage::updateUi()
HoeDown h; HoeDown h;
ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8()))); ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8())));
ui->packDescription->flush();
} }

View File

@ -51,6 +51,7 @@ class ModPage : public QWidget, public BasePage {
auto shouldDisplay() const -> bool override = 0; auto shouldDisplay() const -> bool override = 0;
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0;
virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; };
auto apiProvider() -> ModAPI* { return api.get(); }; auto apiProvider() -> ModAPI* { return api.get(); };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }

View File

@ -14,7 +14,7 @@
<item row="1" column="0" colspan="4"> <item row="1" column="0" colspan="4">
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="2"> <item row="1" column="2">
<widget class="QTextBrowser" name="packDescription"> <widget class="ProjectDescriptionPage" name="packDescription">
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -98,6 +98,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>ProjectDescriptionPage</class>
<extends>QTextBrowser</extends>
<header>ui/widgets/ProjectDescriptionPage.h</header>
</customwidget>
</customwidgets>
<tabstops> <tabstops>
<tabstop>searchEdit</tabstop> <tabstop>searchEdit</tabstop>
<tabstop>searchButton</tabstop> <tabstop>searchButton</tabstop>

View File

@ -59,6 +59,8 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged);
connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected); connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected);
ui->packDescription->setMetaEntry(metaEntryBase());
} }
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
@ -67,6 +69,11 @@ auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString min
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
} }
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
{
return ver.downloadUrl.isEmpty();
}
// I don't know why, but doing this on the parent class makes it so that // I don't know why, but doing this on the parent class makes it so that
// other mod providers start loading before being selected, at least with // other mod providers start loading before being selected, at least with
// my Qt, so we need to implement this in every derived class... // my Qt, so we need to implement this in every derived class...

View File

@ -61,6 +61,7 @@ class FlameModPage : public ModPage {
inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; inline auto metaEntryBase() const -> QString override { return "FlameMods"; };
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override;
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
auto shouldDisplay() const -> bool override; auto shouldDisplay() const -> bool override;
}; };

View File

@ -19,7 +19,7 @@
</font> </font>
</property> </property>
<property name="text"> <property name="text">
<string>Note: CurseForge allows creators to block access to third-party tools like PolyMC. As such, you may need to manually download some mods to be able to install a modpack.</string> <string>Note: CurseForge allows creators to block access to third-party tools like Prism Launcher. As such, you may need to manually download some mods to be able to install a modpack.</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>

View File

@ -103,6 +103,8 @@ void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallbac
void ListModel::request() void ListModel::request()
{ {
m_aborted = false;
beginResetModel(); beginResetModel();
modpacks.clear(); modpacks.clear();
endResetModel(); endResetModel();
@ -117,6 +119,12 @@ void ListModel::request()
QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed); QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed);
} }
void ListModel::abortRequest()
{
m_aborted = jobPtr->abort();
jobPtr.reset();
}
void ListModel::requestFinished() void ListModel::requestFinished()
{ {
jobPtr.reset(); jobPtr.reset();
@ -162,6 +170,9 @@ void ListModel::requestPack()
void ListModel::packRequestFinished() void ListModel::packRequestFinished()
{ {
if (!jobPtr || m_aborted)
return;
jobPtr.reset(); jobPtr.reset();
remainingPacks.removeOne(currentPack); remainingPacks.removeOne(currentPack);

View File

@ -47,9 +47,13 @@ public:
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
void request(); void request();
void abortRequest();
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
[[nodiscard]] bool isMakingRequest() const { return jobPtr.get(); }
[[nodiscard]] bool wasAborted() const { return m_aborted; }
private slots: private slots:
void requestFinished(); void requestFinished();
void requestFailed(QString reason); void requestFailed(QString reason);
@ -65,6 +69,8 @@ private:
void requestLogo(QString file, QString url); void requestLogo(QString file, QString url);
private: private:
bool m_aborted = false;
QList<ModpacksCH::Modpack> modpacks; QList<ModpacksCH::Modpack> modpacks;
LogoMap m_logoMap; LogoMap m_logoMap;

View File

@ -73,6 +73,8 @@ FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent)
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged); connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged);
ui->packDescription->setMetaEntry("FTBPacks");
} }
FtbPage::~FtbPage() FtbPage::~FtbPage()
@ -105,7 +107,7 @@ void FtbPage::retranslate()
void FtbPage::openedImpl() void FtbPage::openedImpl()
{ {
if(!initialised) if(!initialised || listModel->wasAborted())
{ {
listModel->request(); listModel->request();
initialised = true; initialised = true;
@ -114,6 +116,12 @@ void FtbPage::openedImpl()
suggestCurrent(); suggestCurrent();
} }
void FtbPage::closedImpl()
{
if (listModel->isMakingRequest())
listModel->abortRequest();
}
void FtbPage::suggestCurrent() void FtbPage::suggestCurrent()
{ {
if(!isOpened) if(!isOpened)

View File

@ -78,6 +78,7 @@ public:
void retranslate() override; void retranslate() override;
void openedImpl() override; void openedImpl() override;
void closedImpl() override;
bool eventFilter(QObject * watched, QEvent * event) override; bool eventFilter(QObject * watched, QEvent * event) override;

View File

@ -57,7 +57,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QTextBrowser" name="packDescription"> <widget class="ProjectDescriptionPage" name="packDescription">
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -70,6 +70,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>ProjectDescriptionPage</class>
<extends>QTextBrowser</extends>
<header>ui/widgets/ProjectDescriptionPage.h</header>
</customwidget>
</customwidgets>
<tabstops> <tabstops>
<tabstop>searchEdit</tabstop> <tabstop>searchEdit</tabstop>
<tabstop>versionSelectionBox</tabstop> <tabstop>versionSelectionBox</tabstop>

View File

@ -59,6 +59,8 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged);
connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
ui->packDescription->setMetaEntry(metaEntryBase());
} }
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool

View File

@ -41,6 +41,7 @@
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "ui/dialogs/ModDownloadDialog.h" #include "ui/dialogs/ModDownloadDialog.h"
#include "ui/widgets/ProjectItem.h"
#include <QMessageBox> #include <QMessageBox>
@ -74,9 +75,8 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
} }
Modrinth::Modpack pack = modpacks.at(pos); Modrinth::Modpack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) { switch (role) {
return pack.name; case Qt::ToolTipRole: {
} else if (role == Qt::ToolTipRole) {
if (pack.description.length() > 100) { if (pack.description.length() > 100) {
// some magic to prevent to long tooltips and replace html linebreaks // some magic to prevent to long tooltips and replace html linebreaks
QString edit = pack.description.left(97); QString edit = pack.description.left(97);
@ -84,22 +84,32 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
return edit; return edit;
} }
return pack.description; return pack.description;
} else if (role == Qt::DecorationRole) {
if (m_logoMap.contains(pack.iconName)) {
auto icon = m_logoMap.value(pack.iconName);
// FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;(
auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
return icon_scaled;
} }
case Qt::DecorationRole: {
if (m_logoMap.contains(pack.iconName))
return m_logoMap.value(pack.iconName);
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString()); ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString());
return icon; return icon;
} else if (role == Qt::UserRole) { }
case Qt::UserRole: {
QVariant v; QVariant v;
v.setValue(pack); v.setValue(pack);
return v; return v;
} }
case Qt::SizeHintRole:
return QSize(0, 58);
// Custom data
case UserDataTypes::TITLE:
return pack.name;
case UserDataTypes::DESCRIPTION:
return pack.description;
case UserDataTypes::SELECTED:
return false;
default:
break;
}
return {}; return {};
} }
@ -208,7 +218,7 @@ void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, Logo
{ {
if (m_logoMap.contains(logo)) { if (m_logoMap.contains(logo)) {
callback(APPLICATION->metacache() callback(APPLICATION->metacache()
->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))) ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))
->getFullPath()); ->getFullPath());
} else { } else {
requestLogo(logo, logoUrl); requestLogo(logo, logoUrl);
@ -217,12 +227,12 @@ void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, Logo
void ModpackListModel::requestLogo(QString logo, QString url) void ModpackListModel::requestLogo(QString logo, QString url)
{ {
if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) {
return; return;
} }
MetaEntryPtr entry = MetaEntryPtr entry =
APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
@ -311,7 +321,7 @@ void ModpackListModel::searchRequestFailed(QString reason)
//: %1 refers to the launcher itself //: %1 refers to the launcher itself
QString("%1 %2") QString("%1 %2")
.arg(m_parent->displayName()) .arg(m_parent->displayName())
.arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)));
} }
jobPtr.reset(); jobPtr.reset();

View File

@ -43,6 +43,8 @@
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "Json.h" #include "Json.h"
#include "ui/widgets/ProjectItem.h"
#include <HoeDown.h> #include <HoeDown.h>
#include <QComboBox> #include <QComboBox>
@ -70,6 +72,9 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
ui->packDescription->setMetaEntry(metaEntryBase());
} }
ModrinthPage::~ModrinthPage() ModrinthPage::~ModrinthPage()
@ -279,6 +284,7 @@ void ModrinthPage::updateUI()
text += h.process(current.extra.body.toUtf8()); text += h.process(current.extra.body.toUtf8());
ui->packDescription->setHtml(text + current.description); ui->packDescription->setHtml(text + current.description);
ui->packDescription->flush();
} }
void ModrinthPage::suggestCurrent() void ModrinthPage::suggestCurrent()

View File

@ -66,7 +66,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTextBrowser" name="packDescription"> <widget class="ProjectDescriptionPage" name="packDescription">
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -99,6 +99,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>ProjectDescriptionPage</class>
<extends>QTextBrowser</extends>
<header>ui/widgets/ProjectDescriptionPage.h</header>
</customwidget>
</customwidgets>
<tabstops> <tabstops>
<tabstop>searchEdit</tabstop> <tabstop>searchEdit</tabstop>
<tabstop>searchButton</tabstop> <tabstop>searchButton</tabstop>

View File

@ -44,6 +44,6 @@ bool LanguageWizardPage::validatePage()
void LanguageWizardPage::retranslate() void LanguageWizardPage::retranslate()
{ {
setTitle(tr("Language")); setTitle(tr("Language"));
setSubTitle(tr("Select the language to use in %1").arg(BuildConfig.LAUNCHER_NAME)); setSubTitle(tr("Select the language to use in %1").arg(BuildConfig.LAUNCHER_DISPLAYNAME));
mainWidget->retranslate(); mainWidget->retranslate();
} }

View File

@ -29,7 +29,7 @@ void SetupWizard::retranslate()
setButtonText(QWizard::BackButton, tr("< &Back")); setButtonText(QWizard::BackButton, tr("< &Back"));
setButtonText(QWizard::FinishButton, tr("&Finish")); setButtonText(QWizard::FinishButton, tr("&Finish"));
setButtonText(QWizard::CustomButton1, tr("&Refresh")); setButtonText(QWizard::CustomButton1, tr("&Refresh"));
setWindowTitle(tr("%1 Quick Setup").arg(BuildConfig.LAUNCHER_NAME)); setWindowTitle(tr("%1 Quick Setup").arg(BuildConfig.LAUNCHER_DISPLAYNAME));
} }
BaseWizardPage * SetupWizard::getBasePage(int id) BaseWizardPage * SetupWizard::getBasePage(int id)

View File

@ -1,5 +1,7 @@
#include "BrightTheme.h" #include "BrightTheme.h"
#include <QObject>
QString BrightTheme::id() QString BrightTheme::id()
{ {
return "bright"; return "bright";
@ -18,19 +20,19 @@ bool BrightTheme::hasColorScheme()
QPalette BrightTheme::colorScheme() QPalette BrightTheme::colorScheme()
{ {
QPalette brightPalette; QPalette brightPalette;
brightPalette.setColor(QPalette::Window, QColor(239,240,241)); brightPalette.setColor(QPalette::Window, QColor(255,255,255));
brightPalette.setColor(QPalette::WindowText, QColor(49,54,59)); brightPalette.setColor(QPalette::WindowText, QColor(17,17,17));
brightPalette.setColor(QPalette::Base, QColor(252,252,252)); brightPalette.setColor(QPalette::Base, QColor(250,250,250));
brightPalette.setColor(QPalette::AlternateBase, QColor(239,240,241)); brightPalette.setColor(QPalette::AlternateBase, QColor(240,240,240));
brightPalette.setColor(QPalette::ToolTipBase, QColor(49,54,59)); brightPalette.setColor(QPalette::ToolTipBase, QColor(17,17,17));
brightPalette.setColor(QPalette::ToolTipText, QColor(239,240,241)); brightPalette.setColor(QPalette::ToolTipText, QColor(255,255,255));
brightPalette.setColor(QPalette::Text, QColor(49,54,59)); brightPalette.setColor(QPalette::Text, Qt::black);
brightPalette.setColor(QPalette::Button, QColor(239,240,241)); brightPalette.setColor(QPalette::Button, QColor(249,249,249));
brightPalette.setColor(QPalette::ButtonText, QColor(49,54,59)); brightPalette.setColor(QPalette::ButtonText, Qt::black);
brightPalette.setColor(QPalette::BrightText, Qt::red); brightPalette.setColor(QPalette::BrightText, Qt::red);
brightPalette.setColor(QPalette::Link, QColor(41, 128, 185)); brightPalette.setColor(QPalette::Link, QColor(37,137,164));
brightPalette.setColor(QPalette::Highlight, QColor(61, 174, 233)); brightPalette.setColor(QPalette::Highlight, QColor(137,207,84));
brightPalette.setColor(QPalette::HighlightedText, QColor(239,240,241)); brightPalette.setColor(QPalette::HighlightedText, Qt::black);
return fadeInactive(brightPalette, fadeAmount(), fadeColor()); return fadeInactive(brightPalette, fadeAmount(), fadeColor());
} }
@ -41,7 +43,7 @@ double BrightTheme::fadeAmount()
QColor BrightTheme::fadeColor() QColor BrightTheme::fadeColor()
{ {
return QColor(239,240,241); return QColor(255,255,255);
} }
bool BrightTheme::hasStyleSheet() bool BrightTheme::hasStyleSheet()

Some files were not shown because too many files have changed in this diff Show More