Merge branch 'develop' into remove-updater

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
This commit is contained in:
Sefa Eyeoglu 2023-01-24 14:37:40 +01:00
commit 6e841a3b7e
No known key found for this signature in database
GPG Key ID: E13DFD4B47127951
246 changed files with 7090 additions and 9153 deletions

View File

@ -61,7 +61,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ''
qt_version: '6.4.0'
qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -73,7 +73,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.4.0'
qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -105,6 +105,7 @@ jobs:
INSTALL_APPIMAGE_DIR: "install-appdir"
BUILD_DIR: "build"
CCACHE_VAR: ""
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
steps:
##
@ -135,6 +136,7 @@ jobs:
quazip-qt6:p
ccache:p
qt6-5compat:p
cmark:p
- name: Force newer ccache
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
@ -143,7 +145,7 @@ jobs:
- name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.5
uses: hendrikmuhs/ccache-action@v1.2.8
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
@ -165,7 +167,7 @@ jobs:
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.11
uses: actions/cache@v3.2.3
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64
@ -546,11 +548,10 @@ jobs:
submodules: 'true'
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v4
uses: flatpak/flatpak-github-actions/flatpak-builder@v5
with:
bundle: "Prism Launcher.flatpak"
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
cache-key: flatpak-${{ github.sha }}-x86_64
nix:
runs-on: ubuntu-latest

View File

@ -11,5 +11,5 @@ jobs:
with:
identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-Setup-.+\.exe$'
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

3
.gitmodules vendored
View File

@ -16,3 +16,6 @@
[submodule "libraries/extra-cmake-modules"]
path = libraries/extra-cmake-modules
url = https://github.com/KDE/extra-cmake-modules
[submodule "libraries/cmark"]
path = libraries/cmark
url = https://github.com/commonmark/cmark.git

View File

