Compare commits

...

91 Commits
1.3.0 ... 1.3.2

Author SHA1 Message Date
404b9599fd Merge pull request #781 from Scrumplex/chore-bump-1.3.2 2022-06-12 18:51:04 +02:00
4a803e4721 Merge pull request #783 from Scrumplex/fix-avoid-settings-register-warnings
Avoid re-registering InstanceType
2022-06-12 18:50:38 +02:00
2bcc81a75b chore: bump version 2022-06-12 18:23:58 +02:00
0be9244ade Merge pull request #679 from kthchew/fix/win-install-version 2022-06-12 18:23:39 +02:00
a852d29262 Merge pull request #771 from flowln/modrinth_multiple_downloads 2022-06-12 15:41:16 +02:00
7145a55f83 Merge pull request #632 from ryanccn/macos-app-heuristic 2022-06-12 15:41:16 +02:00
4f8915da6c Merge pull request #780 from flowln/guo_ext_2 2022-06-12 15:41:16 +02:00
973c4327e3 Merge pull request #773 from vancez/fix-launch-button 2022-06-12 15:41:16 +02:00
820951bce1 Merge pull request #770 from flowln/technic_links 2022-06-12 15:41:16 +02:00
c8257c632a Merge pull request #764 from Scrumplex/fix-compile-flags 2022-06-12 15:41:16 +02:00
a4d69cce43 Merge pull request #720 from icelimetea/fix-legacy-issues 2022-06-12 15:41:16 +02:00
dfe2e5f44e Merge pull request #709 from Scrumplex/fix-instancetypes
Always store InstanceType
2022-06-12 15:41:16 +02:00
dbdd1c83d9 Merge pull request #732 from MrMelon54/develop 2022-06-12 15:41:16 +02:00
2519bb96f9 Merge pull request #762 from muscaln/flake
Bump flake lock
2022-06-12 15:41:16 +02:00
f169173afa Merge pull request #734 from babbaj/extra-jdks
nix: add package argument for extra jdks
2022-06-12 15:41:16 +02:00
1c70b29b3d Merge pull request #685 from kthchew/fix/technic-quilt
Add Quilt support for Technic modpacks
2022-06-12 15:41:16 +02:00
75b7ca814a Merge pull request #718 from DioEgizio/revert-unfunny-pr 2022-06-12 15:41:16 +02:00
b6b33862ed Prepare for Nix 2.7 (#286)
* Prepare for Nix 2.7

* Fix embarassing oopsie
2022-06-12 15:25:42 +02:00
0dbd70ba9f Merge pull request #701 from jn64/patch-1
Add "mc" keyword to desktop file
2022-06-12 15:25:42 +02:00
7a5a1a9af7 Merge pull request #692 from glowiak/patch-6 2022-06-12 15:25:42 +02:00
938f222791 Merge pull request #682 from istudyatuni/fix-add-account-behaviour 2022-06-12 15:25:42 +02:00
d7fad4bd04 Merge pull request #677 from timoreo22/fix-modrinth-link
Fix modrinth search filters
2022-05-29 18:36:03 +02:00
2746251dcd Fix modrinth search filters 2022-05-29 18:23:34 +02:00
73e46e569f Merge pull request #662 from Scrumplex/chore-bump-1.3.1 2022-05-29 17:05:02 +02:00
4f0f74d201 Merge pull request #675 from muscaln/nix
Nix updates
2022-05-29 11:21:47 -03:00
adf1e1982a fix: remove unnecessary translation (#674) 2022-05-29 16:14:01 +02:00
f5405e835e Merge pull request #624 from ryanccn/global-jvm-args
Make global JVM arguments multi-line
2022-05-29 16:10:55 +02:00
85ee7fb3a4 Merge pull request #672 from Scrumplex/fix-ftblegacy-pack-version
fix: add version to Legacy FTB packs
2022-05-29 16:10:43 +02:00
ee00a5d8ee nix: make LTO optional 2022-05-29 17:07:12 +03:00
65d23fc9b9 Merge pull request #671 from Scrumplex/fix-importing-flame-mmc-packs 2022-05-29 15:15:18 +02:00
3448d7cb70 Merge pull request #659 from Scrumplex/fix-apikey-tos
Clarify terms and conditions for API keys
2022-05-29 18:44:32 +05:30
db1abb02d6 Merge pull request #661 from flowln/wow_curseforge_is_being_uncompetitive_im_so_surprised 2022-05-29 14:42:49 +02:00
36d78577e2 Merge pull request #669 from Scrumplex/fix-binary-version-windows
Set version for Windows binaries
2022-05-29 22:37:23 +10:00
20832682ef Update launcher/ui/pages/global/JavaPage.cpp
Co-authored-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-05-29 20:35:57 +08:00
8e6c592ad9 fix: add version to Legacy FTB packs 2022-05-29 14:28:54 +02:00
0b3115997a fix: fix importing Flame/MMC packs 2022-05-29 14:16:13 +02:00
b07c5982e1 fix: set version for Windows binaries 2022-05-29 13:09:13 +02:00
4a0b0d8735 Merge pull request #653 from muscaln/nix 2022-05-29 13:08:56 +02:00
7b9f67f4b4 Merge pull request #667 from DioEgizio/patch-7 2022-05-29 13:07:25 +02:00
577f17c916 remove vista support from the manifest
we don't support qt <5.12 anymore anyways
2022-05-29 09:45:20 +02:00
d4c1d62781 Update launcher/ui/pages/global/JavaPage.cpp
Co-authored-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2022-05-29 12:15:20 +08:00
f0ec165d42 feat: add warning of non-whitelisted URLs instead of a hard fail
Based on people's votes on Discord :^)
2022-05-28 18:04:16 -03:00
abd240468e clean up validateDownloadUrl 2022-05-28 17:11:55 -03:00
b5e00027d1 change: add 'gitlab.com' to whitelisted Modrinth modpack urls 2022-05-28 17:01:58 -03:00
1698554024 debug: add non-translated debug logging for 'non-whitelisted url' fails 2022-05-28 17:01:57 -03:00
f4604bbf79 change: update whitelisted hosts in Modrinth modpacks 2022-05-28 17:01:52 -03:00
699ad316f0 Rework curseforge download (#611)
* Use the bulk endpoint on mod resolution for faster download
* Search on modrinth for api blocked mods
* Display a dialog for manually downloading blocked mods
2022-05-28 21:53:12 +02:00
fcbe233fdb Merge pull request #651 from Scrumplex/fix-mnemonic-apipage
Fix mnemonics in APIPage
2022-05-28 15:46:38 +02:00
4af8a9ed2d Merge pull request #650 from flowln/modrinth_icons
Fix modpack icon importing with non-standard icon paths
2022-05-28 15:45:26 +02:00
80627b4f89 chore: bump version 2022-05-28 15:42:54 +02:00
83078cd49a Merge pull request #657 from JJL772/fix/misc-fixes
Fix crash when aborting instance import
2022-05-28 15:38:36 +02:00
53e0d13142 Merge pull request #625 from ryanccn/light-macos-icon
Use light background macOS icon
2022-05-28 15:36:26 +02:00
85901082a2 Merge pull request #639 from Scrumplex/fix-prio-modpack-formats
Prefer stricter modpack formats during import
2022-05-28 15:36:18 +02:00
f19e27e875 Merge pull request #629 from kthchew/feature/desktop-shortcut
Add desktop shortcut to Windows installer
2022-05-28 15:36:12 +02:00
c864046cee Merge pull request #623 from ryanccn/clang-format
Add a .clang-format
2022-05-28 15:36:01 +02:00
2d68235e55 Merge branch 'develop' into global-jvm-args 2022-05-28 21:15:19 +08:00
2be8100e7c Merge branch 'develop' into global-jvm-args 2022-05-28 21:15:00 +08:00
ab3e2562db fix: clarify terms and conditions for API keys 2022-05-28 13:52:22 +02:00
123d6c72e4 nix: fix nix-build 2022-05-28 11:32:16 +03:00
0ea2135aa5 nix: initial support for qt6 2022-05-28 11:32:13 +03:00
48e20cb5f7 Fix crash when aborting instance import
Also turned a loop var into a reference to avoid copies on each iteration
2022-05-27 16:41:57 -07:00
0ffe0b6894 nix: move files to nix/ 2022-05-27 22:56:42 +03:00
338156500b nix: override msa id via cmake flag 2022-05-27 22:56:42 +03:00
bfd9bd43c9 flake.lock: update
• Updated input 'flake-compat':
    'github:edolstra/flake-compat/64a525ee38886ab9028e6f61790de0832aa3ef03' (2022-03-25)
  → 'github:edolstra/flake-compat/b4a34015c698c7793d592d66adbab377907a2be8' (2022-04-19)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/30d3d79b7d3607d56546dd2a6b49e156ba0ec634' (2022-03-25)
  → 'github:nixos/nixpkgs/41cc1d5d9584103be4108c1815c350e07c807036' (2022-05-23)
• Removed input 'quazip'
2022-05-27 22:56:42 +03:00
283e50e670 nix: use nixpkgs's quazip 2022-05-27 22:56:40 +03:00
6fb5bb6a5e fix: fix mnemonics in APIPage 2022-05-27 14:50:06 +02:00
5d3bef32ca fix: use absolute path when installing icons 2022-05-27 09:15:32 -03:00
3ff26d5cfe Merge pull request #642 from ryanccn/metaserver-show-default
Show default metaserver in settings
2022-05-26 20:09:07 -03:00
88c1e504b0 Merge pull request #618 from DioEgizio/patch-6
fix appimage not having imageformats
2022-05-26 20:03:03 -03:00
0263677e1f fix: prefer stricter modpack formats during import
Flame modpacks use "manifest.json" as their only characteristic for
identification. Some modpacks might have other files called
"manifest.json", which is why we should prefer modpack formats that have
a stricter structure.
2022-05-26 22:41:20 +02:00
940455f81c Merge pull request #645 from PolyMC/revert-609-feature/fix-blocked-modpacks 2022-05-26 19:20:39 +02:00
f541ea659c better new icon 2022-05-26 18:16:32 +08:00
938cae1130 revert: remove CurseForge workaround for packs too
Partial revert. Handles missing download URLs.
2022-05-25 23:14:13 +02:00
7f73e57c67 Merge pull request #630 from jamierocks/h-atl-depends 2022-05-25 14:52:34 +02:00
e50ec31351 fix 2022-05-25 14:44:47 +08:00
a28fa219d7 fix indent width 2022-05-25 14:21:09 +08:00
8a1ba03bcb show default metaserver 2022-05-25 11:46:15 +08:00
e8b7e70ec9 Merge pull request #637 from byquanton/patch-1 2022-05-24 20:53:19 +02:00
67c5aa0be9 Update org.polymc.PolyMC.metainfo.xml.in
Should fix Flatpak/Flathub build
2022-05-24 17:44:23 +02:00
17b30b2ae2 clean up .clang-format 2022-05-24 22:37:00 +08:00
3cc26b15a1 Merge pull request #631 from kthchew/fix/quazip-warn 2022-05-24 10:25:04 +02:00
fce5c57548 Silence CMake QuaZip not found warnings
These are expected most of the time, and thus just noise.
2022-05-23 17:27:35 -04:00
4ee5264e24 ATLauncher: Delete files from configs if they conflict with a mod 2022-05-23 21:44:01 +01:00
101ca60b2b ATLauncher: Handle extra arguments depends 2022-05-23 20:57:15 +01:00
f28a0aa666 ATLauncher: Handle main class depends 2022-05-23 20:57:14 +01:00
997bf91442 Add desktop shortcut to Windows installer 2022-05-23 14:15:49 -04:00
c3e6b1aa8a use light bigsur icon 2022-05-23 18:40:46 +08:00
6d0ea13f97 make JVM args PlainTextEdit 2022-05-23 16:52:12 +08:00
2a0018e730 add a .clang-format 2022-05-23 08:29:30 +08:00
2be583ad4d Merge branch 'PolyMC:develop' into patch-6 2022-05-22 20:03:10 +02:00
c988b4d213 fix appimage not having imageformats
fixes stuff like the iris icon
2022-05-22 17:26:27 +02:00
65 changed files with 1410 additions and 423 deletions

16
.clang-format Normal file
View File

@ -0,0 +1,16 @@
---
Language: Cpp
BasedOnStyle: Chromium
IndentWidth: 4
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AllowShortIfStatementsOnASingleLine: false
BraceWrapping:
AfterFunction: true
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBraces: Custom
BreakConstructorInitializers: BeforeComma
ColumnLimit: 140
Cpp11BracedListStyle: false

View File

@ -113,12 +113,15 @@ jobs:
if: runner.os == 'Linux' && matrix.appimage == true
run: |
sudo add-apt-repository ppa:savoury1/qt-5-15
sudo add-apt-repository ppa:savoury1/kde-5-80
sudo add-apt-repository ppa:savoury1/gpg
sudo add-apt-repository ppa:savoury1/ffmpeg4
- name: Install Qt (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get -y update
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.appimage == true
@ -203,7 +206,7 @@ jobs:
shell: msys2 {0}
run: |
cd ${{ env.INSTALL_DIR }}
makensis -NOCD "-DVERSION=${{ env.VERSION }}" "-DMUI_ICON=${{ github.workspace }}/program_info/polymc.ico" "-XOutFile ${{ github.workspace }}/PolyMC-Setup.exe" "${{ github.workspace }}/program_info/win_install.nsi"
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
- name: Package (Linux)
if: runner.os == 'Linux' && matrix.appimage != true

1
.gitignore vendored
View File

@ -15,7 +15,6 @@ CMakeLists.txt.user.*
/.settings
/.idea
/.vscode
.clang-format
cmake-build-*/
Debug

View File

@ -45,8 +45,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
# set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_DEBUG "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
option(ENABLE_LTO "Enable Link Time Optimization" off)
@ -74,7 +73,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 3)
set(Launcher_VERSION_HOTFIX 0)
set(Launcher_VERSION_HOTFIX 2)
# Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@ -91,13 +90,6 @@ set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch L
# Imgur API Client ID
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
# MSA Client ID
set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
# CurseForge API Key
# CHANGE THIS IF YOU FORK THIS PROJECT!
set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key")
# Bug tracker URL
set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.")
@ -119,6 +111,22 @@ set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRI
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against")
# API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
# of these platforms, please change these API keys beforehand.
# Be aware that if you were to use these API keys for malicious purposes they might get revoked, which might cause
# breakage to thousands of users.
# If you don't plan to use these features of this software, you can just remove these values.
# By using this key in your builds you accept the terms of use laid down in
# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use
set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
# By using this key in your builds you accept the terms and conditions laid down in
# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions
# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key")
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
@ -128,6 +136,8 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
set(Launcher_RELEASE_VERSION_NAME4 "${Launcher_RELEASE_VERSION_NAME}.0")
set(Launcher_RELEASE_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_HOTFIX},0")
string(TIMESTAMP TODAY "%Y-%m-%d")
set(Launcher_RELEASE_TIMESTAMP "${TODAY}")
@ -143,7 +153,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 1.3)
find_package(QuaZip-Qt5 1.3 QUIET)
endif()
if (NOT QuaZip-Qt5_FOUND)
set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE)

View File

@ -82,8 +82,11 @@ To modify download information or change packaging information send a pull reque
Do whatever you want, we don't care. Just follow the license. If you have any questions about this feel free to ask in an issue.
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.
All launcher code is available under the GPL-3.0-only license.
[Source for the website](https://github.com/PolyMC/polymc.github.io) is hosted under the AGPL-3.0-or-later License.
The logo and related assets are under the CC BY-SA 4.0 license.

View File

@ -1 +1 @@
(import packages/nix/flake-compat.nix).defaultNix
(import nix/flake-compat.nix).defaultNix

31
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1648199409,
"narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=",
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "64a525ee38886ab9028e6f61790de0832aa3ef03",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github"
},
"original": {
@ -34,11 +34,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1648219316,
"narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=",
"lastModified": 1654665288,
"narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634",
"rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8",
"type": "github"
},
"original": {
@ -48,28 +48,11 @@
"type": "github"
}
},
"quazip": {
"flake": false,
"locked": {
"lastModified": 1643049383,
"narHash": "sha256-LcJY6yd6GyeL7X5MP4L94diceM1TYespWByliBsjK98=",
"owner": "stachenov",
"repo": "quazip",
"rev": "09ec1d10c6d627f895109b21728dda000cbfa7d1",
"type": "github"
},
"original": {
"owner": "stachenov",
"repo": "quazip",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus",
"nixpkgs": "nixpkgs",
"quazip": "quazip"
"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:multimc/libnbtplusplus"; flake = false; };
quazip = { url = "github:stachenov/quazip"; flake = false; };
};
outputs = { self, nixpkgs, libnbtplusplus, quazip, ... }:
outputs = { self, nixpkgs, libnbtplusplus, ... }:
let
# Generate a user-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate;
@ -23,11 +22,17 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
in
{
packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./packages/nix/polymc { inherit version self quazip libnbtplusplus; }; });
defaultPackage = forAllSystems (system: self.packages.${system}.polymc);
packages = forAllSystems (system: rec {
polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
polymc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
apps = forAllSystems (system: { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; });
defaultApp = forAllSystems (system: self.apps.${system}.polymc);
default = polymc;
});
defaultPackage = forAllSystems (system: self.packages.${system}.default);
apps = forAllSystems (system: rec { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; default = polymc; });
defaultApp = forAllSystems (system: self.apps.${system}.default);
overlay = final: prev: { polymc = self.defaultPackage.${final.system}; };
};

