aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml50
-rw-r--r--.github/workflows/codeql.yml35
-rw-r--r--.github/workflows/trigger_builds.yml1
-rw-r--r--.github/workflows/trigger_release.yml1
-rw-r--r--CMakeLists.txt3
-rw-r--r--README.md8
-rw-r--r--flatpak/org.prismlauncher.PrismLauncher.yml83
-rw-r--r--flatpak/prime-run4
-rw-r--r--flatpak/prismlauncher11
-rw-r--r--launcher/Application.cpp2
-rw-r--r--launcher/BaseInstance.h2
-rw-r--r--launcher/BaseVersionList.cpp4
-rw-r--r--launcher/VersionProxyModel.cpp4
-rw-r--r--launcher/icons/IconList.cpp4
-rw-r--r--launcher/meta/Index.cpp4
-rw-r--r--launcher/meta/JsonFormat.h5
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp35
-rw-r--r--launcher/minecraft/MinecraftInstance.h4
-rw-r--r--launcher/minecraft/MojangVersionFormat.cpp17
-rw-r--r--launcher/minecraft/OneSixVersionFormat.cpp29
-rw-r--r--launcher/minecraft/PackProfile.cpp6
-rw-r--r--launcher/minecraft/Rule.h2
-rw-r--r--launcher/minecraft/WorldList.cpp6
-rw-r--r--launcher/minecraft/WorldList.h2
-rw-r--r--launcher/minecraft/auth/AccountList.cpp10
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h6
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp2
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp7
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp11
-rw-r--r--launcher/net/HttpMetaCache.cpp72
-rw-r--r--launcher/net/HttpMetaCache.h58
-rw-r--r--launcher/translations/TranslationsModel.cpp9
-rw-r--r--launcher/ui/MainWindow.cpp98
-rw-r--r--launcher/ui/MainWindow.h6
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp195
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.h19
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.ui67
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui6
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp4
-rw-r--r--launcher/ui/pages/instance/VersionPage.ui6
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h8
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbListModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h6
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp8
-rw-r--r--libraries/README.md68
-rw-r--r--libraries/launcher/.gitignore1
-rw-r--r--libraries/launcher/CMakeLists.txt15
-rw-r--r--libraries/launcher/net/minecraft/Launcher.java181
-rw-r--r--libraries/launcher/org/prismlauncher/EntryPoint.java160
-rw-r--r--libraries/launcher/org/prismlauncher/Launcher.java23
-rw-r--r--libraries/launcher/org/prismlauncher/applet/LegacyFrame.java163
-rw-r--r--libraries/launcher/org/prismlauncher/exception/ParameterNotFoundException.java45
-rw-r--r--libraries/launcher/org/prismlauncher/exception/ParseException.java47
-rw-r--r--libraries/launcher/org/prismlauncher/impl/OneSixLauncher.java190
-rw-r--r--libraries/launcher/org/prismlauncher/launcher/Launcher.java44
-rw-r--r--libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java110
-rw-r--r--libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java91
-rw-r--r--libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyFrame.java190
-rw-r--r--libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java126
-rw-r--r--libraries/launcher/org/prismlauncher/utils/Parameters.java84
-rw-r--r--libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java154
-rw-r--r--libraries/launcher/org/prismlauncher/utils/Utils.java49
-rw-r--r--libraries/launcher/org/prismlauncher/utils/logging/Level.java (renamed from libraries/launcher/org/prismlauncher/LauncherFactory.java)57
-rw-r--r--libraries/launcher/org/prismlauncher/utils/logging/Log.java104
m---------libraries/tomlplusplus0
73 files changed, 1901 insertions, 951 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3225cf1e..c0e5b50a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,6 +7,10 @@ on:
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
type: string
default: Debug
+ is_qt_cached:
+ description: Enable Qt caching or not
+ type: string
+ default: true
secrets:
SPARKLE_ED25519_KEY:
description: Private key for signing Sparkle updates
@@ -60,7 +64,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ''
- qt_version: '6.4.0'
+ qt_version: '6.4.1'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -102,14 +106,6 @@ jobs:
with:
submodules: 'true'
- - name: Initialize CodeQL
- if: runner.os == 'Linux' && matrix.qt_ver == 6
- uses: github/codeql-action/init@v2
- with:
- config-file: ./.github/codeql/codeql-config.yml
- queries: security-and-quality
- languages: cpp, java
-
- name: 'Setup MSYS2'
if: runner.os == 'Windows' && matrix.msystem != ''
uses: msys2/setup-msys2@v2
@@ -200,8 +196,7 @@ jobs:
arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
- cache: true
- cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
+ cache: ${{ inputs.is_qt_cached }}
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
@@ -293,14 +288,6 @@ jobs:
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
##
- # CODE SCAN
- ##
-
- - name: Perform CodeQL Analysis
- if: runner.os == 'Linux' && matrix.qt_ver == 6
- uses: github/codeql-action/analyze@v2
-
- ##
# PACKAGE BUILDS
##
@@ -500,21 +487,42 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout
+ if: inputs.build_type == 'Debug'
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Set short version
shell: bash
+ if: inputs.build_type == 'Debug'
run: |
ver_short=`git rev-parse --short HEAD`
echo "VERSION=$ver_short" >> $GITHUB_ENV
- name: Package Snap (Linux)
id: snapcraft
- if: runner.os == 'Linux' && matrix.qt_ver != 5
+ if: inputs.build_type == 'Debug'
uses: snapcore/action-build@v1
- name: Upload Snap (Linux)
- if: runner.os == 'Linux' && matrix.qt_ver != 5
+ if: inputs.build_type == 'Debug'
uses: actions/upload-artifact@v3
with:
name: prismlauncher_${{ env.VERSION }}_amd64.snap
path: ${{ steps.snapcraft.outputs.snap }}
+
+ flatpak:
+ runs-on: ubuntu-latest
+ container:
+ image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
+ options: --privileged
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ if: inputs.build_type == 'Debug'
+ with:
+ submodules: 'true'
+ - name: Build Flatpak (Linux)
+ if: inputs.build_type == 'Debug'
+ uses: flatpak/flatpak-github-actions/flatpak-builder@v4
+ with:
+ bundle: "Prism Launcher.flatpak"
+ manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
+ cache-key: flatpak-${{ github.sha }}-x86_64
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 00000000..0cd1f6e4
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,35 @@
+name: "CodeQL Code Scanning"
+
+on: [ push, pull_request, workflow_dispatch ]
+
+jobs:
+ CodeQL:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ with:
+ submodules: 'true'
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ config-file: ./.github/codeql/codeql-config.yml
+ queries: security-and-quality
+ languages: cpp, java
+
+ - name: Install Dependencies
+ run:
+ sudo apt-get -y update
+
+ sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
+
+ - name: Configure and Build
+ run: |
+ cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja
+
+ cmake --build build
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml
index 8adaa5e5..44751fbc 100644
--- a/.github/workflows/trigger_builds.yml
+++ b/.github/workflows/trigger_builds.yml
@@ -30,5 +30,6 @@ jobs:
uses: ./.github/workflows/build.yml
with:
build_type: Debug
+ is_qt_cached: true
secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index d415b2b1..8baa9693 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -12,6 +12,7 @@ jobs:
uses: ./.github/workflows/build.yml
with:
build_type: Release
+ is_qt_cached: false
secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0db05f98..8fc0d326 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -70,6 +70,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
+# Fix aarch64 build for toml++
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
+
# set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
diff --git a/README.md b/README.md
index f8ea2e84..f02b5695 100644
--- a/README.md
+++ b/README.md
@@ -29,19 +29,19 @@ Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS*
For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions:
-[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?style=flat-square&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?style=flat-square&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?style=flat-square&logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?style=flat-square&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
+[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?style=flat-square&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?style=flat-square&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?style=flat-square&logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?style=flat-square&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
## Community & Support
-Feel free to create a GitHub issue if you find a bug or want to suggest a new feature. We have multiple communities that can also help you.
+Feel free to create a GitHub issue if you find a bug or want to suggest a new feature. We have multiple community spaces where other community members can help you.
#### Join our Discord server:
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner2)](https://discord.gg/prismlauncher)
-#### Join our Matrix space (Will be opened at a later date):
+#### Join our Matrix space:
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&logo=matrix)](https://matrix.to/#/#prismlauncher:matrix.org)
-#### Join our SubReddit:
+#### Join our Subreddit:
[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/PrismLauncher/)
## Building
diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml
new file mode 100644
index 00000000..fca306d7
--- /dev/null
+++ b/flatpak/org.prismlauncher.PrismLauncher.yml
@@ -0,0 +1,83 @@
+id: org.prismlauncher.PrismLauncher
+runtime: org.kde.Platform
+runtime-version: "5.15-22.08"
+sdk: org.kde.Sdk
+sdk-extensions:
+ - org.freedesktop.Sdk.Extension.openjdk17
+ - org.freedesktop.Sdk.Extension.openjdk8
+add-extensions:
+ com.valvesoftware.Steam.Utility.gamescope:
+ version: stable
+ add-ld-path: lib
+ no-autodownload: true
+ autodelete: false
+ directory: utils/gamescope
+
+command: prismlauncher
+finish-args:
+ - --share=ipc
+ - --socket=x11
+ - --socket=wayland
+ - --device=all
+ - --share=network
+ - --socket=pulseaudio
+ # for Discord RPC mods
+ - --filesystem=xdg-run/app/com.discordapp.Discord:create
+ # Mod drag&drop
+ - --filesystem=xdg-download:ro
+
+modules:
+ - name: prismlauncher
+ buildsystem: cmake-ninja
+ config-opts:
+ - -DLauncher_BUILD_PLATFORM=flatpak
+ - -DCMAKE_BUILD_TYPE=Debug
+ build-options:
+ env:
+ JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
+ JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
+ sources:
+ - type: dir
+ path: ../
+ - name: openjdk
+ buildsystem: simple
+ build-commands:
+ - mkdir -p /app/jdk/
+ - /usr/lib/sdk/openjdk17/install.sh
+ - mv /app/jre /app/jdk/17
+ - /usr/lib/sdk/openjdk8/install.sh
+ - mv /app/jre /app/jdk/8
+ cleanup: [/jre]
+ - name: xrandr
+ buildsystem: autotools
+ sources:
+ - type: archive
+ url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
+ sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
+ cleanup: [/share/man, /bin/xkeystone]
+ - name: gamemode
+ buildsystem: meson
+ config-opts:
+ - -Dwith-sd-bus-provider=no-daemon
+ - -Dwith-examples=false
+ post-install:
+ # gamemoderun is installed for users who want to use wrapper commands
+ # post-install is running inside the build dir, we need it from the source though
+ - install -Dm755 ../data/gamemoderun -t /app/bin
+ sources:
+ - type: git
+ url: https://github.com/FeralInteractive/gamemode
+ tag: "1.7"
+ commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
+ - name: enhance
+ buildsystem: simple
+ build-commands:
+ - mkdir -p /app/utils/gamescope
+ - install -Dm755 prime-run /app/bin/prime-run
+ - mv /app/bin/prismlauncher /app/bin/prismrun
+ - install -Dm755 prismlauncher /app/bin/prismlauncher
+ sources:
+ - type: file
+ path: ../flatpak/prime-run
+ - type: file
+ path: ../flatpak/prismlauncher
diff --git a/flatpak/prime-run b/flatpak/prime-run
new file mode 100644
index 00000000..946c28dd
--- /dev/null
+++ b/flatpak/prime-run
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export __NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia
+exec "$@"
diff --git a/flatpak/prismlauncher b/flatpak/prismlauncher
new file mode 100644
index 00000000..bb876711
--- /dev/null
+++ b/flatpak/prismlauncher
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# discord RPC
+for i in {0..9}; do
+ test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i";
+done
+
+export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
+export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
+
+exec /app/bin/prismrun "$@"
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 537e3903..ea8d2326 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -233,7 +233,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
- setApplicationVersion(BuildConfig.printableVersionString());
+ setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime();
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 307240e0..a2a4f824 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -151,7 +151,7 @@ public:
void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log
- virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
+ virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level)
{
return level;
};
diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp
index 4ed82612..dc95e7ea 100644
--- a/launcher/BaseVersionList.cpp
+++ b/launcher/BaseVersionList.cpp
@@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
int BaseVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
- return count();
+ return parent.isValid() ? 0 : count();
}
int BaseVersionList::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QHash<int, QByteArray> BaseVersionList::roleNames() const
diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
index 032f21f9..6aba268d 100644
--- a/launcher/VersionProxyModel.cpp
+++ b/launcher/VersionProxyModel.cpp
@@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par
int VersionProxyModel::columnCount(const QModelIndex &parent) const
{
- return m_columns.size();
+ return parent.isValid() ? 0 : m_columns.size();
}
int VersionProxyModel::rowCount(const QModelIndex &parent) const
{
if(sourceModel())
{
- return sourceModel()->rowCount();
+ return sourceModel()->rowCount(parent);
}
return 0;
}
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index 3a223d1b..01043ad2 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction;
}
-bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
+bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
@@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
int IconList::rowCount(const QModelIndex &parent) const
{
- return icons.size();
+ return parent.isValid() ? 0 : icons.size();
}
void IconList::installIcons(const QStringList &iconFiles)
diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp
index eec1b329..242aad9f 100644
--- a/launcher/meta/Index.cpp
+++ b/launcher/meta/Index.cpp
@@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
}
int Index::rowCount(const QModelIndex &parent) const
{
- return m_lists.size();
+ return parent.isValid() ? 0 : m_lists.size();
}
int Index::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{
diff --git a/launcher/meta/JsonFormat.h b/launcher/meta/JsonFormat.h
index 93217b7e..63128a4e 100644
--- a/launcher/meta/JsonFormat.h
+++ b/launcher/meta/JsonFormat.h
@@ -60,11 +60,6 @@ struct Require
QString suggests;
};
-inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
-{
- return qHash(key.uid, seed);
-}
-
using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr);
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index de22b365..70d0b949 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -436,6 +437,17 @@ QStringList MinecraftInstance::javaArguments()
return args;
}
+QString MinecraftInstance::getLauncher()
+{
+ auto profile = m_components->getProfile();
+
+ // use legacy launcher if the traits are set
+ if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
+ return "legacy";
+
+ return "standard";
+}
+
QMap<QString, QString> MinecraftInstance::getVariables()
{
QMap<QString, QString> out;
@@ -627,26 +639,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "sessionId " + session->session + "\n";
}
- // libraries and class path.
- {
- QStringList jars, nativeJars;
- profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
- for(auto file: jars)
- {
- launchScript += "cp " + file + "\n";
- }
- for(auto file: nativeJars)
- {
- launchScript += "ext " + file + "\n";
- }
- launchScript += "natives " + getNativePath() + "\n";
- }
-
for (auto trait : profile->getTraits())
{
launchScript += "traits " + trait + "\n";
}
- launchScript += "launcher onesix\n";
+
+ launchScript += "launcher " + getLauncher() + "\n";
+
// qDebug() << "Generated launch script:" << launchScript;
return launchScript;
}
@@ -782,6 +781,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
}
out << "";
+ out << "Launcher: " + getLauncher();
+ out << "";
return out;
}
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index 1895d187..1bbd7b83 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -130,6 +131,7 @@ public:
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
/// get arguments passed to java
QStringList javaArguments();
+ QString getLauncher();
/// get variables for launch command variable substitution/environment
QMap<QString, QString> getVariables() override;
diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp
index 9bbb4ada..623dcdfa 100644
--- a/launcher/minecraft/MojangVersionFormat.cpp
+++ b/launcher/minecraft/MojangVersionFormat.cpp
@@ -135,7 +135,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
{
out.insert("artifact", downloadInfoToJson(libinfo->artifact));
}
- if(libinfo->classifiers.size())
+ if(!libinfo->classifiers.isEmpty())
{
QJsonObject classifiersOut;
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
@@ -297,7 +297,7 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
{
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
}
- if(in->mojangDownloads.size())
+ if(!in->mojangDownloads.isEmpty())
{
QJsonObject downloadsOut;
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
@@ -306,6 +306,15 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
}
out.insert("downloads", downloadsOut);
}
+ if(!in->compatibleJavaMajors.isEmpty())
+ {
+ QJsonArray compatibleJavaMajorsOut;
+ for(auto compatibleJavaMajor : in->compatibleJavaMajors)
+ {
+ compatibleJavaMajorsOut.append(compatibleJavaMajor);
+ }
+ out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
+ }
}
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
@@ -396,7 +405,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
iter++;
}
libRoot.insert("natives", nativeList);
- if (library->m_extractExcludes.size())
+ if (!library->m_extractExcludes.isEmpty())
{
QJsonArray excludes;
QJsonObject extract;
@@ -408,7 +417,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
libRoot.insert("extract", extract);
}
}
- if (library->m_rules.size())
+ if (!library->m_rules.isEmpty())
{
QJsonArray allRules;
for (auto &rule : library->m_rules)
diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp
index cec4a55b..280f6b26 100644
--- a/launcher/minecraft/OneSixVersionFormat.cpp
+++ b/launcher/minecraft/OneSixVersionFormat.cpp
@@ -63,13 +63,13 @@ LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, con
QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
{
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
- if (library->m_absoluteURL.size())
+ if (!library->m_absoluteURL.isEmpty())
libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL);
- if (library->m_hint.size())
+ if (!library->m_hint.isEmpty())
libRoot.insert("MMC-hint", library->m_hint);
- if (library->m_filename.size())
+ if (!library->m_filename.isEmpty())
libRoot.insert("MMC-filename", library->m_filename);
- if (library->m_displayname.size())
+ if (!library->m_displayname.isEmpty())
libRoot.insert("MMC-displayname", library->m_displayname);
return libRoot;
}
@@ -225,11 +225,10 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
{
QJsonObject agentObj = requireObject(agentVal);
auto lib = libraryFromJson(*out, agentObj, filename);
+
QString arg = "";
- if (agentObj.contains("argument"))
- {
- readString(agentObj, "argument", arg);
- }
+ readString(agentObj, "argument", arg);
+
AgentPtr agent(new Agent(lib, arg));
out->agents.append(agent);
}
@@ -332,6 +331,20 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
writeString(root, "appletClass", patch->appletClass);
writeStringList(root, "+tweakers", patch->addTweakers);
writeStringList(root, "+traits", patch->traits.values());
+ writeStringList(root, "+jvmArgs", patch->addnJvmArguments);
+ if (!patch->agents.isEmpty())
+ {
+ QJsonArray array;
+ for (auto value: patch->agents)
+ {
+ QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value->library().get());
+ if (!value->argument().isEmpty())
+ agentOut.insert("argument", value->argument());
+
+ array.append(agentOut);
+ }
+ root.insert("+agents", array);
+ }
if (!patch->libraries.isEmpty())
{
QJsonArray array;
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index 1618458f..6ce525eb 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -613,7 +613,7 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const
bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
{
- if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
+ if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()))
{
return false;
}
@@ -675,12 +675,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
int PackProfile::rowCount(const QModelIndex &parent) const
{
- return d->components.size();
+ return parent.isValid() ? 0 : d->components.size();
}
int PackProfile::columnCount(const QModelIndex &parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
void PackProfile::move(const int index, const MoveDirection direction)
diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h
index 236f9a87..846e8e42 100644
--- a/launcher/minecraft/Rule.h
+++ b/launcher/minecraft/Rule.h
@@ -104,7 +104,7 @@ public:
class ImplicitRule : public Rule
{
protected:
- virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
+ virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext)
{
return true;
}
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index aee7be35..ae29a972 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -173,7 +173,7 @@ bool WorldList::resetIcon(int row)
int WorldList::columnCount(const QModelIndex &parent) const
{
- return 4;
+ return parent.isValid()? 0 : 4;
}
QVariant WorldList::data(const QModelIndex &index, int role) const
@@ -398,8 +398,8 @@ void WorldList::installWorld(QFileInfo filename)
w.install(m_dir.absolutePath());
}
-bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
- const QModelIndex &parent)
+bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column,
+ [[maybe_unused]] const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h
index 5138e583..08294755 100644
--- a/launcher/minecraft/WorldList.h
+++ b/launcher/minecraft/WorldList.h
@@ -54,7 +54,7 @@ public:
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
{
- return size();
+ return parent.isValid() ? 0 : static_cast<int>(size());
};
virtual QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index b3b57c74..9e2fd111 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -408,20 +408,20 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
}
}
-int AccountList::rowCount(const QModelIndex &) const
+int AccountList::rowCount(const QModelIndex &parent) const
{
// Return count
- return count();
+ return parent.isValid() ? 0 : count();
}
-int AccountList::columnCount(const QModelIndex &) const
+int AccountList::columnCount(const QModelIndex &parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
{
- if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
+ if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
{
return Qt::NoItemFlags;
}
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 66e80f4a..4ccc5d4d 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -144,7 +144,7 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
int ModFolderModel::columnCount(const QModelIndex &parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ModFolderModel::createUpdateTask()
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index b2356309..0310c8f6 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -426,7 +426,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
int row = index.row();
- if (row < 0 || row >= rowCount(index) || !index.isValid())
+ if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
return false;
if (role == Qt::CheckStateRole)
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 25095a45..fe283b04 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -90,8 +90,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
- [[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
- [[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
+ [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
+ [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
@@ -176,7 +176,7 @@ class ResourceFolderModel : public QAbstractListModel {
* if the resource is complex and has more stuff to parse.
*/
virtual void onParseSucceeded(int ticket, QString resource_id);
- virtual void onParseFailed(int ticket, QString resource_id) {}
+ virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index f8a6c1cf..ebac707d 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -137,7 +137,7 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ResourcePackFolderModel::createUpdateTask()
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index a694e7b2..774f6114 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -121,7 +121,7 @@ ModDetails ReadMCModTOML(QByteArray contents)
return {};
}
auto modsTable = tomlModsTable0->as_table();
- if (!tomlModsTable0) {
+ if (!modsTable) {
qWarning() << "Corrupted mods.toml? [[mods]] was not a table!";
return {};
}
diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
index f0fbdc96..91554b58 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
@@ -393,9 +393,10 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
qWarning() << "Blocked mods found, displaying mod list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"),
- tr("The following mods were blocked on third party launchers.<br/>"
- "You will need to manually download them and add them to the modpack"),
- blocked_mods);
+ tr("The following files are not available for download in third party launchers.<br/>"
+ "You will need to manually download them and add them to the instance."),
+ blocked_mods);
+
message_dialog->setModal(true);
if (message_dialog->exec()) {
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
index 40aee82b..4c7b7a4f 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -211,18 +211,17 @@ void PackInstallTask::onResolveModsSucceeded()
qDebug() << "Blocked files found, displaying file list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"),
- tr("The following files are not available for download in third party launchers.<br/>"
- "You will need to manually download them and add them to the instance."),
- m_blocked_mods);
+ tr("The following files are not available for download in third party launchers.<br/>"
+ "You will need to manually download them and add them to the instance."),
+ m_blocked_mods);
if (message_dialog->exec() == QDialog::Accepted) {
qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
createInstance();
- }
- else {
+ } else {
abort();
}
-
+
} else {
createInstance();
}
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index e242dcf4..0d7ca769 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -47,7 +47,7 @@
auto MetaEntry::getFullPath() -> QString
{
// FIXME: make local?
- return FS::PathCombine(basePath, relativePath);
+ return FS::PathCombine(m_basePath, m_relativePath);
}
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
@@ -99,7 +99,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
return staleEntry(base, resource_path);
}
- if (!expected_etag.isEmpty() && expected_etag != entry->etag) {
+ if (!expected_etag.isEmpty() && expected_etag != entry->m_etag) {
// if the etag doesn't match expected, we disown the entry
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
@@ -107,17 +107,17 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
// if the file changed, check md5sum
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
- if (file_last_changed != entry->local_changed_timestamp) {
+ if (file_last_changed != entry->m_local_changed_timestamp) {
QFile input(real_path);
input.open(QIODevice::ReadOnly);
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
- if (entry->md5sum != md5sum) {
+ if (entry->m_md5sum != md5sum) {
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
}
// md5sums matched... keep entry and save the new state to file
- entry->local_changed_timestamp = file_last_changed;
+ entry->m_local_changed_timestamp = file_last_changed;
SaveEventually();
}
@@ -130,23 +130,23 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
}
// entry passed all the checks we cared about.
- entry->basePath = getBasePath(base);
+ entry->m_basePath = getBasePath(base);
return entry;
}
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
{
- if (!m_entries.contains(stale_entry->baseId)) {
- qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
+ if (!m_entries.contains(stale_entry->m_baseId)) {
+ qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
return false;
}
- if (stale_entry->stale) {
+ if (stale_entry->m_stale) {
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
return false;
}
- m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
+ m_entries[stale_entry->m_baseId].entry_list[stale_entry->m_relativePath] = stale_entry;
SaveEventually();
return true;
@@ -157,7 +157,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
if (!entry)
return false;
- entry->stale = true;
+ entry->m_stale = true;
SaveEventually();
return true;
}
@@ -169,7 +169,7 @@ void HttpMetaCache::evictAll()
qDebug() << "Evicting base" << base;
for (MetaEntryPtr entry : map.entry_list) {
if (!evictEntry(entry))
- qWarning() << "Unexpected missing cache entry" << entry->basePath;
+ qWarning() << "Unexpected missing cache entry" << entry->m_basePath;
}
}
}
@@ -177,10 +177,10 @@ void HttpMetaCache::evictAll()
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
{
auto foo = new MetaEntry();
- foo->baseId = base;
- foo->basePath = getBasePath(base);
- foo->relativePath = resource_path;
- foo->stale = true;
+ foo->m_baseId = base;
+ foo->m_basePath = getBasePath(base);
+ foo->m_relativePath = resource_path;
+ foo->m_stale = true;
return MetaEntryPtr(foo);
}
@@ -235,23 +235,23 @@ void HttpMetaCache::Load()
auto& entrymap = m_entries[base];
auto foo = new MetaEntry();
- foo->baseId = base;
- foo->relativePath = Json::ensureString(element_obj, "path");
- foo->md5sum = Json::ensureString(element_obj, "md5sum");
- foo->etag = Json::ensureString(element_obj, "etag");
- foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
- foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
+ foo->m_baseId = base;
+ foo->m_relativePath = Json::ensureString(element_obj, "path");
+ foo->m_md5sum = Json::ensureString(element_obj, "md5sum");
+ foo->m_etag = Json::ensureString(element_obj, "etag");
+ foo->m_local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
+ foo->m_remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false));
if (!foo->isEternal()) {
- foo->current_age = Json::ensureDouble(element_obj, "current_age");
- foo->max_age = Json::ensureDouble(element_obj, "max_age");
+ foo->m_current_age = Json::ensureDouble(element_obj, "current_age");
+ foo->m_max_age = Json::ensureDouble(element_obj, "max_age");
}
// presumed innocent until closer examination
- foo->stale = false;
+ foo->m_stale = false;
- entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
+ entrymap.entry_list[foo->m_relativePath] = MetaEntryPtr(foo);
}
}
@@ -276,23 +276,23 @@ void HttpMetaCache::SaveNow()
for (auto group : m_entries) {
for (auto entry : group.entry_list) {
// do not save stale entries. they are dead.
- if (entry->stale) {
+ if (entry->m_stale) {
continue;
}
QJsonObject entryObj;
- Json::writeString(entryObj, "base", entry->baseId);
- Json::writeString(entryObj, "path", entry->relativePath);
- Json::writeString(entryObj, "md5sum", entry->md5sum);
- Json::writeString(entryObj, "etag", entry->etag);
- entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
- if (!entry->remote_changed_timestamp.isEmpty())
- entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
+ Json::writeString(entryObj, "base", entry->m_baseId);
+ Json::writeString(entryObj, "path", entry->m_relativePath);
+ Json::writeString(entryObj, "md5sum", entry->m_md5sum);
+ Json::writeString(entryObj, "etag", entry->m_etag);
+ entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->m_local_changed_timestamp)));
+ if (!entry->m_remote_changed_timestamp.isEmpty())
+ entryObj.insert("remote_changed_timestamp", QJsonValue(entry->m_remote_changed_timestamp));
if (entry->isEternal()) {
entryObj.insert("eternal", true);
} else {
- entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
- entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
+ entryObj.insert("current_age", QJsonValue(double(entry->m_current_age)));
+ entryObj.insert("max_age", QJsonValue(double(entry->m_max_age)));
}
entriesArr.append(entryObj);
}
diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h
index 2a07d65a..37f4b49a 100644
--- a/launcher/net/HttpMetaCache.h
+++ b/launcher/net/HttpMetaCache.h
@@ -49,47 +49,47 @@ class MetaEntry {
MetaEntry() = default;
public:
- auto isStale() -> bool { return stale; }
- void setStale(bool stale) { this->stale = stale; }
+ auto isStale() -> bool { return m_stale; }
+ void setStale(bool stale) { m_stale = stale; }
auto getFullPath() -> QString;
- auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
- void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
- void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
+ auto getRemoteChangedTimestamp() -> QString { return m_remote_changed_timestamp; }
+ void setRemoteChangedTimestamp(QString remote_changed_timestamp) { m_remote_changed_timestamp = remote_changed_timestamp; }
+ void setLocalChangedTimestamp(qint64 timestamp) { m_local_changed_timestamp = timestamp; }
- auto getETag() -> QString { return etag; }
- void setETag(QString etag) { this->etag = etag; }
+ auto getETag() -> QString { return m_etag; }
+ void setETag(QString etag) { m_etag = etag; }
- auto getMD5Sum() -> QString { return md5sum; }
- void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
+ auto getMD5Sum() -> QString { return m_md5sum; }
+ void setMD5Sum(QString md5sum) { m_md5sum = md5sum; }
/* Whether the entry expires after some time (false) or not (true). */
- void makeEternal(bool eternal) { is_eternal = eternal; }
- [[nodiscard]] bool isEternal() const { return is_eternal; }
+ void makeEternal(bool eternal) { m_is_eternal = eternal; }
+ [[nodiscard]] bool isEternal() const { return m_is_eternal; }
- auto getCurrentAge() -> qint64 { return current_age; }
- void setCurrentAge(qint64 age) { current_age = age; }
+ auto getCurrentAge() -> qint64 { return m_current_age; }
+ void setCurrentAge(qint64 age) { m_current_age = age; }
- auto getMaximumAge() -> qint64 { return max_age; }
- void setMaximumAge(qint64 age) { max_age = age; }
+ auto getMaximumAge() -> qint64 { return m_max_age; }
+ void setMaximumAge(qint64 age) { m_max_age = age; }
- bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); };
+ bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); };
protected:
- QString baseId;
- QString basePath;
- QString relativePath;
- QString md5sum;
- QString etag;
-
- qint64 local_changed_timestamp = 0;
- QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
- qint64 current_age = 0;
- qint64 max_age = 0;
- bool is_eternal = false;
-
- bool stale = true;
+ QString m_baseId;
+ QString m_basePath;
+ QString m_relativePath;
+ QString m_md5sum;
+ QString m_etag;
+
+ qint64 m_local_changed_timestamp = 0;
+ QString m_remote_changed_timestamp; // QString for now, RFC 2822 encoded time
+ qint64 m_current_age = 0;
+ qint64 m_max_age = 0;
+ bool m_is_eternal = false;
+
+ bool m_stale = true;
};
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp
index 20aa6d04..38f48296 100644
--- a/launcher/translations/TranslationsModel.cpp
+++ b/launcher/translations/TranslationsModel.cpp
@@ -83,12 +83,21 @@ struct Language
else if(key == "es_UY") {
result = u8"español de Latinoamérica";
}
+ else if(key == "en_NZ") {
+ result = u8"New Zealand English"; // No idea why qt translates this to just english and not to New Zealand English
+ }
else if(key == "en@pirate") {
result = u8"Tongue of the High Seas";
}
else if(key == "en@uwu") {
result = u8"Cute Engwish";
}
+ else if(key == "tok") {
+ result = u8"toki pona";
+ }
+ else if(key == "nan") {
+ result = u8"閩南語"; // Using traditional Chinese script. Not sure if we should use simplified instead?
+ }
else {
result = locale.nativeLanguageName();
}
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 85b00b67..929f2a85 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -49,7 +49,7 @@
#include <QKeyEvent>
#include <QAction>
-
+#include <QActionGroup>
#include <QApplication>
#include <QButtonGroup>
#include <QHBoxLayout>
@@ -61,6 +61,7 @@
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
+#include <QFileDialog>
#include <QInputDialog>
#include <QLabel>
#include <QToolButton>
@@ -105,6 +106,7 @@
#include "ui/dialogs/UpdateDialog.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"
+#include "ui/themes/ITheme.h"
#include "UpdateController.h"
#include "KonamiCode.h"
@@ -253,6 +255,9 @@ public:
QMenu * helpMenu = nullptr;
TranslatedToolButton helpMenuButton;
TranslatedAction actionClearMetadata;
+ #ifdef Q_OS_MAC
+ TranslatedAction actionAddToPATH;
+ #endif
TranslatedAction actionReportBug;
TranslatedAction actionDISCORD;
TranslatedAction actionMATRIX;
@@ -264,6 +269,8 @@ public:
TranslatedAction actionLockToolbars;
+ TranslatedAction actionChangeTheme;
+
QVector<TranslatedToolButton *> all_toolbuttons;
QWidget *centralWidget = nullptr;
@@ -290,7 +297,6 @@ public:
actionAddInstance = TranslatedAction(MainWindow);
actionAddInstance->setObjectName(QStringLiteral("actionAddInstance"));
actionAddInstance->setIcon(APPLICATION->getThemedIcon("new"));
- actionAddInstance->setIconVisibleInMenu(false);
actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instanc&e..."));
actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance."));
actionAddInstance->setShortcut(QKeySequence::New);
@@ -350,6 +356,14 @@ public:
actionClearMetadata.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Clear cached metadata"));
all_actions.append(&actionClearMetadata);
+ #ifdef Q_OS_MAC
+ actionAddToPATH = TranslatedAction(MainWindow);
+ actionAddToPATH->setObjectName(QStringLiteral("actionAddToPATH"));
+ actionAddToPATH.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Install to &PATH"));
+ actionAddToPATH.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Install a prismlauncher symlink to /usr/local/bin"));
+ all_actions.append(&actionAddToPATH);
+ #endif
+
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
actionReportBug = TranslatedAction(MainWindow);
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
@@ -428,6 +442,11 @@ public:
actionLockToolbars.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Lock Toolbars"));
actionLockToolbars->setCheckable(true);
all_actions.append(&actionLockToolbars);
+
+ actionChangeTheme = TranslatedAction(MainWindow);
+ actionChangeTheme->setObjectName(QStringLiteral("actionChangeTheme"));
+ actionChangeTheme.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Themes"));
+ all_actions.append(&actionChangeTheme);
}
void createMainToolbar(QMainWindow *MainWindow)
@@ -455,6 +474,10 @@ public:
helpMenu->addAction(actionClearMetadata);
+ #ifdef Q_OS_MAC
+ helpMenu->addAction(actionAddToPATH);
+ #endif
+
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
helpMenu->addAction(actionReportBug);
}
@@ -509,8 +532,6 @@ public:
fileMenu->setSeparatorsCollapsible(false);
fileMenu->addAction(actionAddInstance);
fileMenu->addAction(actionLaunchInstance);
- fileMenu->addAction(actionLaunchInstanceOffline);
- fileMenu->addAction(actionLaunchInstanceDemo);
fileMenu->addAction(actionKillInstance);
fileMenu->addAction(actionCloseWindow);
fileMenu->addSeparator();
@@ -528,6 +549,8 @@ public:
viewMenu = menuBar->addMenu(tr("&View"));
viewMenu->setSeparatorsCollapsible(false);
+ viewMenu->addAction(actionChangeTheme);
+ viewMenu->addSeparator();
viewMenu->addAction(actionCAT);
viewMenu->addSeparator();
@@ -542,6 +565,9 @@ public:
helpMenu = menuBar->addMenu(tr("&Help"));
helpMenu->setSeparatorsCollapsible(false);
helpMenu->addAction(actionClearMetadata);
+ #ifdef Q_OS_MAC
+ helpMenu->addAction(actionAddToPATH);
+ #endif
helpMenu->addSeparator();
helpMenu->addAction(actionAbout);
helpMenu->addAction(actionOpenWiki);
@@ -555,10 +581,11 @@ public:
helpMenu->addAction(actionDISCORD);
if (!BuildConfig.SUBREDDIT_URL.isEmpty())
helpMenu->addAction(actionREDDIT);
- helpMenu->addSeparator();
if(BuildConfig.UPDATER_ENABLED)
+ {
+ helpMenu->addSeparator();
helpMenu->addAction(actionCheckUpdate);
-
+ }
MainWindow->setMenuBar(menuBar);
}
@@ -576,6 +603,7 @@ public:
actionOpenWiki->setObjectName(QStringLiteral("actionOpenWiki"));
actionOpenWiki.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &Help"));
actionOpenWiki.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki"));
+ actionOpenWiki->setIcon(APPLICATION->getThemedIcon("help"));
connect(actionOpenWiki, &QAction::triggered, MainWindow, &MainWindow::on_actionOpenWiki_triggered);
all_actions.append(&actionOpenWiki);
@@ -583,6 +611,7 @@ public:
actionNewsMenuBar->setObjectName(QStringLiteral("actionNewsMenuBar"));
actionNewsMenuBar.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &News"));
actionNewsMenuBar.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki"));
+ actionNewsMenuBar->setIcon(APPLICATION->getThemedIcon("news"));
connect(actionNewsMenuBar, &QAction::triggered, MainWindow, &MainWindow::on_actionMoreNews_triggered);
all_actions.append(&actionNewsMenuBar);
}
@@ -822,6 +851,7 @@ public:
createInstanceToolbar(MainWindow);
MainWindow->updateToolsMenu();
+ MainWindow->updateThemeMenu();
retranslateUi(MainWindow);
@@ -1271,6 +1301,38 @@ void MainWindow::updateToolsMenu()
ui->actionLaunchInstance->setMenu(launchMenu);
}
+void MainWindow::updateThemeMenu()
+{
+ QMenu *themeMenu = ui->actionChangeTheme->menu();
+
+ if (themeMenu) {
+ themeMenu->clear();
+ } else {
+ themeMenu = new QMenu(this);
+ }
+
+ auto themes = APPLICATION->getValidApplicationThemes();
+
+ QActionGroup* themesGroup = new QActionGroup( this );
+
+ for (auto* theme : themes) {
+ QAction * themeAction = themeMenu->addAction(theme->name());
+
+ themeAction->setCheckable(true);
+ if (APPLICATION->settings()->get("ApplicationTheme").toString() == theme->id()) {
+ themeAction->setChecked(true);
+ }
+ themeAction->setActionGroup(themesGroup);
+
+ connect(themeAction, &QAction::triggered, [theme]() {
+ APPLICATION->setApplicationTheme(theme->id(),false);
+ APPLICATION->settings()->set("ApplicationTheme", theme->id());
+ });
+ }
+
+ ui->actionChangeTheme->setMenu(themeMenu);
+}
+
void MainWindow::repopulateAccountsMenu()
{
accountMenu->clear();
@@ -1901,6 +1963,7 @@ void MainWindow::globalSettingsClosed()
proxymodel->sort(0);
updateMainToolBar();
updateToolsMenu();
+ updateThemeMenu();
updateStatusCenter();
// This needs to be done to prevent UI elements disappearing in the event the config is changed
// but Prism Launcher exits abnormally, causing the window state to never be saved:
@@ -1929,6 +1992,29 @@ void MainWindow::on_actionClearMetadata_triggered()
APPLICATION->metacache()->SaveNow();
}
+#ifdef Q_OS_MAC
+void MainWindow::on_actionAddToPATH_triggered()
+{
+ auto binaryPath = APPLICATION->applicationFilePath();
+ auto targetPath = QString("/usr/local/bin/%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
+ qDebug() << "Symlinking" << binaryPath << "to" << targetPath;
+
+ QStringList args;
+ args << "-e";
+ args << QString("do shell script \"mkdir -p /usr/local/bin && ln -sf '%1' '%2'\" with administrator privileges")
+ .arg(binaryPath, targetPath);
+ auto outcome = QProcess::execute("/usr/bin/osascript", args);
+ if (!outcome) {
+ QMessageBox::information(this, tr("Successfully added %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME),
+ tr("%1 was successfully added to your PATH. You can now start it by running `%2`.")
+ .arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.LAUNCHER_APP_BINARY_NAME));
+ } else {
+ QMessageBox::critical(this, tr("Failed to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME),
+ tr("An error occurred while trying to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME));
+ }
+}
+#endif
+
void MainWindow::on_actionOpenWiki_triggered()
{
DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("")));
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index f9d1f1c7..0aa01ee2 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -128,6 +128,10 @@ private slots:
void on_actionClearMetadata_triggered();
+ #ifdef Q_OS_MAC
+ void on_actionAddToPATH_triggered();
+ #endif
+
void on_actionOpenWiki_triggered();
void on_actionMoreNews_triggered();
@@ -170,6 +174,8 @@ private slots:
void updateToolsMenu();
+ void updateThemeMenu();
+
void instanceActivated(QModelIndex);
void instanceChanged(const QModelIndex &current, const QModelIndex &previous);
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 2cf94250..edb4ff7d 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -6,28 +6,47 @@
#include "ui_BlockedModsDialog.h"
#include <QDebug>
+#include <QDragEnterEvent>
+#include <QFileDialog>
+#include <QFileInfo>
#include <QStandardPaths>
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
- : QDialog(parent), ui(new Ui::BlockedModsDialog), mods(mods)
+ : QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods)
{
+ m_hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10));
+ connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished);
+
ui->setupUi(this);
auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole);
connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll);
- connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &BlockedModsDialog::directoryChanged);
+ auto downloadFolderButton = ui->buttonBox->addButton(tr("Add Download Folder"), QDialogButtonBox::ActionRole);
+ connect(downloadFolderButton, &QPushButton::clicked, this, &BlockedModsDialog::addDownloadFolder);
- hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10));
+ connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &BlockedModsDialog::directoryChanged);
- qDebug() << "Mods List: " << mods;
+ qDebug() << "[Blocked Mods Dialog] Mods List: " << mods;
setupWatch();
scanPaths();
this->setWindowTitle(title);
- ui->label->setText(text);
- ui->labelModsFound->setText(tr("Please download the missing mods."));
+ ui->labelDescription->setText(text);
+ ui->labelExplain->setText(
+ QString(tr("Your configured global mods folder and default downloads folder "
+ "are automatically checked for the downloaded mods and they will be copied to the instance if found.<br/>"
+ "Optionally, you may drag and drop the downloaded mods onto this dialog or add a folder to watch "
+ "if you did not download the mods to a default location."))
+ .arg(APPLICATION->settings()->get("CentralModsDir").toString(),
+ QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)));
+
+ // force all URL handeling as external
+ connect(ui->textBrowserWatched, &QTextBrowser::anchorClicked, this, [](const QUrl url) { QDesktopServices::openUrl(url); });
+
+ setAcceptDrops(true);
+
update();
}
@@ -36,20 +55,55 @@ BlockedModsDialog::~BlockedModsDialog()
delete ui;
}
+void BlockedModsDialog::dragEnterEvent(QDragEnterEvent* e)
+{
+ if (e->mimeData()->hasUrls()) {
+ e->acceptProposedAction();
+ }
+}
+
+void BlockedModsDialog::dropEvent(QDropEvent* e)
+{
+ for (const QUrl& url : e->mimeData()->urls()) {
+ QString filePath = url.toLocalFile();
+ qDebug() << "[Blocked Mods Dialog] Dropped file:" << filePath;
+ addHashTask(filePath);
+
+ // watch for changes
+ QFileInfo file = QFileInfo(filePath);
+ QString path = file.dir().absolutePath();
+ qDebug() << "[Blocked Mods Dialog] Adding watch path:" << path;
+ m_watcher.addPath(path);
+ }
+ scanPaths();
+ update();
+}
+
void BlockedModsDialog::openAll()
{
- for (auto& mod : mods) {
+ for (auto& mod : m_mods) {
QDesktopServices::openUrl(mod.websiteUrl);
}
}
+void BlockedModsDialog::addDownloadFolder()
+{
+ QString dir =
+ QFileDialog::getExistingDirectory(this, tr("Select directory where you downloaded the mods"),
+ QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), QFileDialog::ShowDirsOnly);
+ qDebug() << "[Blocked Mods Dialog] Adding watch path:" << dir;
+ m_watcher.addPath(dir);
+ scanPath(dir, true);
+ update();
+}
+
/// @brief update UI with current status of the blocked mod detection
void BlockedModsDialog::update()
{
QString text;
QString span;
- for (auto& mod : mods) {
+ for (auto& mod : m_mods) {
if (mod.matched) {
// &#x2714; -> html for HEAVY CHECK MARK : ✔
span = QString(tr("<span style=\"color:green\"> &#x2714; Found at %1 </span>")).arg(mod.localPath);
@@ -60,10 +114,17 @@ void BlockedModsDialog::update()
text += QString(tr("%1: <a href='%2'>%2</a> <p>Hash: %3 %4</p> <br/>")).arg(mod.name, mod.websiteUrl, mod.hash, span);
}
- ui->textBrowser->setText(text);
+ ui->textBrowserModsListing->setText(text);
+
+ QString watching;
+ for (auto& dir : m_watcher.directories()) {
+ watching += QString("<a href=\"%1\">%1</a><br/>").arg(dir);
+ }
+
+ ui->textBrowserWatched->setText(watching);
if (allModsMatched()) {
- ui->labelModsFound->setText(tr("All mods found ✔"));
+ ui->labelModsFound->setText("<span style=\"color:green\">✔</span>" + tr("All mods found"));
} else {
ui->labelModsFound->setText(tr("Please download the missing mods."));
}
@@ -73,8 +134,9 @@ void BlockedModsDialog::update()
/// @param path the path to the changed directory
void BlockedModsDialog::directoryChanged(QString path)
{
- qDebug() << "Directory changed: " << path;
- scanPath(path);
+ qDebug() << "[Blocked Mods Dialog] Directory changed: " << path;
+ validateMatchedMods();
+ scanPath(path, true);
}
/// @brief add the user downloads folder and the global mods folder to the filesystem watcher
@@ -82,22 +144,23 @@ void BlockedModsDialog::setupWatch()
{
const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString();
- watcher.addPath(downloadsFolder);
- watcher.addPath(modsFolder);
+ m_watcher.addPath(downloadsFolder);
+ m_watcher.addPath(modsFolder);
}
/// @brief scan all watched folder
void BlockedModsDialog::scanPaths()
{
- for (auto& dir : watcher.directories()) {
- scanPath(dir);
+ for (auto& dir : m_watcher.directories()) {
+ scanPath(dir, false);
}
+ runHashTask();
}
/// @brief Scan the directory at path, skip paths that do not contain a file name
/// of a blocked mod we are looking for
/// @param path the directory to scan
-void BlockedModsDialog::scanPath(QString path)
+void BlockedModsDialog::scanPath(QString path, bool start_task)
{
QDir scan_dir(path);
QDirIterator scan_it(path, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::NoIteratorFlags);
@@ -108,17 +171,35 @@ void BlockedModsDialog::scanPath(QString path)
continue;
}
- auto hash_task = Hashing::createBlockedModHasher(file, ModPlatform::Provider::FLAME, "sha1");
+ addHashTask(file);
+ }
- qDebug() << "Creating Hash task for path: " << file;
+ if (start_task) {
+ runHashTask();
+ }
+}
- connect(hash_task.get(), &Task::succeeded, [this, hash_task, file] { checkMatchHash(hash_task->getResult(), file); });
- connect(hash_task.get(), &Task::failed, [file] { qDebug() << "Failed to hash path: " << file; });
+/// @brief add a hashing task for the file located at path, add the path to the pending set if the hasing task is already running
+/// @param path the path to the local file being hashed
+void BlockedModsDialog::addHashTask(QString path)
+{
+ qDebug() << "[Blocked Mods Dialog] adding a Hash task for" << path << "to the pending set.";
+ m_pending_hash_paths.insert(path);
+}
- hashing_task->addTask(hash_task);
- }
+/// @brief add a hashing task for the file located at path and connect it to check that hash against
+/// our blocked mods list
+/// @param path the path to the local file being hashed
+void BlockedModsDialog::buildHashTask(QString path)
+{
+ auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::Provider::FLAME, "sha1");
+
+ qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path;
+
+ connect(hash_task.get(), &Task::succeeded, this, [this, hash_task, path] { checkMatchHash(hash_task->getResult(), path); });
+ connect(hash_task.get(), &Task::failed, this, [path] { qDebug() << "Failed to hash path: " << path; });
- hashing_task->start();
+ m_hashing_task->addTask(hash_task);
}
/// @brief check if the computed hash for the provided path matches a blocked
@@ -129,9 +210,9 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path)
{
bool match = false;
- qDebug() << "Checking for match on hash: " << hash << "| From path:" << path;
+ qDebug() << "[Blocked Mods Dialog] Checking for match on hash: " << hash << "| From path:" << path;
- for (auto& mod : mods) {
+ for (auto& mod : m_mods) {
if (mod.matched) {
continue;
}
@@ -140,7 +221,7 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path)
mod.localPath = path;
match = true;
- qDebug() << "Hash match found:" << mod.name << hash << "| From path:" << path;
+ qDebug() << "[Blocked Mods Dialog] Hash match found:" << mod.name << hash << "| From path:" << path;
break;
}
@@ -159,9 +240,9 @@ bool BlockedModsDialog::checkValidPath(QString path)
QFileInfo file = QFileInfo(path);
QString filename = file.fileName();
- for (auto& mod : mods) {
+ for (auto& mod : m_mods) {
if (mod.name.compare(filename, Qt::CaseInsensitive) == 0) {
- qDebug() << "Name match found:" << mod.name << "| From path:" << path;
+ qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
return true;
}
}
@@ -171,7 +252,61 @@ bool BlockedModsDialog::checkValidPath(QString path)
bool BlockedModsDialog::allModsMatched()
{
- return std::all_of(mods.begin(), mods.end(), [](auto const& mod) { return mod.matched; });
+ return std::all_of(m_mods.begin(), m_mods.end(), [](auto const& mod) { return mod.matched; });
+}
+
+/// @brief ensure matched file paths still exist
+void BlockedModsDialog::validateMatchedMods()
+{
+ bool changed = false;
+ for (auto& mod : m_mods) {
+ if (mod.matched) {
+ QFileInfo file = QFileInfo(mod.localPath);
+ if (!file.exists() || !file.isFile()) {
+ qDebug() << "[Blocked Mods Dialog] File" << mod.localPath << "for mod" << mod.name
+ << "has vanshed! marking as not matched.";
+ mod.localPath = "";
+ mod.matched = false;
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ update();
+ }
+}
+
+/// @brief run hash task or mark a pending run if it is already runing
+void BlockedModsDialog::runHashTask()
+{
+ if (!m_hashing_task->isRunning()) {
+ m_rehash_pending = false;
+
+ if (!m_pending_hash_paths.isEmpty()) {
+ qDebug() << "[Blocked Mods Dialog] there are pending hash tasks, building and running tasks";
+
+ auto path = m_pending_hash_paths.begin();
+ while (path != m_pending_hash_paths.end()) {
+ buildHashTask(*path);
+ path = m_pending_hash_paths.erase(path);
+ }
+
+ m_hashing_task->start();
+ }
+ } else {
+ qDebug() << "[Blocked Mods Dialog] queueing another run of the hashing task";
+ qDebug() << "[Blocked Mods Dialog] pending hash tasks:" << m_pending_hash_paths;
+ m_rehash_pending = true;
+ }
+}
+
+void BlockedModsDialog::hashTaskFinished()
+{
+ qDebug() << "[Blocked Mods Dialog] All hash tasks finished";
+ if (m_rehash_pending) {
+ qDebug() << "[Blocked Mods Dialog] task finished with a rehash pending, rerunning";
+ runHashTask();
+ }
}
/// qDebug print support for the BlockedMod struct
diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h
index 0a5c90db..dac43cba 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.h
+++ b/launcher/ui/dialogs/BlockedModsDialog.h
@@ -31,20 +31,31 @@ public:
~BlockedModsDialog() override;
+protected:
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void dropEvent(QDropEvent *event) override;
private:
Ui::BlockedModsDialog *ui;
- QList<BlockedMod> &mods;
- QFileSystemWatcher watcher;
- shared_qobject_ptr<ConcurrentTask> hashing_task;
+ QList<BlockedMod> &m_mods;
+ QFileSystemWatcher m_watcher;
+ shared_qobject_ptr<ConcurrentTask> m_hashing_task;
+ QSet<QString> m_pending_hash_paths;
+ bool m_rehash_pending;
void openAll();
+ void addDownloadFolder();
void update();
void directoryChanged(QString path);
void setupWatch();
void scanPaths();
- void scanPath(QString path);
+ void scanPath(QString path, bool start_task);
+ void addHashTask(QString path);
+ void buildHashTask(QString path);
void checkMatchHash(QString hash, QString path);
+ void validateMatchedMods();
+ void runHashTask();
+ void hashTaskFinished();
bool checkValidPath(QString path);
bool allModsMatched();
diff --git a/launcher/ui/dialogs/BlockedModsDialog.ui b/launcher/ui/dialogs/BlockedModsDialog.ui
index 371549cf..88105178 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.ui
+++ b/launcher/ui/dialogs/BlockedModsDialog.ui
@@ -15,17 +15,39 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="labelDescription">
<property name="text">
<string notr="true"/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
</widget>
</item>
<item>
- <widget class="QTextBrowser" name="textBrowser">
+ <widget class="QLabel" name="labelExplain">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="textBrowserModsListing">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>165</height>
+ </size>
+ </property>
<property name="acceptRichText">
<bool>true</bool>
</property>
@@ -35,6 +57,47 @@
</widget>
</item>
<item>
+ <widget class="QLabel" name="labelWatched">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Watched Folders:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="textBrowserWatched">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>12</height>
+ </size>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="bottomBoxH">
<item>
<widget class="QLabel" name="labelModsFound">
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index 76f8ec18..33a03336 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -27,11 +27,7 @@
<item row="4" column="1" colspan="3">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
- <widget class="QLineEdit" name="filterEdit">
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
+ <widget class="QLineEdit" name="filterEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="filterLabel">
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index 5e8bd7cc..d64bcb76 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -400,11 +400,11 @@ public:
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
- return m_servers.size();
+ return parent.isValid() ? 0 : m_servers.size();
}
int columnCount(const QModelIndex & parent) const override
{
- return COLUMN_COUNT;
+ return parent.isValid() ? 0 : COLUMN_COUNT;
}
Server * at(int index)
diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui
index fcba5598..14b7cd9f 100644
--- a/launcher/ui/pages/instance/VersionPage.ui
+++ b/launcher/ui/pages/instance/VersionPage.ui
@@ -48,11 +48,7 @@
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
- <widget class="QLineEdit" name="filterEdit">
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
+ <widget class="QLineEdit" name="filterEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="filterLabel">
diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h
index d2636d87..36840649 100644
--- a/launcher/ui/pages/modplatform/ModModel.h
+++ b/launcher/ui/pages/modplatform/ModModel.h
@@ -20,8 +20,8 @@ class ListModel : public QAbstractListModel {
ListModel(ModPage* parent);
~ListModel() override;
- inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
- inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
+ inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); };
+ inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; };
inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
auto debugName() const -> QString;
@@ -41,12 +41,12 @@ class ListModel : public QAbstractListModel {
void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index);
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
- virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {};
+ virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0;
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
- inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
+ inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; };
public slots:
void searchRequestFinished(QJsonDocument& doc);
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 234f9f36..677bc4d6 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -262,7 +262,7 @@ void ModPage::openUrl(const QUrl& url)
const QString address = url.host() + url.path();
QRegularExpressionMatch match;
- const char* page;
+ QString page;
match = modrinth.match(address);
if (match.hasMatch())
@@ -276,7 +276,7 @@ void ModPage::openUrl(const QUrl& url)
page = "curseforge";
}
- if (match.hasMatch()) {
+ if (!page.isNull()) {
const QString slug = match.captured(1);
// ensure the user isn't opening the same mod
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index ef9a9268..2ce04068 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -32,12 +32,12 @@ ListModel::~ListModel()
int ListModel::rowCount(const QModelIndex &parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index 9138dcbb..cdb4532c 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -75,12 +75,12 @@ QVector<QString> AtlOptionalModListModel::getResult() {
}
int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const {
- return m_mods.size();
+ return parent.isValid() ? 0 : m_mods.size();
}
int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const {
// Enabled, Name, Description
- return 3;
+ return parent.isValid() ? 0 : 3;
}
QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const {
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index debae8c3..127c3de5 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -15,12 +15,12 @@ ListModel::~ListModel() {}
int ListModel::rowCount(const QModelIndex& parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex& parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex& index, int role) const
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
index 3a149944..ce2b2b18 100644
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
+++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
@@ -34,12 +34,12 @@ ListModel::~ListModel()
int ListModel::rowCount(const QModelIndex &parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 6f11cc95..6b1f6b89 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -125,12 +125,12 @@ QString ListModel::translatePackType(PackType type) const
int ListModel::rowCount(const QModelIndex &parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 6f33e11e..3be377a1 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -55,8 +55,8 @@ class ModpackListModel : public QAbstractListModel {
ModpackListModel(ModrinthPage* parent);
~ModpackListModel() override = default;
- inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
- inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
+ inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); };
+ inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; };
inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
auto debugName() const -> QString;
@@ -74,7 +74,7 @@ class ModpackListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
- inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
+ inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; };
public slots:
void searchRequestFinished(QJsonDocument& doc_all);
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index 742f4f2a..b2af1ac0 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -80,14 +80,14 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
return QVariant();
}
-int Technic::ListModel::columnCount(const QModelIndex&) const
+int Technic::ListModel::columnCount(const QModelIndex& parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
-int Technic::ListModel::rowCount(const QModelIndex&) const
+int Technic::ListModel::rowCount(const QModelIndex& parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
void Technic::ListModel::searchWithTerm(const QString& term)
diff --git a/libraries/README.md b/libraries/README.md
index dc38477b..ac5a3618 100644
--- a/libraries/README.md
+++ b/libraries/README.md
@@ -42,19 +42,20 @@ MIT licensed.
Java launcher part for Minecraft.
-It:
+It does the following:
-- Starts a process
-- Waits for a launch script on stdin
-- Consumes the launch script you feed it
-- Proceeds with launch when it gets the `launcher` command
+- Waits for a launch script on stdin.
+- Consumes the launch script you feed it.
+- Proceeds with launch when it gets the `launcher` command.
+
+If "abort" is sent, the process will exit.
This means the process is essentially idle until the final command is sent. You can, for example, attach a profiler before you send it.
-A `legacy` and `onesix` launchers are available.
+The `standard` and `legacy` launchers are available.
+- `standard` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title).
- `legacy` is intended for use with Minecraft versions < 1.6 and is deprecated.
-- `onesix` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title).
Example (some parts have been censored):
@@ -64,7 +65,7 @@ mainClass net.minecraft.launchwrapper.Launch
param --username
param CENSORED
param --version
-param MultiMC5
+param Prism Launcher
param --gameDir
param /home/peterix/minecraft/FTB/17ForgeTest/minecraft
param --assetsDir
@@ -81,58 +82,11 @@ param --userType
param mojang
param --tweakClass
param cpw.mods.fml.common.launcher.FMLTweaker
-windowTitle MultiMC: 172ForgeTest
+windowTitle Prism Launcher: 172ForgeTest
windowParams 854x480
userName CENSORED
sessionId token:CENSORED:CENSORED
-cp /home/peterix/minecraft/FTB/libraries/com/mojang/realms/1.3.5/realms-1.3.5.jar
-cp /home/peterix/minecraft/FTB/libraries/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar
-cp /home/peterix/minecraft/FTB/libraries/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar
-cp /home/peterix/minecraft/FTB/libraries/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar
-cp /home/peterix/minecraft/FTB/libraries/java3d/vecmath/1.3.1/vecmath-1.3.1.jar
-cp /home/peterix/minecraft/FTB/libraries/net/sf/trove4j/trove4j/3.0.3/trove4j-3.0.3.jar
-cp /home/peterix/minecraft/FTB/libraries/com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar
-cp /home/peterix/minecraft/FTB/libraries/net/sf/jopt-simple/jopt-simple/4.5/jopt-simple-4.5.jar
-cp /home/peterix/minecraft/FTB/libraries/com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar
-cp /home/peterix/minecraft/FTB/libraries/com/paulscode/codecwav/20101023/codecwav-20101023.jar
-cp /home/peterix/minecraft/FTB/libraries/com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar
-cp /home/peterix/minecraft/FTB/libraries/com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar
-cp /home/peterix/minecraft/FTB/libraries/com/paulscode/soundsystem/20120107/soundsystem-20120107.jar
-cp /home/peterix/minecraft/FTB/libraries/io/netty/netty-all/4.0.10.Final/netty-all-4.0.10.Final.jar
-cp /home/peterix/minecraft/FTB/libraries/com/google/guava/guava/16.0/guava-16.0.jar
-cp /home/peterix/minecraft/FTB/libraries/org/apache/commons/commons-lang3/3.2.1/commons-lang3-3.2.1.jar
-cp /home/peterix/minecraft/FTB/libraries/commons-io/commons-io/2.4/commons-io-2.4.jar
-cp /home/peterix/minecraft/FTB/libraries/commons-codec/commons-codec/1.9/commons-codec-1.9.jar
-cp /home/peterix/minecraft/FTB/libraries/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar
-cp /home/peterix/minecraft/FTB/libraries/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar
-cp /home/peterix/minecraft/FTB/libraries/com/google/code/gson/gson/2.2.4/gson-2.2.4.jar
-cp /home/peterix/minecraft/FTB/libraries/com/mojang/authlib/1.5.16/authlib-1.5.16.jar
-cp /home/peterix/minecraft/FTB/libraries/org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar
-cp /home/peterix/minecraft/FTB/libraries/org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar
-cp /home/peterix/minecraft/FTB/libraries/org/lwjgl/lwjgl/lwjgl/2.9.1/lwjgl-2.9.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/lwjgl/lwjgl/lwjgl_util/2.9.1/lwjgl_util-2.9.1.jar
-cp /home/peterix/minecraft/FTB/libraries/tv/twitch/twitch/5.16/twitch-5.16.jar
-cp /home/peterix/minecraft/FTB/libraries/net/minecraftforge/forge/1.7.10-10.13.0.1178/forge-1.7.10-10.13.0.1178.jar
-cp /home/peterix/minecraft/FTB/libraries/net/minecraft/launchwrapper/1.9/launchwrapper-1.9.jar
-cp /home/peterix/minecraft/FTB/libraries/org/ow2/asm/asm-all/4.1/asm-all-4.1.jar
-cp /home/peterix/minecraft/FTB/libraries/com/typesafe/akka/akka-actor_2.11/2.3.3/akka-actor_2.11-2.3.3.jar
-cp /home/peterix/minecraft/FTB/libraries/com/typesafe/config/1.2.1/config-1.2.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-actors-migration_2.11/1.1.0/scala-actors-migration_2.11-1.1.0.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-compiler/2.11.1/scala-compiler-2.11.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/plugins/scala-continuations-library_2.11/1.0.2/scala-continuations-library_2.11-1.0.2.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/plugins/scala-continuations-plugin_2.11.1/1.0.2/scala-continuations-plugin_2.11.1-1.0.2.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-library/2.11.1/scala-library-2.11.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-parser-combinators_2.11/1.0.1/scala-parser-combinators_2.11-1.0.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-reflect/2.11.1/scala-reflect-2.11.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-swing_2.11/1.0.1/scala-swing_2.11-1.0.1.jar
-cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar
-cp /home/peterix/minecraft/FTB/libraries/lzma/lzma/0.0.1/lzma-0.0.1.jar
-ext /home/peterix/minecraft/FTB/libraries/org/lwjgl/lwjgl/lwjgl-platform/2.9.1/lwjgl-platform-2.9.1-natives-linux.jar
-ext /home/peterix/minecraft/FTB/libraries/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar
-natives /home/peterix/minecraft/FTB/17ForgeTest/natives
-cp /home/peterix/minecraft/FTB/versions/1.7.10/1.7.10.jar
-launcher onesix
+launcher standard
```
Available under `GPL-3.0-only` (with classpath exception), sublicensed from its original `Apache-2.0` codebase
diff --git a/libraries/launcher/.gitignore b/libraries/launcher/.gitignore
index cc1c52bf..dda456e3 100644
--- a/libraries/launcher/.gitignore
+++ b/libraries/launcher/.gitignore
@@ -4,3 +4,4 @@ out
.classpath
.idea
.project
+bin/
diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt
index df25414f..55ed5875 100644
--- a/libraries/launcher/CMakeLists.txt
+++ b/libraries/launcher/CMakeLists.txt
@@ -4,18 +4,21 @@ find_package(Java 1.7 REQUIRED COMPONENTS Development)
include(UseJava)
set(CMAKE_JAVA_JAR_ENTRY_POINT org.prismlauncher.EntryPoint)
-set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked)
+set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7)
set(SRC
org/prismlauncher/EntryPoint.java
- org/prismlauncher/Launcher.java
- org/prismlauncher/LauncherFactory.java
- org/prismlauncher/impl/OneSixLauncher.java
- org/prismlauncher/applet/LegacyFrame.java
+ org/prismlauncher/launcher/Launcher.java
+ org/prismlauncher/launcher/impl/AbstractLauncher.java
+ org/prismlauncher/launcher/impl/StandardLauncher.java
+ org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java
+ org/prismlauncher/launcher/impl/legacy/LegacyFrame.java
org/prismlauncher/exception/ParameterNotFoundException.java
org/prismlauncher/exception/ParseException.java
org/prismlauncher/utils/Parameters.java
- org/prismlauncher/utils/Utils.java
+ org/prismlauncher/utils/ReflectionUtils.java
+ org/prismlauncher/utils/logging/Level.java
+ org/prismlauncher/utils/logging/Log.java
net/minecraft/Launcher.java
)
add_jar(NewLaunch ${SRC})
diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java
index 6bf671be..646e2e3e 100644
--- a/libraries/launcher/net/minecraft/Launcher.java
+++ b/libraries/launcher/net/minecraft/Launcher.java
@@ -1,41 +1,83 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2012-2021 MultiMC Contributors
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
*
- * 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
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package net.minecraft;
import java.applet.Applet;
import java.applet.AppletStub;
-import java.awt.*;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Graphics;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.HashMap;
import java.util.Map;
-import java.util.TreeMap;
-/*
+/**
* WARNING: This class is reflectively accessed by legacy Forge versions.
- * Changing field and method declarations without further testing is not recommended.
+ * <p>
+ * Changing field and method declarations without further testing is not
+ * recommended.
*/
public final class Launcher extends Applet implements AppletStub {
- private final Map<String, String> params = new TreeMap<>();
-
- private Applet wrappedApplet;
+ private static final long serialVersionUID = 1L;
- private URL documentBase;
+ private final Map<String, String> params = new HashMap<>();
+ private Applet wrappedApplet;
+ private final URL documentBase;
private boolean active = false;
public Launcher(Applet applet) {
@@ -43,35 +85,36 @@ public final class Launcher extends Applet implements AppletStub {
}
public Launcher(Applet applet, URL documentBase) {
- this.setLayout(new BorderLayout());
+ setLayout(new BorderLayout());
- this.add(applet, "Center");
+ add(applet, "Center");
- this.wrappedApplet = applet;
+ wrappedApplet = applet;
try {
- if (documentBase != null) {
- this.documentBase = documentBase;
- } else if (applet.getClass().getPackage().getName().startsWith("com.mojang")) {
- // Special case only for Classic versions
-
- this.documentBase = new URL("http", "www.minecraft.net", 80, "/game/");
- } else {
- this.documentBase = new URL("http://www.minecraft.net/game/");
+ if (documentBase == null) {
+ if (applet.getClass().getPackage().getName().startsWith("com.mojang.")) {
+ // Special case only for Classic versions
+ documentBase = new URL("http://www.minecraft.net:80/game/");
+ } else {
+ documentBase = new URL("http://www.minecraft.net/game/");
+ }
}
} catch (MalformedURLException e) {
- throw new RuntimeException(e);
+ throw new AssertionError(e);
}
+
+ this.documentBase = documentBase;
}
public void replace(Applet applet) {
- this.wrappedApplet = applet;
+ wrappedApplet = applet;
applet.setStub(this);
applet.setSize(getWidth(), getHeight());
- this.setLayout(new BorderLayout());
- this.add(applet, "Center");
+ setLayout(new BorderLayout());
+ add(applet, "Center");
applet.init();
@@ -82,42 +125,48 @@ public final class Launcher extends Applet implements AppletStub {
validate();
}
- public void setParameter(String name, String value) {
- params.put(name, value);
+ @Override
+ public boolean isActive() {
+ return active;
+ }
+
+ @Override
+ public URL getDocumentBase() {
+ return documentBase;
+ }
+
+ @Override
+ public URL getCodeBase() {
+ try {
+ return new URL("http://www.minecraft.net/game/");
+ } catch (MalformedURLException e) {
+ throw new AssertionError(e);
+ }
}
@Override
- public String getParameter(String name) {
- String param = params.get(name);
+ public String getParameter(String key) {
+ String param = params.get(key);
if (param != null)
return param;
try {
- return super.getParameter(name);
- } catch (Exception ignored) {}
+ return super.getParameter(key);
+ } catch (Throwable ignored) {
+ }
return null;
}
@Override
- public boolean isActive() {
- return active;
- }
-
- @Override
- public void appletResize(int width, int height) {
- wrappedApplet.resize(width, height);
- }
-
- @Override
public void resize(int width, int height) {
wrappedApplet.resize(width, height);
}
@Override
- public void resize(Dimension d) {
- wrappedApplet.resize(d);
+ public void resize(Dimension size) {
+ wrappedApplet.resize(size);
}
@Override
@@ -140,33 +189,37 @@ public final class Launcher extends Applet implements AppletStub {
active = false;
}
+ @Override
public void destroy() {
wrappedApplet.destroy();
}
@Override
- public URL getCodeBase() {
- try {
- return new URL("http://www.minecraft.net/game/");
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
- }
+ public void appletResize(int width, int height) {
+ wrappedApplet.resize(width, height);
}
@Override
- public URL getDocumentBase() {
- return documentBase;
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ wrappedApplet.setVisible(visible);
}
@Override
- public void setVisible(boolean b) {
- super.setVisible(b);
+ public void paint(Graphics graphics) {
+ }
- wrappedApplet.setVisible(b);
+ @Override
+ public void update(Graphics graphics) {
}
- public void update(Graphics paramGraphics) {}
+ public void setParameter(String key, String value) {
+ params.put(key, value);
+ }
- public void paint(Graphics paramGraphics) {}
+ public void setParameter(String key, boolean value) {
+ setParameter(key, value ? "true" : "false");
+ }
}
diff --git a/libraries/launcher/org/prismlauncher/EntryPoint.java b/libraries/launcher/org/prismlauncher/EntryPoint.java
index 9144e1f1..78804b3e 100644
--- a/libraries/launcher/org/prismlauncher/EntryPoint.java
+++ b/libraries/launcher/org/prismlauncher/EntryPoint.java
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -52,113 +54,125 @@
package org.prismlauncher;
-import org.prismlauncher.exception.ParseException;
-import org.prismlauncher.utils.Parameters;
-
import java.io.BufferedReader;
-import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public final class EntryPoint {
- private static final Logger LOGGER = Logger.getLogger("EntryPoint");
+import org.prismlauncher.exception.ParseException;
+import org.prismlauncher.launcher.Launcher;
+import org.prismlauncher.launcher.impl.StandardLauncher;
+import org.prismlauncher.launcher.impl.legacy.LegacyLauncher;
+import org.prismlauncher.utils.Parameters;
+import org.prismlauncher.utils.logging.Log;
- private final Parameters params = new Parameters();
+public final class EntryPoint {
public static void main(String[] args) {
- EntryPoint listener = new EntryPoint();
-
- int retCode = listener.listen();
+ ExitCode code = listen();
- if (retCode != 0) {
- LOGGER.info("Exiting with " + retCode);
+ if (code != ExitCode.NORMAL) {
+ Log.fatal("Exiting with " + code);
- System.exit(retCode);
+ System.exit(code.numeric);
}
}
- private Action parseLine(String inData) throws ParseException {
- String[] tokens = inData.split("\\s+", 2);
+ private static ExitCode listen() {
+ Parameters params = new Parameters();
+ PreLaunchAction action = PreLaunchAction.PROCEED;
- if (tokens.length == 0)
- throw new ParseException("Unexpected empty string!");
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
+ String line;
- switch (tokens[0]) {
- case "launch": {
- return Action.Launch;
+ while (action == PreLaunchAction.PROCEED) {
+ if ((line = reader.readLine()) != null)
+ action = parseLine(line, params);
+ else
+ action = PreLaunchAction.ABORT;
}
+ } catch (IllegalArgumentException e) {
+ Log.fatal("Aborting due to wrong argument", e);
- case "abort": {
- return Action.Abort;
- }
+ return ExitCode.ILLEGAL_ARGUMENT;
+ } catch (Throwable e) {
+ Log.fatal("Aborting due to exception", e);
- default: {
- if (tokens.length != 2)
- throw new ParseException("Error while parsing:" + inData);
+ return ExitCode.ABORT;
+ }
- params.add(tokens[0], tokens[1]);
+ if (action == PreLaunchAction.ABORT) {
+ Log.fatal("Launch aborted by the launcher");
- return Action.Proceed;
- }
+ return ExitCode.ABORT;
}
- }
- public int listen() {
- Action action = Action.Proceed;
+ try {
+ Launcher launcher;
+ String type = params.getString("launcher");
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(
- System.in,
- StandardCharsets.UTF_8
- ))) {
- String line;
+ switch (type) {
+ case "standard":
+ launcher = new StandardLauncher(params);
+ break;
- while (action == Action.Proceed) {
- if ((line = reader.readLine()) != null) {
- action = parseLine(line);
- } else {
- action = Action.Abort;
- }
+ case "legacy":
+ launcher = new LegacyLauncher(params);
+ break;
+
+ default:
+ throw new IllegalArgumentException("Invalid launcher type: " + type);
}
- } catch (IOException | ParseException e) {
- LOGGER.log(Level.SEVERE, "Launcher ABORT due to exception:", e);
- return 1;
- }
+ launcher.launch();
+
+ return ExitCode.NORMAL;
+ } catch (IllegalArgumentException e) {
+ Log.fatal("Illegal argument", e);
- // Main loop
- if (action == Action.Abort) {
- LOGGER.info("Launch aborted by the launcher.");
+ return ExitCode.ILLEGAL_ARGUMENT;
+ } catch (Throwable e) {
+ Log.fatal("Exception caught from launcher", e);
- return 1;
+ return ExitCode.ERROR;
}
+ }
- try {
- Launcher launcher =
- LauncherFactory
- .getInstance()
- .createLauncher(params);
+ private static PreLaunchAction parseLine(String input, Parameters params) throws ParseException {
+ switch (input) {
+ case "":
+ break;
- launcher.launch();
+ case "launch":
+ return PreLaunchAction.LAUNCH;
- return 0;
- } catch (IllegalArgumentException e) {
- LOGGER.log(Level.SEVERE, "Wrong argument.", e);
+ case "abort":
+ return PreLaunchAction.ABORT;
- return 1;
- } catch (Exception e) {
- LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e);
+ default:
+ String[] pair = input.split(" ", 2);
- return 1;
+ if (pair.length != 2)
+ throw new ParseException(input, "[key] [value]");
+
+ params.add(pair[0], pair[1]);
}
+
+ return PreLaunchAction.PROCEED;
+ }
+
+ private enum PreLaunchAction {
+ PROCEED, LAUNCH, ABORT
}
- private enum Action {
- Proceed,
- Launch,
- Abort
+ private enum ExitCode {
+ NORMAL(0), ABORT(1), ERROR(2), ILLEGAL_ARGUMENT(65);
+
+ private final int numeric;
+
+ ExitCode(int numeric) {
+ this.numeric = numeric;
+ }
+
}
}
diff --git a/libraries/launcher/org/prismlauncher/Launcher.java b/libraries/launcher/org/prismlauncher/Launcher.java
deleted file mode 100644
index 7f25717b..00000000
--- a/libraries/launcher/org/prismlauncher/Launcher.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2012-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.
- */
-
-package org.prismlauncher;
-
-public interface Launcher {
-
- void launch() throws Exception;
-
-}
diff --git a/libraries/launcher/org/prismlauncher/applet/LegacyFrame.java b/libraries/launcher/org/prismlauncher/applet/LegacyFrame.java
deleted file mode 100644
index 4413efa8..00000000
--- a/libraries/launcher/org/prismlauncher/applet/LegacyFrame.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright 2012-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.
- */
-
-package org.prismlauncher.applet;
-
-import net.minecraft.Launcher;
-
-import javax.imageio.ImageIO;
-import java.applet.Applet;
-import java.awt.*;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public final class LegacyFrame extends Frame {
-
- private static final Logger LOGGER = Logger.getLogger("LegacyFrame");
-
- private final Launcher appletWrap;
-
- public LegacyFrame(String title, Applet mcApplet) {
- super(title);
-
- appletWrap = new Launcher(mcApplet);
-
- mcApplet.setStub(appletWrap);
-
- try {
- setIconImage(ImageIO.read(new File("icon.png")));
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e);
- }
-
- addWindowListener(new ForceExitHandler());
- }
-
- public void start (
- String user,
- String session,
- int winSizeW,
- int winSizeH,
- boolean maximize,
- String serverAddress,
- String serverPort,
- boolean isDemo
- ) {
- // Implements support for launching in to multiplayer on classic servers using a mpticket
- // file generated by an external program and stored in the instance's root folder.
-
- Path mpticketFile =
- Paths.get(System.getProperty("user.dir"), "..", "mpticket");
-
- Path mpticketFileCorrupt =
- Paths.get(System.getProperty("user.dir"), "..", "mpticket.corrupt");
-
- if (Files.exists(mpticketFile)) {
- try {
- List<String> lines = Files.readAllLines(mpticketFile, StandardCharsets.UTF_8);
-
- if (lines.size() < 3) {
- Files.move(
- mpticketFile,
- mpticketFileCorrupt,
- StandardCopyOption.REPLACE_EXISTING
- );
-
- LOGGER.warning("Mpticket file is corrupted!");
- } else {
- // Assumes parameters are valid and in the correct order
- appletWrap.setParameter("server", lines.get(0));
- appletWrap.setParameter("port", lines.get(1));
- appletWrap.setParameter("mppass", lines.get(2));
- }
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e);
- }
- }
-
- if (serverAddress != null) {
- appletWrap.setParameter("server", serverAddress);
- appletWrap.setParameter("port", serverPort);
- }
-
- appletWrap.setParameter("username", user);
- appletWrap.setParameter("sessionid", session);
- appletWrap.setParameter("stand-alone", "true"); // Show the quit button.
- appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work.
- appletWrap.setParameter("demo", isDemo ? "true" : "false");
- appletWrap.setParameter("fullscreen", "false");
-
- add(appletWrap);
-
- appletWrap.setPreferredSize(new Dimension(winSizeW, winSizeH));
-
- pack();
-
- setLocationRelativeTo(null);
- setResizable(true);
-
- if (maximize)
- this.setExtendedState(MAXIMIZED_BOTH);
-
- validate();
-
- appletWrap.init();
- appletWrap.start();
-
- setVisible(true);
- }
-
- private final class ForceExitHandler extends WindowAdapter {
-
- @Override
- public void windowClosing(WindowEvent e) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(30000L);
- } catch (InterruptedException localInterruptedException) {
- localInterruptedException.printStackTrace();
- }
-
- LOGGER.info("Forcing exit!");
-
- System.exit(0);
- }
- }).start();
-
- if (appletWrap != null) {
- appletWrap.stop();
- appletWrap.destroy();
- }
-
- // old minecraft versions can hang without this >_<
- System.exit(0);
- }
-
- }
-
-}
diff --git a/libraries/launcher/org/prismlauncher/exception/ParameterNotFoundException.java b/libraries/launcher/org/prismlauncher/exception/ParameterNotFoundException.java
index 641e0c99..524076ff 100644
--- a/libraries/launcher/org/prismlauncher/exception/ParameterNotFoundException.java
+++ b/libraries/launcher/org/prismlauncher/exception/ParameterNotFoundException.java
@@ -1,25 +1,48 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2012-2021 MultiMC Contributors
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
- * 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
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.prismlauncher.exception;
public final class ParameterNotFoundException extends IllegalArgumentException {
+ private static final long serialVersionUID = 1L;
+
public ParameterNotFoundException(String key) {
- super("Unknown parameter name: " + key);
+ super(String.format("Required parameter '%s' was not found", key));
}
}
diff --git a/libraries/launcher/org/prismlauncher/exception/ParseException.java b/libraries/launcher/org/prismlauncher/exception/ParseException.java
index 51d25a62..4608fdd1 100644
--- a/libraries/launcher/org/prismlauncher/exception/ParseException.java
+++ b/libraries/launcher/org/prismlauncher/exception/ParseException.java
@@ -1,25 +1,48 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2012-2021 MultiMC Contributors
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
- * 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
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.prismlauncher.exception;
public final class ParseException extends IllegalArgumentException {
- public ParseException(String message) {
- super(message);
+ private static final long serialVersionUID = 1L;
+
+ public ParseException(String input, String format) {
+ super(String.format("For input '%s' - should match '%s'", input, format));
}
}
diff --git a/libraries/launcher/org/prismlauncher/impl/OneSixLauncher.java b/libraries/launcher/org/prismlauncher/impl/OneSixLauncher.java
deleted file mode 100644
index d6443826..00000000
--- a/libraries/launcher/org/prismlauncher/impl/OneSixLauncher.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/* Copyright 2012-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.
- */
-
-package org.prismlauncher.impl;
-
-import org.prismlauncher.Launcher;
-import org.prismlauncher.applet.LegacyFrame;
-import org.prismlauncher.utils.Parameters;
-import org.prismlauncher.utils.Utils;
-
-import java.applet.Applet;
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public final class OneSixLauncher implements Launcher {
-
- private static final int DEFAULT_WINDOW_WIDTH = 854;
- private static final int DEFAULT_WINDOW_HEIGHT = 480;
-
- private static final Logger LOGGER = Logger.getLogger("OneSixLauncher");
-
- // parameters, separated from ParamBucket
- private final List<String> mcParams;
- private final List<String> traits;
- private final String appletClass;
- private final String mainClass;
- private final String userName, sessionId;
- private final String windowTitle;
-
- // secondary parameters
- private final int winSizeW;
- private final int winSizeH;
- private final boolean maximize;
- private final String cwd;
-
- private final String serverAddress;
- private final String serverPort;
-
- private final ClassLoader classLoader;
-
- public OneSixLauncher(Parameters params) {
- classLoader = ClassLoader.getSystemClassLoader();
-
- mcParams = params.allSafe("param", new ArrayList<String>());
- mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
- appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
- traits = params.allSafe("traits", new ArrayList<String>());
-
- userName = params.first("userName");
- sessionId = params.first("sessionId");
- windowTitle = params.firstSafe("windowTitle", "Minecraft");
-
- serverAddress = params.firstSafe("serverAddress", null);
- serverPort = params.firstSafe("serverPort", null);
-
- cwd = System.getProperty("user.dir");
-
- String windowParams = params.firstSafe("windowParams", null);
-
- if (windowParams != null) {
- String[] dimStrings = windowParams.split("x");
-
- if (windowParams.equalsIgnoreCase("max")) {
- maximize = true;
-
- winSizeW = DEFAULT_WINDOW_WIDTH;
- winSizeH = DEFAULT_WINDOW_HEIGHT;
- } else if (dimStrings.length == 2) {
- maximize = false;
-
- winSizeW = Integer.parseInt(dimStrings[0]);
- winSizeH = Integer.parseInt(dimStrings[1]);
- } else {
- throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams);
- }
- } else {
- maximize = false;
-
- winSizeW = DEFAULT_WINDOW_WIDTH;
- winSizeH = DEFAULT_WINDOW_HEIGHT;
- }
- }
-
- private void invokeMain(Class<?> mainClass) throws Exception {
- Method method = mainClass.getMethod("main", String[].class);
-
- method.invoke(null, (Object) mcParams.toArray(new String[0]));
- }
-
- private void legacyLaunch() throws Exception {
- // Get the Minecraft Class and set the base folder
- Class<?> minecraftClass = classLoader.loadClass(mainClass);
-
- Field baseDirField = Utils.getMinecraftBaseDirField(minecraftClass);
-
- if (baseDirField == null) {
- LOGGER.warning("Could not find Minecraft path field.");
- } else {
- baseDirField.setAccessible(true);
-
- baseDirField.set(null, new File(cwd));
- }
-
- System.setProperty("minecraft.applet.TargetDirectory", cwd);
-
- if (!traits.contains("noapplet")) {
- LOGGER.info("Launching with applet wrapper...");
-
- try {
- Class<?> mcAppletClass = classLoader.loadClass(appletClass);
-
- Applet mcApplet = (Applet) mcAppletClass.getConstructor().newInstance();
-
- LegacyFrame mcWindow = new LegacyFrame(windowTitle, mcApplet);
-
- mcWindow.start(
- userName,
- sessionId,
- winSizeW,
- winSizeH,
- maximize,
- serverAddress,
- serverPort,
- mcParams.contains("--demo")
- );
-
- return;
- } catch (Exception e) {
- LOGGER.log(Level.SEVERE, "Applet wrapper failed: ", e);
-
- LOGGER.warning("Falling back to using main class.");
- }
- }
-
- invokeMain(minecraftClass);
- }
-
- private void launchWithMainClass() throws Exception {
- // window size, title and state, onesix
-
- // FIXME: there is no good way to maximize the minecraft window in onesix.
- // the following often breaks linux screen setups
- // mcparams.add("--fullscreen");
-
- if (!maximize) {
- mcParams.add("--width");
- mcParams.add(Integer.toString(winSizeW));
- mcParams.add("--height");
- mcParams.add(Integer.toString(winSizeH));
- }
-
- if (serverAddress != null) {
- mcParams.add("--server");
- mcParams.add(serverAddress);
- mcParams.add("--port");
- mcParams.add(serverPort);
- }
-
- invokeMain(classLoader.loadClass(mainClass));
- }
-
- @Override
- public void launch() throws Exception {
- if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) {
- // legacy launch uses the applet wrapper
- legacyLaunch();
- } else {
- // normal launch just calls main()
- launchWithMainClass();
- }
- }
-
-}
diff --git a/libraries/launcher/org/prismlauncher/launcher/Launcher.java b/libraries/launcher/org/prismlauncher/launcher/Launcher.java
new file mode 100644
index 00000000..049a83d8
--- /dev/null
+++ b/libraries/launcher/org/prismlauncher/launcher/Launcher.java
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.prismlauncher.launcher;
+
+public interface Launcher {
+
+ void launch() throws Throwable;
+
+}
diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java
new file mode 100644
index 00000000..0c2153a9
--- /dev/null
+++ b/libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.prismlauncher.launcher.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.prismlauncher.exception.ParseException;
+import org.prismlauncher.launcher.Launcher;
+import org.prismlauncher.utils.Parameters;
+
+public abstract class AbstractLauncher implements Launcher {
+
+ private static final int DEFAULT_WINDOW_WIDTH = 854, DEFAULT_WINDOW_HEIGHT = 480;
+
+ // parameters, separated from ParamBucket
+ protected final List<String> gameArgs;
+
+ // secondary parameters
+ protected final int width, height;
+ protected final boolean maximize;
+ protected final String serverAddress, serverPort;
+
+ protected final String mainClassName;
+
+ protected AbstractLauncher(Parameters params) {
+ gameArgs = params.getList("param", new ArrayList<String>());
+ mainClassName = params.getString("mainClass", "net.minecraft.client.Minecraft");
+
+ serverAddress = params.getString("serverAddress", null);
+ serverPort = params.getString("serverPort", null);
+
+ String windowParams = params.getString("windowParams", null);
+
+ if ("max".equals(windowParams) || windowParams == null) {
+ maximize = windowParams != null;
+
+ width = DEFAULT_WINDOW_WIDTH;
+ height = DEFAULT_WINDOW_HEIGHT;
+ } else {
+ maximize = false;
+
+ String[] sizePair = windowParams.split("x", 2);
+
+ if (sizePair.length == 2) {
+ try {
+ width = Integer.parseInt(sizePair[0]);
+ height = Integer.parseInt(sizePair[1]);
+ return;
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ throw new ParseException(windowParams, "[width]x[height]");
+ }
+ }
+
+}
diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java
new file mode 100644
index 00000000..9436ff15
--- /dev/null
+++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.prismlauncher.launcher.impl;
+
+import java.lang.invoke.MethodHandle;
+
+import org.prismlauncher.utils.Parameters;
+import org.prismlauncher.utils.ReflectionUtils;
+
+public final class StandardLauncher extends AbstractLauncher {
+
+ public StandardLauncher(Parameters params) {
+ super(params);
+ }
+
+ @Override
+ public void launch() throws Throwable {
+ // window size, title and state
+ // FIXME doesn't support maximisation
+ if (!maximize) {
+ gameArgs.add("--width");
+ gameArgs.add(Integer.toString(width));
+ gameArgs.add("--height");
+ gameArgs.add(Integer.toString(height));
+ }
+
+ if (serverAddress != null) {
+ gameArgs.add("--server");
+ gameArgs.add(serverAddress);
+ gameArgs.add("--port");
+ gameArgs.add(serverPort);
+ }
+
+ // find and invoke the main method
+ MethodHandle method = ReflectionUtils.findMainMethod(mainClassName);
+ method.invokeExact(gameArgs.toArray(new String[0]));
+ }
+
+}
diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyFrame.java b/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyFrame.java
new file mode 100644
index 00000000..c215e7fe
--- /dev/null
+++ b/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyFrame.java
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 flow <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.prismlauncher.launcher.impl.legacy;
+
+import java.applet.Applet;
+import java.awt.Dimension;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.JFrame;
+
+import org.prismlauncher.utils.logging.Log;
+
+import net.minecraft.Launcher;
+
+public final class LegacyFrame extends JFrame {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Launcher launcher;
+
+ public LegacyFrame(String title, Applet applet) {
+ super(title);
+
+ launcher = new Launcher(applet);
+
+ applet.setStub(launcher);
+
+ try {
+ setIconImage(ImageIO.read(new File("icon.png")));
+ } catch (IOException e) {
+ Log.error("Failed to read window icon", e);
+ }
+
+ addWindowListener(new ForceExitHandler());
+ }
+
+ public void start(String user, String session, int width, int height, boolean maximize, String serverAddress,
+ String serverPort, boolean demo) {
+ // Implements support for launching in to multiplayer on classic servers using a
+ // mpticket file generated by an external program and stored in the instance's
+ // root folder.
+ Path instanceFolder = Paths.get("..");
+ Path mpticket = instanceFolder.resolve("mpticket");
+ Path mpticketCorrupt = instanceFolder.resolve("mpticket.corrupt");
+
+ if (Files.exists(mpticket)) {
+ try {
+ List<String> lines = Files.readAllLines(mpticket, StandardCharsets.UTF_8);
+
+ if (lines.size() < 3) {
+ Files.move(mpticket, mpticketCorrupt, StandardCopyOption.REPLACE_EXISTING);
+
+ Log.warning("mpticket file is corrupted");
+ } else {
+ // Assumes parameters are valid and in the correct order
+ launcher.setParameter("server", lines.get(0));
+ launcher.setParameter("port", lines.get(1));
+ launcher.setParameter("mppass", lines.get(2));
+ }
+ } catch (IOException e) {
+ Log.error("Failed to read mpticket file", e);
+ }
+ }
+
+ if (serverAddress != null) {
+ launcher.setParameter("server", serverAddress);
+ launcher.setParameter("port", serverPort);
+ }
+
+ launcher.setParameter("username", user);
+ launcher.setParameter("sessionid", session);
+ launcher.setParameter("stand-alone", true); // Show the quit button. TODO: why won't this work?
+ launcher.setParameter("haspaid", true); // Some old versions need this for world saves to work.
+ launcher.setParameter("demo", demo);
+ launcher.setParameter("fullscreen", false);
+
+ add(launcher);
+
+ launcher.setPreferredSize(new Dimension(width, height));
+
+ pack();
+
+ setLocationRelativeTo(null);
+ setResizable(true);
+
+ if (maximize)
+ setExtendedState(MAXIMIZED_BOTH);
+
+ validate();
+
+ launcher.init();
+ launcher.start();
+
+ setVisible(true);
+ }
+
+ private final class ForceExitHandler extends WindowAdapter {
+
+ @Override
+ public void windowClosing(WindowEvent event) {
+ // FIXME better solution
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(30000L);
+ } catch (InterruptedException e) {
+ Log.error("Thread interrupted", e);
+ }
+
+ Log.warning("Forcing exit");
+ System.exit(0);
+ }
+ }).start();
+
+ if (launcher != null) {
+ launcher.stop();
+ launcher.destroy();
+ }
+
+ // old minecraft versions can hang without this >_<
+ System.exit(0);
+ }
+
+ }
+
+}
diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java
new file mode 100644
index 00000000..d349177b
--- /dev/null
+++ b/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 flow <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.prismlauncher.launcher.impl.legacy;
+
+import java.io.File;
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.List;
+
+import org.prismlauncher.launcher.impl.AbstractLauncher;
+import org.prismlauncher.utils.Parameters;
+import org.prismlauncher.utils.ReflectionUtils;
+import org.prismlauncher.utils.logging.Log;
+
+/**
+ * Used to launch old versions that support applets.
+ */
+public final class LegacyLauncher extends AbstractLauncher {
+
+ private final String user, session;
+ private final String title;
+ private final String appletClass;
+ private final boolean useApplet;
+ private final String gameDir;
+
+ public LegacyLauncher(Parameters params) {
+ super(params);
+
+ user = params.getString("userName");
+ session = params.getString("sessionId");
+ title = params.getString("windowTitle", "Minecraft");
+ appletClass = params.getString("appletClass", "net.minecraft.client.MinecraftApplet");
+
+ List<String> traits = params.getList("traits", Collections.<String>emptyList());
+ useApplet = !traits.contains("noapplet");
+
+ gameDir = System.getProperty("user.dir");
+ }
+
+ @Override
+ public void launch() throws Throwable {
+ Class<?> main = ClassLoader.getSystemClassLoader().loadClass(mainClassName);
+ Field gameDirField = ReflectionUtils.findMinecraftGameDirField(main);
+
+ if (gameDirField == null)
+ Log.warning("Could not find Minecraft folder field");
+ else {
+ gameDirField.setAccessible(true);
+ gameDirField.set(null, new File(gameDir));
+ }
+
+ if (useApplet) {
+ System.setProperty("minecraft.applet.TargetDirectory", gameDir);
+
+ try {
+ LegacyFrame window = new LegacyFrame(title, ReflectionUtils.createAppletClass(appletClass));
+
+ window.start(user, session, width, height, maximize, serverAddress, serverPort,
+ gameArgs.contains("--demo"));
+ return;
+ } catch (Throwable e) {
+ Log.error("Running applet wrapper failed with exception; falling back to main class", e);
+ }
+ }
+
+ // find and invoke the main method, this time without size parameters
+ // in all versions that support applets, these are ignored
+ MethodHandle method = ReflectionUtils.findMainMethod(main);
+ method.invokeExact(gameArgs.toArray(new String[0]));
+ }
+
+}
diff --git a/libraries/launcher/org/prismlauncher/utils/Parameters.java b/libraries/launcher/org/prismlauncher/utils/Parameters.java
index 98a40c28..6365753e 100644
--- a/libraries/launcher/org/prismlauncher/utils/Parameters.java
+++ b/libraries/launcher/org/prismlauncher/utils/Parameters.java
@@ -1,46 +1,84 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2012-2021 MultiMC Contributors
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
*
- * 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
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package org.prismlauncher.utils;
-import org.prismlauncher.exception.ParameterNotFoundException;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.prismlauncher.exception.ParameterNotFoundException;
+
public final class Parameters {
- private final Map<String, List<String>> paramsMap = new HashMap<>();
+ private final Map<String, List<String>> map = new HashMap<>();
public void add(String key, String value) {
- List<String> params = paramsMap.get(key);
+ List<String> params = map.get(key);
if (params == null) {
params = new ArrayList<>();
- paramsMap.put(key, params);
+ map.put(key, params);
}
params.add(value);
}
- public List<String> all(String key) throws ParameterNotFoundException {
- List<String> params = paramsMap.get(key);
+ public List<String> getList(String key) throws ParameterNotFoundException {
+ List<String> params = map.get(key);
if (params == null)
throw new ParameterNotFoundException(key);
@@ -48,8 +86,8 @@ public final class Parameters {
return params;
}
- public List<String> allSafe(String key, List<String> def) {
- List<String> params = paramsMap.get(key);
+ public List<String> getList(String key, List<String> def) {
+ List<String> params = map.get(key);
if (params == null || params.isEmpty())
return def;
@@ -57,8 +95,8 @@ public final class Parameters {
return params;
}
- public String first(String key) throws ParameterNotFoundException {
- List<String> list = all(key);
+ public String getString(String key) throws ParameterNotFoundException {
+ List<String> list = getList(key);
if (list.isEmpty())
throw new ParameterNotFoundException(key);
@@ -66,8 +104,8 @@ public final class Parameters {
return list.get(0);
}
- public String firstSafe(String key, String def) {
- List<String> params = paramsMap.get(key);
+ public String getString(String key, String def) {
+ List<String> params = map.get(key);
if (params == null || params.isEmpty())
return def;
diff --git a/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java b/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java
new file mode 100644
index 00000000..dd212ef9
--- /dev/null
+++ b/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea <fr3shtea@outlook.com>
+ * Copyright (C) 2022 solonovamax <solonovamax@12oclockpoint.com>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.prismlauncher.utils;
+
+import java.applet.Applet;
+import java.io.File;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import org.prismlauncher.utils.logging.Log;
+
+public final class ReflectionUtils {
+
+ private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+ private static final ClassLoader LOADER = ClassLoader.getSystemClassLoader();
+
+ /**
+ * Construct a Java applet by its class name.
+ *
+ * @param clazz The class name
+ * @return The applet instance
+ * @throws Throwable
+ */
+ public static Applet createAppletClass(String clazz) throws Throwable {
+ Class<?> appletClass = LOADER.loadClass(clazz);
+
+ MethodHandle appletConstructor = LOOKUP.findConstructor(appletClass, MethodType.methodType(void.class));
+ return (Applet) appletConstructor.invoke();
+ }
+
+ /**
+ * Best guess of the game directory field within net.minecraft.client.Minecraft.
+ * Designed for legacy versions - newer versions do not use a static field.
+ *
+ * @param clazz The class
+ * @return The first field matching criteria
+ */
+ public static Field findMinecraftGameDirField(Class<?> clazz) {
+ Log.debug("Resolving minecraft game directory field");
+
+ // search for private static File
+ for (Field field : clazz.getDeclaredFields()) {
+ if (field.getType() != File.class) {
+ continue;
+ }
+
+ int fieldModifiers = field.getModifiers();
+
+ if (!Modifier.isStatic(fieldModifiers)) {
+ Log.debug("Rejecting field " + field.getName() + " because it is not static");
+ continue;
+ }
+
+ if (!Modifier.isPrivate(fieldModifiers)) {
+ Log.debug("Rejecting field " + field.getName() + " because it is not private");
+ continue;
+ }
+
+ if (Modifier.isFinal(fieldModifiers)) {
+ Log.debug("Rejecting field " + field.getName() + " because it is final");
+ continue;
+ }
+
+ Log.debug("Identified field " + field.getName() + " to match conditions for game directory field");
+
+ return field;
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the main method within a class.
+ *
+ * @param clazz The class
+ * @return A method matching the descriptor of a main method
+ * @throws ClassNotFoundException
+ * @throws NoSuchMethodException
+ * @throws IllegalAccessException
+ */
+ public static MethodHandle findMainMethod(Class<?> clazz) throws NoSuchMethodException, IllegalAccessException {
+ return LOOKUP.findStatic(clazz, "main", MethodType.methodType(void.class, String[].class));
+ }
+
+ /**
+ * Gets the main method within a class by its name.
+ *
+ * @param clazz The class name
+ * @return A method matching the descriptor of a main method
+ * @throws ClassNotFoundException
+ * @throws NoSuchMethodException
+ * @throws IllegalAccessException
+ */
+ public static MethodHandle findMainMethod(String clazz)
+ throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException {
+ return findMainMethod(LOADER.loadClass(clazz));
+ }
+
+}
diff --git a/libraries/launcher/org/prismlauncher/utils/Utils.java b/libraries/launcher/org/prismlauncher/utils/Utils.java
deleted file mode 100644
index ae9a4de2..00000000
--- a/libraries/launcher/org/prismlauncher/utils/Utils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012-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.
- */
-
-package org.prismlauncher.utils;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-
-public final class Utils {
-
- private Utils() {}
-
- /**
- * Finds a field that looks like a Minecraft base folder in a supplied class
- *
- * @param clazz the class to scan
- */
- public static Field getMinecraftBaseDirField(Class<?> clazz) {
- for (Field f : clazz.getDeclaredFields()) {
- // Has to be File
- if (f.getType() != File.class)
- continue;
-
- // And Private Static.
- if (!Modifier.isStatic(f.getModifiers()) || !Modifier.isPrivate(f.getModifiers()))
- continue;
-
- return f;
- }
-
- return null;
- }
-
-}
-
diff --git a/libraries/launcher/org/prismlauncher/LauncherFactory.java b/libraries/launcher/org/prismlauncher/utils/logging/Level.java
index 98f2bbba..552b0b55 100644
--- a/libraries/launcher/org/prismlauncher/LauncherFactory.java
+++ b/libraries/launcher/org/prismlauncher/utils/logging/Level.java
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,48 +33,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-package org.prismlauncher;
+package org.prismlauncher.utils.logging;
-import org.prismlauncher.impl.OneSixLauncher;
-import org.prismlauncher.utils.Parameters;
+public enum Level {
+ LAUNCHER("Launcher"),
+ DEBUG("Debug"),
+ INFO("Info"),
+ MESSAGE("Message"),
+ WARNING("Warning"),
+ ERROR("Error", true),
+ FATAL("Fatal", true);
-import java.util.HashMap;
-import java.util.Map;
+ String name;
+ boolean stderr;
-public final class LauncherFactory {
-
- private static final LauncherFactory INSTANCE = new LauncherFactory();
-
- private final Map<String, LauncherProvider> launcherRegistry = new HashMap<>();
-
- private LauncherFactory() {
- launcherRegistry.put("onesix", new LauncherProvider() {
- @Override
- public Launcher provide(Parameters parameters) {
- return new OneSixLauncher(parameters);
- }
- });
- }
-
- public Launcher createLauncher(Parameters parameters) {
- String name = parameters.first("launcher");
-
- LauncherProvider launcherProvider = launcherRegistry.get(name);
-
- if (launcherProvider == null)
- throw new IllegalArgumentException("Invalid launcher type: " + name);
-
- return launcherProvider.provide(parameters);
- }
-
- public static LauncherFactory getInstance() {
- return INSTANCE;
+ Level(String name) {
+ this(name, false);
}
- public interface LauncherProvider {
-
- Launcher provide(Parameters parameters);
-
+ Level(String name, boolean stderr) {
+ this.name = name;
+ this.stderr = stderr;
}
}
diff --git a/libraries/launcher/org/prismlauncher/utils/logging/Log.java b/libraries/launcher/org/prismlauncher/utils/logging/Log.java
new file mode 100644
index 00000000..e3aa538b
--- /dev/null
+++ b/libraries/launcher/org/prismlauncher/utils/logging/Log.java
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.prismlauncher.utils.logging;
+
+import java.io.PrintStream;
+
+/**
+ * Used to print messages with different levels used to colourise the output.
+ * Used instead of a logging framework, as the launcher knows how to parse these
+ * messages.
+ */
+public final class Log {
+
+ // original before possibly overridden by MC
+ private static final PrintStream OUT = new PrintStream(System.out), ERR = new PrintStream(System.err);
+ private static final boolean DEBUG = Boolean.getBoolean("org.prismlauncher.debug");
+
+ public static void launcher(String message) {
+ log(message, Level.LAUNCHER);
+ }
+
+ public static void error(String message) {
+ log(message, Level.ERROR);
+ }
+
+ public static void debug(String message) {
+ log(message, Level.DEBUG);
+ }
+
+ public static void warning(String message) {
+ log(message, Level.WARNING);
+ }
+
+ public static void error(String message, Throwable e) {
+ error(message);
+ e.printStackTrace(ERR);
+ }
+
+ public static void fatal(String message) {
+ log(message, Level.FATAL);
+ }
+
+ public static void fatal(String message, Throwable e) {
+ fatal(message);
+ e.printStackTrace(ERR);
+ }
+
+ /**
+ * Logs a message with the prefix <code>!![LEVEL]!</code>. This is picked up by
+ * the log viewer to give it nice colours.
+ *
+ * @param message The message
+ * @param level The level
+ */
+ public static void log(String message, Level level) {
+ if (!DEBUG && level == Level.DEBUG)
+ return;
+
+ String prefix = "!![" + level.name + "]!";
+ // prefix first line
+ message = prefix + message;
+ // prefix subsequent lines
+ message = message.replace("\n", "\n" + prefix);
+
+ if (level.stderr)
+ ERR.println(message);
+ else
+ OUT.println(message);
+ }
+
+}
diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus
-Subproject cc741c9f5f2a62856a2a2e9e275f61eb0591c09
+Subproject 0a90913abf9390b9e08ab6d3b40ac11634553f3