@ -28,19 +28,28 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader)
if(MSVC)
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
# /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag
set(CMAKE_CXX_FLAGS "/W4 /permissive- /GS ${CMAKE_CXX_FLAGS}")
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
set(CMAKE_CXX_FLAGS "/GS /permissive- /W4 ${CMAKE_CXX_FLAGS}")
# LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs
# This implicitly selects an entrypoint specific to the subsystem selected
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM
# This allows tests to still use have console without using seperate linker flags
# /LTCG allows for linking wholy optimizated programs
# /MANIFEST:NO disables generating a manifest file, we instead provide our own
# /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB
set(CMAKE_EXE_LINKER_FLAGS "/MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "/LTCG /MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
# /GL enables whole program optimizations
# /Gw helps reduce binary size
# /Gy allows the compiler to package individual functions
# /guard:cf enables control flow guard
foreach(lang C CXX)
set("CMAKE_${lang}_FLAGS_RELEASE" "/GL /Gw /Gy /guard:cf")
endforeach()
# See https://github.com/ccache/ccache/issues/1040
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
@ -199,9 +208,15 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
################################ 3rd Party Libs ################################
if(NOT Launcher_FORCE_BUNDLED_LIBS)
# Successive configurations of cmake without cleaning the build dir will cause zlib fallback to fail due to cached values
# Record when fallback triggered and skip this find_package
if(NOT Launcher_FORCE_BUNDLED_LIBS AND NOT FORCE_BUNDLED_ZLIB)
find_package(ZLIB QUIET)
endif()
if(NOT ZLIB_FOUND)
set(FORCE_BUNDLED_ZLIB TRUE CACHE BOOL "")
mark_as_advanced(FORCE_BUNDLED_ZLIB)
endif()
# Find the required Qt parts
include(QtVersionlessBackport)
@ -257,8 +272,13 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
# Find ghc_filesystem
find_package(ghc_filesystem QUIET)
# Find cmark
find_package(cmark QUIET)
endif()
include(ECMQtDeclareLoggingCategory)
####################################### Program Info #######################################
set(Launcher_APP_BINARY_NAME "prismlauncher" CACHE STRING "Name of the Launcher binary")
@ -363,16 +383,16 @@ option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
add_subdirectory(libraries/libnbtplusplus)
add_subdirectory(libraries/systeminfo) # system information library
add_subdirectory(libraries/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker
if(NOT ZLIB_FOUND)
if(FORCE_BUNDLED_ZLIB)
message(STATUS "Using bundled zlib")
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib
set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" CACHE STRING "")
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" CACHE STRING "" FORCE)
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
add_library(ZLIB::ZLIB ALIAS zlibstatic)
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
@ -397,6 +417,16 @@ if(NOT tomlplusplus_FOUND)
else()
message(STATUS "Using system tomlplusplus")
endif()
if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark")
set(CMARK_STATIC ON CACHE BOOL "Build static libcmark library" FORCE)
set(CMARK_SHARED OFF CACHE BOOL "Build shared libcmark library" FORCE)
set(CMARK_TESTS OFF CACHE BOOL "Build cmark tests and enable testing" FORCE)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark_static)
else()
message(STATUS "Using system cmark")
endif()
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
add_subdirectory(libraries/gamemode)
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API

View File

@ -1,7 +1,7 @@
## Prism Launcher
Prism Launcher - Minecraft Launcher
Copyright (C) 2022 Prism Launcher Contributors
Copyright (C) 2022-2023 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
@ -156,23 +156,34 @@
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
## Hoedown
## cmark
Copyright (c) 2008, Natacha Porté
Copyright (c) 2011, Vicent Martí
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
Copyright (c) 2014, John MacFarlane
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
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.
## Batch icon set

View File

@ -1,16 +1,15 @@
<p align="left">
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
<source media="(prefers-color-scheme: light)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo.svg">
<img alt="Prism Launcher" src="/program_info/org.prismlauncher.PrismLauncher.logo.svg" width="50%">
<img alt="Prism Launcher" src="/program_info/org.prismlauncher.PrismLauncher.logo.svg" width="40%">
</picture>
</p>
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
This is a **fork** of the MultiMC Launcher and is not endorsed by MultiMC.
<p align="center">
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.<br />
<br />This is a <b>fork</b> of the MultiMC Launcher and is <b>not</b> endorsed by it.
</p>
## Installation
@ -18,7 +17,7 @@ This is a **fork** of the MultiMC Launcher and is not endorsed by MultiMC.
<img src="https://repology.org/badge/vertical-allrepos/prismlauncher.svg" alt="Packaging status" align="right">
</a>
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download/).
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download).
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions).
### Development Builds
@ -29,44 +28,33 @@ Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS*
For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions:
[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?style=flat-square&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?style=flat-square&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?style=flat-square&logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?style=flat-square&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?label=MPR&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git)<br />[![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?label=CORP&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?label=Gentoo&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
These packages are also availiable to all the distributions based on the ones mentioned above.
## Community & Support
Feel free to create a GitHub issue if you find a bug or want to suggest a new feature. We have multiple community spaces where other community members can help you.
Feel free to create a GitHub issue if you find a bug or want to suggest a new feature. We have multiple community spaces where other community members can help you:
#### Join our Discord server:
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner2)](https://discord.gg/prismlauncher)
- **Our Discord server:**
#### Join our Matrix space:
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&logo=matrix)](https://matrix.to/#/#prismlauncher:matrix.org)
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/prismlauncher)
- **Our Matrix space:**
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://matrix.to/#/#prismlauncher:matrix.org)
- **Our Subreddit:**
#### Join our Subreddit:
[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/PrismLauncher/)
## Building
If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
## Translations
The translation effort for PrismLauncher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations>
## Forking/Redistributing/Custom builds policy
## Building
We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy:
- Make it clear that your fork is not PrismLauncher and is not endorsed by or affiliated with the PrismLauncher project (<https://prismlauncher.org>).
- Go through [CMakeLists.txt](CMakeLists.txt) and change PrismLauncher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
If you have any questions or want any clarification on the above conditions please make an issue and ask us.
Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
## Sponsors & Partners
@ -84,7 +72,7 @@ Thanks to Weblate for hosting our translation efforts.
<img src="https://hosted.weblate.org/widgets/prismlauncher/-/open-graph.png" alt="Translation status" width="300" />
</a>
Thanks to Netlify for providing us their excellent web services, as part of their [Open Source program](https://www.netlify.com/open-source/)
Thanks to Netlify for providing us their excellent web services, as part of their [Open Source program](https://www.netlify.com/open-source/).
<a href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" /> </a>
@ -92,11 +80,24 @@ Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/),
<a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a>
## Forking/Redistributing/Custom builds policy
## License
We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy:
- Make it clear that your fork is not PrismLauncher and is not endorsed by or affiliated with the PrismLauncher project (<https://prismlauncher.org>).
- Go through [CMakeLists.txt](CMakeLists.txt) and change PrismLauncher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
If you have any questions or want any clarification on the above conditions please make an issue and ask us.
Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
## License ![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?label=License&logo=gnu&color=C4282D)
All launcher code is available under the GPL-3.0-only license.
![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?style=for-the-badge&logo=gnu&color=C4282D)
The logo and related assets are under the CC BY-SA 4.0 license.

View File

@ -76,7 +76,9 @@ Config::Config()
// Assume that builds outside of Git repos are "stable"
if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND")
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND"))
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND")
|| GIT_REFSPEC == QStringLiteral("")
|| GIT_TAG == QStringLiteral("GIT-NOTFOUND"))
{
GIT_REFSPEC = "refs/heads/stable";
GIT_TAG = versionString();

View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"lastModified": 1668681692,
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
"type": "github"
},
"original": {
@ -34,11 +34,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1666057921,
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
"lastModified": 1671417167,
"narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
"rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7",
"type": "github"
},
"original": {
@ -52,24 +52,7 @@
"inputs": {
"flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus",
"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"
"nixpkgs": "nixpkgs"
}
}
},

View File

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

View File

@ -39,6 +39,7 @@ modules:
sources:
- type: dir
path: ../
builddir: true
- name: openjdk
buildsystem: simple
build-commands:

View File

@ -62,15 +62,11 @@
#include "ui/pages/global/APIPage.h"
#include "ui/pages/global/CustomCommandsPage.h"
#ifdef Q_OS_WIN
#include "ui/WinDarkmode.h"
#include <versionhelpers.h>
#endif
#include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h"
#include "ui/setupwizard/PasteWizardPage.h"
#include "ui/setupwizard/ThemeWizardPage.h"
#include "ui/dialogs/CustomMessageBox.h"
@ -150,19 +146,12 @@ static const QLatin1String liveCheckFile("live.check");
PixmapCache* PixmapCache::s_instance = nullptr;
namespace {
/** This is used so that we can output to the log file in addition to the CLI. */
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
const char *levels = "DWCFIS";
const QString format("%1 %2 %3\n");
qint64 msecstotal = APPLICATION->timeSinceStart();
qint64 seconds = msecstotal / 1000;
qint64 msecs = msecstotal % 1000;
QString foo;
char buf[1025] = {0};
::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs);
QString out = format.arg(buf).arg(levels[type]).arg(msg);
QString out = qFormatLogMessage(type, context, msg);
out += QChar::LineFeed;
APPLICATION->logFile->write(out.toUtf8());
APPLICATION->logFile->flush();
@ -231,9 +220,19 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_serverToJoin = parser.value("server");
m_profileToUse = parser.value("profile");
m_liveCheck = parser.isSet("alive");
m_zipToImport = parser.value("import");
m_instanceIdToShowWindowOf = parser.value("show");
for (auto zip_path : parser.values("import")){
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
}
// treat unspecified positional arguments as import urls
for (auto zip_path : parser.positionalArguments()) {
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
}
// error if --launch is missing with --server or --profile
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
{
@ -317,7 +316,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
/*
* Establish the mechanism for communication with an already running PolyMC that uses the same data path.
* Establish the mechanism for communication with an already running PrismLauncher that uses the same data path.
* If there is one, tell it what the user actually wanted to do and exit.
* We want to initialize this before logging to avoid messing with the log of a potential already running copy.
*/
@ -335,12 +334,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout);
if(!m_zipToImport.isEmpty())
if(!m_zipsToImport.isEmpty())
{
ApplicationMessage import;
import.command = "import";
import.args.insert("path", m_zipToImport.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
for (auto zip_url : m_zipsToImport) {
ApplicationMessage import;
import.command = "import";
import.args.insert("path", zip_url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
}
else
@ -396,6 +397,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
return;
}
qInstallMessageHandler(appDebugOutput);
qSetMessagePattern(
"%{time process}" " "
"%{if-debug}D%{endif}" "%{if-info}I%{endif}" "%{if-warning}W%{endif}" "%{if-critical}C%{endif}" "%{if-fatal}F%{endif}"
" " "|" " "
"%{if-category}[%{category}]: %{endif}"
"%{message}");
qDebug() << "<> Log initialized.";
}
@ -462,7 +471,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Theming
m_settings->registerSetting("IconTheme", QString("pe_colored"));
m_settings->registerSetting("ApplicationTheme", QString("system"));
m_settings->registerSetting("ApplicationTheme", QString());
m_settings->registerSetting("BackgroundCat", QString("kitteh"));
// Remembered state
@ -811,10 +820,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
});
{
setIconTheme(settings()->get("IconTheme").toString());
qDebug() << "<> Icon theme set.";
setApplicationTheme(settings()->get("ApplicationTheme").toString(), true);
qDebug() << "<> Application theme set.";
applyCurrentlySelectedTheme();
}
updateCapabilities();
@ -857,7 +863,8 @@ bool Application::createSetupWizard()
return false;
}();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired;
bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
if(wizardRequired)
{
@ -876,6 +883,12 @@ bool Application::createSetupWizard()
{
m_setupWizard->addPage(new PasteWizardPage(m_setupWizard));
}
if (themeInterventionRequired)
{
settings()->set("ApplicationTheme", QString("system")); // set default theme after going into theme wizard
m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard));
}
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show();
return true;
@ -898,7 +911,7 @@ bool Application::event(QEvent* event)
if (event->type() == QEvent::FileOpen) {
auto ev = static_cast<QFileOpenEvent*>(event);
m_mainWindow->droppedURLs({ ev->url() });
m_mainWindow->processURLs({ ev->url() });
}
return QApplication::event(event);
@ -958,10 +971,10 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
if(!m_zipToImport.isEmpty())
if(!m_zipsToImport.isEmpty())
{
qDebug() << "<> Importing instance from zip:" << m_zipToImport;
m_mainWindow->droppedURLs({ m_zipToImport });
qDebug() << "<> Importing from zip:" << m_zipsToImport;
m_mainWindow->processURLs( m_zipsToImport );
}
}
@ -1014,7 +1027,7 @@ void Application::messageReceived(const QByteArray& message)
qWarning() << "Received" << command << "message without a zip path/URL.";
return;
}
m_mainWindow->droppedURLs({ QUrl(path) });
m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
}
else if(command == "launch")
{
@ -1083,9 +1096,14 @@ QList<ITheme*> Application::getValidApplicationThemes()
return m_themeManager->getValidApplicationThemes();
}
void Application::setApplicationTheme(const QString& name, bool initial)
void Application::applyCurrentlySelectedTheme()
{
m_themeManager->setApplicationTheme(name, initial);
m_themeManager->applyCurrentlySelectedTheme();
}
void Application::setApplicationTheme(const QString& name)
{
m_themeManager->setApplicationTheme(name);
}
void Application::setIconTheme(const QString& name)
@ -1313,16 +1331,7 @@ MainWindow* Application::showMainWindow(bool minimized)
m_mainWindow = new MainWindow();
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
#ifdef Q_OS_WIN
if (IsWindows10OrGreater())
{
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
} else {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
}
}
#endif
if(minimized)
{
m_mainWindow->showMinimized();

View File

@ -119,9 +119,11 @@ public:
void setIconTheme(const QString& name);
void applyCurrentlySelectedTheme();
QList<ITheme*> getValidApplicationThemes();
void setApplicationTheme(const QString& name, bool initial);
void setApplicationTheme(const QString& name);
shared_qobject_ptr<ExternalUpdater> updater() {
return m_updater;
@ -207,6 +209,7 @@ signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
void globalSettingsClosed();
int currentCatChanged(int index);
#ifdef Q_OS_MACOS
void clickedOnDock();
@ -304,7 +307,7 @@ public:
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
QUrl m_zipToImport;
QList<QUrl> m_zipsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};

View File

@ -47,8 +47,8 @@ void ApplicationMessage::parse(const QByteArray & input) {
args.clear();
auto parsedArgs = root.value("args").toObject();
for(auto iter = parsedArgs.begin(); iter != parsedArgs.end(); iter++) {
args[iter.key()] = iter.value().toString();
for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
args.insert(iter.key(), iter.value().toString());
}
}
@ -56,8 +56,8 @@ QByteArray ApplicationMessage::serialize() {
QJsonObject root;
root.insert("command", command);
QJsonObject outArgs;
for (auto iter = args.begin(); iter != args.end(); iter++) {
outArgs[iter.key()] = iter.value();
for (auto iter = args.constBegin(); iter != args.constEnd(); iter++) {
outArgs.insert(iter.key(), iter.value());
}
root.insert("args", outArgs);

View File

@ -1,12 +1,12 @@
#pragma once
#include <QString>
#include <QMap>
#include <QHash>
#include <QByteArray>
struct ApplicationMessage {
QString command;
QMap<QString, QString> args;
QHash<QString, QString> args;
QByteArray serialize();
void parse(const QByteArray & input);

View File

@ -38,9 +38,9 @@ set(CORE_SOURCES
InstanceImportTask.h
InstanceImportTask.cpp
# Mod downloading task
ModDownloadTask.h
ModDownloadTask.cpp
# Resource downloading task
ResourceDownloadTask.h
ResourceDownloadTask.cpp
# Use tracking separate from memory management
Usable.h
@ -325,12 +325,18 @@ set(MINECRAFT_SOURCES
minecraft/mod/Resource.cpp
minecraft/mod/ResourceFolderModel.h
minecraft/mod/ResourceFolderModel.cpp
minecraft/mod/DataPack.h
minecraft/mod/DataPack.cpp
minecraft/mod/ResourcePack.h
minecraft/mod/ResourcePack.cpp
minecraft/mod/ResourcePackFolderModel.h
minecraft/mod/ResourcePackFolderModel.cpp
minecraft/mod/TexturePack.h
minecraft/mod/TexturePack.cpp
minecraft/mod/ShaderPack.h
minecraft/mod/ShaderPack.cpp
minecraft/mod/WorldSave.h
minecraft/mod/WorldSave.cpp
minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h
@ -341,10 +347,18 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h
minecraft/mod/tasks/LocalResourcePackParseTask.cpp
minecraft/mod/tasks/LocalTexturePackParseTask.h
minecraft/mod/tasks/LocalTexturePackParseTask.cpp
minecraft/mod/tasks/LocalShaderPackParseTask.h
minecraft/mod/tasks/LocalShaderPackParseTask.cpp
minecraft/mod/tasks/LocalWorldSaveParseTask.h
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
minecraft/mod/tasks/LocalResourceParse.h
minecraft/mod/tasks/LocalResourceParse.cpp
# Assets
minecraft/AssetsUtils.h
@ -453,7 +467,7 @@ set(API_SOURCES
modplatform/ModIndex.h
modplatform/ModIndex.cpp
modplatform/ModAPI.h
modplatform/ResourceAPI.h
modplatform/EnsureMetadataTask.h
modplatform/EnsureMetadataTask.cpp
@ -464,8 +478,8 @@ set(API_SOURCES
modplatform/flame/FlameAPI.cpp
modplatform/modrinth/ModrinthAPI.h
modplatform/modrinth/ModrinthAPI.cpp
modplatform/helpers/NetworkModAPI.h
modplatform/helpers/NetworkModAPI.cpp
modplatform/helpers/NetworkResourceAPI.h
modplatform/helpers/NetworkResourceAPI.cpp
modplatform/helpers/HashUtils.h
modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h
@ -545,6 +559,24 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
HEADER Logging.h
IDENTIFIER authCredentials
CATEGORY_NAME "launcher.auth.credentials"
DEFAULT_SEVERITY Warning
DESCRIPTION "Secrets and credentials for debugging purposes"
EXPORT "${Launcher_Name}"
)
if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this
ecm_qt_install_logging_categories(
EXPORT "${Launcher_Name}"
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
endif()
################################ COMPILE ################################
set(LOGIC_SOURCES
@ -591,7 +623,7 @@ SET(LAUNCHER_SOURCES
DesktopServices.cpp
VersionProxyModel.h
VersionProxyModel.cpp
HoeDown.h
Markdown.h
# Super secret!
KonamiCode.h
@ -643,6 +675,8 @@ SET(LAUNCHER_SOURCES
ui/setupwizard/LanguageWizardPage.h
ui/setupwizard/PasteWizardPage.cpp
ui/setupwizard/PasteWizardPage.h
ui/setupwizard/ThemeWizardPage.cpp
ui/setupwizard/ThemeWizardPage.h
# GUI - themes
ui/themes/FusionTheme.cpp
@ -729,6 +763,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/VanillaPage.cpp
ui/pages/modplatform/VanillaPage.h
ui/pages/modplatform/ResourcePage.cpp
ui/pages/modplatform/ResourcePage.h
ui/pages/modplatform/ResourceModel.cpp
ui/pages/modplatform/ResourceModel.h
ui/pages/modplatform/ModPage.cpp
ui/pages/modplatform/ModPage.h
ui/pages/modplatform/ModModel.cpp
@ -761,10 +800,10 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/flame/FlameModel.h
ui/pages/modplatform/flame/FlamePage.cpp
ui/pages/modplatform/flame/FlamePage.h
ui/pages/modplatform/flame/FlameModModel.cpp
ui/pages/modplatform/flame/FlameModModel.h
ui/pages/modplatform/flame/FlameModPage.cpp
ui/pages/modplatform/flame/FlameModPage.h
ui/pages/modplatform/flame/FlameResourceModels.cpp
ui/pages/modplatform/flame/FlameResourceModels.h
ui/pages/modplatform/flame/FlameResourcePages.cpp
ui/pages/modplatform/flame/FlameResourcePages.h
ui/pages/modplatform/modrinth/ModrinthPage.cpp
ui/pages/modplatform/modrinth/ModrinthPage.h
@ -779,10 +818,10 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/ImportPage.cpp
ui/pages/modplatform/ImportPage.h
ui/pages/modplatform/modrinth/ModrinthModModel.cpp
ui/pages/modplatform/modrinth/ModrinthModModel.h
ui/pages/modplatform/modrinth/ModrinthModPage.cpp
ui/pages/modplatform/modrinth/ModrinthModPage.h
ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
ui/pages/modplatform/modrinth/ModrinthResourcePages.h
# GUI - dialogs
ui/dialogs/AboutDialog.cpp
@ -801,8 +840,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h
ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourcePackDialog.cpp
ui/dialogs/ImportResourcePackDialog.h
ui/dialogs/ImportResourceDialog.cpp
ui/dialogs/ImportResourceDialog.h
ui/dialogs/LoginDialog.cpp
ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp
@ -825,8 +864,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/VersionSelectDialog.h
ui/dialogs/SkinUploadDialog.cpp
ui/dialogs/SkinUploadDialog.h
ui/dialogs/ModDownloadDialog.cpp
ui/dialogs/ModDownloadDialog.h
ui/dialogs/ResourceDownloadDialog.cpp
ui/dialogs/ResourceDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h
ui/dialogs/BlockedModsDialog.cpp
@ -880,6 +919,8 @@ SET(LAUNCHER_SOURCES
ui/widgets/ProgressWidget.cpp
ui/widgets/WideBar.h
ui/widgets/WideBar.cpp
ui/widgets/ThemeCustomizationWidget.h
ui/widgets/ThemeCustomizationWidget.cpp
# GUI - instance group view
ui/instanceview/InstanceProxyModel.cpp
@ -895,18 +936,9 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
if(WIN32)
set(LAUNCHER_SOURCES
${LAUNCHER_SOURCES}
# GUI - dark titlebar for Windows 10/11
ui/WinDarkmode.h
ui/WinDarkmode.cpp
)
endif()
qt_wrap_ui(LAUNCHER_UI
ui/setupwizard/PasteWizardPage.ui
ui/setupwizard/ThemeWizardPage.ui
ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui
ui/pages/global/LauncherPage.ui
@ -928,7 +960,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
ui/pages/modplatform/atlauncher/AtlPage.ui
ui/pages/modplatform/VanillaPage.ui
ui/pages/modplatform/ModPage.ui
ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/ImportPage.ui
@ -939,6 +971,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/widgets/CustomCommands.ui
ui/widgets/InfoFrame.ui
ui/widgets/ModFilterWidget.ui
ui/widgets/ThemeCustomizationWidget.ui
ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui
@ -949,7 +982,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourcePackDialog.ui
ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AboutDialog.ui
@ -1014,7 +1047,7 @@ target_link_libraries(Launcher_logic
)
target_link_libraries(Launcher_logic
QuaZip::QuaZip
hoedown
cmark::cmark
LocalPeer
Launcher_rainbow
)
@ -1155,6 +1188,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/tls"
@ -1164,6 +1199,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
endif()
configure_file(

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -56,6 +57,7 @@
#include <shlobj.h>
#include <shobjidl.h>
#include <sys/utime.h>
#include <versionhelpers.h>
#include <windows.h>
#include <winnls.h>
#include <string>
@ -213,6 +215,22 @@ bool copy::operator()(const QString& offset, bool dryRun)
return err.value() == 0;
}
bool move(const QString& source, const QString& dest)
{
std::error_code err;
ensureFilePathExists(dest);
fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err);
if (err) {
qWarning() << "Failed to move file:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << source;
qDebug() << "Destination file:" << dest;
}
return err.value() == 0;
}
bool deletePath(QString path)
{
std::error_code err;
@ -226,7 +244,7 @@ bool deletePath(QString path)
return err.value() == 0;
}
bool trash(QString path, QString *pathInTrash = nullptr)
bool trash(QString path, QString *pathInTrash)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
return false;
@ -234,6 +252,10 @@ bool trash(QString path, QString *pathInTrash = nullptr)
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
if (DesktopServices::isFlatpak())
return false;
#if defined Q_OS_WIN32
if (IsWindowsServer())
return false;
#endif
return QFile::moveToTrash(path, pathInTrash);
#endif
}

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -121,6 +122,14 @@ class copy : public QObject {
int m_copied;
};
/**
* @brief moves a file by renaming it
* @param source source file path
* @param dest destination filepath
*
*/
bool move(const QString& source, const QString& dest);
/**
* Delete a folder recursively
*/
@ -129,7 +138,7 @@ bool deletePath(QString path);
/**
* Trash a folder / file
*/
bool trash(QString path, QString *pathInTrash);
bool trash(QString path, QString *pathInTrash = nullptr);
QString PathCombine(const QString& path1, const QString& path2);
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);

View File

@ -1,76 +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 <hoedown/html.h>
#include <hoedown/document.h>
#include <QString>
#include <QByteArray>
/**
* hoedown wrapper, because dealing with resource lifetime in C is stupid
*/
class HoeDown
{
public:
class buffer
{
public:
buffer(size_t unit = 4096)
{
buf = hoedown_buffer_new(unit);
}
~buffer()
{
hoedown_buffer_free(buf);
}
const char * cstr()
{
return hoedown_buffer_cstr(buf);
}
void put(QByteArray input)
{
hoedown_buffer_put(buf, reinterpret_cast<uint8_t *>(input.data()), input.size());
}
const uint8_t * data() const
{
return buf->data;
}
size_t size() const
{
return buf->size;
}
hoedown_buffer * buf;
} ib, ob;
HoeDown()
{
renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0);
document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8);
}
~HoeDown()
{
hoedown_document_free(document);
hoedown_html_renderer_free(renderer);
}
QString process(QByteArray input)
{
ib.put(input);
hoedown_document_render(document, ob.buf, ib.data(), ib.size());
return ob.cstr();
}
private:
hoedown_document * document;
hoedown_renderer * renderer;
};

View File

@ -257,20 +257,26 @@ void InstanceImportTask::extractAborted()
void InstanceImportTask::processFlame()
{
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
FlameCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
auto pack_version_id = pack_version_id_it.value();
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
auto pack_version_id = pack_version_id_it.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
// FIXME: Find a way to get IDs in directly imported ZIPs
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, {}, {});
}
inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon);
@ -335,21 +341,33 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth()
{
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
ModrinthCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
QString pack_version_id;
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
if (pack_version_id_it != m_extra_info.constEnd())
pack_version_id = pack_version_id_it.value();
QString pack_version_id;
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
if (pack_version_id_it != m_extra_info.constEnd())
pack_version_id = pack_version_id_it.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
QString pack_id;
if (!m_sourceUrl.isEmpty()) {
QRegularExpression regex(R"(data\/(.*)\/versions)");
pack_id = regex.match(m_sourceUrl.toString()).captured(1);
}
// FIXME: Find a way to get the ID in directly imported ZIPs
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
}
inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon);

View File

@ -112,7 +112,15 @@ void LaunchController::decideAccount()
}
}
m_accountToUse = accounts->defaultAccount();
// Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
if (instanceAccountIndex == -1) {
m_accountToUse = accounts->defaultAccount();
} else {
m_accountToUse = accounts->at(instanceAccountIndex);
}
if (!m_accountToUse)
{
// If no default account is set, ask the user which one to use.

34
launcher/Markdown.h Normal file
View File

@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Joshua Goins <josh@redstrate.com>
*
* 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 <QString>
#include <cmark.h>
static QString markdownToHTML(const QString& markdown)
{
const QByteArray markdownData = markdown.toUtf8();
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
QString htmlStr(buffer);
free(buffer);
return htmlStr;
}

View File

@ -1,72 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* 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/>.
*/
#include "ModDownloadTask.h"
#include "Application.h"
#include "minecraft/mod/ModFolderModel.h"
ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed)
: m_mod(mod), m_mod_version(version), mods(mods)
{
if (is_indexed) {
m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version));
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ModDownloadTask::hasOldMod);
addTask(m_update_task);
}
m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl));
m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed);
addTask(m_filesNetJob);
}
void ModDownloadTask::downloadSucceeded()
{
m_filesNetJob.reset();
auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete);
if (!name.isEmpty() && filename != m_mod_version.fileName) {
mods->uninstallMod(filename, true);
}
}
void ModDownloadTask::downloadFailed(QString reason)
{
emitFailed(reason);
m_filesNetJob.reset();
}
void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
{
emit progress(current, total);
}
// This indirection is done so that we don't delete a mod before being sure it was
// downloaded successfully!
void ModDownloadTask::hasOldMod(QString name, QString filename)
{
to_delete = {name, filename};
}