View File

@ -871,6 +871,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_mcedit.reset(new MCEditTool(m_settings));
}
#ifdef Q_OS_MACOS
connect(this, &Application::clickedOnDock, [this]() {
this->showMainWindow();
});
#endif
connect(this, &Application::aboutToQuit, [this](){
if(m_instances)
{
@ -954,6 +960,21 @@ bool Application::createSetupWizard()
return false;
}
bool Application::event(QEvent* event) {
#ifdef Q_OS_MACOS
if (event->type() == QEvent::ApplicationStateChange) {
auto ev = static_cast<QApplicationStateChangeEvent*>(event);
if (m_prevAppState == Qt::ApplicationActive
&& ev->applicationState() == Qt::ApplicationActive) {
emit clickedOnDock();
}
m_prevAppState = ev->applicationState();
}
#endif
return QApplication::event(event);
}
void Application::setupWizardFinished(int status)
{
qDebug() << "Wizard result =" << status;

View File

@ -94,6 +94,8 @@ public:
Application(int &argc, char **argv);
virtual ~Application();
bool event(QEvent* event) override;
std::shared_ptr<SettingsObject> settings() const {
return m_settings;
}
@ -181,6 +183,10 @@ signals:
void globalSettingsAboutToOpen();
void globalSettingsClosed();
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
public slots:
bool launch(
InstancePtr instance,
@ -238,6 +244,10 @@ private:
QString m_rootPath;
Status m_status = Application::StartingUp;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
#endif
#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
bool consoleAttached = false;

View File

@ -59,7 +59,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("InstanceType", "OneSix");
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
// a locally stored instance
if (!m_settings->getSetting("InstanceType"))
m_settings->registerSetting("InstanceType", "");
// Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);

View File

@ -128,6 +128,8 @@ set(NET_SOURCES
net/PasteUpload.h
net/Sink.h
net/Validator.h
net/Upload.cpp
net/Upload.h
)
# Game launch logic
@ -837,6 +839,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/SkinUploadDialog.h
ui/dialogs/ModDownloadDialog.cpp
ui/dialogs/ModDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h
# GUI - widgets
ui/widgets/Common.cpp
@ -940,6 +944,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
)
qt5_add_resources(LAUNCHER_RESOURCES
@ -958,7 +963,7 @@ qt5_add_resources(LAUNCHER_RESOURCES
######## Windows resource files ########
if(WIN32)
set(LAUNCHER_RCS ../${Launcher_Branding_WindowsRC})
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
endif()
# Add executable

View File

@ -454,4 +454,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
return false;
#endif
}
QStringList listFolderPaths(QDir root)
{
auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); };
QStringList entries;
root.refresh();
for (auto entry : root.entryInfoList(QDir::Filter::Files)) {
entries.append(createAbsPath(entry));
}
for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) {
entries.append(listFolderPaths(createAbsPath(entry)));
}
return entries;
}
bool overrideFolder(QString overwritten_path, QString override_path)
{
if (!FS::ensureFolderPathExists(overwritten_path))
return false;
QStringList paths_to_override;
QDir root_override (override_path);
for (auto file : listFolderPaths(root_override)) {
QString destination = file;
destination.replace(override_path, overwritten_path);
qDebug() << QString("Applying override %1 in %2").arg(file, destination);
if (QFile::exists(destination))
QFile::remove(destination);
if (!QFile::rename(file, destination)) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination);
return false;
}
}
return true;
}
}

