diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15c2a3342..333d645cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,9 +39,6 @@ on: APPLE_NOTARIZE_PASSWORD: description: Password used for notarizing macOS builds required: false - CACHIX_AUTH_TOKEN: - description: Private token for authenticating against Cachix cache - required: false GPG_PRIVATE_KEY: description: Private key for AppImage signing required: false @@ -68,6 +65,9 @@ jobs: qt_arch: "" qt_version: "6.5.3" qt_modules: "qt5compat qtimageformats qtnetworkauth" + linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" + linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" + appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" - os: windows-2022 name: "Windows-MinGW-w64" @@ -80,9 +80,9 @@ jobs: architecture: "x64" vcvars_arch: "amd64" qt_ver: 6 - qt_host: windows - qt_arch: "" - qt_version: "6.7.3" + qt_host: "windows" + qt_arch: "win64_msvc2022_64" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" nscurl_tag: "v24.9.26.122" nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" @@ -93,9 +93,9 @@ jobs: architecture: "arm64" vcvars_arch: "amd64_arm64" qt_ver: 6 - qt_host: windows - qt_arch: "win64_msvc2019_arm64" - qt_version: "6.7.3" + qt_host: "windows" + qt_arch: "win64_msvc2022_arm64_cross_compiled" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" nscurl_tag: "v24.9.26.122" nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" @@ -106,7 +106,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: "" - qt_version: "6.7.3" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: macos-14 @@ -216,14 +216,14 @@ jobs: - name: Install host Qt (Windows MSVC arm64) if: runner.os == 'Windows' && matrix.architecture == 'arm64' - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: aqtversion: "==3.1.*" py7zrversion: ">=0.20.2" version: ${{ matrix.qt_version }} host: "windows" target: "desktop" - arch: "" + arch: ${{ matrix.qt_arch }} modules: ${{ matrix.qt_modules }} cache: ${{ inputs.is_qt_cached }} cache-key-prefix: host-qt-arm64-windows @@ -232,7 +232,7 @@ jobs: - name: Install Qt (macOS, Linux & Windows MSVC) if: matrix.msystem == '' - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: aqtversion: "==3.1.*" py7zrversion: ">=0.20.2" @@ -252,19 +252,26 @@ jobs: - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.qt_ver != 5 + env: + APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }} + LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }} + LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }} run: | - wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" - wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage" - wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" + wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage" + wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage" - wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" + wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage" + + sha256sum -c - <<< "$LINUXDEPLOY_HASH" + sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH" + sha256sum -c - <<< "$APPIMAGEUPDATE_HASH" sudo apt install libopengl0 libfuse2 - name: Add QT_HOST_PATH var (Windows MSVC arm64) if: runner.os == 'Windows' && matrix.architecture == 'arm64' run: | - echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV + echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV - name: Setup java (macOS) if: runner.os == 'macOS' @@ -629,76 +636,3 @@ jobs: shell: msys2 {0} run: | ccache -s - - flatpak: - runs-on: ubuntu-latest - container: - image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8 - options: --privileged - steps: - - name: Checkout - uses: actions/checkout@v4 - if: inputs.build_type == 'Debug' - with: - submodules: true - - - name: Set short version - shell: bash - run: echo "VERSION=${GITHUB_SHA::7}" >> $GITHUB_ENV - - - name: Build Flatpak (Linux) - if: inputs.build_type == 'Debug' - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 - with: - bundle: ShatteredPrism-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak - manifest-path: flatpak/org.lunaislazier.ShatteredPrism.yml - - nix: - name: Nix (${{ matrix.system }}) - - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-22.04 - system: x86_64-linux - - - os: macos-13 - system: x86_64-darwin - - - os: macos-14 - system: aarch64-darwin - - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v30 - - # For PRs - - name: Setup Nix Magic Cache - uses: DeterminateSystems/magic-nix-cache-action@v8 - - # For in-tree builds - - name: Setup Cachix - uses: cachix/cachix-action@v15 - with: - name: unmojang - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - - name: Run flake checks - run: | - nix flake check --print-build-logs --show-trace - - - name: Build debug package - if: ${{ inputs.build_type == 'Debug' }} - run: | - nix build --print-build-logs .#shatteredprism-debug - - - name: Build release package - if: ${{ inputs.build_type != 'Debug' }} - run: | - nix build --print-build-logs .#shatteredprism diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml new file mode 100644 index 000000000..00001821d --- /dev/null +++ b/.github/workflows/flatpak.yml @@ -0,0 +1,62 @@ +name: Flatpak + +on: + push: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "nix/**" + # We don't do anything with these artifacts on releases. They go to Flathub + tags-ignore: + - "*" + pull_request: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "nix/**" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build (${{ matrix.arch }}) + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + + - os: ubuntu-22.04-arm + arch: aarch64 + + runs-on: ${{ matrix.os }} + + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8 + options: --privileged + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Set short version + shell: bash + run: | + echo "VERSION=${GITHUB_SHA::7}" >> "$GITHUB_ENV" + + - name: Build Flatpak + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: FjordLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak + manifest-path: flatpak/org.fjordlauncher.FjordLauncher.yml + arch: ${{ matrix.arch }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 000000000..d2f9c1423 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,88 @@ +name: Nix + +on: + push: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "flatpak/**" + tags: + - "*" + pull_request_target: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "flatpak/**" + workflow_dispatch: + +permissions: + contents: read + +env: + DEBUG: ${{ github.ref_type != 'tag' }} + USE_DETERMINATE: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + name: Build (${{ matrix.system }}) + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + system: x86_64-linux + + - os: ubuntu-22.04-arm + system: aarch64-linux + + - os: macos-13 + system: x86_64-darwin + + - os: macos-14 + system: aarch64-darwin + + runs-on: ${{ matrix.os }} + + permissions: + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v16 + with: + determinate: ${{ env.USE_DETERMINATE }} + + # For PRs + - name: Setup Nix Magic Cache + if: ${{ env.USE_DETERMINATE }} + uses: DeterminateSystems/flakehub-cache-action@v1 + + # For in-tree builds + - name: Setup Cachix + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + uses: cachix/cachix-action@v15 + with: + name: fjordlauncher + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + + - name: Run Flake checks + run: | + nix flake check --print-build-logs --show-trace + + - name: Build debug package + if: ${{ env.DEBUG }} + run: | + nix build --print-build-logs .#fjordlauncher-debug + + - name: Build release package + if: ${{ !env.DEBUG }} + run: | + nix build --print-build-logs .#fjordlauncher diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..d49eb4b8d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,45 @@ +name: Publish + +on: + release: + types: [ released ] + +permissions: + contents: read + +jobs: + flakehub: + name: FlakeHub + + runs-on: ubuntu-latest + + permissions: + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Install Nix + uses: cachix/install-nix-action@v30 + + - name: Publish on FlakeHub + uses: determinatesystems/flakehub-push@v5 + with: + visibility: "public" + + winget: + name: Winget + + runs-on: windows-latest + + steps: + - name: Publish on Winget + uses: vedantmgoyal2009/winget-releaser@v2 + with: + identifier: PrismLauncher.PrismLauncher + version: ${{ github.event.release.tag_name }} + installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$' + token: ${{ secrets.WINGET_TOKEN }} diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index 0b8386d69..9efafc8cc 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -38,6 +38,5 @@ jobs: APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index d2bcb9507..549a84041 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -23,7 +23,6 @@ jobs: APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml deleted file mode 100644 index eacf23099..000000000 --- a/.github/workflows/winget.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Publish to WinGet -on: - release: - types: [released] - -jobs: - publish: - runs-on: windows-latest - steps: - - uses: vedantmgoyal2009/winget-releaser@v2 - with: - identifier: PrismLauncher.PrismLauncher - version: ${{ github.event.release.tag_name }} - installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$' - token: ${{ secrets.WINGET_TOKEN }} diff --git a/.gitignore b/.gitignore index b5523f685..1f4e258a3 100644 --- a/.gitignore +++ b/.gitignore @@ -47,8 +47,12 @@ run/ # Nix/NixOS .direnv/ -.pre-commit-config.yaml +## Used when manually invoking stdenv phases +outputs/ +## Regular artifacts result +result-* +repl-result-* # Flatpak .flatpak-builder diff --git a/CMakeLists.txt b/CMakeLists.txt index 08b7a8c44..a7077f43c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,13 @@ else() # ATL's pack list needs more than the default 1 Mib stack on windows if(WIN32) set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}") + + # -ffunction-sections and -fdata-sections help reduce binary size + # -mguard=cf enables Control Flow Guard + # TODO: Look into -gc-sections to further reduce binary size + foreach(lang C CXX) + set("CMAKE_${lang}_FLAGS_RELEASE" "-ffunction-sections -fdata-sections -mguard=cf") + endforeach() endif() endif() @@ -106,14 +113,14 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI else() # AppleClang and Clang message(STATUS "Address Sanitizer available on Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null") endif() elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # GCC message(STATUS "Address Sanitizer available on GCC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover") link_libraries("asan") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") message(STATUS "Address Sanitizer available on MSVC") @@ -181,7 +188,7 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 1) -set(Launcher_VERSION_MINOR 6) +set(Launcher_VERSION_MINOR 7) set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0") diff --git a/default.nix b/default.nix index 6466507b7..5ecef5590 100644 --- a/default.nix +++ b/default.nix @@ -1,9 +1,4 @@ -(import ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } -) { src = ./.; }).defaultNix +(import (fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz"; + sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU="; +}) { src = ./.; }).defaultNix diff --git a/flake.nix b/flake.nix index b1db6afc3..c7ab6d042 100644 --- a/flake.nix +++ b/flake.nix @@ -15,28 +15,6 @@ url = "github:PrismLauncher/libnbtplusplus"; flake = false; }; - - nix-filter.url = "github:numtide/nix-filter"; - - /* - Inputs below this are optional and can be removed - - ``` - { - inputs.shatteredprism = { - url = "github:lunaislazier/ShatteredPrism"; - inputs = { - flake-compat.follows = ""; - }; - }; - } - ``` - */ - - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; }; outputs = @@ -44,9 +22,8 @@ self, nixpkgs, libnbtplusplus, - nix-filter, - ... }: + let inherit (nixpkgs) lib; @@ -58,27 +35,108 @@ forAllSystems = lib.genAttrs systems; nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system}); in + { checks = forAllSystems ( system: + let - checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; }; + pkgs = nixpkgsFor.${system}; + llvm = pkgs.llvmPackages_19; in - lib.filterAttrs (_: lib.isDerivation) checks' + + { + formatting = + pkgs.runCommand "check-formatting" + { + nativeBuildInputs = with pkgs; [ + deadnix + llvm.clang-tools + markdownlint-cli + nixfmt-rfc-style + statix + ]; + } + '' + cd ${self} + + echo "Running clang-format...." + clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp} + + echo "Running deadnix..." + deadnix --fail + + echo "Running markdownlint..." + markdownlint --dot . + + echo "Running nixfmt..." + find -type f -name '*.nix' -exec nixfmt --check {} + + + echo "Running statix" + statix check . + + touch $out + ''; + } ); devShells = forAllSystems ( system: + let pkgs = nixpkgsFor.${system}; + llvm = pkgs.llvmPackages_19; + + packages' = self.packages.${system}; + + # Re-use our package wrapper to wrap our development environment + qt-wrapper-env = packages'.shatteredprism.overrideAttrs (old: { + name = "qt-wrapper-env"; + + # Required to use script-based makeWrapper below + strictDeps = true; + + # We don't need/want the unwrapped Fjord package + paths = [ ]; + + nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [ + # Ensure the wrapper is script based so it can be sourced + pkgs.makeWrapper + ]; + + # Inspired by https://discourse.nixos.org/t/python-qt-woes/11808/10 + buildCommand = '' + makeQtWrapper ${lib.getExe pkgs.runtimeShellPackage} "$out" + sed -i '/^exec/d' "$out" + ''; + }); in + { default = pkgs.mkShell { - inputsFrom = [ self.packages.${system}.shatteredprism-unwrapped ]; - buildInputs = with pkgs; [ + inputsFrom = [ packages'.shatteredprism-unwrapped ]; + + packages = with pkgs; [ ccache - ninja + llvm.clang-tools ]; + + cmakeBuildType = "Debug"; + cmakeFlags = [ "-GNinja" ] ++ packages'.shatteredprism.cmakeFlags; + dontFixCmake = true; + + shellHook = '' + echo "Sourcing ${qt-wrapper-env}" + source ${qt-wrapper-env} + + git submodule update --init --force + + if [ ! -f compile_commands.json ]; then + cmakeConfigurePhase + cd .. + ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json + fi + ''; }; } ); @@ -89,7 +147,6 @@ shatteredprism-unwrapped = prev.callPackage ./nix/unwrapped.nix { inherit libnbtplusplus - nix-filter self ; }; @@ -99,6 +156,7 @@ packages = forAllSystems ( system: + let pkgs = nixpkgsFor.${system}; @@ -111,6 +169,7 @@ default = shatteredPackages.shatteredprism; }; in + # Only output them if they're available on the current system lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages ); @@ -118,16 +177,18 @@ # We put these under legacyPackages as they are meant for CI, not end user consumption legacyPackages = forAllSystems ( system: + let - shatteredPackages = self.packages.${system}; - legacyPackages = self.legacyPackages.${system}; + packages' = self.packages.${system}; + legacyPackages' = self.legacyPackages.${system}; in + { - shatteredprism-debug = shatteredPackages.shatteredprism.override { - shatteredprism-unwrapped = legacyPackages.shatteredprism-unwrapped-debug; + shatteredprism-debug = packages'.shatteredprism.override { + shatteredprism-unwrapped = legacyPackages'.shatteredprism-unwrapped-debug; }; - shatteredprism-unwrapped-debug = shatteredPackages.shatteredprism-unwrapped.overrideAttrs { + shatteredprism-unwrapped-debug = packages'.shatteredprism-unwrapped.overrideAttrs { cmakeBuildType = "Debug"; dontStrip = true; }; diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 904e89b95..0f1bb4538 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -160,6 +160,7 @@ #if defined Q_OS_WIN32 #include +#include #include "WindowsConsole.h" #endif @@ -231,7 +232,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString())); setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT); setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); - startTime = QDateTime::currentDateTime(); + m_startTime = QDateTime::currentDateTime(); // Don't quit on hiding the last window this->setQuitOnLastWindowClosed(false); @@ -1124,8 +1125,16 @@ bool Application::createSetupWizard() // set default theme after going into theme wizard if (!validIcons) settings()->set("IconTheme", QString("pe_colored")); - if (!validWidgets) - settings()->set("ApplicationTheme", QString("system")); + if (!validWidgets) { +#if defined(Q_OS_WIN32) && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + const QString style = + QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark ? QStringLiteral("dark") : QStringLiteral("bright"); +#else + const QString style = QStringLiteral("system"); +#endif + + settings()->set("ApplicationTheme", style); + } m_themeManager->applyCurrentlySelectedTheme(true); @@ -1192,6 +1201,9 @@ bool Application::event(QEvent* event) #endif if (event->type() == QEvent::FileOpen) { + if (!m_mainWindow) { + showMainWindow(false); + } auto ev = static_cast(event); m_mainWindow->processURLs({ ev->url() }); } @@ -1350,6 +1362,9 @@ void Application::messageReceived(const QByteArray& message) qWarning() << "Received" << command << "message without a zip path/URL."; return; } + if (!m_mainWindow) { + showMainWindow(false); + } m_mainWindow->processURLs({ normalizeImportUrl(url) }); } else if (command == "launch") { QString id = received.args["id"]; diff --git a/launcher/Application.h b/launcher/Application.h index 164ec3a4f..ba26dc607 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -112,7 +112,7 @@ class Application : public QApplication { std::shared_ptr settings() const { return m_settings; } - qint64 timeSinceStart() const { return startTime.msecsTo(QDateTime::currentDateTime()); } + qint64 timeSinceStart() const { return m_startTime.msecsTo(QDateTime::currentDateTime()); } QIcon getThemedIcon(const QString& name); @@ -236,7 +236,7 @@ class Application : public QApplication { bool shouldExitNow() const; private: - QDateTime startTime; + QDateTime m_startTime; shared_qobject_ptr m_network; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c855745cc..fc57894d9 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1066,8 +1066,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/CopyInstanceDialog.h ui/dialogs/CustomMessageBox.cpp ui/dialogs/CustomMessageBox.h - ui/dialogs/EditAccountDialog.cpp - ui/dialogs/EditAccountDialog.h ui/dialogs/ExportInstanceDialog.cpp ui/dialogs/ExportInstanceDialog.h ui/dialogs/ExportPackDialog.cpp @@ -1257,7 +1255,6 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/OfflineLoginDialog.ui ui/dialogs/AuthlibInjectorLoginDialog.ui ui/dialogs/AboutDialog.ui - ui/dialogs/EditAccountDialog.ui ui/dialogs/ReviewMessageBox.ui ui/dialogs/ScrollMessageBox.ui ui/dialogs/BlockedModsDialog.ui diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 12a82f73d..7538ce08c 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -307,6 +307,7 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) if (!replacing) { roles.clear(); filterModel->setSourceModel(replacing); + endResetModel(); return; } diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 772c90e42..07b5d7b40 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -171,7 +171,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto os_arch = results["os.arch"]; auto java_version = results["java.version"]; auto java_vendor = results["java.vendor"]; - bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; + bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64"; result.validity = Result::Validity::Valid; result.is_64bit = is_64; diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 0251b302d..4b93d2077 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -254,20 +254,60 @@ void LaunchTask::emitFailed(QString reason) Task::emitFailed(reason); } -void LaunchTask::substituteVariables(QStringList& args) const +QString expandVariables(const QString& input, QProcessEnvironment dict) { - auto env = m_instance->createEnvironment(); + QString result = input; - for (auto key : env.keys()) { - args.replaceInStrings("$" + key, env.value(key)); + enum { base, maybeBrace, variable, brace } state = base; + int startIdx = -1; + for (int i = 0; i < result.length();) { + QChar c = result.at(i++); + switch (state) { + case base: + if (c == '$') + state = maybeBrace; + break; + case maybeBrace: + if (c == '{') { + state = brace; + startIdx = i; + } else if (c.isLetterOrNumber() || c == '_') { + state = variable; + startIdx = i - 1; + } else { + state = base; + } + break; + case brace: + if (c == '}') { + const auto res = dict.value(result.mid(startIdx, i - 1 - startIdx), ""); + if (!res.isEmpty()) { + result.replace(startIdx - 2, i - startIdx + 2, res); + i = startIdx - 2 + res.length(); + } + state = base; + } + break; + case variable: + if (!c.isLetterOrNumber() && c != '_') { + const auto res = dict.value(result.mid(startIdx, i - startIdx - 1), ""); + if (!res.isEmpty()) { + result.replace(startIdx - 1, i - startIdx, res); + i = startIdx - 1 + res.length(); + } + state = base; + } + break; + } } + if (state == variable) { + if (const auto res = dict.value(result.mid(startIdx), ""); !res.isEmpty()) + result.replace(startIdx - 1, result.length() - startIdx + 1, res); + } + return result; } -void LaunchTask::substituteVariables(QString& cmd) const +QString LaunchTask::substituteVariables(QString& cmd, bool isLaunch) const { - auto env = m_instance->createEnvironment(); - - for (auto key : env.keys()) { - cmd.replace("$" + key, env.value(key)); - } + return expandVariables(cmd, isLaunch ? m_instance->createLaunchEnvironment() : m_instance->createEnvironment()); } diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index 56065af5b..2e87ece95 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -87,8 +87,7 @@ class LaunchTask : public Task { shared_qobject_ptr getLogModel(); public: - void substituteVariables(QStringList& args) const; - void substituteVariables(QString& cmd) const; + QString substituteVariables(QString& cmd, bool isLaunch = false) const; QString censorPrivateInfo(QString in); protected: /* methods */ diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 18e51d7e3..167f74190 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -32,7 +32,7 @@ class LogModel : public QAbstractListModel { private /* types */: struct entry { - MessageLevel::Enum level; + MessageLevel::Enum level = MessageLevel::Enum::Unknown; QString line; }; diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp index 725101224..946560c10 100644 --- a/launcher/launch/steps/PostLaunchCommand.cpp +++ b/launcher/launch/steps/PostLaunchCommand.cpp @@ -47,19 +47,15 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask* parent) : LaunchStep(parent) void PostLaunchCommand::executeTask() { - // FIXME: where to put this? + auto cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - auto args = QProcess::splitCommand(m_command); - m_parent->substituteVariables(args); + auto args = QProcess::splitCommand(cmd); - emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher); const QString program = args.takeFirst(); m_process.start(program, args); #else - m_parent->substituteVariables(m_command); - - emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher); - m_process.start(m_command); + m_process.start(cmd); #endif } diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp index 6d071a66e..3505febf7 100644 --- a/launcher/launch/steps/PreLaunchCommand.cpp +++ b/launcher/launch/steps/PreLaunchCommand.cpp @@ -47,19 +47,14 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask* parent) : LaunchStep(parent) void PreLaunchCommand::executeTask() { - // FIXME: where to put this? + auto cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - auto args = QProcess::splitCommand(m_command); - m_parent->substituteVariables(args); - - emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher); + auto args = QProcess::splitCommand(cmd); const QString program = args.takeFirst(); m_process.start(program, args); #else - m_parent->substituteVariables(m_command); - - emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher); - m_process.start(m_command); + m_process.start(cmd); #endif } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 5378f28f8..b664a339f 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -654,6 +654,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() // dlsym variant is only needed for OpenGL and not included in the vulkan layer appendLib("libMangoHud_dlsym.so"); appendLib("libMangoHud_opengl.so"); + appendLib("libMangoHud_shim.so"); preloadList << mangoHudLibString; } @@ -1131,13 +1132,6 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(step); } - // run pre-launch command if that's needed - if (getPreLaunchCommand().size()) { - auto step = makeShared(pptr); - step->setWorkingDirectory(gameRoot()); - process->appendStep(step); - } - // load meta { auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline; @@ -1150,6 +1144,13 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(makeShared(pptr)); } + // run pre-launch command if that's needed + if (getPreLaunchCommand().size()) { + auto step = makeShared(pptr); + step->setWorkingDirectory(gameRoot()); + process->appendStep(step); + } + // if we aren't in offline mode,. if (session->status != AuthSession::PlayableOffline) { if (!session->demo) { diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp index b9fb7eb0c..c0a82e61e 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.cpp +++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp @@ -8,7 +8,10 @@ void MinecraftLoadAndCheck::executeTask() { // add offline metadata load task auto components = m_inst->getPackProfile(); - components->reload(m_netmode); + if (auto result = components->reload(m_netmode); !result) { + emitFailed(result.error); + return; + } m_task = components->getCurrentTask(); if (!m_task) { diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index cf6129dff..bf637e1cc 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -180,29 +180,32 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c } // Read the given file into component containers -static bool loadPackProfile(PackProfile* parent, - const QString& filename, - const QString& componentJsonPattern, - ComponentContainer& container) +static PackProfile::Result loadPackProfile(PackProfile* parent, + const QString& filename, + const QString& componentJsonPattern, + ComponentContainer& container) { QFile componentsFile(filename); if (!componentsFile.exists()) { - qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen."; - return false; + auto message = QObject::tr("Components file %1 doesn't exist. This should never happen.").arg(filename); + qCWarning(instanceProfileC) << message; + return PackProfile::Result::Error(message); } if (!componentsFile.open(QFile::ReadOnly)) { - qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString(); + auto message = QObject::tr("Couldn't open %1 for reading: %2").arg(componentsFile.fileName(), componentsFile.errorString()); + qCCritical(instanceProfileC) << message; qCWarning(instanceProfileC) << "Ignoring overridden order"; - return false; + return PackProfile::Result::Error(message); } // and it's valid JSON QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); if (error.error != QJsonParseError::NoError) { - qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); + auto message = QObject::tr("Couldn't parse %1 as json: %2").arg(componentsFile.fileName(), error.errorString()); + qCCritical(instanceProfileC) << message; qCWarning(instanceProfileC) << "Ignoring overridden order"; - return false; + return PackProfile::Result::Error(message); } // and then read it and process it if all above is true. @@ -219,11 +222,13 @@ static bool loadPackProfile(PackProfile* parent, container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj)); } } catch ([[maybe_unused]] const JSONValidationError& err) { - qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; + auto message = QObject::tr("Couldn't parse %1 : bad file format").arg(componentsFile.fileName()); + qCCritical(instanceProfileC) << message; + qCWarning(instanceProfileC) << "error:" << err.what(); container.clear(); - return false; + return PackProfile::Result::Error(message); } - return true; + return PackProfile::Result::Success(); } // END: component file format @@ -290,44 +295,43 @@ void PackProfile::save_internal() d->dirty = false; } -bool PackProfile::load() +PackProfile::Result PackProfile::load() { auto filename = componentsFilePath(); // load the new component list and swap it with the current one... ComponentContainer newComponents; - if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) { + if (auto result = loadPackProfile(this, filename, patchesPattern(), newComponents); !result) { qCritical() << d->m_instance->name() << "|" << "Failed to load the component config"; - return false; - } else { - // FIXME: actually use fine-grained updates, not this... - beginResetModel(); - // disconnect all the old components - for (auto component : d->components) { - disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - } - d->components.clear(); - d->componentIndex.clear(); - for (auto component : newComponents) { - if (d->componentIndex.contains(component->m_uid)) { - qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid; - continue; - } - connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - d->components.append(component); - d->componentIndex[component->m_uid] = component; - } - endResetModel(); - d->loaded = true; - return true; + return result; } + // FIXME: actually use fine-grained updates, not this... + beginResetModel(); + // disconnect all the old components + for (auto component : d->components) { + disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); + } + d->components.clear(); + d->componentIndex.clear(); + for (auto component : newComponents) { + if (d->componentIndex.contains(component->m_uid)) { + qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid; + continue; + } + connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); + d->components.append(component); + d->componentIndex[component->m_uid] = component; + } + endResetModel(); + d->loaded = true; + return Result::Success(); } -void PackProfile::reload(Net::Mode netmode) +PackProfile::Result PackProfile::reload(Net::Mode netmode) { // Do not reload when the update/resolve task is running. It is in control. if (d->m_updateTask) { - return; + return Result::Success(); } // flush any scheduled saves to not lose state @@ -336,9 +340,11 @@ void PackProfile::reload(Net::Mode netmode) // FIXME: differentiate when a reapply is required by propagating state from components invalidateLaunchProfile(); - if (load()) { - resolve(netmode); + if (auto result = load(); !result) { + return result; } + resolve(netmode); + return Result::Success(); } Task::Ptr PackProfile::getCurrentTask() diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index b2de26ea0..d812dfa48 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -62,6 +62,19 @@ class PackProfile : public QAbstractListModel { public: enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS }; + struct Result { + bool success; + QString error; + + // Implicit conversion to bool + operator bool() const { return success; } + + // Factory methods for convenience + static Result Success() { return { true, "" }; } + + static Result Error(const QString& errorMessage) { return { false, errorMessage }; } + }; + explicit PackProfile(MinecraftInstance* instance); virtual ~PackProfile(); @@ -102,7 +115,7 @@ class PackProfile : public QAbstractListModel { bool revertToBase(int index); /// reload the list, reload all components, resolve dependencies - void reload(Net::Mode netmode); + Result reload(Net::Mode netmode); // reload all components, resolve dependencies void resolve(Net::Mode netmode); @@ -169,7 +182,7 @@ class PackProfile : public QAbstractListModel { void disableInteraction(bool disable); private: - bool load(); + Result load(); bool installJarMods_internal(QStringList filepaths); bool installCustomJar_internal(QString filepath); bool installAgents_internal(QStringList filepaths); diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 39600489f..695589176 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -99,44 +99,44 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile )XXX") .arg(BuildConfig.LOGIN_CALLBACK_URL)); - oauth2.setReplyHandler(replyHandler); + m_oauth2.setReplyHandler(replyHandler); } else { - oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this)); + m_oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this)); } - oauth2.setAuthorizationUrl(QUrl("https://login.live.com/oauth20_connect.srf")); - oauth2.setAccessTokenUrl(QUrl("https://login.live.com/oauth20_token.srf")); + m_oauth2.setAuthorizationUrl(QUrl("https://login.live.com/oauth20_connect.srf")); + m_oauth2.setAccessTokenUrl(QUrl("https://login.live.com/oauth20_token.srf")); const auto& scope = "service::user.auth.xboxlive.com::MBI_SSL"; - oauth2.setScope(scope); + m_oauth2.setScope(scope); // QOAuth2AuthorizationCodeFlow doesn't pass a "scope" when refreshing access tokens, but Microsoft expects it. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap* parameters) { + m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap* parameters) { if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) { (*parameters)["scope"] = scope; } }); #else - oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap* parameters) { + m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap* parameters) { if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) { (*parameters).insert("scope", scope); } }); #endif - oauth2.setClientIdentifier(m_clientId); - oauth2.setNetworkAccessManager(APPLICATION->network().get()); + m_oauth2.setClientIdentifier(m_clientId); + m_oauth2.setNetworkAccessManager(APPLICATION->network().get()); - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] { - m_data->msaClientID = oauth2.clientIdentifier(); + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] { + m_data->msaClientID = m_oauth2.clientIdentifier(); m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc(); - m_data->msaToken.notAfter = oauth2.expirationAt(); - m_data->msaToken.extra = oauth2.extraTokens(); - m_data->msaToken.refresh_token = oauth2.refreshToken(); - m_data->msaToken.token = oauth2.token(); + m_data->msaToken.notAfter = m_oauth2.expirationAt(); + m_data->msaToken.extra = m_oauth2.extraTokens(); + m_data->msaToken.refresh_token = m_oauth2.refreshToken(); + m_data->msaToken.token = m_oauth2.token(); emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); }); - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser); - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) { + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser); + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) { auto state = AccountTaskState::STATE_FAILED_HARD; - if (oauth2.status() == QAbstractOAuth::Status::Granted || silent) { + if (m_oauth2.status() == QAbstractOAuth::Status::Granted || silent) { if (err == QAbstractOAuth2::Error::NetworkError) { state = AccountTaskState::STATE_OFFLINE; } else { @@ -150,16 +150,16 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile qWarning() << message; emit finished(state, message); }); - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::error, this, + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::error, this, [this](const QString& error, const QString& errorDescription, const QUrl& uri) { qWarning() << "Failed to login because" << error << errorDescription; emit finished(AccountTaskState::STATE_FAILED_HARD, errorDescription); }); - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this, + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this, [this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; }); - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this, + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this, [this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; }); } @@ -180,20 +180,20 @@ void MSAStep::perform() emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - refresh token is empty.")); return; } - oauth2.setRefreshToken(m_data->msaToken.refresh_token); - oauth2.refreshAccessToken(); + m_oauth2.setRefreshToken(m_data->msaToken.refresh_token); + m_oauth2.refreshAccessToken(); } else { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 - oauth2.setModifyParametersFunction( + m_oauth2.setModifyParametersFunction( [](QAbstractOAuth::Stage stage, QMultiMap* map) { map->insert("prompt", "select_account"); }); #else - oauth2.setModifyParametersFunction( + m_oauth2.setModifyParametersFunction( [](QAbstractOAuth::Stage stage, QMap* map) { map->insert("prompt", "select_account"); }); #endif *m_data = AccountData(); m_data->msaClientID = m_clientId; - oauth2.grant(); + m_oauth2.grant(); } } diff --git a/launcher/minecraft/auth/steps/MSAStep.h b/launcher/minecraft/auth/steps/MSAStep.h index 675cfb2ca..2f4e7812b 100644 --- a/launcher/minecraft/auth/steps/MSAStep.h +++ b/launcher/minecraft/auth/steps/MSAStep.h @@ -55,5 +55,5 @@ class MSAStep : public AuthStep { private: bool m_silent; QString m_clientId; - QOAuth2AuthorizationCodeFlow oauth2; + QOAuth2AuthorizationCodeFlow m_oauth2; }; diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 54dff3715..168d72a10 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -132,6 +132,7 @@ void LauncherPartLaunch::executeTask() QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); if (!wrapperCommandStr.isEmpty()) { + wrapperCommandStr = m_parent->substituteVariables(wrapperCommandStr); auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); auto wrapperCommand = wrapperArgs.takeFirst(); auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); @@ -171,6 +172,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) case LoggedProcess::Aborted: case LoggedProcess::Crashed: { m_parent->setPid(-1); + m_parent->instance()->setMinecraftRunning(false); emitFailed(tr("Game crashed.")); return; } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 7417dffe6..d15bb094f 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -293,86 +293,90 @@ ModDetails ReadFabricModInfo(QByteArray contents) // https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md ModDetails ReadQuiltModInfo(QByteArray contents) { - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = Json::requireObject(jsonDoc, "quilt.mod.json"); - auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version"); - ModDetails details; + try { + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + auto object = Json::requireObject(jsonDoc, "quilt.mod.json"); + auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version"); - // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md - if (schemaVersion == 1) { - auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); + // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md + if (schemaVersion == 1) { + auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); - details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); - details.version = Json::requireString(modInfo.value("version"), "Mod version"); + details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); + details.version = Json::requireString(modInfo.value("version"), "Mod version"); - auto modMetadata = Json::ensureObject(modInfo.value("metadata")); + auto modMetadata = Json::ensureObject(modInfo.value("metadata")); - details.name = Json::ensureString(modMetadata.value("name"), details.mod_id); - details.description = Json::ensureString(modMetadata.value("description")); + details.name = Json::ensureString(modMetadata.value("name"), details.mod_id); + details.description = Json::ensureString(modMetadata.value("description")); - auto modContributors = Json::ensureObject(modMetadata.value("contributors")); + auto modContributors = Json::ensureObject(modMetadata.value("contributors")); - // We don't really care about the role of a contributor here - details.authors += modContributors.keys(); + // We don't really care about the role of a contributor here + details.authors += modContributors.keys(); - auto modContact = Json::ensureObject(modMetadata.value("contact")); + auto modContact = Json::ensureObject(modMetadata.value("contact")); - if (modContact.contains("homepage")) { - details.homeurl = Json::requireString(modContact.value("homepage")); - } - if (modContact.contains("issues")) { - details.issue_tracker = Json::requireString(modContact.value("issues")); - } + if (modContact.contains("homepage")) { + details.homeurl = Json::requireString(modContact.value("homepage")); + } + if (modContact.contains("issues")) { + details.issue_tracker = Json::requireString(modContact.value("issues")); + } - if (modMetadata.contains("license")) { - auto license = modMetadata.value("license"); - if (license.isArray()) { - for (auto l : license.toArray()) { - if (l.isString()) { - details.licenses.append(ModLicense(l.toString())); - } else if (l.isObject()) { - auto obj = l.toObject(); - details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), - obj.value("url").toString(), obj.value("description").toString())); + if (modMetadata.contains("license")) { + auto license = modMetadata.value("license"); + if (license.isArray()) { + for (auto l : license.toArray()) { + if (l.isString()) { + details.licenses.append(ModLicense(l.toString())); + } else if (l.isObject()) { + auto obj = l.toObject(); + details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), + obj.value("url").toString(), obj.value("description").toString())); + } } + } else if (license.isString()) { + details.licenses.append(ModLicense(license.toString())); + } else if (license.isObject()) { + auto obj = license.toObject(); + details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), + obj.value("url").toString(), obj.value("description").toString())); + } + } + + if (modMetadata.contains("icon")) { + auto icon = modMetadata.value("icon"); + if (icon.isObject()) { + auto obj = icon.toObject(); + // take the largest icon + int largest = 0; + for (auto key : obj.keys()) { + auto size = key.split('x').first().toInt(); + if (size > largest) { + largest = size; + } + } + if (largest > 0) { + auto key = QString::number(largest) + "x" + QString::number(largest); + details.icon_file = obj.value(key).toString(); + } else { // parsing the sizes failed + // take the first + for (auto i : obj) { + details.icon_file = i.toString(); + break; + } + } + } else if (icon.isString()) { + details.icon_file = icon.toString(); } - } else if (license.isString()) { - details.licenses.append(ModLicense(license.toString())); - } else if (license.isObject()) { - auto obj = license.toObject(); - details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(), - obj.value("description").toString())); } } - if (modMetadata.contains("icon")) { - auto icon = modMetadata.value("icon"); - if (icon.isObject()) { - auto obj = icon.toObject(); - // take the largest icon - int largest = 0; - for (auto key : obj.keys()) { - auto size = key.split('x').first().toInt(); - if (size > largest) { - largest = size; - } - } - if (largest > 0) { - auto key = QString::number(largest) + "x" + QString::number(largest); - details.icon_file = obj.value(key).toString(); - } else { // parsing the sizes failed - // take the first - for (auto i : obj) { - details.icon_file = i.toString(); - break; - } - } - } else if (icon.isString()) { - details.icon_file = icon.toString(); - } - } + } catch (const Exception& e) { + qWarning() << "Unable to parse mod info:" << e.cause(); } return details; } diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index b7364d9ab..17274722d 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -73,7 +73,7 @@ class ResourceAPI { std::optional search; std::optional sorting; std::optional loaders; - std::optional > versions; + std::optional> versions; std::optional side; std::optional categoryIds; }; @@ -168,11 +168,23 @@ class ResourceAPI { protected: [[nodiscard]] inline QString debugName() const { return "External resource API"; } - [[nodiscard]] inline auto getGameVersionsString(std::list mcVersions) const -> QString + [[nodiscard]] inline QString mapMCVersionToModrinth(Version v) const + { + static const QString preString = " Pre-Release "; + auto verStr = v.toString(); + + if (verStr.contains(preString)) { + verStr.replace(preString, "-pre"); + } + verStr.replace(" ", "-"); + return verStr; + } + + [[nodiscard]] inline QString getGameVersionsString(std::list mcVersions) const { QString s; for (auto& ver : mcVersions) { - s += QString("\"%1\",").arg(ver.toString()); + s += QString("\"%1\",").arg(mapMCVersionToModrinth(ver)); } s.remove(s.length() - 1, 1); // remove last comma return s; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index d69bf12c0..8ee599f27 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -112,6 +112,8 @@ void Flame::FileResolvingTask::netJobFinished() auto obj = Json::requireObject(file); auto version = FlameMod::loadIndexedPackVersion(obj); auto fileid = version.fileId.toInt(); + Q_ASSERT(fileid != 0); + Q_ASSERT(m_manifest.files.contains(fileid)); m_manifest.files[fileid].version = version; auto url = QUrl(version.downloadUrl, QUrl::TolerantMode); if (!url.isValid() && "sha1" == version.hash_type && !version.hash.isEmpty()) { diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 53eadcf02..699eb792a 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -270,6 +270,7 @@ std::optional FlameAPI::getLatestVersion(QList instanceLoaders, ModPlatform::ModLoaderTypes modLoaders) { + static const auto noLoader = ModPlatform::ModLoaderType(0); QHash bestMatch; auto checkVersion = [&bestMatch](const ModPlatform::IndexedVersion& version, const ModPlatform::ModLoaderType& loader) { if (bestMatch.contains(loader)) { @@ -284,7 +285,7 @@ std::optional FlameAPI::getLatestVersion(QList FlameAPI::getLatestVersion(QList fabric version will be prioritizated on update auto currentLoaders = instanceLoaders + ModPlatform::modLoaderTypesToList(modLoaders); - currentLoaders.append(ModPlatform::ModLoaderType(0)); // add a fallback in case the versions do not define a loader + currentLoaders.append(noLoader); // add a fallback in case the versions do not define a loader for (auto loader : currentLoaders) { if (bestMatch.contains(loader)) { - return bestMatch.value(loader); + auto bestForLoader = bestMatch.value(loader); + // awkward case where the mod has only two loaders and one of them is not specified + if (loader != noLoader && bestMatch.contains(noLoader) && bestMatch.size() == 2) { + auto bestForNoLoader = bestMatch.value(noLoader); + if (bestForNoLoader.date > bestForLoader.date) { + return bestForNoLoader; + } + } + return bestForLoader; } } return {}; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 7de05f177..eb5b3cc67 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -105,9 +105,6 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> ModPlatform::IndexedVersion { auto versionArray = Json::requireArray(obj, "gameVersions"); - if (versionArray.isEmpty()) { - return {}; - } ModPlatform::IndexedVersion file; for (auto mcVer : versionArray) { diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index e576a6a84..278105f4a 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -45,7 +45,7 @@ static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest) Flame::File file; loadFileV1(file, obj); - + Q_ASSERT(file.projectId != 0); pack.files.insert(file.fileId, file); } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index a954f65a5..bdef1a0e5 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -54,7 +54,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash, if (mcVersions.has_value()) { QStringList game_versions; for (auto& ver : mcVersions.value()) { - game_versions.append(ver.toString()); + game_versions.append(mapMCVersionToModrinth(ver)); } Json::writeStringList(body_obj, "game_versions", game_versions); } @@ -87,7 +87,7 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, if (mcVersions.has_value()) { QStringList game_versions; for (auto& ver : mcVersions.value()) { - game_versions.append(ver.toString()); + game_versions.append(mapMCVersionToModrinth(ver)); } Json::writeStringList(body_obj, "game_versions", game_versions); } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 070f59dad..4c82a5ec6 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -81,6 +81,21 @@ class ModrinthAPI : public NetworkResourceAPI { return {}; } + [[nodiscard]] static inline QString mapMCVersionFromModrinth(QString v) + { + static const QString preString = " Pre-Release "; + bool pre = false; + if (v.contains("-pre")) { + pre = true; + v.replace("-pre", preString); + } + v.replace("-", " "); + if (pre) { + v.replace(" Pre Release ", preString); + } + return v; + } + private: [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) { @@ -170,7 +185,7 @@ class ModrinthAPI : public NetworkResourceAPI { { QString s; for (auto& ver : mcVersions) { - s += QString("\"versions:%1\",").arg(ver.toString()); + s += QString("\"versions:%1\",").arg(mapMCVersionToModrinth(ver)); } s.remove(s.length() - 1, 1); // remove last comma return s.isEmpty() ? QString() : s; @@ -187,7 +202,7 @@ class ModrinthAPI : public NetworkResourceAPI { : QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]") .arg(BuildConfig.MODRINTH_PROD_URL) .arg(args.dependency.addonId.toString()) - .arg(args.mcVersion.toString()) + .arg(mapMCVersionToModrinth(args.mcVersion)) .arg(getModLoaderStrings(args.loader).join("\",\"")); }; }; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 48b27a597..72550937c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -131,9 +131,7 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArra pack.versionsLoaded = true; } -auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, - QString preferred_hash_type, - QString preferred_file_name) -> ModPlatform::IndexedVersion +ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name) { ModPlatform::IndexedVersion file; @@ -145,7 +143,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, return {}; } for (auto mcVer : versionArray) { - file.mcVersion.append(mcVer.toString()); + file.mcVersion.append(ModrinthAPI::mapMCVersionFromModrinth(mcVer.toString())); } auto loaders = Json::requireArray(obj, "loaders"); for (auto loader : loaders) { @@ -247,9 +245,9 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, return {}; } -auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, - QJsonArray& arr, - const BaseInstance* inst) -> ModPlatform::IndexedVersion +ModPlatform::IndexedVersion Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, + QJsonArray& arr, + const BaseInstance* inst) { auto profile = (dynamic_cast(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index c52a1743b..89ef6e4c4 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -40,9 +40,6 @@ #include "modplatform/modrinth/ModrinthAPI.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - #include static ModrinthAPI api; @@ -134,6 +131,7 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion auto gameVersions = Json::ensureArray(obj, "game_versions"); if (!gameVersions.isEmpty()) { file.gameVersion = Json::ensureString(gameVersions[0]); + file.gameVersion = ModrinthAPI::mapMCVersionFromModrinth(file.gameVersion); } auto loaders = Json::requireArray(obj, "loaders"); for (auto loader : loaders) { diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 95c1a8f44..3a58a4667 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -54,7 +54,7 @@ Task::State FileSink::init(QNetworkRequest& request) return Task::State::Failed; } - wroteAnyData = false; + m_wroteAnyData = false; m_output_file.reset(new PSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; @@ -72,17 +72,19 @@ Task::State FileSink::write(QByteArray& data) qCCritical(taskNetLogC) << "Failed writing into " + m_filename; m_output_file->cancelWriting(); m_output_file.reset(); - wroteAnyData = false; + m_wroteAnyData = false; return Task::State::Failed; } - wroteAnyData = true; + m_wroteAnyData = true; return Task::State::Running; } Task::State FileSink::abort() { - m_output_file->cancelWriting(); + if (m_output_file) { + m_output_file->cancelWriting(); + } failAllValidators(); return Task::State::Failed; } @@ -100,7 +102,7 @@ Task::State FileSink::finalize(QNetworkReply& reply) // if we wrote any data to the save file, we try to commit the data to the real file. // if it actually got a proper file, we write it even if it was empty - if (gotFile || wroteAnyData) { + if (gotFile || m_wroteAnyData) { // ask validators for data consistency // we only do this for actual downloads, not 'your data is still the same' cache hits if (!finalizeAllValidators(reply)) diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index 272f8ddc3..67c25361c 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -58,7 +58,7 @@ class FileSink : public Sink { protected: QString m_filename; - bool wroteAnyData = false; + bool m_wroteAnyData = false; std::unique_ptr m_output_file; }; } // namespace Net diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 889588a11..432c0c84b 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -78,7 +78,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply& reply) { QFileInfo output_file_info(m_filename); - if (wroteAnyData) { + if (m_wroteAnyData) { m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); } diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index cf2e72858..310653508 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -80,7 +80,7 @@ void NetRequest::executeTask() emit finished(); return; case State::Running: - qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString(); + qCDebug(logCat) << getUid().toString() << "Running " << m_url.toString(); break; case State::Inactive: case State::Failed: diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index ca80fcaa7..98e26ad01 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -52,6 +52,30 @@ #include #include "Application.h" + +constexpr int MaxMclogsLines = 25000; +constexpr int InitialMclogsLines = 10000; +constexpr int FinalMclogsLines = 14900; + +QString truncateLogForMclogs(const QString& logContent) +{ + QStringList lines = logContent.split("\n"); + if (lines.size() > MaxMclogsLines) { + QString truncatedLog = lines.mid(0, InitialMclogsLines).join("\n"); + truncatedLog += + "\n\n\n\n\n\n\n\n\n\n" + "------------------------------------------------------------\n" + "----------------------- Log truncated ----------------------\n" + "------------------------------------------------------------\n" + "----- Middle portion omitted to fit mclo.gs size limits ----\n" + "------------------------------------------------------------\n" + "\n\n\n\n\n\n\n\n\n\n"; + truncatedLog += lines.mid(lines.size() - FinalMclogsLines - 1).join("\n"); + return truncatedLog; + } + return logContent; +} + QString GuiUtil::fetchFlameKey(QWidget* parentWidget) { ProgressDialog prog(parentWidget); @@ -78,6 +102,7 @@ std::optional GuiUtil::uploadPaste(const QString& name, const QString& ProgressDialog dialog(parentWidget); auto pasteTypeSetting = static_cast(APPLICATION->settings()->get("PastebinType").toInt()); auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString(); + bool shouldTruncate = false; { QUrl baseUrl; @@ -97,10 +122,36 @@ std::optional GuiUtil::uploadPaste(const QString& name, const QString& if (response != QMessageBox::Yes) return {}; + + if (baseUrl.toString() == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) { + auto truncateResponse = CustomMessageBox::selectable( + parentWidget, QObject::tr("Confirm Truncation"), + QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n" + "The launcher can keep the first %3 and last %4 lines, trimming the middle.\n\n" + "If you choose 'No', mclo.gs will only keep the first %2 lines, cutting off " + "potentially useful info like crashes at the end.\n\n" + "Proceed with truncation?") + .arg(text.count("\n")) + .arg(MaxMclogsLines) + .arg(InitialMclogsLines) + .arg(FinalMclogsLines), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No) + ->exec(); + + if (truncateResponse == QMessageBox::Cancel) { + return {}; + } + shouldTruncate = truncateResponse == QMessageBox::Yes; + } } } - std::unique_ptr paste(new PasteUpload(parentWidget, text, pasteCustomAPIBaseSetting, pasteTypeSetting)); + QString textToUpload = text; + if (shouldTruncate) { + textToUpload = truncateLogForMclogs(text); + } + + std::unique_ptr paste(new PasteUpload(parentWidget, textToUpload, pasteCustomAPIBaseSetting, pasteTypeSetting)); dialog.execWithTask(paste.get()); if (!paste->wasSuccessful()) { diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 712404746..598d47988 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -979,6 +979,14 @@ void MainWindow::processURLs(QList urls) continue; } + if (APPLICATION->instances()->count() <= 0) { + CustomMessageBox::selectable(this, tr("No instance!"), + tr("No instance available to add the resource to.\nPlease create a new instance before " + "attempting to install this resource again."), + QMessageBox::Critical) + ->show(); + continue; + } ImportResourceDialog dlg(localFileName, type, this); if (dlg.exec() != QDialog::Accepted) diff --git a/launcher/ui/dialogs/EditAccountDialog.cpp b/launcher/ui/dialogs/EditAccountDialog.cpp deleted file mode 100644 index 9d0175bbc..000000000 --- a/launcher/ui/dialogs/EditAccountDialog.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "EditAccountDialog.h" -#include -#include -#include -#include "ui_EditAccountDialog.h" - -EditAccountDialog::EditAccountDialog(const QString& text, QWidget* parent, int flags) : QDialog(parent), ui(new Ui::EditAccountDialog) -{ - ui->setupUi(this); - - ui->label->setText(text); - ui->label->setVisible(!text.isEmpty()); - - ui->userTextBox->setEnabled(flags & UsernameField); - ui->passTextBox->setEnabled(flags & PasswordField); - - ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); -} - -EditAccountDialog::~EditAccountDialog() -{ - delete ui; -} - -void EditAccountDialog::on_label_linkActivated(const QString& link) -{ - DesktopServices::openUrl(QUrl(link)); -} - -void EditAccountDialog::setUsername(const QString& user) const -{ - ui->userTextBox->setText(user); -} - -QString EditAccountDialog::username() const -{ - return ui->userTextBox->text(); -} - -void EditAccountDialog::setPassword(const QString& pass) const -{ - ui->passTextBox->setText(pass); -} - -QString EditAccountDialog::password() const -{ - return ui->passTextBox->text(); -} diff --git a/launcher/ui/dialogs/EditAccountDialog.h b/launcher/ui/dialogs/EditAccountDialog.h deleted file mode 100644 index 7a9ccba79..000000000 --- a/launcher/ui/dialogs/EditAccountDialog.h +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace Ui { -class EditAccountDialog; -} - -class EditAccountDialog : public QDialog { - Q_OBJECT - - public: - explicit EditAccountDialog(const QString& text = "", QWidget* parent = 0, int flags = UsernameField | PasswordField); - ~EditAccountDialog(); - - void setUsername(const QString& user) const; - void setPassword(const QString& pass) const; - - QString username() const; - QString password() const; - - enum Flags { - NoFlags = 0, - - //! Specifies that the dialog should have a username field. - UsernameField, - - //! Specifies that the dialog should have a password field. - PasswordField, - }; - - private slots: - void on_label_linkActivated(const QString& link); - - private: - Ui::EditAccountDialog* ui; -}; diff --git a/launcher/ui/dialogs/EditAccountDialog.ui b/launcher/ui/dialogs/EditAccountDialog.ui deleted file mode 100644 index e87509bcb..000000000 --- a/launcher/ui/dialogs/EditAccountDialog.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - EditAccountDialog - - - - 0 - 0 - 400 - 148 - - - - Login - - - - - - Message label placeholder. - - - Qt::RichText - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Email - - - - - - - QLineEdit::Password - - - Password - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - EditAccountDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - EditAccountDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/launcher/ui/dialogs/ProfileSetupDialog.ui b/launcher/ui/dialogs/ProfileSetupDialog.ui index 9dbabb4b3..947110da7 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.ui +++ b/launcher/ui/dialogs/ProfileSetupDialog.ui @@ -30,6 +30,9 @@ Choose your name carefully: true + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + nameEdit diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index b793e55a4..d4602a75b 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -207,7 +207,7 @@ - <html><head/><body><p>Note: you only need to set this to access private data. Read the <a href="https://docs.modrinth.com/#section/Authentication">documentation</a> for more information.</p></body></html> + <html><head/><body><p>Note: you only need to set this to access private data. Read the <a href="https://docs.modrinth.com/api/#authentication">documentation</a> for more information.</p></body></html> true diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index a02da25bd..b84140712 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -66,7 +66,7 @@ class AccountListPage : public QMainWindow, public BasePage { return icon; } QString id() const override { return "accounts"; } - QString helpPage() const override { return "/getting-started/adding-an-account"; } + QString helpPage() const override { return "getting-started/adding-an-account"; } void retranslate() override; public slots: diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index cf8d86cd4..af66209de 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -93,6 +93,11 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent) ui->serverJoinAddress->setEnabled(true); ui->serverJoinAddressButton->setStyleSheet("QRadioButton::indicator { width: 0px; height: 0px; }"); } + connect(ui->javaPathTextBox, &QLineEdit::textChanged, [this](QString newValue) { + if (m_instance->settings()->get("JavaPath").toString() != newValue) { + m_instance->settings()->set("AutomaticJava", false); + } + }); loadSettings(); diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index a909d10d1..0fccd1d33 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -245,7 +245,6 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin } // MODRINTH - void ModrinthManagedPackPage::parseManagedPack() { qDebug() << "Parsing Modrinth pack"; @@ -338,6 +337,25 @@ void ModrinthManagedPackPage::suggestVersion() ManagedPackPage::suggestVersion(); } +/// @brief Called when the update task has completed. +/// Internally handles the closing of the instance window if the update was successful and shows a message box. +/// @param did_succeed Whether the update task was successful. +void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const +{ + // Close the window if the update was successful + if (did_succeed) { + if (m_instance_window != nullptr) + m_instance_window->close(); + + CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) + ->show(); + } else { + CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) + ->show(); + } + +} + void ModrinthManagedPackPage::update() { auto index = ui->versionsComboBox->currentIndex(); @@ -363,10 +381,9 @@ void ModrinthManagedPackPage::update() extracted->setIcon(m_inst->iconKey()); extracted->setConfirmUpdate(false); + // Run our task then handle the result auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } void ModrinthManagedPackPage::updateFromFile() @@ -386,14 +403,12 @@ void ModrinthManagedPackPage::updateFromFile() extracted->setIcon(m_inst->iconKey()); extracted->setConfirmUpdate(false); + // Run our task then handle the result auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } // FLAME - FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent) : ManagedPackPage(inst, instance_window, parent) { @@ -531,9 +546,7 @@ void FlameManagedPackPage::update() extracted->setConfirmUpdate(false); auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } void FlameManagedPackPage::updateFromFile() @@ -555,8 +568,6 @@ void FlameManagedPackPage::updateFromFile() extracted->setConfirmUpdate(false); auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } #include "ManagedPackPage.moc" diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index c44f77070..e8d304c6b 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -94,6 +94,8 @@ class ManagedPackPage : public QWidget, public BasePage { BaseInstance* m_inst; bool m_loaded = false; + + void onUpdateTaskCompleted(bool did_succeed) const; }; /** Simple page for when we aren't a managed pack. */ diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index e3d7f1f7b..792f9c881 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -252,8 +252,11 @@ void VersionPage::updateButtons(int row) bool VersionPage::reloadPackProfile() { try { - m_profile->reload(Net::Mode::Online); - return true; + auto result = m_profile->reload(Net::Mode::Online); + if (!result) { + QMessageBox::critical(this, tr("Error"), result.error); + } + return result; } catch (const Exception& e) { QMessageBox::critical(this, tr("Error"), e.cause()); return false; diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index a1674455a..7fba08026 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -40,18 +40,29 @@ #include "HintOverrideProxyStyle.h" #include "ThemeManager.h" -SystemTheme::SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme) +// See https://github.com/MultiMC/Launcher/issues/1790 +// or https://github.com/PrismLauncher/PrismLauncher/issues/490 +static const QStringList S_NATIVE_STYLES{ "windows11", "windowsvista", "macos", "system", "windows" }; + +SystemTheme::SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme) { - themeName = isDefaultTheme ? "system" : styleName; - widgetTheme = styleName; - colorPalette = palette; + m_themeName = isDefaultTheme ? "system" : styleName; + m_widgetTheme = styleName; + // NOTE: SystemTheme is reconstructed on page refresh. We can't accurately determine the system palette here + // See also S_NATIVE_STYLES comment + if (S_NATIVE_STYLES.contains(m_themeName)) { + m_colorPalette = defaultPalette; + } else { + auto style = QStyleFactory::create(styleName); + m_colorPalette = style->standardPalette(); + delete style; + } } void SystemTheme::apply(bool initial) { - // See https://github.com/MultiMC/Launcher/issues/1790 - // or https://github.com/PrismLauncher/PrismLauncher/issues/490 - if (initial) { + // See S_NATIVE_STYLES comment + if (initial && S_NATIVE_STYLES.contains(m_themeName)) { QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); return; } @@ -61,35 +72,35 @@ void SystemTheme::apply(bool initial) QString SystemTheme::id() { - return themeName; + return m_themeName; } QString SystemTheme::name() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Windows Vista"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("Windows 11"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("System"); } else { - return themeName; + return m_themeName; } } QString SystemTheme::tooltip() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Widget style trying to look like your win32 theme"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x inspired widget style"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("WinUI 3 inspired Qt widget style"); - } else if (themeName.toLower() == "fusion") { + } else if (m_themeName.toLower() == "fusion") { return QObject::tr("The default Qt widget style"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("Your current system theme"); } else { return ""; @@ -98,12 +109,12 @@ QString SystemTheme::tooltip() QString SystemTheme::qtTheme() { - return widgetTheme; + return m_widgetTheme; } QPalette SystemTheme::colorScheme() { - return colorPalette; + return m_colorPalette; } QString SystemTheme::appStyleSheet() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 7c260fdc4..7ae24c3db 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -38,7 +38,7 @@ class SystemTheme : public ITheme { public: - SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme); + SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme); virtual ~SystemTheme() {} void apply(bool initial) override; @@ -53,7 +53,7 @@ class SystemTheme : public ITheme { QColor fadeColor() override; private: - QPalette colorPalette; - QString widgetTheme; - QString themeName; + QPalette m_colorPalette; + QString m_widgetTheme; + QString m_themeName; }; diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index a9036107c..8de7562d1 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -68,8 +68,8 @@ class ThemeManager { QDir m_applicationThemeFolder{ "themes" }; QDir m_catPacksFolder{ "catpacks" }; std::map> m_catPacks; - QString m_defaultStyle; QPalette m_defaultPalette; + QString m_defaultStyle; LogColors m_logColors; void initializeThemes(); diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index 41def3ba1..02b629162 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -40,7 +40,7 @@ class CheckComboModel : public QIdentityProxyModel { { if (role == Qt::CheckStateRole) { auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString(); - return checked.contains(txt) ? Qt::Checked : Qt::Unchecked; + return m_checked.contains(txt) ? Qt::Checked : Qt::Unchecked; } if (role == Qt::DisplayRole) return QIdentityProxyModel::data(index, Qt::DisplayRole); @@ -50,10 +50,10 @@ class CheckComboModel : public QIdentityProxyModel { { if (role == Qt::CheckStateRole) { auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString(); - if (checked.contains(txt)) { - checked.removeOne(txt); + if (m_checked.contains(txt)) { + m_checked.removeOne(txt); } else { - checked.push_back(txt); + m_checked.push_back(txt); } emit dataChanged(index, index); emit checkStateChanged(); @@ -61,13 +61,13 @@ class CheckComboModel : public QIdentityProxyModel { } return QIdentityProxyModel::setData(index, value, role); } - QStringList getChecked() { return checked; } + QStringList getChecked() { return m_checked; } signals: void checkStateChanged(); private: - QStringList checked; + QStringList m_checked; }; CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(", ") @@ -92,7 +92,7 @@ void CheckComboBox::setSourceModel(QAbstractItemModel* new_model) void CheckComboBox::hidePopup() { - if (!containerMousePress) + if (!m_containerMousePress) QComboBox::hidePopup(); } @@ -138,7 +138,7 @@ bool CheckComboBox::eventFilter(QObject* receiver, QEvent* event) } case QEvent::MouseButtonPress: { auto ev = static_cast(event); - containerMousePress = ev && view()->indexAt(ev->pos()).isValid(); + m_containerMousePress = ev && view()->indexAt(ev->pos()).isValid(); break; } case QEvent::Wheel: diff --git a/launcher/ui/widgets/CheckComboBox.h b/launcher/ui/widgets/CheckComboBox.h index 876c6e3e1..469587762 100644 --- a/launcher/ui/widgets/CheckComboBox.h +++ b/launcher/ui/widgets/CheckComboBox.h @@ -60,5 +60,5 @@ class CheckComboBox : public QComboBox { private: QString m_default_text; QString m_separator; - bool containerMousePress; + bool m_containerMousePress = false; }; \ No newline at end of file diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui index 5431eab60..aabb68329 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.ui +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -47,6 +47,9 @@ true + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + @@ -68,6 +71,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + diff --git a/nix/README.md b/nix/README.md index 5e8ab74c5..b04f33979 100644 --- a/nix/README.md +++ b/nix/README.md @@ -38,9 +38,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; @@ -86,9 +83,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; diff --git a/nix/checks.nix b/nix/checks.nix deleted file mode 100644 index ec219d6f8..000000000 --- a/nix/checks.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - runCommand, - deadnix, - llvmPackages_18, - markdownlint-cli, - nixfmt-rfc-style, - statix, - self, -}: -{ - formatting = - runCommand "check-formatting" - { - nativeBuildInputs = [ - deadnix - llvmPackages_18.clang-tools - markdownlint-cli - nixfmt-rfc-style - statix - ]; - } - '' - cd ${self} - - echo "Running clang-format...." - clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp} - - echo "Running deadnix..." - deadnix --fail - - echo "Running markdownlint..." - markdownlint --dot . - - echo "Running nixfmt..." - nixfmt --check . - - echo "Running statix" - statix check . - - touch $out - ''; -} diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 0b63c6336..7b75949da 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -3,7 +3,7 @@ stdenv, cmake, cmark, - apple-sdk_11, + darwin, extra-cmake-modules, gamemode, ghc_filesystem, @@ -11,32 +11,54 @@ kdePackages, libnbtplusplus, ninja, - nix-filter, self, stripJavaArchivesHook, tomlplusplus, zlib, + msaClientID ? null, gamemodeSupport ? stdenv.hostPlatform.isLinux, }: + assert lib.assertMsg ( gamemodeSupport -> stdenv.hostPlatform.isLinux ) "gamemodeSupport is only available on Linux."; + +let + date = + let + # YYYYMMDD + date' = lib.substring 0 8 self.lastModifiedDate; + year = lib.substring 0 4 date'; + month = lib.substring 4 2 date'; + date = lib.substring 6 2 date'; + in + if (self ? "lastModifiedDate") then + lib.concatStringsSep "-" [ + year + month + date + ] + else + "unknown"; +in + stdenv.mkDerivation { pname = "shatteredprism-unwrapped"; - version = self.shortRev or self.dirtyShortRev or "unknown"; + version = "1.7-unstable-${date}"; - src = nix-filter.lib { - root = self; - include = [ - "buildconfig" - "cmake" - "launcher" - "libraries" - "program_info" - "tests" - ../COPYING.md + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ ../CMakeLists.txt + ../COPYING.md + + ../buildconfig + ../cmake + ../launcher + ../libraries + ../program_info + ../tests ]; }; @@ -63,7 +85,7 @@ stdenv.mkDerivation { tomlplusplus zlib ] - ++ lib.optionals stdenv.hostPlatform.isDarwin [ apple-sdk_11 ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.apple_sdk.frameworks.Cocoa ] ++ lib.optional gamemodeSupport gamemode; hardeningEnable = lib.optionals stdenv.hostPlatform.isLinux [ "pie" ];