View File

@ -28,6 +28,10 @@ class shared_qobject_ptr : public QSharedPointer<T> {
constexpr shared_qobject_ptr(const shared_qobject_ptr<Derived>& other) : QSharedPointer<T>(other)
{}
template <typename Derived>
constexpr shared_qobject_ptr(const QSharedPointer<Derived>& other) : QSharedPointer<T>(other)
{}
void reset() { QSharedPointer<T>::reset(); }
void reset(const shared_qobject_ptr<T>& other)
{

View File

@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
* 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/>.
*/
#include "ResourceDownloadTask.h"
#include "Application.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
ModPlatform::IndexedVersion version,
const std::shared_ptr<ResourceFolderModel> packs,
bool is_indexed)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
{
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) {
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), m_pack, m_pack_version));
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
addTask(m_update_task);
}
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
QDir dir { m_pack_model->dir() };
{
// FIXME: Make this more generic. May require adding additional info to IndexedVersion,
// or adquiring a reference to the base instance.
if (!m_pack_version.custom_target_folder.isEmpty()) {
dir.cdUp();
dir.cd(m_pack_version.custom_target_folder);
}
}
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed);
addTask(m_filesNetJob);
}
void ResourceDownloadTask::downloadSucceeded()
{
m_filesNetJob.reset();
auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete);
if (!name.isEmpty() && filename != m_pack_version.fileName) {
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model)
model->uninstallMod(filename, true);
else
m_pack_model->uninstallResource(filename);
}
}
void ResourceDownloadTask::downloadFailed(QString reason)
{
emitFailed(reason);
m_filesNetJob.reset();
}
void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
{
emit progress(current, total);
}
// This indirection is done so that we don't delete a mod before being sure it was
// downloaded successfully!
void ResourceDownloadTask::hasOldResource(QString name, QString filename)
{
to_delete = { name, filename };
}

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -25,32 +25,32 @@
#include "modplatform/ModIndex.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
class ModFolderModel;
class ResourceFolderModel;
class ModDownloadTask : public SequentialTask {
class ResourceDownloadTask : public SequentialTask {
Q_OBJECT
public:
explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed = true);
const QString& getFilename() const { return m_mod_version.fileName; }
explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr<ResourceFolderModel> packs, bool is_indexed = true);
const QString& getFilename() const { return m_pack_version.fileName; }
const QString& getCustomPath() const { return m_pack_version.custom_target_folder; }
const QVariant& getVersionID() const { return m_pack_version.fileId; }
private:
ModPlatform::IndexedPack m_mod;
ModPlatform::IndexedVersion m_mod_version;
const std::shared_ptr<ModFolderModel> mods;
ModPlatform::IndexedPack m_pack;
ModPlatform::IndexedVersion m_pack_version;
const std::shared_ptr<ResourceFolderModel> m_pack_model;
NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total);
void downloadFailed(QString reason);
void downloadSucceeded();
std::tuple<QString, QString> to_delete {"", ""};
private slots:
void hasOldMod(QString name, QString filename);
void hasOldResource(QString name, QString filename);
};

View File

@ -192,6 +192,10 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
// Use account for instance, this does not have a global override
m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", "");
qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true);

View File

@ -55,12 +55,13 @@
#include "PackProfile_p.h"
#include "ComponentUpdateTask.h"
#include "modplatform/ModAPI.h"
#include "Application.h"
#include "modplatform/ResourceAPI.h"
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{
{"net.minecraftforge", ModAPI::Forge},
{"net.fabricmc.fabric-loader", ModAPI::Fabric},
{"org.quiltmc.quilt-loader", ModAPI::Quilt}
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{
{"net.minecraftforge", ResourceAPI::Forge},
{"net.fabricmc.fabric-loader", ResourceAPI::Fabric},
{"org.quiltmc.quilt-loader", ResourceAPI::Quilt}
};
PackProfile::PackProfile(MinecraftInstance * instance)
@ -1066,19 +1067,22 @@ void PackProfile::disableInteraction(bool disable)
}
}
ModAPI::ModLoaderTypes PackProfile::getModLoaders()
std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
{
ModAPI::ModLoaderTypes result = ModAPI::Unspecified;
ResourceAPI::ModLoaderTypes result;
bool has_any_loader = false;
QMapIterator<QString, ModAPI::ModLoaderType> i(modloaderMapping);
QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
while (i.hasNext())
{
while (i.hasNext()) {
i.next();
Component* c = getComponent(i.key());
if (c != nullptr && c->isEnabled()) {
if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) {
result |= i.value();
has_any_loader = true;
}
}
if (!has_any_loader)
return {};
return result;
}

View File

@ -49,7 +49,7 @@
#include "BaseVersion.h"
#include "MojangDownloadInfo.h"
#include "net/Mode.h"
#include "modplatform/ModAPI.h"
#include "modplatform/ResourceAPI.h"
class MinecraftInstance;
struct PackProfileData;
@ -145,7 +145,7 @@ public:
// todo(merged): is this the best approach
void appendComponent(ComponentPtr component);
ModAPI::ModLoaderTypes getModLoaders();
std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
private:
void scheduleSave();

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -545,6 +546,10 @@ bool World::replace(World &with)
bool World::destroy()
{
if(!is_valid) return false;
if (FS::trash(m_containerFile.filePath()))
return true;
if (m_containerFile.isDir())
{
QDir d(m_containerFile.filePath());

View File

@ -1,5 +1,6 @@
#include "Parsers.h"
#include "Json.h"
#include "Logging.h"
#include <QJsonDocument>
#include <QJsonArray>
@ -75,9 +76,7 @@ bool getBool(QJsonValue value, bool & out) {
bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) {
qDebug() << "Parsing" << name <<":";
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
@ -137,9 +136,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
qDebug() << "Parsing Minecraft profile...";
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -275,9 +272,7 @@ decoded base64 "value":
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
qDebug() << "Parsing Minecraft profile...";
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -389,9 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
qDebug() << "Parsing Minecraft entitlements...";
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -424,9 +417,7 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
bool parseRolloutResponse(QByteArray & data, bool& result) {
qDebug() << "Parsing Rollout response...";
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -455,9 +446,7 @@ bool parseRolloutResponse(QByteArray & data, bool& result) {
bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
QJsonParseError jsonError;
qDebug() << "Parsing Mojang response...";
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString();

View File

@ -3,6 +3,7 @@
#include <QNetworkRequest>
#include <QUuid>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
@ -41,9 +42,7 @@ void EntitlementsStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater();
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
// TODO: check presence of same entitlementsRequestId?
// TODO: validate JWTs?

View File

@ -2,9 +2,10 @@
#include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "minecraft/auth/AccountTask.h"
#include "net/NetUtils.h"
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
@ -51,14 +52,10 @@ void LauncherLoginStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater();
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
if (Net::isApplicationError(error)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
@ -76,9 +73,7 @@ void LauncherLoginStep::onRequestDone(
if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
qWarning() << "Could not parse login_with_xbox response...";
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to parse the Minecraft access token response.")

View File

@ -42,6 +42,7 @@
#include "minecraft/auth/Parsers.h"
#include "Application.h"
#include "Logging.h"
using OAuth2 = Katabasis::DeviceFlow;
using Activity = Katabasis::Activity;
@ -117,14 +118,12 @@ void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) {
// Succeeded or did not invalidate tokens
emit hideVerificationUriAndCode();
QVariantMap extraTokens = m_oauth2->extraTokens();
#ifndef NDEBUG
if (!extraTokens.isEmpty()) {
qDebug() << "Extra tokens in response:";
qCDebug(authCredentials()) << "Extra tokens in response:";
foreach (QString key, extraTokens.keys()) {
qDebug() << "\t" << key << ":" << extraTokens.value(key);
qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key);
}
}
#endif
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
return;
}

View File

@ -2,6 +2,7 @@
#include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
@ -40,9 +41,7 @@ void MinecraftProfileStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater();
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) {

View File

@ -2,6 +2,7 @@
#include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
@ -43,9 +44,7 @@ void MinecraftProfileStepMojang::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater();
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) {

View File

@ -4,6 +4,7 @@
#include <QJsonParseError>
#include <QJsonDocument>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
@ -58,9 +59,7 @@ void XboxAuthorizationStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater();
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
if (Net::isApplicationError(error)) {

View File

@ -3,7 +3,7 @@
#include <QNetworkRequest>
#include <QUrlQuery>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
@ -56,9 +56,7 @@ void XboxProfileStep::onRequestDone(
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
#ifndef NDEBUG
qDebug() << data;
#endif
qCDebug(authCredentials()) << data;
if (Net::isApplicationError(error)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
@ -74,9 +72,7 @@ void XboxProfileStep::onRequestDone(
return;
}
#ifndef NDEBUG
qDebug() << "XBox profile: " << data;
#endif
qCDebug(authCredentials()) << "XBox profile: " << data;
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
}

View File

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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/>.
*/
#include "DataPack.h"
#include <QDebug>
#include <QMap>
#include <QRegularExpression>
#include "Version.h"
// Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
{ 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } },
{ 10, { Version("1.19"), Version("1.19.3") } },
};
void DataPack::setPackFormat(int new_format_id)
{
QMutexLocker locker(&m_data_lock);
if (!s_pack_format_versions.contains(new_format_id)) {
qWarning() << "Pack format '" << new_format_id << "' is not a recognized data pack id!";
}
m_pack_format = new_format_id;
}
void DataPack::setDescription(QString new_description)
{
QMutexLocker locker(&m_data_lock);
m_description = new_description;
}
std::pair<Version, Version> DataPack::compatibleVersions() const
{
if (!s_pack_format_versions.contains(m_pack_format)) {
return { {}, {} };
}
return s_pack_format_versions.constFind(m_pack_format).value();
}
std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) const
{
auto const& cast_other = static_cast<DataPack const&>(other);
switch (type) {
default: {
auto res = Resource::compare(other, type);
if (res.first != 0)
return res;
}
case SortType::PACK_FORMAT: {
auto this_ver = packFormat();
auto other_ver = cast_other.packFormat();
if (this_ver > other_ver)
return { 1, type == SortType::PACK_FORMAT };
if (this_ver < other_ver)
return { -1, type == SortType::PACK_FORMAT };
}
}
return { 0, false };
}
bool DataPack::applyFilter(QRegularExpression filter) const
{
if (filter.match(description()).hasMatch())
return true;
if (filter.match(QString::number(packFormat())).hasMatch())
return true;
if (filter.match(compatibleVersions().first.toString()).hasMatch())
return true;
if (filter.match(compatibleVersions().second.toString()).hasMatch())
return true;
return Resource::applyFilter(filter);
}
bool DataPack::valid() const
{
return m_pack_format != 0;
}

View File

@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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 "Resource.h"
#include <QMutex>
class Version;
/* TODO:
*
* Store localized descriptions
* */
class DataPack : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
DataPack(QObject* parent = nullptr) : Resource(parent) {}
DataPack(QFileInfo file_info) : Resource(file_info) {}
/** Gets the numerical ID of the pack format. */
[[nodiscard]] int packFormat() const { return m_pack_format; }
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
/** Gets the description of the data pack. */
[[nodiscard]] QString description() const { return m_description; }
/** Thread-safe. */
void setPackFormat(int new_format_id);
/** Thread-safe. */
void setDescription(QString new_description);
bool valid() const override;
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
protected:
mutable QMutex m_data_lock;
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
* See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
*/
int m_pack_format = 0;
/** The data pack's description, as defined in the pack.mcmeta file.
*/
QString m_description;
};

View File

@ -43,6 +43,9 @@
#include "MetadataHandler.h"
#include "Version.h"
#include "minecraft/mod/ModDetails.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
{
@ -68,6 +71,10 @@ void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
m_local_details.metadata = metadata;
}
void Mod::setDetails(const ModDetails& details) {
m_local_details = details;
}
std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
{
auto cast_other = dynamic_cast<Mod const*>(&other);
@ -91,6 +98,11 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
if (this_ver < other_ver)
return { -1, type == SortType::VERSION };
}
case SortType::PROVIDER: {
auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
if (compare_result != 0)
return { compare_result, type == SortType::PROVIDER };
}
}
return { 0, false };
}
@ -189,4 +201,16 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
m_local_details = std::move(details);
if (metadata)
setMetadata(std::move(metadata));
};
auto Mod::provider() const -> std::optional<QString>
{
if (metadata())
return ProviderCaps.readableName(metadata()->provider);
return {};
}
bool Mod::valid() const
{
return !m_local_details.mod_id.isEmpty();
}

View File

@ -39,6 +39,8 @@
#include <QFileInfo>
#include <QList>
#include <optional>
#include "Resource.h"
#include "ModDetails.h"
@ -61,6 +63,7 @@ public:
auto description() const -> QString;
auto authors() const -> QStringList;
auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>;
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
@ -68,6 +71,9 @@ public:
void setStatus(ModStatus status);
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
void setDetails(const ModDetails& details);
bool valid() const override;
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;

View File

@ -81,7 +81,7 @@ struct ModDetails
ModDetails() = default;
/** Metadata should be handled manually to properly set the mod status. */
ModDetails(ModDetails& other)
ModDetails(const ModDetails& other)
: mod_id(other.mod_id)
, name(other.name)
, version(other.version)
@ -92,7 +92,7 @@ struct ModDetails
, status(other.status)
{}
ModDetails& operator=(ModDetails& other)
ModDetails& operator=(const ModDetails& other)
{
this->mod_id = other.mod_id;
this->name = other.name;
@ -106,7 +106,7 @@ struct ModDetails
return *this;
}
ModDetails& operator=(ModDetails&& other)
ModDetails& operator=(const ModDetails&& other)
{
this->mod_id = other.mod_id;
this->name = other.name;

View File

@ -48,10 +48,11 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed)
{
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE };
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
}
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
@ -82,7 +83,15 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
}
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn: {
auto provider = at(row)->provider();
if (!provider.has_value()) {
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
return tr("Unknown");
}
return provider.value();
}
default:
return QVariant();
}
@ -118,6 +127,8 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
return tr("Version");
case DateColumn:
return tr("Last changed");
case ProviderColumn:
return tr("Provider");
default:
return QVariant();
}
@ -133,6 +144,8 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
return tr("The version of the mod.");
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
case ProviderColumn:
return tr("Where the mod was downloaded from.");
default:
return QVariant();
}