View File

@ -124,4 +124,8 @@ QString getDesktopDir();
// call it *name* and assign it the icon *icon*
// return true if operation succeeded
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
// Equivalent to doing QDir::rename, but allowing for overrides
bool overrideFolder(QString overwritten_path, QString override_path);
}

View File

@ -60,9 +60,9 @@
#include "net/ChecksumValidator.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ScrollMessageBox.h"
#include <algorithm>
#include <iterator>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{
@ -72,7 +72,8 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
bool InstanceImportTask::abort()
{
m_filesNetJob->abort();
if (m_filesNetJob)
m_filesNetJob->abort();
m_extractFuture.cancel();
return false;
@ -135,18 +136,20 @@ void InstanceImportTask::processZipPack()
return;
}
QStringList blacklist = {"instance.cfg", "manifest.json"};
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json");
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
QString modrinthFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "modrinth.index.json");
QuaZipDir packZipDir(m_packZip.get());
// https://docs.modrinth.com/docs/modpacks/format_definition/#storage
bool modrinthFound = packZipDir.exists("/modrinth.index.json");
bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json");
QString root;
if(!mmcFound.isNull())
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
if(modrinthFound)
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcFound;
root = mmcFound;
m_modpackType = ModpackType::MultiMC;
// process as Modrinth pack
qDebug() << "Modrinth:" << modrinthFound;
m_modpackType = ModpackType::Modrinth;
}
else if (technicFound)
{
@ -156,19 +159,25 @@ void InstanceImportTask::processZipPack()
extractDir.cd(".minecraft");
m_modpackType = ModpackType::Technic;
}
else if(!flameFound.isNull())
else
{
// process as Flame pack
qDebug() << "Flame:" << flameFound;
root = flameFound;
m_modpackType = ModpackType::Flame;
}
else if(!modrinthFound.isNull())
{
// process as Modrinth pack
qDebug() << "Modrinth:" << modrinthFound;
root = modrinthFound;
m_modpackType = ModpackType::Modrinth;
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
if (!mmcRoot.isNull())
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot;
m_modpackType = ModpackType::MultiMC;
}
else if(!flameRoot.isNull())
{
// process as Flame pack
qDebug() << "Flame:" << flameRoot;
root = flameRoot;
m_modpackType = ModpackType::Flame;
}
}
if(m_modpackType == ModpackType::Unknown)
{
@ -385,61 +394,136 @@ void InstanceImportTask::processFlame()
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
{
auto results = m_modIdResolver->getResults();
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for(auto result: results.files)
{
QString filename = result.fileName;
if(!result.required)
{
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath , relpath);
switch(result.type)
{
case Flame::File::Type::Folder:
{
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod:
{
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
break;
}
case Flame::File::Type::Modpack:
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
case Flame::File::Type::Unknown:
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
break;
//first check for blocked mods
QString text;
auto anyBlocked = false;
for(const auto& result: results.files.values()) {
if (!result.resolved || result.url.isEmpty()) {
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
anyBlocked = true;
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
m_filesNetJob.reset();
emitSucceeded();
if(anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list";
auto message_dialog = new ScrollMessageBox(m_parent,
tr("Blocked mods found"),
tr("The following mods were blocked on third party launchers.<br/>"
"You will need to manually download them and add them to the modpack"),
text);
message_dialog->setModal(true);
message_dialog->show();
connect(message_dialog, &QDialog::rejected, [&]() {
m_modIdResolver.reset();
emitFailed("Canceled");
});
connect(message_dialog, &QDialog::accepted, [&]() {
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (const auto &result: m_modIdResolver->getResults().files) {
QString filename = result.fileName;
if (!result.required) {
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath, relpath);
switch (result.type) {
case Flame::File::Type::Folder: {
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
}
break;
}
case Flame::File::Type::Modpack:
logWarning(
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
case Flame::File::Type::Unknown:
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
break;
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
});
}else{
//TODO extract to function ?
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (const auto &result: m_modIdResolver->getResults().files) {
QString filename = result.fileName;
if (!result.required) {
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath, relpath);
switch (result.type) {
case Flame::File::Type::Folder: {
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
}
break;
}
case Flame::File::Type::Modpack:
logWarning(
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
case Flame::File::Type::Unknown:
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
break;
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
{
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}
);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
@ -498,6 +582,7 @@ void InstanceImportTask::processMultiMC()
emitSucceeded();
}
// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files;
@ -515,29 +600,33 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
for (auto& obj : jsonFiles) {
for (auto modInfo : jsonFiles) {
Modrinth::File file;
file.path = Json::requireString(obj, "path");
file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(obj, "env");
QString support = Json::ensureString(env, "client", "unsupported");
if (support == "unsupported") {
continue;
} else if (support == "optional") {
// TODO: Make a review dialog for choosing which ones the user wants!
if (!had_optional) {
had_optional = true;
auto info = CustomMessageBox::selectable(
m_parent, tr("Optional mod detected!"),
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information);
info->exec();
auto env = Json::ensureObject(modInfo, "env");
// 'env' field is optional
if (!env.isEmpty()) {
QString support = Json::ensureString(env, "client", "unsupported");
if (support == "unsupported") {
continue;
} else if (support == "optional") {
// TODO: Make a review dialog for choosing which ones the user wants!
if (!had_optional) {
had_optional = true;
auto info = CustomMessageBox::selectable(
m_parent, tr("Optional mod detected!"),
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
QMessageBox::Information);
info->exec();
}
if (file.path.endsWith(".jar"))
file.path += ".disabled";
}
if (file.path.endsWith(".jar"))
file.path += ".disabled";
}
QJsonObject hashes = Json::requireObject(obj, "hashes");
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
QString hash;
QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha1");
@ -555,12 +644,28 @@ void InstanceImportTask::processModrinth()
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) {
throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
auto download_arr = Json::ensureArray(modInfo, "downloads");
for(auto download : download_arr) {
qWarning() << download.toString();
bool is_last = download.toString() == download_arr.last().toString();
auto download_url = QUrl(download.toString());
if (!download_url.isValid()) {
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
.arg(download_url.toString(), file.path);
if(is_last && file.downloads.isEmpty())
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
else {
file.downloads.push_back(download_url);
}
}
files.push_back(file);
}
@ -592,15 +697,25 @@ void InstanceImportTask::processModrinth()
return;
}
QString overridePath = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) {
QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
if (!QFile::rename(overridePath, mcPath)) {
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(override_path)) {
if (!QFile::rename(override_path, mcPath)) {
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return;
}
}
// Do client overrides
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(client_override_path)) {
if (!FS::overrideFolder(mcPath, client_override_path)) {
emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
return;
}
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
@ -625,13 +740,24 @@ void InstanceImportTask::processModrinth()
instance.saveNow();
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (auto &file : files)
for (auto file : files)
{
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
qDebug() << "Will download" << file.download << "to" << path;
auto dl = Net::Download::makeFile(file.download, path);
qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
if (file.downloads.size() > 0) {
// FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :)
connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
dl->succeeded();
});
}
}
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{

View File

@ -42,6 +42,7 @@
#include <QFutureWatcher>
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
#include "modplatform/flame/PackManifest.h"
#include <nonstd/optional>
@ -59,6 +60,10 @@ public:
bool canAbort() const override { return true; }
bool abort() override;
const QVector<Flame::File> &getBlockedFiles() const
{
return m_blockedMods;
}
protected:
//! Entry point for tasks.
@ -87,6 +92,7 @@ private: /* data */
std::unique_ptr<QuaZip> m_packZip;
QFuture<nonstd::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
QVector<Flame::File> m_blockedMods;
enum class ModpackType{
Unknown,
MultiMC,

View File

@ -547,8 +547,20 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst;
// TODO: Handle incompatible instances
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
instanceSettings->registerSetting("InstanceType", "");
QString inst_type = instanceSettings->get("InstanceType").toString();
// NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty())
{
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}

View File

@ -105,6 +105,11 @@ void LaunchController::decideAccount()
// Open the account manager.
APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts");
}
else if (reply == QMessageBox::No)
{
// Do not open "profile select" dialog.
return;
}
}
m_accountToUse = accounts->defaultAccount();

View File

@ -93,7 +93,7 @@ void UpdateController::installUpdates()
qDebug() << "Installing updates.";
#ifdef Q_OS_WIN
QString finishCmd = QApplication::applicationFilePath();
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined (Q_OS_OPENBSD)
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME);
#elif defined Q_OS_MAC
QString finishCmd = QApplication::applicationFilePath();

View File

@ -56,6 +56,15 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
emit iconUpdated({});
}
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
reindex();
}
void IconList::directoryChanged(const QString &path)
{
QDir new_dir (path);
@ -141,6 +150,8 @@ void IconList::directoryChanged(const QString &path)
emit iconUpdated(key);
}
}
sortIconList();
}
void IconList::fileChanged(const QString &path)
@ -273,7 +284,7 @@ void IconList::installIcons(const QStringList &iconFiles)
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
@ -290,7 +301,7 @@ void IconList::installIcon(const QString &file, const QString &name)
if(!fileinfo.isReadable() || !fileinfo.isFile())
return;
QString target = FS::PathCombine(m_dir.dirName(), name);
QString target = FS::PathCombine(getDirectory(), name);
QFile::copy(file, target);
}

View File

@ -71,6 +71,7 @@ private:
// hide assign op
IconList &operator=(const IconList &) = delete;
void reindex();
void sortIconList();
public slots:
void directoryChanged(const QString &path);

View File

@ -27,10 +27,6 @@ int main(int argc, char *argv[])
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
// initialize Qt
Application app(argc, argv);

View File

@ -168,6 +168,8 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
m_settings->set("InstanceType", "OneSix");
m_components.reset(new PackProfile(this));
}

View File

@ -68,7 +68,7 @@ class ModAPI {
{
QString s;
for(auto& ver : mcVersions){
s += QString("%1,").arg(ver.toString());
s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
return s;

View File

@ -414,7 +414,31 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
{
if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) {
return true;
}
auto mainClass = m_version.mainClass.mainClass;
auto extraArguments = m_version.extraArguments.arguments;
auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty();
auto hasExtraArgumentsDepends = !m_version.extraArguments.depends.isEmpty();
if (hasMainClassDepends || hasExtraArgumentsDepends) {
QSet<QString> mods;
for (const auto& item : m_version.mods) {
mods.insert(item.name);
}
if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) {
mainClass = "";
}
if (hasExtraArgumentsDepends && !mods.contains(m_version.extraArguments.depends)) {
extraArguments = "";
}
}
if (mainClass.isEmpty() && extraArguments.isEmpty()) {
return true;
}
@ -442,12 +466,12 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
auto f = std::make_shared<VersionFile>();
f->name = m_pack + " " + m_version_name;
if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) {
f->mainClass = m_version.mainClass;
if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) {
f->mainClass = mainClass;
}
// Parse out tweakers
auto args = m_version.extraArguments.split(" ");
auto args = extraArguments.split(" ");
QString previous;
for(auto arg : args) {
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
@ -757,6 +781,17 @@ bool PackInstallTask::extractMods(
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
auto &from = iter.key();
auto &to = iter.value();
// If the file already exists, assume the mod is the correct copy - and remove
// the copy from the Configs.zip
QFileInfo fileInfo(to);
if (fileInfo.exists()) {
if (!QFile::remove(to)) {
qWarning() << "Failed to delete" << to;
return false;
}
}
FS::copy fileCopyOperation(from, to);
if(!fileCopyOperation()) {
qWarning() << "Failed to copy" << from << "to" << to;

View File

@ -212,6 +212,18 @@ static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj
m.update = Json::ensureString(obj, "update", "");
}
static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObject& obj)
{
m.mainClass = Json::ensureString(obj, "mainClass", "");
m.depends = Json::ensureString(obj, "depends", "");
}
static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, QJsonObject& obj)
{
a.arguments = Json::ensureString(obj, "arguments", "");
a.depends = Json::ensureString(obj, "depends", "");
}
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
{
v.version = Json::requireString(obj, "version");
@ -220,12 +232,12 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
if(obj.contains("mainClass")) {
auto main = Json::requireObject(obj, "mainClass");
v.mainClass = Json::ensureString(main, "mainClass", "");
loadVersionMainClass(v.mainClass, main);
}
if(obj.contains("extraArguments")) {
auto arguments = Json::requireObject(obj, "extraArguments");
v.extraArguments = Json::ensureString(arguments, "arguments", "");
loadVersionExtraArguments(v.extraArguments, arguments);
}
if(obj.contains("loader")) {

View File

@ -150,13 +150,25 @@ struct VersionMessages
QString update;
};
struct PackVersionMainClass
{
QString mainClass;
QString depends;
};
struct PackVersionExtraArguments
{
QString arguments;
QString depends;
};
struct PackVersion
{
QString version;
QString minecraft;
bool noConfigs;
QString mainClass;
QString extraArguments;
PackVersionMainClass mainClass;
PackVersionExtraArguments extraArguments;
VersionLoader loader;
QVector<VersionLibrary> libraries;

View File

@ -1,7 +1,9 @@
#include "FileResolvingTask.h"
#include "Json.h"
Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest& toProcess)
#include "Json.h"
#include "net/Upload.h"
Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess)
: m_network(network), m_toProcess(toProcess)
{}
@ -10,40 +12,116 @@ void Flame::FileResolvingTask::executeTask()
setStatus(tr("Resolving mod IDs..."));
setProgress(0, m_toProcess.files.size());
m_dljob = new NetJob("Mod id resolver", m_network);
results.resize(m_toProcess.files.size());
int index = 0;
for (auto& file : m_toProcess.files) {
auto projectIdStr = QString::number(file.projectId);
auto fileIdStr = QString::number(file.fileId);
QString metaurl = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(projectIdStr, fileIdStr);
auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]);
m_dljob->addNetAction(dl);
index++;
}
result.reset(new QByteArray());
//build json data to send
QJsonObject object;
object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
l.push_back(s.fileId);
return l;
}));
QByteArray data = Json::toText(object);
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data);
m_dljob->addNetAction(dl);
connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished);
m_dljob->start();
}
void Flame::FileResolvingTask::netJobFinished()
{
bool failed = false;
int index = 0;
for (auto& bytes : results) {
auto& out = m_toProcess.files[index];
// job to check modrinth for blocked projects
auto job = new NetJob("Modrinth check", m_network);
blockedProjects = QMap<File *,QByteArray *>();
auto doc = Json::requireDocument(*result);
auto array = Json::requireArray(doc.object()["data"]);
for (QJsonValueRef file : array) {
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
auto& out = m_toProcess.files[fileid];
try {
failed &= (!out.parseFromBytes(bytes));
out.parseFromObject(Json::requireObject(file));
} catch (const JSONValidationError& e) {
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
qCritical() << "JSON:";
qCritical() << bytes;
failed = true;
qDebug() << "Blocked mod on curseforge" << out.fileName;
auto hash = out.hash;
if(!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = new QByteArray();
auto dl = Net::Download::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
out.resolved = true;
});
job->addNetAction(dl);
blockedProjects.insert(&out, output);
}
}
index++;
}
if (!failed) {
emitSucceeded();
connect(job, &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
job->start();
}
void Flame::FileResolvingTask::modrinthCheckFinished() {
qDebug() << "Finished with blocked mods : " << blockedProjects.size();
for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) {
auto &out = *it;
auto bytes = blockedProjects[out];
if (!out->resolved) {
delete bytes;
continue;
}
QJsonDocument doc = QJsonDocument::fromJson(*bytes);
auto obj = doc.object();
auto array = Json::requireArray(obj,"files");
for (auto file: array) {
auto fileObj = Json::requireObject(file);
auto primary = Json::requireBoolean(fileObj,"primary");
if (primary) {
out->url = Json::requireUrl(fileObj,"url");
qDebug() << "Found alternative on modrinth " << out->fileName;
break;
}
}
delete bytes;
}
//copy to an output list and filter out projects found on modrinth
auto block = new QList<File *>();
auto it = blockedProjects.keys();
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
return !f->resolved;
});
//Display not found mods early
if (!block->empty()) {
//blocked mods found, we need the slug for displaying.... we need another job :D !
auto slugJob = new NetJob("Slug Job", m_network);
auto slugs = QVector<QByteArray>(block->size());
auto index = 0;
for (auto fileInfo: *block) {
auto projectId = fileInfo->projectId;
slugs[index] = QByteArray();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::Download::makeByteArray(url, &slugs[index]);
slugJob->addNetAction(dl);
index++;
}
connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() {
slugJob->deleteLater();
auto index = 0;
for (const auto &slugResult: slugs) {
auto json = QJsonDocument::fromJson(slugResult);
auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
"websiteUrl");
auto mod = block->at(index);
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
mod->websiteUrl = link;
index++;
}
emitSucceeded();
});
slugJob->start();
} else {
emitFailed(tr("Some mod ID resolving tasks failed."));
emitSucceeded();
}
}

