aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/rfc.yml69
-rw-r--r--.github/workflows/build.yml74
-rw-r--r--.github/workflows/trigger_builds.yml32
-rw-r--r--.github/workflows/trigger_release.yml99
-rw-r--r--BUILD.md340
-rw-r--r--CMakeLists.txt97
-rw-r--r--README.md135
-rw-r--r--buildconfig/BuildConfig.cpp.in2
-rw-r--r--buildconfig/BuildConfig.h10
-rw-r--r--launcher/Application.cpp45
-rw-r--r--launcher/BaseInstance.cpp1
-rw-r--r--launcher/CMakeLists.txt13
-rw-r--r--launcher/InstanceCopyTask.cpp1
-rw-r--r--launcher/InstanceCreationTask.cpp2
-rw-r--r--launcher/InstanceImportTask.cpp3
-rw-r--r--launcher/InstanceList.cpp20
-rw-r--r--launcher/InstancePageProvider.h41
-rw-r--r--launcher/LaunchController.cpp14
-rw-r--r--launcher/ModDownloadTask.h1
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp11
-rw-r--r--launcher/minecraft/PackProfile.cpp254
-rw-r--r--launcher/minecraft/PackProfile.h2
-rw-r--r--launcher/minecraft/PackProfile_p.h12
-rw-r--r--launcher/minecraft/auth/AccountData.cpp5
-rw-r--r--launcher/minecraft/auth/AccountData.h2
-rw-r--r--launcher/minecraft/auth/AccountList.cpp3
-rw-r--r--launcher/minecraft/auth/AccountTask.cpp8
-rw-r--r--launcher/minecraft/auth/AccountTask.h1
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp3
-rw-r--r--launcher/minecraft/auth/steps/MSAStep.cpp8
-rw-r--r--launcher/minecraft/auth/steps/MSAStep.h1
-rw-r--r--launcher/minecraft/legacy/LegacyInstance.cpp256
-rw-r--r--launcher/minecraft/legacy/LegacyInstance.h142
-rw-r--r--launcher/minecraft/legacy/LegacyModList.cpp136
-rw-r--r--launcher/minecraft/legacy/LegacyModList.h47
-rw-r--r--launcher/minecraft/legacy/LegacyUpgradeTask.cpp138
-rw-r--r--launcher/minecraft/legacy/LegacyUpgradeTask.h29
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp2
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp31
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.cpp2
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp2
-rw-r--r--launcher/modplatform/technic/TechnicPackProcessor.cpp2
-rw-r--r--launcher/resources/multimc/multimc.qrc4
-rw-r--r--launcher/resources/multimc/scalable/matrix.svg7
-rw-r--r--launcher/ui/MainWindow.cpp18
-rw-r--r--launcher/ui/MainWindow.h2
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp53
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h9
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp31
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h23
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.ui98
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp16
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui12
-rw-r--r--launcher/ui/pages/instance/LegacyUpgradePage.cpp51
-rw-r--r--launcher/ui/pages/instance/LegacyUpgradePage.h64
-rw-r--r--launcher/ui/pages/instance/LegacyUpgradePage.ui54
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp15
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.ui2
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.cpp345
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.h3
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.ui179
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp16
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp319
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h3
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui179
-rw-r--r--launcher/ui/widgets/PageContainer.cpp3
-rw-r--r--packages/nix/polymc/default.nix10
-rw-r--r--program_info/org.polymc.PolyMC.metainfo.xml.in12
-rw-r--r--program_info/polymc-header-black.svg34
-rw-r--r--program_info/polymc-header.Source.svg139
-rw-r--r--program_info/polymc-header.svg34
72 files changed, 1418 insertions, 2417 deletions
diff --git a/.github/ISSUE_TEMPLATE/rfc.yml b/.github/ISSUE_TEMPLATE/rfc.yml
new file mode 100644
index 00000000..664430fe
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/rfc.yml
@@ -0,0 +1,69 @@
+# Template based on https://gitlab.archlinux.org/archlinux/rfcs/-/blob/0ba3b61e987e197f8d1901709409b8564958f78a/rfcs/0000-template.rst
+name: Request for Comment (RFC)
+description: Propose a larger change and start a discussion.
+labels: [RFC]
+body:
+- type: markdown
+ attributes:
+ value: |
+ ### Use this form to suggest a larger change for PolyMC.
+- type: textarea
+ attributes:
+ label: Goal
+ description: Short description, 1-2 sentences.
+ placeholder: Remove the cat from the launcher.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Motivation
+ description: |
+ Introduce the topic. If this is a not-well-known section of PolyMC, a detailed explanation of the background is recommended.
+ Some example points of discussion:
+ - What specific problems are you facing right now that you're trying to address?
+ - Are there any previous discussions? Link to them and summarize them (don't
+ - force your readers to read them though!).
+ - Is there any precedent set by other software? If so, link to resources.
+ placeholder: I don't like cats. I think many users also don't like cats.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Specification
+ description: A concrete, thorough explanation of what is being planned.
+ placeholder: Remove the cat button and all references to the cat from the codebase. Including resource files.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Drawbacks
+ description: Carefully consider every possible objection and issue with your proposal. This section should be updated as feedback comes in from discussion.
+ placeholder: Some users might like cats.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Unresolved Questions
+ description: |
+ Are there any portions of your proposal which need to be discussed with the community before the RFC can proceed?
+ Be careful here -- an RFC with a lot of remaining questions is likely to be stalled.
+ If your RFC is mostly unresolved questions and not too much substance, it may not be ready.
+ placeholder: Do a lot of users care about the cat?
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Alternatives Considered
+ description: A list of alternatives, that have been considered and offer equal or similar features to the proposed change.
+ placeholder: Maybe the cat could be replaced with an axolotl?
+ validations:
+ required: true
+- type: checkboxes
+ attributes:
+ label: This suggestion is unique
+ options:
+ - label: I have searched the issue tracker and did not find an issue describing my suggestion, especially not one that has been rejected.
+ required: true
+- type: textarea
+ attributes:
+ label: You may use the editor below to elaborate further.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d2ccc59e..b5797e95 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,7 +1,12 @@
-name: build_portable
+name: Build
on:
- [push, pull_request, workflow_dispatch]
+ workflow_call:
+ inputs:
+ build_type:
+ description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
+ type: string
+ default: Debug
jobs:
build:
@@ -11,8 +16,13 @@ jobs:
include:
- os: ubuntu-20.04
+ qt_version: 5.12.8
+ qt_host: linux
+
+ - os: ubuntu-20.04
qt_version: 5.15.2
qt_host: linux
+ app_image: true
- os: windows-2022
qt_version: 5.15.2
@@ -59,6 +69,12 @@ jobs:
copy "${{ github.workspace }}\Qt\Tools\OpenSSL\Win_x86\bin\libssl-1_1.dll" "${{ github.workspace }}\${{ env.INSTALL_DIR }}\"
copy "${{ github.workspace }}\Qt\Tools\OpenSSL\Win_x86\bin\libcrypto-1_1.dll" "${{ github.workspace }}\${{ env.INSTALL_DIR }}\"
+ - name: Set short version
+ shell: bash
+ run: |
+ ver_short=`git rev-parse --short HEAD`
+ echo "VERSION=$ver_short" >> $GITHUB_ENV
+
- name: Install OpenJDK
uses: AdoptOpenJDK/install-jdk@v1
with:
@@ -72,6 +88,7 @@ jobs:
key: ${{ runner.os }}-${{ matrix.qt_version }}-${{ matrix.qt_arch }}-qt_cache
- name: Install Qt
+ if: runner.os != 'Linux' || matrix.app_image == true
uses: jurplel/install-qt-action@v2
with:
version: ${{ matrix.qt_version }}
@@ -80,18 +97,24 @@ jobs:
cached: ${{ steps.cache-qt.outputs.cache-hit }}
dir: "${{ github.workspace }}/Qt/"
+ - name: Install System Qt on Linux
+ if: runner.os == 'Linux' && matrix.app_image != true
+ run: |
+ sudo apt-get -y update
+ sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
+
- name: Install Ninja
uses: urkle/action-get-ninja@v1
- - name: Download linuxdeploy family
- if: runner.os == 'Linux'
+ - name: Download linuxdeploy family for AppImage on Linux
+ if: matrix.app_image == true
run: |
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
- name: Download JREs for AppImage on Linux
- if: runner.os == 'Linux'
+ if: matrix.app_image == true
shell: bash
run: |
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
@@ -99,12 +122,12 @@ jobs:
- name: Configure CMake
if: runner.os != 'Linux'
run: |
- cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=Debug -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -G Ninja
- name: Configure CMake on Linux
if: runner.os == 'Linux'
run: |
- cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DLauncher_LAYOUT=lin-system -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DLauncher_PORTABLE=OFF -G Ninja
- name: Build
run: |
@@ -115,16 +138,16 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }}
- - name: Install for AppImage on Linux
+ - name: Install on Linux
if: runner.os == 'Linux'
run: |
DESTDIR=${{ env.INSTALL_DIR }} cmake --install ${{ env.BUILD_DIR }}
- name: Bundle AppImage
- if: runner.os == 'Linux'
+ if: matrix.app_image == true
shell: bash
run: |
- export OUTPUT="PolyMC-${{ github.sha }}-x86_64.AppImage"
+ export OUTPUT="PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage
@@ -145,13 +168,13 @@ jobs:
- name: Run windeployqt
if: runner.os == 'Windows'
run: |
- windeployqt --no-translations "${{ env.INSTALL_DIR }}/polymc.exe"
+ windeployqt --no-translations --no-system-d3d-compiler --no-opengl-sw "${{ env.INSTALL_DIR }}/polymc.exe"
- name: Run macdeployqt
if: runner.os == 'macOS'
run: |
cd ${{ env.INSTALL_DIR }}
- macdeployqt "PolyMC.app" -executable="PolyMC.app/Contents/MacOS/polymc" -always-overwrite -use-debug-libs
+ macdeployqt "PolyMC.app" -executable="PolyMC.app/Contents/MacOS/polymc" -always-overwrite
- name: chmod binary on macOS
if: runner.os == 'macOS'
@@ -162,25 +185,38 @@ jobs:
if: runner.os == 'macOS'
run: |
cd ${{ env.INSTALL_DIR }}
- tar -czf ../polymc.tar.gz *
+ tar -czf ../PolyMC.tar.gz *
+
+ - name: tar on Linux
+ if: runner.os == 'Linux' && matrix.app_image != true
+ run: |
+ cd ${{ env.INSTALL_DIR }}
+ tar -czf ../PolyMC.tar.gz *
+
+ - name: Upload Linux tar.gz
+ if: runner.os == 'Linux' && matrix.app_image != true
+ uses: actions/upload-artifact@v2
+ with:
+ name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
+ path: PolyMC.tar.gz
- name: Upload AppImage for Linux
- if: runner.os == 'Linux'
+ if: matrix.app_image == true
uses: actions/upload-artifact@v2
with:
- name: PolyMC-${{ github.sha }}-x86_64.AppImage
- path: PolyMC-${{ github.sha }}-x86_64.AppImage
+ name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
+ path: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
- name: Upload package for Windows
if: runner.os == 'Windows'
uses: actions/upload-artifact@v2
with:
- name: polymc-${{ runner.os }}-${{ github.sha }}-portable
+ name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_DIR }}/**
- name: Upload package for macOS
if: runner.os == 'macOS'
uses: actions/upload-artifact@v2
with:
- name: polymc-${{ runner.os }}-${{ github.sha }}-portable
- path: polymc.tar.gz
+ name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
+ path: PolyMC.tar.gz
diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml
new file mode 100644
index 00000000..1561b9d6
--- /dev/null
+++ b/.github/workflows/trigger_builds.yml
@@ -0,0 +1,32 @@
+name: Build Application
+
+on:
+ push:
+ branches-ignore:
+ - 'stable'
+ paths-ignore:
+ - '**.md'
+ - '**/LICENSE'
+ - 'flake.lock'
+ - '**.nix'
+ pull_request:
+ paths-ignore:
+ - '**.md'
+ - '**/LICENSE'
+ - 'flake.lock'
+ - '**.nix'
+ workflow_dispatch:
+
+jobs:
+
+ build_debug:
+ name: Build Debug
+ uses: ./.github/workflows/build.yml
+ with:
+ build_type: Debug
+
+ build_release:
+ name: Build Release
+ uses: ./.github/workflows/build.yml
+ with:
+ build_type: Release
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
new file mode 100644
index 00000000..b487e731
--- /dev/null
+++ b/.github/workflows/trigger_release.yml
@@ -0,0 +1,99 @@
+name: Build Application and Make Release
+
+on:
+ push:
+ tags:
+ - '*'
+
+jobs:
+
+ build_release:
+ name: Build Release
+ uses: ./.github/workflows/build.yml
+ with:
+ build_type: Release
+
+ create_release:
+ needs: build_release
+ runs-on: ubuntu-latest
+ outputs:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ steps:
+ - name: Grab and store version
+ run: |
+ tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
+ echo "VERSION=$tag_name" >> $GITHUB_ENV
+ - name: Create release
+ id: create_release
+ uses: softprops/action-gh-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ github.ref }}
+ name: PolyMC ${{ env.VERSION }}
+ draft: true
+ prerelease: false
+
+ upload_release:
+ needs: create_release
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v2
+
+ - name: Grab and store version
+ run: |
+ tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
+ echo "VERSION=$tag_name" >> $GITHUB_ENV
+
+ - name: Package artifacts properly
+ run: |
+ mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz
+ mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
+ mv PolyMC-Windows* PolyMC-Windows-${{ env.VERSION }}
+ mv PolyMC-macOS*/PolyMC.tar.gz PolyMC-macOS-${{ env.VERSION }}.tar.gz
+
+ cd PolyMC-Windows-${{ env.VERSION }}
+ zip -r -9 ../PolyMC-Windows-${{ env.VERSION }}.zip *
+ cd ..
+
+ - name: Upload Linux asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ needs.create_release.outputs.upload_url }}
+ asset_name: PolyMC-Linux-${{ env.VERSION }}.tar.gz
+ asset_path: PolyMC-Linux-${{ env.VERSION }}.tar.gz
+ asset_content_type: application/gzip
+
+ - name: Upload Linux AppImage asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ needs.create_release.outputs.upload_url }}
+ asset_name: PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
+ asset_path: PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
+ asset_content_type: application/x-executable
+
+ - name: Upload Windows asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ needs.create_release.outputs.upload_url }}
+ asset_name: PolyMC-Windows-${{ env.VERSION }}.zip
+ asset_path: PolyMC-Windows-${{ env.VERSION }}.zip
+ asset_content_type: application/zip
+
+ - name: Upload macOS asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ needs.create_release.outputs.upload_url }}
+ asset_name: PolyMC-macOS-${{ env.VERSION }}.tar.gz
+ asset_path: PolyMC-macOS-${{ env.VERSION }}.tar.gz
+ asset_content_type: application/gzip
diff --git a/BUILD.md b/BUILD.md
index 3b6e6446..8a76b68b 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -1,341 +1,5 @@
# Build Instructions
-# Contents
+Build instructions are available on [the website](https://polymc.org/wiki/development/build-instructions/).
-- [Getting the source](#getting-the-source)
-- [Linux](#linux)
-- [Windows](#windows)
-- [macOS](#macos)
-
-# Getting the source
-
-Clone the source code using git and grab all the submodules:
-
-```
-git clone https://github.com/PolyMC/PolyMC.git
-cd PolyMC
-git submodule init
-git submodule update
-```
-
-The rest of the documentation assumes you have already cloned the repository.
-
-# Linux and FreeBSD
-
-Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution. If you're using FreeBSD you should use 13.0-RELEASE or newer.
-
-## Build dependencies
-- A C++ compiler capable of building C++11 code.
-- Qt Development tools 5.6 or newer (`qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5` on Debian-based system)
-- cmake 3.1 or newer (`cmake` on Debian-based system)
-- zlib (`zlib1g-dev` on Debian-based system)
-- Java JDK (`openjdk-17-jdk`on Debian-based system)
-- GL headers (`libgl1-mesa-dev` on Debian-based system)
-- games/lwjgl port if using FreeBSD
-
-You can use IDEs like KDevelop or QtCreator to open the CMake project if you want to work on the code.
-
-### Building a portable binary
-
-```sh
-mkdir install
-# configure the project
-cmake -S . -B build \
- -DCMAKE_INSTALL_PREFIX=./install
-# build
-cd build
-make -j$(nproc) install
-```
-
-### Building & Installing to the System
-
-This is the preferred method for installation, and is suitable for packages.
-
-```sh
-# configure everything
-cmake -S . -B build \
-  -DCMAKE_BUILD_TYPE=Release \
- -DCMAKE_INSTALL_PREFIX="/usr" \ # Use "/usr" when building Linux packages. If building on FreeBSD or not for package, use "/usr/local"
- -DLauncher_LAYOUT=lin-system
-cd build
-make -j$(nproc) install # Optionally specify DESTDIR for packages (i.e. DESTDIR=${pkgdir})
-```
-
-### Building a .deb
-
-Requirements: [makedeb](https://docs.makedeb.org/) installed on your system.
-
-```
-git clone https://mpr.makedeb.org/polymc.git
-cd polymc
-makedeb -s
-```
-
-The deb will be located in the directory the repo was cloned in.
-
-### Building an .rpm
-
-Build dependencies are automatically installed using `dnf`, but you do need the `rpmdevtools` package (on Fedora)
-in order to fetch sources and setup your tree.
-You don't need to clone the repo for this; the spec file handles that
-
-```
-cd ~
-# setup your ~/rpmbuild directory, required for rpmbuild to work.
-rpmdev-setuptree
-# get the rpm spec file from the polymc-misc repo
-wget https://raw.githubusercontent.com/PolyMC/polymc-misc/master/rpm/polymc.spec
-# install build dependencies
-sudo dnf builddep polymc.spec
-# download build sources
-spectool -g -R polymc.spec
-# now build!
-rpmbuild -bb polymc.spec
-```
-
-The path to the rpm packages will be printed when the build is complete.
-
-### Building a Slackware package
-
-To build a Slackware package, first install [qt5 SlackBuild](http://slackbuilds.org/repository/14.2/libraries/qt5/) (on 15.0 and newer installed by defualt), then set up a [JDK](https://codeberg.org/glowiak/SlackBuilds/raw/branch/master/tgz/adoptium-jdk8.tar.gz).
-
-If you're using Slackware 14.2, update cmake with these commands:
-
-```
-mkdir -p /tmp/SBo
-cd /tmp/SBo
-wget -c https://github.com/Kitware/CMake/releases/download/v3.22.2/cmake-3.22.2.tar.gz
-tar xzvf cmake-3.22.2.tar.gz
-cd cmake-3.22.2
-./configure --prefix=/usr
-make
-sudo make install
-```
-
-Next, download the [SlackBuild](https://codeberg.org/glowiak/SlackBuilds/raw/branch/master/tgz/polymc.tar.gz), unpack it and type in extracted directory:
-
-```
-sudo ./polymc.SlackBuild # script will do everything, just sit up and wait
-sudo /sbin/installpkg /tmp/polymc-version-arch-1_SBo.tgz # install the created package
-```
-
-### Building a flatpak
-
-You don't need to clone the entire PolyMC repo for this; the flatpak file handles that.
-`flatpak` and `flatpak-builder` need to be installed on your system
-
-```sh
-git clone https://github.com/flathub/org.polymc.PolyMC
-cd org.polymc.PolyMC
-# remove --user --install if you want to build without installing
-flatpak-builder --user --install flatbuild org.polymc.PolyMC.yml
-```
-
-### Installing Qt using the installer (optional)
-
-1. Run the Qt installer.
-2. Choose a place to install Qt.
-3. Choose the components you want to install.
- - You need Qt 5.6.x 64-bit ticked.
- - You need Tools/Qt Creator ticked.
- - Other components are selected by default, you can untick them if you don't need them.
-4. Accept the license agreements.
-5. Double check the install details and then click "Install".
- - Installation can take a very long time, go grab a cup of tea or something and let it work.
-
-### Loading the project in Qt Creator (optional)
-
-1. Open Qt Creator.
-2. Choose `File->Open File or Project`.
-3. Navigate to the Launcher source folder you cloned and choose CMakeLists.txt.
-4. Read the instructions that just popped up about a build location and choose one.
-5. You should see "Run CMake" in the window.
- - Make sure that Generator is set to "Unix Generator (Desktop Qt 5.6.x GCC 64bit)".
- - Hit the "Run CMake" button.
- - You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- - Hit "Finish" if CMake ran successfully.
-6. Cross your fingers and press the Run button (bottom left of Qt Creator).
- - If the project builds successfully it will run and the Launcher window will pop up.
-
-**If this doesn't work for you, let us know on our Discord.**
-
-# Windows
-
-Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only.
-
-## Dependencies
-
-- [Qt 5.6+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
- - http://download.qt.io/new_archive/qt/5.6/5.6.0/qt-opensource-windows-x86-mingw492-5.6.0.exe
- - Download the MinGW version (MSVC version does not work).
-- [OpenSSL](https://github.com/IndySockets/OpenSSL-Binaries/tree/master/Archive/) -- Win32 OpenSSL, version 1.0.2g (from 2016)
- - https://github.com/IndySockets/OpenSSL-Binaries/raw/master/Archive/openssl-1.0.2g-i386-win32.zip
- - the usual OpenSSL for Windows (http://slproweb.com/products/Win32OpenSSL.html) only provides the newest version of OpenSSL, and we need the 1.0.2g version
- - **Download the 32-bit version, not 64-bit.**
- - Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
- - We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
-- [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine
-- [Java JDK 8](https://adoptium.net/releases.html?variant=openjdk8) - Use the MSI installer.
-- [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
-
-Ensure that OpenSSL, zlib, Java and CMake are on `PATH`.
-
-## Getting set up
-
-### Installing Qt
-
-1. Run the Qt installer
-2. Choose a place to install Qt (C:\Qt is the default),
-3. Choose the components you want to install
- - You need Qt 5.6 (32 bit) ticked,
- - You need Tools/Qt Creator ticked,
- - Other components are selected by default, you can untick them if you don't need them.
-4. Accept the license agreements,
-5. Double check the install details and then click "Install"
- - Installation can take a very long time, go grab a cup of tea or something and let it work.
-
-### Installing OpenSSL
-
-1. Download .zip file from the link above.
-2. Unzip and add the directory to PATH, so CMake can find it.
-
-### Installing CMake
-
-1. Run the CMake installer,
-2. It's easiest if you choose to add CMake to the PATH for all users,
- - If you don't choose to do this, remember where you installed CMake.
-
-### Loading the project
-
-1. Open Qt Creator,
-2. Choose File->Open File or Project,
-3. Navigate to the Launcher source folder you cloned and choose CMakeLists.txt,
-4. Read the instructions that just popped up about a build location and choose one,
-5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
- - Otherwise you can skip this step.
-6. You should see "Run CMake" in the window,
- - Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.6.x MinGW 32bit)",
- - Hit the "Run CMake" button,
- - You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- - Hit "Finish" if CMake ran successfully.
-7. Cross your fingers and press the Run button (bottom left of Qt Creator)!
- - If the project builds successfully it will run and the Launcher window will pop up,
- - Test OpenSSL by making an instance and trying to log in. If Qt Creator couldn't find OpenSSL during the CMake stage, login will fail and you'll get an error.
-
-The following .dlls are needed for the app to run (copy them to build directory if you want to be able to move the build to another pc):
-
-```
-
-platforms/qwindows.dll
-libeay32.dll
-libgcc_s_dw2-1.dll
-libssp-0.dll
-libstdc++-6.dll
-libwinpthread-1.dll
-Qt5Core.dll
-Qt5Gui.dll
-Qt5Network.dll
-Qt5Svg.dll
-Qt5Widgets.dll
-Qt5Xml.dll
-ssleay32.dll
-zlib1.dll
-
-```
-
-**These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on our Discord.**
-
-### Compile from command line on Windows
-
-1. If you installed Qt with the web installer, there should be a shortcut called `Qt 5.4 for Desktop (MinGW 4.9 32-bit)` in the Start menu on Windows 7 and 10. Best way to find it is to search for it. Do note you cannot just use cmd.exe, you have to use the shortcut, otherwise the proper MinGW software will not be on the PATH.
-2. Once that is open, change into your user directory, and clone PolyMC by doing `git clone --recursive https://github.com/PolyMC/PolyMC.git`, and change directory to the folder you cloned to.
-3. Make a build directory, and change directory to the directory and do `cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:\Path\that\makes\sense\for\you`. By default, it will install to C:\Program Files (x86), which you might not want, if you want a local installation. If you want to install it to that directory, make sure to run the command window as administrator.
-4. Do `mingw32-make -jX`, where X is the number of cores your CPU has plus one.
-5. Now to wait for it to compile. This could take some time. Hopefully it compiles properly.
-6. Run the command `mingw32-make install`, and it should install PolyMC, to whatever the `-DCMAKE_INSTALL_PREFIX` was.
-7. In most cases, whenever compiling, the OpenSSL dll's aren't put into the directory to where PolyMC installs, meaning you cannot log in. The best way to fix this is just to do `copy C:\OpenSSL-Win32\*.dll C:\Where\you\installed\PolyMC\to`. This should copy the required OpenSSL dll's to log in.
-
-# macOS
-
-### Install prerequisites:
-
-- Install XCode Command Line tools
-- Install the official build of CMake (https://cmake.org/download/)
-- Install JDK 8 (https://adoptium.net/releases.html?variant=openjdk8&jvmVariant=hotspot)
-- Get Qt 5.6 and install it (https://download.qt.io/new_archive/qt/5.6/5.6.3/) or higher (tested) (https://www.qt.io/download-qt-installer?utm_referrer=https%3A%2F%2Fwww.qt.io%2Fdownload-open-source)
-
-You can use `homebrew` to simplify the installation of build dependencies
-
-### XCode Command Line tools
-
-If you don't have XCode CommandLine tools installed, you can install them by using this command in the Terminal App
-
-```bash
-xcode-select --install
-```
-
-### Build
-
-Pick an installation path - this is where the final `PolyMC.app` will be constructed when you run `make install`. Supply it as the `CMAKE_INSTALL_PREFIX` argument during CMake configuration. By default, it's in the dist folder under PolyMC
-
-```
-mkdir build
-cd build
-cmake \
- -DCMAKE_C_COMPILER=/usr/bin/clang \
- -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
- -DCMAKE_BUILD_TYPE=Release \
- -DCMAKE_INSTALL_PREFIX:PATH="$(dirname $PWD)/dist/" \
- -DCMAKE_PREFIX_PATH="/path/to/Qt/" \
- -DQt5_DIR="/path/to/Qt/" \
- -DLauncher_LAYOUT=mac-bundle \
- -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 \
- ..
-make install
-```
-
-Remember to replace `/path/to/Qt/` with the actual path. For newer Qt installations, it is often in your home directory.
-
-**Note:** The final app bundle may not run due to code signing issues, which
-need to be fixed with `codesign -fs -`.
-
-# OpenBSD
-
-Tested on OpenBSD 7.0-alpha i386, on older should work too
-
-## Build dependencies
-- A C++ compiler capable of building C++11 code (included in base system)
-- Qt Development tools 5.6 or newer ([meta/qt5](https://openports.se/meta/qt5))
-- cmake 3.1 or newer ([devel/cmake](https://openports.se/devel/cmake))
-- zlib (included in base system)
-- Java JDK ([devel/jdk-1.8](https://openports.se/devel/jdk/1.8))
-- GL headers (included in base system)
-- lwjgl ([games/lwjgl](https://openports.se/games/lwjgl) and [games/lwjgl3](https://openports.se/games/lwjgl3))
-
-You can use IDEs like KDevelop or QtCreator to open the CMake project if you want to work on the code.
-
-### Building a portable binary
-
-```sh
-mkdir install
-# configure the project
-cmake -S . -B build \
- -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_PREFIX_PATH=/usr/local/lib/qt5/cmake
-# build
-cd build
-make -j$(nproc) install
-```
-
-### Building & Installing to the System
-
-This is the preferred method for installation, and is suitable for packages.
-
-```sh
-# configure everything
-cmake -S . -B build \
-  -DCMAKE_BUILD_TYPE=Release \
- -DCMAKE_INSTALL_PREFIX="/usr/local" \ # /usr/local is default in OpenBSD and FreeBSD
- -DLauncher_LAYOUT=lin-system -DCMAKE_PREFIX_PATH=/usr/local/lib/qt5/cmake # use linux layout and point to qt5 libs
-cd build
-make -j$(nproc) install # Optionally specify DESTDIR for packages (i.e. DESTDIR=${pkgdir})
-```
+If you would like to contribute or fix an issue with the Build instructions you can do so [here](https://github.com/PolyMC/polymc.github.io/blob/master/src/wiki/development/build-instructions.md).
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6365f4c0..db35eb09 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,13 +46,14 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
##################################### Set Application options #####################################
######## Set URLs ########
-set(Launcher_NEWS_RSS_URL "https://polymc.github.io/feed/feed.xml" CACHE STRING "URL to fetch PolyMC's news RSS feed from.")
-set(Launcher_NEWS_OPEN_URL "https://polymc.github.io/news/" CACHE STRING "URL that gets opened when the user clicks 'More News'")
+set(Launcher_NEWS_RSS_URL "https://polymc.org/feed/feed.xml" CACHE STRING "URL to fetch PolyMC's news RSS feed from.")
+set(Launcher_NEWS_OPEN_URL "https://polymc.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
+set(Launcher_HELP_URL "https://polymc.org/wiki/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1)
-set(Launcher_VERSION_MINOR 0)
-set(Launcher_VERSION_HOTFIX 6)
+set(Launcher_VERSION_MINOR 1)
+set(Launcher_VERSION_HOTFIX 0)
# Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@@ -70,7 +71,7 @@ set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch L
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 "17b47edd-c884-4997-926d-9e7f9a6b4647" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
+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")
# Bug tracker URL
set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.")
@@ -78,9 +79,14 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STR
# Translations Platform URL
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/polymc/polymc/" CACHE STRING "URL for the translations platform.")
+# Matrix Space
+set(Launcher_MATRIX_URL "https://matrix.to/#/#polymc:polymc.org" CACHE STRING "URL to the Matrix Space")
+
# Discord URL
set(Launcher_DISCORD_URL "https://discord.gg/Z52pwxWCHP" CACHE STRING "URL for the Discord guild.")
+
+
# Subreddit URL
set(Launcher_SUBREDDIT_URL "" CACHE STRING "URL for the subreddit.")
@@ -143,25 +149,11 @@ add_subdirectory(program_info)
####################################### Install layout #######################################
-# How to install the build results
-set(Launcher_LAYOUT "auto" CACHE STRING "The layout for the launcher installation (auto, win-bundle, lin-nodeps, lin-system, mac-bundle)")
-set_property(CACHE Launcher_LAYOUT PROPERTY STRINGS auto win-bundle lin-nodeps lin-system mac-bundle)
-
-if(Launcher_LAYOUT STREQUAL "auto")
- if(UNIX AND APPLE)
- set(Launcher_LAYOUT_REAL "mac-bundle")
- elseif(UNIX)
- set(Launcher_LAYOUT_REAL "lin-nodeps")
- elseif(WIN32)
- set(Launcher_LAYOUT_REAL "win-bundle")
- else()
- message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
- endif()
-else()
- set(Launcher_LAYOUT_REAL ${Launcher_LAYOUT})
-endif()
+# Install the build results according to platform
+set(Launcher_PORTABLE 1 CACHE BOOL "The type of installation (Portable or System)")
+
-if(Launcher_LAYOUT_REAL STREQUAL "mac-bundle")
+if(UNIX AND APPLE)
set(BINARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
@@ -192,47 +184,40 @@ if(Launcher_LAYOUT_REAL STREQUAL "mac-bundle")
# Add the icon
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
-elseif(Launcher_LAYOUT_REAL STREQUAL "lin-nodeps")
+elseif(UNIX)
set(BINARY_DEST_DIR "bin")
- set(LIBRARY_DEST_DIR "bin")
- set(PLUGIN_DEST_DIR "plugins")
- set(BUNDLE_DEST_DIR ".")
- set(RESOURCES_DEST_DIR ".")
- set(JARS_DEST_DIR "bin/jars")
+ if(Launcher_PORTABLE)
+ set(LIBRARY_DEST_DIR "bin")
+ set(BUNDLE_DEST_DIR ".")
+ set(JARS_DEST_DIR "bin/jars")
- # install as bundle with no dependencies included
- set(INSTALL_BUNDLE "nodeps")
-
- # Set RPATH
- SET(Launcher_BINARY_RPATH "$ORIGIN/")
-
- # Install basic runner script
- configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY)
- install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION ${BUNDLE_DEST_DIR} RENAME ${Launcher_Name})
-
-elseif(Launcher_LAYOUT_REAL STREQUAL "lin-system")
- set(Launcher_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
- set(Launcher_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
- set(Launcher_SHARE_DEST_DIR "share/polymc" CACHE STRING "Path to the shared data directory")
- set(JARS_DEST_DIR "${Launcher_SHARE_DEST_DIR}/jars")
- set(Launcher_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
- set(Launcher_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory")
- set(Launcher_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
+ # launcher/Application.cpp will use this value
+ set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_PORTABLE")
- set(BINARY_DEST_DIR ${Launcher_BINARY_DEST_DIR})
- set(LIBRARY_DEST_DIR ${Launcher_LIBRARY_DEST_DIR})
+ # Install basic runner script
+ configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY)
+ install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION ${BUNDLE_DEST_DIR} RENAME ${Launcher_Name})
+ else()
+ set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
+ set(JARS_DEST_DIR "share/jars")
+ set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
+ set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory")
+ set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
- MESSAGE(STATUS "Compiling for linux system with ${Launcher_SHARE_DEST_DIR} and LAUNCHER_LINUX_DATADIR")
- SET(Launcher_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DLAUNCHER_LINUX_DATADIR")
+ set(Launcher_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}")
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${Launcher_DESKTOP_DEST_DIR})
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${Launcher_METAINFO_DEST_DIR})
- install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${Launcher_ICON_DEST_DIR})
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR})
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR})
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR})
+ endif()
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
-elseif(Launcher_LAYOUT_REAL STREQUAL "win-bundle")
+ # Set RPATH
+ SET(Launcher_BINARY_RPATH "$ORIGIN/")
+
+elseif(WIN32)
set(BINARY_DEST_DIR ".")
set(LIBRARY_DEST_DIR ".")
set(PLUGIN_DEST_DIR ".")
@@ -249,7 +234,7 @@ elseif(Launcher_LAYOUT_REAL STREQUAL "win-bundle")
# install as bundle
set(INSTALL_BUNDLE "full")
else()
- message(FATAL_ERROR "No sensible install layout set.")
+ message(FATAL_ERROR "Platform not supported")
endif()
################################ Included Libs ################################
diff --git a/README.md b/README.md
index b7df751c..a114869c 100644
--- a/README.md
+++ b/README.md
@@ -10,127 +10,20 @@ This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. The Poly
<br>
# Installation
-- All packages (archived by version) can be found [here](https://packages.polymc.org/) ([latest](https://packages.polymc.org/latest)).
-- Last build status: https://jenkins.polymc.org/job/PolyMC/lastBuild/
-## 🐧 Linux
+- All downloads and instructions for PolyMC can be found [here](https://polymc.org/download/)
+- Last build status: https://github.com/PolyMC/PolyMC/actions
-### <img src="https://www.vectorlogo.zone/logos/linuxfoundation/linuxfoundation-icon.svg" height="20" alt=""/> Cross-distro packages
-
-<a href='https://flathub.org/apps/details/org.polymc.PolyMC'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
-
-<a href="https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage"><img src="https://docs.appimage.org/_images/download-appimage-banner.svg" width="240" alt="Download as AppImage" /></a>
-
-- [AppImage SHA256](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage.sha256)
-
-### <img src="https://www.vectorlogo.zone/logos/archlinux/archlinux-icon.svg" height="20"/> Arch Linux
-
-There are several AUR packages available:
-
-[![polymc](https://img.shields.io/badge/aur-polymc-blue)](https://aur.archlinux.org/packages/polymc/)
-[![polymc-bin](https://img.shields.io/badge/aur-polymc--bin-blue)](https://aur.archlinux.org/packages/polymc-bin/)
-[![polymc-git](https://img.shields.io/badge/aur-polymc--git-blue)](https://aur.archlinux.org/packages/polymc-git/)
-
-```sh
-# stable source package:
-yay -S polymc
-# stable binary package:
-yay -S polymc-bin
-# latest git package:
-yay -S polymc-git
-```
-
-### <img src="https://www.vectorlogo.zone/logos/debian/debian-icon.svg" height="20" /> Debian
-
-We use [makedeb](https://docs.makedeb.org/) for our Debian packages.
-Several MPR packages are available:
-
-[![polymc](https://img.shields.io/badge/mpr-polymc-orange)](https://mpr.makedeb.org/packages/polymc)
-[![polymc-bin](https://img.shields.io/badge/mpr-polymc--bin-orange)](https://mpr.makedeb.org/packages/polymc-bin)
-[![polymc-git](https://img.shields.io/badge/mpr-polymc--git-orange)](https://mpr.makedeb.org/packages/polymc-git)
-
-```sh
-# stable source package:
-sudo tap install polymc
-# stable binary package:
-sudo tap install polymc-bin
-# latest git package:
-sudo tap install polymc-git
-```
-
-### <img src="https://www.vectorlogo.zone/logos/nixos/nixos-icon.svg" height="20" /> Nix
-
-A [Nix derivation](packages/nix/NIX.md) is available.
-
-### <img src="https://www.gentoo.org/assets/img/logo/gentoo-signet.svg" height="20" /> Gentoo
-
-A Gentoo ebuild is available in the [swirl](https://git.swurl.xyz/swirl/ebuilds) overlay, named `games-action/polymc`.
-
-```sh
-# as root:
-emerge --oneshot eselect-repository
-eselect repository enable swirl
-emaint sync -r swirl
-emerge polymc
-# to use latest git version:
-sudo tee -a /etc/portage/package.accept_keywords <<< "=games-action/polymc-9999 **"
-```
-
-### <img src="https://www.vectorlogo.zone/logos/getfedora/getfedora-icon.svg" height="20"> Fedora
-
-An RPM package is available on [COPR](https://copr.fedorainfracloud.org/coprs/polymc/polymc/).
-
-```sh
-sudo dnf copr enable polymc/polymc
-sudo dnf install polymc
-```
-
-Alternatively, a COPR maintained by a PolyMC user (instead of Jenkins' automated builds) is available [here](https://copr.fedorainfracloud.org/coprs/sentry/polymc).
-
-```sh
-sudo dnf copr enable sentry/polymc
-sudo dnf install polymc
-```
-
-### <img src="https://lotar.altervista.org/wiki/_media/news/slackware-logo.png" height="20" /> Slackware
-
-[A SlackBuild](https://codeberg.org/glowiak/SlackBuilds/src/branch/master/repository/polymc.md) is available. You will need [qt5](http://slackbuilds.org/repository/14.2/libraries/qt5/) (on 15.0 installed by default), [a JDK](https://codeberg.org/glowiak/SlackBuilds/src/branch/master/repository/adoptium-jdk8.md), and if you're on 14.2, you need to compile newer CMake version manually. To build, type in extracted directory with all dependiences met:
-
- sudo ./polymc.SlackBuild
- sudo installpkg /tmp/polymc-version-arch-1_SBo.tgz
-
-If you are too lazy to do all these steps, you can just download [a prebuild x86_64 package](http://glowiak.github.io/file/polymc-latest-slackware) and install it with /sbin/installpkg:
-
- sudo /sbin/installpkg ~/Downloads/polymc-version-x86_64-1_SBo.tgz
-
-## <img src="https://www.vectorlogo.zone/logos/microsoft/microsoft-icon.svg" height="20" /> Windows
-
-[Windows (32-bit)](https://packages.polymc.org/latest/win32/win32.zip) ([SHA256](https://packages.polymc.org/latest/win32/win32.zip.sha256)) - this is a portable package, you can extract it anywhere and run it. This package needs testing.
-
-## <img src="https://www.vectorlogo.zone/logos/apple/apple-tile.svg" height="20" /> MacOS
-
-MacOS currently does not have any packages. We are still working on setting up MacOS packaging. Meanwhile, you can [build](https://github.com/PolyMC/PolyMC/blob/develop/BUILD.md#macos) it for yourself.
-
-## <img src="https://www.vectorlogo.zone/logos/freebsd/freebsd-icon.svg" height="20" /> FreeBSD
-
-For FreeBSD available are:
-
-- [AppBSD Image](http://glowiak.github.io/file/polymc-latest-fbsd64-appbsd) - a portable application, requires [AppBSD](https://codeberg.org/glowiak/appbsd/) to be installed.
-
-- [Gzipped binaries](http://glowiak.github.io/file/polymc-latest-fbsd64-raw) - traditional way to distribute, unpack and run.
-
-In both cases you need X11, Qt5 and Java installed. Both files are 64bit only.
-
-## <img src="https://raw.githubusercontent.com/AliasIO/wappalyzer/master/src/drivers/webextension/images/icons/OpenBSD%20httpd.svg" height="20" /> OpenBSD
-
-For OpenBSD available are [gzipped 32-bit binaries](http://glowiak.github.io/file/polymc-latest-obsd32-raw), download, unpack and run.
-
-You need X11, Qt5 and Java installed.
## Development Builds
There are per-commit development builds available [here](https://github.com/PolyMC/PolyMC/actions). These have debug information in the binaries, so their file sizes are relatively larger.
-Builds are provided for Linux, AppImage on Linux, Windows, and macOS.
+Portable builds are provided for AppImage on Linux, Windows, and macOS.
+
+For Debian and Arch, you can use these packages for the latest development versions:
+[![polymc-git](https://img.shields.io/badge/aur-polymc--git-blue)](https://aur.archlinux.org/packages/polymc-git/)
+[![polymc-git](https://img.shields.io/badge/mpr-polymc--git-orange)](https://mpr.makedeb.org/packages/polymc-git)
+For flatpak, you can use [flathub-beta](https://discourse.flathub.org/t/how-to-use-flathub-beta/2111)
# Help & Support
@@ -155,9 +48,10 @@ If you want to contribute to PolyMC you might find it useful to join our Discord
## Building
-If you want to build PolyMC yourself, check [BUILD.md](BUILD.md) for build instructions.
+If you want to build PolyMC yourself, check [Build Instructions](https://polymc.org/wiki/development/build-instructions/) for build instructions.
## Code formatting
+
Just follow the existing formatting.
In general, in order of importance:
@@ -172,6 +66,15 @@ In general, in order of importance:
The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate.org/projects/polymc/polymc/) and information about translating PolyMC is available at https://github.com/PolyMC/Translations
+## Download information
+To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/blob/master/src/download.md)
+
## Forking/Redistributing/Custom builds policy
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.
+
+All launcher code is available under the GPL-3 license.
+
+[Source for the website](https://github.com/PolyMC/polymc.github.io) is hosted under the AGPL-3 License.
+
+The logo and related assets are under the CC BY-NC-SA 4.0 license.
diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index ed6a755a..6524fb5d 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -48,12 +48,14 @@ Config::Config()
VERSION_STR = "@Launcher_VERSION_STRING@";
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
+ HELP_URL = "@Launcher_HELP_URL@";
IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@";
MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@";
META_URL = "@Launcher_META_URL@";
BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
+ MATRIX_URL = "@Launcher_MATRIX_URL@";
DISCORD_URL = "@Launcher_DISCORD_URL@";
SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@";
}
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index a5c3c859..79210505 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -67,6 +67,11 @@ public:
QString NEWS_OPEN_URL;
/**
+ * URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help
+ */
+ QString HELP_URL;
+
+ /**
* Client ID you can get from Imgur when you register an application
*/
QString IMGUR_CLIENT_ID;
@@ -83,6 +88,7 @@ public:
QString BUG_TRACKER_URL;
QString TRANSLATIONS_URL;
+ QString MATRIX_URL;
QString DISCORD_URL;
QString SUBREDDIT_URL;
@@ -90,8 +96,8 @@ public:
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString AUTH_BASE = "https://authserver.mojang.com/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
- QString FMLLIBS_BASE_URL = "https://files.multimc.org/fmllibs/";
- QString TRANSLATIONS_BASE_URL = "https://meta.polymc.org/translations/";
+ QString FMLLIBS_BASE_URL = "https://files.polymc.org/fmllibs/";
+ QString TRANSLATIONS_BASE_URL = "https://i18n.polymc.org/";
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index ce40c4f3..84b5a0fe 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -192,27 +192,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
#endif
startTime = QDateTime::currentDateTime();
-#ifdef Q_OS_LINUX
- {
- QFile osrelease("/proc/sys/kernel/osrelease");
- if (osrelease.open(QFile::ReadOnly | QFile::Text)) {
- QTextStream in(&osrelease);
- auto contents = in.readAll();
- if(
- contents.contains("WSL", Qt::CaseInsensitive) ||
- contents.contains("Microsoft", Qt::CaseInsensitive)
- ) {
- showFatalErrorMessage(
- "Unsupported system detected!",
- "Linux-on-Windows distributions are not supported.\n\n"
- "Please use the Windows binary when playing on Windows."
- );
- return;
- }
- }
- }
-#endif
-
// Don't quit on hiding the last window
this->setQuitOnLastWindowClosed(false);
@@ -315,16 +294,20 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
else
{
-#ifdef LAUNCHER_LINUX_DATADIR
- QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
- if (xdgDataHome.isEmpty())
- xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
- dataPath = xdgDataHome + "/polymc";
- adjustedBy += "XDG standard " + dataPath;
-#elif defined(Q_OS_MAC)
+#if !defined(LAUNCHER_PORTABLE) || defined(Q_OS_MAC)
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
dataPath = foo.absolutePath();
- adjustedBy += "Fallback to special Mac location " + dataPath;
+ adjustedBy += dataPath;
+
+#ifdef Q_OS_LINUX
+ // TODO: this should be removed in a future version
+ // TODO: provide a migration path similar to macOS migration
+ QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
+ if (bar.exists()) {
+ dataPath = bar.absolutePath();
+ adjustedBy += "Legacy data path " + dataPath;
+ }
+#endif
#else
dataPath = applicationDirPath();
adjustedBy += "Fallback to binary path " + dataPath;
@@ -526,8 +509,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
#elif defined(Q_OS_WIN32)
m_rootPath = binPath;
#elif defined(Q_OS_MAC)
- QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
+ QDir foo(FS::PathCombine(binPath, "../.."));
m_rootPath = foo.absolutePath();
+ // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues)
+ FS::updateTimestamp(m_rootPath);
#endif
#ifdef MULTIMC_JARS_LOCATION
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 374d4a29..1bff9e1d 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -39,6 +39,7 @@ 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");
// Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 51a63722..751b354b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -279,13 +279,6 @@ set(MINECRAFT_SOURCES
minecraft/launch/VerifyJavaInstall.cpp
minecraft/launch/VerifyJavaInstall.h
- minecraft/legacy/LegacyModList.h
- minecraft/legacy/LegacyModList.cpp
- minecraft/legacy/LegacyInstance.h
- minecraft/legacy/LegacyInstance.cpp
- minecraft/legacy/LegacyUpgradeTask.h
- minecraft/legacy/LegacyUpgradeTask.cpp
-
minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.h
@@ -693,8 +686,6 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/OtherLogsPage.h
ui/pages/instance/ServersPage.cpp
ui/pages/instance/ServersPage.h
- ui/pages/instance/LegacyUpgradePage.cpp
- ui/pages/instance/LegacyUpgradePage.h
ui/pages/instance/WorldListPage.cpp
ui/pages/instance/WorldListPage.h
@@ -796,6 +787,8 @@ SET(LAUNCHER_SOURCES
ui/pagedialog/PageDialog.h
ui/dialogs/ProgressDialog.cpp
ui/dialogs/ProgressDialog.h
+ ui/dialogs/ReviewMessageBox.cpp
+ ui/dialogs/ReviewMessageBox.h
ui/dialogs/UpdateDialog.cpp
ui/dialogs/UpdateDialog.h
ui/dialogs/VersionSelectDialog.cpp
@@ -874,7 +867,6 @@ qt5_wrap_ui(LAUNCHER_UI
ui/pages/instance/InstanceSettingsPage.ui
ui/pages/instance/VersionPage.ui
ui/pages/instance/WorldListPage.ui
- ui/pages/instance/LegacyUpgradePage.ui
ui/pages/instance/ScreenshotsPage.ui
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
ui/pages/modplatform/atlauncher/AtlPage.ui
@@ -904,6 +896,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/dialogs/AboutDialog.ui
ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui
+ ui/dialogs/ReviewMessageBox.ui
)
qt5_add_resources(LAUNCHER_RESOURCES
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 35adeaf9..c2bfe839 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -42,7 +42,6 @@ void InstanceCopyTask::copyFinished()
}
// FIXME: shouldn't this be able to report errors?
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
- instanceSettings->registerSetting("InstanceType", "Legacy");
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
inst->setName(m_instName);
diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp
index eafc5126..4c37bd7f 100644
--- a/launcher/InstanceCreationTask.cpp
+++ b/launcher/InstanceCreationTask.cpp
@@ -17,8 +17,6 @@ void InstanceCreationTask::executeTask()
{
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->suspendSave();
- instanceSettings->registerSetting("InstanceType", "Legacy");
- instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
auto components = inst.getPackProfile();
components->buildingFromScratch();
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index ec378538..6dd615c7 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -261,8 +261,6 @@ void InstanceImportTask::processFlame()
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
- instanceSettings->registerSetting("InstanceType", "Legacy");
- instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto mcVersion = pack.minecraft.version;
// Hack to correct some 'special sauce'...
@@ -422,7 +420,6 @@ void InstanceImportTask::processMultiMC()
{
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
- instanceSettings->registerSetting("InstanceType", "Legacy");
NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index ad18740b..6e37e3d8 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -32,7 +32,6 @@
#include "BaseInstance.h"
#include "InstanceTask.h"
#include "settings/INISettingsObject.h"
-#include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h"
#include "minecraft/MinecraftInstance.h"
#include "FileSystem.h"
@@ -544,23 +543,8 @@ 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;
-
- instanceSettings->registerSetting("InstanceType", "Legacy");
-
- QString inst_type = instanceSettings->get("InstanceType").toString();
-
- if (inst_type == "OneSix" || inst_type == "Nostalgia")
- {
- inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
- }
- else if (inst_type == "Legacy")
- {
- inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
- }
- else
- {
- inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
- }
+ // TODO: Handle incompatible instances
+ inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h
index 97eeab8c..357157d0 100644
--- a/launcher/InstancePageProvider.h
+++ b/launcher/InstancePageProvider.h
@@ -1,6 +1,5 @@
#pragma once
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/legacy/LegacyInstance.h"
#include <FileSystem.h>
#include "ui/pages/BasePage.h"
#include "ui/pages/BasePageProvider.h"
@@ -14,7 +13,6 @@
#include "ui/pages/instance/ScreenshotsPage.h"
#include "ui/pages/instance/InstanceSettingsPage.h"
#include "ui/pages/instance/OtherLogsPage.h"
-#include "ui/pages/instance/LegacyUpgradePage.h"
#include "ui/pages/instance/WorldListPage.h"
#include "ui/pages/instance/ServersPage.h"
#include "ui/pages/instance/GameOptionsPage.h"
@@ -34,31 +32,20 @@ public:
QList<BasePage *> values;
values.append(new LogPage(inst));
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
- if(onesix)
- {
- values.append(new VersionPage(onesix.get()));
- auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Mods"), "Loader-mods");
- modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
- values.append(modsPage);
- values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods"));
- values.append(new ResourcePackPage(onesix.get()));
- values.append(new TexturePackPage(onesix.get()));
- values.append(new ShaderPackPage(onesix.get()));
- values.append(new NotesPage(onesix.get()));
- values.append(new WorldListPage(onesix.get(), onesix->worldList()));
- values.append(new ServersPage(onesix));
- // values.append(new GameOptionsPage(onesix.get()));
- values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
- values.append(new InstanceSettingsPage(onesix.get()));
- }
- std::shared_ptr<LegacyInstance> legacy = std::dynamic_pointer_cast<LegacyInstance>(inst);
- if(legacy)
- {
- values.append(new LegacyUpgradePage(legacy));
- values.append(new NotesPage(legacy.get()));
- values.append(new WorldListPage(legacy.get(), legacy->worldList()));
- values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots")));
- }
+ values.append(new VersionPage(onesix.get()));
+ auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Mods"), "Loader-mods");
+ modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
+ values.append(modsPage);
+ values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods"));
+ values.append(new ResourcePackPage(onesix.get()));
+ values.append(new TexturePackPage(onesix.get()));
+ values.append(new ShaderPackPage(onesix.get()));
+ values.append(new NotesPage(onesix.get()));
+ values.append(new WorldListPage(onesix.get(), onesix->worldList()));
+ values.append(new ServersPage(onesix));
+ // values.append(new GameOptionsPage(onesix.get()));
+ values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
+ values.append(new InstanceSettingsPage(onesix.get()));
auto logMatcher = inst->getLogFileMatcher();
if(logMatcher)
{
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index 32fc99cb..4fd2eade 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -56,7 +56,7 @@ void LaunchController::decideAccount()
m_parentWidget,
tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Mojang or Minecraft "
- "account logged in."
+ "account logged in. "
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information,
QMessageBox::Yes | QMessageBox::No
@@ -228,6 +228,18 @@ void LaunchController::login() {
emitFailed(errorString);
return;
}
+ case AccountState::Disabled: {
+ auto errorString = tr("The launcher's client identification has changed. Please remove this account and add it again.");
+ QMessageBox::warning(
+ m_parentWidget,
+ tr("Client identification changed"),
+ errorString,
+ QMessageBox::StandardButton::Ok,
+ QMessageBox::StandardButton::Ok
+ );
+ emitFailed(errorString);
+ return;
+ }
case AccountState::Gone: {
auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to.");
QMessageBox::warning(
diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h
index 7e4f1b7d..ddada5a2 100644
--- a/launcher/ModDownloadTask.h
+++ b/launcher/ModDownloadTask.h
@@ -10,6 +10,7 @@ class ModDownloadTask : public Task {
Q_OBJECT
public:
explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr<ModFolderModel> mods);
+ const QString& getFilename() const { return filename; }
public slots:
bool abort() override;
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 7327f9d5..6db12c42 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -124,18 +124,7 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
- // DEPRECATED: Read what versions the user configuration thinks should be used
- m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
- m_settings->registerSetting("LWJGLVersion", "");
- m_settings->registerSetting("ForgeVersion", "");
- m_settings->registerSetting("LiteloaderVersion", "");
-
m_components.reset(new PackProfile(this));
- m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString());
- auto setting = m_settings->getSetting("LWJGLVersion");
- m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString());
- m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString());
- m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString());
}
void MinecraftInstance::saveNow()
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index 59a8f133..d516e555 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -272,18 +272,6 @@ void PackProfile::save_internal()
bool PackProfile::load()
{
auto filename = componentsFilePath();
- QFile componentsFile(filename);
-
- // migrate old config to new one, if needed
- if(!componentsFile.exists())
- {
- if(!migratePreComponentConfig())
- {
- // FIXME: the user should be notified...
- qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name();
- return false;
- }
- }
// load the new component list and swap it with the current one...
ComponentContainer newComponents;
@@ -369,239 +357,6 @@ void PackProfile::updateFailed(const QString& error)
invalidateLaunchProfile();
}
-// NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig).
-static void upgradeDeprecatedFiles(QString root, QString instanceName)
-{
- auto versionJsonPath = FS::PathCombine(root, "version.json");
- auto customJsonPath = FS::PathCombine(root, "custom.json");
- auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json");
-
- QString sourceFile;
- QString renameFile;
-
- // convert old crap.
- if(QFile::exists(customJsonPath))
- {
- sourceFile = customJsonPath;
- renameFile = versionJsonPath;
- }
- else if(QFile::exists(versionJsonPath))
- {
- sourceFile = versionJsonPath;
- }
- if(!sourceFile.isEmpty() && !QFile::exists(mcJson))
- {
- if(!FS::ensureFilePathExists(mcJson))
- {
- qWarning() << "Couldn't create patches folder for" << instanceName;
- return;
- }
- if(!renameFile.isEmpty() && QFile::exists(renameFile))
- {
- if(!QFile::rename(renameFile, renameFile + ".old"))
- {
- qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName;
- return;
- }
- }
- auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false);
- ProfileUtils::removeLwjglFromPatch(file);
- file->uid = "net.minecraft";
- file->version = file->minecraftVersion;
- file->name = "Minecraft";
-
- Meta::Require needsLwjgl;
- needsLwjgl.uid = "org.lwjgl";
- file->requires.insert(needsLwjgl);
-
- if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson))
- {
- return;
- }
- if(!QFile::rename(sourceFile, sourceFile + ".old"))
- {
- qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName;
- return;
- }
- }
-}
-
-/*
- * Migrate old layout to the component based one...
- * - Part of the version information is taken from `instance.cfg` (fed to this class from outside).
- * - Part is taken from the old order.json file.
- * - Part is loaded from loose json files in the instance's `patches` directory.
- */
-bool PackProfile::migratePreComponentConfig()
-{
- // upgrade the very old files from the beginnings of MultiMC 5
- upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name());
-
- QList<ComponentPtr> components;
- QSet<QString> loaded;
-
- auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict)
- {
- auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json");
- auto intendedVersion = d->getOldConfigVersion(uid);
- // load up the base minecraft patch
- ComponentPtr component;
- if(QFile::exists(jsonFilePath))
- {
- if(intendedVersion.isEmpty())
- {
- intendedVersion = emptyVersion;
- }
- auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false);
- // fix uid
- file->uid = uid;
- // if version is missing, add it from the outside.
- if(file->version.isEmpty())
- {
- file->version = intendedVersion;
- }
- // if this is a dependency (LWJGL), mark it also as volatile
- if(asDependency)
- {
- file->m_volatile = true;
- }
- // insert requirements if needed
- if(!req.uid.isEmpty())
- {
- file->requires.insert(req);
- }
- // insert conflicts if needed
- if(!conflict.uid.isEmpty())
- {
- file->conflicts.insert(conflict);
- }
- // FIXME: @QUALITY do not ignore return value
- ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath);
- component = new Component(this, uid, file);
- component->m_version = intendedVersion;
- }
- else if(!intendedVersion.isEmpty())
- {
- auto metaVersion = APPLICATION->metadataIndex()->get(uid, intendedVersion);
- component = new Component(this, metaVersion);
- }
- else
- {
- return;
- }
- component->m_dependencyOnly = asDependency;
- component->m_important = !asDependency;
- components.append(component);
- };
- // TODO: insert depends and conflicts here if these are customized files...
- Meta::Require reqLwjgl;
- reqLwjgl.uid = "org.lwjgl";
- reqLwjgl.suggests = "2.9.1";
- Meta::Require conflictLwjgl3;
- conflictLwjgl3.uid = "org.lwjgl3";
- Meta::Require nullReq;
- addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3);
- addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq);
-
- // first, collect all other file-based patches and load them
- QMap<QString, ComponentPtr> loadedComponents;
- QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches"));
- for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files))
- {
- // parse the file
- qDebug() << "Reading" << info.fileName();
- auto file = ProfileUtils::parseJsonFile(info, true);
-
- // correct missing or wrong uid based on the file name
- QString uid = info.completeBaseName();
-
- // ignore builtins, they've been handled already
- if (uid == "net.minecraft")
- continue;
- if (uid == "org.lwjgl")
- continue;
-
- // handle horrible corner cases
- if(uid.isEmpty())
- {
- // if you have a file named '.json', make it just go away.
- // FIXME: @QUALITY do not ignore return value
- QFile::remove(info.absoluteFilePath());
- continue;
- }
- file->uid = uid;
- // FIXME: @QUALITY do not ignore return value
- ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath());
-
- auto component = new Component(this, file->uid, file);
- auto version = d->getOldConfigVersion(file->uid);
- if(!version.isEmpty())
- {
- component->m_version = version;
- }
- loadedComponents[file->uid] = component;
- }
- // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files
- auto loadSpecial = [&](const QString & uid, int order)
- {
- auto patchVersion = d->getOldConfigVersion(uid);
- if(!patchVersion.isEmpty() && !loadedComponents.contains(uid))
- {
- auto patch = new Component(this, APPLICATION->metadataIndex()->get(uid, patchVersion));
- patch->setOrder(order);
- loadedComponents[uid] = patch;
- }
- };
- loadSpecial("net.minecraftforge", 5);
- loadSpecial("com.mumfrey.liteloader", 10);
-
- // load the old order.json file, if present
- ProfileUtils::PatchOrder userOrder;
- ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder);
-
- // now add all the patches by user sort order
- for (auto uid : userOrder)
- {
- // ignore builtins
- if (uid == "net.minecraft")
- continue;
- if (uid == "org.lwjgl")
- continue;
- // ordering has a patch that is gone?
- if(!loadedComponents.contains(uid))
- {
- continue;
- }
- components.append(loadedComponents.take(uid));
- }
-
- // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json
- if(!loadedComponents.isEmpty())
- {
- // inserting into multimap by order number as key sorts the patches and detects duplicates
- QMultiMap<int, ComponentPtr> files;
- auto iter = loadedComponents.begin();
- while(iter != loadedComponents.end())
- {
- files.insert((*iter)->getOrder(), *iter);
- iter++;
- }
-
- // then just extract the patches and put them in the list
- for (auto order : files.keys())
- {
- const auto &values = files.values(order);
- for(auto &value: values)
- {
- // TODO: put back the insertion of problem messages here, so the user knows about the id duplication
- components.append(value);
- }
- }
- }
- // new we have a complete list of components...
- return savePackProfile(componentsFilePath(), components);
-}
-
// END: save/load
void PackProfile::appendComponent(ComponentPtr component)
@@ -1169,15 +924,6 @@ std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
return d->m_profile;
}
-void PackProfile::setOldConfigVersion(const QString& uid, const QString& version)
-{
- if(version.isEmpty())
- {
- return;
- }
- d->m_oldConfigVersions[uid] = version;
-}
-
bool PackProfile::setComponentVersion(const QString& uid, const QString& version, bool important)
{
auto iter = d->componentIndex.find(uid);
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index f30deb5a..989d1c6a 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -143,8 +143,6 @@ private:
bool installCustomJar_internal(QString filepath);
bool removeComponent_internal(ComponentPtr patch);
- bool migratePreComponentConfig();
-
private: /* data */
std::unique_ptr<PackProfileData> d;
diff --git a/launcher/minecraft/PackProfile_p.h b/launcher/minecraft/PackProfile_p.h
index fce921bb..715e0460 100644
--- a/launcher/minecraft/PackProfile_p.h
+++ b/launcher/minecraft/PackProfile_p.h
@@ -18,18 +18,6 @@ struct PackProfileData
// the launch profile (volatile, temporary thing created on demand)
std::shared_ptr<LaunchProfile> m_profile;
- // version information migrated from instance.cfg file. Single use on migration!
- std::map<QString, QString> m_oldConfigVersions;
- QString getOldConfigVersion(const QString& uid) const
- {
- const auto iter = m_oldConfigVersions.find(uid);
- if(iter != m_oldConfigVersions.cend())
- {
- return (*iter).second;
- }
- return QString();
- }
-
// persistent list of components and related machinery
ComponentContainer components;
ComponentIndex componentIndex;
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
index 9b84fe1a..f791db14 100644
--- a/launcher/minecraft/auth/AccountData.cpp
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -327,6 +327,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
}
if(type == AccountType::MSA) {
+ auto clientIDV = data.value("msa-client-id");
+ if (clientIDV.isString()) {
+ msaClientID = clientIDV.toString();
+ } // leave msaClientID empty if it doesn't exist or isn't a string
msaToken = tokenFromJSONV3(data, "msa");
userToken = tokenFromJSONV3(data, "utoken");
xboxApiToken = tokenFromJSONV3(data, "xrp-main");
@@ -360,6 +364,7 @@ QJsonObject AccountData::saveState() const {
}
else if (type == AccountType::MSA) {
output["type"] = "MSA";
+ output["msa-client-id"] = msaClientID;
tokenToJSONV3(output, msaToken, "msa");
tokenToJSONV3(output, userToken, "utoken");
tokenToJSONV3(output, xboxApiToken, "xrp-main");
diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h
index 606c1ad1..6749a471 100644
--- a/launcher/minecraft/auth/AccountData.h
+++ b/launcher/minecraft/auth/AccountData.h
@@ -47,6 +47,7 @@ enum class AccountState {
Offline,
Working,
Online,
+ Disabled,
Errored,
Expired,
Gone
@@ -81,6 +82,7 @@ struct AccountData {
bool legacy = false;
bool canMigrateToMSA = false;
+ QString msaClientID;
Katabasis::Token msaToken;
Katabasis::Token userToken;
Katabasis::Token xboxApiToken;
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index 04470e1c..e404cdda 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -291,6 +291,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
case AccountState::Expired: {
return tr("Expired", "Account status");
}
+ case AccountState::Disabled: {
+ return tr("Disabled", "Account status");
+ }
case AccountState::Gone: {
return tr("Gone", "Account status");
}
diff --git a/launcher/minecraft/auth/AccountTask.cpp b/launcher/minecraft/auth/AccountTask.cpp
index 98d8d94d..321b350f 100644
--- a/launcher/minecraft/auth/AccountTask.cpp
+++ b/launcher/minecraft/auth/AccountTask.cpp
@@ -43,6 +43,8 @@ QString AccountTask::getStateMessage() const
return tr("Authentication task succeeded.");
case AccountTaskState::STATE_OFFLINE:
return tr("Failed to contact the authentication server.");
+ case AccountTaskState::STATE_DISABLED:
+ return tr("Client ID has changed. New session needs to be created.");
case AccountTaskState::STATE_FAILED_SOFT:
return tr("Encountered an error during authentication.");
case AccountTaskState::STATE_FAILED_HARD:
@@ -78,6 +80,12 @@ bool AccountTask::changeState(AccountTaskState newState, QString reason)
emitFailed(reason);
return false;
}
+ case AccountTaskState::STATE_DISABLED: {
+ m_data->errorString = reason;
+ m_data->accountState = AccountState::Disabled;
+ emitFailed(reason);
+ return false;
+ }
case AccountTaskState::STATE_FAILED_SOFT: {
m_data->errorString = reason;
m_data->accountState = AccountState::Errored;
diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h
index dac3f1b5..c2a5d86c 100644
--- a/launcher/minecraft/auth/AccountTask.h
+++ b/launcher/minecraft/auth/AccountTask.h
@@ -35,6 +35,7 @@ enum class AccountTaskState
STATE_CREATED,
STATE_WORKING,
STATE_SUCCEEDED,
+ STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index ffc81ed8..a604cadf 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -176,6 +176,9 @@ void MinecraftAccount::authFailed(QString reason)
{
switch (m_currentTask->taskState()) {
case AccountTaskState::STATE_OFFLINE:
+ case AccountTaskState::STATE_DISABLED: {
+ // NOTE: user will need to fix this themselves.
+ }
case AccountTaskState::STATE_FAILED_SOFT: {
// NOTE: this doesn't do much. There was an error of some sort.
}
diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp
index 779aee43..207d9373 100644
--- a/launcher/minecraft/auth/steps/MSAStep.cpp
+++ b/launcher/minecraft/auth/steps/MSAStep.cpp
@@ -12,9 +12,10 @@ using OAuth2 = Katabasis::DeviceFlow;
using Activity = Katabasis::Activity;
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) {
+ m_clientId = APPLICATION->getMSAClientID();
OAuth2::Options opts;
opts.scope = "XboxLive.signin offline_access";
- opts.clientIdentifier = APPLICATION->getMSAClientID();
+ opts.clientIdentifier = m_clientId;
opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
@@ -48,6 +49,10 @@ void MSAStep::rehydrate() {
void MSAStep::perform() {
switch(m_action) {
case Refresh: {
+ if (m_data->msaClientID != m_clientId) {
+ emit hideVerificationUriAndCode();
+ emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed."));
+ }
m_oauth2->refresh();
return;
}
@@ -57,6 +62,7 @@ void MSAStep::perform() {
m_oauth2->setExtraRequestParams(extraOpts);
*m_data = AccountData();
+ m_data->msaClientID = m_clientId;
m_oauth2->login();
return;
}
diff --git a/launcher/minecraft/auth/steps/MSAStep.h b/launcher/minecraft/auth/steps/MSAStep.h
index 49ba3542..301e1465 100644
--- a/launcher/minecraft/auth/steps/MSAStep.h
+++ b/launcher/minecraft/auth/steps/MSAStep.h
@@ -29,4 +29,5 @@ private slots:
private:
Katabasis::DeviceFlow *m_oauth2 = nullptr;
Action m_action;
+ QString m_clientId;
};
diff --git a/launcher/minecraft/legacy/LegacyInstance.cpp b/launcher/minecraft/legacy/LegacyInstance.cpp
deleted file mode 100644
index f467ec06..00000000
--- a/launcher/minecraft/legacy/LegacyInstance.cpp
+++ /dev/null
@@ -1,256 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <QFileInfo>
-#include <minecraft/launch/LauncherPartLaunch.h>
-#include <QDir>
-#include <settings/Setting.h>
-
-#include "LegacyInstance.h"
-
-#include "minecraft/legacy/LegacyModList.h"
-#include "minecraft/WorldList.h"
-#include <MMCZip.h>
-#include <FileSystem.h>
-
-LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
- : BaseInstance(globalSettings, settings, rootDir)
-{
- settings->registerSetting("NeedsRebuild", true);
- settings->registerSetting("ShouldUpdate", false);
- settings->registerSetting("JarVersion", QString());
- settings->registerSetting("IntendedJarVersion", QString());
- /*
- * custom base jar has no default. it is determined in code... see the accessor methods for
- *it
- *
- * for instances that DO NOT have the CustomBaseJar setting (legacy instances),
- * [.]minecraft/bin/mcbackup.jar is the default base jar
- */
- settings->registerSetting("UseCustomBaseJar", true);
- settings->registerSetting("CustomBaseJar", "");
-}
-
-QString LegacyInstance::mainJarToPreserve() const
-{
- bool customJar = m_settings->get("UseCustomBaseJar").toBool();
- if(customJar)
- {
- auto base = baseJar();
- if(QFile::exists(base))
- {
- return base;
- }
- }
- auto runnable = runnableJar();
- if(QFile::exists(runnable))
- {
- return runnable;
- }
- return QString();
-}
-
-
-QString LegacyInstance::baseJar() const
-{
- bool customJar = m_settings->get("UseCustomBaseJar").toBool();
- if (customJar)
- {
- return customBaseJar();
- }
- else
- return defaultBaseJar();
-}
-
-QString LegacyInstance::customBaseJar() const
-{
- QString value = m_settings->get("CustomBaseJar").toString();
- if (value.isNull() || value.isEmpty())
- {
- return defaultCustomBaseJar();
- }
- return value;
-}
-
-bool LegacyInstance::shouldUseCustomBaseJar() const
-{
- return m_settings->get("UseCustomBaseJar").toBool();
-}
-
-
-Task::Ptr LegacyInstance::createUpdateTask(Net::Mode)
-{
- return nullptr;
-}
-
-std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const
-{
- if (!jar_mod_list)
- {
- auto list = new LegacyModList(jarModsDir(), modListFile());
- jar_mod_list.reset(list);
- }
- jar_mod_list->update();
- return jar_mod_list;
-}
-
-QString LegacyInstance::gameRoot() const
-{
- QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
- QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
-
- if (mcDir.exists() && !dotMCDir.exists())
- return mcDir.filePath();
- else
- return dotMCDir.filePath();
-}
-
-QString LegacyInstance::binRoot() const
-{
- return FS::PathCombine(gameRoot(), "bin");
-}
-
-QString LegacyInstance::modsRoot() const {
- return FS::PathCombine(gameRoot(), "mods");
-}
-
-
-QString LegacyInstance::jarModsDir() const
-{
- return FS::PathCombine(instanceRoot(), "instMods");
-}
-
-QString LegacyInstance::libDir() const
-{
- return FS::PathCombine(gameRoot(), "lib");
-}
-
-QString LegacyInstance::savesDir() const
-{
- return FS::PathCombine(gameRoot(), "saves");
-}
-
-QString LegacyInstance::coreModsDir() const
-{
- return FS::PathCombine(gameRoot(), "coremods");
-}
-
-QString LegacyInstance::resourceDir() const
-{
- return FS::PathCombine(gameRoot(), "resources");
-}
-QString LegacyInstance::texturePacksDir() const
-{
- return FS::PathCombine(gameRoot(), "texturepacks");
-}
-
-QString LegacyInstance::runnableJar() const
-{
- return FS::PathCombine(binRoot(), "minecraft.jar");
-}
-
-QString LegacyInstance::modListFile() const
-{
- return FS::PathCombine(instanceRoot(), "modlist");
-}
-
-QString LegacyInstance::instanceConfigFolder() const
-{
- return FS::PathCombine(gameRoot(), "config");
-}
-
-bool LegacyInstance::shouldRebuild() const
-{
- return m_settings->get("NeedsRebuild").toBool();
-}
-
-QString LegacyInstance::currentVersionId() const
-{
- return m_settings->get("JarVersion").toString();
-}
-
-QString LegacyInstance::intendedVersionId() const
-{
- return m_settings->get("IntendedJarVersion").toString();
-}
-
-bool LegacyInstance::shouldUpdate() const
-{
- QVariant var = settings()->get("ShouldUpdate");
- if (!var.isValid() || var.toBool() == false)
- {
- return intendedVersionId() != currentVersionId();
- }
- return true;
-}
-
-QString LegacyInstance::defaultBaseJar() const
-{
- return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar";
-}
-
-QString LegacyInstance::defaultCustomBaseJar() const
-{
- return FS::PathCombine(binRoot(), "mcbackup.jar");
-}
-
-std::shared_ptr<WorldList> LegacyInstance::worldList() const
-{
- if (!m_world_list)
- {
- m_world_list.reset(new WorldList(savesDir()));
- }
- return m_world_list;
-}
-
-QString LegacyInstance::typeName() const
-{
- return tr("Legacy");
-}
-
-QString LegacyInstance::getStatusbarDescription()
-{
- return tr("Instance from previous versions.");
-}
-
-QStringList LegacyInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
-{
- QStringList out;
-
- auto alltraits = traits();
- if(alltraits.size())
- {
- out << "Traits:";
- for (auto trait : alltraits)
- {
- out << " " + trait;
- }
- out << "";
- }
-
- QString windowParams;
- if (settings()->get("LaunchMaximized").toBool())
- {
- out << "Window size: max (if available)";
- }
- else
- {
- auto width = settings()->get("MinecraftWinWidth").toInt();
- auto height = settings()->get("MinecraftWinHeight").toInt();
- out << "Window size: " + QString::number(width) + " x " + QString::number(height);
- }
- out << "";
- return out;
-}
diff --git a/launcher/minecraft/legacy/LegacyInstance.h b/launcher/minecraft/legacy/LegacyInstance.h
deleted file mode 100644
index 298543f7..00000000
--- a/launcher/minecraft/legacy/LegacyInstance.h
+++ /dev/null
@@ -1,142 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "BaseInstance.h"
-#include "launch/LaunchTask.h"
-
-class ModFolderModel;
-class LegacyModList;
-class WorldList;
-class Task;
-/*
- * WHY: Legacy instances - from MultiMC 3 and 4 - are here only to provide a way to upgrade them to the current format.
- */
-class LegacyInstance : public BaseInstance
-{
- Q_OBJECT
-public:
-
- explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
-
- virtual void saveNow() override {}
-
- /// Path to the instance's minecraft.jar
- QString runnableJar() const;
-
- //! Path to the instance's modlist file.
- QString modListFile() const;
-
- ////// Directories //////
- QString libDir() const;
- QString savesDir() const;
- QString texturePacksDir() const;
- QString jarModsDir() const;
- QString coreModsDir() const;
- QString resourceDir() const;
-
- QString instanceConfigFolder() const override;
-
- QString gameRoot() const override; // Path to the instance's minecraft directory.
- QString modsRoot() const override; // Path to the instance's minecraft directory.
- QString binRoot() const; // Path to the instance's minecraft bin directory.
-
- /// Get the curent base jar of this instance. By default, it's the
- /// versions/$version/$version.jar
- QString baseJar() const;
-
- /// the default base jar of this instance
- QString defaultBaseJar() const;
- /// the default custom base jar of this instance
- QString defaultCustomBaseJar() const;
-
- // the main jar that we actually want to keep when migrating the instance
- QString mainJarToPreserve() const;
-
- /*!
- * Whether or not custom base jar is used
- */
- bool shouldUseCustomBaseJar() const;
-
- /*!
- * The value of the custom base jar
- */
- QString customBaseJar() const;
-
- std::shared_ptr<LegacyModList> jarModList() const;
- std::shared_ptr<WorldList> worldList() const;
-
- /*!
- * Whether or not the instance's minecraft.jar needs to be rebuilt.
- * If this is true, when the instance launches, its jar mods will be
- * re-added to a fresh minecraft.jar file.
- */
- bool shouldRebuild() const;
-
- QString currentVersionId() const;
- QString intendedVersionId() const;
-
- QSet<QString> traits() const override
- {
- return {"legacy-instance", "texturepacks"};
- };
-
- virtual bool shouldUpdate() const;
- virtual Task::Ptr createUpdateTask(Net::Mode mode) override;
-
- virtual QString typeName() const override;
-
- bool canLaunch() const override
- {
- return false;
- }
- bool canEdit() const override
- {
- return true;
- }
- bool canExport() const override
- {
- return false;
- }
- shared_qobject_ptr<LaunchTask> createLaunchTask(
- AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override
- {
- return nullptr;
- }
- IPathMatcher::Ptr getLogFileMatcher() override
- {
- return nullptr;
- }
- QString getLogFileRoot() override
- {
- return gameRoot();
- }
-
- QString getStatusbarDescription() override;
- QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
-
- QProcessEnvironment createEnvironment() override
- {
- return QProcessEnvironment();
- }
- QMap<QString, QString> getVariables() const override
- {
- return {};
- }
-protected:
- mutable std::shared_ptr<LegacyModList> jar_mod_list;
- mutable std::shared_ptr<WorldList> m_world_list;
-};
diff --git a/launcher/minecraft/legacy/LegacyModList.cpp b/launcher/minecraft/legacy/LegacyModList.cpp
deleted file mode 100644
index e9948ab1..00000000
--- a/launcher/minecraft/legacy/LegacyModList.cpp
+++ /dev/null
@@ -1,136 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "LegacyModList.h"
-#include <FileSystem.h>
-#include <QString>
-#include <QDebug>
-
-LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
- : m_dir(dir), m_list_file(list_file)
-{
- FS::ensureFolderPathExists(m_dir.absolutePath());
- m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
- m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
-}
-
- struct OrderItem
- {
- QString id;
- bool enabled = false;
- };
- typedef QList<OrderItem> OrderList;
-
-static void internalSort(QList<LegacyModList::Mod> &what)
-{
- auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right)
- {
- return left.fileName().localeAwareCompare(right.fileName()) < 0;
- };
- std::sort(what.begin(), what.end(), predicate);
-}
-
-static OrderList readListFile(const QString &m_list_file)
-{
- OrderList itemList;
- if (m_list_file.isNull() || m_list_file.isEmpty())
- return itemList;
-
- QFile textFile(m_list_file);
- if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
- return OrderList();
-
- QTextStream textStream;
- textStream.setAutoDetectUnicode(true);
- textStream.setDevice(&textFile);
- while (true)
- {
- QString line = textStream.readLine();
- if (line.isNull() || line.isEmpty())
- break;
- else
- {
- OrderItem it;
- it.enabled = !line.endsWith(".disabled");
- if (!it.enabled)
- {
- line.chop(9);
- }
- it.id = line;
- itemList.append(it);
- }
- }
- textFile.close();
- return itemList;
-}
-
-bool LegacyModList::update()
-{
- if (!m_dir.exists() || !m_dir.isReadable())
- return false;
-
- QList<Mod> orderedMods;
- QList<Mod> newMods;
- m_dir.refresh();
- auto folderContents = m_dir.entryInfoList();
-
- // first, process the ordered items (if any)
- OrderList listOrder = readListFile(m_list_file);
- for (auto item : listOrder)
- {
- QFileInfo infoEnabled(m_dir.filePath(item.id));
- QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
- int idxEnabled = folderContents.indexOf(infoEnabled);
- int idxDisabled = folderContents.indexOf(infoDisabled);
- bool isEnabled;
- // if both enabled and disabled versions are present, it's a special case...
- if (idxEnabled >= 0 && idxDisabled >= 0)
- {
- // we only process the one we actually have in the order file.
- // and exactly as we have it.
- // THIS IS A CORNER CASE
- isEnabled = item.enabled;
- }
- else
- {
- // only one is present.
- // we pick the one that we found.
- // we assume the mod was enabled/disabled by external means
- isEnabled = idxEnabled >= 0;
- }
- int idx = isEnabled ? idxEnabled : idxDisabled;
- QFileInfo &info = isEnabled ? infoEnabled : infoDisabled;
- // if the file from the index file exists
- if (idx != -1)
- {
- // remove from the actual folder contents list
- folderContents.takeAt(idx);
- // append the new mod
- orderedMods.append(info);
- }
- }
- // if there are any untracked files... append them sorted at the end
- if (folderContents.size())
- {
- for (auto entry : folderContents)
- {
- newMods.append(entry);
- }
- internalSort(newMods);
- orderedMods.append(newMods);
- }
- mods.swap(orderedMods);
- return true;
-}
diff --git a/launcher/minecraft/legacy/LegacyModList.h b/launcher/minecraft/legacy/LegacyModList.h
deleted file mode 100644
index fade736e..00000000
--- a/launcher/minecraft/legacy/LegacyModList.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QList>
-#include <QString>
-#include <QDir>
-
-class LegacyModList
-{
-public:
-
- using Mod = QFileInfo;
-
- LegacyModList(const QString &dir, const QString &list_file = QString());
-
- /// Reloads the mod list and returns true if the list changed.
- bool update();
-
- QDir dir()
- {
- return m_dir;
- }
-
- const QList<Mod> & allMods()
- {
- return mods;
- }
-
-protected:
- QDir m_dir;
- QString m_list_file;
- QList<Mod> mods;
-};
diff --git a/launcher/minecraft/legacy/LegacyUpgradeTask.cpp b/launcher/minecraft/legacy/LegacyUpgradeTask.cpp
deleted file mode 100644
index a4ea60cd..00000000
--- a/launcher/minecraft/legacy/LegacyUpgradeTask.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-#include "LegacyUpgradeTask.h"
-#include "settings/INISettingsObject.h"
-#include "FileSystem.h"
-#include "NullInstance.h"
-#include "pathmatcher/RegexpMatcher.h"
-#include <QtConcurrentRun>
-#include "LegacyInstance.h"
-#include "minecraft/MinecraftInstance.h"
-#include "minecraft/PackProfile.h"
-#include "LegacyModList.h"
-#include "classparser.h"
-
-LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance)
-{
- m_origInstance = origInstance;
-}
-
-void LegacyUpgradeTask::executeTask()
-{
- setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
-
- FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
- folderCopy.followSymlinks(true);
-
- m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
- connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &LegacyUpgradeTask::copyFinished);
- connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &LegacyUpgradeTask::copyAborted);
- m_copyFutureWatcher.setFuture(m_copyFuture);
-}
-
-static QString decideVersion(const QString& currentVersion, const QString& intendedVersion)
-{
- if(intendedVersion != currentVersion)
- {
- if(!intendedVersion.isEmpty())
- {
- return intendedVersion;
- }
- else if(!currentVersion.isEmpty())
- {
- return currentVersion;
- }
- }
- else
- {
- if(!intendedVersion.isEmpty())
- {
- return intendedVersion;
- }
- }
- return QString();
-}
-
-void LegacyUpgradeTask::copyFinished()
-{
- auto successful = m_copyFuture.result();
- if(!successful)
- {
- emitFailed(tr("Instance folder copy failed."));
- return;
- }
- auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance);
-
- auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
- instanceSettings->registerSetting("InstanceType", "Legacy");
- instanceSettings->set("InstanceType", "OneSix");
- // NOTE: this scope ensures the instance is fully saved before we emitSucceeded
- {
- MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
- inst.setName(m_instName);
-
- QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
- if(preferredVersionNumber.isNull())
- {
- // try to decide version based on the jar(s?)
- preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
- if(preferredVersionNumber.isNull())
- {
- preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
- if(preferredVersionNumber.isNull())
- {
- emitFailed(tr("Could not decide Minecraft version."));
- return;
- }
- }
- }
- auto components = inst.getPackProfile();
- components->buildingFromScratch();
- components->setComponentVersion("net.minecraft", preferredVersionNumber, true);
-
- QString jarPath = legacyInst->mainJarToPreserve();
- if(!jarPath.isNull())
- {
- qDebug() << "Preserving base jar! : " << jarPath;
- // FIXME: handle case when the jar is unreadable?
- // TODO: check the hash, if it's the same as the upstream jar, do not do this
- components->installCustomJar(jarPath);
- }
-
- auto jarMods = legacyInst->jarModList()->allMods();
- for(auto & jarMod: jarMods)
- {
- QString modPath = jarMod.absoluteFilePath();
- qDebug() << "jarMod: " << modPath;
- components->installJarMods({modPath});
- }
-
- // remove all the extra garbage we no longer need
- auto removeAll = [&](const QString &root, const QStringList &things)
- {
- for(auto &thing : things)
- {
- auto removePath = FS::PathCombine(root, thing);
- QFileInfo stat(removePath);
- if(stat.isDir())
- {
- FS::deletePath(removePath);
- }
- else
- {
- QFile::remove(removePath);
- }
- }
- };
- QStringList rootRemovables = {"modlist", "version", "instMods"};
- QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
- removeAll(inst.instanceRoot(), rootRemovables);
- removeAll(inst.gameRoot(), mcRemovables);
- }
- emitSucceeded();
-}
-
-void LegacyUpgradeTask::copyAborted()
-{
- emitFailed(tr("Instance folder copy has been aborted."));
- return;
-}
-
diff --git a/launcher/minecraft/legacy/LegacyUpgradeTask.h b/launcher/minecraft/legacy/LegacyUpgradeTask.h
deleted file mode 100644
index 542e17b8..00000000
--- a/launcher/minecraft/legacy/LegacyUpgradeTask.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include "InstanceTask.h"
-#include "net/NetJob.h"
-#include <QUrl>
-#include <QFuture>
-#include <QFutureWatcher>
-#include "settings/SettingsObject.h"
-#include "BaseVersion.h"
-#include "BaseInstance.h"
-
-
-class LegacyUpgradeTask : public InstanceTask
-{
- Q_OBJECT
-public:
- explicit LegacyUpgradeTask(InstancePtr origInstance);
-
-protected:
- //! Entry point for tasks.
- virtual void executeTask() override;
- void copyFinished();
- void copyAborted();
-
-private: /* data */
- InstancePtr m_origInstance;
- QFuture<bool> m_copyFuture;
- QFutureWatcher<bool> m_copyFutureWatcher;
-};
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index 8de5fc9f..9dcb3504 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -720,8 +720,6 @@ void PackInstallTask::install()
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
instanceSettings->suspendSave();
- instanceSettings->registerSetting("InstanceType", "Legacy");
- instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto components = instance.getPackProfile();
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index a8b2495a..4adaf5f1 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -50,42 +50,43 @@ void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray
for(auto versionIter: arr) {
auto obj = versionIter.toObject();
- FlameMod::IndexedVersion file;
- file.addonId = pack.addonId;
- file.fileId = Json::requireInteger(obj, "id");
- file.date = Json::requireString(obj, "fileDate");
+
auto versionArray = Json::requireArray(obj, "gameVersion");
- if (versionArray.empty()) {
+ if (versionArray.isEmpty()) {
continue;
}
+
+ FlameMod::IndexedVersion file;
for(auto mcVer : versionArray){
file.mcVersion.append(mcVer.toString());
}
+ file.addonId = pack.addonId;
+ file.fileId = Json::requireInteger(obj, "id");
+ file.date = Json::requireString(obj, "fileDate");
file.version = Json::requireString(obj, "displayName");
file.downloadUrl = Json::requireString(obj, "downloadUrl");
file.fileName = Json::requireString(obj, "fileName");
auto modules = Json::requireArray(obj, "modules");
- bool valid = false;
+ bool is_valid_fabric_version = false;
for(auto m : modules){
auto fname = Json::requireString(m.toObject(),"foldername");
+ // FIXME: This does not work properly when a mod supports more than one mod loader, since
+ // they bundle the meta files for all of them in the same arquive, even when that version
+ // doesn't support the given mod loader.
if(hasFabric){
if(fname == "fabric.mod.json"){
- valid = true;
- break;
- }
- }else{
- //this cannot check for the recent mcmod.toml formats
- if(fname == "mcmod.info"){
- valid = true;
+ is_valid_fabric_version = true;
break;
}
}
+ else break;
+ // NOTE: Since we're not validating forge versions, we can just skip this loop.
}
- if(!valid && hasFabric){
+
+ if(hasFabric && !is_valid_fabric_version)
continue;
- }
unsortedVersions.append(file);
}
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
index 1d300192..f655a066 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -122,8 +122,6 @@ void PackInstallTask::install()
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
instanceSettings->suspendSave();
- instanceSettings->registerSetting("InstanceType", "Legacy");
- instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto components = instance.getPackProfile();
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
index 03570226..33df6fa4 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -181,8 +181,6 @@ void PackInstallTask::install()
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
instanceSettings->suspendSave();
- instanceSettings->registerSetting("InstanceType", "Legacy");
- instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto components = instance.getPackProfile();
diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp
index c45061ac..156a295a 100644
--- a/launcher/modplatform/technic/TechnicPackProcessor.cpp
+++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp
@@ -31,8 +31,6 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft");
QString configPath = FS::PathCombine(stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
- instanceSettings->registerSetting("InstanceType", "Legacy");
- instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(globalSettings, instanceSettings, stagingPath);
instance.setName(instName);
diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc
index ef29cf9b..d31885b9 100644
--- a/launcher/resources/multimc/multimc.qrc
+++ b/launcher/resources/multimc/multimc.qrc
@@ -246,9 +246,13 @@
<!-- placeholder when loading screenshot images -->
<file>scalable/screenshot-placeholder.svg</file>
+ <!-- matrix logo -->
+ <file>scalable/matrix.svg</file>
+
<!-- discord logo icon thing. from discord. traced from bitmap -->
<file>scalable/discord.svg</file>
+
<!-- instance icons -->
<file>32x32/instances/chicken.png</file>
<file>128x128/instances/chicken.png</file>
diff --git a/launcher/resources/multimc/scalable/matrix.svg b/launcher/resources/multimc/scalable/matrix.svg
new file mode 100644
index 00000000..237c55a2
--- /dev/null
+++ b/launcher/resources/multimc/scalable/matrix.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64">
+ <g fill="#fff">
+ <path d="M49.46 42.2h-5.32c-.178 0-.323-.145-.323-.323V30.91l-.074-1.808c-.047-.53-.173-.992-.376-1.376-.194-.367-.487-.664-.868-.883s-.93-.332-1.62-.332-1.238.13-1.647.382-.743.597-.976 1.01a4.21 4.21 0 0 0-.486 1.462c-.085.567-.128 1.15-.128 1.732v10.79c0 .178-.145.323-.323.323H32c-.178 0-.323-.145-.323-.323V31.02l-.037-1.69c-.024-.524-.124-1.013-.297-1.45-.164-.415-.43-.74-.814-.992s-.972-.378-1.752-.378c-.22 0-.527.053-.908.157-.368.1-.732.294-1.08.577s-.65.694-.904 1.235-.382 1.27-.382 2.167v11.24c0 .178-.144.323-.323.323h-5.32c-.178 0-.323-.145-.323-.323V22.515c0-.178.145-.322.323-.322h5.02c.178 0 .323.145.323.322V24.3c.618-.726 1.33-1.315 2.125-1.757 1.032-.574 2.225-.865 3.548-.865 1.265 0 2.44.25 3.5.743.934.44 1.68 1.17 2.224 2.18.556-.703 1.263-1.34 2.108-1.895 1.036-.682 2.274-1.028 3.68-1.028 1.048 0 2.036.13 2.937.387.917.263 1.715.69 2.373 1.267s1.18 1.348 1.548 2.278c.363.922.547 2.04.547 3.323v12.964c0 .178-.145.323-.323.323z" opacity=".5" />
+ <path d="M24.88 22.515v2.623h.075c.7-.998 1.542-1.774 2.53-2.323s2.117-.824 3.39-.824c1.224 0 2.342.238 3.353.712s1.78 1.31 2.305 2.51c.574-.85 1.355-1.6 2.342-2.248s2.154-.974 3.504-.974c1.024 0 1.973.125 2.848.375s1.623.65 2.248 1.2 1.11 1.268 1.462 2.154.525 1.955.525 3.204v12.964h-5.32V30.91l-.075-1.836c-.05-.574-.187-1.073-.412-1.5s-.556-.762-.993-1.012-1.03-.374-1.78-.374-1.355.145-1.817.43-.824.663-1.087 1.124-.437.987-.524 1.574a12 12 0 0 0-.131 1.78v10.79H32V31.022l-.037-1.705c-.025-.562-.13-1.08-.32-1.556s-.5-.855-.937-1.143-1.08-.43-1.93-.43c-.25 0-.58.056-.993.17a3.3 3.3 0 0 0-1.199.637c-.388.313-.718.762-.993 1.35s-.412 1.355-.412 2.304v11.24h-5.32V22.515z" opacity=".5" />
+ </g>
+ <path d="M1.432 6.084v51.833h3.73v1.244H0V4.84h5.162v1.243zm20.788 16.43v2.623h.075c.7-.998 1.542-1.774 2.53-2.323s2.117-.824 3.4-.824c1.224 0 2.342.238 3.353.712s1.78 1.3 2.305 2.5c.574-.85 1.355-1.6 2.342-2.248s2.154-.974 3.504-.974c1.024 0 1.973.125 2.848.375s1.623.65 2.248 1.2 1.1 1.268 1.462 2.154.525 1.955.525 3.204v12.964h-5.32V30.9l-.075-1.836c-.05-.574-.187-1.073-.412-1.5s-.556-.762-.993-1.012-1.03-.374-1.78-.374-1.355.145-1.817.43a3.12 3.12 0 0 0-1.087 1.124c-.263.46-.437.987-.524 1.574a12 12 0 0 0-.131 1.78v10.8h-5.32V31.022l-.037-1.705c-.025-.562-.13-1.08-.32-1.556s-.5-.855-.937-1.143-1.08-.43-1.93-.43c-.25 0-.58.056-.993.17a3.3 3.3 0 0 0-1.199.637c-.388.313-.718.762-.993 1.35s-.412 1.355-.412 2.304v11.24H17.2V22.515zm40.348 35.402V6.084h-3.73V4.84H64v54.32h-5.162v-1.244z" />
+</svg> \ No newline at end of file
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index a3da64e4..7b758e05 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -233,6 +233,7 @@ public:
TranslatedToolButton helpMenuButton;
TranslatedAction actionReportBug;
TranslatedAction actionDISCORD;
+ TranslatedAction actionMATRIX;
TranslatedAction actionREDDIT;
TranslatedAction actionAbout;
@@ -341,13 +342,23 @@ public:
all_actions.append(&actionReportBug);
helpMenu->addAction(actionReportBug);
}
+
+ if(!BuildConfig.MATRIX_URL.isEmpty()) {
+ actionMATRIX = TranslatedAction(MainWindow);
+ actionMATRIX->setObjectName(QStringLiteral("actionMATRIX"));
+ actionMATRIX->setIcon(APPLICATION->getThemedIcon("matrix"));
+ actionMATRIX.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Matrix"));
+ actionMATRIX.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open %1 Matrix space"));
+ all_actions.append(&actionMATRIX);
+ helpMenu->addAction(actionMATRIX);
+ }
if (!BuildConfig.DISCORD_URL.isEmpty()) {
actionDISCORD = TranslatedAction(MainWindow);
actionDISCORD->setObjectName(QStringLiteral("actionDISCORD"));
actionDISCORD->setIcon(APPLICATION->getThemedIcon("discord"));
actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord"));
- actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open %1 discord voice chat."));
+ actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open %1 Discord guild."));
all_actions.append(&actionDISCORD);
helpMenu->addAction(actionDISCORD);
}
@@ -1469,6 +1480,11 @@ void MainWindow::on_actionDISCORD_triggered()
DesktopServices::openUrl(QUrl(BuildConfig.DISCORD_URL));
}
+void MainWindow::on_actionMATRIX_triggered()
+{
+ DesktopServices::openUrl(QUrl(BuildConfig.MATRIX_URL));
+}
+
void MainWindow::on_actionChangeInstIcon_triggered()
{
if (!m_selectedInstance)
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index fd65620e..f2852d78 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -72,6 +72,8 @@ private slots:
void on_actionREDDIT_triggered();
+ void on_actionMATRIX_triggered();
+
void on_actionDISCORD_triggered();
void on_actionCopyInstance_triggered();
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
index 6b807b8c..a53f93e8 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -5,6 +5,7 @@
#include <InstanceList.h>
#include "ProgressDialog.h"
+#include "ReviewMessageBox.h"
#include <QLayout>
#include <QPushButton>
@@ -39,9 +40,10 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods
// Bonk Qt over its stupid head and make sure it understands which button is the default one...
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button
auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
+ OkButton->setEnabled(false);
OkButton->setDefault(true);
OkButton->setAutoDefault(true);
- connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::accept);
+ connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm);
auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel);
CancelButton->setDefault(false);
@@ -52,6 +54,7 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods
HelpButton->setDefault(false);
HelpButton->setAutoDefault(false);
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
+
QMetaObject::connectSlotsByName(this);
setWindowModality(Qt::WindowModal);
setWindowTitle("Download mods");
@@ -67,6 +70,25 @@ void ModDownloadDialog::reject()
QDialog::reject();
}
+void ModDownloadDialog::confirm()
+{
+ auto keys = modTask.keys();
+ keys.sort(Qt::CaseInsensitive);
+
+ auto confirm_dialog = ReviewMessageBox::create(
+ this,
+ tr("Confirm mods to download")
+ );
+
+ for(auto task : keys){
+ confirm_dialog->appendMod(task, modTask.find(task).value()->getFilename());
+ }
+
+ connect(confirm_dialog, &QDialog::accepted, this, &ModDownloadDialog::accept);
+
+ confirm_dialog->open();
+}
+
void ModDownloadDialog::accept()
{
QDialog::accept();
@@ -83,16 +105,35 @@ QList<BasePage *> ModDownloadDialog::getPages()
};
}
-void ModDownloadDialog::setSuggestedMod(const QString& name, ModDownloadTask* task)
+void ModDownloadDialog::addSelectedMod(const QString& name, ModDownloadTask* task)
+{
+ removeSelectedMod(name);
+ modTask.insert(name, task);
+
+ m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty());
+}
+
+void ModDownloadDialog::removeSelectedMod(const QString &name)
+{
+ if(modTask.contains(name))
+ delete modTask.find(name).value();
+ modTask.remove(name);
+
+ m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty());
+}
+
+bool ModDownloadDialog::isModSelected(const QString &name, const QString& filename) const
{
- modTask.reset(task);
- m_buttons->button(QDialogButtonBox::Ok)->setEnabled(task);
+ // FIXME: Is there a way to check for versions without checking the filename
+ // as a heuristic, other than adding such info to ModDownloadTask itself?
+ auto iter = modTask.find(name);
+ return iter != modTask.end() && (iter.value()->getFilename() == filename);
}
ModDownloadDialog::~ModDownloadDialog()
{
}
-ModDownloadTask *ModDownloadDialog::getTask() {
- return modTask.release();
+const QList<ModDownloadTask*> ModDownloadDialog::getTasks() {
+ return modTask.values();
}
diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h
index ece8e328..309d89d0 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.h
+++ b/launcher/ui/dialogs/ModDownloadDialog.h
@@ -29,12 +29,15 @@ public:
QString dialogTitle() override;
QList<BasePage *> getPages() override;
- void setSuggestedMod(const QString & name = QString(), ModDownloadTask * task = nullptr);
+ void addSelectedMod(const QString & name = QString(), ModDownloadTask * task = nullptr);
+ void removeSelectedMod(const QString & name = QString());
+ bool isModSelected(const QString & name, const QString & filename) const;
- ModDownloadTask * getTask();
+ const QList<ModDownloadTask*> getTasks();
const std::shared_ptr<ModFolderModel> &mods;
public slots:
+ void confirm();
void accept() override;
void reject() override;
@@ -49,6 +52,6 @@ private:
ModrinthPage *modrinthPage = nullptr;
FlameModPage *flameModPage = nullptr;
- std::unique_ptr<ModDownloadTask> modTask;
+ QHash<QString, ModDownloadTask*> modTask;
BaseInstance *m_instance;
};
diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp
new file mode 100644
index 00000000..2bfd02e0
--- /dev/null
+++ b/launcher/ui/dialogs/ReviewMessageBox.cpp
@@ -0,0 +1,31 @@
+#include "ReviewMessageBox.h"
+#include "ui_ReviewMessageBox.h"
+
+ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QString const& icon)
+ : QDialog(parent), ui(new Ui::ReviewMessageBox)
+{
+ ui->setupUi(this);
+}
+
+ReviewMessageBox::~ReviewMessageBox()
+{
+ delete ui;
+}
+
+auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon) -> ReviewMessageBox*
+{
+ return new ReviewMessageBox(parent, title, icon);
+}
+
+void ReviewMessageBox::appendMod(const QString& name, const QString& filename)
+{
+ auto itemTop = new QTreeWidgetItem(ui->modTreeWidget);
+ itemTop->setText(0, name);
+
+ auto filenameItem = new QTreeWidgetItem(itemTop);
+ filenameItem->setText(0, tr("Filename: %1").arg(filename));
+
+ itemTop->insertChildren(0, { filenameItem });
+
+ ui->modTreeWidget->addTopLevelItem(itemTop);
+}
diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h
new file mode 100644
index 00000000..48742cd9
--- /dev/null
+++ b/launcher/ui/dialogs/ReviewMessageBox.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <QDialog>
+
+namespace Ui {
+class ReviewMessageBox;
+}
+
+class ReviewMessageBox final : public QDialog {
+ Q_OBJECT
+
+ public:
+ static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*;
+
+ void appendMod(const QString& name, const QString& filename);
+
+ ~ReviewMessageBox();
+
+ private:
+ ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon);
+
+ Ui::ReviewMessageBox* ui;
+};
diff --git a/launcher/ui/dialogs/ReviewMessageBox.ui b/launcher/ui/dialogs/ReviewMessageBox.ui
new file mode 100644
index 00000000..d04f3b3f
--- /dev/null
+++ b/launcher/ui/dialogs/ReviewMessageBox.ui
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ReviewMessageBox</class>
+ <widget class="QDialog" name="ReviewMessageBox">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Confirm mod selection</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>You're about to download the following mods:</string>
+ </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="QTreeWidget" name="modTreeWidget">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectItems</enum>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string/>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ReviewMessageBox</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>200</x>
+ <y>265</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>149</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ReviewMessageBox</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>200</x>
+ <y>265</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>149</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 9e1d761b..ee68cd08 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -242,16 +242,16 @@ void LauncherPage::applySettings()
switch (ui->themeComboBox->currentIndex())
{
case 0:
- s->set("IconTheme", "pe_dark");
+ s->set("IconTheme", "pe_colored");
break;
case 1:
s->set("IconTheme", "pe_light");
break;
case 2:
- s->set("IconTheme", "pe_blue");
+ s->set("IconTheme", "pe_dark");
break;
case 3:
- s->set("IconTheme", "pe_colored");
+ s->set("IconTheme", "pe_blue");
break;
case 4:
s->set("IconTheme", "OSX");
@@ -263,10 +263,10 @@ void LauncherPage::applySettings()
s->set("IconTheme", "flat");
break;
case 7:
- s->set("IconTheme", "custom");
+ s->set("IconTheme", "multimc");
break;
case 8:
- s->set("IconTheme", "multimc");
+ s->set("IconTheme", "custom");
break;
}
@@ -319,7 +319,7 @@ void LauncherPage::loadSettings()
m_currentUpdateChannel = s->get("UpdateChannel").toString();
//FIXME: make generic
auto theme = s->get("IconTheme").toString();
- if (theme == "pe_dark")
+ if (theme == "pe_colored")
{
ui->themeComboBox->setCurrentIndex(0);
}
@@ -327,11 +327,11 @@ void LauncherPage::loadSettings()
{
ui->themeComboBox->setCurrentIndex(1);
}
- else if (theme == "pe_blue")
+ else if (theme == "pe_dark")
{
ui->themeComboBox->setCurrentIndex(2);
}
- else if (theme == "pe_colored")
+ else if (theme == "pe_blue")
{
ui->themeComboBox->setCurrentIndex(3);
}
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 6f68d0dd..c110dd09 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -245,7 +245,7 @@
</property>
<item>
<property name="text">
- <string>Simple (Dark Icons)</string>
+ <string>Simple (Colored Icons)</string>
</property>
</item>
<item>
@@ -255,12 +255,12 @@
</item>
<item>
<property name="text">
- <string>Simple (Blue Icons)</string>
+ <string>Simple (Dark Icons)</string>
</property>
</item>
<item>
<property name="text">
- <string>Simple (Colored Icons)</string>
+ <string>Simple (Blue Icons)</string>
</property>
</item>
<item>
@@ -275,17 +275,17 @@
</item>
<item>
<property name="text">
- <string notr="true">Flat</string>
+ <string>Flat</string>
</property>
</item>
<item>
<property name="text">
- <string>Custom</string>
+ <string>Legacy</string>
</property>
</item>
<item>
<property name="text">
- <string>MultiMC</string>
+ <string>Custom</string>
</property>
</item>
</widget>
diff --git a/launcher/ui/pages/instance/LegacyUpgradePage.cpp b/launcher/ui/pages/instance/LegacyUpgradePage.cpp
deleted file mode 100644
index cb78af02..00000000
--- a/launcher/ui/pages/instance/LegacyUpgradePage.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-#include "LegacyUpgradePage.h"
-#include "ui_LegacyUpgradePage.h"
-
-#include "InstanceList.h"
-#include "minecraft/legacy/LegacyInstance.h"
-#include "minecraft/legacy/LegacyUpgradeTask.h"
-#include "Application.h"
-
-#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ProgressDialog.h"
-
-LegacyUpgradePage::LegacyUpgradePage(InstancePtr inst, QWidget *parent)
- : QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst)
-{
- ui->setupUi(this);
-}
-
-LegacyUpgradePage::~LegacyUpgradePage()
-{
- delete ui;
-}
-
-void LegacyUpgradePage::runModalTask(Task *task)
-{
- connect(task, &Task::failed, [this](QString reason)
- {
- CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show();
- });
- ProgressDialog loadDialog(this);
- loadDialog.setSkipButton(true, tr("Abort"));
- if(loadDialog.execWithTask(task) == QDialog::Accepted)
- {
- m_container->requestClose();
- }
-}
-
-void LegacyUpgradePage::on_upgradeButton_clicked()
-{
- QString newName = tr("%1 (Migrated)").arg(m_inst->name());
- auto upgradeTask = new LegacyUpgradeTask(m_inst);
- upgradeTask->setName(newName);
- upgradeTask->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id()));
- upgradeTask->setIcon(m_inst->iconKey());
- unique_qobject_ptr<Task> task(APPLICATION->instances()->wrapInstanceTask(upgradeTask));
- runModalTask(task.get());
-}
-
-bool LegacyUpgradePage::shouldDisplay() const
-{
- return !m_inst->isRunning();
-}
diff --git a/launcher/ui/pages/instance/LegacyUpgradePage.h b/launcher/ui/pages/instance/LegacyUpgradePage.h
deleted file mode 100644
index 7c51956b..00000000
--- a/launcher/ui/pages/instance/LegacyUpgradePage.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QWidget>
-
-#include "minecraft/legacy/LegacyInstance.h"
-#include "ui/pages/BasePage.h"
-#include <Application.h>
-#include "tasks/Task.h"
-
-namespace Ui
-{
-class LegacyUpgradePage;
-}
-
-class LegacyUpgradePage : public QWidget, public BasePage
-{
- Q_OBJECT
-
-public:
- explicit LegacyUpgradePage(InstancePtr inst, QWidget *parent = 0);
- virtual ~LegacyUpgradePage();
- virtual QString displayName() const override
- {
- return tr("Upgrade");
- }
- virtual QIcon icon() const override
- {
- return APPLICATION->getThemedIcon("checkupdate");
- }
- virtual QString id() const override
- {
- return "upgrade";
- }
- virtual QString helpPage() const override
- {
- return "Legacy-upgrade";
- }
- virtual bool shouldDisplay() const override;
-
-private slots:
- void on_upgradeButton_clicked();
-
-private:
- void runModalTask(Task *task);
-
-private:
- Ui::LegacyUpgradePage *ui;
- InstancePtr m_inst;
-};
diff --git a/launcher/ui/pages/instance/LegacyUpgradePage.ui b/launcher/ui/pages/instance/LegacyUpgradePage.ui
deleted file mode 100644
index b22c03e5..00000000
--- a/launcher/ui/pages/instance/LegacyUpgradePage.ui
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>LegacyUpgradePage</class>
- <widget class="QWidget" name="LegacyUpgradePage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>546</width>
- <height>405</height>
- </rect>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTextBrowser" name="textBrowser">
- <property name="html">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:600;&quot;&gt;Upgrade is required&lt;/span&gt;&lt;/h1&gt;
-&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;PolyMC now supports old Minecraft versions and all the required features in the new (OneSix) instance format. As a consequence, the old (Legacy) format has been entirely disabled and old instances need to be upgraded.&lt;/p&gt;
-&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The upgrade will create a new instance with the same contents as the current one, in the new format. The original instance will remain untouched, in case anything goes wrong in the process.&lt;/p&gt;
-&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please report any issues on our &lt;a href=&quot;https://github.com/PolyMC/PolyMC/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#3584e4;&quot;&gt;github issues page&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCommandLinkButton" name="upgradeButton">
- <property name="text">
- <string>Upgrade the instance</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 494d32f0..ffb87bbe 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -143,11 +143,19 @@ ModFolderPage::ModFolderPage(
ui(new Ui::ModFolderPage)
{
ui->setupUi(this);
+
+ // This is structured like that so that these changes
+ // do not affect the Resouce pack and Shader pack tabs
if(id == "mods") {
- auto act = new QAction(tr("Install Mods"), this);
- ui->actionsToolbar->insertActionBefore(ui->actionView_configs,act);
+ auto act = new QAction(tr("Download mods"), this);
+ act->setToolTip(tr("Download mods from online mod platforms"));
+ ui->actionsToolbar->insertActionBefore(ui->actionAdd, act);
connect(act, &QAction::triggered, this, &ModFolderPage::on_actionInstall_mods_triggered);
+
+ ui->actionAdd->setText(tr("Add .jar"));
+ ui->actionAdd->setToolTip(tr("Add mods via local file"));
}
+
ui->actionsToolbar->insertSpacer(ui->actionView_configs);
m_inst = inst;
@@ -365,8 +373,7 @@ void ModFolderPage::on_actionInstall_mods_triggered()
}
ModDownloadDialog mdownload(m_mods, this, m_inst);
if(mdownload.exec()) {
- ModDownloadTask *task = mdownload.getTask();
- if (task) {
+ for(auto task : mdownload.getTasks()){
connect(task, &Task::failed, [this, task](QString reason) {
task->deleteLater();
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
diff --git a/launcher/ui/pages/instance/ModFolderPage.ui b/launcher/ui/pages/instance/ModFolderPage.ui
index 0fb51e84..ab59b0df 100644
--- a/launcher/ui/pages/instance/ModFolderPage.ui
+++ b/launcher/ui/pages/instance/ModFolderPage.ui
@@ -96,7 +96,7 @@
<string>&amp;Add</string>
</property>
<property name="toolTip">
- <string>Add mods</string>
+ <string>Add</string>
</property>
</action>
<action name="actionRemove">
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
index 2cf83261..e8afba5a 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
@@ -175,13 +175,13 @@ void ListModel::performPaginatedSearch()
"pageSize=25&"
"searchFilter=%2&"
"sort=%3&"
- "%4"
+ "modLoaderType=%4&"
"gameVersion=%5"
)
.arg(nextSearchOffset)
.arg(currentSearchTerm)
.arg(sorts[currentSort])
- .arg(hasFabric ? "modLoaderType=4&" : "")
+ .arg(hasFabric ? 4 : 1) // Enum: https://docs.curseforge.com/?http#tocS_ModLoaderType
.arg(mcVersion);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
index a816c681..114ac907 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
@@ -4,193 +4,214 @@
#include <QKeyEvent>
#include "Application.h"
-#include "Json.h"
-#include "ui/dialogs/ModDownloadDialog.h"
-#include "InstanceImportTask.h"
#include "FlameModModel.h"
+#include "InstanceImportTask.h"
+#include "Json.h"
#include "ModDownloadTask.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
+#include "ui/dialogs/ModDownloadDialog.h"
FlameModPage::FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance)
- : QWidget(dialog), m_instance(instance), ui(new Ui::FlameModPage), dialog(dialog)
-{
- ui->setupUi(this);
- connect(ui->searchButton, &QPushButton::clicked, this, &FlameModPage::triggerSearch);
- ui->searchEdit->installEventFilter(this);
- listModel = new FlameMod::ListModel(this);
- ui->packView->setModel(listModel);
-
- ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
-
- // index is used to set the sorting with the flame api
- ui->sortByBox->addItem(tr("Sort by Featured"));
- ui->sortByBox->addItem(tr("Sort by Popularity"));
- ui->sortByBox->addItem(tr("Sort by last updated"));
- ui->sortByBox->addItem(tr("Sort by Name"));
- ui->sortByBox->addItem(tr("Sort by Author"));
- ui->sortByBox->addItem(tr("Sort by Downloads"));
-
- connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged);
+ : QWidget(dialog), m_instance(instance), ui(new Ui::FlameModPage),
+ dialog(dialog) {
+ ui->setupUi(this);
+ connect(ui->searchButton, &QPushButton::clicked, this,
+ &FlameModPage::triggerSearch);
+ ui->searchEdit->installEventFilter(this);
+ listModel = new FlameMod::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(
+ Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ // index is used to set the sorting with the flame api
+ ui->sortByBox->addItem(tr("Sort by Featured"));
+ ui->sortByBox->addItem(tr("Sort by Popularity"));
+ ui->sortByBox->addItem(tr("Sort by last updated"));
+ ui->sortByBox->addItem(tr("Sort by Name"));
+ ui->sortByBox->addItem(tr("Sort by Author"));
+ ui->sortByBox->addItem(tr("Sort by Downloads"));
+
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged,
+ this, &FlameModPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this,
+ &FlameModPage::onVersionSelectionChanged);
+ connect(ui->modSelectionButton, &QPushButton::clicked, this,
+ &FlameModPage::onModSelected);
}
-FlameModPage::~FlameModPage()
-{
- delete ui;
-}
+FlameModPage::~FlameModPage() { delete ui; }
-bool FlameModPage::eventFilter(QObject* watched, QEvent* event)
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- }
+bool FlameModPage::eventFilter(QObject *watched, QEvent *event) {
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
}
- return QWidget::eventFilter(watched, event);
+ }
+ return QWidget::eventFilter(watched, event);
}
-bool FlameModPage::shouldDisplay() const
-{
- return true;
-}
+bool FlameModPage::shouldDisplay() const { return true; }
-void FlameModPage::openedImpl()
-{
- suggestCurrent();
- triggerSearch();
+void FlameModPage::openedImpl() {
+ updateSelectionButton();
+ triggerSearch();
}
-void FlameModPage::triggerSearch()
-{
- listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+void FlameModPage::triggerSearch() {
+ listModel->searchWithTerm(ui->searchEdit->text(),
+ ui->sortByBox->currentIndex());
}
-void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
-{
- ui->versionSelectionBox->clear();
-
- if(!first.isValid())
- {
- if(isOpened)
- {
- dialog->setSuggestedMod();
+void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) {
+ ui->versionSelectionBox->clear();
+
+ if (!first.isValid()) {
+ return;
+ }
+
+ current = listModel->data(first, Qt::UserRole).value<FlameMod::IndexedPack>();
+ QString text = "";
+ QString name = current.name;
+
+ if (current.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
+ if (!current.authors.empty()) {
+ auto authorToStr = [](FlameMod::ModpackAuthor &author) {
+ if (author.url.isEmpty()) {
+ return author.name;
+ }
+ return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
+ };
+ QStringList authorStrs;
+ for (auto &author : current.authors) {
+ authorStrs.push_back(authorToStr(author));
+ }
+ text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ }
+ text += "<br><br>";
+
+ ui->packDescription->setHtml(text + current.description);
+
+ if (!current.versionsLoaded) {
+ qDebug() << "Loading flame mod versions";
+
+ ui->modSelectionButton->setText(tr("Loading versions..."));
+ ui->modSelectionButton->setEnabled(false);
+
+ auto netJob =
+ new NetJob(QString("Flame::ModVersions(%1)").arg(current.name),
+ APPLICATION->network());
+ auto response = new QByteArray();
+ int addonId = current.addonId;
+ netJob->addNetAction(Net::Download::makeByteArray(
+ QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files")
+ .arg(addonId),
+ response));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] {
+ if(addonId != current.addonId){
+ return; //wrong request
}
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Flame at "
+ << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
return;
- }
-
- current = listModel->data(first, Qt::UserRole).value<FlameMod::IndexedPack>();
- QString text = "";
- QString name = current.name;
-
- if (current.websiteUrl.isEmpty())
- text = name;
- else
- text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
- if (!current.authors.empty()) {
- auto authorToStr = [](FlameMod::ModpackAuthor & author) {
- if(author.url.isEmpty()) {
- return author.name;
- }
- return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
- };
- QStringList authorStrs;
- for(auto & author: current.authors) {
- authorStrs.push_back(authorToStr(author));
+ }
+ QJsonArray arr = doc.array();
+ try {
+ FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(),
+ m_instance);
+ } catch (const JSONValidationError &e) {
+ qDebug() << *response;
+ qWarning() << "Error while reading Flame mod version: " << e.cause();
+ }
+ auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
+ QString mcVersion = packProfile->getComponentVersion("net.minecraft");
+ QString loaderString =
+ (packProfile->getComponentVersion("net.minecraftforge").isEmpty())
+ ? "fabric"
+ : "forge";
+ for (int i = 0; i < current.versions.size(); i++) {
+ auto version = current.versions[i];
+ if (!version.mcVersion.contains(mcVersion)) {
+ continue;
}
- text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ }
+ if (ui->versionSelectionBox->count() == 0) {
+ ui->versionSelectionBox->addItem(tr("No Valid Version found!"),
+ QVariant(-1));
+ }
+
+ ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
+ updateSelectionButton();
+ });
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
+ netJob->deleteLater();
+ delete response;
+ });
+ netJob->start();
+ } else {
+ for (int i = 0; i < current.versions.size(); i++) {
+ ui->versionSelectionBox->addItem(current.versions[i].version,
+ QVariant(i));
}
- text += "<br><br>";
-
- ui->packDescription->setHtml(text + current.description);
-
- if (!current.versionsLoaded)
- {
- qDebug() << "Loading flame mod versions";
- auto netJob = new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), APPLICATION->network());
- std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
- int addonId = current.addonId;
- netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get()));
-
- QObject::connect(netJob, &NetJob::succeeded, this, [this, response, netJob]
- {
- netJob->deleteLater();
- QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << *response;
- return;
- }
- QJsonArray arr = doc.array();
- try
- {
- FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance);
- }
- catch(const JSONValidationError &e)
- {
- qDebug() << *response;
- qWarning() << "Error while reading Flame mod version: " << e.cause();
- }
- auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
- QString mcVersion = packProfile->getComponentVersion("net.minecraft");
- QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge";
- for(int i = 0; i < current.versions.size(); i++) {
- auto version = current.versions[i];
- if(!version.mcVersion.contains(mcVersion)){
- continue;
- }
- ui->versionSelectionBox->addItem(version.version, QVariant(i));
- }
- if(ui->versionSelectionBox->count() == 0){
- ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant(-1));
- }
-
- suggestCurrent();
- });
- netJob->start();
+ if (ui->versionSelectionBox->count() == 0) {
+ ui->versionSelectionBox->addItem(tr("No Valid Version found!"),
+ QVariant(-1));
}
- else
- {
- for(int i = 0; i < current.versions.size(); i++) {
- ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
- }
- if(ui->versionSelectionBox->count() == 0){
- ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant(-1));
- }
- suggestCurrent();
- }
-}
-void FlameModPage::suggestCurrent()
-{
- if(!isOpened)
- {
- return;
- }
+ updateSelectionButton();
+ }
+}
- if (selectedVersion == -1)
- {
- dialog->setSuggestedMod();
- return;
- }
+void FlameModPage::updateSelectionButton() {
+ if (!isOpened || selectedVersion < 0) {
+ ui->modSelectionButton->setEnabled(false);
+ return;
+ }
+
+ ui->modSelectionButton->setEnabled(true);
+ auto &version = current.versions[selectedVersion];
+ if (!dialog->isModSelected(current.name, version.fileName)) {
+ ui->modSelectionButton->setText(tr("Select mod for download"));
+ } else {
+ ui->modSelectionButton->setText(tr("Deselect mod for download"));
+ }
+}
- auto version = current.versions[selectedVersion];
- dialog->setSuggestedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName , dialog->mods));
+void FlameModPage::onVersionSelectionChanged(QString data) {
+ if (data.isNull() || data.isEmpty()) {
+ selectedVersion = -1;
+ return;
+ }
+ selectedVersion = ui->versionSelectionBox->currentData().toInt();
+ updateSelectionButton();
}
-void FlameModPage::onVersionSelectionChanged(QString data)
-{
- if(data.isNull() || data.isEmpty())
- {
- selectedVersion = -1;
- return;
- }
- selectedVersion = ui->versionSelectionBox->currentData().toInt();
- suggestCurrent();
+void FlameModPage::onModSelected() {
+ auto &version = current.versions[selectedVersion];
+ if (dialog->isModSelected(current.name, version.fileName)) {
+ dialog->removeSelectedMod(current.name);
+ } else {
+ dialog->addSelectedMod(current.name,
+ new ModDownloadTask(version.downloadUrl,
+ version.fileName, dialog->mods));
+ }
+
+ updateSelectionButton();
}
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h
index 8fa3248a..b5b19a4f 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h
@@ -50,12 +50,13 @@ public:
BaseInstance *m_instance;
private:
- void suggestCurrent();
+ void updateSelectionButton();
private slots:
void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data);
+ void onModSelected();
private:
Ui::FlameModPage *ui = nullptr;
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.ui b/launcher/ui/pages/modplatform/flame/FlameModPage.ui
index 7da0bb4a..36df7e8a 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.ui
@@ -1,90 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>FlameModPage</class>
- <widget class="QWidget" name="FlameModPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>837</width>
- <height>685</height>
- </rect>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QListView" name="packView">
- <property name="iconSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QTextBrowser" name="packDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- <property name="openLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <widget class="QPushButton" name="searchButton">
- <property name="text">
- <string>Search</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit">
- <property name="placeholderText">
- <string>Search and filter ...</string>
- </property>
- </widget>
- </item>
+ <class>FlameModPage</class>
+ <widget class="QWidget" name="FlameModPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0,0,0" columnminimumwidth="0,0,0">
+ <item row="1" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="modSelectionButton">
+ <property name="text">
+ <string>Select mod for download</string>
+ </property>
+ </widget>
+ </item>
</layout>
- </widget>
- <tabstops>
- <tabstop>searchEdit</tabstop>
- <tabstop>searchButton</tabstop>
- <tabstop>packView</tabstop>
- <tabstop>packDescription</tabstop>
- <tabstop>sortByBox</tabstop>
- <tabstop>versionSelectionBox</tabstop>
- </tabstops>
- <resources/>
- <connections/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0">
+ <widget class="QListView" name="packView">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
</ui>
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 1138a298..7e6ac2fd 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -109,13 +109,16 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
if (current.versionsLoaded == false)
{
qDebug() << "Loading flame modpack versions";
- NetJob *netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
- std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+ auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
+ auto response = new QByteArray();
int addonId = current.addonId;
- netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get()));
+ netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response));
- QObject::connect(netJob, &NetJob::succeeded, this, [this, response]
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId]
{
+ if(addonId != current.addonId){
+ return; //wrong request
+ }
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
@@ -140,6 +143,11 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
suggestCurrent();
});
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob]
+ {
+ netJob->deleteLater();
+ delete response;
+ });
netJob->start();
}
else
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index c5a54c29..35cd743a 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -4,177 +4,202 @@
#include <QKeyEvent>
#include "Application.h"
-#include "Json.h"
-#include "ui/dialogs/ModDownloadDialog.h"
#include "InstanceImportTask.h"
-#include "ModrinthModel.h"
+#include "Json.h"
#include "ModDownloadTask.h"
+#include "ModrinthModel.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
+#include "ui/dialogs/ModDownloadDialog.h"
ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance)
- : QWidget(dialog), m_instance(instance), ui(new Ui::ModrinthPage), dialog(dialog)
-{
- ui->setupUi(this);
- connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch);
- ui->searchEdit->installEventFilter(this);
- listModel = new Modrinth::ListModel(this);
- ui->packView->setModel(listModel);
-
- ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
-
- // index is used to set the sorting with the modrinth api
- ui->sortByBox->addItem(tr("Sort by Relevence"));
- ui->sortByBox->addItem(tr("Sort by Downloads"));
- ui->sortByBox->addItem(tr("Sort by Follows"));
- ui->sortByBox->addItem(tr("Sort by last updated"));
- ui->sortByBox->addItem(tr("Sort by newest"));
-
- connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
+ : QWidget(dialog), m_instance(instance), ui(new Ui::ModrinthPage),
+ dialog(dialog) {
+ ui->setupUi(this);
+ connect(ui->searchButton, &QPushButton::clicked, this,
+ &ModrinthPage::triggerSearch);
+ ui->searchEdit->installEventFilter(this);
+ listModel = new Modrinth::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(
+ Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ // index is used to set the sorting with the modrinth api
+ ui->sortByBox->addItem(tr("Sort by Relevence"));
+ ui->sortByBox->addItem(tr("Sort by Downloads"));
+ ui->sortByBox->addItem(tr("Sort by Follows"));
+ ui->sortByBox->addItem(tr("Sort by last updated"));
+ ui->sortByBox->addItem(tr("Sort by newest"));
+
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged,
+ this, &ModrinthPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this,
+ &ModrinthPage::onVersionSelectionChanged);
+ connect(ui->modSelectionButton, &QPushButton::clicked, this,
+ &ModrinthPage::onModSelected);
}
-ModrinthPage::~ModrinthPage()
-{
- delete ui;
-}
+ModrinthPage::~ModrinthPage() { delete ui; }
-bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- }
+bool ModrinthPage::eventFilter(QObject *watched, QEvent *event) {
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
}
- return QWidget::eventFilter(watched, event);
+ }
+ return QWidget::eventFilter(watched, event);
}
-bool ModrinthPage::shouldDisplay() const
-{
- return true;
-}
+bool ModrinthPage::shouldDisplay() const { return true; }
-void ModrinthPage::openedImpl()
-{
- suggestCurrent();
- triggerSearch();
+void ModrinthPage::openedImpl() {
+ updateSelectionButton();
+ triggerSearch();
}
-void ModrinthPage::triggerSearch()
-{
- listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+void ModrinthPage::triggerSearch() {
+ listModel->searchWithTerm(ui->searchEdit->text(),
+ ui->sortByBox->currentIndex());
}
-void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
-{
- ui->versionSelectionBox->clear();
-
- if(!first.isValid())
- {
- if(isOpened)
- {
- dialog->setSuggestedMod();
+void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) {
+ ui->versionSelectionBox->clear();
+
+ if (!first.isValid()) {
+ return;
+ }
+
+ current = listModel->data(first, Qt::UserRole).value<Modrinth::IndexedPack>();
+ QString text = "";
+ QString name = current.name;
+
+ if (current.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
+ text += "<br>" + tr(" by ") + "<a href=\"" + current.author.url + "\">" +
+ current.author.name + "</a><br><br>";
+ ui->packDescription->setHtml(text + current.description);
+
+ if (!current.versionsLoaded) {
+ qDebug() << "Loading Modrinth mod versions";
+
+ ui->modSelectionButton->setText(tr("Loading versions..."));
+ ui->modSelectionButton->setEnabled(false);
+
+ auto netJob =
+ new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name),
+ APPLICATION->network());
+ auto response = new QByteArray();
+ QString addonId = current.addonId;
+ netJob->addNetAction(Net::Download::makeByteArray(
+ QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId),
+ response));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] {
+ if(addonId != current.addonId){
+ return;
}
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Modrinth at "
+ << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
return;
- }
-
- current = listModel->data(first, Qt::UserRole).value<Modrinth::IndexedPack>();
- QString text = "";
- QString name = current.name;
-
- if (current.websiteUrl.isEmpty())
- text = name;
- else
- text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
- text += "<br>"+ tr(" by ") + "<a href=\""+current.author.url+"\">"+current.author.name+"</a><br><br>";
- ui->packDescription->setHtml(text + current.description);
-
- if (!current.versionsLoaded)
- {
- qDebug() << "Loading Modrinth mod versions";
- auto netJob = new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network());
- std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
- QString addonId = current.addonId;
- netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId), response.get()));
-
- QObject::connect(netJob, &NetJob::succeeded, this, [this, response, netJob]
- {
- netJob->deleteLater();
- QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << *response;
- return;
- }
- QJsonArray arr = doc.array();
- try
- {
- Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance);
- }
- catch(const JSONValidationError &e)
- {
- qDebug() << *response;
- qWarning() << "Error while reading Modrinth mod version: " << e.cause();
- }
- auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
- QString mcVersion = packProfile->getComponentVersion("net.minecraft");
- QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge";
- for(int i = 0; i < current.versions.size(); i++) {
- auto version = current.versions[i];
- if(!version.mcVersion.contains(mcVersion) || !version.loaders.contains(loaderString)){
- continue;
- }
- ui->versionSelectionBox->addItem(version.version, QVariant(i));
- }
- if(ui->versionSelectionBox->count() == 0){
- ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant(-1));
- }
-
- suggestCurrent();
- });
- netJob->start();
- }
- else
- {
- for(int i = 0; i < current.versions.size(); i++) {
- ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
- }
- if(ui->versionSelectionBox->count() == 0){
- ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant(-1));
+ }
+ QJsonArray arr = doc.array();
+ try {
+ Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(),
+ m_instance);
+ } catch (const JSONValidationError &e) {
+ qDebug() << *response;
+ qWarning() << "Error while reading Modrinth mod version: " << e.cause();
+ }
+ auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
+ QString mcVersion = packProfile->getComponentVersion("net.minecraft");
+ QString loaderString =
+ (packProfile->getComponentVersion("net.minecraftforge").isEmpty())
+ ? "fabric"
+ : "forge";
+ for (int i = 0; i < current.versions.size(); i++) {
+ auto version = current.versions[i];
+ if (!version.mcVersion.contains(mcVersion) ||
+ !version.loaders.contains(loaderString)) {
+ continue;
}
- suggestCurrent();
+ ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ }
+ if (ui->versionSelectionBox->count() == 0) {
+ ui->versionSelectionBox->addItem(tr("No Valid Version found !"),
+ QVariant(-1));
+ }
+
+ ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
+ updateSelectionButton();
+ });
+
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
+ netJob->deleteLater();
+ delete response;
+ });
+
+ netJob->start();
+ } else {
+ for (int i = 0; i < current.versions.size(); i++) {
+ ui->versionSelectionBox->addItem(current.versions[i].version,
+ QVariant(i));
+ }
+ if (ui->versionSelectionBox->count() == 0) {
+ ui->versionSelectionBox->addItem(tr("No Valid Version found !"),
+ QVariant(-1));
}
+
+ updateSelectionButton();
+ }
}
-void ModrinthPage::suggestCurrent()
-{
- if(!isOpened)
- {
- return;
- }
+void ModrinthPage::updateSelectionButton() {
+ if (!isOpened || selectedVersion < 0) {
+ ui->modSelectionButton->setEnabled(false);
+ return;
+ }
+
+ ui->modSelectionButton->setEnabled(true);
+ auto &version = current.versions[selectedVersion];
+ if (!dialog->isModSelected(current.name, version.fileName)) {
+ ui->modSelectionButton->setText(tr("Select mod for download"));
+ } else {
+ ui->modSelectionButton->setText(tr("Deselect mod for download"));
+ }
+}
- if (selectedVersion == -1)
- {
- dialog->setSuggestedMod();
- return;
- }
- auto version = current.versions[selectedVersion];
- dialog->setSuggestedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName , dialog->mods));
+void ModrinthPage::onVersionSelectionChanged(QString data) {
+ if (data.isNull() || data.isEmpty()) {
+ selectedVersion = -1;
+ return;
+ }
+ selectedVersion = ui->versionSelectionBox->currentData().toInt();
+ updateSelectionButton();
}
-void ModrinthPage::onVersionSelectionChanged(QString data)
-{
- if(data.isNull() || data.isEmpty())
- {
- selectedVersion = -1;
- return;
- }
- selectedVersion = ui->versionSelectionBox->currentData().toInt();
- suggestCurrent();
+void ModrinthPage::onModSelected() {
+ auto &version = current.versions[selectedVersion];
+ if (dialog->isModSelected(current.name, version.fileName)) {
+ dialog->removeSelectedMod(current.name);
+ } else {
+ dialog->addSelectedMod(current.name,
+ new ModDownloadTask(version.downloadUrl,
+ version.fileName, dialog->mods));
+ }
+
+ updateSelectionButton();
}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
index 3c517069..52b538e3 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -50,12 +50,13 @@ public:
BaseInstance *m_instance;
private:
- void suggestCurrent();
+ void updateSelectionButton();
private slots:
void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data);
+ void onModSelected();
private:
Ui::ModrinthPage *ui = nullptr;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
index 6d183de5..d0a8b8f7 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -1,90 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>ModrinthPage</class>
- <widget class="QWidget" name="ModrinthPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>837</width>
- <height>685</height>
- </rect>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QListView" name="packView">
- <property name="iconSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QTextBrowser" name="packDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- <property name="openLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <widget class="QPushButton" name="searchButton">
- <property name="text">
- <string>Search</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit">
- <property name="placeholderText">
- <string>Search and filter ...</string>
- </property>
- </widget>
- </item>
+ <class>ModrinthPage</class>
+ <widget class="QWidget" name="ModrinthPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="2">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QListView" name="packView">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ </widget>
+ </item>
</layout>
- </widget>
- <tabstops>
- <tabstop>searchEdit</tabstop>
- <tabstop>searchButton</tabstop>
- <tabstop>packView</tabstop>
- <tabstop>packDescription</tabstop>
- <tabstop>sortByBox</tabstop>
- <tabstop>versionSelectionBox</tabstop>
- </tabstops>
- <resources/>
- <connections/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="modSelectionButton">
+ <property name="text">
+ <string>Select mod for download</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
</ui>
diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp
index 6de49467..558a98fb 100644
--- a/launcher/ui/widgets/PageContainer.cpp
+++ b/launcher/ui/widgets/PageContainer.cpp
@@ -14,6 +14,7 @@
*/
#include "PageContainer.h"
+#include "BuildConfig.h"
#include "PageContainer_p.h"
#include <QStackedLayout>
@@ -207,7 +208,7 @@ void PageContainer::help()
QString pageId = m_currentPage->helpPage();
if (pageId.isEmpty())
return;
- DesktopServices::openUrl(QUrl("https://github.com/PolyMC/PolyMC/wiki/" + pageId));
+ DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(pageId)));
}
}
diff --git a/packages/nix/polymc/default.nix b/packages/nix/polymc/default.nix
index a9610017..63fc6b7e 100644
--- a/packages/nix/polymc/default.nix
+++ b/packages/nix/polymc/default.nix
@@ -47,12 +47,7 @@ mkDerivation rec {
dontWrapQtApps = true;
- postPatch = ''
- # hardcode jdk paths
- substituteInPlace launcher/java/JavaUtils.cpp \
- --replace 'scanJavaDir("/usr/lib/jvm")' 'javas.append("${jdk}/lib/openjdk/bin/java")' \
- --replace 'scanJavaDir("/usr/lib32/jvm")' 'javas.append("${jdk8}/lib/openjdk/bin/java")'
- '' + lib.optionalString (msaClientID != "") ''
+ postPatch = lib.optionalString (msaClientID != "") ''
# add client ID
substituteInPlace CMakeLists.txt \
--replace '17b47edd-c884-4997-926d-9e7f9a6b4647' '${msaClientID}'
@@ -69,7 +64,7 @@ mkDerivation rec {
cmakeFlags = [
"-GNinja"
- "-DLauncher_LAYOUT=lin-system"
+ "-DLauncher_PORTABLE=OFF"
];
postInstall = ''
@@ -77,6 +72,7 @@ mkDerivation rec {
wrapProgram $out/bin/polymc \
"''${qtWrapperArgs[@]}" \
--set GAME_LIBRARY_PATH ${gameLibraryPath} \
+ --prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}
'';
diff --git a/program_info/org.polymc.PolyMC.metainfo.xml.in b/program_info/org.polymc.PolyMC.metainfo.xml.in
index 03401f3d..7972965c 100644
--- a/program_info/org.polymc.PolyMC.metainfo.xml.in
+++ b/program_info/org.polymc.PolyMC.metainfo.xml.in
@@ -11,7 +11,7 @@
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<url type="homepage">https://polymc.org/</url>
- <url type="help">https://github.com/PolyMC/PolyMC#help--support</url>
+ <url type="help">https://polymc.org/wiki/</url>
<description>
<p>PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.</p>
<p>Features:</p>
@@ -28,23 +28,23 @@
<screenshots>
<screenshot type="default">
<caption>The main PolyMC window</caption>
- <image type="source" width="1011" height="994">https://polymc.github.io/assets/img/screenshots/LauncherDark.png</image>
+ <image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherDark.png</image>
</screenshot>
<screenshot>
<caption>Modpack installation</caption>
- <image type="source" width="911" height="682">https://polymc.github.io/assets/img/screenshots/ModpackInstallDark.png</image>
+ <image type="source" width="911" height="682">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
</screenshot>
<screenshot>
<caption>Mod installation</caption>
- <image type="source" width="987" height="723">https://polymc.github.io/assets/img/screenshots/ModInstallDark.png</image>
+ <image type="source" width="987" height="723">https://polymc.org/img/screenshots/ModInstallDark.png</image>
</screenshot>
<screenshot>
<caption>Instance management</caption>
- <image type="source" width="902" height="920">https://polymc.github.io/assets/img/screenshots/PropertiesDark.png</image>
+ <image type="source" width="902" height="920">https://polymc.org/img/screenshots/PropertiesDark.png</image>
</screenshot>
<screenshot>
<caption>Cat :)</caption>
- <image type="source" width="1011" height="994">https://polymc.github.io/assets/img/screenshots/LauncherCatDark.png</image>
+ <image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
</screenshot>
</screenshots>
<releases>
diff --git a/program_info/polymc-header-black.svg b/program_info/polymc-header-black.svg
index 34cda4ab..e9e7c3e2 100644
--- a/program_info/polymc-header-black.svg
+++ b/program_info/polymc-header-black.svg
@@ -1,31 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-<svg width="1424" height="512" version="1.1" viewBox="0 0 376.77 135.47" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg viewBox="0 0 376.77 135.47" xmlns="http://www.w3.org/2000/svg" class="home">
<defs>
- <linearGradient id="linearGradient84726" x1="4.4979" x2="12.435" y1="3.8011" y2="9.5681" gradientUnits="userSpaceOnUse">
+ <linearGradient id="a" x1="4.498" x2="12.435" y1="3.801" y2="9.568" gradientUnits="userSpaceOnUse">
<stop stop-color="#88b858" offset="0"/>
<stop stop-color="#72b147" offset=".5"/>
<stop stop-color="#5a9a30" offset="1"/>
</linearGradient>
</defs>
- <g transform="matrix(6.95 0 0 6.9572 3.7759 1.0225)">
- <g>
- <path d="m3.561 16.016s0-3.5642 4.9056-3.5642c4.9069 0 4.9056 3.5642 4.9056 3.5642z" fill="#765338"/>
- <path d="m8.4667 12.452-4.9056 3.5642-3.0319-9.3311z" fill="#b7835a"/>
- <path d="m8.4667 12.452 7.9375-5.7669-3.0319 9.3311z" fill="#5b422d"/>
- <path d="m8.8308 12.716-0.36417 0.26458-0.36417-0.26458c0-0.26458 0.36417-0.26458 0.36417-0.26458s0.36417 0 0.36417 0.26458z" fill="#72b147"/>
- <path d="m8.4667 12.452s-2e-7 -5.7669 7.9375-5.7669l-0.22507 0.69269-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819z" fill="#5a9a30"/>
- <path d="m8.1025 12.716-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.22507-0.69269c7.9375 1e-7 7.9375 5.7669 7.9375 5.7669z" fill="#88b858"/>
- <path d="m0.52917 6.6846 7.9375 5.7669 7.9375-5.7669-7.9375-5.7669z" fill="url(#linearGradient84726)"/>
+ <path d="M42.375 118.668s0-24.797 34.094-24.797c34.103 0 34.094 24.797 34.094 24.797z" fill="#765338"/>
+ <path d="M76.47 93.872 42.376 118.67 21.304 53.751z" fill="#b7835a"/>
+ <path d="m76.47 93.872 55.165-40.121-21.072 64.918z" fill="#5b422d"/>
+ <path d="m79 95.709-2.53 1.84-2.532-1.84c0-1.84 2.531-1.84 2.531-1.84s2.531 0 2.531 1.84z" fill="#72b147"/>
+ <path d="M76.47 93.872s0-40.121 55.165-40.121l-1.564 4.819-6.384 8.324-6.384.962-6.383 8.324-6.384.961-6.384 8.325-6.384.961-6.384 8.324-6.383.962z" fill="#5a9a30"/>
+ <path d="m73.938 95.709-6.383-.961-6.384-8.325-6.384-.961-6.384-8.324-6.384-.962-6.383-8.324-6.384-.962-6.384-8.324-1.564-4.819c55.165 0 55.165 40.121 55.165 40.121z" fill="#88b858"/>
+ <path d="m.53 6.685 7.937 5.766 7.937-5.766L8.467.918z" fill="url(#a)" transform="matrix(6.95 0 0 6.9572 17.626 7.241)"/>
+ <path d="m22.868 58.567-1.564-4.82L76.469 93.87l55.166-40.122-1.564 4.82L76.47 97.55z" fill="none"/>
+ <g fill="#000" stroke-width=".265" aria-label="PolyMC">
+ <g stroke-width=".01" aria-label="PolyMC">
+ <path d="M168.153 47.323q5.434 0 9.287 1.858 3.852 1.788 5.916 5.228 2.133 3.44 2.133 8.324 0 2.958-.895 5.847-.894 2.82-2.889 5.16-1.926 2.27-5.09 3.646t-7.705 1.375h-7.361v18.3h-6.673V47.322zm.688 25.041q2.958 0 4.884-.963 1.927-.963 3.027-2.408 1.1-1.513 1.582-3.164.482-1.651.482-2.958 0-1.514-.482-3.096-.481-1.651-1.65-2.958-1.101-1.376-2.959-2.202-1.788-.894-4.471-.894h-7.705v18.643zM187.49 82.6q0-4.265 1.994-7.705 2.064-3.44 5.641-5.434 3.577-1.995 8.118-1.995 4.678 0 8.186 1.995 3.509 1.995 5.435 5.434 1.926 3.44 1.926 7.705t-1.926 7.773q-1.926 3.44-5.503 5.435-3.509 1.995-8.187 1.995-4.54 0-8.118-1.857-3.508-1.927-5.572-5.298-1.995-3.44-1.995-8.048zm6.397.07q0 2.751 1.238 5.021 1.238 2.202 3.302 3.509 2.133 1.307 4.678 1.307 2.683 0 4.747-1.307 2.133-1.307 3.302-3.509 1.17-2.27 1.17-5.022 0-2.752-1.17-4.953-1.17-2.27-3.302-3.577-2.064-1.376-4.747-1.376-2.614 0-4.747 1.376-2.063 1.376-3.302 3.646-1.17 2.201-1.17 4.884zM223.251 43.858h6.398v53.246h-6.398zM238.914 110.808l18.78-42.17h5.917l-18.368 42.17zm7.017-13.484-13.69-28.687h7.223l11.489 25.453zM266.775 97.085V51.2h.063l23.855 33.774-3.043-.67L311.384 51.2h.122v45.885h-7.06v-29.88l.487 3.59-16.065 22.7h-.122l-16.31-22.7 1.218-3.286v29.576zM352.299 93.62q-.974.67-2.921 1.643-1.887.973-4.564 1.704-2.617.67-5.843.608-5.172-.062-9.31-1.825-4.138-1.826-6.999-4.869-2.86-3.104-4.442-7.06-1.522-4.016-1.522-8.52 0-5.05 1.583-9.25t4.503-7.241q2.921-3.104 6.938-4.808 4.016-1.704 8.763-1.704 4.199 0 7.485 1.156 3.347 1.096 5.598 2.496l-2.799 6.633q-1.704-1.156-4.26-2.313-2.556-1.156-5.781-1.156-2.921 0-5.599 1.217-2.678 1.156-4.686 3.347-2.008 2.13-3.225 4.99-1.156 2.86-1.156 6.208 0 3.468 1.034 6.39 1.096 2.92 3.043 5.05 2.008 2.07 4.747 3.287 2.799 1.156 6.268 1.156 3.408 0 5.964-1.035 2.616-1.095 4.199-2.434z"/>
</g>
- <path d="m0.75424 7.3773-0.22507-0.69269 7.9375 5.7669 7.9375-5.7669-0.22507 0.69269-7.7124 5.6034z" fill-opacity="0"/>
- </g>
- <g id="polymc-header-text" fill="black" transform="matrix(6.9306 0 0 6.9306 -702.9 -659.02)" stroke-width=".26458" aria-label="PolyMC">
- <path d="m120.51 108.48v2.7957h-1.3074v-7.5241h2.8784q1.2609 0 1.9999 0.65629 0.74414 0.65629 0.74414 1.7363 0 1.1059-0.72864 1.7208-0.72346 0.61495-2.0309 0.61495zm0-1.049h1.571q0.69763 0 1.0645-0.32556 0.3669-0.33073 0.3669-0.95084 0-0.60978-0.37207-0.97152-0.37207-0.3669-1.0232-0.37723h-1.6071z"/>
- <path d="m125.55 108.43q0-0.82165 0.32556-1.4779 0.32556-0.66145 0.91467-1.0128 0.58911-0.35657 1.3539-0.35657 1.1317 0 1.8345 0.72864 0.70797 0.72863 0.76481 1.9327l5e-3 0.29455q0 0.82682-0.32039 1.478-0.31523 0.65112-0.9095 1.0077-0.58911 0.35657-1.3643 0.35657-1.1834 0-1.8965-0.78548-0.70796-0.79065-0.70796-2.1032zm1.2557 0.10852q0 0.863 0.35657 1.3539 0.35656 0.48576 0.99218 0.48576t0.98702-0.49609q0.35657-0.49609 0.35657-1.4521 0-0.84749-0.36691-1.3436-0.36173-0.49609-0.98701-0.49609-0.61495 0-0.97668 0.49092-0.36174 0.48576-0.36174 1.4573z"/>
- <path d="m133.14 111.27h-1.2557v-7.9375h1.2557z"/>
- <path d="m136.46 109.47 1.1369-3.793h1.3384l-2.2221 6.4389q-0.5116 1.4108-1.7363 1.4108-0.27388 0-0.60461-0.093v-0.97151l0.23771 0.0155q0.47542 0 0.71314-0.1757 0.24287-0.17053 0.3824-0.57877l0.18087-0.48059-1.9637-5.5655h1.3539z"/>
- <path d="m141.48 103.75 2.1704 5.7671 2.1652-5.7671h1.6898v7.5241h-1.3022v-2.4805l0.12919-3.3176-2.2221 5.7981h-0.93534l-2.2169-5.7929 0.12919 3.3124v2.4805h-1.3022v-7.5241z"/>
- <path d="m154.79 108.82q-0.11369 1.2041-0.88883 1.881-0.77515 0.67179-2.0619 0.67179-0.89916 0-1.5865-0.42375-0.68212-0.42891-1.0542-1.2144t-0.38758-1.8242v-0.7028q0-1.0645 0.37724-1.8758 0.37724-0.81131 1.08-1.2506 0.70797-0.43925 1.633-0.43925 1.2454 0 2.005 0.67696 0.75965 0.67696 0.88367 1.912h-1.3022q-0.093-0.81132-0.47543-1.1679-0.37723-0.36174-1.111-0.36174-0.85265 0-1.3126 0.62529-0.45475 0.62011-0.46509 1.8242v0.66662q0 1.2196 0.43408 1.8604 0.43925 0.64078 1.2816 0.64078 0.76998 0 1.1576-0.34623t0.49093-1.1524z"/>
</g>
</svg>
diff --git a/program_info/polymc-header.Source.svg b/program_info/polymc-header.Source.svg
new file mode 100644
index 00000000..c960f33b
--- /dev/null
+++ b/program_info/polymc-header.Source.svg
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="1424"
+ height="512"
+ version="1.1"
+ viewBox="0 0 376.77 135.47"
+ id="svg866"
+ sodipodi:docname="PolyMC-Header.Source.svg"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview868"
+ pagecolor="#343434"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.61938202"
+ inkscape:cx="534.40363"
+ inkscape:cy="299.49206"
+ inkscape:window-width="2560"
+ inkscape:window-height="1382"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg866" />
+ <defs
+ id="defs831">
+ <linearGradient
+ id="linearGradient84726"
+ x1="4.4979"
+ x2="12.435"
+ y1="3.8011"
+ y2="9.5681"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ stop-color="#88b858"
+ offset="0"
+ id="stop824" />
+ <stop
+ stop-color="#72b147"
+ offset=".5"
+ id="stop826" />
+ <stop
+ stop-color="#5a9a30"
+ offset="1"
+ id="stop828" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84726"
+ id="linearGradient1147"
+ gradientUnits="userSpaceOnUse"
+ x1="4.4979"
+ y1="3.8011"
+ x2="12.435"
+ y2="9.5681" />
+ </defs>
+ <g
+ id="g1157">
+ <g
+ transform="matrix(6.95,0,0,6.9572,17.626477,7.241195)"
+ id="g1322">
+ <g
+ id="g1318">
+ <path
+ d="m 3.561,16.016 c 0,0 0,-3.5642 4.9056,-3.5642 4.9069,0 4.9056,3.5642 4.9056,3.5642 z"
+ fill="#765338"
+ id="path1304" />
+ <path
+ d="M 8.4667,12.452 3.5611,16.0162 0.5292,6.6851 Z"
+ fill="#b7835a"
+ id="path1306" />
+ <path
+ d="m 8.4667,12.452 7.9375,-5.7669 -3.0319,9.3311 z"
+ fill="#5b422d"
+ id="path1308" />
+ <path
+ d="M 8.8308,12.716 8.46663,12.98058 8.10246,12.716 c 0,-0.26458 0.36417,-0.26458 0.36417,-0.26458 0,0 0.36417,0 0.36417,0.26458 z"
+ fill="#72b147"
+ id="path1310" />
+ <path
+ d="m 8.4667,12.452 c 0,0 -2e-7,-5.7669 7.9375,-5.7669 l -0.22507,0.69269 -0.91853,1.1965 -0.91853,0.13819 -0.91853,1.1965 -0.91853,0.13819 -0.91853,1.1965 -0.91853,0.13819 -0.91853,1.1965 -0.91853,0.13819 z"
+ fill="#5a9a30"
+ id="path1312" />
+ <path
+ d="M 8.1025,12.716 7.18397,12.57781 6.26544,11.38131 5.34691,11.24312 4.42838,10.04662 3.50985,9.90843 2.59132,8.71193 1.67279,8.57374 0.75426,7.37724 0.52919,6.68455 c 7.9375,1e-7 7.9375,5.7669 7.9375,5.7669 z"
+ fill="#88b858"
+ id="path1314" />
+ <path
+ d="m 0.52917,6.6846 7.9375,5.7669 7.9375,-5.7669 -7.9375,-5.7669 z"
+ fill="url(#linearGradient84726)"
+ id="path1316"
+ style="fill:url(#linearGradient1147)" />
+ </g>
+ <path
+ d="m 0.75424,7.3773 -0.22507,-0.69269 7.9375,5.7669 7.9375,-5.7669 -0.22507,0.69269 -7.7124,5.6034 z"
+ fill-opacity="0"
+ id="path1320" />
+ </g>
+ <g
+ id="polymc-header-text-3"
+ fill="white"
+ transform="matrix(6.9306,0,0,6.9306,-695.39957,-649.40511)"
+ stroke-width="0.26458"
+ aria-label="PolyMC"
+ style="fill:#ffffff;fill-opacity:1">
+ <text
+ xml:space="preserve"
+ style="font-size:9.92603px;line-height:1.25;font-family:sans-serif;letter-spacing:-0.610833px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke-width:0.0101009"
+ x="121.65298"
+ y="107.71044"
+ id="text4534"><tspan
+ sodipodi:role="line"
+ id="tspan4532"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.92603px;font-family:'Josefin Sans';-inkscape-font-specification:'Josefin Sans';fill:#ffffff;fill-opacity:1;stroke-width:0.0101009"
+ x="121.65298"
+ y="107.71044">Poly<tspan
+ style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:8.78072px;font-family:'Josefin Sans';-inkscape-font-specification:'Josefin Sans Semi-Bold'"
+ id="tspan137828"
+ rotate="0 0">MC</tspan></tspan></text>
+ </g>
+ </g>
+ <g
+ id="polymc-header-text-4"
+ fill="white"
+ transform="matrix(6.9306,0,0,6.9306,-697.30938,-585.54339)"
+ stroke-width="0.26458"
+ aria-label="PolyMC"
+ style="fill:#ffffff;fill-opacity:1" />
+</svg>
diff --git a/program_info/polymc-header.svg b/program_info/polymc-header.svg
index a896787f..837004e1 100644
--- a/program_info/polymc-header.svg
+++ b/program_info/polymc-header.svg
@@ -1,31 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-<svg width="1424" height="512" version="1.1" viewBox="0 0 376.77 135.47" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg viewBox="0 0 376.77 135.47" xmlns="http://www.w3.org/2000/svg" class="home">
<defs>
- <linearGradient id="linearGradient84726" x1="4.4979" x2="12.435" y1="3.8011" y2="9.5681" gradientUnits="userSpaceOnUse">
+ <linearGradient id="a" x1="4.498" x2="12.435" y1="3.801" y2="9.568" gradientUnits="userSpaceOnUse">
<stop stop-color="#88b858" offset="0"/>
<stop stop-color="#72b147" offset=".5"/>
<stop stop-color="#5a9a30" offset="1"/>
</linearGradient>
</defs>
- <g transform="matrix(6.95 0 0 6.9572 3.7759 1.0225)">
- <g>
- <path d="m3.561 16.016s0-3.5642 4.9056-3.5642c4.9069 0 4.9056 3.5642 4.9056 3.5642z" fill="#765338"/>
- <path d="m8.4667 12.452-4.9056 3.5642-3.0319-9.3311z" fill="#b7835a"/>
- <path d="m8.4667 12.452 7.9375-5.7669-3.0319 9.3311z" fill="#5b422d"/>
- <path d="m8.8308 12.716-0.36417 0.26458-0.36417-0.26458c0-0.26458 0.36417-0.26458 0.36417-0.26458s0.36417 0 0.36417 0.26458z" fill="#72b147"/>
- <path d="m8.4667 12.452s-2e-7 -5.7669 7.9375-5.7669l-0.22507 0.69269-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819z" fill="#5a9a30"/>
- <path d="m8.1025 12.716-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.22507-0.69269c7.9375 1e-7 7.9375 5.7669 7.9375 5.7669z" fill="#88b858"/>
- <path d="m0.52917 6.6846 7.9375 5.7669 7.9375-5.7669-7.9375-5.7669z" fill="url(#linearGradient84726)"/>
+ <path d="M42.375 118.668s0-24.797 34.094-24.797c34.103 0 34.094 24.797 34.094 24.797z" fill="#765338"/>
+ <path d="M76.47 93.872 42.376 118.67 21.304 53.751z" fill="#b7835a"/>
+ <path d="m76.47 93.872 55.165-40.121-21.072 64.918z" fill="#5b422d"/>
+ <path d="m79 95.709-2.53 1.84-2.532-1.84c0-1.84 2.531-1.84 2.531-1.84s2.531 0 2.531 1.84z" fill="#72b147"/>
+ <path d="M76.47 93.872s0-40.121 55.165-40.121l-1.564 4.819-6.384 8.324-6.384.962-6.383 8.324-6.384.961-6.384 8.325-6.384.961-6.384 8.324-6.383.962z" fill="#5a9a30"/>
+ <path d="m73.938 95.709-6.383-.961-6.384-8.325-6.384-.961-6.384-8.324-6.384-.962-6.383-8.324-6.384-.962-6.384-8.324-1.564-4.819c55.165 0 55.165 40.121 55.165 40.121z" fill="#88b858"/>
+ <path d="m.53 6.685 7.937 5.766 7.937-5.766L8.467.918z" fill="url(#a)" transform="matrix(6.95 0 0 6.9572 17.626 7.241)"/>
+ <path d="m22.868 58.567-1.564-4.82L76.469 93.87l55.166-40.122-1.564 4.82L76.47 97.55z" fill="none"/>
+ <g fill="#fff" stroke-width=".265" aria-label="PolyMC">
+ <g stroke-width=".01" aria-label="PolyMC">
+ <path d="M168.153 47.323q5.434 0 9.287 1.858 3.852 1.788 5.916 5.228 2.133 3.44 2.133 8.324 0 2.958-.895 5.847-.894 2.82-2.889 5.16-1.926 2.27-5.09 3.646t-7.705 1.375h-7.361v18.3h-6.673V47.322zm.688 25.041q2.958 0 4.884-.963 1.927-.963 3.027-2.408 1.1-1.513 1.582-3.164.482-1.651.482-2.958 0-1.514-.482-3.096-.481-1.651-1.65-2.958-1.101-1.376-2.959-2.202-1.788-.894-4.471-.894h-7.705v18.643zM187.49 82.6q0-4.265 1.994-7.705 2.064-3.44 5.641-5.434 3.577-1.995 8.118-1.995 4.678 0 8.186 1.995 3.509 1.995 5.435 5.434 1.926 3.44 1.926 7.705t-1.926 7.773q-1.926 3.44-5.503 5.435-3.509 1.995-8.187 1.995-4.54 0-8.118-1.857-3.508-1.927-5.572-5.298-1.995-3.44-1.995-8.048zm6.397.07q0 2.751 1.238 5.021 1.238 2.202 3.302 3.509 2.133 1.307 4.678 1.307 2.683 0 4.747-1.307 2.133-1.307 3.302-3.509 1.17-2.27 1.17-5.022 0-2.752-1.17-4.953-1.17-2.27-3.302-3.577-2.064-1.376-4.747-1.376-2.614 0-4.747 1.376-2.063 1.376-3.302 3.646-1.17 2.201-1.17 4.884zM223.251 43.858h6.398v53.246h-6.398zM238.914 110.808l18.78-42.17h5.917l-18.368 42.17zm7.017-13.484-13.69-28.687h7.223l11.489 25.453zM266.775 97.085V51.2h.063l23.855 33.774-3.043-.67L311.384 51.2h.122v45.885h-7.06v-29.88l.487 3.59-16.065 22.7h-.122l-16.31-22.7 1.218-3.286v29.576zM352.299 93.62q-.974.67-2.921 1.643-1.887.973-4.564 1.704-2.617.67-5.843.608-5.172-.062-9.31-1.825-4.138-1.826-6.999-4.869-2.86-3.104-4.442-7.06-1.522-4.016-1.522-8.52 0-5.05 1.583-9.25t4.503-7.241q2.921-3.104 6.938-4.808 4.016-1.704 8.763-1.704 4.199 0 7.485 1.156 3.347 1.096 5.598 2.496l-2.799 6.633q-1.704-1.156-4.26-2.313-2.556-1.156-5.781-1.156-2.921 0-5.599 1.217-2.678 1.156-4.686 3.347-2.008 2.13-3.225 4.99-1.156 2.86-1.156 6.208 0 3.468 1.034 6.39 1.096 2.92 3.043 5.05 2.008 2.07 4.747 3.287 2.799 1.156 6.268 1.156 3.408 0 5.964-1.035 2.616-1.095 4.199-2.434z"/>
</g>
- <path d="m0.75424 7.3773-0.22507-0.69269 7.9375 5.7669 7.9375-5.7669-0.22507 0.69269-7.7124 5.6034z" fill-opacity="0"/>
- </g>
- <g id="polymc-header-text" fill="white" transform="matrix(6.9306 0 0 6.9306 -702.9 -659.02)" stroke-width=".26458" aria-label="PolyMC">
- <path d="m120.51 108.48v2.7957h-1.3074v-7.5241h2.8784q1.2609 0 1.9999 0.65629 0.74414 0.65629 0.74414 1.7363 0 1.1059-0.72864 1.7208-0.72346 0.61495-2.0309 0.61495zm0-1.049h1.571q0.69763 0 1.0645-0.32556 0.3669-0.33073 0.3669-0.95084 0-0.60978-0.37207-0.97152-0.37207-0.3669-1.0232-0.37723h-1.6071z"/>
- <path d="m125.55 108.43q0-0.82165 0.32556-1.4779 0.32556-0.66145 0.91467-1.0128 0.58911-0.35657 1.3539-0.35657 1.1317 0 1.8345 0.72864 0.70797 0.72863 0.76481 1.9327l5e-3 0.29455q0 0.82682-0.32039 1.478-0.31523 0.65112-0.9095 1.0077-0.58911 0.35657-1.3643 0.35657-1.1834 0-1.8965-0.78548-0.70796-0.79065-0.70796-2.1032zm1.2557 0.10852q0 0.863 0.35657 1.3539 0.35656 0.48576 0.99218 0.48576t0.98702-0.49609q0.35657-0.49609 0.35657-1.4521 0-0.84749-0.36691-1.3436-0.36173-0.49609-0.98701-0.49609-0.61495 0-0.97668 0.49092-0.36174 0.48576-0.36174 1.4573z"/>
- <path d="m133.14 111.27h-1.2557v-7.9375h1.2557z"/>
- <path d="m136.46 109.47 1.1369-3.793h1.3384l-2.2221 6.4389q-0.5116 1.4108-1.7363 1.4108-0.27388 0-0.60461-0.093v-0.97151l0.23771 0.0155q0.47542 0 0.71314-0.1757 0.24287-0.17053 0.3824-0.57877l0.18087-0.48059-1.9637-5.5655h1.3539z"/>
- <path d="m141.48 103.75 2.1704 5.7671 2.1652-5.7671h1.6898v7.5241h-1.3022v-2.4805l0.12919-3.3176-2.2221 5.7981h-0.93534l-2.2169-5.7929 0.12919 3.3124v2.4805h-1.3022v-7.5241z"/>
- <path d="m154.79 108.82q-0.11369 1.2041-0.88883 1.881-0.77515 0.67179-2.0619 0.67179-0.89916 0-1.5865-0.42375-0.68212-0.42891-1.0542-1.2144t-0.38758-1.8242v-0.7028q0-1.0645 0.37724-1.8758 0.37724-0.81131 1.08-1.2506 0.70797-0.43925 1.633-0.43925 1.2454 0 2.005 0.67696 0.75965 0.67696 0.88367 1.912h-1.3022q-0.093-0.81132-0.47543-1.1679-0.37723-0.36174-1.111-0.36174-0.85265 0-1.3126 0.62529-0.45475 0.62011-0.46509 1.8242v0.66662q0 1.2196 0.43408 1.8604 0.43925 0.64078 1.2816 0.64078 0.76998 0 1.1576-0.34623t0.49093-1.1524z"/>
</g>
</svg>