View File

@ -67,6 +67,7 @@ public:
NameColumn,
VersionColumn,
DateColumn,
ProviderColumn,
NUM_COLUMNS
};
enum ModStatusAction {

View File

@ -143,5 +143,9 @@ bool Resource::enable(EnableAction action)
bool Resource::destroy()
{
m_type = ResourceType::UNKNOWN;
if (FS::trash(m_file_info.filePath()))
return true;
return FS::deletePath(m_file_info.filePath());
}

View File

@ -20,7 +20,8 @@ enum class SortType {
DATE,
VERSION,
ENABLED,
PACK_FORMAT
PACK_FORMAT,
PROVIDER
};
enum class EnableAction {

View File

@ -20,6 +20,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractL
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); });
}
ResourceFolderModel::~ResourceFolderModel()
@ -275,7 +276,11 @@ void ResourceFolderModel::resolveResource(Resource* res)
connect(
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
QThreadPool::globalInstance()->start(task);
m_helper_thread_task.addTask(task);
if (!m_helper_thread_task.isRunning()) {
QThreadPool::globalInstance()->start(&m_helper_thread_task);
}
}
void ResourceFolderModel::onUpdateSucceeded()

View File

@ -10,6 +10,7 @@
#include "Resource.h"
#include "tasks/Task.h"
#include "tasks/ConcurrentTask.h"
class QSortFilterProxyModel;
@ -197,6 +198,7 @@ class ResourceFolderModel : public QAbstractListModel {
// Represents the relationship between a resource's internal ID and it's row position on the model.
QMap<QString, int> m_resources_index;
ConcurrentTask m_helper_thread_task;
QMap<int, Task::Ptr> m_active_parse_tasks;
std::atomic<int> m_next_resolution_ticket = 0;
};

View File

@ -13,11 +13,12 @@
// Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("1.19.3"), Version("1.19.3") } },
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } },
{ 12, { Version("1.19.3"), Version("1.19.3") } },
};
void ResourcePack::setPackFormat(int new_format_id)
@ -25,7 +26,7 @@ void ResourcePack::setPackFormat(int new_format_id)
QMutexLocker locker(&m_data_lock);
if (!s_pack_format_versions.contains(new_format_id)) {
qWarning() << "Pack format '%1' is not a recognized resource pack id!";
qWarning() << "Pack format '" << new_format_id << "' is not a recognized resource pack id!";
}
m_pack_format = new_format_id;

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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/>.
*/
#include "ShaderPack.h"
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
void ShaderPack::setPackFormat(ShaderPackFormat new_format)
{
QMutexLocker locker(&m_data_lock);
m_pack_format = new_format;
}
bool ShaderPack::valid() const
{
return m_pack_format != ShaderPackFormat::INVALID;
}

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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 "Resource.h"
/* Info:
* Currently For Optifine / Iris shader packs,
* could be expanded to support others should they exist?
*
* This class and enum are mostly here as placeholders for validating
* that a shaderpack exists and is in the right format,
* namely that they contain a folder named 'shaders'.
*
* In the technical sense it would be possible to parse files like `shaders/shaders.properties`
* to get information like the available profiles but this is not all that useful without more knowledge of the
* shader mod used to be able to change settings.
*/
#include <QMutex>
enum class ShaderPackFormat { VALID, INVALID };
class ShaderPack : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
[[nodiscard]] ShaderPackFormat packFormat() const { return m_pack_format; }
ShaderPack(QObject* parent = nullptr) : Resource(parent) {}
ShaderPack(QFileInfo file_info) : Resource(file_info) {}
/** Thread-safe. */
void setPackFormat(ShaderPackFormat new_format);
bool valid() const override;
protected:
mutable QMutex m_data_lock;
ShaderPackFormat m_pack_format = ShaderPackFormat::INVALID;
};

View File

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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/>.
*/
#include "WorldSave.h"
#include "minecraft/mod/tasks/LocalWorldSaveParseTask.h"
void WorldSave::setSaveFormat(WorldSaveFormat new_save_format)
{
QMutexLocker locker(&m_data_lock);
m_save_format = new_save_format;
}
void WorldSave::setSaveDirName(QString dir_name)
{
QMutexLocker locker(&m_data_lock);
m_save_dir_name = dir_name;
}
bool WorldSave::valid() const
{
return m_save_format != WorldSaveFormat::INVALID;
}

View File

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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 "Resource.h"
#include <QMutex>
class Version;
enum class WorldSaveFormat { SINGLE, MULTI, INVALID };
class WorldSave : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
WorldSave(QObject* parent = nullptr) : Resource(parent) {}
WorldSave(QFileInfo file_info) : Resource(file_info) {}
/** Gets the format of the save. */
[[nodiscard]] WorldSaveFormat saveFormat() const { return m_save_format; }
/** Gets the name of the save dir (first found in multi mode). */
[[nodiscard]] QString saveDirName() const { return m_save_dir_name; }
/** Thread-safe. */
void setSaveFormat(WorldSaveFormat new_save_format);
/** Thread-safe. */
void setSaveDirName(QString dir_name);
bool valid() const override;
protected:
mutable QMutex m_data_lock;
/** The format in which the save file is in.
* Since saves can be distributed in various slightly different ways, this allows us to treat them separately.
*/
WorldSaveFormat m_save_format = WorldSaveFormat::INVALID;
QString m_save_dir_name;
};

View File

@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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/>.
*/
#include "LocalDataPackParseTask.h"
#include "FileSystem.h"
#include "Json.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include <QCryptographicHash>
namespace DataPackUtils {
bool process(DataPack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
return DataPackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
return DataPackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for data pack parse task!";
return false;
}
}
bool processFolder(DataPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return mcmeta_invalid(); // can't open mcmeta file
auto data = mcmeta_file.readAll();
bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
mcmeta_file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // mcmeta file isn't a valid file
}
QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data"));
if (!data_dir_info.exists() || !data_dir_info.isDir()) {
return false; // data dir does not exists or isn't valid
}
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
return true; // all tests passed
}
bool processZIP(DataPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
QuaZipFile file(&zip);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
if (zip.setCurrentFile("pack.mcmeta")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return mcmeta_invalid();
}
auto data = file.readAll();
bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // could not set pack.mcmeta as current file.
}
QuaZipDir zipDir(&zip);
if (!zipDir.exists("/data")) {
return false; // data dir does not exists at zip root
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return true; // only need basic info already checked
}
zip.close();
return true;
}
// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
{
try {
auto json_doc = QJsonDocument::fromJson(raw_data);
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
pack.setDescription(Json::ensureString(pack_obj, "description", ""));
} catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause();
return false;
}
return true;
}
bool validate(QFileInfo file)
{
DataPack dp{ file };
return DataPackUtils::process(dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
}
} // namespace DataPackUtils
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {}
bool LocalDataPackParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalDataPackParseTask::executeTask()
{
if (!DataPackUtils::process(m_data_pack))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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 <QDebug>
#include <QObject>
#include "minecraft/mod/DataPack.h"
#include "tasks/Task.h"
namespace DataPackUtils {
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processMCMeta(DataPack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a data pack or not. */
bool validate(QFileInfo file);
} // namespace DataPackUtils
class LocalDataPackParseTask : public Task {
Q_OBJECT
public:
LocalDataPackParseTask(int token, DataPack& dp);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
int m_token;
DataPack& m_data_pack;
bool m_aborted = false;
};

View File

@ -11,12 +11,13 @@
#include "FileSystem.h"
#include "Json.h"
#include "minecraft/mod/ModDetails.h"
#include "settings/INIFile.h"
namespace {
namespace ModUtils {
// NEW format
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/c8d8f1929aff9979e322af79a59ce81f3e02db6a
// OLD format:
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
@ -73,10 +74,11 @@ ModDetails ReadMCModInfo(QByteArray contents)
version = Json::ensureString(val, "").toInt();
if (version != 2) {
qCritical() << "BAD stuff happened to mod json:";
qCritical() << contents;
return {};
qWarning() << QString(R"(The value of 'modListVersion' is "%1" (expected "2")! The file may be corrupted.)").arg(version);
qWarning() << "The contents of 'mcmod.info' are as follows:";
qWarning() << contents;
}
auto arrVal = jsonDoc.object().value("modlist");
if (arrVal.isUndefined()) {
arrVal = jsonDoc.object().value("modList");
@ -283,35 +285,46 @@ ModDetails ReadLiteModInfo(QByteArray contents)
return details;
}
} // namespace
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
: Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
{}
void LocalModParseTask::processAsZip()
bool process(Mod& mod, ProcessingLevel level)
{
QuaZip zip(m_modFile.filePath());
switch (mod.type()) {
case ResourceType::FOLDER:
return processFolder(mod, level);
case ResourceType::ZIPFILE:
return processZIP(mod, level);
case ResourceType::LITEMOD:
return processLitemod(mod);
default:
qWarning() << "Invalid type for mod parse task!";
return false;
}
}
bool processZIP(Mod& mod, ProcessingLevel level)
{
ModDetails details;
QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
return false;
QuaZipFile file(&zip);
if (zip.setCurrentFile("META-INF/mods.toml")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
return;
return false;
}
m_result->details = ReadMCModTOML(file.readAll());
details = ReadMCModTOML(file.readAll());
file.close();
// to replace ${file.jarVersion} with the actual version, as needed
if (m_result->details.version == "${file.jarVersion}") {
if (details.version == "${file.jarVersion}") {
if (zip.setCurrentFile("META-INF/MANIFEST.MF")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
return;
return false;
}
// quick and dirty line-by-line parser
@ -330,93 +343,131 @@ void LocalModParseTask::processAsZip()
manifestVersion = "NONE";
}
m_result->details.version = manifestVersion;
details.version = manifestVersion;
file.close();
}
}
zip.close();
return;
mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("mcmod.info")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
return;
return false;
}
m_result->details = ReadMCModInfo(file.readAll());
details = ReadMCModInfo(file.readAll());
file.close();
zip.close();
return;
mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("quilt.mod.json")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
return;
return false;
}
m_result->details = ReadQuiltModInfo(file.readAll());
details = ReadQuiltModInfo(file.readAll());
file.close();
zip.close();
return;
mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("fabric.mod.json")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
return;
return false;
}
m_result->details = ReadFabricModInfo(file.readAll());
details = ReadFabricModInfo(file.readAll());
file.close();
zip.close();
return;
mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("forgeversion.properties")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
return;
return false;
}
m_result->details = ReadForgeInfo(file.readAll());
details = ReadForgeInfo(file.readAll());
file.close();
zip.close();
return;
mod.setDetails(details);
return true;
}
zip.close();
return false; // no valid mod found in archive
}
void LocalModParseTask::processAsFolder()
bool processFolder(Mod& mod, ProcessingLevel level)
{
QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info"));
if (mcmod_info.isFile()) {
ModDetails details;
QFileInfo mcmod_info(FS::PathCombine(mod.fileinfo().filePath(), "mcmod.info"));
if (mcmod_info.exists() && mcmod_info.isFile()) {
QFile mcmod(mcmod_info.filePath());
if (!mcmod.open(QIODevice::ReadOnly))
return;
return false;
auto data = mcmod.readAll();
if (data.isEmpty() || data.isNull())
return;
m_result->details = ReadMCModInfo(data);
return false;
details = ReadMCModInfo(data);
mod.setDetails(details);
return true;
}
return false; // no valid mcmod.info file found
}
void LocalModParseTask::processAsLitemod()
bool processLitemod(Mod& mod, ProcessingLevel level)
{
QuaZip zip(m_modFile.filePath());
ModDetails details;
QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
return false;
QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
return;
return false;
}
m_result->details = ReadLiteModInfo(file.readAll());
details = ReadLiteModInfo(file.readAll());
file.close();
mod.setDetails(details);
return true;
}
zip.close();
return false; // no valid litemod.json found in archive
}
/** Checks whether a file is valid as a mod or not. */
bool validate(QFileInfo file)
{
Mod mod{ file };
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
}
} // namespace ModUtils
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
: Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
{}
bool LocalModParseTask::abort()
{
m_aborted.store(true);
@ -425,19 +476,10 @@ bool LocalModParseTask::abort()
void LocalModParseTask::executeTask()
{
switch (m_type) {
case ResourceType::ZIPFILE:
processAsZip();
break;
case ResourceType::FOLDER:
processAsFolder();
break;
case ResourceType::LITEMOD:
processAsLitemod();
break;
default:
break;
}
Mod mod{ m_modFile };
ModUtils::process(mod, ModUtils::ProcessingLevel::Full);
m_result->details = mod.details();
if (m_aborted)
emit finished();

View File

@ -8,32 +8,48 @@
#include "tasks/Task.h"
class LocalModParseTask : public Task
{
namespace ModUtils {
ModDetails ReadFabricModInfo(QByteArray contents);
ModDetails ReadQuiltModInfo(QByteArray contents);
ModDetails ReadForgeInfo(QByteArray contents);
ModDetails ReadLiteModInfo(QByteArray contents);
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
/** Checks whether a file is valid as a mod or not. */
bool validate(QFileInfo file);
} // namespace ModUtils
class LocalModParseTask : public Task {
Q_OBJECT
public:
public:
struct Result {
ModDetails details;
};
using ResultPtr = std::shared_ptr<Result>;
ResultPtr result() const {
return m_result;
}
ResultPtr result() const { return m_result; }
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile);
LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile);
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
private:
void processAsZip();
void processAsFolder();
void processAsLitemod();
private:
private:
int m_token;
ResourceType m_type;
QFileInfo m_modFile;

View File

@ -22,6 +22,7 @@
#include "Json.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include <QCryptographicHash>
@ -32,99 +33,152 @@ bool process(ResourcePack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
ResourcePackUtils::processFolder(pack, level);
return true;
return ResourcePackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
ResourcePackUtils::processZIP(pack, level);
return true;
return ResourcePackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
void processFolder(ResourcePack& pack, ProcessingLevel level)
bool processFolder(ResourcePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
if (mcmeta_file_info.isFile()) {
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return;
return mcmeta_invalid(); // can't open mcmeta file
auto data = mcmeta_file.readAll();
ResourcePackUtils::processMCMeta(pack, std::move(data));
bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
mcmeta_file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // mcmeta file isn't a valid file
}
if (level == ProcessingLevel::BasicInfoOnly)
return;
QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets"));
if (!assets_dir_info.exists() || !assets_dir_info.isDir()) {
return false; // assets dir does not exists or isn't valid
}
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
auto png_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return true; // the png is optional
};
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return;
if (image_file_info.exists() && image_file_info.isFile()) {
QFile pack_png_file(image_file_info.filePath());
if (!pack_png_file.open(QIODevice::ReadOnly))
return png_invalid(); // can't open pack.png file
auto data = mcmeta_file.readAll();
auto data = pack_png_file.readAll();
ResourcePackUtils::processPackPNG(pack, std::move(data));
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close();
pack_png_file.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // pack.png does not exists or is not a valid file.
}
return true; // all tests passed
}
void processZIP(ResourcePack& pack, ProcessingLevel level)
bool processZIP(ResourcePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
return false; // can't open zip file
QuaZipFile file(&zip);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
if (zip.setCurrentFile("pack.mcmeta")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return;
return mcmeta_invalid();
}
auto data = file.readAll();
ResourcePackUtils::processMCMeta(pack, std::move(data));
bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // could not set pack.mcmeta as current file.
}
QuaZipDir zipDir(&zip);
if (!zipDir.exists("/assets")) {
return false; // assets dir does not exists at zip root
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return;
return true; // only need basic info already checked
}
auto png_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return true; // the png is optional
};
if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return;
return png_invalid();
}
auto data = file.readAll();
ResourcePackUtils::processPackPNG(pack, std::move(data));
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // could not set pack.mcmeta as current file.
}
zip.close();
return true;
}
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{
try {
auto json_doc = QJsonDocument::fromJson(raw_data);
@ -134,17 +188,21 @@ void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
pack.setDescription(Json::ensureString(pack_obj, "description", ""));
} catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause();
return false;
}
return true;
}
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
pack.setImage(img);
} else {
qWarning() << "Failed to parse pack.png.";
return false;
}
return true;
}
bool validate(QFileInfo file)