View File

@ -10,7 +10,7 @@ class FileResolvingTask : public Task
{
Q_OBJECT
public:
explicit FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest &toProcess);
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
virtual ~FileResolvingTask() {};
const Flame::Manifest &getResults() const
@ -27,7 +27,11 @@ protected slots:
private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network;
Flame::Manifest m_toProcess;
QVector<QByteArray> results;
std::shared_ptr<QByteArray> result;
NetJob::Ptr m_dljob;
void modrinthCheckFinished();
QMap<File *, QByteArray *> blockedProjects;
};
}

View File

@ -65,16 +65,12 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
// pick the latest version supported
file.mcVersion = versionArray[0].toString();
file.version = Json::requireString(version, "displayName");
file.fileName = Json::requireString(version, "fileName");
file.downloadUrl = Json::ensureString(version, "downloadUrl");
if(file.downloadUrl.isEmpty()){
//FIXME : HACK, MAY NOT WORK FOR LONG
file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3")
.arg(QString::number(QString::number(file.fileId).leftRef(4).toInt())
,QString::number(QString::number(file.fileId).rightRef(3).toInt())
,QUrl::toPercentEncoding(file.fileName));
// only add if we have a download URL (third party distribution is enabled)
if (!file.downloadUrl.isEmpty()) {
unsortedVersions.append(file);
}
unsortedVersions.append(file);
}
auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; };

View File

@ -18,7 +18,6 @@ struct IndexedVersion {
QString version;
QString mcVersion;
QString downloadUrl;
QString fileName;
};
struct IndexedPack