View File

@ -31,11 +31,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file);

View File

@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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/>.
*/
#include <QObject>
#include "LocalResourceParse.h"
#include "LocalDataPackParseTask.h"
#include "LocalModParseTask.h"
#include "LocalResourcePackParseTask.h"
#include "LocalShaderPackParseTask.h"
#include "LocalTexturePackParseTask.h"
#include "LocalWorldSaveParseTask.h"
static const QMap<PackedResourceType, QString> s_packed_type_names = {
{PackedResourceType::ResourcePack, QObject::tr("resource pack")},
{PackedResourceType::TexturePack, QObject::tr("texture pack")},
{PackedResourceType::DataPack, QObject::tr("data pack")},
{PackedResourceType::ShaderPack, QObject::tr("shader pack")},
{PackedResourceType::WorldSave, QObject::tr("world save")},
{PackedResourceType::Mod , QObject::tr("mod")},
{PackedResourceType::UNKNOWN, QObject::tr("unknown")}
};
namespace ResourceUtils {
PackedResourceType identify(QFileInfo file){
if (file.exists() && file.isFile()) {
if (ResourcePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a resource pack";
return PackedResourceType::ResourcePack;
} else if (TexturePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a pre 1.6 texture pack";
return PackedResourceType::TexturePack;
} else if (DataPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a data pack";
return PackedResourceType::DataPack;
} else if (ModUtils::validate(file)) {
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (WorldSaveUtils::validate(file)) {
qDebug() << file.fileName() << "is a world save";
return PackedResourceType::WorldSave;
} else if (ShaderPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a shader pack";
return PackedResourceType::ShaderPack;
} else {
qDebug() << "Can't Identify" << file.fileName() ;
}
} else {
qDebug() << "Can't find" << file.absolutePath();
}
return PackedResourceType::UNKNOWN;
}
QString getPackedTypeName(PackedResourceType type) {
return s_packed_type_names.constFind(type).value();
}
}

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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 <set>
#include <QDebug>
#include <QFileInfo>
#include <QObject>
enum class PackedResourceType { DataPack, ResourcePack, TexturePack, ShaderPack, WorldSave, Mod, UNKNOWN };
namespace ResourceUtils {
static const std::set<PackedResourceType> ValidResourceTypes = { PackedResourceType::DataPack, PackedResourceType::ResourcePack,
PackedResourceType::TexturePack, PackedResourceType::ShaderPack,
PackedResourceType::WorldSave, PackedResourceType::Mod };
PackedResourceType identify(QFileInfo file);
QString getPackedTypeName(PackedResourceType type);
} // namespace ResourceUtils

View File

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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/>.
*/
#include "LocalShaderPackParseTask.h"
#include "FileSystem.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
namespace ShaderPackUtils {
bool process(ShaderPack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
return ShaderPackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
return ShaderPackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for shader pack parse task!";
return false;
}
}
bool processFolder(ShaderPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
QFileInfo shaders_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "shaders"));
if (!shaders_dir_info.exists() || !shaders_dir_info.isDir()) {
return false; // assets dir does not exists or isn't valid
}
pack.setPackFormat(ShaderPackFormat::VALID);
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
return true; // all tests passed
}
bool processZIP(ShaderPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
QuaZipFile file(&zip);
QuaZipDir zipDir(&zip);
if (!zipDir.exists("/shaders")) {
return false; // assets dir does not exists at zip root
}
pack.setPackFormat(ShaderPackFormat::VALID);
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return true; // only need basic info already checked
}
zip.close();
return true;
}
bool validate(QFileInfo file)
{
ShaderPack sp{ file };
return ShaderPackUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
}
} // namespace ShaderPackUtils
LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {}
bool LocalShaderPackParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalShaderPackParseTask::executeTask()
{
if (!ShaderPackUtils::process(m_shader_pack))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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 <QDebug>
#include <QObject>
#include "minecraft/mod/ShaderPack.h"
#include "tasks/Task.h"
namespace ShaderPackUtils {
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
/** Checks whether a file is valid as a shader pack or not. */
bool validate(QFileInfo file);
} // namespace ShaderPackUtils
class LocalShaderPackParseTask : public Task {
Q_OBJECT
public:
LocalShaderPackParseTask(int token, ShaderPack& sp);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
int m_token;
ShaderPack& m_shader_pack;
bool m_aborted = false;
};

View File

@ -32,18 +32,16 @@ bool process(TexturePack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
TexturePackUtils::processFolder(pack, level);
return true;
return TexturePackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
TexturePackUtils::processZIP(pack, level);
return true;
return TexturePackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
void processFolder(TexturePack& pack, ProcessingLevel level)
bool processFolder(TexturePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
@ -51,39 +49,51 @@ void processFolder(TexturePack& pack, ProcessingLevel level)
if (mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return;
return false;
auto data = mcmeta_file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data));
bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
mcmeta_file.close();
if (!packTXT_result) {
return false;
}
} else {
return false;
}
if (level == ProcessingLevel::BasicInfoOnly)
return;
return true;
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return;
return false;
auto data = mcmeta_file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data));
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close();
if (!packPNG_result) {
return false;
}
} else {
return false;
}
return true;
}
void processZIP(TexturePack& pack, ProcessingLevel level)
bool processZIP(TexturePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
return false;
QuaZipFile file(&zip);
@ -91,51 +101,62 @@ void processZIP(TexturePack& pack, ProcessingLevel level)
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return;
return false;
}
auto data = file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data));
bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
file.close();
if (!packTXT_result) {
return false;
}
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return;
return true;
}
if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return;
return false;
}
auto data = file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data));
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close();
if (!packPNG_result) {
return false;
}
}
zip.close();
return true;
}
void processPackTXT(TexturePack& pack, QByteArray&& raw_data)
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
{
pack.setDescription(QString(raw_data));
return true;
}
void processPackPNG(TexturePack& pack, QByteArray&& raw_data)
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
pack.setImage(img);
} else {
qWarning() << "Failed to parse pack.png.";
return false;
}
return true;
}
bool validate(QFileInfo file)

View File

@ -32,11 +32,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processPackTXT(TexturePack& pack, QByteArray&& raw_data);
void processPackPNG(TexturePack& pack, QByteArray&& raw_data);
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file);

View File

@ -0,0 +1,190 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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/>.
*/
#include "LocalWorldSaveParseTask.h"
#include "FileSystem.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include <QDir>
#include <QFileInfo>
namespace WorldSaveUtils {
bool process(WorldSave& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
return WorldSaveUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
return WorldSaveUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for world save parse task!";
return false;
}
}
/// @brief checks a folder structure to see if it contains a level.dat
/// @param dir the path to check
/// @param saves used in recursive call if a "saves" dir was found
/// @return std::tuple of (
/// bool <found level.dat>,
/// QString <name of folder containing level.dat>,
/// bool <saves folder found>
/// )
static std::tuple<bool, QString, bool> contains_level_dat(QDir dir, bool saves = false)
{
for (auto const& entry : dir.entryInfoList()) {
if (!entry.isDir()) {
continue;
}
if (!saves && entry.fileName() == "saves") {
return contains_level_dat(QDir(entry.filePath()), true);
}
QFileInfo level_dat(FS::PathCombine(entry.filePath(), "level.dat"));
if (level_dat.exists() && level_dat.isFile()) {
return std::make_tuple(true, entry.fileName(), saves);
}
}
return std::make_tuple(false, "", saves);
}
bool processFolder(WorldSave& save, ProcessingLevel level)
{
Q_ASSERT(save.type() == ResourceType::FOLDER);
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(QDir(save.fileinfo().filePath()));
if (!found) {
return false;
}
save.setSaveDirName(save_dir_name);
if (found_saves_dir) {
save.setSaveFormat(WorldSaveFormat::MULTI);
} else {
save.setSaveFormat(WorldSaveFormat::SINGLE);
}
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
// reserved for more intensive processing
return true; // all tests passed
}
/// @brief checks a folder structure to see if it contains a level.dat
/// @param zip the zip file to check
/// @return std::tuple of (
/// bool <found level.dat>,
/// QString <name of folder containing level.dat>,
/// bool <saves folder found>
/// )
static std::tuple<bool, QString, bool> contains_level_dat(QuaZip& zip)
{
bool saves = false;
QuaZipDir zipDir(&zip);
if (zipDir.exists("/saves")) {
saves = true;
zipDir.cd("/saves");
}
for (auto const& entry : zipDir.entryList()) {
zipDir.cd(entry);
if (zipDir.exists("level.dat")) {
return std::make_tuple(true, entry, saves);
}
zipDir.cd("..");
}
return std::make_tuple(false, "", saves);
}
bool processZIP(WorldSave& save, ProcessingLevel level)
{
Q_ASSERT(save.type() == ResourceType::ZIPFILE);
QuaZip zip(save.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(zip);
if (save_dir_name.endsWith("/")) {
save_dir_name.chop(1);
}
if (!found) {
return false;
}
save.setSaveDirName(save_dir_name);
if (found_saves_dir) {
save.setSaveFormat(WorldSaveFormat::MULTI);
} else {
save.setSaveFormat(WorldSaveFormat::SINGLE);
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return true; // only need basic info already checked
}
// reserved for more intensive processing
zip.close();
return true;
}
bool validate(QFileInfo file)
{
WorldSave sp{ file };
return WorldSaveUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
}
} // namespace WorldSaveUtils
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {}
bool LocalWorldSaveParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalWorldSaveParseTask::executeTask()
{
if (!WorldSaveUtils::process(m_save))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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 <QDebug>
#include <QObject>
#include "minecraft/mod/WorldSave.h"
#include "tasks/Task.h"
namespace WorldSaveUtils {
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(WorldSave& save, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(WorldSave& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(WorldSave& pack, ProcessingLevel level = ProcessingLevel::Full);
bool validate(QFileInfo file);
} // namespace WorldSaveUtils
class LocalWorldSaveParseTask : public Task {
Q_OBJECT
public:
LocalWorldSaveParseTask(int token, WorldSave& save);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
int m_token;
WorldSave& m_save;
bool m_aborted = false;
};

View File

@ -1,18 +1,18 @@
#pragma once
#include "minecraft/mod/Mod.h"
#include "modplatform/ModAPI.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/ModIndex.h"
#include "tasks/Task.h"
class ModDownloadTask;
class ResourceDownloadTask;
class ModFolderModel;
class CheckUpdateTask : public Task {
Q_OBJECT
public:
CheckUpdateTask(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder)
CheckUpdateTask(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {};
struct UpdatableMod {
@ -21,11 +21,11 @@ class CheckUpdateTask : public Task {
QString old_version;
QString new_version;
QString changelog;
ModPlatform::Provider provider;
ModDownloadTask* download;
ModPlatform::ResourceProvider provider;
ResourceDownloadTask* download;
public:
UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::Provider p, ModDownloadTask* t)
UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, ResourceDownloadTask* t)
: name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t)
{}
};
@ -44,7 +44,7 @@ class CheckUpdateTask : public Task {
protected:
QList<Mod*>& m_mods;
std::list<Version>& m_game_versions;
ModAPI::ModLoaderTypes m_loaders;
std::optional<ResourceAPI::ModLoaderTypes> m_loaders;
std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> m_updatable;

View File

@ -13,14 +13,12 @@
#include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
#include "net/NetJob.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
static ModrinthAPI modrinth_api;
static FlameAPI flame_api;
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov)
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr)
{
auto hash_task = createNewHash(mod);
@ -31,7 +29,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider
hash_task->start();
}
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::Provider prov)
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{
m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10);
@ -107,13 +105,13 @@ void EnsureMetadataTask::executeTask()
}
}
NetJob::Ptr version_task;
Task::Ptr version_task;
switch (m_provider) {
case (ModPlatform::Provider::MODRINTH):
case (ModPlatform::ResourceProvider::MODRINTH):
version_task = modrinthVersionsTask();
break;
case (ModPlatform::Provider::FLAME):
case (ModPlatform::ResourceProvider::FLAME):
version_task = flameVersionsTask();
break;
}
@ -127,13 +125,13 @@ void EnsureMetadataTask::executeTask()
};
connect(version_task.get(), &Task::finished, this, [this, invalidade_leftover] {
NetJob::Ptr project_task;
Task::Ptr project_task;
switch (m_provider) {
case (ModPlatform::Provider::MODRINTH):
case (ModPlatform::ResourceProvider::MODRINTH):
project_task = modrinthProjectsTask();
break;
case (ModPlatform::Provider::FLAME):
case (ModPlatform::ResourceProvider::FLAME):
project_task = flameProjectsTask();
break;
}
@ -149,7 +147,7 @@ void EnsureMetadataTask::executeTask()
m_current_task = nullptr;
});
m_current_task = project_task.get();
m_current_task = project_task;
project_task->start();
});
@ -164,7 +162,7 @@ void EnsureMetadataTask::executeTask()
setStatus(tr("Requesting metadata information from %1 for '%2'...")
.arg(ProviderCaps.readableName(m_provider), m_mods.begin().value()->name()));
m_current_task = version_task.get();
m_current_task = version_task;
version_task->start();
}
@ -210,9 +208,9 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
// Modrinth
NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask()
Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
{
auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first();
auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto* response = new QByteArray();
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);
@ -221,7 +219,7 @@ NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask()
if (!ver_task)
return {};
connect(ver_task.get(), &NetJob::succeeded, this, [this, response] {
connect(ver_task.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@ -260,14 +258,14 @@ NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask()
return ver_task;
}
NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask()
Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
{
QHash<QString, QString> addonIds;
for (auto const& data : m_temp_versions)
addonIds.insert(data.addonId.toString(), data.hash);
auto response = new QByteArray();
NetJob::Ptr proj_task;
Task::Ptr proj_task;
if (addonIds.isEmpty()) {
qWarning() << "No addonId found!";
@ -281,7 +279,7 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask()
if (!proj_task)
return {};
connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] {
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@ -291,51 +289,61 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask()
return;
}
QJsonArray entries;
try {
QJsonArray entries;
if (addonIds.size() == 1)
entries = { doc.object() };
else
entries = Json::requireArray(doc);
for (auto entry : entries) {
auto entry_obj = Json::requireObject(entry);
ModPlatform::IndexedPack pack;
Modrinth::loadIndexedPack(pack, entry_obj);
auto hash = addonIds.find(pack.addonId.toString()).value();
auto mod_iter = m_mods.find(hash);
if (mod_iter == m_mods.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* mod = mod_iter.value();
try {
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
}
}
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << doc;
}
for (auto entry : entries) {
ModPlatform::IndexedPack pack;
try {
auto entry_obj = Json::requireObject(entry);
Modrinth::loadIndexedPack(pack, entry_obj);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << doc;
// Skip this entry, since it has problems
continue;
}
auto hash = addonIds.find(pack.addonId.toString()).value();
auto mod_iter = m_mods.find(hash);
if (mod_iter == m_mods.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* mod = mod_iter.value();
try {
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
}
}
});
return proj_task;
}
// Flame
NetJob::Ptr EnsureMetadataTask::flameVersionsTask()
Task::Ptr EnsureMetadataTask::flameVersionsTask()
{
auto* response = new QByteArray();
@ -400,7 +408,7 @@ NetJob::Ptr EnsureMetadataTask::flameVersionsTask()
return ver_task;
}
NetJob::Ptr EnsureMetadataTask::flameProjectsTask()
Task::Ptr EnsureMetadataTask::flameProjectsTask()
{
QHash<QString, QString> addonIds;
for (auto const& hash : m_mods.keys()) {
@ -414,7 +422,7 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask()
}
auto response = new QByteArray();
NetJob::Ptr proj_task;
Task::Ptr proj_task;
if (addonIds.isEmpty()) {
qWarning() << "No addonId found!";
@ -428,7 +436,7 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask()
if (!proj_task)
return {};
connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] {
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {

View File

@ -14,8 +14,8 @@ class EnsureMetadataTask : public Task {
Q_OBJECT
public:
EnsureMetadataTask(Mod*, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH);
EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH);
EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
~EnsureMetadataTask() = default;
@ -28,11 +28,11 @@ class EnsureMetadataTask : public Task {
private:
// FIXME: Move to their own namespace
auto modrinthVersionsTask() -> NetJob::Ptr;
auto modrinthProjectsTask() -> NetJob::Ptr;
auto modrinthVersionsTask() -> Task::Ptr;
auto modrinthProjectsTask() -> Task::Ptr;
auto flameVersionsTask() -> NetJob::Ptr;
auto flameProjectsTask() -> NetJob::Ptr;
auto flameVersionsTask() -> Task::Ptr;
auto flameProjectsTask() -> Task::Ptr;
// Helpers
enum class RemoveFromList {
@ -57,9 +57,9 @@ class EnsureMetadataTask : public Task {
private:
QHash<QString, Mod*> m_mods;
QDir m_index_dir;
ModPlatform::Provider m_provider;
ModPlatform::ResourceProvider m_provider;
QHash<QString, ModPlatform::IndexedVersion> m_temp_versions;
ConcurrentTask* m_hashing_task;
NetJob* m_current_task;
Task::Ptr m_current_task;
};

View File

@ -1,118 +0,0 @@
// 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
#include <QString>
#include <QList>
#include <list>
#include "../Version.h"
#include "net/NetJob.h"
namespace ModPlatform {
class ListModel;
struct IndexedPack;
}
class ModAPI {
protected:
using CallerType = ModPlatform::ListModel;
public:
virtual ~ModAPI() = default;
enum ModLoaderType {
Unspecified = 0,
Forge = 1 << 0,
Cauldron = 1 << 1,
LiteLoader = 1 << 2,
Fabric = 1 << 3,
Quilt = 1 << 4
};
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SearchArgs {
int offset;
QString search;
QString sorting;
ModLoaderTypes loaders;
std::list<Version> versions;
};
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) = 0;
virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0;
virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0;
struct VersionSearchArgs {
QString addonId;
std::list<Version> mcVersions;
ModLoaderTypes loaders;
};
virtual void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const = 0;
static auto getModLoaderString(ModLoaderType type) -> const QString {
switch (type) {
case Unspecified:
break;
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
}
return "";
}
protected:
inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString
{
QString s;
for(auto& ver : mcVersions){
s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
return s;
}
};

View File

@ -24,47 +24,47 @@
namespace ModPlatform {
auto ProviderCapabilities::name(Provider p) -> const char*
auto ProviderCapabilities::name(ResourceProvider p) -> const char*
{
switch (p) {
case Provider::MODRINTH:
case ResourceProvider::MODRINTH:
return "modrinth";
case Provider::FLAME:
case ResourceProvider::FLAME:
return "curseforge";
}
return {};
}
auto ProviderCapabilities::readableName(Provider p) -> QString
auto ProviderCapabilities::readableName(ResourceProvider p) -> QString
{
switch (p) {
case Provider::MODRINTH:
case ResourceProvider::MODRINTH:
return "Modrinth";
case Provider::FLAME:
case ResourceProvider::FLAME:
return "CurseForge";
}
return {};
}
auto ProviderCapabilities::hashType(Provider p) -> QStringList
auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList
{
switch (p) {
case Provider::MODRINTH:
case ResourceProvider::MODRINTH:
return { "sha512", "sha1" };
case Provider::FLAME:
case ResourceProvider::FLAME:
// Try newer formats first, fall back to old format
return { "sha1", "md5", "murmur2" };
}
return {};
}
auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString
auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString type) -> QString
{
QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1;
switch (p) {
case Provider::MODRINTH: {
case ResourceProvider::MODRINTH: {
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512;
break;
}
case Provider::FLAME:
case ResourceProvider::FLAME:
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5;
break;
}

View File

@ -28,17 +28,16 @@ class QIODevice;
namespace ModPlatform {
enum class Provider {
MODRINTH,
FLAME
};
enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK };
class ProviderCapabilities {
public:
auto name(Provider) -> const char*;
auto readableName(Provider) -> QString;
auto hashType(Provider) -> QStringList;
auto hash(Provider, QIODevice*, QString type = "") -> QString;
auto name(ResourceProvider) -> const char*;
auto readableName(ResourceProvider) -> QString;
auto hashType(ResourceProvider) -> QStringList;
auto hash(ResourceProvider, QIODevice*, QString type = "") -> QString;
};
struct ModpackAuthor {
@ -66,6 +65,10 @@ struct IndexedVersion {
QString hash;
bool is_preferred = true;
QString changelog;
// For internal use, not provided by APIs
bool is_currently_selected = false;
QString custom_target_folder;
};
struct ExtraPackData {
@ -81,7 +84,7 @@ struct ExtraPackData {
struct IndexedPack {
QVariant addonId;
Provider provider;
ResourceProvider provider;
QString name;
QString slug;
QString description;
@ -96,9 +99,26 @@ struct IndexedPack {
// Don't load by default, since some modplatform don't have that info
bool extraDataLoaded = true;
ExtraPackData extraData;
// For internal use, not provided by APIs
[[nodiscard]] bool isVersionSelected(size_t index) const
{
if (!versionsLoaded)
return false;
return versions.at(index).is_currently_selected;
}
[[nodiscard]] bool isAnyVersionSelected() const
{
if (!versionsLoaded)
return false;
return std::any_of(versions.constBegin(), versions.constEnd(),
[](auto const& v) { return v.is_currently_selected; });
}
};
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
Q_DECLARE_METATYPE(ModPlatform::Provider)
Q_DECLARE_METATYPE(ModPlatform::ResourceProvider)

View File

@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/*
* 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
#include <QDebug>
#include <QList>
#include <QString>
#include <list>
#include <optional>
#include "../Version.h"
#include "modplatform/ModIndex.h"
#include "tasks/Task.h"
/* Simple class with a common interface for interacting with APIs */
class ResourceAPI {
public:
virtual ~ResourceAPI() = default;
enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 };
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SortingMethod {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request.
unsigned int index;
// The real name of the sorting, as used in the respective API specification.
// Used by Modrinth in the API request.
QString name;
// The human-readable name of the sorting, used for display in the UI.
QString readable_name;
};
struct SearchArgs {
ModPlatform::ResourceType type{};
int offset = 0;
std::optional<QString> search;
std::optional<SortingMethod> sorting;
std::optional<ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions;
};
struct SearchCallbacks {
std::function<void(QJsonDocument&)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
std::function<void()> on_abort;
};
struct VersionSearchArgs {
ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions;
std::optional<ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other)
{
pack = other.pack;
mcVersions = other.mcVersions;
loaders = other.loaders;
}
};
struct VersionSearchCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
};
struct ProjectInfoArgs {
ModPlatform::IndexedPack pack;
ProjectInfoArgs(ProjectInfoArgs const&) = default;
void operator=(ProjectInfoArgs other) { pack = other.pack; }
};
struct ProjectInfoCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
};
public:
/** Gets a list of available sorting methods for this API. */
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
public slots:
[[nodiscard]] virtual Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, QByteArray* response) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
static auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
default:
break;
}
return "";
}
protected:
[[nodiscard]] inline QString debugName() const { return "External resource API"; }
[[nodiscard]] inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString
{
QString s;
for (auto& ver : mcVersions) {
s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); // remove last comma
return s;
}
};

View File

@ -1,13 +1,17 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
#include "FlameAPI.h"
#include "FlameModIndex.h"
#include "Application.h"
#include "BuildConfig.h"
#include "Json.h"
#include "net/NetJob.h"
#include "net/Upload.h"
auto FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response)
{
auto* netJob = new NetJob(QString("Flame::MatchFingerprints"), APPLICATION->network());
@ -106,13 +110,19 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion
{
auto versions_url_optional = getVersionsURL(args);
if (!versions_url_optional.has_value())
return {};
auto versions_url = versions_url_optional.value();
QEventLoop loop;
auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network());
auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
auto response = new QByteArray();
ModPlatform::IndexedVersion ver;
netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response));
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] {
QJsonParseError parse_error{};
@ -161,7 +171,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
return ver;
}
auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob*
Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const
{
auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network());
@ -178,13 +188,13 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const ->
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); });
QObject::connect(netJob, &NetJob::finished, [response] { delete response; });
QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob;
}
auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*
Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const
{
auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network());
@ -201,8 +211,23 @@ auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); });
QObject::connect(netJob, &NetJob::finished, [response] { delete response; });
QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob;
}
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
{ 3, "LastUpdated", QObject::tr("Sort by Last Updated") },
{ 4, "Name", QObject::tr("Sort by Name") },
{ 5, "Author", QObject::tr("Sort by Author") },
{ 6, "TotalDownloads", QObject::tr("Sort by Downloads") },
{ 7, "Category", QObject::tr("Sort by Category") },
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } };
QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const
{
return s_sorts;
}

View File

@ -1,75 +1,36 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
#include "modplatform/helpers/NetworkResourceAPI.h"
class FlameAPI : public NetworkModAPI {
class FlameAPI : public NetworkResourceAPI {
public:
auto matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr;
auto getModFileChangelog(int modId, int fileId) -> QString;
auto getModDescription(int modId) -> QString;
auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion;
auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override;
auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response);
Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
private:
inline auto getSortFieldInt(QString sortString) const -> int
static int getClassId(ModPlatform::ResourceType type)
{
return sortString == "Featured" ? 1
: sortString == "Popularity" ? 2
: sortString == "LastUpdated" ? 3
: sortString == "Name" ? 4
: sortString == "Author" ? 5
: sortString == "TotalDownloads" ? 6
: sortString == "Category" ? 7
: sortString == "GameVersion" ? 8
: 1;
switch (type) {
default:
case ModPlatform::ResourceType::MOD:
return 6;
}
}
private:
inline auto getModSearchURL(SearchArgs& args) const -> QString override
{
auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString();
return QString(
"https://api.curseforge.com/v1/mods/search?"
"gameId=432&"
"classId=6&"
"index=%1&"
"pageSize=25&"
"searchFilter=%2&"
"sortField=%3&"
"sortOrder=desc&"
"modLoaderType=%4&"
"%5")
.arg(args.offset)
.arg(args.search)
.arg(getSortFieldInt(args.sorting))
.arg(getMappedModLoader(args.loaders))
.arg(gameVersionStr);
};
inline auto getModInfoURL(QString& id) const -> QString override
{
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
};
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{
QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : "";
QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders));
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3")
.arg(args.addonId)
.arg(gameVersionQuery)
.arg(modLoaderQuery);
};
public:
static auto getMappedModLoader(const ModLoaderTypes loaders) -> int
static int getMappedModLoader(ModLoaderTypes loaders)
{
// https://docs.curseforge.com/?http#tocS_ModLoaderType
if (loaders & Forge)
@ -81,4 +42,43 @@ class FlameAPI : public NetworkModAPI {
return 4; // Quilt would probably be 5
return 0;
}
private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
{
auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
QStringList get_arguments;
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
get_arguments.append(QString("index=%1").arg(args.offset));
get_arguments.append("pageSize=25");
if (args.search.has_value())
get_arguments.append(QString("searchFilter=%1").arg(args.search.value()));
if (args.sorting.has_value())
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc");
if (args.loaders.has_value())
get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
get_arguments.append(gameVersionStr);
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
};
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
{
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
};
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{
QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())};
QStringList get_parameters;
if (args.mcVersions.has_value())
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
if (args.loaders.has_value())
get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
return url + get_parameters.join('&');
};
};