View File

@ -41,7 +41,7 @@ static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest)
auto obj = Json::requireObject(item);
Flame::File file;
loadFileV1(file, obj);
m.files.append(file);
m.files.insert(file.fileId,file);
}
m.overrides = Json::ensureString(manifest, "overrides", "overrides");
}
@ -61,21 +61,9 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath)
loadManifestV1(m, obj);
}
bool Flame::File::parseFromBytes(const QByteArray& bytes)
bool Flame::File::parseFromObject(const QJsonObject& obj)
{
auto doc = Json::requireDocument(bytes);
if (!doc.isObject()) {
throw JSONValidationError(QString("data is not an object? that's not supposed to happen"));
}
auto obj = Json::ensureObject(doc.object(), "data");
fileName = Json::requireString(obj, "fileName");
QString rawUrl = Json::requireString(obj, "downloadUrl");
url = QUrl(rawUrl, QUrl::TolerantMode);
if (!url.isValid()) {
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
}
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
// It is also optional
type = File::Type::SingleFile;
@ -87,6 +75,25 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
// this is probably a mod, dunno what else could modpacks download
targetFolder = "mods";
}
// get the hash
hash = QString();
auto hashes = Json::ensureArray(obj, "hashes");
for(QJsonValueRef item : hashes) {
auto hobj = Json::requireObject(item);
auto algo = Json::requireInteger(hobj, "algo");
auto value = Json::requireString(hobj, "value");
if (algo == 1) {
hash = value;
}
}
// may throw, if the project is blocked
QString rawUrl = Json::ensureString(obj, "downloadUrl");
url = QUrl(rawUrl, QUrl::TolerantMode);
if (!url.isValid()) {
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
}
resolved = true;
return true;

View File

@ -2,19 +2,24 @@
#include <QString>
#include <QVector>
#include <QMap>
#include <QUrl>
#include <QJsonObject>
namespace Flame
{
struct File
{
// NOTE: throws JSONValidationError
bool parseFromBytes(const QByteArray &bytes);
bool parseFromObject(const QJsonObject& object);
int projectId = 0;
int fileId = 0;
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
bool required = true;
QString hash;
// NOTE: only set on blocked files ! Empty otherwise.
QString websiteUrl;
// our
bool resolved = false;
@ -54,7 +59,8 @@ struct Manifest
QString name;
QString version;
QString author;
QVector<Flame::File> files;
//File id -> File
QMap<int,Flame::File> files;
QString overrides;
};

View File

@ -79,11 +79,11 @@ class ModrinthAPI : public NetworkModAPI {
{
return QString(BuildConfig.MODRINTH_PROD_URL +
"/project/%1/version?"
"game_versions=[%2]"
"game_versions=[%2]&"
"loaders=[\"%3\"]")
.arg(args.addonId)
.arg(getGameVersionsString(args.mcVersions))
.arg(getModLoaderStrings(args.loaders).join("\",\""));
.arg(args.addonId,
getGameVersionsString(args.mcVersions),
getModLoaderStrings(args.loaders).join("\",\""));
};
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString

View File

@ -42,6 +42,8 @@
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include <QSet>
static ModrinthAPI api;
namespace Modrinth {
@ -93,23 +95,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
pack.versionsLoaded = true;
}
auto validateDownloadUrl(QUrl url) -> bool
{
auto domain = url.host();
if(domain == "cdn.modrinth.com")
return true;
if(domain == "edge.forgecdn.net")
return true;
if(domain == "media.forgecdn.net")
return true;
if(domain == "github.com")
return true;
if(domain == "raw.githubusercontent.com")
return true;
return false;
}
auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
{
ModpackVersion file;
@ -139,9 +124,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
auto url = Json::requireString(parent, "url");
if(!validateDownloadUrl(url))
continue;
file.download_url = url;
if(is_primary)
break;

View File

@ -40,6 +40,7 @@
#include <QByteArray>
#include <QCryptographicHash>
#include <QQueue>
#include <QString>
#include <QUrl>
#include <QVector>
@ -48,14 +49,12 @@ class MinecraftInstance;
namespace Modrinth {
struct File
{
struct File {
QString path;
QCryptographicHash::Algorithm hashAlgorithm;
QByteArray hash;
// TODO: should this support multiple download URLs, like the JSON does?
QUrl download;
QQueue<QUrl> downloads;
};
struct ModpackExtra {

View File

@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
}
}
else if (libraryName.startsWith("net.minecraftforge:minecraftforge:"))
else
{
components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2));
}
else if (libraryName.startsWith("net.fabricmc:fabric-loader:"))
{
components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2));
static QStringList possibleLoaders{
"net.minecraftforge:minecraftforge:",
"net.fabricmc:fabric-loader:",
"org.quiltmc:quilt-loader:"
};
for (const auto& loader : possibleLoaders)
{
if (libraryName.startsWith(loader))
{
auto loaderComponent = loader.chopped(1).replace(":", ".");
components->setComponentVersion(loaderComponent, libraryName.section(':', 2));
break;
}
}
}
}
}

199
launcher/net/Upload.cpp Normal file
View File

@ -0,0 +1,199 @@
//
// Created by timoreo on 20/05/22.
//
#include "Upload.h"
#include <utility>
#include "ByteArraySink.h"
#include "BuildConfig.h"
#include "Application.h"
namespace Net {
void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
setProgress(bytesReceived, bytesTotal);
}
void Upload::downloadError(QNetworkReply::NetworkError error) {
if (error == QNetworkReply::OperationCanceledError) {
qCritical() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
// error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void Upload::sslErrors(const QList<QSslError> &errors) {
int i = 1;
for (const auto& error : errors) {
qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
}
bool Upload::handleRedirect()
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
}
// there is a Location header, but it's not correct. we need to apply some workarounds...
QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
// empty, yet present redirect header? WTF?
return false;
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* IF the URL begins with //, we need to insert the URL scheme.
* See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
} else if (redirectStr.startsWith("/")) {
/*
* IF the URL begins with /, we need to process it as a relative URL
*/
auto url = m_reply->url();
url.setPath(redirectStr, QUrl::TolerantMode);
redirectStr = url.toString();
}
/*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
* FIXME: report Qt bug for this
*/
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qWarning() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qDebug() << "Fixed location header:" << redirect;
} else {
qDebug() << "Location header:" << redirect;
}
m_url = QUrl(redirect.toString());
qDebug() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
}
void Upload::downloadFinished() {
// handle HTTP redirection first
// very unlikely for post requests, still can happen
if (handleRedirect()) {
qDebug() << "Upload redirected:" << m_url.toString();
return;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) {
qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit succeeded();
return;
} else if (m_state == State::Failed) {
qDebug() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qDebug() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qDebug() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qDebug() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
m_reply.reset();
qDebug() << "Upload succeeded:" << m_url.toString();
emit succeeded();
}
void Upload::downloadReadyRead() {
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
}
}
void Upload::executeTask() {
setStatus(tr("Uploading %1").arg(m_url.toString()));
if (m_state == State::AbortedByUser) {
qWarning() << "Attempt to start an aborted Upload:" << m_url.toString();
emit aborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
emitSucceeded();
qDebug() << "Upload cache hit " << m_url.toString();
return;
case State::Running:
qDebug() << "Uploading " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed("");
return;
case State::AbortedByUser:
emitAborted();
return;
}
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT);
if (request.url().host().contains("api.curseforge.com")) {
request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
}
//TODO other types of post requests ?
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* rep = m_network->post(request, m_post_data);
m_reply.reset(rep);
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
}
Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) {
auto* up = new Upload();
up->m_url = std::move(url);
up->m_sink.reset(new ByteArraySink(output));
up->m_post_data = std::move(m_post_data);
return up;
}
} // Net

31
launcher/net/Upload.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include "NetAction.h"
#include "Sink.h"
namespace Net {
class Upload : public NetAction {
Q_OBJECT
public:
static Upload::Ptr makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data);
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError> & errors);
void downloadFinished() override;
void downloadReadyRead() override;
public slots:
void executeTask() override;
private:
std::unique_ptr<Sink> m_sink;
QByteArray m_post_data;
bool handleRedirect();
};
} // Net

View File

@ -52,7 +52,7 @@ QString MCEditTool::getProgramPath()
#else
const QString mceditPath = path();
QDir mceditDir(mceditPath);
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (mceditDir.exists("mcedit.sh"))
{
return mceditDir.absoluteFilePath("mcedit.sh");

View File

@ -2101,6 +2101,9 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
selectionBad();
return;
}
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
if (m_selectedInstance)
@ -2127,6 +2130,8 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
updateToolsMenu();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
else
{
@ -2216,3 +2221,9 @@ void MainWindow::updateStatusCenter()
m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
}
}
void MainWindow::refreshCurrentInstance(bool running)
{
auto current = view->selectionModel()->currentIndex();
instanceChanged(current, current);
}

View File

@ -192,6 +192,8 @@ private slots:
void keyReleaseEvent(QKeyEvent *event) override;
#endif
void refreshCurrentInstance(bool running);
private:
void retranslateUi();

View File

@ -0,0 +1,15 @@
#include "ScrollMessageBox.h"
#include "ui_ScrollMessageBox.h"
ScrollMessageBox::ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body) :
QDialog(parent), ui(new Ui::ScrollMessageBox) {
ui->setupUi(this);
this->setWindowTitle(title);
ui->label->setText(text);
ui->textBrowser->setText(body);
}
ScrollMessageBox::~ScrollMessageBox() {
delete ui;
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui { class ScrollMessageBox; }
QT_END_NAMESPACE
class ScrollMessageBox : public QDialog {
Q_OBJECT
public:
ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body);
~ScrollMessageBox() override;
private:
Ui::ScrollMessageBox *ui;
};

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScrollMessageBox</class>
<widget class="QDialog" name="ScrollMessageBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>455</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">ScrollMessageBox</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string notr="true"/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTextBrowser" name="textBrowser">
<property name="acceptRichText">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ScrollMessageBox</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>425</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>227</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ScrollMessageBox</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>425</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>227</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -48,6 +48,7 @@
#include "tools/BaseProfiler.h"
#include "Application.h"
#include "net/PasteUpload.h"
#include "BuildConfig.h"
APIPage::APIPage(QWidget *parent) :
QWidget(parent),
@ -76,6 +77,8 @@ APIPage::APIPage(QWidget *parent) :
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
ui->tabWidget->tabBar()->hide();
ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
loadSettings();
resetBaseURLNote();

View File

@ -36,13 +36,16 @@
<item>
<widget class="QGroupBox" name="groupBox_paste">
<property name="title">
<string>Pastebin Service</string>
<string>&amp;Pastebin Service</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="pasteServiceTypeLabel">
<property name="text">
<string>Paste Service Type</string>
<string>Paste Service &amp;Type</string>
</property>
<property name="buddy">
<cstring>pasteTypeComboBox</cstring>
</property>
</widget>
</item>
@ -52,7 +55,10 @@
<item>
<widget class="QLabel" name="baseURLLabel">
<property name="text">
<string>Base URL</string>
<string>Base &amp;URL</string>
</property>
<property name="buddy">
<cstring>baseURLEntry</cstring>
</property>
</widget>
</item>
@ -146,7 +152,7 @@
<item>
<widget class="QLineEdit" name="metaURL">
<property name="placeholderText">
<string>(Default)</string>
<string/>
</property>
</widget>
</item>

View File

@ -95,7 +95,7 @@ void JavaPage::applySettings()
// Java Settings
s->set("JavaPath", ui->javaPathTextBox->text());
s->set("JvmArgs", ui->jvmArgsTextBox->text());
s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " "));
s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked());
s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked());
JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget());
@ -120,7 +120,7 @@ void JavaPage::loadSettings()
// Java Settings
ui->javaPathTextBox->setText(s->get("JavaPath").toString());
ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString());
ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString());
ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool());
ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool());
}
@ -166,7 +166,7 @@ void JavaPage::on_javaTestBtn_clicked()
return;
}
checker.reset(new JavaCommon::TestCheck(
this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(),
this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "),
ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value()));
connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished()));
checker->run();

View File

@ -150,6 +150,35 @@
<string>Java Runtime</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="1">
<widget class="QPushButton" name="javaDetectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Auto-detect...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>JVM arguments:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelJavaPath">
<property name="sizePolicy">
@ -166,40 +195,8 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>J&amp;VM arguments:</string>
</property>
<property name="buddy">
<cstring>jvmArgsTextBox</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
<property name="text">
<string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="javaDetectBtn">
<item row="3" column="2">
<widget class="QPushButton" name="javaTestBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -207,7 +204,7 @@
</sizepolicy>
</property>
<property name="text">
<string>&amp;Auto-detect...</string>
<string>&amp;Test</string>
</property>
</widget>
</item>
@ -237,22 +234,22 @@
</item>
</layout>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="javaTestBtn">
<item row="4" column="1">
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
<property name="text">
<string>&amp;Test</string>
<string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="jvmArgsTextBox"/>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="skipJavaWizardCheckbox">
<property name="toolTip">
@ -263,6 +260,25 @@
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QPlainTextEdit" name="jvmArgsTextBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -291,7 +307,6 @@
<tabstop>permGenSpinBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaPathTextBox</tabstop>
<tabstop>jvmArgsTextBox</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
<tabstop>tabWidget</tabstop>

View File