View File

@ -7,7 +7,10 @@
#include "FileSystem.h"
#include "Json.h"
#include "ModDownloadTask.h"
#include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
static FlameAPI api;
@ -126,7 +129,8 @@ void FlameCheckUpdate::executeTask()
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
setProgress(i++, m_mods.size());
auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders });
ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() };
auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders });
// Check if we were aborted while getting the latest version
if (m_was_aborted) {
@ -160,7 +164,7 @@ void FlameCheckUpdate::executeTask()
for (auto& author : mod->authors())
pack.authors.append({ author });
pack.description = mod->description();
pack.provider = ModPlatform::Provider::FLAME;
pack.provider = ModPlatform::ResourceProvider::FLAME;
auto old_version = mod->version();
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
@ -168,10 +172,10 @@ void FlameCheckUpdate::executeTask()
old_version = current_ver.version;
}
auto download_task = new ModDownloadTask(pack, latest_ver, m_mods_folder);
auto download_task = new ResourceDownloadTask(pack, latest_ver, m_mods_folder);
m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version,
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
ModPlatform::Provider::FLAME, download_task);
ModPlatform::ResourceProvider::FLAME, download_task);
}
}

View File

@ -8,7 +8,7 @@ class FlameCheckUpdate : public CheckUpdateTask {
Q_OBJECT
public:
FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder)
FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{}

View File

@ -53,6 +53,13 @@
#include "ui/dialogs/BlockedModsDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <QDebug>
#include <QFileInfo>
#include "minecraft/World.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" },
{ "1.4.2", "6.0.1.355" },
{ "1.4.7", "6.6.2.534" },
@ -176,7 +183,7 @@ bool FlameCreationTask::updateInstance()
QEventLoop loop;
connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] {
connect(job.get(), &Task::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] {
// Parse the API response
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*raw_response, &parse_error);
@ -218,7 +225,7 @@ bool FlameCreationTask::updateInstance()
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
}
});
connect(job, &NetJob::finished, &loop, &QEventLoop::quit);
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = job;
job->start();
@ -361,7 +368,9 @@ bool FlameCreationTask::createInstance()
FS::deletePath(jarmodsPath);
}
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
instance.setName(name());
m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack);
@ -399,6 +408,10 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
QList<BlockedMod> blocked_mods;
auto anyBlocked = false;
for (const auto& result : results.files.values()) {
if (result.fileName.endsWith(".zip")) {
m_ZIP_resources.append(std::make_pair(result.fileName, result.targetFolder));
}
if (!result.resolved || result.url.isEmpty()) {
BlockedMod blocked_mod;
blocked_mod.name = result.fileName;
@ -437,38 +450,6 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
}
}
/// @brief copy the matched blocked mods to the instance staging area
/// @param blocked_mods list of the blocked mods and their matched paths
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
{
setStatus(tr("Copying Blocked Mods..."));
setAbortable(false);
int i = 0;
int total = blocked_mods.length();
setProgress(i, total);
for (auto const& mod : blocked_mods) {
if (!mod.matched) {
qDebug() << mod.name << "was not matched to a local file, skipping copy";
continue;
}
auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
if (!FS::copy(mod.localPath, dest_path)()) {
qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
}
i++;
setProgress(i, total);
}
setAbortable(true);
}
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
{
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network());
@ -507,7 +488,10 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
}
m_mod_id_resolver.reset();
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); });
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
m_files_job.reset();
validateZIPResouces();
});
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset();
setError(reason);
@ -518,3 +502,103 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
setStatus(tr("Downloading mods..."));
m_files_job->start();
}
/// @brief copy the matched blocked mods to the instance staging area
/// @param blocked_mods list of the blocked mods and their matched paths
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
{
setStatus(tr("Copying Blocked Mods..."));
setAbortable(false);
int i = 0;
int total = blocked_mods.length();
setProgress(i, total);
for (auto const& mod : blocked_mods) {
if (!mod.matched) {
qDebug() << mod.name << "was not matched to a local file, skipping copy";
continue;
}
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
qDebug() << "Will try to copy" << mod.localPath << "to" << destPath;
if (!FS::copy(mod.localPath, destPath)()) {
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
}
i++;
setProgress(i, total);
}
setAbortable(true);
}
void FlameCreationTask::validateZIPResouces()
{
qDebug() << "Validating whether resources stored as .zip are in the right place";
for (auto [fileName, targetFolder] : m_ZIP_resources) {
qDebug() << "Checking" << fileName << "...";
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
/// @brief check the target and move the the file
/// @return path where file can now be found
auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) {
if (targetFolder != realTarget) {
qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget;
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName);
qDebug() << "Moving" << localPath << "to" << destPath;
if (FS::move(localPath, destPath)) {
return destPath;
}
}
return localPath;
};
auto installWorld = [this](QString worldPath){
qDebug() << "Installing World from" << worldPath;
QFileInfo worldFileInfo(worldPath);
World w(worldFileInfo);
if (!w.isValid()) {
qDebug() << "World at" << worldPath << "is not valid, skipping install.";
} else {
w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves"));
}
};
QFileInfo localFileInfo(localPath);
auto type = ResourceUtils::identify(localFileInfo);
QString worldPath;
switch (type) {
case PackedResourceType::ResourcePack :
validatePath(fileName, targetFolder, "resourcepacks");
break;
case PackedResourceType::TexturePack :
validatePath(fileName, targetFolder, "texturepacks");
break;
case PackedResourceType::DataPack :
validatePath(fileName, targetFolder, "datapacks");
break;
case PackedResourceType::Mod :
validatePath(fileName, targetFolder, "mods");
break;
case PackedResourceType::ShaderPack :
// in theroy flame API can't do this but who knows, that *may* change ?
// better to handle it if it *does* occure in the future
validatePath(fileName, targetFolder, "shaderpacks");
break;
case PackedResourceType::WorldSave :
worldPath = validatePath(fileName, targetFolder, "saves");
installWorld(worldPath);
break;
case PackedResourceType::UNKNOWN :
default :
qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is.";
break;
}
}
}

View File

@ -77,6 +77,7 @@ class FlameCreationTask final : public InstanceCreationTask {
void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
void validateZIPResouces();
private:
QWidget* m_parent = nullptr;
@ -85,10 +86,12 @@ class FlameCreationTask final : public InstanceCreationTask {
Flame::Manifest m_pack;
// Handle to allow aborting
NetJob* m_process_update_file_info_job = nullptr;
Task::Ptr m_process_update_file_info_job = nullptr;
NetJob::Ptr m_files_job = nullptr;
QString m_managed_id, m_managed_version_id;
QList<std::pair<QString, QString>> m_ZIP_resources;
std::optional<InstancePtr> m_instance;
};

View File

@ -11,7 +11,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps;
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
pack.provider = ModPlatform::Provider::FLAME;
pack.provider = ModPlatform::ResourceProvider::FLAME;
pack.name = Json::requireString(obj, "name");
pack.slug = Json::requireString(obj, "slug");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
@ -76,10 +76,10 @@ static QString enumToString(int hash_algorithm)
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst)
const BaseInstance* inst)
{
QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<MinecraftInstance*>(inst))->getPackProfile();
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
for (auto versionIter : arr) {
@ -127,7 +127,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
auto hash_list = Json::ensureArray(obj, "hashes");
for (auto h : hash_list) {
auto hash_entry = Json::ensureObject(h);
auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME);
auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME);
auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm"));
if (hash_types.contains(hash_algo)) {
file.hash = Json::requireString(hash_entry, "value");

View File

@ -17,7 +17,7 @@ void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
} // namespace FlameMod

View File

@ -12,12 +12,12 @@ namespace Hashing {
static ModPlatform::ProviderCapabilities ProviderCaps;
Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider)
Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider)
{
switch (provider) {
case ModPlatform::Provider::MODRINTH:
case ModPlatform::ResourceProvider::MODRINTH:
return createModrinthHasher(file_path);
case ModPlatform::Provider::FLAME:
case ModPlatform::ResourceProvider::FLAME:
return createFlameHasher(file_path);
default:
qCritical() << "[Hashing]"
@ -36,12 +36,12 @@ Hasher::Ptr createFlameHasher(QString file_path)
return new FlameHasher(file_path);
}
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider)
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider)
{
return new BlockedModHasher(file_path, provider);
}
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type)
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type)
{
auto hasher = new BlockedModHasher(file_path, provider);
hasher->useHashType(type);
@ -62,8 +62,8 @@ void ModrinthHasher::executeTask()
return;
}
auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first();
m_hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type);
auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
m_hash = ProviderCaps.hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type);
file.close();
@ -92,7 +92,7 @@ void FlameHasher::executeTask()
}
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider)
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider)
: Hasher(file_path), provider(provider) {
setObjectName(QString("BlockedModHasher: %1").arg(file_path));
hash_type = ProviderCaps.hashType(provider).first();

View File

@ -42,21 +42,21 @@ class ModrinthHasher : public Hasher {
class BlockedModHasher : public Hasher {
public:
BlockedModHasher(QString file_path, ModPlatform::Provider provider);
BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider);
void executeTask() override;
QStringList getHashTypes();
bool useHashType(QString type);
private:
ModPlatform::Provider provider;
ModPlatform::ResourceProvider provider;
QString hash_type;
};
Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider);
Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider);
Hasher::Ptr createFlameHasher(QString file_path);
Hasher::Ptr createModrinthHasher(QString file_path);
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider);
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type);
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider);
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type);
} // namespace Hashing

View File

@ -1,97 +0,0 @@
#include "NetworkModAPI.h"
#include "ui/pages/modplatform/ModModel.h"
#include "Application.h"
#include "net/NetJob.h"
void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const
{
auto netJob = new NetJob(QString("%1::Search").arg(caller->debugName()), APPLICATION->network());
auto searchUrl = getModSearchURL(args);
auto response = new QByteArray();
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); });
QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed);
QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted);
QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
caller->searchRequestFinished(doc);
});
netJob->start();
}
void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback)
{
auto response = new QByteArray();
auto job = getProject(pack.addonId.toString(), response);
QObject::connect(job, &NetJob::succeeded, [callback, &pack, response] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
callback(doc, pack);
});
job->start();
}
void NetworkModAPI::getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const
{
auto netJob = new NetJob(QString("ModVersions(%2)").arg(args.addonId), APPLICATION->network());
auto response = new QByteArray();
netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response));
QObject::connect(netJob, &NetJob::succeeded, [response, callback, args] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
callback(doc, args.addonId);
});
QObject::connect(netJob, &NetJob::finished, [response, netJob] {
netJob->deleteLater();
delete response;
});
netJob->start();
}
auto NetworkModAPI::getProject(QString addonId, QByteArray* response) const -> NetJob*
{
auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network());
auto searchUrl = getModInfoURL(addonId);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob, &NetJob::finished, [response, netJob] {
netJob->deleteLater();
delete response;
});
return netJob;
}

View File

@ -1,17 +0,0 @@
#pragma once
#include "modplatform/ModAPI.h"
class NetworkModAPI : public ModAPI {
public:
void searchMods(CallerType* caller, SearchArgs&& args) const override;
void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) override;
void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const override;
auto getProject(QString addonId, QByteArray* response) const -> NetJob* override;
protected:
virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0;
virtual auto getModInfoURL(QString& id) const -> QString = 0;
virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0;
};

View File

@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
#include "NetworkResourceAPI.h"
#include "Application.h"
#include "net/NetJob.h"
#include "modplatform/ModIndex.h"
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
{
auto search_url_optional = getSearchURL(args);
if (!search_url_optional.has_value()) {
callbacks.on_fail("Failed to create search URL", -1);
return nullptr;
}
auto search_url = search_url_optional.value();
auto response = new QByteArray();
auto netJob = new NetJob(QString("%1::Search").arg(debugName()), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
QObject::connect(netJob, &NetJob::succeeded, [=]{
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
callbacks.on_fail(parse_error.errorString(), -1);
return;
}
callbacks.on_succeed(doc);
});
QObject::connect(netJob, &NetJob::failed, [=](QString reason){
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
});
QObject::connect(netJob, &NetJob::aborted, [=]{
callbacks.on_abort();
});
return netJob;
}
Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const
{
auto response = new QByteArray();
auto job = getProject(args.pack.addonId.toString(), response);
QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
callbacks.on_succeed(doc, args.pack);
});
return job;
}
Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const
{
auto versions_url_optional = getVersionsURL(args);
if (!versions_url_optional.has_value())
return nullptr;
auto versions_url = versions_url_optional.value();
auto netJob = new NetJob(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
auto response = new QByteArray();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
QObject::connect(netJob, &NetJob::succeeded, [=] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
callbacks.on_succeed(doc, args.pack);
});
QObject::connect(netJob, &NetJob::finished, [response] {
delete response;
});
return netJob;
}
Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const
{
auto project_url_optional = getInfoURL(addonId);
if (!project_url_optional.has_value())
return nullptr;
auto project_url = project_url_optional.value();
auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
QObject::connect(netJob, &NetJob::finished, [response] {
delete response;
});
return netJob;
}

View File

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "modplatform/ResourceAPI.h"
class NetworkResourceAPI : public ResourceAPI {
public:
Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override;
Task::Ptr getProject(QString addonId, QByteArray* response) const override;
Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override;
Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override;
protected:
[[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 0;
};

View File

@ -1,10 +1,15 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
#include "ModrinthAPI.h"
#include "Application.h"
#include "Json.h"
#include "net/NetJob.h"
#include "net/Upload.h"
auto ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response) -> NetJob::Ptr
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response)
{
auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
@ -16,7 +21,7 @@ auto ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray*
return netJob;
}
auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) -> NetJob::Ptr
Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response)
{
auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersions"), APPLICATION->network());
@ -35,23 +40,26 @@ auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format
return netJob;
}
auto ModrinthAPI::latestVersion(QString hash,
QString hash_format,
std::list<Version> mcVersions,
ModLoaderTypes loaders,
QByteArray* response) -> NetJob::Ptr
Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response)
{
auto* netJob = new NetJob(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
QJsonObject body_obj;
Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders));
if (loaders.has_value())
Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value()));
QStringList game_versions;
for (auto& ver : mcVersions) {
game_versions.append(ver.toString());
if (mcVersions.has_value()) {
QStringList game_versions;
for (auto& ver : mcVersions.value()) {
game_versions.append(ver.toString());
}
Json::writeStringList(body_obj, "game_versions", game_versions);
}
Json::writeStringList(body_obj, "game_versions", game_versions);
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
@ -64,11 +72,11 @@ auto ModrinthAPI::latestVersion(QString hash,
return netJob;
}
auto ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format,
std::list<Version> mcVersions,
ModLoaderTypes loaders,
QByteArray* response) -> NetJob::Ptr
Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response)
{
auto* netJob = new NetJob(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
@ -77,13 +85,16 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes,
Json::writeStringList(body_obj, "hashes", hashes);
Json::writeString(body_obj, "algorithm", hash_format);
Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders));
if (loaders.has_value())
Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value()));
QStringList game_versions;
for (auto& ver : mcVersions) {
game_versions.append(ver.toString());
if (mcVersions.has_value()) {
QStringList game_versions;
for (auto& ver : mcVersions.value()) {
game_versions.append(ver.toString());
}
Json::writeStringList(body_obj, "game_versions", game_versions);
}
Json::writeStringList(body_obj, "game_versions", game_versions);
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
@ -95,14 +106,29 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes,
return netJob;
}
auto ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob*
Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const
{
auto netJob = new NetJob(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); });
QObject::connect(netJob, &NetJob::finished, [response, netJob] {
delete response;
netJob->deleteLater();
});
return netJob;
}
// https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "relevance", QObject::tr("Sort by Relevance") },
{ 2, "downloads", QObject::tr("Sort by Downloads") },
{ 3, "follows", QObject::tr("Sort by Follows") },
{ 4, "newest", QObject::tr("Sort by Last Updated") },
{ 5, "updated", QObject::tr("Sort by Newest") } };
QList<ResourceAPI::SortingMethod> ModrinthAPI::getSortingMethods() const
{
return s_sorts;
}

View File

@ -1,69 +1,54 @@
// SPDX-FileCopyrightText: 2022-2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* 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 "BuildConfig.h"
#include "modplatform/ModAPI.h"
#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
#include "modplatform/helpers/NetworkResourceAPI.h"
#include <QDebug>
class ModrinthAPI : public NetworkModAPI {
class ModrinthAPI : public NetworkResourceAPI {
public:
auto currentVersion(QString hash,
QString hash_format,
QByteArray* response) -> NetJob::Ptr;
QByteArray* response) -> Task::Ptr;
auto currentVersions(const QStringList& hashes,
QString hash_format,
QByteArray* response) -> NetJob::Ptr;
QByteArray* response) -> Task::Ptr;
auto latestVersion(QString hash,
QString hash_format,
std::list<Version> mcVersions,
ModLoaderTypes loaders,
QByteArray* response) -> NetJob::Ptr;
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes,
QString hash_format,
std::list<Version> mcVersions,
ModLoaderTypes loaders,
QByteArray* response) -> NetJob::Ptr;
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr;
auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
public:
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
for (auto loader : {Forge, Fabric, Quilt})
{
if ((types & loader) || types == Unspecified)
{
l << ModAPI::getModLoaderString(loader);
for (auto loader : {Forge, Fabric, Quilt}) {
if (types & loader) {
l << getModLoaderString(loader);
}
}
if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
l << ModAPI::getModLoaderString(Fabric);
l << getModLoaderString(Fabric);
return l;
}
@ -78,28 +63,54 @@ class ModrinthAPI : public NetworkModAPI {
}
private:
inline auto getModSearchURL(SearchArgs& args) const -> QString override
[[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type)
{
if (!validateModLoaders(args.loaders)) {
qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
return "";
switch (type) {
case ModPlatform::ResourceType::MOD:
return "mod";
default:
qWarning() << "Invalid resource type for Modrinth API!";
break;
}
return QString(BuildConfig.MODRINTH_PROD_URL +
"/search?"
"offset=%1&"
"limit=25&"
"query=%2&"
"index=%3&"
"facets=[[%4],%5[\"project_type:mod\"]]")
.arg(args.offset)
.arg(args.search)
.arg(args.sorting)
.arg(getModLoaderFilters(args.loaders))
.arg(getGameVersionsArray(args.versions));
return "";
}
[[nodiscard]] QString createFacets(SearchArgs const& args) const
{
QStringList facets_list;
if (args.loaders.has_value())
facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value())));
if (args.versions.has_value())
facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value())));
facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type)));
return QString("[%1]").arg(facets_list.join(','));
}
public:
[[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> override
{
if (args.loaders.has_value()) {
if (!validateModLoaders(args.loaders.value())) {
qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
return {};
}
}
QStringList get_arguments;
get_arguments.append(QString("offset=%1").arg(args.offset));
get_arguments.append(QString("limit=25"));
if (args.search.has_value())
get_arguments.append(QString("query=%1").arg(args.search.value()));
if (args.sorting.has_value())
get_arguments.append(QString("index=%1").arg(args.sorting.value().name));
get_arguments.append(QString("facets=%1").arg(createFacets(args)));
return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&');
};
inline auto getModInfoURL(QString& id) const -> QString override
inline auto getInfoURL(QString const& id) const -> std::optional<QString> override
{
return BuildConfig.MODRINTH_PROD_URL + "/project/" + id;
};
@ -109,15 +120,16 @@ class ModrinthAPI : public NetworkModAPI {
return BuildConfig.MODRINTH_PROD_URL + QString("/projects?ids=[\"%1\"]").arg(ids.join("\",\""));
};
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
inline auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> override
{
return QString(BuildConfig.MODRINTH_PROD_URL +
"/project/%1/version?"
"game_versions=[%2]&"
"loaders=[\"%3\"]")
.arg(args.addonId,
getGameVersionsString(args.mcVersions),
getModLoaderStrings(args.loaders).join("\",\""));
QStringList get_arguments;
if (args.mcVersions.has_value())
get_arguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value())));
if (args.loaders.has_value())
get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\"")));
return QString("%1/project/%2/version%3%4")
.arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&'));
};
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
@ -127,12 +139,12 @@ class ModrinthAPI : public NetworkModAPI {
s += QString("\"versions:%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
return s.isEmpty() ? QString() : QString("[%1],").arg(s);
return s.isEmpty() ? QString() : s;
}
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
{
return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt));
return loaders & (Forge | Fabric | Quilt);
}
};

View File

@ -4,12 +4,15 @@
#include "Json.h"
#include "ModDownloadTask.h"
#include "ResourceDownloadTask.h"
#include "modplatform/helpers/HashUtils.h"
#include "tasks/ConcurrentTask.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
@ -34,7 +37,7 @@ void ModrinthCheckUpdate::executeTask()
// Create all hashes
QStringList hashes;
auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first();
auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10);
for (auto* mod : m_mods) {
@ -108,11 +111,13 @@ void ModrinthCheckUpdate::executeTask()
// Sometimes a version may have multiple files, one with "forge" and one with "fabric",
// so we may want to filter it
QString loader_filter;
static auto flags = { ModAPI::ModLoaderType::Forge, ModAPI::ModLoaderType::Fabric, ModAPI::ModLoaderType::Quilt };
for (auto flag : flags) {
if (m_loaders.testFlag(flag)) {
loader_filter = api.getModLoaderString(flag);
break;
if (m_loaders.has_value()) {
static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt };
for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) {
loader_filter = api.getModLoaderString(flag);
break;
}
}
}
@ -152,12 +157,12 @@ void ModrinthCheckUpdate::executeTask()
for (auto& author : mod->authors())
pack.authors.append({ author });
pack.description = mod->description();
pack.provider = ModPlatform::Provider::MODRINTH;
pack.provider = ModPlatform::ResourceProvider::MODRINTH;
auto download_task = new ModDownloadTask(pack, project_ver, m_mods_folder);
auto download_task = new ResourceDownloadTask(pack, project_ver, m_mods_folder);
m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog,
ModPlatform::Provider::MODRINTH, download_task);
ModPlatform::ResourceProvider::MODRINTH, download_task);
}
}
} catch (Json::JsonException& e) {
@ -170,7 +175,7 @@ void ModrinthCheckUpdate::executeTask()
setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(1, 3);
m_net_job = job.get();
m_net_job = qSharedPointerObjectCast<NetJob, Task>(job);
job->start();
lock.exec();

View File

@ -8,7 +8,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
Q_OBJECT
public:
ModrinthCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder)
ModrinthCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{}
@ -19,5 +19,5 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
void executeTask() override;
private:
NetJob* m_net_job = nullptr;
NetJob::Ptr m_net_job = nullptr;
};

View File

@ -202,14 +202,14 @@ bool ModrinthCreationTask::createInstance()
auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", minecraftVersion, true);
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
if (!fabricVersion.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
if (!quiltVersion.isEmpty())
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
if (!forgeVersion.isEmpty())
components->setComponentVersion("net.minecraftforge", forgeVersion);
if (!m_fabric_version.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
if (!m_quilt_version.isEmpty())
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!m_forge_version.isEmpty())
components->setComponentVersion("net.minecraftforge", m_forge_version);
if (m_instIcon != "default") {
instance.setIconKey(m_instIcon);
@ -217,7 +217,9 @@ bool ModrinthCreationTask::createInstance()
instance.setIconKey("modrinth");
}
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
instance.setName(name());
instance.saveNow();
@ -277,7 +279,7 @@ bool ModrinthCreationTask::createInstance()
return ended_well;
}
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_managed_info, bool show_optional_dialog)
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_internal_data, bool show_optional_dialog)
{
try {
auto doc = Json::requireDocument(index_path);
@ -289,7 +291,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
throw JSONValidationError("Unknown game: " + game);
}
if (set_managed_info) {
if (set_internal_data) {
if (m_managed_version_id.isEmpty())
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
@ -365,19 +367,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
files.push_back(file);
}
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
if (name == "minecraft") {
minecraftVersion = Json::requireString(*it, "Minecraft version");
} else if (name == "fabric-loader") {
fabricVersion = Json::requireString(*it, "Fabric Loader version");
} else if (name == "quilt-loader") {
quiltVersion = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") {
forgeVersion = Json::requireString(*it, "Forge version");
} else {
throw JSONValidationError("Unknown dependency type: " + name);
if (set_internal_data) {
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
if (name == "minecraft") {
m_minecraft_version = Json::requireString(*it, "Minecraft version");
} else if (name == "fabric-loader") {
m_fabric_version = Json::requireString(*it, "Fabric Loader version");
} else if (name == "quilt-loader") {
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") {
m_forge_version = Json::requireString(*it, "Forge version");
} else {
throw JSONValidationError("Unknown dependency type: " + name);
}
}
}
} else {

View File

@ -37,12 +37,12 @@ class ModrinthCreationTask final : public InstanceCreationTask {
bool createInstance() override;
private:
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_managed_info = true, bool show_optional_dialog = true);
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_internal_data = true, bool show_optional_dialog = true);
private:
QWidget* m_parent = nullptr;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version;
QString m_managed_id, m_managed_version_id, m_managed_name;
std::vector<Modrinth::File> m_files;

View File

@ -27,13 +27,14 @@
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
// https://docs.modrinth.com/api-spec/#tag/projects/operation/getProject
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::ensureString(obj, "project_id");
if (pack.addonId.toString().isEmpty())
pack.addonId = Json::requireString(obj, "id");
pack.provider = ModPlatform::Provider::MODRINTH;
pack.provider = ModPlatform::ResourceProvider::MODRINTH;
pack.name = Json::requireString(obj, "title");
pack.slug = Json::ensureString(obj, "slug", "");
@ -44,7 +45,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
pack.description = Json::ensureString(obj, "description", "");
pack.logoUrl = Json::requireString(obj, "icon_url");
pack.logoUrl = Json::ensureString(obj, "icon_url", "");
pack.logoName = pack.addonId.toString();
ModPlatform::ModpackAuthor modAuthor;
@ -87,7 +88,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraData.donate.append(donate);
}
pack.extraData.body = Json::ensureString(obj, "body");
pack.extraData.body = Json::ensureString(obj, "body").remove("<br>");
pack.extraDataLoaded = true;
}
@ -95,10 +96,10 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst)
const BaseInstance* inst)
{
QVector<ModPlatform::IndexedVersion> unsortedVersions;
QString mcVersion = (static_cast<MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft");
QString mcVersion = (static_cast<const MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft");
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@ -179,7 +180,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
file.hash = Json::requireString(hash_list, preferred_hash_type);
file.hash_type = preferred_hash_type;
} else {
auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH);
auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH);
for (auto& hash_type : hash_types) {
if (hash_list.contains(hash_type)) {
file.hash = Json::requireString(hash_list, hash_type);

View File

@ -29,7 +29,7 @@ void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
} // namespace Modrinth

View File

@ -97,7 +97,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo
mod.name = mod_pack.name;
mod.filename = mod_version.fileName;
if (mod_pack.provider == ModPlatform::Provider::FLAME) {
if (mod_pack.provider == ModPlatform::ResourceProvider::FLAME) {
mod.mode = "metadata:curseforge";
} else {
mod.mode = "url";
@ -176,11 +176,11 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
in_stream << QString("\n[update]\n");
in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider));
switch (mod.provider) {
case (ModPlatform::Provider::FLAME):
case (ModPlatform::ResourceProvider::FLAME):
in_stream << QString("file-id = %1\n").arg(mod.file_id.toString());
in_stream << QString("project-id = %1\n").arg(mod.project_id.toString());
break;
case (ModPlatform::Provider::MODRINTH):
case (ModPlatform::ResourceProvider::MODRINTH):
addToStream("mod-id", mod.mod_id().toString());
addToStream("version", mod.version().toString());
break;
@ -273,7 +273,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
}
{ // [update] info
using Provider = ModPlatform::Provider;
using Provider = ModPlatform::ResourceProvider;
auto update_table = table["update"];
if (!update_table || !update_table.is_table()) {

View File

@ -49,7 +49,7 @@ class V1 {
QString hash {};
// [update]
ModPlatform::Provider provider {};
ModPlatform::ResourceProvider provider {};
QVariant file_id {};
QVariant project_id {};

View File

@ -172,7 +172,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
auto libraryObject = Json::ensureObject(library, {}, "");
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-'))
if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) && libraryName.contains('-'))
{
QString libraryVersion = libraryName.section(':', 2);
if (!libraryVersion.startsWith("1.7.10-"))

View File

@ -52,7 +52,6 @@ class NetAction : public Task {
virtual ~NetAction() = default;
QUrl url() { return m_url; }
auto index() -> int { return m_index_within_job; }
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
@ -75,9 +74,6 @@ class NetAction : public Task {
public:
shared_qobject_ptr<QNetworkAccessManager> m_network;
/// index within the parent job, FIXME: nuke
int m_index_within_job = 0;
/// the network reply
unique_qobject_ptr<QNetworkReply> m_reply;

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