@ -110,14 +110,16 @@ void ImportPage::updateState()
{
// FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input);
// mrpack is a modrinth pack
// Allow non-latin people to use ZIP files!
auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
if(fi.exists() && (zip || fi.suffix() == "mrpack"))
bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
// mrpack is a modrinth pack
bool isMRPack = fi.suffix() == "mrpack";
if(fi.exists() && (isZip || isMRPack))
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
dialog->setSuggestedIcon("default");
}
}
@ -130,7 +132,7 @@ void ImportPage::updateState()
}
// hook, line and sinker.
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
dialog->setSuggestedIcon("default");
}
}
@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
filter += ";;" + tr("Modrinth pack (*.mrpack)");
//: Option for filtering for *.mrpack files when importing
filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid())
{

View File

@ -201,7 +201,7 @@ void FlamePage::suggestCurrent()
return;
}
dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion));
dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion,this));
QString editedLogoName;
editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
listModel->getLogo(current.logoName, current.logoUrl,

View File

@ -175,7 +175,7 @@ void Page::suggestCurrent()
return;
}
dialog->setSuggestedPack(selected.name, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
dialog->setSuggestedPack(selected.name + " " + selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
QString editedLogoName;
if(selected.logo.toLower().startsWith("ftb"))
{

View File

@ -60,7 +60,11 @@
</widget>
</item>
<item row="0" column="1">
<widget class="QTextBrowser" name="packDescription"/>
<widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>

View File

@ -24,24 +24,65 @@ import java.net.URL;
import java.util.Map;
import java.util.TreeMap;
/*
* WARNING: This class is reflectively accessed by legacy Forge versions.
* Changing field and method declarations without further testing is not recommended.
*/
public final class Launcher extends Applet implements AppletStub {
private final Map<String, String> params = new TreeMap<>();
private final Applet wrappedApplet;
private Applet wrappedApplet;
private URL documentBase;
private boolean active = false;
public Launcher(Applet applet) {
this(applet, null);
}
public Launcher(Applet applet, URL documentBase) {
this.setLayout(new BorderLayout());
this.add(applet, "Center");
this.wrappedApplet = applet;
try {
if (documentBase != null) {
this.documentBase = documentBase;
} else if (applet.getClass().getPackage().getName().startsWith("com.mojang")) {
// Special case only for Classic versions
this.documentBase = new URL("http", "www.minecraft.net", 80, "/game/");
} else {
this.documentBase = new URL("http://www.minecraft.net/game/");
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public void setParameter(String name, String value)
{
public void replace(Applet applet) {
this.wrappedApplet = applet;
applet.setStub(this);
applet.setSize(getWidth(), getHeight());
this.setLayout(new BorderLayout());
this.add(applet, "Center");
applet.init();
active = true;
applet.start();
validate();
}
public void setParameter(String name, String value) {
params.put(name, value);
}
@ -54,7 +95,7 @@ public final class Launcher extends Applet implements AppletStub {
try {
return super.getParameter(name);
} catch (Exception ignore) {}
} catch (Exception ignored) {}
return null;
}
@ -108,25 +149,13 @@ public final class Launcher extends Applet implements AppletStub {
try {
return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return null;
}
@Override
public URL getDocumentBase() {
try {
// Special case only for Classic versions
if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang"))
return new URL("http", "www.minecraft.net", 80, "/game/");
return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
return documentBase;
}
@Override

View File

@ -1,5 +1,5 @@
{ lib
, mkDerivation
{ stdenv
, lib
, fetchFromGitHub
, cmake
, ninja
@ -7,18 +7,20 @@
, jdk
, zlib
, file
, makeWrapper
, wrapQtAppsHook
, xorg
, libpulseaudio
, qtbase
, quazip
, libGL
, msaClientID ? ""
, extraJDKs ? [ ]
# flake
, self
, version
, libnbtplusplus
, quazip
, enableLTO ? false
}:
let
@ -35,45 +37,40 @@ let
# This variable will be passed to Minecraft by PolyMC
gameLibraryPath = libpath + ":/run/opengl-driver/lib";
javaPaths = lib.makeSearchPath "bin/java" ([ jdk jdk8 ] ++ extraJDKs);
in
mkDerivation rec {
stdenv.mkDerivation rec {
pname = "polymc";
inherit version;
src = lib.cleanSource self;
nativeBuildInputs = [ cmake ninja file makeWrapper ];
buildInputs = [ qtbase jdk zlib ];
nativeBuildInputs = [ cmake ninja jdk file wrapQtAppsHook ];
buildInputs = [ qtbase quazip zlib ];
dontWrapQtApps = true;
postPatch = lib.optionalString (msaClientID != "") ''
# add client ID
substituteInPlace CMakeLists.txt \
--replace '17b47edd-c884-4997-926d-9e7f9a6b4647' '${msaClientID}'
'';
postUnpack = ''
# Copy submodules inputs
rm -rf source/libraries/{libnbtplusplus,quazip}
mkdir source/libraries/{libnbtplusplus,quazip}
# Copy libnbtplusplus
rm -rf source/libraries/libnbtplusplus
mkdir source/libraries/libnbtplusplus
cp -a ${libnbtplusplus}/* source/libraries/libnbtplusplus
cp -a ${quazip}/* source/libraries/quazip
chmod a+r+w source/libraries/{libnbtplusplus,quazip}/*
chmod a+r+w source/libraries/libnbtplusplus/*
'';
cmakeFlags = [
"-GNinja"
"-DLauncher_PORTABLE=OFF"
];
"-DLauncher_QT_VERSION_MAJOR=${lib.versions.major qtbase.version}"
] ++ lib.optionals enableLTO [ "-DENABLE_LTO=on" ]
++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ];
postInstall = ''
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
wrapProgram $out/bin/polymc \
"''${qtWrapperArgs[@]}" \
wrapQtApp $out/bin/polymc \
--set GAME_LIBRARY_PATH ${gameLibraryPath} \
--prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \
--prefix POLYMC_JAVA_PATHS : ${javaPaths} \
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}
'';

View File

@ -1,9 +1,9 @@
let
lock = builtins.fromJSON (builtins.readFile ../../flake.lock);
lock = builtins.fromJSON (builtins.readFile ../flake.lock);
inherit (lock.nodes.flake-compat.locked) rev narHash;
flake-compat = fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${rev}.tar.gz";
sha256 = narHash;
};
in
import flake-compat { src = ../..; }
import flake-compat { src = ../.; }

View File

@ -14,6 +14,8 @@ set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" PARENT_SCOPE
set(Launcher_ManPage "program_info/polymc.6.txt" PARENT_SCOPE)
set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE)
set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE)
set(Launcher_Branding_ICO "program_info/polymc.ico")
set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE)
set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE)
set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE)
@ -21,3 +23,7 @@ set(Launcher_Portable_File "program_info/portable.txt" PARENT_SCOPE)
configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop)
configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml)
configure_file(polymc.rc.in polymc.rc @ONLY)
configure_file(polymc.manifest.in polymc.manifest @ONLY)
configure_file(polymc.ico polymc.ico COPYONLY)
configure_file(win_install.nsi.in win_install.nsi @ONLY)

View File

@ -1,32 +1,174 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_68_227)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M924 356.627C924 346.845 924.004 337.062 923.944 327.279C923.895 319.038 923.8 310.799 923.576 302.562C923.092 284.609 922.033 266.502 918.84 248.749C915.602 230.741 910.314 213.98 901.981 197.617C893.789 181.534 883.088 166.817 870.32 154.058C857.555 141.299 842.834 130.605 826.746 122.418C810.366 114.083 793.587 108.797 775.558 105.56C757.803 102.372 739.691 101.315 721.738 100.83C713.495 100.607 705.253 100.513 697.008 100.462C687.22 100.402 677.432 100.407 667.644 100.407L553.997 100H468.997L357.361 100.407C347.554 100.407 337.747 100.402 327.94 100.462C319.678 100.513 311.42 100.607 303.161 100.83C285.167 101.315 267.014 102.373 249.217 105.565C231.164 108.801 214.36 114.085 197.958 122.414C181.835 130.602 167.083 141.297 154.291 154.058C141.501 166.816 130.78 181.53 122.573 197.61C114.217 213.981 108.919 230.752 105.673 248.77C102.477 266.516 101.418 284.617 100.931 302.562C100.709 310.8 100.613 319.039 100.563 327.279C100.503 337.063 100 349.216 100 358.999L100.003 469.089L100 554.998L100.508 667.427C100.508 677.223 100.504 687.019 100.563 696.815C100.613 705.067 100.709 713.317 100.932 721.566C101.418 739.542 102.479 757.675 105.678 775.452C108.923 793.484 114.22 810.269 122.569 826.653C130.777 842.759 141.5 857.495 154.291 870.272C167.082 883.049 181.83 893.757 197.95 901.956C214.362 910.302 231.174 915.595 249.238 918.836C267.027 922.029 285.174 923.088 303.161 923.573C311.42 923.796 319.679 923.891 327.941 923.941C337.748 924.001 347.554 923.997 357.361 923.997L470.006 924H555.217L667.644 923.996C677.432 923.996 687.22 924.001 697.008 923.941C705.253 923.891 713.495 923.796 721.738 923.573C739.698 923.087 757.816 922.027 775.579 918.832C793.597 915.591 810.368 910.3 826.739 901.959C842.831 893.761 857.554 883.051 870.32 870.272C883.086 857.497 893.786 842.763 901.978 826.66C910.316 810.268 915.604 793.475 918.844 775.431C922.034 757.661 923.092 739.535 923.577 721.566C923.8 713.316 923.895 705.066 923.944 696.815C924.005 687.019 924 677.223 924 667.427C924 667.427 923.994 556.983 923.994 554.998V468.999C923.994 467.533 924 356.627 924 356.627Z" fill="url(#paint0_linear_68_227)"/>
</g>
<path d="M338.18 779.507C338.18 779.507 338.18 653.214 512.004 653.214C685.874 653.214 685.827 779.507 685.827 779.507H338.18Z" fill="#765338"/>
<path d="M512.007 653.221L338.183 779.514L230.752 448.878L512.007 653.221Z" fill="#B7835A"/>
<path d="M512.007 653.221L793.263 448.878L685.831 779.514L512.007 653.221Z" fill="#5B422D"/>
<path d="M524.909 662.576L512.005 671.951L499.101 662.576C499.101 653.201 512.005 653.201 512.005 653.201C512.005 653.201 524.909 653.201 524.909 662.576Z" fill="#72B147"/>
<path d="M512.007 653.221C512.007 653.221 512.007 448.878 793.263 448.878L785.288 473.423L752.741 515.819L720.194 520.716L687.647 563.113L655.1 568.009L622.553 610.406L590.006 615.302L557.459 657.699L524.912 662.595L512.007 653.221Z" fill="#5A9A30"/>
<path d="M499.102 662.576L466.555 657.679L434.008 615.283L401.461 610.386L368.914 567.99L336.367 563.093L303.82 520.697L271.273 515.8L238.726 473.403L230.751 448.859C512.007 448.859 512.007 653.202 512.007 653.202L499.102 662.576Z" fill="#88B858"/>
<path d="M230.75 448.861L512.006 653.204L793.262 448.861L512.006 244.518L230.75 448.861Z" fill="url(#paint1_linear_68_227)"/>
<defs>
<filter id="filter0_d_68_227" x="90" y="100" width="844" height="844" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="10"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_68_227"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_68_227" result="shape"/>
</filter>
<linearGradient id="paint0_linear_68_227" x1="512" y1="100" x2="512" y2="924" gradientUnits="userSpaceOnUse">
<stop stop-color="#292929"/>
<stop offset="1" stop-color="#171717"/>
</linearGradient>
<linearGradient id="paint1_linear_68_227" x1="371.378" y1="346.687" x2="652.619" y2="551.034" gradientUnits="userSpaceOnUse">
<stop stop-color="#88B858"/>
<stop offset="0.5" stop-color="#72B147"/>
<stop offset="1" stop-color="#5A9A30"/>
</linearGradient>
</defs>
<svg
width="1024"
height="1024"
viewBox="0 0 1024 1024"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g filter="url(#filter0_d_102_69)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M924 354.627C924 344.845 924.004 335.062 923.944 325.279C923.895 317.038 923.8 308.799 923.576 300.562C923.092 282.609 922.033 264.502 918.84 246.749C915.602 228.741 910.314 211.98 901.981 195.617C893.789 179.534 883.088 164.817 870.32 152.058C857.555 139.299 842.834 128.605 826.746 120.418C810.366 112.083 793.587 106.797 775.558 103.56C757.803 100.372 739.691 99.315 721.738 98.83C713.495 98.607 705.253 98.513 697.008 98.462C687.22 98.402 677.432 98.407 667.644 98.407L553.997 98H468.997L357.361 98.407C347.554 98.407 337.747 98.402 327.94 98.462C319.678 98.513 311.42 98.607 303.161 98.83C285.167 99.315 267.014 100.373 249.217 103.565C231.164 106.801 214.36 112.085 197.958 120.414C181.835 128.602 167.083 139.297 154.291 152.058C141.501 164.816 130.78 179.53 122.573 195.61C114.217 211.981 108.919 228.752 105.673 246.77C102.477 264.516 101.418 282.617 100.931 300.562C100.709 308.8 100.613 317.039 100.563 325.279C100.503 335.063 100 347.216 100 356.999L100.003 467.089L100 552.998L100.508 665.427C100.508 675.223 100.504 685.019 100.563 694.815C100.613 703.067 100.709 711.317 100.932 719.566C101.418 737.542 102.479 755.675 105.678 773.452C108.923 791.484 114.22 808.269 122.569 824.653C130.777 840.759 141.5 855.495 154.291 868.272C167.082 881.049 181.83 891.757 197.95 899.956C214.362 908.302 231.174 913.595 249.238 916.836C267.027 920.029 285.174 921.088 303.161 921.573C311.42 921.796 319.679 921.891 327.941 921.941C337.748 922.001 347.554 921.997 357.361 921.997L470.006 922H555.217L667.644 921.996C677.432 921.996 687.22 922.001 697.008 921.941C705.253 921.891 713.495 921.796 721.738 921.573C739.698 921.087 757.816 920.027 775.579 916.832C793.597 913.591 810.368 908.3 826.739 899.959C842.831 891.761 857.554 881.051 870.32 868.272C883.086 855.497 893.786 840.763 901.978 824.66C910.316 808.268 915.604 791.475 918.844 773.431C922.034 755.661 923.092 737.535 923.577 719.566C923.8 711.316 923.895 703.066 923.944 694.815C924.005 685.019 924 675.223 924 665.427C924 665.427 923.994 554.983 923.994 552.998V466.999C923.994 465.533 924 354.627 924 354.627Z"
fill="url(#paint0_linear_102_69)"
/>
</g>
<mask
id="mask0_102_69"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="100"
y="98"
width="824"
height="824"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M924 354.627C924 344.845 924.004 335.062 923.944 325.279C923.895 317.038 923.8 308.799 923.576 300.562C923.092 282.609 922.033 264.502 918.84 246.749C915.602 228.741 910.314 211.98 901.981 195.617C893.789 179.534 883.088 164.817 870.32 152.058C857.555 139.299 842.834 128.605 826.746 120.418C810.366 112.083 793.587 106.797 775.558 103.56C757.803 100.372 739.691 99.315 721.738 98.83C713.495 98.607 705.253 98.513 697.008 98.462C687.22 98.402 677.432 98.407 667.644 98.407L553.997 98H468.997L357.361 98.407C347.554 98.407 337.747 98.402 327.94 98.462C319.678 98.513 311.42 98.607 303.161 98.83C285.167 99.315 267.014 100.373 249.217 103.565C231.164 106.801 214.36 112.085 197.958 120.414C181.835 128.602 167.083 139.297 154.291 152.058C141.501 164.816 130.78 179.53 122.573 195.61C114.217 211.981 108.919 228.752 105.673 246.77C102.477 264.516 101.418 282.617 100.931 300.562C100.709 308.8 100.613 317.039 100.563 325.279C100.503 335.063 100 347.216 100 356.999L100.003 467.089L100 552.998L100.508 665.427C100.508 675.223 100.504 685.019 100.563 694.815C100.613 703.067 100.709 711.317 100.932 719.566C101.418 737.542 102.479 755.675 105.678 773.452C108.923 791.484 114.22 808.269 122.569 824.653C130.777 840.759 141.5 855.495 154.291 868.272C167.082 881.049 181.83 891.757 197.95 899.956C214.362 908.302 231.174 913.595 249.238 916.836C267.027 920.029 285.174 921.088 303.161 921.573C311.42 921.796 319.679 921.891 327.941 921.941C337.748 922.001 347.554 921.997 357.361 921.997L470.006 922H555.217L667.644 921.996C677.432 921.996 687.22 922.001 697.008 921.941C705.253 921.891 713.495 921.796 721.738 921.573C739.698 921.087 757.816 920.027 775.579 916.832C793.597 913.591 810.368 908.3 826.739 899.959C842.831 891.761 857.554 881.051 870.32 868.272C883.086 855.497 893.786 840.763 901.978 824.66C910.316 808.268 915.604 791.475 918.844 773.431C922.034 755.661 923.092 737.535 923.577 719.566C923.8 711.316 923.895 703.066 923.944 694.815C924.005 685.019 924 675.223 924 665.427C924 665.427 923.994 554.983 923.994 552.998V466.999C923.994 465.533 924 354.627 924 354.627Z"
fill="white"
/>
</mask>
<g mask="url(#mask0_102_69)">
<rect
x="42"
y="36"
width="914"
height="914"
fill="url(#paint1_linear_102_69)"
/>
<g filter="url(#filter1_b_102_69)">
<rect
x="100"
y="98"
width="824"
height="824"
rx="126"
fill="black"
fill-opacity="0.01"
/>
</g>
</g>
<path
d="M367.15 732.923C367.15 732.923 367.15 627.678 512.003 627.678C656.895 627.678 656.856 732.923 656.856 732.923H367.15Z"
fill="#765338"
/>
<path
d="M512.006 627.684L367.153 732.929L277.626 457.399L512.006 627.684Z"
fill="#B7835A"
/>
<path
d="M512.006 627.684L746.385 457.399L656.859 732.929L512.006 627.684Z"
fill="#5B422D"
/>
<path
d="M522.757 635.48L512.004 643.292L501.25 635.48C501.25 627.667 512.004 627.667 512.004 627.667C512.004 627.667 522.757 627.667 522.757 635.48Z"
fill="#72B147"
/>
<path
d="M512.006 627.684C512.006 627.684 512.006 457.399 746.385 457.399L739.74 477.852L712.617 513.183L685.495 517.263L658.372 552.594L631.25 556.674L604.127 592.005L577.005 596.085L549.882 631.416L522.76 635.496L512.006 627.684Z"
fill="#5A9A30"
/>
<path
d="M501.252 635.48L474.129 631.399L447.007 596.069L419.884 591.988L392.762 556.658L365.639 552.578L338.517 517.247L311.394 513.167L284.272 477.836L277.626 457.382C512.006 457.382 512.006 627.668 512.006 627.668L501.252 635.48Z"
fill="#88B858"
/>
<path
d="M277.625 457.384L512.005 627.67L746.385 457.384L512.005 287.098L277.625 457.384Z"
fill="url(#paint2_linear_102_69)"
/>
<defs>
<filter
id="filter0_d_102_69"
x="90"
y="98"
width="844"
height="844"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="10" />
<feGaussianBlur stdDeviation="5" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_102_69"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_102_69"
result="shape"
/>
</filter>
<filter
id="filter1_b_102_69"
x="89.1269"
y="87.1269"
width="845.746"
height="845.746"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImage" stdDeviation="5.43656" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur_102_69"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur_102_69"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_102_69"
x1="-181.14"
y1="98"
x2="-181.14"
y2="1484.28"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="0.489516" stop-color="#EFEFEF" />
<stop offset="1" stop-color="#C0C0C0" />
</linearGradient>
<linearGradient
id="paint1_linear_102_69"
x1="928.377"
y1="992.826"
x2="928.377"
y2="134.072"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F6F3F3" />
<stop offset="1" stop-color="white" />
</linearGradient>
<linearGradient
id="paint2_linear_102_69"
x1="394.815"
y1="372.239"
x2="629.182"
y2="542.528"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#88B858" />
<stop offset="0.5" stop-color="#72B147" />
<stop offset="1" stop-color="#5A9A30" />
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -8,5 +8,5 @@ Exec=@Launcher_APP_BINARY_NAME@
StartupNotify=true
Icon=org.polymc.PolyMC
Categories=Game;
Keywords=game;minecraft;launcher;
Keywords=game;minecraft;launcher;mc;
StartupWMClass=PolyMC

View File

@ -28,23 +28,23 @@
<screenshots>
<screenshot type="default">
<caption>The main PolyMC window</caption>
<image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherDark.png</image>
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherDark.png</image>
</screenshot>
<screenshot>
<caption>Modpack installation</caption>
<image type="source" width="911" height="682">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
<image type="source" width="860" height="848">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
</screenshot>
<screenshot>
<caption>Mod installation</caption>
<image type="source" width="987" height="723">https://polymc.org/img/screenshots/ModInstallDark.png</image>
<image type="source" width="1018" height="858">https://polymc.org/img/screenshots/ModInstallDark.png</image>
</screenshot>
<screenshot>
<caption>Instance management</caption>
<image type="source" width="902" height="920">https://polymc.org/img/screenshots/PropertiesDark.png</image>
<image type="source" width="777" height="693">https://polymc.org/img/screenshots/PropertiesDark.png</image>
</screenshot>
<screenshot>
<caption>Cat :)</caption>
<image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
</screenshot>
</screenshots>
<releases>

Binary file not shown.

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity name="PolyMC.Application.1" type="win32" version="1.0.0.0" />
<assemblyIdentity name="PolyMC.Application.1" type="win32" version="@Launcher_RELEASE_VERSION_NAME4@" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
@ -16,15 +16,13 @@
<description>Custom Minecraft launcher for managing multiple installs.</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates app support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates app support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--The ID below indicates app support for Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!--The ID below indicates app support for Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!--The ID below indicates app support for Windows 10 -->
<!--The ID below indicates app support for Windows 10/11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>

View File

@ -7,7 +7,7 @@ IDI_ICON1 ICON DISCARDABLE "polymc.ico"
1 RT_MANIFEST "polymc.manifest"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
FILEVERSION @Launcher_RELEASE_VERSION_NAME4_COMMA@
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
@ -17,9 +17,9 @@ BEGIN
BEGIN
VALUE "CompanyName", "MultiMC & PolyMC Contributors"
VALUE "FileDescription", "PolyMC"
VALUE "FileVersion", "1.0.0.0"
VALUE "FileVersion", "@Launcher_RELEASE_VERSION_NAME4@"
VALUE "ProductName", "PolyMC"
VALUE "ProductVersion", "1"
VALUE "ProductVersion", "@Launcher_RELEASE_VERSION_NAME4@"
END
END
BLOCK "VarFileInfo"

View File

@ -4,10 +4,13 @@
Unicode true
Name "PolyMC"
InstallDir "$LOCALAPPDATA\Programs\PolyMC"
InstallDirRegKey HKCU "Software\PolyMC" "InstallDir"
Name "@Launcher_CommonName@"
InstallDir "$LOCALAPPDATA\Programs\@Launcher_CommonName@"
InstallDirRegKey HKCU "Software\@Launcher_CommonName@" "InstallDir"
RequestExecutionLevel user
OutFile "../@Launcher_CommonName@-Setup.exe"
!define MUI_ICON "../@Launcher_Branding_ICO@"
;--------------------------------
@ -18,7 +21,7 @@ RequestExecutionLevel user
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN "$InstDir\polymc.exe"
!define MUI_FINISHPAGE_RUN "$InstDir\@Launcher_APP_BINARY_NAME@.exe"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
@ -98,16 +101,23 @@ RequestExecutionLevel user
;--------------------------------
; Version info
VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@"
VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@"
VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@"
;--------------------------------
; The stuff to install
Section "PolyMC"
Section "@Launcher_CommonName@"
SectionIn RO
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F'
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
SetOutPath $INSTDIR
File "polymc.exe"
File "@Launcher_APP_BINARY_NAME@.exe"
File "qt.conf"
File *.dll
File /r "iconengines"
@ -117,20 +127,20 @@ Section "PolyMC"
File /r "styles"
; Write the installation path into the registry
WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR"
WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR"
; Write the uninstall keys for Windows
${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1
${If} ${Errors}
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe"
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe"
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors"
WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors"
WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
@ -141,9 +151,15 @@ Section "PolyMC"
SectionEnd
Section "Start Menu Shortcuts" SHORTCUTS
Section "Start Menu Shortcut" SM_SHORTCUTS
CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0
CreateShortcut "$SMPROGRAMS\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd
Section "Desktop Shortcut" DESKTOP_SHORTCUTS
CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd
@ -153,12 +169,12 @@ SectionEnd
Section "Uninstall"
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F'
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC"
DeleteRegKey HKCU SOFTWARE\PolyMC
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@
Delete $INSTDIR\polymc.exe
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
Delete $INSTDIR\uninstall.exe
Delete $INSTDIR\portable.txt
@ -214,7 +230,8 @@ Section "Uninstall"
RMDir /r $INSTDIR\platforms
RMDir /r $INSTDIR\styles
Delete "$SMPROGRAMS\PolyMC.lnk"
Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk"
Delete "$DESKTOP\@Launcher_CommonName@.lnk"
RMDir "$INSTDIR"
@ -228,6 +245,7 @@ Function .onInit
${GetParameters} $R0
${GetOptions} $R0 "/NoShortcuts" $R1
${IfNot} ${Errors}
!insertmacro UnselectSection ${SHORTCUTS}
!insertmacro UnselectSection ${SM_SHORTCUTS}
!insertmacro UnselectSection ${DESKTOP_SHORTCUTS}
${EndIf}
FunctionEnd