Merge remote-tracking branch 'upstream/develop' into develop

Signed-off-by: sneedium <sneed@sneedmc.org>
This commit is contained in:
sneedium 2022-06-14 21:29:00 -04:00
commit 984e5b3b19
Signed by: sneedium
GPG Key ID: 906F66490FBE722F
398 changed files with 19013 additions and 10108 deletions

16
.clang-format Normal file
View File

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

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
open_collective: polymc

View File

@ -1,6 +1,6 @@
name: Bug Report name: Bug Report
description: File a bug report description: File a bug report
labels: [bug, needs-triage] labels: [bug]
body: body:
- type: markdown - type: markdown
attributes: attributes:
@ -8,7 +8,11 @@ body:
If you need help with running Minecraft, please visit us on our Discord before making a bug report. If you need help with running Minecraft, please visit us on our Discord before making a bug report.
Before submitting a bug report, please make sure you have read this *entire* form, and that: Before submitting a bug report, please make sure you have read this *entire* form, and that:
<<<<<<< HEAD
* You have read the [FAQ](https://github.com/Sneederix/SneedMC/wiki/FAQ) and it has not answered your question * You have read the [FAQ](https://github.com/Sneederix/SneedMC/wiki/FAQ) and it has not answered your question
=======
* You have read the [PolyMC wiki](https://polymc.org/wiki/) and it has not answered your question.
>>>>>>> upstream/develop
* Your bug is not caused by Minecraft or any mods you have installed. * Your bug is not caused by Minecraft or any mods you have installed.
* Your issue has not been reported before, [make sure to use the search function!](https://github.com/Sneederix/SneedMC/issues) * Your issue has not been reported before, [make sure to use the search function!](https://github.com/Sneederix/SneedMC/issues)
@ -23,6 +27,13 @@ body:
- macOS - macOS
- Linux - Linux
- Other - Other
- type: textarea
attributes:
label: Version of PolyMC
description: The version of PolyMC used in the bug report.
placeholder: PolyMC 1.2.2
validations:
required: true
- type: textarea - type: textarea
attributes: attributes:
label: Description of bug label: Description of bug

68
.github/ISSUE_TEMPLATE/rfc.yml vendored Normal file
View File

@ -0,0 +1,68 @@
# Template based on https://gitlab.archlinux.org/archlinux/rfcs/-/blob/0ba3b61e987e197f8d1901709409b8564958f78a/rfcs/0000-template.rst
name: Request for Comment (RFC)
description: Propose a larger change and start a discussion.
labels: [rfc]
body:
- type: markdown
attributes:
value: |
### Use this form to suggest a larger change for PolyMC.
- type: textarea
attributes:
label: Goal
description: Short description, 1-2 sentences.
placeholder: Remove the cat from the launcher.
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: |
Introduce the topic. If this is a not-well-known section of PolyMC, a detailed explanation of the background is recommended.
Some example points of discussion:
- What specific problems are you facing right now that you're trying to address?
- Are there any previous discussions? Link to them and summarize them (don't force your readers to read them though!).
- Is there any precedent set by other software? If so, link to resources.
placeholder: I don't like cats. I think many users also don't like cats.
validations:
required: true
- type: textarea
attributes:
label: Specification
description: A concrete, thorough explanation of what is being planned.
placeholder: Remove the cat button and all references to the cat from the codebase. Including resource files.
validations:
required: true
- type: textarea
attributes:
label: Drawbacks
description: Carefully consider every possible objection and issue with your proposal. This section should be updated as feedback comes in from discussion.
placeholder: Some users might like cats.
validations:
required: true
- type: textarea
attributes:
label: Unresolved Questions
description: |
Are there any portions of your proposal which need to be discussed with the community before the RFC can proceed?
Be careful here -- an RFC with a lot of remaining questions is likely to be stalled.
If your RFC is mostly unresolved questions and not too much substance, it may not be ready.
placeholder: Do a lot of users care about the cat?
validations:
required: true
- type: textarea
attributes:
label: Alternatives Considered
description: A list of alternatives, that have been considered and offer equal or similar features to the proposed change.
placeholder: Maybe the cat could be replaced with an axolotl?
validations:
required: true
- type: checkboxes
attributes:
label: This suggestion is unique
options:
- label: I have searched the issue tracker and did not find an issue describing my suggestion, especially not one that has been rejected.
required: true
- type: textarea
attributes:
label: You may use the editor below to elaborate further.

View File

@ -1,6 +1,6 @@
name: Suggestion name: Suggestion
description: Make a suggestion description: Make a suggestion
labels: [idea, needs-triage] labels: [enhancement]
body: body:
- type: markdown - type: markdown
attributes: attributes:

41
.github/scripts/prepare_JREs.sh vendored Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
URL_JDK8="https://api.adoptium.net/v3/binary/version/jdk8u312-b07/linux/x64/jre/hotspot/normal/eclipse"
URL_JDK17="https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jre/hotspot/normal/eclipse"
mkdir -p JREs
pushd JREs
wget --content-disposition "$URL_JDK8"
wget --content-disposition "$URL_JDK17"
for file in *;
do
mkdir temp
re='(OpenJDK([[:digit:]]+)U-jre_x64_linux_hotspot_([[:digit:]]+)(.*).tar.gz)'
if [[ $file =~ $re ]];
then
version_major=${BASH_REMATCH[2]}
version_trailing=${BASH_REMATCH[4]}
if [ $version_major = 17 ];
then
hyphen='-'
else
hyphen=''
fi
version_edit=$(echo $version_trailing | sed -e 's/_/+/g' | sed -e 's/b/-b/g')
dir_name=jdk$hyphen$version_major$version_edit-jre
mkdir jre$version_major
tar -xzf $file -C temp
pushd temp/$dir_name
cp -r . ../../jre$version_major
popd
fi
rm -rf temp
done
popd

19
.github/workflows/backport.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Backport PR to stable
on:
pull_request:
branches: [ develop ]
types: [ closed ]
jobs:
release_pull_request:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'backport')
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Backport PR by cherry-pick-ing
uses: Nathanmalnoury/gh-backport-action@master
with:
pr_branch: 'stable'
github_token: ${{ secrets.GITHUB_TOKEN }}

306
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,306 @@
name: Build
on:
workflow_call:
inputs:
build_type:
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
type: string
default: Debug
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-20.04
- os: ubuntu-20.04
appimage: true
- os: windows-2022
name: "Windows-i686"
msystem: mingw32
- os: windows-2022
name: "Windows-x86_64"
msystem: mingw64
- os: macos-12
macosx_deployment_target: 10.13
runs-on: ${{ matrix.os }}
env:
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
INSTALL_DIR: "install"
INSTALL_PORTABLE_DIR: "install-portable"
INSTALL_APPIMAGE_DIR: "install-appdir"
BUILD_DIR: "build"
CCACHE_VAR: ""
steps:
##
# PREPARE
##
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'true'
- name: 'Setup MSYS2'
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
update: true
install: >-
git
pacboy: >-
toolchain:p
cmake:p
ninja:p
qt5:p
ccache:p
nsis:p
- name: Setup ccache
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.1
with:
key: ${{ matrix.os }}-${{ matrix.appimage }}
- name: Setup ccache (Windows)
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
shell: msys2 {0}
run: |
ccache --set-config=cache_dir='${{ github.workspace }}\.ccache'
ccache --set-config=max_size='500M'
ccache --set-config=compression=true
ccache -p # Show config
ccache -z # Zero stats
- name: Use ccache on Debug builds only
if: inputs.build_type == 'Debug'
shell: bash
run: |
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
- name: Retrieve ccache cache (Windows)
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.2
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-${{ matrix.msystem }}
restore-keys: |
${{ matrix.os }}-${{ matrix.msystem }}
- name: Set short version
shell: bash
run: |
ver_short=`git rev-parse --short HEAD`
echo "VERSION=$ver_short" >> $GITHUB_ENV
- name: Install Qt (macOS)
if: runner.os == 'macOS'
run: |
brew update
brew install qt@5 ninja
- name: Update Qt (AppImage)
if: runner.os == 'Linux' && matrix.appimage == true
run: |
sudo add-apt-repository ppa:savoury1/qt-5-15
sudo add-apt-repository ppa:savoury1/kde-5-80
sudo add-apt-repository ppa:savoury1/gpg
sudo add-apt-repository ppa:savoury1/ffmpeg4
- name: Install Qt (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get -y update
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.appimage == true
run: |
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
##
# CONFIGURE
##
- name: Configure CMake (macOS)
if: runner.os == 'macOS'
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DQt5_DIR=/usr/local/opt/qt@5 -DCMAKE_PREFIX_PATH=/usr/local/opt/qt@5 -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja
- name: Configure CMake (Windows)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja
##
# BUILD
##
- name: Build
if: runner.os != 'Windows'
run: |
cmake --build ${{ env.BUILD_DIR }}
- name: Build (Windows)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cmake --build ${{ env.BUILD_DIR }}
##
# PACKAGE BUILDS
##
- name: Package (macOS)
if: runner.os == 'macOS'
run: |
cmake --install ${{ env.BUILD_DIR }}
cd ${{ env.INSTALL_DIR }}
chmod +x "PolyMC.app/Contents/MacOS/polymc"
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PolyMC.app/Contents/MacOS/polymc"
tar -czf ../PolyMC.tar.gz *
- name: Package (Windows)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cmake --install ${{ env.BUILD_DIR }}
cd ${{ env.INSTALL_DIR }}
if [ "${{ matrix.msystem }}" == "mingw32" ]; then
cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./
elif [ "${{ matrix.msystem }}" == "mingw64" ]; then
cp /mingw64/bin/libcrypto-1_1-x64.dll /mingw64/bin/libssl-1_1-x64.dll ./
fi
- name: Package (Windows, portable)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
- name: Package (Windows, installer)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
- name: Package (Linux)
if: runner.os == 'Linux' && matrix.appimage != true
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
cd ${{ env.INSTALL_DIR }}
tar --owner root --group root -czf ../PolyMC.tar.gz *
- name: Package (Linux, portable)
if: runner.os == 'Linux' && matrix.appimage != true
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
cd ${{ env.INSTALL_PORTABLE_DIR }}
tar -czf ../PolyMC-portable.tar.gz *
- name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.appimage == true
shell: bash
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
export OUTPUT="PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk
cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
export LD_LIBRARY_PATH
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg
##
# UPLOAD BUILDS
##
- name: Upload binary tarball (macOS)
if: runner.os == 'macOS'
uses: actions/upload-artifact@v3
with:
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC.tar.gz
- name: Upload binary zip (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v3
with:
name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_DIR }}/**
- name: Upload binary zip (Windows, portable)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v3
with:
name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
- name: Upload installer (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v3
with:
name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC-Setup.exe
- name: Upload binary tarball (Linux)
if: runner.os == 'Linux' && matrix.appimage != true
uses: actions/upload-artifact@v3
with:
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC.tar.gz
- name: Upload binary tarball (Linux, portable)
if: runner.os == 'Linux' && matrix.appimage != true
uses: actions/upload-artifact@v3
with:
name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC-portable.tar.gz
- name: Upload AppImage (Linux)
if: runner.os == 'Linux' && matrix.appimage == true
uses: actions/upload-artifact@v3
with:
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage

61
.github/workflows/pr-comment.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Comment on pull request
on:
workflow_run:
workflows: ['Build Application']
types: [completed]
jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v5
with:
# This snippet is public-domain, taken from
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
script: |
async function upsertComment(owner, repo, issue_number, purpose, body) {
const {data: comments} = await github.rest.issues.listComments(
{owner, repo, issue_number});
const marker = `<!-- bot: ${purpose} -->`;
body = marker + "\n" + body;
const existing = comments.filter((c) => c.body.includes(marker));
if (existing.length > 0) {
const last = existing[existing.length - 1];
core.info(`Updating comment ${last.id}`);
await github.rest.issues.updateComment({
owner, repo,
body,
comment_id: last.id,
});
} else {
core.info(`Creating a comment in issue / PR #${issue_number}`);
await github.rest.issues.createComment({issue_number, body, owner, repo});
}
}
const {owner, repo} = context.repo;
const run_id = ${{github.event.workflow_run.id}};
const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
if (!pull_requests.length) {
return core.error("This workflow doesn't match any pull requests!");
}
const artifacts = await github.paginate(
github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
for (const art of artifacts) {
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
core.info("Review thread message body:", body);
for (const pr of pull_requests) {
await upsertComment(owner, repo, pr.number,
"nightly-link", body);
}

30
.github/workflows/trigger_builds.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Build Application
on:
push:
branches-ignore:
- 'stable'
paths-ignore:
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
pull_request:
paths-ignore:
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
workflow_dispatch:
jobs:
build_debug:
name: Build Debug
uses: ./.github/workflows/build.yml
with:
build_type: Debug

76
.github/workflows/trigger_release.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: Build Application and Make Release
on:
push:
tags:
- '*'
jobs:
build_release:
name: Build Release
uses: ./.github/workflows/build.yml
with:
build_type: Release
create_release:
needs: build_release
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'true'
path: 'PolyMC-source'
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Grab and store version
run: |
tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
echo "VERSION=$tag_name" >> $GITHUB_ENV
- name: Package artifacts properly
run: |
mv ${{ github.workspace }}/PolyMC-source PolyMC-${{ env.VERSION }}
mv PolyMC-Linux-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz
mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
mv PolyMC-macOS*/PolyMC.tar.gz PolyMC-macOS-${{ env.VERSION }}.tar.gz
tar -czf PolyMC-${{ env.VERSION }}.tar.gz PolyMC-${{ env.VERSION }}
for d in PolyMC-Windows-*; do
cd "${d}" || continue
ARCH="$(echo -n ${d} | cut -d '-' -f 3)"
INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)"
NAME="PolyMC-Windows-${ARCH}"
test -z "${PORT}" || NAME="${NAME}-Portable"
test -z "${INST}" || mv PolyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
cd ..
done
- name: Create release
id: create_release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
name: PolyMC ${{ env.VERSION }}
draft: true
prerelease: false
files: |
PolyMC-Linux-${{ env.VERSION }}.tar.gz
PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
PolyMC-Windows-i686-${{ env.VERSION }}.zip
PolyMC-Windows-i686-Portable-${{ env.VERSION }}.zip
PolyMC-Windows-i686-Setup-${{ env.VERSION }}.exe
PolyMC-Windows-x86_64-${{ env.VERSION }}.zip
PolyMC-Windows-x86_64-Portable-${{ env.VERSION }}.zip
PolyMC-Windows-x86_64-Setup-${{ env.VERSION }}.exe
PolyMC-macOS-${{ env.VERSION }}.tar.gz
PolyMC-${{ env.VERSION }}.tar.gz

5
.gitignore vendored
View File

@ -14,6 +14,7 @@ CMakeLists.txt.user.*
/.project /.project
/.settings /.settings
/.idea /.idea
/.vscode
cmake-build-*/ cmake-build-*/
Debug Debug
@ -42,3 +43,7 @@ run/
# Nix/NixOS # Nix/NixOS
result/ result/
# Flatpak
.flatpak-builder
flatbuild

4
.gitmodules vendored
View File

@ -1,7 +1,7 @@
[submodule "depends/libnbtplusplus"] [submodule "depends/libnbtplusplus"]
path = libraries/libnbtplusplus path = libraries/libnbtplusplus
url = https://github.com/MultiMC/libnbtplusplus.git url = https://github.com/PolyMC/libnbtplusplus.git
pushurl = git@github.com:MultiMC/libnbtplusplus.git pushurl = git@github.com:PolyMC/libnbtplusplus.git
[submodule "libraries/quazip"] [submodule "libraries/quazip"]
path = libraries/quazip path = libraries/quazip

View File

@ -1,6 +1,6 @@
# Build Instructions # Build Instructions
# Contents Build instructions are available on [the website](https://polymc.org/wiki/development/build-instructions/).
- [Getting the source](#getting-the-source) - [Getting the source](#getting-the-source)
- [Linux](#linux) - [Linux](#linux)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1) cmake_minimum_required(VERSION 3.15) # minimum version required by QuaZip
if(WIN32) if(WIN32)
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
@ -6,7 +6,7 @@ if(WIN32)
endif() endif()
project(Launcher) project(Launcher)
enable_testing() include(CTest)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD) string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
if(IS_IN_SOURCE_BUILD) if(IS_IN_SOURCE_BUILD)
@ -34,25 +34,46 @@ set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) include(GenerateExportHeader)
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "-Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE) if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif() endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
# Fix build with Qt 5.13 # Fix build with Qt 5.13
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
# set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
option(ENABLE_LTO "Enable Link Time Optimization" off)
if(ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
if(ipo_supported AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel"))
message(STATUS "IPO / LTO enabled")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
elseif(ipo_supported)
message(STATUS "Not enabling IPO / LTO on debug builds")
else()
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
endif()
endif()
##################################### Set Application options ##################################### ##################################### Set Application options #####################################
######## Set URLs ######## ######## Set URLs ########
set(Launcher_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch SneedMC's news RSS feed from.") set(Launcher_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch SneedMC's news RSS feed from.")
set(Launcher_NEWS_OPEN_URL "https://multimc.org/posts.html" CACHE STRING "URL that gets opened when the user clicks 'More News'") set(Launcher_NEWS_OPEN_URL "https://multimc.org/posts.html" CACHE STRING "URL that gets opened when the user clicks 'More News'")
set(Launcher_HELP_URL "" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_MINOR 4)
set(Launcher_VERSION_HOTFIX 6) set(Launcher_VERSION_HOTFIX 0)
# Build number # Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@ -75,17 +96,37 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/Sneederix/SneedMC/issues" CACHE
# Translations Platform URL # Translations Platform URL
set(Launcher_TRANSLATIONS_URL "" CACHE STRING "URL for the translations platform.") set(Launcher_TRANSLATIONS_URL "" CACHE STRING "URL for the translations platform.")
# Matrix Space
set(Launcher_MATRIX_URL "https://matrix.to/#/#polymc:matrix.org" CACHE STRING "URL to the Matrix Space")
# Discord URL # Discord URL
set(Launcher_DISCORD_URL "https://discord.gg/Z52pwxWCHP" CACHE STRING "URL for the Discord guild.") set(Launcher_DISCORD_URL "https://discord.gg/Z52pwxWCHP" CACHE STRING "URL for the Discord guild.")
# Subreddit URL # Subreddit URL
set(Launcher_SUBREDDIT_URL "" CACHE STRING "URL for the subreddit.") set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRING "URL for the subreddit.")
# Builds # Builds
# TODO: Launcher_FORCE_BUNDLED_LIBS should be off in the future, but as of QuaZip 1.2, we can't do that yet. set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_FORCE_BUNDLED_LIBS ON CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against") set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against")
# API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
# of these platforms, please change these API keys beforehand.
# Be aware that if you were to use these API keys for malicious purposes they might get revoked, which might cause
# breakage to thousands of users.
# If you don't plan to use these features of this software, you can just remove these values.
# By using this key in your builds you accept the terms of use laid down in
# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use
set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
# By using this key in your builds you accept the terms and conditions laid down in
# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions
# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key")
#### Check the current Git commit and branch #### Check the current Git commit and branch
include(GetGitRevisionDescription) include(GetGitRevisionDescription)
@ -95,6 +136,8 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}") message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}") set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
set(Launcher_RELEASE_VERSION_NAME4 "${Launcher_RELEASE_VERSION_NAME}.0")
set(Launcher_RELEASE_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_HOTFIX},0")
string(TIMESTAMP TODAY "%Y-%m-%d") string(TIMESTAMP TODAY "%Y-%m-%d")
set(Launcher_RELEASE_TIMESTAMP "${TODAY}") set(Launcher_RELEASE_TIMESTAMP "${TODAY}")
@ -110,7 +153,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
if(NOT Launcher_FORCE_BUNDLED_LIBS) if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 REQUIRED) find_package(QuaZip-Qt5 1.3 QUIET)
endif() endif()
if (NOT QuaZip-Qt5_FOUND) if (NOT QuaZip-Qt5_FOUND)
set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE)
@ -140,33 +183,18 @@ add_subdirectory(program_info)
####################################### Install layout ####################################### ####################################### Install layout #######################################
# How to install the build results if(NOT (UNIX AND APPLE))
set(Launcher_LAYOUT "auto" CACHE STRING "The layout for the launcher installation (auto, win-bundle, lin-nodeps, lin-system, mac-bundle)") # Install "portable.txt" if selected component is "portable"
set_property(CACHE Launcher_LAYOUT PROPERTY STRINGS auto win-bundle lin-nodeps lin-system mac-bundle) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
endif()
if(Launcher_LAYOUT STREQUAL "auto")
if(UNIX AND APPLE) if(UNIX AND APPLE)
set(Launcher_LAYOUT_REAL "mac-bundle")
elseif(UNIX)
set(Launcher_LAYOUT_REAL "lin-nodeps")
elseif(WIN32)
set(Launcher_LAYOUT_REAL "win-bundle")
else()
message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
endif()
else()
set(Launcher_LAYOUT_REAL ${Launcher_LAYOUT})
endif()
if(Launcher_LAYOUT_REAL STREQUAL "mac-bundle")
set(BINARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") set(BINARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources") set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources")
set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars") set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars")
set(BUNDLE_DEST_DIR ".")
# Apps to bundle # Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app")
@ -178,7 +206,7 @@ if(Launcher_LAYOUT_REAL STREQUAL "mac-bundle")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2021 ${Launcher_Copyright}") set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}")
# directories to look for dependencies # directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
@ -189,13 +217,14 @@ if(Launcher_LAYOUT_REAL STREQUAL "mac-bundle")
# Add the icon # Add the icon
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns) install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
elseif(Launcher_LAYOUT_REAL STREQUAL "lin-nodeps") elseif(UNIX)
set(BINARY_DEST_DIR "bin") set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "bin") set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
set(PLUGIN_DEST_DIR "plugins") set(JARS_DEST_DIR "share/jars")
set(BUNDLE_DEST_DIR ".") set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
set(RESOURCES_DEST_DIR ".") set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory")
set(JARS_DEST_DIR "bin/jars") set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory")
# install as bundle with no dependencies included # install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps") set(INSTALL_BUNDLE "nodeps")
@ -203,37 +232,22 @@ elseif(Launcher_LAYOUT_REAL STREQUAL "lin-nodeps")
# Set RPATH # Set RPATH
SET(Launcher_BINARY_RPATH "$ORIGIN/") SET(Launcher_BINARY_RPATH "$ORIGIN/")
# Install basic runner script # jars path is determined on runtime, relative to "Application root path", generally /usr or the root of the portable bundle
set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_JARS_LOCATION=${JARS_DEST_DIR}")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR} RENAME "${Launcher_APP_BINARY_NAME}.6")
# Install basic runner script if component "portable" is selected
configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY) configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY)
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION ${BUNDLE_DEST_DIR} RENAME ${Launcher_Name}) install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION "." RENAME ${Launcher_Name} COMPONENT portable EXCLUDE_FROM_ALL)
elseif(Launcher_LAYOUT_REAL STREQUAL "lin-system") elseif(WIN32)
set(Launcher_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
set(Launcher_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
set(Launcher_SHARE_DEST_DIR "share/sneedmc" CACHE STRING "Path to the shared data directory")
set(JARS_DEST_DIR "${Launcher_SHARE_DEST_DIR}/jars")
set(Launcher_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
set(Launcher_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory")
set(Launcher_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
set(BINARY_DEST_DIR ${Launcher_BINARY_DEST_DIR})
set(LIBRARY_DEST_DIR ${Launcher_LIBRARY_DEST_DIR})
MESSAGE(STATUS "Compiling for linux system with ${Launcher_SHARE_DEST_DIR} and LAUNCHER_LINUX_DATADIR")
SET(Launcher_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DLAUNCHER_LINUX_DATADIR")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${Launcher_DESKTOP_DEST_DIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${Launcher_METAINFO_DEST_DIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${Launcher_ICON_DEST_DIR})
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
elseif(Launcher_LAYOUT_REAL STREQUAL "win-bundle")
set(BINARY_DEST_DIR ".") set(BINARY_DEST_DIR ".")
set(LIBRARY_DEST_DIR ".") set(LIBRARY_DEST_DIR ".")
set(PLUGIN_DEST_DIR ".") set(PLUGIN_DEST_DIR ".")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".") set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "jars") set(JARS_DEST_DIR "jars")
@ -246,7 +260,7 @@ elseif(Launcher_LAYOUT_REAL STREQUAL "win-bundle")
# install as bundle # install as bundle
set(INSTALL_BUNDLE "full") set(INSTALL_BUNDLE "full")
else() else()
message(FATAL_ERROR "No sensible install layout set.") message(FATAL_ERROR "Platform not supported")
endif() endif()
################################ Included Libs ################################ ################################ Included Libs ################################
@ -269,6 +283,8 @@ if (FORCE_BUNDLED_QUAZIP)
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.
set(QUAZIP_INSTALL 0) set(QUAZIP_INSTALL 0)
add_subdirectory(libraries/quazip) # zip manipulation library add_subdirectory(libraries/quazip) # zip manipulation library
else()
message(STATUS "Using system QuaZip")
endif() endif()
add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader

View File

@ -5,8 +5,7 @@
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, version 3.
(at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of

View File

@ -7,14 +7,19 @@
SneedMC is a sneedful launcher for Minecraft that focuses on sneedictability, long term sneedility, and sneedicity. SneedMC is a sneedful launcher for Minecraft that focuses on sneedictability, long term sneedility, and sneedicity.
<<<<<<< HEAD
=======
This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC.
If you want to read about why this fork was created, check out [our FAQ page](https://polymc.org/wiki/overview/faq/).
>>>>>>> upstream/develop
<br> <br>
# Installation # Installation
- All packages (archived by version) can be found [here](https://packages.polymc.org/) ([latest](https://packages.polymc.org/latest)).
- Last build status: https://jenkins.polymc.org/job/PolyMC/lastBuild/
## 🐧 Linux - All downloads and instructions for PolyMC can be found [here](https://polymc.org/download/)
- Last build status: https://github.com/PolyMC/PolyMC/actions
<<<<<<< HEAD
### <img src="https://www.vectorlogo.zone/logos/linuxfoundation/linuxfoundation-icon.svg" height="20" alt=""/> Cross-distro packages ### <img src="https://www.vectorlogo.zone/logos/linuxfoundation/linuxfoundation-icon.svg" height="20" alt=""/> Cross-distro packages
<a href='https://flathub.org/apps/details/org.polymc.PolyMC'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a> <a href='https://flathub.org/apps/details/org.polymc.PolyMC'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
@ -119,11 +124,18 @@ In both cases you need X11, Qt5 and Java installed. Both files are 64bit only.
For OpenBSD available are [gzipped 32-bit binaries](http://glowiak.github.io/file/polymc-latest-obsd32-raw), download, unpack and run. For OpenBSD available are [gzipped 32-bit binaries](http://glowiak.github.io/file/polymc-latest-obsd32-raw), download, unpack and run.
You need X11, Qt5 and Java installed. You need X11, Qt5 and Java installed.
=======
>>>>>>> upstream/develop
## Development Builds ## Development Builds
There are per-commit development builds available [here](https://github.com/PolyMC/PolyMC/actions). These have debug information in the binaries, so their file sizes are relatively larger. There are per-commit development builds available [here](https://github.com/PolyMC/PolyMC/actions). These have debug information in the binaries, so their file sizes are relatively larger.
Builds are provided for Linux, AppImage on Linux, Windows, and macOS. Portable builds are provided for AppImage on Linux, Windows, and macOS.
For Debian and Arch, you can use these packages for the latest development versions:
[![polymc-git](https://img.shields.io/badge/aur-polymc--git-blue)](https://aur.archlinux.org/packages/polymc-git/)
[![polymc-git](https://img.shields.io/badge/mpr-polymc--git-orange)](https://mpr.makedeb.org/packages/polymc-git)
For flatpak, you can use [flathub-beta](https://discourse.flathub.org/t/how-to-use-flathub-beta/2111)
# Help & Support # Help & Support
@ -133,14 +145,26 @@ Feel free to create an issue if you need help. However, you might find it easier
For people who don't want to use Discord, we have a Matrix Space which is bridged to the Discord server: For people who don't want to use Discord, we have a Matrix Space which is bridged to the Discord server:
<<<<<<< HEAD
[![SneedMC Space](https://img.shields.io/matrix/polymc:polymc.org?label=SneedMC%20Space&server_fqdn=matrix.polymc.org)](https://matrix.to/#/#polymc:polymc.org) [![SneedMC Space](https://img.shields.io/matrix/polymc:polymc.org?label=SneedMC%20Space&server_fqdn=matrix.polymc.org)](https://matrix.to/#/#polymc:polymc.org)
=======
[![PolyMC Space](https://img.shields.io/matrix/polymc:matrix.org?label=PolyMC%20space)](https://matrix.to/#/#polymc:matrix.org)
>>>>>>> upstream/develop
If there are any issues with the space or you are using a client that does not support the feature here are the individual rooms: If there are any issues with the space or you are using a client that does not support the feature here are the individual rooms:
[![Support](https://img.shields.io/matrix/support:polymc.org?label=%23support&server_fqdn=matrix.polymc.org)](https://matrix.to/#/#support:polymc.org) [![Development](https://img.shields.io/matrix/polymc-development:matrix.org?label=PolyMC%20Development)](https://matrix.to/#/#polymc-development:matrix.org)
[![Discussion](https://img.shields.io/matrix/discussion:polymc.org?label=%23discussion&server_fqdn=matrix.polymc.org)](https://matrix.to/#/#discussion:polymc.org) [![Discussion](https://img.shields.io/matrix/polymc-discussion:matrix.org?label=PolyMC%20Discussion)](https://matrix.to/#/#polymc-discussion:matrix.org)
[![Development](https://img.shields.io/matrix/development:polymc.org?label=%23development&server_fqdn=matrix.polymc.org)](https://matrix.to/#/#development:polymc.org) [![Github](https://img.shields.io/matrix/polymc-github:matrix.org?label=PolyMC%20Github)](https://matrix.to/#/#polymc-github:matrix.org)
[![News](https://img.shields.io/matrix/news:polymc.org?label=%23news&server_fqdn=matrix.polymc.org)](https://matrix.to/#/#news:polymc.org) [![Maintainers](https://img.shields.io/matrix/polymc-maintainers:matrix.org?label=PolyMC%20Maintainers)](https://matrix.to/#/#polymc-maintainers:matrix.org)
[![News](https://img.shields.io/matrix/polymc-news:matrix.org?label=PolyMC%20News)](https://matrix.to/#/#polymc-news:matrix.org)
[![Offtopic](https://img.shields.io/matrix/polymc-offtopic:matrix.org?label=PolyMC%20Offtopic)](https://matrix.to/#/#polymc-offtopic:matrix.org)
[![Support](https://img.shields.io/matrix/polymc-support:matrix.org?label=PolyMC%20Support)](https://matrix.to/#/#polymc-support:matrix.org)
[![Voice](https://img.shields.io/matrix/polymc-voice:matrix.org?label=PolyMC%20Voice)](https://matrix.to/#/#polymc-voice:matrix.org)
We also have a subreddit you can post your issues and suggestions on:
[r/PolyMCLauncher](https://www.reddit.com/r/PolyMCLauncher/)
# Development # Development
@ -148,9 +172,14 @@ If you want to contribute to SneedMC you might find it useful to join our Discor
## Building ## Building
<<<<<<< HEAD
If you want to build SneedMC yourself, check [BUILD.md](BUILD.md) for build instructions. If you want to build SneedMC yourself, check [BUILD.md](BUILD.md) for build instructions.
=======
If you want to build PolyMC yourself, check [Build Instructions](https://polymc.org/wiki/development/build-instructions/) for build instructions.
>>>>>>> upstream/develop
## Code formatting ## Code formatting
Just follow the existing formatting. Just follow the existing formatting.
In general, in order of importance: In general, in order of importance:
@ -165,6 +194,41 @@ In general, in order of importance:
The translation effort for SneedMC is hosted on [Weblate](https://hosted.weblate.org/projects/polymc/polymc/) and information about translating SneedMC is available at https://github.com/PolyMC/Translations The translation effort for SneedMC is hosted on [Weblate](https://hosted.weblate.org/projects/polymc/polymc/) and information about translating SneedMC is available at https://github.com/PolyMC/Translations
## Download information
To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/blob/master/src/download.md)
## Forking/Redistributing/Custom builds policy ## Forking/Redistributing/Custom builds policy
<<<<<<< HEAD
SNEED SNEED
=======
We don't care what you do with your fork/custom build as long as you do the following as a basic courtesy:
- Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility)
- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org).
- Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
If you have any questions or want any clarification on the above conditions please make an issue and ask us.
Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
All launcher code is available under the GPL-3.0-only license.
The logo and related assets are under the CC BY-SA 4.0 license.
## Sponsors
Thank you to all our generous backers over at Open Collective! Support PolyMC by [becoming a backer](https://opencollective.com/polymc).
[![OpenCollective Backers](https://opencollective.com/polymc/backers.svg?width=890&limit=1000)](https://opencollective.com/polymc#backers)
Also, thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/).
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/opensource/)
Additionally, thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes!
<a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a>
>>>>>>> upstream/develop

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BuildConfig.h" #include "BuildConfig.h"
#include <QObject> #include <QObject>
@ -24,10 +59,18 @@ Config::Config()
VERSION_BUILD = @Launcher_VERSION_BUILD@; VERSION_BUILD = @Launcher_VERSION_BUILD@;
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@"; BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
<<<<<<< HEAD
=======
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
>>>>>>> upstream/develop
GIT_COMMIT = "@Launcher_GIT_COMMIT@"; GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@"; GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";
if(GIT_REFSPEC.startsWith("refs/heads/")) if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND"))
{
VERSION_CHANNEL = QStringLiteral("stable");
}
else if(GIT_REFSPEC.startsWith("refs/heads/"))
{ {
VERSION_CHANNEL = GIT_REFSPEC; VERSION_CHANNEL = GIT_REFSPEC;
VERSION_CHANNEL.remove("refs/heads/"); VERSION_CHANNEL.remove("refs/heads/");
@ -44,12 +87,15 @@ Config::Config()
VERSION_STR = "@Launcher_VERSION_STRING@"; VERSION_STR = "@Launcher_VERSION_STRING@";
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@"; NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@"; NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
HELP_URL = "@Launcher_HELP_URL@";
IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@"; IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@";
MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@"; MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@";
CURSEFORGE_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@"; META_URL = "@Launcher_META_URL@";
BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@"; BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@"; TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@";
DISCORD_URL = "@Launcher_DISCORD_URL@"; DISCORD_URL = "@Launcher_DISCORD_URL@";
SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@"; SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@";
} }

View File

@ -1,11 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QString> #include <QString>
/** /**
* \brief The Config class holds all the build-time information passed from the build system. * \brief The Config class holds all the build-time information passed from the build system.
*/ */
class Config class Config {
{
public: public:
Config(); Config();
QString LAUNCHER_NAME; QString LAUNCHER_NAME;
@ -28,14 +63,23 @@ public:
/// A short string identifying this build's platform. For example, "lin64" or "win32". /// A short string identifying this build's platform. For example, "lin64" or "win32".
QString BUILD_PLATFORM; QString BUILD_PLATFORM;
<<<<<<< HEAD
=======
/// URL for the updater's channel
QString UPDATER_BASE;
>>>>>>> upstream/develop
/// User-Agent to use. /// User-Agent to use.
QString USER_AGENT; QString USER_AGENT;
/// User-Agent to use for uncached requests. /// User-Agent to use for uncached requests.
QString USER_AGENT_UNCACHED; QString USER_AGENT_UNCACHED;
<<<<<<< HEAD
QString VERSION_CHANNEL; QString VERSION_CHANNEL;
=======
>>>>>>> upstream/develop
/// The git commit hash of this build /// The git commit hash of this build
QString GIT_COMMIT; QString GIT_COMMIT;
@ -56,6 +100,11 @@ public:
*/ */
QString NEWS_OPEN_URL; QString NEWS_OPEN_URL;
/**
* URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help
*/
QString HELP_URL;
/** /**
* Client ID you can get from Imgur when you register an application * Client ID you can get from Imgur when you register an application
*/ */
@ -66,6 +115,11 @@ public:
*/ */
QString MSA_CLIENT_ID; QString MSA_CLIENT_ID;
/**
* Client API key for CurseForge
*/
QString CURSEFORGE_API_KEY;
/** /**
* Metadata repository URL prefix * Metadata repository URL prefix
*/ */
@ -73,6 +127,7 @@ public:
QString BUG_TRACKER_URL; QString BUG_TRACKER_URL;
QString TRANSLATIONS_URL; QString TRANSLATIONS_URL;
QString MATRIX_URL;
QString DISCORD_URL; QString DISCORD_URL;
QString SUBREDDIT_URL; QString SUBREDDIT_URL;
@ -80,14 +135,29 @@ public:
QString LIBRARY_BASE = "https://libraries.minecraft.net/"; QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString AUTH_BASE = "https://authserver.mojang.com/"; QString AUTH_BASE = "https://authserver.mojang.com/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/"; QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
<<<<<<< HEAD
QString FMLLIBS_BASE_URL = "https://files.multimc.org/fmllibs/"; QString FMLLIBS_BASE_URL = "https://files.multimc.org/fmllibs/";
QString TRANSLATIONS_BASE_URL = "https://files.multimc.org/translations/"; QString TRANSLATIONS_BASE_URL = "https://files.multimc.org/translations/";
=======
QString FMLLIBS_BASE_URL = "https://files.polymc.org/fmllibs/";
QString TRANSLATIONS_BASE_URL = "https://i18n.polymc.org/";
>>>>>>> upstream/develop
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/"; QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/"; QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";
QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/"; QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/";
QString ATL_API_BASE_URL = "https://api.atlauncher.com/v1/";
QString TECHNIC_API_BASE_URL = "https://api.technicpack.net/";
/**
* The build that is reported to the Technic API.
*/
QString TECHNIC_API_BUILD = "multimc";
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
/** /**
* \brief Converts the Version to a string. * \brief Converts the Version to a string.
@ -97,4 +167,3 @@ public:
}; };
extern const Config BuildConfig; extern const Config BuildConfig;

View File

@ -1,786 +0,0 @@
# - Functions to help assemble a standalone bundle application.
# A collection of CMake utility functions useful for dealing with .app
# bundles on the Mac and bundle-like directories on any OS.
#
# The following functions are provided by this module:
# fixup_bundle
# copy_and_fixup_bundle
# verify_app
# get_bundle_main_executable
# get_dotapp_dir
# get_bundle_and_executable
# get_bundle_all_executables
# get_item_key
# clear_bundle_keys
# set_bundle_key_values
# get_bundle_keys
# copy_resolved_item_into_bundle
# copy_resolved_framework_into_bundle
# fixup_bundle_item
# verify_bundle_prerequisites
# verify_bundle_symlinks
# Requires CMake 2.6 or greater because it uses function, break and
# PARENT_SCOPE. Also depends on GetPrerequisites.cmake.
#
# FIXUP_BUNDLE(<app> <libs> <dirs>)
# Fix up a bundle in-place and make it standalone, such that it can be
# drag-n-drop copied to another machine and run on that machine as long as all
# of the system libraries are compatible.
#
# If you pass plugins to fixup_bundle as the libs parameter, you should install
# them or copy them into the bundle before calling fixup_bundle. The "libs"
# parameter is a list of libraries that must be fixed up, but that cannot be
# determined by otool output analysis. (i.e., plugins)
#
# Gather all the keys for all the executables and libraries in a bundle, and
# then, for each key, copy each prerequisite into the bundle. Then fix each one
# up according to its own list of prerequisites.
#
# Then clear all the keys and call verify_app on the final bundle to ensure
# that it is truly standalone.
#
# COPY_AND_FIXUP_BUNDLE(<src> <dst> <libs> <dirs>)
# Makes a copy of the bundle <src> at location <dst> and then fixes up the
# new copied bundle in-place at <dst>...
#
# VERIFY_APP(<app>)
# Verifies that an application <app> appears valid based on running analysis
# tools on it. Calls "message(FATAL_ERROR" if the application is not verified.
#
# GET_BUNDLE_MAIN_EXECUTABLE(<bundle> <result_var>)
# The result will be the full path name of the bundle's main executable file
# or an "error:" prefixed string if it could not be determined.
#
# GET_DOTAPP_DIR(<exe> <dotapp_dir_var>)
# Returns the nearest parent dir whose name ends with ".app" given the full
# path to an executable. If there is no such parent dir, then simply return
# the dir containing the executable.
#
# The returned directory may or may not exist.
#
# GET_BUNDLE_AND_EXECUTABLE(<app> <bundle_var> <executable_var> <valid_var>)
# Takes either a ".app" directory name or the name of an executable
# nested inside a ".app" directory and returns the path to the ".app"
# directory in <bundle_var> and the path to its main executable in
# <executable_var>
#
# GET_BUNDLE_ALL_EXECUTABLES(<bundle> <exes_var>)
# Scans the given bundle recursively for all executable files and accumulates
# them into a variable.
#
# GET_ITEM_KEY(<item> <key_var>)
# Given a file (item) name, generate a key that should be unique considering
# the set of libraries that need copying or fixing up to make a bundle
# standalone. This is essentially the file name including extension with "."
# replaced by "_"
#
# This key is used as a prefix for CMake variables so that we can associate a
# set of variables with a given item based on its key.
#
# CLEAR_BUNDLE_KEYS(<keys_var>)
# Loop over the list of keys, clearing all the variables associated with each
# key. After the loop, clear the list of keys itself.
#
# Caller of get_bundle_keys should call clear_bundle_keys when done with list
# of keys.
#
# SET_BUNDLE_KEY_VALUES(<keys_var> <context> <item> <exepath> <dirs>
# <copyflag>)
# Add a key to the list (if necessary) for the given item. If added,
# also set all the variables associated with that key.
#
# GET_BUNDLE_KEYS(<app> <libs> <dirs> <keys_var>)
# Loop over all the executable and library files within the bundle (and given
# as extra <libs>) and accumulate a list of keys representing them. Set
# values associated with each key such that we can loop over all of them and
# copy prerequisite libs into the bundle and then do appropriate
# install_name_tool fixups.
#
# COPY_RESOLVED_ITEM_INTO_BUNDLE(<resolved_item> <resolved_embedded_item>)
# Copy a resolved item into the bundle if necessary. Copy is not necessary if
# the resolved_item is "the same as" the resolved_embedded_item.
#
# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE(<resolved_item> <resolved_embedded_item>)
# Copy a resolved framework into the bundle if necessary. Copy is not necessary
# if the resolved_item is "the same as" the resolved_embedded_item.
#
# By default, BU_COPY_FULL_FRAMEWORK_CONTENTS is not set. If you want full
# frameworks embedded in your bundles, set BU_COPY_FULL_FRAMEWORK_CONTENTS to
# ON before calling fixup_bundle. By default,
# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE copies the framework dylib itself plus
# the framework Resources directory.
#
# FIXUP_BUNDLE_ITEM(<resolved_embedded_item> <exepath> <dirs>)
# Get the direct/non-system prerequisites of the resolved embedded item. For
# each prerequisite, change the way it is referenced to the value of the
# _EMBEDDED_ITEM keyed variable for that prerequisite. (Most likely changing to
# an "@executable_path" style reference.)
#
# This function requires that the resolved_embedded_item be "inside" the bundle
# already. In other words, if you pass plugins to fixup_bundle as the libs
# parameter, you should install them or copy them into the bundle before
# calling fixup_bundle. The "libs" parameter is a list of libraries that must
# be fixed up, but that cannot be determined by otool output analysis. (i.e.,
# plugins)
#
# Also, change the id of the item being fixed up to its own _EMBEDDED_ITEM
# value.
#
# Accumulate changes in a local variable and make *one* call to
# install_name_tool at the end of the function with all the changes at once.
#
# If the BU_CHMOD_BUNDLE_ITEMS variable is set then bundle items will be
# marked writable before install_name_tool tries to change them.
#
# VERIFY_BUNDLE_PREREQUISITES(<bundle> <result_var> <info_var>)
# Verifies that the sum of all prerequisites of all files inside the bundle
# are contained within the bundle or are "system" libraries, presumed to exist
# everywhere.
#
# VERIFY_BUNDLE_SYMLINKS(<bundle> <result_var> <info_var>)
# Verifies that any symlinks found in the bundle point to other files that are
# already also in the bundle... Anything that points to an external file causes
# this function to fail the verification.
#=============================================================================
# Copyright 2008-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
# The functions defined in this file depend on the get_prerequisites function
# (and possibly others) found in:
#
get_filename_component(BundleUtilities_cmake_dir "${CMAKE_CURRENT_LIST_FILE}" PATH)
include("${BundleUtilities_cmake_dir}/GetPrerequisites.cmake")
function(get_bundle_main_executable bundle result_var)
set(result "error: '${bundle}/Contents/Info.plist' file does not exist")
if(EXISTS "${bundle}/Contents/Info.plist")
set(result "error: no CFBundleExecutable in '${bundle}/Contents/Info.plist' file")
set(line_is_main_executable 0)
set(bundle_executable "")
# Read Info.plist as a list of lines:
#
set(eol_char "E")
file(READ "${bundle}/Contents/Info.plist" info_plist)
string(REGEX REPLACE ";" "\\\\;" info_plist "${info_plist}")
string(REGEX REPLACE "\n" "${eol_char};" info_plist "${info_plist}")
# Scan the lines for "<key>CFBundleExecutable</key>" - the line after that
# is the name of the main executable.
#
foreach(line ${info_plist})
if(line_is_main_executable)
string(REGEX REPLACE "^.*<string>(.*)</string>.*$" "\\1" bundle_executable "${line}")
break()
endif()
if(line MATCHES "^.*<key>CFBundleExecutable</key>.*$")
set(line_is_main_executable 1)
endif()
endforeach()
if(NOT "${bundle_executable}" STREQUAL "")
if(EXISTS "${bundle}/Contents/MacOS/${bundle_executable}")
set(result "${bundle}/Contents/MacOS/${bundle_executable}")
else()
# Ultimate goal:
# If not in "Contents/MacOS" then scan the bundle for matching files. If
# there is only one executable file that matches, then use it, otherwise
# it's an error...
#
#file(GLOB_RECURSE file_list "${bundle}/${bundle_executable}")
# But for now, pragmatically, it's an error. Expect the main executable
# for the bundle to be in Contents/MacOS, it's an error if it's not:
#
set(result "error: '${bundle}/Contents/MacOS/${bundle_executable}' does not exist")
endif()
endif()
else()
#
# More inclusive technique... (This one would work on Windows and Linux
# too, if a developer followed the typical Mac bundle naming convention...)
#
# If there is no Info.plist file, try to find an executable with the same
# base name as the .app directory:
#
endif()
set(${result_var} "${result}" PARENT_SCOPE)
endfunction()
function(get_dotapp_dir exe dotapp_dir_var)
set(s "${exe}")
if(s MATCHES "^.*/.*\\.app/.*$")
# If there is a ".app" parent directory,
# ascend until we hit it:
# (typical of a Mac bundle executable)
#
set(done 0)
while(NOT ${done})
get_filename_component(snamewe "${s}" NAME_WE)
get_filename_component(sname "${s}" NAME)
get_filename_component(sdir "${s}" PATH)
set(s "${sdir}")
if(sname MATCHES "\\.app$")
set(done 1)
set(dotapp_dir "${sdir}/${sname}")
endif()
endwhile()
else()
# Otherwise use a directory containing the exe
# (typical of a non-bundle executable on Mac, Windows or Linux)
#
is_file_executable("${s}" is_executable)
if(is_executable)
get_filename_component(sdir "${s}" PATH)
set(dotapp_dir "${sdir}")
else()
set(dotapp_dir "${s}")
endif()
endif()
set(${dotapp_dir_var} "${dotapp_dir}" PARENT_SCOPE)
endfunction()
function(get_bundle_and_executable app bundle_var executable_var valid_var)
set(valid 0)
if(EXISTS "${app}")
# Is it a directory ending in .app?
if(IS_DIRECTORY "${app}")
if(app MATCHES "\\.app$")
get_bundle_main_executable("${app}" executable)
if(EXISTS "${app}" AND EXISTS "${executable}")
set(${bundle_var} "${app}" PARENT_SCOPE)
set(${executable_var} "${executable}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled .app directory case...")
else()
message(STATUS "warning: *NOT* handled - .app directory case...")
endif()
else()
message(STATUS "warning: *NOT* handled - directory but not .app case...")
endif()
else()
# Is it an executable file?
is_file_executable("${app}" is_executable)
if(is_executable)
get_dotapp_dir("${app}" dotapp_dir)
if(EXISTS "${dotapp_dir}")
set(${bundle_var} "${dotapp_dir}" PARENT_SCOPE)
set(${executable_var} "${app}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled executable file in .app dir case...")
else()
get_filename_component(app_dir "${app}" PATH)
set(${bundle_var} "${app_dir}" PARENT_SCOPE)
set(${executable_var} "${app}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled executable file in any dir case...")
endif()
else()
message(STATUS "warning: *NOT* handled - not .app dir, not executable file...")
endif()
endif()
else()
message(STATUS "warning: *NOT* handled - directory/file ${app} does not exist...")
endif()
if(NOT valid)
set(${bundle_var} "error: not a bundle" PARENT_SCOPE)
set(${executable_var} "error: not a bundle" PARENT_SCOPE)
endif()
set(${valid_var} ${valid} PARENT_SCOPE)
endfunction()
function(get_bundle_all_executables bundle exes_var)
set(exes "")
file(GLOB_RECURSE file_list "${bundle}/*")
foreach(f ${file_list})
is_file_executable("${f}" is_executable)
if(is_executable)
set(exes ${exes} "${f}")
endif()
endforeach()
set(${exes_var} "${exes}" PARENT_SCOPE)
endfunction()
function(get_item_key item key_var)
get_filename_component(item_name "${item}" NAME)
if(WIN32)
string(TOLOWER "${item_name}" item_name)
endif()
string(REGEX REPLACE "\\." "_" ${key_var} "${item_name}")
set(${key_var} ${${key_var}} PARENT_SCOPE)
endfunction()
function(clear_bundle_keys keys_var)
foreach(key ${${keys_var}})
set(${key}_ITEM PARENT_SCOPE)
set(${key}_RESOLVED_ITEM PARENT_SCOPE)
set(${key}_DEFAULT_EMBEDDED_PATH PARENT_SCOPE)
set(${key}_EMBEDDED_ITEM PARENT_SCOPE)
set(${key}_RESOLVED_EMBEDDED_ITEM PARENT_SCOPE)
set(${key}_COPYFLAG PARENT_SCOPE)
endforeach()
set(${keys_var} PARENT_SCOPE)
endfunction()
function(set_bundle_key_values keys_var context item exepath dirs copyflag)
get_filename_component(item_name "${item}" NAME)
get_item_key("${item}" key)
list(LENGTH ${keys_var} length_before)
gp_append_unique(${keys_var} "${key}")
list(LENGTH ${keys_var} length_after)
if(NOT length_before EQUAL length_after)
gp_resolve_item("${context}" "${item}" "${exepath}" "${dirs}" resolved_item)
gp_item_default_embedded_path("${item}" default_embedded_path)
if(item MATCHES "[^/]+\\.framework/")
# For frameworks, construct the name under the embedded path from the
# opening "${item_name}.framework/" to the closing "/${item_name}":
#
string(REGEX REPLACE "^.*(${item_name}.framework/.*/?${item_name}).*$" "${default_embedded_path}/\\1" embedded_item "${item}")
else()
# For other items, just use the same name as the original, but in the
# embedded path:
#
set(embedded_item "${default_embedded_path}/${item_name}")
endif()
# Replace @executable_path and resolve ".." references:
#
string(REPLACE "@executable_path" "${exepath}" resolved_embedded_item "${embedded_item}")
get_filename_component(resolved_embedded_item "${resolved_embedded_item}" ABSOLUTE)
# *But* -- if we are not copying, then force resolved_embedded_item to be
# the same as resolved_item. In the case of multiple executables in the
# original bundle, using the default_embedded_path results in looking for
# the resolved executable next to the main bundle executable. This is here
# so that exes in the other sibling directories (like "bin") get fixed up
# properly...
#
if(NOT copyflag)
set(resolved_embedded_item "${resolved_item}")
endif()
set(${keys_var} ${${keys_var}} PARENT_SCOPE)
set(${key}_ITEM "${item}" PARENT_SCOPE)
set(${key}_RESOLVED_ITEM "${resolved_item}" PARENT_SCOPE)
set(${key}_DEFAULT_EMBEDDED_PATH "${default_embedded_path}" PARENT_SCOPE)
set(${key}_EMBEDDED_ITEM "${embedded_item}" PARENT_SCOPE)
set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_embedded_item}" PARENT_SCOPE)
set(${key}_COPYFLAG "${copyflag}" PARENT_SCOPE)
else()
#message("warning: item key '${key}' already in the list, subsequent references assumed identical to first")
endif()
endfunction()
function(get_bundle_keys app libs dirs keys_var)
set(${keys_var} PARENT_SCOPE)
get_bundle_and_executable("${app}" bundle executable valid)
if(valid)
# Always use the exepath of the main bundle executable for @executable_path
# replacements:
#
get_filename_component(exepath "${executable}" PATH)
# But do fixups on all executables in the bundle:
#
get_bundle_all_executables("${bundle}" exes)
# For each extra lib, accumulate a key as well and then also accumulate
# any of its prerequisites. (Extra libs are typically dynamically loaded
# plugins: libraries that are prerequisites for full runtime functionality
# but that do not show up in otool -L output...)
#
foreach(lib ${libs})
set_bundle_key_values(${keys_var} "${lib}" "${lib}" "${exepath}" "${dirs}" 0)
set(prereqs "")
get_prerequisites("${lib}" prereqs 1 1 "${exepath}" "${dirs}")
foreach(pr ${prereqs})
set_bundle_key_values(${keys_var} "${lib}" "${pr}" "${exepath}" "${dirs}" 1)
endforeach()
endforeach()
# For each executable found in the bundle, accumulate keys as we go.
# The list of keys should be complete when all prerequisites of all
# binaries in the bundle have been analyzed.
#
foreach(exe ${exes})
# Add the exe itself to the keys:
#
set_bundle_key_values(${keys_var} "${exe}" "${exe}" "${exepath}" "${dirs}" 0)
# Add each prerequisite to the keys:
#
set(prereqs "")
get_prerequisites("${exe}" prereqs 1 1 "${exepath}" "${dirs}")
foreach(pr ${prereqs})
set_bundle_key_values(${keys_var} "${exe}" "${pr}" "${exepath}" "${dirs}" 1)
endforeach()
endforeach()
# Propagate values to caller's scope:
#
set(${keys_var} ${${keys_var}} PARENT_SCOPE)
foreach(key ${${keys_var}})
set(${key}_ITEM "${${key}_ITEM}" PARENT_SCOPE)
set(${key}_RESOLVED_ITEM "${${key}_RESOLVED_ITEM}" PARENT_SCOPE)
set(${key}_DEFAULT_EMBEDDED_PATH "${${key}_DEFAULT_EMBEDDED_PATH}" PARENT_SCOPE)
set(${key}_EMBEDDED_ITEM "${${key}_EMBEDDED_ITEM}" PARENT_SCOPE)
set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE)
set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE)
endforeach()
endif()
endfunction()
function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
if(WIN32)
# ignore case on Windows
string(TOLOWER "${resolved_item}" resolved_item_compare)
string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
else()
set(resolved_item_compare "${resolved_item}")
set(resolved_embedded_item_compare "${resolved_embedded_item}")
endif()
if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...")
else()
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
if(UNIX AND NOT APPLE)
file(RPATH_REMOVE FILE "${resolved_embedded_item}")
endif()
endif()
endfunction()
function(copy_resolved_framework_into_bundle resolved_item resolved_embedded_item)
if(WIN32)
# ignore case on Windows
string(TOLOWER "${resolved_item}" resolved_item_compare)
string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
else()
set(resolved_item_compare "${resolved_item}")
set(resolved_embedded_item_compare "${resolved_embedded_item}")
endif()
if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...")
else()
if(BU_COPY_FULL_FRAMEWORK_CONTENTS)
# Full Framework (everything):
get_filename_component(resolved_dir "${resolved_item}" PATH)
get_filename_component(resolved_dir "${resolved_dir}/../.." ABSOLUTE)
get_filename_component(resolved_embedded_dir "${resolved_embedded_item}" PATH)
get_filename_component(resolved_embedded_dir "${resolved_embedded_dir}/../.." ABSOLUTE)
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_dir}' '${resolved_embedded_dir}'")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_dir}" "${resolved_embedded_dir}")
else()
# Framework lib itself:
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
# Plus Resources, if they exist:
string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Resources" resolved_resources "${resolved_item}")
string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Resources" resolved_embedded_resources "${resolved_embedded_item}")
if(EXISTS "${resolved_resources}")
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_resources}' '${resolved_embedded_resources}'")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_resources}" "${resolved_embedded_resources}")
endif()
endif()
if(UNIX AND NOT APPLE)
file(RPATH_REMOVE FILE "${resolved_embedded_item}")
endif()
endif()
endfunction()
function(fixup_bundle_item resolved_embedded_item exepath dirs)
# This item's key is "ikey":
#
get_item_key("${resolved_embedded_item}" ikey)
# Ensure the item is "inside the .app bundle" -- it should not be fixed up if
# it is not in the .app bundle... Otherwise, we'll modify files in the build
# tree, or in other varied locations around the file system, with our call to
# install_name_tool. Make sure that doesn't happen here:
#
get_dotapp_dir("${exepath}" exe_dotapp_dir)
string(LENGTH "${exe_dotapp_dir}/" exe_dotapp_dir_length)
string(LENGTH "${resolved_embedded_item}" resolved_embedded_item_length)
set(path_too_short 0)
set(is_embedded 0)
if(${resolved_embedded_item_length} LESS ${exe_dotapp_dir_length})
set(path_too_short 1)
endif()
if(NOT path_too_short)
string(SUBSTRING "${resolved_embedded_item}" 0 ${exe_dotapp_dir_length} item_substring)
if("${exe_dotapp_dir}/" STREQUAL "${item_substring}")
set(is_embedded 1)
endif()
endif()
if(NOT is_embedded)
message(" exe_dotapp_dir/='${exe_dotapp_dir}/'")
message(" item_substring='${item_substring}'")
message(" resolved_embedded_item='${resolved_embedded_item}'")
message("")
message("Install or copy the item into the bundle before calling fixup_bundle.")
message("Or maybe there's a typo or incorrect path in one of the args to fixup_bundle?")
message("")
message(FATAL_ERROR "cannot fixup an item that is not in the bundle...")
endif()
set(prereqs "")
get_prerequisites("${resolved_embedded_item}" prereqs 1 0 "${exepath}" "${dirs}")
set(changes "")
foreach(pr ${prereqs})
# Each referenced item's key is "rkey" in the loop:
#
get_item_key("${pr}" rkey)
if(NOT "${${rkey}_EMBEDDED_ITEM}" STREQUAL "")
set(changes ${changes} "-change" "${pr}" "${${rkey}_EMBEDDED_ITEM}")
else()
message("warning: unexpected reference to '${pr}'")
endif()
endforeach()
if(BU_CHMOD_BUNDLE_ITEMS)
execute_process(COMMAND chmod u+w "${resolved_embedded_item}")
endif()
# Change this item's id and all of its references in one call
# to install_name_tool:
#
execute_process(COMMAND install_name_tool
${changes} -id "${${ikey}_EMBEDDED_ITEM}" "${resolved_embedded_item}"
)
endfunction()
function(fixup_bundle app libs dirs)
message(STATUS "fixup_bundle")
message(STATUS " app='${app}'")
message(STATUS " libs='${libs}'")
message(STATUS " dirs='${dirs}'")
get_bundle_and_executable("${app}" bundle executable valid)
if(valid)
get_filename_component(exepath "${executable}" PATH)
message(STATUS "fixup_bundle: preparing...")
get_bundle_keys("${app}" "${libs}" "${dirs}" keys)
message(STATUS "fixup_bundle: copying...")
list(LENGTH keys n)
math(EXPR n ${n}*2)
set(i 0)
foreach(key ${keys})
math(EXPR i ${i}+1)
if(${${key}_COPYFLAG})
message(STATUS "${i}/${n}: copying '${${key}_RESOLVED_ITEM}'")
else()
message(STATUS "${i}/${n}: *NOT* copying '${${key}_RESOLVED_ITEM}'")
endif()
set(show_status 0)
if(show_status)
message(STATUS "key='${key}'")
message(STATUS "item='${${key}_ITEM}'")
message(STATUS "resolved_item='${${key}_RESOLVED_ITEM}'")
message(STATUS "default_embedded_path='${${key}_DEFAULT_EMBEDDED_PATH}'")
message(STATUS "embedded_item='${${key}_EMBEDDED_ITEM}'")
message(STATUS "resolved_embedded_item='${${key}_RESOLVED_EMBEDDED_ITEM}'")
message(STATUS "copyflag='${${key}_COPYFLAG}'")
message(STATUS "")
endif()
if(${${key}_COPYFLAG})
set(item "${${key}_ITEM}")
if(item MATCHES "[^/]+\\.framework/")
copy_resolved_framework_into_bundle("${${key}_RESOLVED_ITEM}"
"${${key}_RESOLVED_EMBEDDED_ITEM}")
else()
copy_resolved_item_into_bundle("${${key}_RESOLVED_ITEM}"
"${${key}_RESOLVED_EMBEDDED_ITEM}")
endif()
endif()
endforeach()
message(STATUS "fixup_bundle: fixing...")
foreach(key ${keys})
math(EXPR i ${i}+1)
if(APPLE)
message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'")
fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}")
else()
message(STATUS "${i}/${n}: fix-up not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'")
endif()
endforeach()
message(STATUS "fixup_bundle: cleaning up...")
clear_bundle_keys(keys)
message(STATUS "fixup_bundle: verifying...")
verify_app("${app}")
else()
message(SEND_ERROR "error: fixup_bundle: not a valid bundle")
endif()
message(STATUS "fixup_bundle: done")
endfunction()
function(copy_and_fixup_bundle src dst libs dirs)
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${src}" "${dst}")
fixup_bundle("${dst}" "${libs}" "${dirs}")
endfunction()
function(verify_bundle_prerequisites bundle result_var info_var)
set(result 1)
set(info "")
set(count 0)
get_bundle_main_executable("${bundle}" main_bundle_exe)
file(GLOB_RECURSE file_list "${bundle}/*")
foreach(f ${file_list})
is_file_executable("${f}" is_executable)
if(is_executable)
get_filename_component(exepath "${f}" PATH)
math(EXPR count "${count} + 1")
message(STATUS "executable file ${count}: ${f}")
set(prereqs "")
get_prerequisites("${f}" prereqs 1 1 "${exepath}" "")
# On the Mac,
# "embedded" and "system" prerequisites are fine... anything else means
# the bundle's prerequisites are not verified (i.e., the bundle is not
# really "standalone")
#
# On Windows (and others? Linux/Unix/...?)
# "local" and "system" prereqs are fine...
#
set(external_prereqs "")
foreach(p ${prereqs})
set(p_type "")
gp_file_type("${f}" "${p}" p_type)
if(APPLE)
if(NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system")
set(external_prereqs ${external_prereqs} "${p}")
endif()
else()
if(NOT "${p_type}" STREQUAL "local" AND NOT "${p_type}" STREQUAL "system")
set(external_prereqs ${external_prereqs} "${p}")
endif()
endif()
endforeach()
if(external_prereqs)
# Found non-system/somehow-unacceptable prerequisites:
set(result 0)
set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n")
endif()
endif()
endforeach()
if(result)
set(info "Verified ${count} executable files in '${bundle}'")
endif()
set(${result_var} "${result}" PARENT_SCOPE)
set(${info_var} "${info}" PARENT_SCOPE)
endfunction()
function(verify_bundle_symlinks bundle result_var info_var)
set(result 1)
set(info "")
set(count 0)
# TODO: implement this function for real...
# Right now, it is just a stub that verifies unconditionally...
set(${result_var} "${result}" PARENT_SCOPE)
set(${info_var} "${info}" PARENT_SCOPE)
endfunction()
function(verify_app app)
set(verified 0)
set(info "")
get_bundle_and_executable("${app}" bundle executable valid)
message(STATUS "===========================================================================")
message(STATUS "Analyzing app='${app}'")
message(STATUS "bundle='${bundle}'")
message(STATUS "executable='${executable}'")
message(STATUS "valid='${valid}'")
# Verify that the bundle does not have any "external" prerequisites:
#
verify_bundle_prerequisites("${bundle}" verified info)
message(STATUS "verified='${verified}'")
message(STATUS "info='${info}'")
message(STATUS "")
if(verified)
# Verify that the bundle does not have any symlinks to external files:
#
verify_bundle_symlinks("${bundle}" verified info)
message(STATUS "verified='${verified}'")
message(STATUS "info='${info}'")
message(STATUS "")
endif()
if(NOT verified)
message(FATAL_ERROR "error: verify_app failed")
endif()
endfunction()

View File

@ -1,902 +0,0 @@
# - Functions to analyze and list executable file prerequisites.
# This module provides functions to list the .dll, .dylib or .so
# files that an executable or shared library file depends on. (Its
# prerequisites.)
#
# It uses various tools to obtain the list of required shared library files:
# dumpbin (Windows)
# objdump (MinGW on Windows)
# ldd (Linux/Unix)
# otool (Mac OSX)
# The following functions are provided by this module:
# get_prerequisites
# list_prerequisites
# list_prerequisites_by_glob
# gp_append_unique
# is_file_executable
# gp_item_default_embedded_path
# (projects can override with gp_item_default_embedded_path_override)
# gp_resolve_item
# (projects can override with gp_resolve_item_override)
# gp_resolved_file_type
# (projects can override with gp_resolved_file_type_override)
# gp_file_type
# Requires CMake 2.6 or greater because it uses function, break, return and
# PARENT_SCOPE.
#
# GET_PREREQUISITES(<target> <prerequisites_var> <exclude_system> <recurse>
# <exepath> <dirs>)
# Get the list of shared library files required by <target>. The list in
# the variable named <prerequisites_var> should be empty on first entry to
# this function. On exit, <prerequisites_var> will contain the list of
# required shared library files.
#
# <target> is the full path to an executable file. <prerequisites_var> is the
# name of a CMake variable to contain the results. <exclude_system> must be 0
# or 1 indicating whether to include or exclude "system" prerequisites. If
# <recurse> is set to 1 all prerequisites will be found recursively, if set to
# 0 only direct prerequisites are listed. <exepath> is the path to the top
# level executable used for @executable_path replacment on the Mac. <dirs> is
# a list of paths where libraries might be found: these paths are searched
# first when a target without any path info is given. Then standard system
# locations are also searched: PATH, Framework locations, /usr/lib...
#
# LIST_PREREQUISITES(<target> [<recurse> [<exclude_system> [<verbose>]]])
# Print a message listing the prerequisites of <target>.
#
# <target> is the name of a shared library or executable target or the full
# path to a shared library or executable file. If <recurse> is set to 1 all
# prerequisites will be found recursively, if set to 0 only direct
# prerequisites are listed. <exclude_system> must be 0 or 1 indicating whether
# to include or exclude "system" prerequisites. With <verbose> set to 0 only
# the full path names of the prerequisites are printed, set to 1 extra
# informatin will be displayed.
#
# LIST_PREREQUISITES_BY_GLOB(<glob_arg> <glob_exp>)
# Print the prerequisites of shared library and executable files matching a
# globbing pattern. <glob_arg> is GLOB or GLOB_RECURSE and <glob_exp> is a
# globbing expression used with "file(GLOB" or "file(GLOB_RECURSE" to retrieve
# a list of matching files. If a matching file is executable, its prerequisites
# are listed.
#
# Any additional (optional) arguments provided are passed along as the
# optional arguments to the list_prerequisites calls.
#
# GP_APPEND_UNIQUE(<list_var> <value>)
# Append <value> to the list variable <list_var> only if the value is not
# already in the list.
#
# IS_FILE_EXECUTABLE(<file> <result_var>)
# Return 1 in <result_var> if <file> is a binary executable, 0 otherwise.
#
# GP_ITEM_DEFAULT_EMBEDDED_PATH(<item> <default_embedded_path_var>)
# Return the path that others should refer to the item by when the item
# is embedded inside a bundle.
#
# Override on a per-project basis by providing a project-specific
# gp_item_default_embedded_path_override function.
#
# GP_RESOLVE_ITEM(<context> <item> <exepath> <dirs> <resolved_item_var>)
# Resolve an item into an existing full path file.
#
# Override on a per-project basis by providing a project-specific
# gp_resolve_item_override function.
#
# GP_RESOLVED_FILE_TYPE(<original_file> <file> <exepath> <dirs> <type_var>)
# Return the type of <file> with respect to <original_file>. String
# describing type of prerequisite is returned in variable named <type_var>.
#
# Use <exepath> and <dirs> if necessary to resolve non-absolute <file>
# values -- but only for non-embedded items.
#
# Possible types are:
# system
# local
# embedded
# other
# Override on a per-project basis by providing a project-specific
# gp_resolved_file_type_override function.
#
# GP_FILE_TYPE(<original_file> <file> <type_var>)
# Return the type of <file> with respect to <original_file>. String
# describing type of prerequisite is returned in variable named <type_var>.
#
# Possible types are:
# system
# local
# embedded
# other
#=============================================================================
# Copyright 2008-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
function(gp_append_unique list_var value)
set(contains 0)
foreach(item ${${list_var}})
if("${item}" STREQUAL "${value}")
set(contains 1)
break()
endif()
endforeach()
if(NOT contains)
set(${list_var} ${${list_var}} "${value}" PARENT_SCOPE)
endif()
endfunction()
function(is_file_executable file result_var)
#
# A file is not executable until proven otherwise:
#
set(${result_var} 0 PARENT_SCOPE)
get_filename_component(file_full "${file}" ABSOLUTE)
string(TOLOWER "${file_full}" file_full_lower)
# If file name ends in .exe on Windows, *assume* executable:
#
if(WIN32 AND NOT UNIX)
if("${file_full_lower}" MATCHES "\\.exe$")
set(${result_var} 1 PARENT_SCOPE)
return()
endif()
# A clause could be added here that uses output or return value of dumpbin
# to determine ${result_var}. In 99%+? practical cases, the exe name
# match will be sufficient...
#
endif()
# Use the information returned from the Unix shell command "file" to
# determine if ${file_full} should be considered an executable file...
#
# If the file command's output contains "executable" and does *not* contain
# "text" then it is likely an executable suitable for prerequisite analysis
# via the get_prerequisites macro.
#
if(UNIX)
if(NOT file_cmd)
find_program(file_cmd "file")
mark_as_advanced(file_cmd)
endif()
if(file_cmd)
execute_process(COMMAND "${file_cmd}" "${file_full}"
OUTPUT_VARIABLE file_ov
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Replace the name of the file in the output with a placeholder token
# (the string " _file_full_ ") so that just in case the path name of
# the file contains the word "text" or "executable" we are not fooled
# into thinking "the wrong thing" because the file name matches the
# other 'file' command output we are looking for...
#
string(REPLACE "${file_full}" " _file_full_ " file_ov "${file_ov}")
string(TOLOWER "${file_ov}" file_ov)
#message(STATUS "file_ov='${file_ov}'")
if("${file_ov}" MATCHES "executable")
#message(STATUS "executable!")
if("${file_ov}" MATCHES "text")
#message(STATUS "but text, so *not* a binary executable!")
else()
set(${result_var} 1 PARENT_SCOPE)
return()
endif()
endif()
# Also detect position independent executables on Linux,
# where "file" gives "shared object ... (uses shared libraries)"
if("${file_ov}" MATCHES "shared object.*\(uses shared libs\)")
set(${result_var} 1 PARENT_SCOPE)
return()
endif()
# "file" version 5.22 does not print "(used shared libraries)"
# but uses "interpreter"
if("${file_ov}" MATCHES "shared object.*interpreter")
set(${result_var} 1 PARENT_SCOPE)
return()
endif()
else()
message(STATUS "warning: No 'file' command, skipping execute_process...")
endif()
endif()
endfunction()
function(gp_item_default_embedded_path item default_embedded_path_var)
# On Windows and Linux, "embed" prerequisites in the same directory
# as the executable by default:
#
set(path "@executable_path")
set(overridden 0)
# On the Mac, relative to the executable depending on the type
# of the thing we are embedding:
#
if(APPLE)
#
# The assumption here is that all executables in the bundle will be
# in same-level-directories inside the bundle. The parent directory
# of an executable inside the bundle should be MacOS or a sibling of
# MacOS and all embedded paths returned from here will begin with
# "@executable_path/../" and will work from all executables in all
# such same-level-directories inside the bundle.
#
# By default, embed things right next to the main bundle executable:
#
set(path "@executable_path/../../Contents/MacOS")
# Embed .dylibs right next to the main bundle executable:
#
if(item MATCHES "\\.dylib$")
set(path "@executable_path/../MacOS")
set(overridden 1)
endif()
# Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS):
#
if(NOT overridden)
if(item MATCHES "[^/]+\\.framework/")
set(path "@executable_path/../Frameworks")
set(overridden 1)
endif()
endif()
endif()
# Provide a hook so that projects can override the default embedded location
# of any given library by whatever logic they choose:
#
if(COMMAND gp_item_default_embedded_path_override)
gp_item_default_embedded_path_override("${item}" path)
endif()
set(${default_embedded_path_var} "${path}" PARENT_SCOPE)
endfunction()
function(gp_resolve_item context item exepath dirs resolved_item_var)
set(resolved 0)
set(resolved_item "${item}")
# Is it already resolved?
#
if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
set(resolved 1)
endif()
if(NOT resolved)
if(item MATCHES "@executable_path")
#
# @executable_path references are assumed relative to exepath
#
string(REPLACE "@executable_path" "${exepath}" ri "${item}")
get_filename_component(ri "${ri}" ABSOLUTE)
if(EXISTS "${ri}")
#message(STATUS "info: embedded item exists (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
else()
message(STATUS "warning: embedded item does not exist '${ri}'")
endif()
endif()
endif()
if(NOT resolved)
if(item MATCHES "@loader_path")
#
# @loader_path references are assumed relative to the
# PATH of the given "context" (presumably another library)
#
get_filename_component(contextpath "${context}" PATH)
string(REPLACE "@loader_path" "${contextpath}" ri "${item}")
get_filename_component(ri "${ri}" ABSOLUTE)
if(EXISTS "${ri}")
#message(STATUS "info: embedded item exists (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
else()
message(STATUS "warning: embedded item does not exist '${ri}'")
endif()
endif()
endif()
if(NOT resolved)
if(item MATCHES "@rpath")
#
# @rpath references are relative to the paths built into the binaries with -rpath
# We handle this case like we do for other Unixes
#
string(REPLACE "@rpath/" "" norpath_item "${item}")
set(ri "ri-NOTFOUND")
find_file(ri "${norpath_item}" ${exepath} ${dirs} NO_DEFAULT_PATH)
if(ri)
#message(STATUS "info: 'find_file' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
endif()
endif()
endif()
if(NOT resolved)
set(ri "ri-NOTFOUND")
find_file(ri "${item}" ${exepath} ${dirs} NO_DEFAULT_PATH)
find_file(ri "${item}" ${exepath} ${dirs} /usr/lib)
if(ri)
#message(STATUS "info: 'find_file' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
endif()
endif()
if(NOT resolved)
if(item MATCHES "[^/]+\\.framework/")
set(fw "fw-NOTFOUND")
find_file(fw "${item}"
"~/Library/Frameworks"
"/Library/Frameworks"
"/System/Library/Frameworks"
)
if(fw)
#message(STATUS "info: 'find_file' found framework (${fw})")
set(resolved 1)
set(resolved_item "${fw}")
set(fw "fw-NOTFOUND")
endif()
endif()
endif()
# Using find_program on Windows will find dll files that are in the PATH.
# (Converting simple file names into full path names if found.)
#
if(WIN32 AND NOT UNIX)
if(NOT resolved)
set(ri "ri-NOTFOUND")
find_program(ri "${item}" PATHS "${exepath};${dirs}" NO_DEFAULT_PATH)
find_program(ri "${item}" PATHS "${exepath};${dirs}")
if(ri)
#message(STATUS "info: 'find_program' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
endif()
endif()
endif()
# Provide a hook so that projects can override item resolution
# by whatever logic they choose:
#
if(COMMAND gp_resolve_item_override)
gp_resolve_item_override("${context}" "${item}" "${exepath}" "${dirs}" resolved_item resolved)
endif()
if(NOT resolved)
message(STATUS "
warning: cannot resolve item '${item}'
possible problems:
need more directories?
need to use InstallRequiredSystemLibraries?
run in install tree instead of build tree?
")
# message(STATUS "
#******************************************************************************
#warning: cannot resolve item '${item}'
#
# possible problems:
# need more directories?
# need to use InstallRequiredSystemLibraries?
# run in install tree instead of build tree?
#
# context='${context}'
# item='${item}'
# exepath='${exepath}'
# dirs='${dirs}'
# resolved_item_var='${resolved_item_var}'
#******************************************************************************
#")
endif()
set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE)
endfunction()
function(gp_resolved_file_type original_file file exepath dirs type_var)
#message(STATUS "**")
if(NOT IS_ABSOLUTE "${original_file}")
message(STATUS "warning: gp_resolved_file_type expects absolute full path for first arg original_file")
endif()
set(is_embedded 0)
set(is_local 0)
set(is_system 0)
set(resolved_file "${file}")
if("${file}" MATCHES "^@(executable|loader)_path")
set(is_embedded 1)
endif()
if(NOT is_embedded)
if(NOT IS_ABSOLUTE "${file}")
gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file)
endif()
string(TOLOWER "${original_file}" original_lower)
string(TOLOWER "${resolved_file}" lower)
if(UNIX)
if(resolved_file MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11R6/|/usr/bin/)")
set(is_system 1)
endif()
endif()
if(APPLE)
if(resolved_file MATCHES "^(/System/Library/|/usr/lib/)")
set(is_system 1)
endif()
endif()
if(WIN32)
string(TOLOWER "$ENV{SystemRoot}" sysroot)
string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}")
string(TOLOWER "$ENV{windir}" windir)
string(REGEX REPLACE "\\\\" "/" windir "${windir}")
if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)")
set(is_system 1)
endif()
if(UNIX)
# if cygwin, we can get the properly formed windows paths from cygpath
find_program(CYGPATH_EXECUTABLE cygpath)
if(CYGPATH_EXECUTABLE)
execute_process(COMMAND ${CYGPATH_EXECUTABLE} -W
OUTPUT_VARIABLE env_windir
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${CYGPATH_EXECUTABLE} -S
OUTPUT_VARIABLE env_sysdir
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(TOLOWER "${env_windir}" windir)
string(TOLOWER "${env_sysdir}" sysroot)
if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)")
set(is_system 1)
endif()
endif()
endif()
endif()
if(NOT is_system)
get_filename_component(original_path "${original_lower}" PATH)
get_filename_component(path "${lower}" PATH)
if("${original_path}" STREQUAL "${path}")
set(is_local 1)
else()
string(LENGTH "${original_path}/" original_length)
string(LENGTH "${lower}" path_length)
if(${path_length} GREATER ${original_length})
string(SUBSTRING "${lower}" 0 ${original_length} path)
if("${original_path}/" STREQUAL "${path}")
set(is_embedded 1)
endif()
endif()
endif()
endif()
endif()
# Return type string based on computed booleans:
#
set(type "other")
if(is_system)
set(type "system")
elseif(is_embedded)
set(type "embedded")
elseif(is_local)
set(type "local")
endif()
#message(STATUS "gp_resolved_file_type: '${file}' '${resolved_file}'")
#message(STATUS " type: '${type}'")
if(NOT is_embedded)
if(NOT IS_ABSOLUTE "${resolved_file}")
if(lower MATCHES "^msvc[^/]+dll" AND is_system)
message(STATUS "info: non-absolute msvc file '${file}' returning type '${type}'")
else()
message(STATUS "warning: gp_resolved_file_type non-absolute file '${file}' returning type '${type}' -- possibly incorrect")
endif()
endif()
endif()
# Provide a hook so that projects can override the decision on whether a
# library belongs to the system or not by whatever logic they choose:
#
if(COMMAND gp_resolved_file_type_override)
gp_resolved_file_type_override("${resolved_file}" type)
endif()
set(${type_var} "${type}" PARENT_SCOPE)
#message(STATUS "**")
endfunction()
function(gp_file_type original_file file type_var)
if(NOT IS_ABSOLUTE "${original_file}")
message(STATUS "warning: gp_file_type expects absolute full path for first arg original_file")
endif()
get_filename_component(exepath "${original_file}" PATH)
set(type "")
gp_resolved_file_type("${original_file}" "${file}" "${exepath}" "" type)
set(${type_var} "${type}" PARENT_SCOPE)
endfunction()
function(get_prerequisites target prerequisites_var exclude_system recurse exepath dirs)
set(verbose 0)
set(eol_char "E")
if(NOT IS_ABSOLUTE "${target}")
message("warning: target '${target}' is not absolute...")
endif()
if(NOT EXISTS "${target}")
message("warning: target '${target}' does not exist...")
endif()
set(gp_cmd_paths ${gp_cmd_paths}
"C:/Program Files/Microsoft Visual Studio 9.0/VC/bin"
"C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin"
"C:/Program Files/Microsoft Visual Studio 8/VC/BIN"
"C:/Program Files (x86)/Microsoft Visual Studio 8/VC/BIN"
"C:/Program Files/Microsoft Visual Studio .NET 2003/VC7/BIN"
"C:/Program Files (x86)/Microsoft Visual Studio .NET 2003/VC7/BIN"
"/usr/local/bin"
"/usr/bin"
)
# <setup-gp_tool-vars>
#
# Try to choose the right tool by default. Caller can set gp_tool prior to
# calling this function to force using a different tool.
#
if("${gp_tool}" STREQUAL "")
set(gp_tool "ldd")
if(APPLE)
set(gp_tool "otool")
endif()
if(WIN32 AND NOT UNIX) # This is how to check for cygwin, har!
find_program(gp_dumpbin "dumpbin" PATHS ${gp_cmd_paths})
if(gp_dumpbin)
set(gp_tool "dumpbin")
else() # Try harder. Maybe we're on MinGW
set(gp_tool "objdump")
endif()
endif()
endif()
find_program(gp_cmd ${gp_tool} PATHS ${gp_cmd_paths})
if(NOT gp_cmd)
message(FATAL_ERROR "FATAL ERROR: could not find '${gp_tool}' - cannot analyze prerequisites!")
return()
endif()
set(gp_tool_known 0)
if("${gp_tool}" STREQUAL "ldd")
set(gp_cmd_args "")
set(gp_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$")
set(gp_regex_error "not found${eol_char}$")
set(gp_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$")
set(gp_regex_cmp_count 1)
set(gp_tool_known 1)
endif()
if("${gp_tool}" STREQUAL "otool")
set(gp_cmd_args "-L")
set(gp_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$")
set(gp_regex_error "")
set(gp_regex_fallback "")
set(gp_regex_cmp_count 3)
set(gp_tool_known 1)
endif()
if("${gp_tool}" STREQUAL "dumpbin")
set(gp_cmd_args "/dependents")
set(gp_regex "^ ([^ ].*[Dd][Ll][Ll])${eol_char}$")
set(gp_regex_error "")
set(gp_regex_fallback "")
set(gp_regex_cmp_count 1)
set(gp_tool_known 1)
endif()
if("${gp_tool}" STREQUAL "objdump")
set(gp_cmd_args "-p")
set(gp_regex "^\t*DLL Name: (.*\\.[Dd][Ll][Ll])${eol_char}$")
set(gp_regex_error "")
set(gp_regex_fallback "")
set(gp_regex_cmp_count 1)
set(gp_tool_known 1)
endif()
if(NOT gp_tool_known)
message(STATUS "warning: gp_tool='${gp_tool}' is an unknown tool...")
message(STATUS "CMake function get_prerequisites needs more code to handle '${gp_tool}'")
message(STATUS "Valid gp_tool values are dumpbin, ldd, objdump and otool.")
return()
endif()
if("${gp_tool}" STREQUAL "dumpbin")
# When running dumpbin, it also needs the "Common7/IDE" directory in the
# PATH. It will already be in the PATH if being run from a Visual Studio
# command prompt. Add it to the PATH here in case we are running from a
# different command prompt.
#
get_filename_component(gp_cmd_dir "${gp_cmd}" PATH)
get_filename_component(gp_cmd_dlls_dir "${gp_cmd_dir}/../../Common7/IDE" ABSOLUTE)
# Use cmake paths as a user may have a PATH element ending with a backslash.
# This will escape the list delimiter and create havoc!
if(EXISTS "${gp_cmd_dlls_dir}")
# only add to the path if it is not already in the path
set(gp_found_cmd_dlls_dir 0)
file(TO_CMAKE_PATH "$ENV{PATH}" env_path)
foreach(gp_env_path_element ${env_path})
if("${gp_env_path_element}" STREQUAL "${gp_cmd_dlls_dir}")
set(gp_found_cmd_dlls_dir 1)
endif()
endforeach()
if(NOT gp_found_cmd_dlls_dir)
file(TO_NATIVE_PATH "${gp_cmd_dlls_dir}" gp_cmd_dlls_dir)
set(ENV{PATH} "$ENV{PATH};${gp_cmd_dlls_dir}")
endif()
endif()
endif()
#
# </setup-gp_tool-vars>
if("${gp_tool}" STREQUAL "ldd")
set(old_ld_env "$ENV{LD_LIBRARY_PATH}")
foreach(dir ${exepath} ${dirs})
set(ENV{LD_LIBRARY_PATH} "${dir}:$ENV{LD_LIBRARY_PATH}")
endforeach()
endif()
# Track new prerequisites at each new level of recursion. Start with an
# empty list at each level:
#
set(unseen_prereqs)
# Run gp_cmd on the target:
#
execute_process(
COMMAND ${gp_cmd} ${gp_cmd_args} ${target}
OUTPUT_VARIABLE gp_cmd_ov
)
if("${gp_tool}" STREQUAL "ldd")
set(ENV{LD_LIBRARY_PATH} "${old_ld_env}")
endif()
if(verbose)
message(STATUS "<RawOutput cmd='${gp_cmd} ${gp_cmd_args} ${target}'>")
message(STATUS "gp_cmd_ov='${gp_cmd_ov}'")
message(STATUS "</RawOutput>")
endif()
get_filename_component(target_dir "${target}" PATH)
# Convert to a list of lines:
#
string(REGEX REPLACE ";" "\\\\;" candidates "${gp_cmd_ov}")
string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}")
# check for install id and remove it from list, since otool -L can include a
# reference to itself
set(gp_install_id)
if("${gp_tool}" STREQUAL "otool")
execute_process(
COMMAND otool -D ${target}
OUTPUT_VARIABLE gp_install_id_ov
)
# second line is install name
string(REGEX REPLACE ".*:\n" "" gp_install_id "${gp_install_id_ov}")
if(gp_install_id)
# trim
string(REGEX MATCH "[^\n ].*[^\n ]" gp_install_id "${gp_install_id}")
#message("INSTALL ID is \"${gp_install_id}\"")
endif()
endif()
# Analyze each line for file names that match the regular expression:
#
foreach(candidate ${candidates})
if("${candidate}" MATCHES "${gp_regex}")
# Extract information from each candidate:
if(gp_regex_error AND "${candidate}" MATCHES "${gp_regex_error}")
string(REGEX REPLACE "${gp_regex_fallback}" "\\1" raw_item "${candidate}")
else()
string(REGEX REPLACE "${gp_regex}" "\\1" raw_item "${candidate}")
endif()
if(gp_regex_cmp_count GREATER 1)
string(REGEX REPLACE "${gp_regex}" "\\2" raw_compat_version "${candidate}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}")
endif()
if(gp_regex_cmp_count GREATER 2)
string(REGEX REPLACE "${gp_regex}" "\\3" raw_current_version "${candidate}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}")
endif()
# Use the raw_item as the list entries returned by this function. Use the
# gp_resolve_item function to resolve it to an actual full path file if
# necessary.
#
set(item "${raw_item}")
# Add each item unless it is excluded:
#
set(add_item 1)
if("${item}" STREQUAL "${gp_install_id}")
set(add_item 0)
endif()
if(add_item AND ${exclude_system})
set(type "")
gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type)
if("${type}" STREQUAL "system")
set(add_item 0)
endif()
endif()
if(add_item)
list(LENGTH ${prerequisites_var} list_length_before_append)
gp_append_unique(${prerequisites_var} "${item}")
list(LENGTH ${prerequisites_var} list_length_after_append)
if(${recurse})
# If item was really added, this is the first time we have seen it.
# Add it to unseen_prereqs so that we can recursively add *its*
# prerequisites...
#
# But first: resolve its name to an absolute full path name such
# that the analysis tools can simply accept it as input.
#
if(NOT list_length_before_append EQUAL list_length_after_append)
gp_resolve_item("${target}" "${item}" "${exepath}" "${dirs}" resolved_item)
set(unseen_prereqs ${unseen_prereqs} "${resolved_item}")
endif()
endif()
endif()
else()
if(verbose)
message(STATUS "ignoring non-matching line: '${candidate}'")
endif()
endif()
endforeach()
list(LENGTH ${prerequisites_var} prerequisites_var_length)
if(prerequisites_var_length GREATER 0)
list(SORT ${prerequisites_var})
endif()
if(${recurse})
set(more_inputs ${unseen_prereqs})
foreach(input ${more_inputs})
get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${exepath}" "${dirs}")
endforeach()
endif()
set(${prerequisites_var} ${${prerequisites_var}} PARENT_SCOPE)
endfunction()
function(list_prerequisites target)
if("${ARGV1}" STREQUAL "")
set(all 1)
else()
set(all "${ARGV1}")
endif()
if("${ARGV2}" STREQUAL "")
set(exclude_system 0)
else()
set(exclude_system "${ARGV2}")
endif()
if("${ARGV3}" STREQUAL "")
set(verbose 0)
else()
set(verbose "${ARGV3}")
endif()
set(count 0)
set(count_str "")
set(print_count "${verbose}")
set(print_prerequisite_type "${verbose}")
set(print_target "${verbose}")
set(type_str "")
get_filename_component(exepath "${target}" PATH)
set(prereqs "")
get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${exepath}" "")
if(print_target)
message(STATUS "File '${target}' depends on:")
endif()
foreach(d ${prereqs})
math(EXPR count "${count} + 1")
if(print_count)
set(count_str "${count}. ")
endif()
if(print_prerequisite_type)
gp_file_type("${target}" "${d}" type)
set(type_str " (${type})")
endif()
message(STATUS "${count_str}${d}${type_str}")
endforeach()
endfunction()
function(list_prerequisites_by_glob glob_arg glob_exp)
message(STATUS "=============================================================================")
message(STATUS "List prerequisites of executables matching ${glob_arg} '${glob_exp}'")
message(STATUS "")
file(${glob_arg} file_list ${glob_exp})
foreach(f ${file_list})
is_file_executable("${f}" is_f_executable)
if(is_f_executable)
message(STATUS "=============================================================================")
list_prerequisites("${f}" ${ARGN})
message(STATUS "")
endif()
endforeach()
endfunction()

View File

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSCameraUsageDescription</key>
<string>A Minecraft mod wants to access your camera.</string>
<key>NSMicrophoneUsageDescription</key>
<string>A Minecraft mod wants to access your microphone.</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>

View File

@ -5,6 +5,7 @@ set(TEST_RESOURCE_PATH ${CMAKE_CURRENT_LIST_DIR})
message(${TEST_RESOURCE_PATH}) message(${TEST_RESOURCE_PATH})
function(add_unit_test name) function(add_unit_test name)
if(BUILD_TESTING)
set(options "") set(options "")
set(oneValueArgs DATA) set(oneValueArgs DATA)
set(multiValueArgs SOURCES LIBS) set(multiValueArgs SOURCES LIBS)
@ -45,4 +46,5 @@ function(add_unit_test name)
target_include_directories(${name}_test PRIVATE "${TEST_RESOURCE_PATH}/UnitTest/") target_include_directories(${name}_test PRIVATE "${TEST_RESOURCE_PATH}/UnitTest/")
add_test(NAME ${name} COMMAND ${name}_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) add_test(NAME ${name} COMMAND ${name}_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif()
endfunction() endfunction()

View File

@ -1,881 +0,0 @@
# - Use Module for Java
# This file provides functions for Java. It is assumed that FindJava.cmake
# has already been loaded. See FindJava.cmake for information on how to
# load Java into your CMake project.
#
# add_jar(TARGET_NAME SRC1 SRC2 .. SRCN RCS1 RCS2 .. RCSN)
#
# This command creates a <TARGET_NAME>.jar. It compiles the given source
# files (SRC) and adds the given resource files (RCS) to the jar file.
# If only resource files are given then just a jar file is created.
#
# Additional instructions:
# To add compile flags to the target you can set these flags with
# the following variable:
#
# set(CMAKE_JAVA_COMPILE_FLAGS -nowarn)
#
# To add a path or a jar file to the class path you can do this
# with the CMAKE_JAVA_INCLUDE_PATH variable.
#
# set(CMAKE_JAVA_INCLUDE_PATH /usr/share/java/shibboleet.jar)
#
# To use a different output name for the target you can set it with:
#
# set(CMAKE_JAVA_TARGET_OUTPUT_NAME shibboleet.jar)
# add_jar(foobar foobar.java)
#
# To use a different output directory than CMAKE_CURRENT_BINARY_DIR
# you can set it with:
#
# set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/bin)
#
# To define an entry point in your jar you can set it with:
#
# set(CMAKE_JAVA_JAR_ENTRY_POINT com/examples/MyProject/Main)
#
# To add a VERSION to the target output name you can set it using
# CMAKE_JAVA_TARGET_VERSION. This will create a jar file with the name
# shibboleet-1.0.0.jar and will create a symlink shibboleet.jar
# pointing to the jar with the version information.
#
# set(CMAKE_JAVA_TARGET_VERSION 1.2.0)
# add_jar(shibboleet shibbotleet.java)
#
# If the target is a JNI library, utilize the following commands to
# create a JNI symbolic link:
#
# set(CMAKE_JNI_TARGET TRUE)
# set(CMAKE_JAVA_TARGET_VERSION 1.2.0)
# add_jar(shibboleet shibbotleet.java)
# install_jar(shibboleet ${LIB_INSTALL_DIR}/shibboleet)
# install_jni_symlink(shibboleet ${JAVA_LIB_INSTALL_DIR})
#
# If a single target needs to produce more than one jar from its
# java source code, to prevent the accumulation of duplicate class
# files in subsequent jars, set/reset CMAKE_JAR_CLASSES_PREFIX prior
# to calling the add_jar() function:
#
# set(CMAKE_JAR_CLASSES_PREFIX com/redhat/foo)
# add_jar(foo foo.java)
#
# set(CMAKE_JAR_CLASSES_PREFIX com/redhat/bar)
# add_jar(bar bar.java)
#
# Target Properties:
# The add_jar() functions sets some target properties. You can get these
# properties with the
# get_property(TARGET <target_name> PROPERTY <propery_name>)
# command.
#
# INSTALL_FILES The files which should be installed. This is used by
# install_jar().
# JNI_SYMLINK The JNI symlink which should be installed.
# This is used by install_jni_symlink().
# JAR_FILE The location of the jar file so that you can include
# it.
# CLASS_DIR The directory where the class files can be found. For
# example to use them with javah.
#
# find_jar(<VAR>
# name | NAMES name1 [name2 ...]
# [PATHS path1 [path2 ... ENV var]]
# [VERSIONS version1 [version2]]
# [DOC "cache documentation string"]
# )
#
# This command is used to find a full path to the named jar. A cache
# entry named by <VAR> is created to stor the result of this command. If
# the full path to a jar is found the result is stored in the variable
# and the search will not repeated unless the variable is cleared. If
# nothing is found, the result will be <VAR>-NOTFOUND, and the search
# will be attempted again next time find_jar is invoked with the same
# variable.
# The name of the full path to a file that is searched for is specified
# by the names listed after NAMES argument. Additional search locations
# can be specified after the PATHS argument. If you require special a
# version of a jar file you can specify it with the VERSIONS argument.
# The argument after DOC will be used for the documentation string in
# the cache.
#
# install_jar(TARGET_NAME DESTINATION)
#
# This command installs the TARGET_NAME files to the given DESTINATION.
# It should be called in the same scope as add_jar() or it will fail.
#
# install_jni_symlink(TARGET_NAME DESTINATION)
#
# This command installs the TARGET_NAME JNI symlinks to the given
# DESTINATION. It should be called in the same scope as add_jar()
# or it will fail.
#
# create_javadoc(<VAR>
# PACKAGES pkg1 [pkg2 ...]
# [SOURCEPATH <sourcepath>]
# [CLASSPATH <classpath>]
# [INSTALLPATH <install path>]
# [DOCTITLE "the documentation title"]
# [WINDOWTITLE "the title of the document"]
# [AUTHOR TRUE|FALSE]
# [USE TRUE|FALSE]
# [VERSION TRUE|FALSE]
# )
#
# Create java documentation based on files or packages. For more
# details please read the javadoc manpage.
#
# There are two main signatures for create_javadoc. The first
# signature works with package names on a path with source files:
#
# Example:
# create_javadoc(my_example_doc
# PACKAGES com.exmaple.foo com.example.bar
# SOURCEPATH "${CMAKE_CURRENT_SOURCE_DIR}"
# CLASSPATH ${CMAKE_JAVA_INCLUDE_PATH}
# WINDOWTITLE "My example"
# DOCTITLE "<h1>My example</h1>"
# AUTHOR TRUE
# USE TRUE
# VERSION TRUE
# )
#
# The second signature for create_javadoc works on a given list of
# files.
#
# create_javadoc(<VAR>
# FILES file1 [file2 ...]
# [CLASSPATH <classpath>]
# [INSTALLPATH <install path>]
# [DOCTITLE "the documentation title"]
# [WINDOWTITLE "the title of the document"]
# [AUTHOR TRUE|FALSE]
# [USE TRUE|FALSE]
# [VERSION TRUE|FALSE]
# )
#
# Example:
# create_javadoc(my_example_doc
# FILES ${example_SRCS}
# CLASSPATH ${CMAKE_JAVA_INCLUDE_PATH}
# WINDOWTITLE "My example"
# DOCTITLE "<h1>My example</h1>"
# AUTHOR TRUE
# USE TRUE
# VERSION TRUE
# )
#
# Both signatures share most of the options. These options are the
# same as what you can find in the javadoc manpage. Please look at
# the manpage for CLASSPATH, DOCTITLE, WINDOWTITLE, AUTHOR, USE and
# VERSION.
#
# The documentation will be by default installed to
#
# ${CMAKE_INSTALL_PREFIX}/share/javadoc/<VAR>
#
# if you don't set the INSTALLPATH.
#
#=============================================================================
# Copyright 2010-2011 Andreas schneider <asn@redhat.com>
# Copyright 2010 Ben Boeckel <ben.boeckel@kitware.com>
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
function (__java_copy_file src dest comment)
add_custom_command(
OUTPUT ${dest}
COMMAND cmake -E copy_if_different
ARGS ${src}
${dest}
DEPENDS ${src}
COMMENT ${comment})
endfunction (__java_copy_file src dest comment)
# define helper scripts
set(_JAVA_CLASS_FILELIST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/UseJavaClassFilelist.cmake)
set(_JAVA_SYMLINK_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/UseJavaSymlinks.cmake)
function(add_jar _TARGET_NAME)
set(_JAVA_SOURCE_FILES ${ARGN})
if (NOT DEFINED CMAKE_JAVA_TARGET_OUTPUT_DIR)
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
endif(NOT DEFINED CMAKE_JAVA_TARGET_OUTPUT_DIR)
if (CMAKE_JAVA_JAR_ENTRY_POINT)
set(_ENTRY_POINT_OPTION e)
set(_ENTRY_POINT_VALUE ${CMAKE_JAVA_JAR_ENTRY_POINT})
endif (CMAKE_JAVA_JAR_ENTRY_POINT)
if (LIBRARY_OUTPUT_PATH)
set(CMAKE_JAVA_LIBRARY_OUTPUT_PATH ${LIBRARY_OUTPUT_PATH})
else (LIBRARY_OUTPUT_PATH)
set(CMAKE_JAVA_LIBRARY_OUTPUT_PATH ${CMAKE_JAVA_TARGET_OUTPUT_DIR})
endif (LIBRARY_OUTPUT_PATH)
set(CMAKE_JAVA_INCLUDE_PATH
${CMAKE_JAVA_INCLUDE_PATH}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_JAVA_OBJECT_OUTPUT_PATH}
${CMAKE_JAVA_LIBRARY_OUTPUT_PATH}
)
if (WIN32 AND NOT CYGWIN AND NOT CMAKE_CROSSCOMPILING)
set(CMAKE_JAVA_INCLUDE_FLAG_SEP ";")
else ()
set(CMAKE_JAVA_INCLUDE_FLAG_SEP ":")
endif()
foreach (JAVA_INCLUDE_DIR ${CMAKE_JAVA_INCLUDE_PATH})
set(CMAKE_JAVA_INCLUDE_PATH_FINAL "${CMAKE_JAVA_INCLUDE_PATH_FINAL}${CMAKE_JAVA_INCLUDE_FLAG_SEP}${JAVA_INCLUDE_DIR}")
endforeach(JAVA_INCLUDE_DIR)
set(CMAKE_JAVA_CLASS_OUTPUT_PATH "${CMAKE_JAVA_TARGET_OUTPUT_DIR}${CMAKE_FILES_DIRECTORY}/${_TARGET_NAME}.dir")
set(_JAVA_TARGET_OUTPUT_NAME "${_TARGET_NAME}.jar")
if (CMAKE_JAVA_TARGET_OUTPUT_NAME AND CMAKE_JAVA_TARGET_VERSION)
set(_JAVA_TARGET_OUTPUT_NAME "${CMAKE_JAVA_TARGET_OUTPUT_NAME}-${CMAKE_JAVA_TARGET_VERSION}.jar")
set(_JAVA_TARGET_OUTPUT_LINK "${CMAKE_JAVA_TARGET_OUTPUT_NAME}.jar")
elseif (CMAKE_JAVA_TARGET_VERSION)
set(_JAVA_TARGET_OUTPUT_NAME "${_TARGET_NAME}-${CMAKE_JAVA_TARGET_VERSION}.jar")
set(_JAVA_TARGET_OUTPUT_LINK "${_TARGET_NAME}.jar")
elseif (CMAKE_JAVA_TARGET_OUTPUT_NAME)
set(_JAVA_TARGET_OUTPUT_NAME "${CMAKE_JAVA_TARGET_OUTPUT_NAME}.jar")
endif (CMAKE_JAVA_TARGET_OUTPUT_NAME AND CMAKE_JAVA_TARGET_VERSION)
# reset
set(CMAKE_JAVA_TARGET_OUTPUT_NAME)
set(_JAVA_CLASS_FILES)
set(_JAVA_COMPILE_FILES)
set(_JAVA_DEPENDS)
set(_JAVA_RESOURCE_FILES)
foreach(_JAVA_SOURCE_FILE ${_JAVA_SOURCE_FILES})
get_filename_component(_JAVA_EXT ${_JAVA_SOURCE_FILE} EXT)
get_filename_component(_JAVA_FILE ${_JAVA_SOURCE_FILE} NAME_WE)
get_filename_component(_JAVA_PATH ${_JAVA_SOURCE_FILE} PATH)
get_filename_component(_JAVA_FULL ${_JAVA_SOURCE_FILE} ABSOLUTE)
file(RELATIVE_PATH _JAVA_REL_BINARY_PATH ${CMAKE_JAVA_TARGET_OUTPUT_DIR} ${_JAVA_FULL})
file(RELATIVE_PATH _JAVA_REL_SOURCE_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${_JAVA_FULL})
string(LENGTH ${_JAVA_REL_BINARY_PATH} _BIN_LEN)
string(LENGTH ${_JAVA_REL_SOURCE_PATH} _SRC_LEN)
if (${_BIN_LEN} LESS ${_SRC_LEN})
set(_JAVA_REL_PATH ${_JAVA_REL_BINARY_PATH})
else (${_BIN_LEN} LESS ${_SRC_LEN})
set(_JAVA_REL_PATH ${_JAVA_REL_SOURCE_PATH})
endif (${_BIN_LEN} LESS ${_SRC_LEN})
get_filename_component(_JAVA_REL_PATH ${_JAVA_REL_PATH} PATH)
if (_JAVA_EXT MATCHES ".java")
list(APPEND _JAVA_COMPILE_FILES ${_JAVA_SOURCE_FILE})
set(_JAVA_CLASS_FILE "${CMAKE_JAVA_CLASS_OUTPUT_PATH}/${_JAVA_REL_PATH}/${_JAVA_FILE}.class")
set(_JAVA_CLASS_FILES ${_JAVA_CLASS_FILES} ${_JAVA_CLASS_FILE})
elseif (_JAVA_EXT MATCHES ".jar"
OR _JAVA_EXT MATCHES ".war"
OR _JAVA_EXT MATCHES ".ear"
OR _JAVA_EXT MATCHES ".sar")
list(APPEND CMAKE_JAVA_INCLUDE_PATH ${_JAVA_SOURCE_FILE})
elseif (_JAVA_EXT STREQUAL "")
list(APPEND CMAKE_JAVA_INCLUDE_PATH ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}} ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}_CLASSPATH})
list(APPEND _JAVA_DEPENDS ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}})
else (_JAVA_EXT MATCHES ".java")
__java_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/${_JAVA_SOURCE_FILE}
${CMAKE_JAVA_CLASS_OUTPUT_PATH}/${_JAVA_SOURCE_FILE}
"Copying ${_JAVA_SOURCE_FILE} to the build directory")
list(APPEND _JAVA_RESOURCE_FILES ${_JAVA_SOURCE_FILE})
endif (_JAVA_EXT MATCHES ".java")
endforeach(_JAVA_SOURCE_FILE)
# create an empty java_class_filelist
if (NOT EXISTS ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist)
file(WRITE ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist "")
endif()
if (_JAVA_COMPILE_FILES)
# Compile the java files and create a list of class files
add_custom_command(
# NOTE: this command generates an artificial dependency file
OUTPUT ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}
COMMAND ${Java_JAVAC_EXECUTABLE}
${CMAKE_JAVA_COMPILE_FLAGS}
-classpath "${CMAKE_JAVA_INCLUDE_PATH_FINAL}"
-d ${CMAKE_JAVA_CLASS_OUTPUT_PATH}
${_JAVA_COMPILE_FILES}
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}
DEPENDS ${_JAVA_COMPILE_FILES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Building Java objects for ${_TARGET_NAME}.jar"
)
add_custom_command(
OUTPUT ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist
COMMAND ${CMAKE_COMMAND}
-DCMAKE_JAVA_CLASS_OUTPUT_PATH=${CMAKE_JAVA_CLASS_OUTPUT_PATH}
-DCMAKE_JAR_CLASSES_PREFIX="${CMAKE_JAR_CLASSES_PREFIX}"
-P ${_JAVA_CLASS_FILELIST_SCRIPT}
DEPENDS ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endif (_JAVA_COMPILE_FILES)
# create the jar file
set(_JAVA_JAR_OUTPUT_PATH
${CMAKE_JAVA_TARGET_OUTPUT_DIR}/${_JAVA_TARGET_OUTPUT_NAME})
if (CMAKE_JNI_TARGET)
add_custom_command(
OUTPUT ${_JAVA_JAR_OUTPUT_PATH}
COMMAND ${Java_JAR_EXECUTABLE}
-cf${_ENTRY_POINT_OPTION} ${_JAVA_JAR_OUTPUT_PATH} ${_ENTRY_POINT_VALUE}
${_JAVA_RESOURCE_FILES} @java_class_filelist
COMMAND ${CMAKE_COMMAND}
-D_JAVA_TARGET_DIR=${CMAKE_JAVA_TARGET_OUTPUT_DIR}
-D_JAVA_TARGET_OUTPUT_NAME=${_JAVA_TARGET_OUTPUT_NAME}
-D_JAVA_TARGET_OUTPUT_LINK=${_JAVA_TARGET_OUTPUT_LINK}
-P ${_JAVA_SYMLINK_SCRIPT}
COMMAND ${CMAKE_COMMAND}
-D_JAVA_TARGET_DIR=${CMAKE_JAVA_TARGET_OUTPUT_DIR}
-D_JAVA_TARGET_OUTPUT_NAME=${_JAVA_JAR_OUTPUT_PATH}
-D_JAVA_TARGET_OUTPUT_LINK=${_JAVA_TARGET_OUTPUT_LINK}
-P ${_JAVA_SYMLINK_SCRIPT}
DEPENDS ${_JAVA_RESOURCE_FILES} ${_JAVA_DEPENDS} ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist
WORKING_DIRECTORY ${CMAKE_JAVA_CLASS_OUTPUT_PATH}
COMMENT "Creating Java archive ${_JAVA_TARGET_OUTPUT_NAME}"
)
else ()
add_custom_command(
OUTPUT ${_JAVA_JAR_OUTPUT_PATH}
COMMAND ${Java_JAR_EXECUTABLE}
-cf${_ENTRY_POINT_OPTION} ${_JAVA_JAR_OUTPUT_PATH} ${_ENTRY_POINT_VALUE}
${_JAVA_RESOURCE_FILES} @java_class_filelist
COMMAND ${CMAKE_COMMAND}
-D_JAVA_TARGET_DIR=${CMAKE_JAVA_TARGET_OUTPUT_DIR}
-D_JAVA_TARGET_OUTPUT_NAME=${_JAVA_TARGET_OUTPUT_NAME}
-D_JAVA_TARGET_OUTPUT_LINK=${_JAVA_TARGET_OUTPUT_LINK}
-P ${_JAVA_SYMLINK_SCRIPT}
WORKING_DIRECTORY ${CMAKE_JAVA_CLASS_OUTPUT_PATH}
DEPENDS ${_JAVA_RESOURCE_FILES} ${_JAVA_DEPENDS} ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist
COMMENT "Creating Java archive ${_JAVA_TARGET_OUTPUT_NAME}"
)
endif (CMAKE_JNI_TARGET)
# Add the target and make sure we have the latest resource files.
add_custom_target(${_TARGET_NAME} ALL DEPENDS ${_JAVA_JAR_OUTPUT_PATH})
set_property(
TARGET
${_TARGET_NAME}
PROPERTY
INSTALL_FILES
${_JAVA_JAR_OUTPUT_PATH}
)
if (_JAVA_TARGET_OUTPUT_LINK)
set_property(
TARGET
${_TARGET_NAME}
PROPERTY
INSTALL_FILES
${_JAVA_JAR_OUTPUT_PATH}
${CMAKE_JAVA_TARGET_OUTPUT_DIR}/${_JAVA_TARGET_OUTPUT_LINK}
)
if (CMAKE_JNI_TARGET)
set_property(
TARGET
${_TARGET_NAME}
PROPERTY
JNI_SYMLINK
${CMAKE_JAVA_TARGET_OUTPUT_DIR}/${_JAVA_TARGET_OUTPUT_LINK}
)
endif (CMAKE_JNI_TARGET)
endif (_JAVA_TARGET_OUTPUT_LINK)
set_property(
TARGET
${_TARGET_NAME}
PROPERTY
JAR_FILE
${_JAVA_JAR_OUTPUT_PATH}
)
set_property(
TARGET
${_TARGET_NAME}
PROPERTY
CLASSDIR
${CMAKE_JAVA_CLASS_OUTPUT_PATH}
)
endfunction(add_jar)
function(INSTALL_JAR _TARGET_NAME _DESTINATION)
get_property(__FILES
TARGET
${_TARGET_NAME}
PROPERTY
INSTALL_FILES
)
if (__FILES)
install(
FILES
${__FILES}
DESTINATION
${_DESTINATION}
)
else (__FILES)
message(SEND_ERROR "The target ${_TARGET_NAME} is not known in this scope.")
endif (__FILES)
endfunction(INSTALL_JAR _TARGET_NAME _DESTINATION)
function(INSTALL_JNI_SYMLINK _TARGET_NAME _DESTINATION)
get_property(__SYMLINK
TARGET
${_TARGET_NAME}
PROPERTY
JNI_SYMLINK
)
if (__SYMLINK)
install(
FILES
${__SYMLINK}
DESTINATION
${_DESTINATION}
)
else (__SYMLINK)
message(SEND_ERROR "The target ${_TARGET_NAME} is not known in this scope.")
endif (__SYMLINK)
endfunction(INSTALL_JNI_SYMLINK _TARGET_NAME _DESTINATION)
function (find_jar VARIABLE)
set(_jar_names)
set(_jar_files)
set(_jar_versions)
set(_jar_paths
/usr/share/java/
/usr/local/share/java/
${Java_JAR_PATHS})
set(_jar_doc "NOTSET")
set(_state "name")
foreach (arg ${ARGN})
if (${_state} STREQUAL "name")
if (${arg} STREQUAL "VERSIONS")
set(_state "versions")
elseif (${arg} STREQUAL "NAMES")
set(_state "names")
elseif (${arg} STREQUAL "PATHS")
set(_state "paths")
elseif (${arg} STREQUAL "DOC")
set(_state "doc")
else (${arg} STREQUAL "NAMES")
set(_jar_names ${arg})
if (_jar_doc STREQUAL "NOTSET")
set(_jar_doc "Finding ${arg} jar")
endif (_jar_doc STREQUAL "NOTSET")
endif (${arg} STREQUAL "VERSIONS")
elseif (${_state} STREQUAL "versions")
if (${arg} STREQUAL "NAMES")
set(_state "names")
elseif (${arg} STREQUAL "PATHS")
set(_state "paths")
elseif (${arg} STREQUAL "DOC")
set(_state "doc")
else (${arg} STREQUAL "NAMES")
set(_jar_versions ${_jar_versions} ${arg})
endif (${arg} STREQUAL "NAMES")
elseif (${_state} STREQUAL "names")
if (${arg} STREQUAL "VERSIONS")
set(_state "versions")
elseif (${arg} STREQUAL "PATHS")
set(_state "paths")
elseif (${arg} STREQUAL "DOC")
set(_state "doc")
else (${arg} STREQUAL "VERSIONS")
set(_jar_names ${_jar_names} ${arg})
if (_jar_doc STREQUAL "NOTSET")
set(_jar_doc "Finding ${arg} jar")
endif (_jar_doc STREQUAL "NOTSET")
endif (${arg} STREQUAL "VERSIONS")
elseif (${_state} STREQUAL "paths")
if (${arg} STREQUAL "VERSIONS")
set(_state "versions")
elseif (${arg} STREQUAL "NAMES")
set(_state "names")
elseif (${arg} STREQUAL "DOC")
set(_state "doc")
else (${arg} STREQUAL "VERSIONS")
set(_jar_paths ${_jar_paths} ${arg})
endif (${arg} STREQUAL "VERSIONS")
elseif (${_state} STREQUAL "doc")
if (${arg} STREQUAL "VERSIONS")
set(_state "versions")
elseif (${arg} STREQUAL "NAMES")
set(_state "names")
elseif (${arg} STREQUAL "PATHS")
set(_state "paths")
else (${arg} STREQUAL "VERSIONS")
set(_jar_doc ${arg})
endif (${arg} STREQUAL "VERSIONS")
endif (${_state} STREQUAL "name")
endforeach (arg ${ARGN})
if (NOT _jar_names)
message(FATAL_ERROR "find_jar: No name to search for given")
endif (NOT _jar_names)
foreach (jar_name ${_jar_names})
foreach (version ${_jar_versions})
set(_jar_files ${_jar_files} ${jar_name}-${version}.jar)
endforeach (version ${_jar_versions})
set(_jar_files ${_jar_files} ${jar_name}.jar)
endforeach (jar_name ${_jar_names})
find_file(${VARIABLE}
NAMES ${_jar_files}
PATHS ${_jar_paths}
DOC ${_jar_doc}
NO_DEFAULT_PATH)
endfunction (find_jar VARIABLE)
function(create_javadoc _target)
set(_javadoc_packages)
set(_javadoc_files)
set(_javadoc_sourcepath)
set(_javadoc_classpath)
set(_javadoc_installpath "${CMAKE_INSTALL_PREFIX}/share/javadoc")
set(_javadoc_doctitle)
set(_javadoc_windowtitle)
set(_javadoc_author FALSE)
set(_javadoc_version FALSE)
set(_javadoc_use FALSE)
set(_state "package")
foreach (arg ${ARGN})
if (${_state} STREQUAL "package")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
set(_javadoc_packages ${arg})
set(_state "packages")
endif ()
elseif (${_state} STREQUAL "packages")
if (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
list(APPEND _javadoc_packages ${arg})
endif ()
elseif (${_state} STREQUAL "files")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
list(APPEND _javadoc_files ${arg})
endif ()
elseif (${_state} STREQUAL "sourcepath")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
list(APPEND _javadoc_sourcepath ${arg})
endif ()
elseif (${_state} STREQUAL "classpath")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
list(APPEND _javadoc_classpath ${arg})
endif ()
elseif (${_state} STREQUAL "installpath")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
set(_javadoc_installpath ${arg})
endif ()
elseif (${_state} STREQUAL "doctitle")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
set(_javadoc_doctitle ${arg})
endif ()
elseif (${_state} STREQUAL "windowtitle")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
set(_javadoc_windowtitle ${arg})
endif ()
elseif (${_state} STREQUAL "author")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
set(_javadoc_author ${arg})
endif ()
elseif (${_state} STREQUAL "use")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
set(_javadoc_use ${arg})
endif ()
elseif (${_state} STREQUAL "version")
if (${arg} STREQUAL "PACKAGES")
set(_state "packages")
elseif (${arg} STREQUAL "FILES")
set(_state "files")
elseif (${arg} STREQUAL "SOURCEPATH")
set(_state "sourcepath")
elseif (${arg} STREQUAL "CLASSPATH")
set(_state "classpath")
elseif (${arg} STREQUAL "INSTALLPATH")
set(_state "installpath")
elseif (${arg} STREQUAL "DOCTITLE")
set(_state "doctitle")
elseif (${arg} STREQUAL "WINDOWTITLE")
set(_state "windowtitle")
elseif (${arg} STREQUAL "AUTHOR")
set(_state "author")
elseif (${arg} STREQUAL "USE")
set(_state "use")
elseif (${arg} STREQUAL "VERSION")
set(_state "version")
else ()
set(_javadoc_version ${arg})
endif ()
endif (${_state} STREQUAL "package")
endforeach (arg ${ARGN})
set(_javadoc_builddir ${CMAKE_CURRENT_BINARY_DIR}/javadoc/${_target})
set(_javadoc_options -d ${_javadoc_builddir})
if (_javadoc_sourcepath)
set(_start TRUE)
foreach(_path ${_javadoc_sourcepath})
if (_start)
set(_sourcepath ${_path})
set(_start FALSE)
else (_start)
set(_sourcepath ${_sourcepath}:${_path})
endif (_start)
endforeach(_path ${_javadoc_sourcepath})
set(_javadoc_options ${_javadoc_options} -sourcepath ${_sourcepath})
endif (_javadoc_sourcepath)
if (_javadoc_classpath)
set(_start TRUE)
foreach(_path ${_javadoc_classpath})
if (_start)
set(_classpath ${_path})
set(_start FALSE)
else (_start)
set(_classpath ${_classpath}:${_path})
endif (_start)
endforeach(_path ${_javadoc_classpath})
set(_javadoc_options ${_javadoc_options} -classpath "${_classpath}")
endif (_javadoc_classpath)
if (_javadoc_doctitle)
set(_javadoc_options ${_javadoc_options} -doctitle '${_javadoc_doctitle}')
endif (_javadoc_doctitle)
if (_javadoc_windowtitle)
set(_javadoc_options ${_javadoc_options} -windowtitle '${_javadoc_windowtitle}')
endif (_javadoc_windowtitle)
if (_javadoc_author)
set(_javadoc_options ${_javadoc_options} -author)
endif (_javadoc_author)
if (_javadoc_use)
set(_javadoc_options ${_javadoc_options} -use)
endif (_javadoc_use)
if (_javadoc_version)
set(_javadoc_options ${_javadoc_options} -version)
endif (_javadoc_version)
add_custom_target(${_target}_javadoc ALL
COMMAND ${Java_JAVADOC_EXECUTABLE} ${_javadoc_options}
${_javadoc_files}
${_javadoc_packages}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
install(
DIRECTORY ${_javadoc_builddir}
DESTINATION ${_javadoc_installpath}
)
endfunction(create_javadoc)

View File

@ -1,52 +0,0 @@
#
# This script create a list of compiled Java class files to be added to a
# jar file. This avoids including cmake files which get created in the
# binary directory.
#
#=============================================================================
# Copyright 2010-2011 Andreas schneider <asn@redhat.com>
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
if (CMAKE_JAVA_CLASS_OUTPUT_PATH)
if (EXISTS "${CMAKE_JAVA_CLASS_OUTPUT_PATH}")
set(_JAVA_GLOBBED_FILES)
if (CMAKE_JAR_CLASSES_PREFIX)
foreach(JAR_CLASS_PREFIX ${CMAKE_JAR_CLASSES_PREFIX})
message(STATUS "JAR_CLASS_PREFIX: ${JAR_CLASS_PREFIX}")
file(GLOB_RECURSE _JAVA_GLOBBED_TMP_FILES "${CMAKE_JAVA_CLASS_OUTPUT_PATH}/${JAR_CLASS_PREFIX}/*.class")
if (_JAVA_GLOBBED_TMP_FILES)
list(APPEND _JAVA_GLOBBED_FILES ${_JAVA_GLOBBED_TMP_FILES})
endif (_JAVA_GLOBBED_TMP_FILES)
endforeach(JAR_CLASS_PREFIX ${CMAKE_JAR_CLASSES_PREFIX})
else()
file(GLOB_RECURSE _JAVA_GLOBBED_FILES "${CMAKE_JAVA_CLASS_OUTPUT_PATH}/*.class")
endif (CMAKE_JAR_CLASSES_PREFIX)
set(_JAVA_CLASS_FILES)
# file(GLOB_RECURSE foo RELATIVE) is broken so we need this.
foreach(_JAVA_GLOBBED_FILE ${_JAVA_GLOBBED_FILES})
file(RELATIVE_PATH _JAVA_CLASS_FILE ${CMAKE_JAVA_CLASS_OUTPUT_PATH} ${_JAVA_GLOBBED_FILE})
set(_JAVA_CLASS_FILES ${_JAVA_CLASS_FILES}${_JAVA_CLASS_FILE}\n)
endforeach(_JAVA_GLOBBED_FILE ${_JAVA_GLOBBED_FILES})
# write to file
file(WRITE ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist ${_JAVA_CLASS_FILES})
else (EXISTS "${CMAKE_JAVA_CLASS_OUTPUT_PATH}")
message(SEND_ERROR "FATAL: Java class output path doesn't exist")
endif (EXISTS "${CMAKE_JAVA_CLASS_OUTPUT_PATH}")
else (CMAKE_JAVA_CLASS_OUTPUT_PATH)
message(SEND_ERROR "FATAL: Can't find CMAKE_JAVA_CLASS_OUTPUT_PATH")
endif (CMAKE_JAVA_CLASS_OUTPUT_PATH)

View File

@ -1,32 +0,0 @@
#
# Helper script for UseJava.cmake
#
#=============================================================================
# Copyright 2010-2011 Andreas schneider <asn@redhat.com>
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
if (UNIX AND _JAVA_TARGET_OUTPUT_LINK)
if (_JAVA_TARGET_OUTPUT_NAME)
find_program(LN_EXECUTABLE
NAMES
ln
)
execute_process(
COMMAND ${LN_EXECUTABLE} -sf "${_JAVA_TARGET_OUTPUT_NAME}" "${_JAVA_TARGET_OUTPUT_LINK}"
WORKING_DIRECTORY ${_JAVA_TARGET_DIR}
)
else (_JAVA_TARGET_OUTPUT_NAME)
message(SEND_ERROR "FATAL: Can't find _JAVA_TARGET_OUTPUT_NAME")
endif (_JAVA_TARGET_OUTPUT_NAME)
endif (UNIX AND _JAVA_TARGET_OUTPUT_LINK)

View File

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

View File

@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1641205782, "lastModified": 1650374568,
"narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=", "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7", "rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -16,21 +16,6 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils": {
"locked": {
"lastModified": 1642700792,
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"libnbtplusplus": { "libnbtplusplus": {
"flake": false, "flake": false,
"locked": { "locked": {
@ -49,43 +34,25 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1643169865, "lastModified": 1654665288,
"narHash": "sha256-+KIpNRazbc8Gac9jdWCKQkFv9bjceaLaLhlwqUEYu8c=", "narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "945ec499041db73043f745fad3b2a3a01e826081", "rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "nixos-unstable", "ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"quazip": {
"flake": false,
"locked": {
"lastModified": 1643049383,
"narHash": "sha256-LcJY6yd6GyeL7X5MP4L94diceM1TYespWByliBsjK98=",
"owner": "stachenov",
"repo": "quazip",
"rev": "09ec1d10c6d627f895109b21728dda000cbfa7d1",
"type": "github"
},
"original": {
"owner": "stachenov",
"repo": "quazip",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"libnbtplusplus": "libnbtplusplus", "libnbtplusplus": "libnbtplusplus",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs"
"quazip": "quazip"
} }
} }
}, },

View File

@ -1,50 +1,39 @@
{ {
description = "SneedMC flake"; description = "A sneedful launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)";
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils"; inputs = {
inputs.flake-compat = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
url = "github:edolstra/flake-compat"; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
flake = false; libnbtplusplus = { url = "github:multimc/libnbtplusplus"; flake = false; };
};
inputs.libnbtplusplus = {
url = "github:multimc/libnbtplusplus";
flake = false;
};
inputs.quazip = {
url = "github:stachenov/quazip";
flake = false;
}; };
outputs = args@{ self, nixpkgs, flake-utils, libnbtplusplus, quazip, ... }: outputs = { self, nixpkgs, libnbtplusplus, ... }:
let let
systems = [ # Generate a user-friendly version number.
"aarch64-linux" version = builtins.substring 0 8 self.lastModifiedDate;
# "aarch64-darwin" # qtbase is currently broken
"i686-linux" # System types to support (qtbase is currently broken for "aarch64-darwin")
"x86_64-darwin" supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" ];
"x86_64-linux"
]; # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
in { forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
overlay = final: prev: {
inherit (self.packages.${final.system}) sneedmc; # Nixpkgs instantiated for supported system types.
}; pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
} // flake-utils.lib.eachSystem systems (system: in
let pkgs = import nixpkgs { inherit system; }; {
in { packages = forAllSystems (system: rec {
packages = { sneedmc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
sneedmc = pkgs.libsForQt5.callPackage ./packages/nix/sneedmc { sneedmc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
inherit self;
submoduleQuazip = quazip; default = sneedmc;
submoduleNbt = libnbtplusplus;
};
};
apps = {
sneedmc = flake-utils.lib.mkApp {
name = "sneedmc";
drv = self.packages.${system}.sneedmc;
};
};
defaultPackage = self.packages.${system}.sneedmc;
defaultApp = self.apps.${system}.sneedmc;
}); });
defaultPackage = forAllSystems (system: self.packages.${system}.default);
apps = forAllSystems (system: rec { sneedmc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/sneedmc"; }; default = sneedmc; });
defaultApp = forAllSystems (system: self.apps.${system}.default);
overlay = final: prev: { sneedmc = self.defaultPackage.${final.system}; };
};
} }

View File

@ -1,6 +1,43 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "net/PasteUpload.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
@ -26,6 +63,7 @@
#include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h"
#include "ui/setupwizard/PasteWizardPage.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
@ -45,6 +83,7 @@
#include <QStringList> #include <QStringList>
#include <QDebug> #include <QDebug>
#include <QStyleFactory> #include <QStyleFactory>
#include <QWindow>
#include "InstanceList.h" #include "InstanceList.h"
@ -113,6 +152,47 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr); fflush(stderr);
} }
#ifdef LAUNCHER_WITH_UPDATER
QString getIdealPlatform(QString currentPlatform) {
auto info = Sys::getKernelInfo();
switch(info.kernelType) {
case Sys::KernelType::Darwin: {
if(info.kernelMajor >= 17) {
// macOS 10.13 or newer
return "osx64-5.15.2";
}
else {
// macOS 10.12 or older
return "osx64";
}
}
case Sys::KernelType::Windows: {
// FIXME: 5.15.2 is not stable on Windows, due to a large number of completely unpredictable and hard to reproduce issues
break;
/*
if(info.kernelMajor == 6 && info.kernelMinor >= 1) {
// Windows 7
return "win32-5.15.2";
}
else if (info.kernelMajor > 6) {
// Above Windows 7
return "win32-5.15.2";
}
else {
// Below Windows 7
return "win32";
}
*/
}
case Sys::KernelType::Undetermined:
case Sys::KernelType::Linux: {
break;
}
}
return currentPlatform;
}
#endif
} }
Application::Application(int &argc, char **argv) : QApplication(argc, argv) Application::Application(int &argc, char **argv) : QApplication(argc, argv)
@ -146,32 +226,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setApplicationName(BuildConfig.LAUNCHER_NAME); setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(BuildConfig.LAUNCHER_DISPLAYNAME); setApplicationDisplayName(BuildConfig.LAUNCHER_DISPLAYNAME);
setApplicationVersion(BuildConfig.printableVersionString()); setApplicationVersion(BuildConfig.printableVersionString());
#if (QT_VERSION >= QT_VERSION_CHECK(5,7,0))
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
#endif
startTime = QDateTime::currentDateTime(); startTime = QDateTime::currentDateTime();
#ifdef Q_OS_LINUX
{
QFile osrelease("/proc/sys/kernel/osrelease");
if (osrelease.open(QFile::ReadOnly | QFile::Text)) {
QTextStream in(&osrelease);
auto contents = in.readAll();
if(
contents.contains("WSL", Qt::CaseInsensitive) ||
contents.contains("Microsoft", Qt::CaseInsensitive)
) {
showFatalErrorMessage(
"Unsupported system detected!",
"Linux-on-Windows distributions are not supported.\n\n"
"Please use the Windows binary when playing on Windows."
);
return;
}
}
}
#endif
// Don't quit on hiding the last window // Don't quit on hiding the last window
this->setQuitOnLastWindowClosed(false); this->setQuitOnLastWindowClosed(false);
@ -261,6 +318,26 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString origcwdPath = QDir::currentPath(); QString origcwdPath = QDir::currentPath();
QString binPath = applicationDirPath(); QString binPath = applicationDirPath();
{
// Root path is used for updates and portable data
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr
m_rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32)
m_rootPath = binPath;
#elif defined(Q_OS_MAC)
QDir foo(FS::PathCombine(binPath, "../.."));
m_rootPath = foo.absolutePath();
// on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues)
FS::updateTimestamp(m_rootPath);
#endif
#ifdef LAUNCHER_JARS_LOCATION
m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION);
#endif
}
QString adjustedBy; QString adjustedBy;
QString dataPath; QString dataPath;
// change folder // change folder
@ -269,7 +346,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{ {
// the dir param. it makes multimc data path point to whatever the user specified // the dir param. it makes multimc data path point to whatever the user specified
// on command line // on command line
adjustedBy += "Command line " + dirParam; adjustedBy = "Command line";
dataPath = dirParam; dataPath = dirParam;
} }
else else
@ -283,10 +360,23 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
#elif defined(Q_OS_MAC) #elif defined(Q_OS_MAC)
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
dataPath = foo.absolutePath(); dataPath = foo.absolutePath();
adjustedBy += "Fallback to special Mac location " + dataPath; adjustedBy = "Persistent data path";
#else
dataPath = applicationDirPath(); #ifdef Q_OS_LINUX
adjustedBy += "Fallback to binary path " + dataPath; // TODO: this should be removed in a future version
// TODO: provide a migration path similar to macOS migration
QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
if (bar.exists()) {
dataPath = bar.absolutePath();
adjustedBy = "Legacy data path";
}
#endif
#ifndef Q_OS_MACOS
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath;
adjustedBy = "Portable data path";
}
#endif #endif
} }
@ -327,69 +417,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
return; return;
} }
#if defined(Q_OS_MAC)
// move user data to new location if on macOS and it still exists in Contents/MacOS
QDir fi(applicationDirPath());
QString originalData = fi.absolutePath();
// if the config file exists in Contents/MacOS, then user data is still there and needs to moved
if (QFileInfo::exists(FS::PathCombine(originalData, BuildConfig.LAUNCHER_CONFIGFILE)))
{
if (!QFileInfo::exists(FS::PathCombine(originalData, "dontmovemacdata")))
{
QMessageBox::StandardButton askMoveDialogue;
askMoveDialogue = QMessageBox::question(
nullptr,
BuildConfig.LAUNCHER_DISPLAYNAME,
"Would you like to move application data to a new data location? It will improve the launcher's performance, but if you switch to older versions it will look like instances have disappeared. If you select no, you can migrate later in settings. You should select yes unless you're commonly switching between different versions (eg. develop and stable).",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes
);
if (askMoveDialogue == QMessageBox::Yes)
{
qDebug() << "On macOS and found config file in old location, moving user data...";
QDir dir;
QStringList dataFiles {
"*.log", // Launcher log files: ${Launcher_Name}-@.log
"accounts.json",
"accounts",
"assets",
"cache",
"icons",
"instances",
"libraries",
"meta",
"metacache",
"mods",
BuildConfig.LAUNCHER_CONFIGFILE,
"themes",
"translations"
};
QDirIterator files(originalData, dataFiles);
while (files.hasNext()) {
QString filePath(files.next());
QString fileName(files.fileName());
if (!dir.rename(filePath, FS::PathCombine(dataPath, fileName)))
{
qWarning() << "Failed to move " << fileName;
}
}
}
else
{
dataPath = originalData;
QDir::setCurrent(dataPath);
QFile file(originalData + "/dontmovemacdata");
file.open(QIODevice::WriteOnly);
}
}
else
{
dataPath = originalData;
QDir::setCurrent(dataPath);
}
}
#endif
/* /*
* Establish the mechanism for communication with an already running SneedMC that uses the same data path. * Establish the mechanism for communication with an already running SneedMC that uses the same data path.
* If there is one, tell it what the user actually wanted to do and exit. * If there is one, tell it what the user actually wanted to do and exit.
@ -476,22 +503,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Log initialized."; qDebug() << "<> Log initialized.";
} }
// Set up paths
{ {
// Root path is used for updates.
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
QDir foo(FS::PathCombine(binPath, ".."));
m_rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32)
m_rootPath = binPath;
#elif defined(Q_OS_MAC)
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
m_rootPath = foo.absolutePath();
#endif
#ifdef MULTIMC_JARS_LOCATION
m_jarsPath = TOSTRING(MULTIMC_JARS_LOCATION);
#endif
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Version : " << BuildConfig.printableVersionString();
@ -549,6 +561,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Remembered state // Remembered state
m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
m_settings->registerSetting("MenuBarInsteadOfToolBar", false);
QString defaultMonospace; QString defaultMonospace;
int defaultSize = 11; int defaultSize = 11;
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@ -618,6 +632,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("JavaVendor", ""); m_settings->registerSetting("JavaVendor", "");
m_settings->registerSetting("LastHostname", ""); m_settings->registerSetting("LastHostname", "");
m_settings->registerSetting("JvmArgs", ""); m_settings->registerSetting("JvmArgs", "");
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);
// Native library workarounds // Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false); m_settings->registerSetting("UseNativeOpenAL", false);
@ -631,6 +647,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Minecraft launch method // Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
// Minecraft offline player name
m_settings->registerSetting("LastOfflinePlayerName", "");
// Wrapper command for launch // Wrapper command for launch
m_settings->registerSetting("WrapperCommand", ""); m_settings->registerSetting("WrapperCommand", "");
@ -657,13 +679,43 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("NewInstanceGeometry", ""); m_settings->registerSetting("NewInstanceGeometry", "");
// pastebin URL m_settings->registerSetting("UpdateDialogGeometry", "");
m_settings->registerSetting("PastebinURL", "https://0x0.st");
// HACK: This code feels so stupid is there a less stupid way of doing this?
{
m_settings->registerSetting("PastebinURL", "");
m_settings->registerSetting("PastebinType", PasteUpload::PasteType::Mclogs);
m_settings->registerSetting("PastebinCustomAPIBase", "");
QString pastebinURL = m_settings->get("PastebinURL").toString();
bool userHadDefaultPastebin = pastebinURL == "https://0x0.st";
if (!pastebinURL.isEmpty() && !userHadDefaultPastebin)
{
m_settings->set("PastebinType", PasteUpload::PasteType::NullPointer);
m_settings->set("PastebinCustomAPIBase", pastebinURL);
m_settings->reset("PastebinURL");
}
bool ok;
int pasteType = m_settings->get("PastebinType").toInt(&ok);
// If PastebinType is invalid then reset the related settings.
if (!ok || !(PasteUpload::PasteType::First <= pasteType && pasteType <= PasteUpload::PasteType::Last))
{
m_settings->reset("PastebinType");
m_settings->reset("PastebinCustomAPIBase");
}
}
// meta URL
m_settings->registerSetting("MetaURLOverride", "");
m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("CloseAfterLaunch", false);
m_settings->registerSetting("QuitAfterGameStop", false);
// Custom MSA credentials // Custom MSA credentials
m_settings->registerSetting("MSAClientIDOverride", ""); m_settings->registerSetting("MSAClientIDOverride", "");
m_settings->registerSetting("CFKeyOverride", "");
m_settings->registerSetting("UserAgentOverride", "");
// Init page provider // Init page provider
{ {
@ -792,6 +844,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath()); m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath()); m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath()); m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
@ -816,6 +869,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_mcedit.reset(new MCEditTool(m_settings)); m_mcedit.reset(new MCEditTool(m_settings));
} }
#ifdef Q_OS_MACOS
connect(this, &Application::clickedOnDock, [this]() {
this->showMainWindow();
});
#endif
connect(this, &Application::aboutToQuit, [this](){ connect(this, &Application::aboutToQuit, [this](){
if(m_instances) if(m_instances)
{ {
@ -847,6 +906,10 @@ bool Application::createSetupWizard()
{ {
bool javaRequired = [&]() bool javaRequired = [&]()
{ {
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
if(ignoreJavaWizard) {
return false;
}
QString currentHostName = QHostInfo::localHostName(); QString currentHostName = QHostInfo::localHostName();
QString oldHostName = settings()->get("LastHostname").toString(); QString oldHostName = settings()->get("LastHostname").toString();
if (currentHostName != oldHostName) if (currentHostName != oldHostName)
@ -868,7 +931,8 @@ bool Application::createSetupWizard()
return true; return true;
return false; return false;
}(); }();
bool wizardRequired = javaRequired || languageRequired; bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired;
if(wizardRequired) if(wizardRequired)
{ {
@ -877,10 +941,16 @@ bool Application::createSetupWizard()
{ {
m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard)); m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard));
} }
if (javaRequired) if (javaRequired)
{ {
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
} }
if (pasteInterventionRequired)
{
m_setupWizard->addPage(new PasteWizardPage(m_setupWizard));
}
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show(); m_setupWizard->show();
return true; return true;
@ -888,6 +958,21 @@ bool Application::createSetupWizard()
return false; return false;
} }
bool Application::event(QEvent* event) {
#ifdef Q_OS_MACOS
if (event->type() == QEvent::ApplicationStateChange) {
auto ev = static_cast<QApplicationStateChangeEvent*>(event);
if (m_prevAppState == Qt::ApplicationActive
&& ev->applicationState() == Qt::ApplicationActive) {
emit clickedOnDock();
}
m_prevAppState = ev->applicationState();
}
#endif
return QApplication::event(event);
}
void Application::setupWizardFinished(int status) void Application::setupWizardFinished(int status)
{ {
qDebug() << "Wizard result =" << status; qDebug() << "Wizard result =" << status;
@ -1063,6 +1148,15 @@ std::vector<ITheme *> Application::getValidApplicationThemes()
return ret; return ret;
} }
bool Application::isFlatpak()
{
#ifdef Q_OS_LINUX
return QFile::exists("/.flatpak-info");
#else
return false;
#endif
}
void Application::setApplicationTheme(const QString& name, bool initial) void Application::setApplicationTheme(const QString& name, bool initial)
{ {
auto systemPalette = qApp->palette(); auto systemPalette = qApp->palette();
@ -1174,6 +1268,12 @@ bool Application::kill(InstancePtr instance)
return true; return true;
} }
void Application::closeCurrentWindow()
{
if (focusWindow())
focusWindow()->close();
}
void Application::addRunningInstance() void Application::addRunningInstance()
{ {
m_runningInstances ++; m_runningInstances ++;
@ -1426,7 +1526,7 @@ QString Application::getJarsPath()
{ {
return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
} }
return m_jarsPath; return FS::PathCombine(m_rootPath, m_jarsPath);
} }
QString Application::getMSAClientID() QString Application::getMSAClientID()
@ -1438,3 +1538,34 @@ QString Application::getMSAClientID()
return BuildConfig.MSA_CLIENT_ID; return BuildConfig.MSA_CLIENT_ID;
} }
QString Application::getCurseKey()
{
QString keyOverride = m_settings->get("CFKeyOverride").toString();
if (!keyOverride.isEmpty()) {
return keyOverride;
}
return BuildConfig.CURSEFORGE_API_KEY;
}
QString Application::getUserAgent()
{
QString uaOverride = m_settings->get("UserAgentOverride").toString();
if (!uaOverride.isEmpty()) {
return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
}
return BuildConfig.USER_AGENT;
}
QString Application::getUserAgentUncached()
{
QString uaOverride = m_settings->get("UserAgentOverride").toString();
if (!uaOverride.isEmpty()) {
uaOverride += " (Uncached)";
return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
}
return BuildConfig.USER_AGENT_UNCACHED;
}

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QApplication> #include <QApplication>
@ -57,6 +92,8 @@ public:
Application(int &argc, char **argv); Application(int &argc, char **argv);
virtual ~Application(); virtual ~Application();
bool event(QEvent* event) override;
std::shared_ptr<SettingsObject> settings() const { std::shared_ptr<SettingsObject> settings() const {
return m_settings; return m_settings;
} }
@ -67,6 +104,8 @@ public:
QIcon getThemedIcon(const QString& name); QIcon getThemedIcon(const QString& name);
bool isFlatpak();
void setIconTheme(const QString& name); void setIconTheme(const QString& name);
std::vector<ITheme *> getValidApplicationThemes(); std::vector<ITheme *> getValidApplicationThemes();
@ -112,6 +151,9 @@ public:
QString getJarsPath(); QString getJarsPath();
QString getMSAClientID(); QString getMSAClientID();
QString getCurseKey();
QString getUserAgent();
QString getUserAgentUncached();
/// this is the root of the 'installation'. Used for automatic updates /// this is the root of the 'installation'. Used for automatic updates
const QString &root() { const QString &root() {
@ -133,6 +175,10 @@ signals:
void globalSettingsAboutToOpen(); void globalSettingsAboutToOpen();
void globalSettingsClosed(); void globalSettingsClosed();
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
public slots: public slots:
bool launch( bool launch(
InstancePtr instance, InstancePtr instance,
@ -142,6 +188,7 @@ public slots:
MinecraftAccountPtr accountToUse = nullptr MinecraftAccountPtr accountToUse = nullptr
); );
bool kill(InstancePtr instance); bool kill(InstancePtr instance);
void closeCurrentWindow();
private slots: private slots:
void on_windowClose(); void on_windowClose();
@ -188,6 +235,10 @@ private:
QString m_rootPath; QString m_rootPath;
Status m_status = Application::StartingUp; Status m_status = Application::StartingUp;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
#endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams // used on Windows to attach the standard IO streams
bool consoleAttached = false; bool consoleAttached = false;

View File

@ -1,4 +1,25 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -40,6 +61,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0);
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
// a locally stored instance
if (!m_settings->getSetting("InstanceType"))
m_settings->registerSetting("InstanceType", "");
// Custom Commands // Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting); m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
@ -55,6 +81,14 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
// Managed Packs
m_settings->registerSetting("ManagedPack", false);
m_settings->registerSetting("ManagedPackType", "");
m_settings->registerSetting("ManagedPackID", "");
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
} }
QString BaseInstance::getPreLaunchCommand() QString BaseInstance::getPreLaunchCommand()
@ -72,6 +106,46 @@ QString BaseInstance::getPostExitCommand()
return settings()->get("PostExitCommand").toString(); return settings()->get("PostExitCommand").toString();
} }
bool BaseInstance::isManagedPack()
{
return settings()->get("ManagedPack").toBool();
}
QString BaseInstance::getManagedPackType()
{
return settings()->get("ManagedPackType").toString();
}
QString BaseInstance::getManagedPackID()
{
return settings()->get("ManagedPackID").toString();
}
QString BaseInstance::getManagedPackName()
{
return settings()->get("ManagedPackName").toString();
}
QString BaseInstance::getManagedPackVersionID()
{
return settings()->get("ManagedPackVersionID").toString();
}
QString BaseInstance::getManagedPackVersionName()
{
return settings()->get("ManagedPackVersionName").toString();
}
void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
{
settings()->set("ManagedPack", true);
settings()->set("ManagedPackType", type);
settings()->set("ManagedPackID", id);
settings()->set("ManagedPackName", name);
settings()->set("ManagedPackVersionID", versionId);
settings()->set("ManagedPackVersionName", version);
}
int BaseInstance::getConsoleMaxLines() const int BaseInstance::getConsoleMaxLines() const
{ {
auto lineSetting = settings()->getSetting("ConsoleMaxLines"); auto lineSetting = settings()->getSetting("ConsoleMaxLines");

View File

@ -1,4 +1,25 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -119,6 +140,14 @@ public:
QString getPostExitCommand(); QString getPostExitCommand();
QString getWrapperCommand(); QString getWrapperCommand();
bool isManagedPack();
QString getManagedPackType();
QString getManagedPackID();
QString getManagedPackName();
QString getManagedPackVersionID();
QString getManagedPackVersionName();
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
/// guess log level from a line of game log /// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{ {

View File

@ -128,6 +128,8 @@ set(NET_SOURCES
net/PasteUpload.h net/PasteUpload.h
net/Sink.h net/Sink.h
net/Validator.h net/Validator.h
net/Upload.cpp
net/Upload.h
) )
# Game launch logic # Game launch logic
@ -144,6 +146,8 @@ set(LAUNCH_SOURCES
launch/steps/TextPrint.h launch/steps/TextPrint.h
launch/steps/Update.cpp launch/steps/Update.cpp
launch/steps/Update.h launch/steps/Update.h
launch/steps/QuitAfterGameStop.cpp
launch/steps/QuitAfterGameStop.h
launch/LaunchStep.cpp launch/LaunchStep.cpp
launch/LaunchStep.h launch/LaunchStep.h
launch/LaunchTask.cpp launch/LaunchTask.cpp
@ -211,6 +215,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/MigrationEligibilityStep.h minecraft/auth/steps/MigrationEligibilityStep.h
minecraft/auth/steps/MinecraftProfileStep.cpp minecraft/auth/steps/MinecraftProfileStep.cpp
minecraft/auth/steps/MinecraftProfileStep.h minecraft/auth/steps/MinecraftProfileStep.h
minecraft/auth/steps/MinecraftProfileStepMojang.cpp
minecraft/auth/steps/MinecraftProfileStepMojang.h
minecraft/auth/steps/MSAStep.cpp minecraft/auth/steps/MSAStep.cpp
minecraft/auth/steps/MSAStep.h minecraft/auth/steps/MSAStep.h
minecraft/auth/steps/XboxAuthorizationStep.cpp minecraft/auth/steps/XboxAuthorizationStep.cpp
@ -257,13 +263,6 @@ set(MINECRAFT_SOURCES
minecraft/launch/VerifyJavaInstall.cpp minecraft/launch/VerifyJavaInstall.cpp
minecraft/launch/VerifyJavaInstall.h minecraft/launch/VerifyJavaInstall.h
minecraft/legacy/LegacyModList.h
minecraft/legacy/LegacyModList.cpp
minecraft/legacy/LegacyInstance.h
minecraft/legacy/LegacyInstance.cpp
minecraft/legacy/LegacyUpgradeTask.h
minecraft/legacy/LegacyUpgradeTask.cpp
minecraft/GradleSpecifier.h minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.h minecraft/MinecraftInstance.h
@ -303,19 +302,22 @@ set(MINECRAFT_SOURCES
minecraft/WorldList.h minecraft/WorldList.h
minecraft/WorldList.cpp minecraft/WorldList.cpp
minecraft/mod/MetadataHandler.h
minecraft/mod/Mod.h minecraft/mod/Mod.h
minecraft/mod/Mod.cpp minecraft/mod/Mod.cpp
minecraft/mod/ModDetails.h minecraft/mod/ModDetails.h
minecraft/mod/ModFolderModel.h minecraft/mod/ModFolderModel.h
minecraft/mod/ModFolderModel.cpp minecraft/mod/ModFolderModel.cpp
minecraft/mod/ModFolderLoadTask.h
minecraft/mod/ModFolderLoadTask.cpp
minecraft/mod/LocalModParseTask.h
minecraft/mod/LocalModParseTask.cpp
minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.h
minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/ResourcePackFolderModel.cpp
minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/tasks/ModFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp
# Assets # Assets
minecraft/AssetsUtils.h minecraft/AssetsUtils.h
@ -331,13 +333,14 @@ set(MINECRAFT_SOURCES
mojang/PackageManifest.h mojang/PackageManifest.h
mojang/PackageManifest.cpp mojang/PackageManifest.cpp
) minecraft/Agent.h)
add_unit_test(GradleSpecifier add_unit_test(GradleSpecifier
SOURCES minecraft/GradleSpecifier_test.cpp SOURCES minecraft/GradleSpecifier_test.cpp
LIBS Launcher_logic LIBS Launcher_logic
) )
if(BUILD_TESTING)
add_executable(PackageManifest add_executable(PackageManifest
mojang/PackageManifest_test.cpp mojang/PackageManifest_test.cpp
) )
@ -353,6 +356,7 @@ add_test(
COMMAND PackageManifest COMMAND PackageManifest
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
) )
endif()
add_unit_test(MojangVersionFormat add_unit_test(MojangVersionFormat
SOURCES minecraft/MojangVersionFormat_test.cpp SOURCES minecraft/MojangVersionFormat_test.cpp
@ -394,6 +398,11 @@ set(TASKS_SOURCES
tasks/SequentialTask.cpp tasks/SequentialTask.cpp
) )
add_unit_test(Task
SOURCES tasks/Task_test.cpp
LIBS Launcher_logic
)
set(SETTINGS_SOURCES set(SETTINGS_SOURCES
# Settings # Settings
settings/INIFile.cpp settings/INIFile.cpp
@ -470,6 +479,19 @@ set(META_SOURCES
meta/Index.h meta/Index.h
) )
set(API_SOURCES
modplatform/ModIndex.h
modplatform/ModIndex.cpp
modplatform/ModAPI.h
modplatform/flame/FlameAPI.h
modplatform/modrinth/ModrinthAPI.h
modplatform/helpers/NetworkModAPI.h
modplatform/helpers/NetworkModAPI.cpp
)
set(FTB_SOURCES set(FTB_SOURCES
modplatform/legacy_ftb/PackFetchTask.h modplatform/legacy_ftb/PackFetchTask.h
modplatform/legacy_ftb/PackFetchTask.cpp modplatform/legacy_ftb/PackFetchTask.cpp
@ -496,6 +518,8 @@ set(FLAME_SOURCES
set(MODRINTH_SOURCES set(MODRINTH_SOURCES
modplatform/modrinth/ModrinthPackIndex.cpp modplatform/modrinth/ModrinthPackIndex.cpp
modplatform/modrinth/ModrinthPackIndex.h modplatform/modrinth/ModrinthPackIndex.h
modplatform/modrinth/ModrinthPackManifest.cpp
modplatform/modrinth/ModrinthPackManifest.h
) )
set(MODPACKSCH_SOURCES set(MODPACKSCH_SOURCES
@ -505,11 +529,24 @@ set(MODPACKSCH_SOURCES
modplatform/modpacksch/FTBPackManifest.cpp modplatform/modpacksch/FTBPackManifest.cpp
) )
set(PACKWIZ_SOURCES
modplatform/packwiz/Packwiz.h
modplatform/packwiz/Packwiz.cpp
)
add_unit_test(Packwiz
SOURCES modplatform/packwiz/Packwiz_test.cpp
DATA modplatform/packwiz/testdata
LIBS Launcher_logic
)
set(TECHNIC_SOURCES set(TECHNIC_SOURCES
modplatform/technic/SingleZipPackInstallTask.h modplatform/technic/SingleZipPackInstallTask.h
modplatform/technic/SingleZipPackInstallTask.cpp modplatform/technic/SingleZipPackInstallTask.cpp
modplatform/technic/SolderPackInstallTask.h modplatform/technic/SolderPackInstallTask.h
modplatform/technic/SolderPackInstallTask.cpp modplatform/technic/SolderPackInstallTask.cpp
modplatform/technic/SolderPackManifest.h
modplatform/technic/SolderPackManifest.cpp
modplatform/technic/TechnicPackProcessor.h modplatform/technic/TechnicPackProcessor.h
modplatform/technic/TechnicPackProcessor.cpp modplatform/technic/TechnicPackProcessor.cpp
) )
@ -521,6 +558,8 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLPackInstallTask.h modplatform/atlauncher/ATLPackInstallTask.h
modplatform/atlauncher/ATLPackManifest.cpp modplatform/atlauncher/ATLPackManifest.cpp
modplatform/atlauncher/ATLPackManifest.h modplatform/atlauncher/ATLPackManifest.h
modplatform/atlauncher/ATLShareCode.cpp
modplatform/atlauncher/ATLShareCode.h
) )
add_unit_test(Index add_unit_test(Index
@ -548,10 +587,12 @@ set(LOGIC_SOURCES
${TOOLS_SOURCES} ${TOOLS_SOURCES}
${META_SOURCES} ${META_SOURCES}
${ICONS_SOURCES} ${ICONS_SOURCES}
${API_SOURCES}
${FTB_SOURCES} ${FTB_SOURCES}
${FLAME_SOURCES} ${FLAME_SOURCES}
${MODRINTH_SOURCES} ${MODRINTH_SOURCES}
${MODPACKSCH_SOURCES} ${MODPACKSCH_SOURCES}
${PACKWIZ_SOURCES}
${TECHNIC_SOURCES} ${TECHNIC_SOURCES}
${ATLAUNCHER_SOURCES} ${ATLAUNCHER_SOURCES}
) )
@ -615,6 +656,8 @@ SET(LAUNCHER_SOURCES
ui/setupwizard/JavaWizardPage.h ui/setupwizard/JavaWizardPage.h
ui/setupwizard/LanguageWizardPage.cpp ui/setupwizard/LanguageWizardPage.cpp
ui/setupwizard/LanguageWizardPage.h ui/setupwizard/LanguageWizardPage.h
ui/setupwizard/PasteWizardPage.cpp
ui/setupwizard/PasteWizardPage.h
# GUI - themes # GUI - themes
ui/themes/FusionTheme.cpp ui/themes/FusionTheme.cpp
@ -668,8 +711,6 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/OtherLogsPage.h ui/pages/instance/OtherLogsPage.h
ui/pages/instance/ServersPage.cpp ui/pages/instance/ServersPage.cpp
ui/pages/instance/ServersPage.h ui/pages/instance/ServersPage.h
ui/pages/instance/LegacyUpgradePage.cpp
ui/pages/instance/LegacyUpgradePage.h
ui/pages/instance/WorldListPage.cpp ui/pages/instance/WorldListPage.cpp
ui/pages/instance/WorldListPage.h ui/pages/instance/WorldListPage.h
@ -697,6 +738,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/VanillaPage.cpp ui/pages/modplatform/VanillaPage.cpp
ui/pages/modplatform/VanillaPage.h ui/pages/modplatform/VanillaPage.h
ui/pages/modplatform/ModPage.cpp
ui/pages/modplatform/ModPage.h
ui/pages/modplatform/ModModel.cpp
ui/pages/modplatform/ModModel.h
ui/pages/modplatform/atlauncher/AtlFilterModel.cpp ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
ui/pages/modplatform/atlauncher/AtlFilterModel.h ui/pages/modplatform/atlauncher/AtlFilterModel.h
ui/pages/modplatform/atlauncher/AtlListModel.cpp ui/pages/modplatform/atlauncher/AtlListModel.cpp
@ -727,6 +773,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/flame/FlameModPage.cpp ui/pages/modplatform/flame/FlameModPage.cpp
ui/pages/modplatform/flame/FlameModPage.h ui/pages/modplatform/flame/FlameModPage.h
ui/pages/modplatform/modrinth/ModrinthPage.cpp
ui/pages/modplatform/modrinth/ModrinthPage.h
ui/pages/modplatform/modrinth/ModrinthModel.cpp
ui/pages/modplatform/modrinth/ModrinthModel.h
ui/pages/modplatform/technic/TechnicModel.cpp ui/pages/modplatform/technic/TechnicModel.cpp
ui/pages/modplatform/technic/TechnicModel.h ui/pages/modplatform/technic/TechnicModel.h
ui/pages/modplatform/technic/TechnicPage.cpp ui/pages/modplatform/technic/TechnicPage.cpp
@ -735,10 +786,10 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/ImportPage.cpp ui/pages/modplatform/ImportPage.cpp
ui/pages/modplatform/ImportPage.h ui/pages/modplatform/ImportPage.h
ui/pages/modplatform/modrinth/ModrinthModel.cpp ui/pages/modplatform/modrinth/ModrinthModModel.cpp
ui/pages/modplatform/modrinth/ModrinthModel.h ui/pages/modplatform/modrinth/ModrinthModModel.h
ui/pages/modplatform/modrinth/ModrinthPage.cpp ui/pages/modplatform/modrinth/ModrinthModPage.cpp
ui/pages/modplatform/modrinth/ModrinthPage.h ui/pages/modplatform/modrinth/ModrinthModPage.h
# GUI - dialogs # GUI - dialogs
ui/dialogs/AboutDialog.cpp ui/dialogs/AboutDialog.cpp
@ -777,7 +828,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/SkinUploadDialog.h ui/dialogs/SkinUploadDialog.h
ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.cpp
ui/dialogs/ModDownloadDialog.h ui/dialogs/ModDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h
# GUI - widgets # GUI - widgets
ui/widgets/Common.cpp ui/widgets/Common.cpp
@ -802,6 +854,8 @@ SET(LAUNCHER_SOURCES
ui/widgets/LogView.h ui/widgets/LogView.h
ui/widgets/MCModInfoFrame.cpp ui/widgets/MCModInfoFrame.cpp
ui/widgets/MCModInfoFrame.h ui/widgets/MCModInfoFrame.h
ui/widgets/ModFilterWidget.cpp
ui/widgets/ModFilterWidget.h
ui/widgets/ModListView.cpp ui/widgets/ModListView.cpp
ui/widgets/ModListView.h ui/widgets/ModListView.h
ui/widgets/PageContainer.cpp ui/widgets/PageContainer.cpp
@ -831,6 +885,7 @@ SET(LAUNCHER_SOURCES
) )
qt5_wrap_ui(LAUNCHER_UI qt5_wrap_ui(LAUNCHER_UI
ui/setupwizard/PasteWizardPage.ui
ui/pages/global/AccountListPage.ui ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui ui/pages/global/JavaPage.ui
ui/pages/global/LauncherPage.ui ui/pages/global/LauncherPage.ui
@ -847,21 +902,21 @@ qt5_wrap_ui(LAUNCHER_UI
ui/pages/instance/InstanceSettingsPage.ui ui/pages/instance/InstanceSettingsPage.ui
ui/pages/instance/VersionPage.ui ui/pages/instance/VersionPage.ui
ui/pages/instance/WorldListPage.ui ui/pages/instance/WorldListPage.ui
ui/pages/instance/LegacyUpgradePage.ui
ui/pages/instance/ScreenshotsPage.ui ui/pages/instance/ScreenshotsPage.ui
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/atlauncher/AtlPage.ui
ui/pages/modplatform/VanillaPage.ui ui/pages/modplatform/VanillaPage.ui
ui/pages/modplatform/ModPage.ui
ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/flame/FlameModPage.ui
ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/ftb/FtbPage.ui ui/pages/modplatform/ftb/FtbPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
ui/widgets/InstanceCardWidget.ui ui/widgets/InstanceCardWidget.ui
ui/widgets/CustomCommands.ui ui/widgets/CustomCommands.ui
ui/widgets/MCModInfoFrame.ui ui/widgets/MCModInfoFrame.ui
ui/widgets/ModFilterWidget.ui
ui/dialogs/CopyInstanceDialog.ui ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui ui/dialogs/ProgressDialog.ui
@ -876,6 +931,8 @@ qt5_wrap_ui(LAUNCHER_UI
ui/dialogs/AboutDialog.ui ui/dialogs/AboutDialog.ui
ui/dialogs/LoginDialog.ui ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
) )
qt5_add_resources(LAUNCHER_RESOURCES qt5_add_resources(LAUNCHER_RESOURCES
@ -894,7 +951,7 @@ qt5_add_resources(LAUNCHER_RESOURCES
######## Windows resource files ######## ######## Windows resource files ########
if(WIN32) if(WIN32)
set(LAUNCHER_RCS ../${Launcher_Branding_WindowsRC}) set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
endif() endif()
# Add executable # Add executable
@ -920,8 +977,8 @@ target_link_libraries(Launcher_logic
Launcher_iconfix Launcher_iconfix
QuaZip::QuaZip QuaZip::QuaZip
hoedown hoedown
SneedMC_rainbow
LocalPeer LocalPeer
Launcher_rainbow
) )
target_link_libraries(Launcher_logic) target_link_libraries(Launcher_logic)
@ -942,7 +999,7 @@ if(DEFINED Launcher_APP_BINARY_DEFS)
endif() endif()
install(TARGETS ${Launcher_Name} install(TARGETS ${Launcher_Name}
BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime BUNDLE DESTINATION "." COMPONENT Runtime
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
) )
@ -963,7 +1020,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
DIRECTORY "${QT_PLUGINS_DIR}/imageformats" DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR} DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime COMPONENT Runtime
REGEX "tga|tiff|mng|webp" EXCLUDE REGEX "tga|tiff|mng" EXCLUDE
) )
# Icon engines # Icon engines
install( install(
@ -993,7 +1050,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
DIRECTORY "${QT_PLUGINS_DIR}/imageformats" DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR} DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime COMPONENT Runtime
REGEX "tga|tiff|mng|webp" EXCLUDE REGEX "tga|tiff|mng" EXCLUDE
REGEX "d\\." EXCLUDE REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE REGEX "\\.dSYM" EXCLUDE

View File

@ -1,8 +1,43 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 dada513 <dada513@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2022 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 "DesktopServices.h" #include "DesktopServices.h"
#include <QDir> #include <QDir>
#include <QDesktopServices> #include <QDesktopServices>
#include <QProcess> #include <QProcess>
#include <QDebug> #include <QDebug>
#include "Application.h"
/** /**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing. * This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
@ -84,7 +119,14 @@ bool openDirectory(const QString &path, bool ensureExists)
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!APPLICATION->isFlatpak())
{
return IndirectOpen(f); return IndirectOpen(f);
}
else
{
return f();
}
#else #else
return f(); return f();
#endif #endif
@ -98,7 +140,14 @@ bool openFile(const QString &path)
return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!APPLICATION->isFlatpak())
{
return IndirectOpen(f); return IndirectOpen(f);
}
else
{
return f();
}
#else #else
return f(); return f();
#endif #endif
@ -109,10 +158,17 @@ bool openFile(const QString &application, const QString &path, const QString &wo
qDebug() << "Opening file" << path << "using" << application; qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if(!APPLICATION->isFlatpak())
{
return IndirectOpen([&]() return IndirectOpen([&]()
{ {
return QProcess::startDetached(application, QStringList() << path, workingDirectory); return QProcess::startDetached(application, QStringList() << path, workingDirectory);
}, pid); }, pid);
}
else
{
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
}
#else #else
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid); return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
#endif #endif
@ -122,11 +178,18 @@ bool run(const QString &application, const QStringList &args, const QString &wor
{ {
qDebug() << "Running" << application << "with args" << args.join(' '); qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!APPLICATION->isFlatpak())
{
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() return IndirectOpen([&]()
{ {
return QProcess::startDetached(application, args, workingDirectory); return QProcess::startDetached(application, args, workingDirectory);
}, pid); }, pid);
}
else
{
return QProcess::startDetached(application, args, workingDirectory, pid);
}
#else #else
return QProcess::startDetached(application, args, workingDirectory, pid); return QProcess::startDetached(application, args, workingDirectory, pid);
#endif #endif
@ -140,7 +203,14 @@ bool openUrl(const QUrl &url)
return QDesktopServices::openUrl(url); return QDesktopServices::openUrl(url);
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!APPLICATION->isFlatpak())
{
return IndirectOpen(f); return IndirectOpen(f);
}
else
{
return f();
}
#else #else
return f(); return f();
#endif #endif

View File

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

View File

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

View File

@ -42,7 +42,6 @@ void InstanceCopyTask::copyFinished()
} }
// FIXME: shouldn't this be able to report errors? // FIXME: shouldn't this be able to report errors?
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
inst->setName(m_instName); inst->setName(m_instName);

View File

@ -9,6 +9,15 @@
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version) InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
{ {
m_version = version; m_version = version;
m_usingLoader = false;
}
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion)
{
m_version = version;
m_usingLoader = true;
m_loader = loader;
m_loaderVersion = loaderVersion;
} }
void InstanceCreationTask::executeTask() void InstanceCreationTask::executeTask()
@ -17,12 +26,12 @@ void InstanceCreationTask::executeTask()
{ {
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->suspendSave(); instanceSettings->suspendSave();
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
auto components = inst.getPackProfile(); auto components = inst.getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_version->descriptor(), true); components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
if(m_usingLoader)
components->setComponentVersion(m_loader, m_loaderVersion->descriptor());
inst.setName(m_instName); inst.setName(m_instName);
inst.setIconKey(m_instIcon); inst.setIconKey(m_instIcon);
instanceSettings->resumeSave(); instanceSettings->resumeSave();

View File

@ -12,6 +12,7 @@ class InstanceCreationTask : public InstanceTask
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceCreationTask(BaseVersionPtr version); explicit InstanceCreationTask(BaseVersionPtr version);
explicit InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
@ -19,4 +20,7 @@ protected:
private: /* data */ private: /* data */
BaseVersionPtr m_version; BaseVersionPtr m_version;
bool m_usingLoader;
QString m_loader;
BaseVersionPtr m_loaderVersion;
}; };

View File

@ -1,4 +1,25 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,30 +35,48 @@
*/ */
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include <QtConcurrentRun>
#include "Application.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Application.h"
#include "MMCZip.h" #include "MMCZip.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "settings/INISettingsObject.h" #include "icons/IconList.h"
#include "icons/IconUtils.h" #include "icons/IconUtils.h"
#include <QtConcurrentRun> #include "settings/INISettingsObject.h"
// FIXME: this does not belong here, it's Minecraft/Flame specific // FIXME: this does not belong here, it's Minecraft/Flame specific
#include <quazip/quazipdir.h>
#include "Json.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/PackManifest.h" #include "modplatform/flame/PackManifest.h"
#include "Json.h" #include "modplatform/modrinth/ModrinthPackManifest.h"
#include <quazip/quazipdir.h>
#include "modplatform/technic/TechnicPackProcessor.h" #include "modplatform/technic/TechnicPackProcessor.h"
#include "icons/IconList.h"
#include "Application.h" #include "Application.h"
#include "icons/IconList.h"
#include "net/ChecksumValidator.h"
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl) #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ScrollMessageBox.h"
#include <algorithm>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{ {
m_sourceUrl = sourceUrl; m_sourceUrl = sourceUrl;
m_parent = parent;
}
bool InstanceImportTask::abort()
{
if (m_filesNetJob)
m_filesNetJob->abort();
m_extractFuture.cancel();
return false;
} }
void InstanceImportTask::executeTask() void InstanceImportTask::executeTask()
@ -97,17 +136,20 @@ void InstanceImportTask::processZipPack()
return; return;
} }
QStringList blacklist = {"instance.cfg", "manifest.json"}; QuaZipDir packZipDir(m_packZip.get());
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json"); // https://docs.modrinth.com/docs/modpacks/format_definition/#storage
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); bool modrinthFound = packZipDir.exists("/modrinth.index.json");
bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json");
QString root; QString root;
if(!mmcFound.isNull())
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
if(modrinthFound)
{ {
// process as MultiMC instance/pack // process as Modrinth pack
qDebug() << "MultiMC:" << mmcFound; qDebug() << "Modrinth:" << modrinthFound;
root = mmcFound; m_modpackType = ModpackType::Modrinth;
m_modpackType = ModpackType::MultiMC;
} }
else if (technicFound) else if (technicFound)
{ {
@ -117,13 +159,26 @@ void InstanceImportTask::processZipPack()
extractDir.cd(".minecraft"); extractDir.cd(".minecraft");
m_modpackType = ModpackType::Technic; m_modpackType = ModpackType::Technic;
} }
else if(!flameFound.isNull()) else
{
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
if (!mmcRoot.isNull())
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot;
m_modpackType = ModpackType::MultiMC;
}
else if(!flameRoot.isNull())
{ {
// process as Flame pack // process as Flame pack
qDebug() << "Flame:" << flameFound; qDebug() << "Flame:" << flameRoot;
root = flameFound; root = flameRoot;
m_modpackType = ModpackType::Flame; m_modpackType = ModpackType::Flame;
} }
}
if(m_modpackType == ModpackType::Unknown) if(m_modpackType == ModpackType::Unknown)
{ {
emitFailed(tr("Archive does not contain a recognized modpack type.")); emitFailed(tr("Archive does not contain a recognized modpack type."));
@ -180,15 +235,18 @@ void InstanceImportTask::extractFinished()
switch(m_modpackType) switch(m_modpackType)
{ {
case ModpackType::Flame:
processFlame();
return;
case ModpackType::MultiMC: case ModpackType::MultiMC:
processMultiMC(); processMultiMC();
return; return;
case ModpackType::Technic: case ModpackType::Technic:
processTechnic(); processTechnic();
return; return;
case ModpackType::Flame:
processFlame();
return;
case ModpackType::Modrinth:
processModrinth();
return;
case ModpackType::Unknown: case ModpackType::Unknown:
emitFailed(tr("Archive does not contain a recognized modpack type.")); emitFailed(tr("Archive does not contain a recognized modpack type."));
return; return;
@ -241,6 +299,7 @@ void InstanceImportTask::processFlame()
QString forgeVersion; QString forgeVersion;
QString fabricVersion; QString fabricVersion;
// TODO: is Quilt relevant here?
for(auto &loader: pack.minecraft.modLoaders) for(auto &loader: pack.minecraft.modLoaders)
{ {
auto id = loader.id; auto id = loader.id;
@ -261,8 +320,6 @@ void InstanceImportTask::processFlame()
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath); auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto mcVersion = pack.minecraft.version; auto mcVersion = pack.minecraft.version;
// Hack to correct some 'special sauce'... // Hack to correct some 'special sauce'...
@ -285,7 +342,7 @@ void InstanceImportTask::processFlame()
} }
else else
{ {
logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion)); logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion));
} }
} }
components->setComponentVersion("net.minecraftforge", forgeVersion); components->setComponentVersion("net.minecraftforge", forgeVersion);
@ -337,35 +394,58 @@ void InstanceImportTask::processFlame()
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
{ {
auto results = m_modIdResolver->getResults(); auto results = m_modIdResolver->getResults();
//first check for blocked mods
QString text;
auto anyBlocked = false;
for(const auto& result: results.files.values()) {
if (!result.resolved || result.url.isEmpty()) {
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
anyBlocked = true;
}
}
if(anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list";
auto message_dialog = new ScrollMessageBox(m_parent,
tr("Blocked mods found"),
tr("The following mods were blocked on third party launchers.<br/>"
"You will need to manually download them and add them to the modpack"),
text);
message_dialog->setModal(true);
message_dialog->show();
connect(message_dialog, &QDialog::rejected, [&]() {
m_modIdResolver.reset();
emitFailed("Canceled");
});
connect(message_dialog, &QDialog::accepted, [&]() {
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for(auto result: results.files) for (const auto &result: m_modIdResolver->getResults().files) {
{
QString filename = result.fileName; QString filename = result.fileName;
if(!result.required) if (!result.required) {
{
filename += ".disabled"; filename += ".disabled";
} }
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath, relpath); auto path = FS::PathCombine(m_stagingPath, relpath);
switch(result.type) switch (result.type) {
{ case Flame::File::Type::Folder: {
case Flame::File::Type::Folder:
{
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever. // fall-through intentional, we treat these as plain old mods and dump them wherever.
} }
case Flame::File::Type::SingleFile: case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod: case Flame::File::Type::Mod: {
{ if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path; qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path); auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl); m_filesNetJob->addNetAction(dl);
}
break; break;
} }
case Flame::File::Type::Modpack: case Flame::File::Type::Modpack:
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); logWarning(
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
relpath));
break; break;
case Flame::File::Type::Cmod2: case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc: case Flame::File::Type::Ctoc:
@ -375,23 +455,75 @@ void InstanceImportTask::processFlame()
} }
} }
m_modIdResolver.reset(); m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
{
m_filesNetJob.reset(); m_filesNetJob.reset();
emitSucceeded(); emitSucceeded();
} }
); );
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
{
m_filesNetJob.reset(); m_filesNetJob.reset();
emitFailed(reason); emitFailed(reason);
}); });
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
{
setProgress(current, total); setProgress(current, total);
}); });
setStatus(tr("Downloading mods...")); setStatus(tr("Downloading mods..."));
m_filesNetJob->start(); m_filesNetJob->start();
});
}else{
//TODO extract to function ?
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (const auto &result: m_modIdResolver->getResults().files) {
QString filename = result.fileName;
if (!result.required) {
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath, relpath);
switch (result.type) {
case Flame::File::Type::Folder: {
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
}
break;
}
case Flame::File::Type::Modpack:
logWarning(
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
case Flame::File::Type::Unknown:
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
break;
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}
} }
); );
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
@ -422,7 +554,6 @@ void InstanceImportTask::processMultiMC()
{ {
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath); auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy");
NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
@ -433,21 +564,16 @@ void InstanceImportTask::processMultiMC()
instance.setName(m_instName); instance.setName(m_instName);
// if the icon was specified by user, use that. otherwise pull icon from the pack // if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon != "default") if (m_instIcon != "default") {
{
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
} } else {
else
{
m_instIcon = instance.iconKey(); m_instIcon = instance.iconKey();
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
if (!importIconPath.isNull() && QFile::exists(importIconPath)) if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
{
// import icon // import icon
auto iconList = APPLICATION->icons(); auto iconList = APPLICATION->icons();
if (iconList->iconFileExists(m_instIcon)) if (iconList->iconFileExists(m_instIcon)) {
{
iconList->deleteIcon(m_instIcon); iconList->deleteIcon(m_instIcon);
} }
iconList->installIcons({ importIconPath }); iconList->installIcons({ importIconPath });
@ -455,3 +581,199 @@ void InstanceImportTask::processMultiMC()
} }
emitSucceeded(); emitSucceeded();
} }
// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try {
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
auto doc = Json::requireDocument(indexPath);
auto obj = Json::requireObject(doc, "modrinth.index.json");
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
if (formatVersion == 1) {
auto game = Json::requireString(obj, "game", "modrinth.index.json");
if (game != "minecraft") {
throw JSONValidationError("Unknown game: " + game);
}
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
for (auto modInfo : jsonFiles) {
Modrinth::File file;
file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(modInfo, "env");
// 'env' field is optional
if (!env.isEmpty()) {
QString support = Json::ensureString(env, "client", "unsupported");
if (support == "unsupported") {
continue;
} else if (support == "optional") {
// TODO: Make a review dialog for choosing which ones the user wants!
if (!had_optional) {
had_optional = true;
auto info = CustomMessageBox::selectable(
m_parent, tr("Optional mod detected!"),
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
QMessageBox::Information);
info->exec();
}
if (file.path.endsWith(".jar"))
file.path += ".disabled";
}
}
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
QString hash;
QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha1");
hashAlgorithm = QCryptographicHash::Sha1;
if (hash.isEmpty()) {
hash = Json::ensureString(hashes, "sha512");
hashAlgorithm = QCryptographicHash::Sha512;
if (hash.isEmpty()) {
hash = Json::ensureString(hashes, "sha256");
hashAlgorithm = QCryptographicHash::Sha256;
if (hash.isEmpty()) {
throw JSONValidationError("No hash found for: " + file.path);
}
}
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
auto download_arr = Json::ensureArray(modInfo, "downloads");
for(auto download : download_arr) {
qWarning() << download.toString();
bool is_last = download.toString() == download_arr.last().toString();
auto download_url = QUrl(download.toString());
if (!download_url.isValid()) {
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
.arg(download_url.toString(), file.path);
if(is_last && file.downloads.isEmpty())
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
else {
file.downloads.push_back(download_url);
}
}
files.push_back(file);
}
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
if (name == "minecraft") {
minecraftVersion = Json::requireString(*it, "Minecraft version");
}
else if (name == "fabric-loader") {
fabricVersion = Json::requireString(*it, "Fabric Loader version");
}
else if (name == "quilt-loader") {
quiltVersion = Json::requireString(*it, "Quilt Loader version");
}
else if (name == "forge") {
forgeVersion = Json::requireString(*it, "Forge version");
}
else {
throw JSONValidationError("Unknown dependency type: " + name);
}
}
} else {
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
}
QFile::remove(indexPath);
} catch (const JSONValidationError& e) {
emitFailed(tr("Could not understand pack index:\n") + e.cause());
return;
}
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(override_path)) {
if (!QFile::rename(override_path, mcPath)) {
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return;
}
}
// Do client overrides
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(client_override_path)) {
if (!FS::overrideFolder(mcPath, client_override_path)) {
emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
return;
}
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", minecraftVersion, true);
if (!fabricVersion.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion, true);
if (!quiltVersion.isEmpty())
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion, true);
if (!forgeVersion.isEmpty())
components->setComponentVersion("net.minecraftforge", forgeVersion, true);
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
}
else
{
instance.setIconKey("modrinth");
}
instance.setName(m_instName);
instance.saveNow();
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
for (auto file : files)
{
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
if (file.downloads.size() > 0) {
// FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :)
connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
dl->succeeded();
});
}
}
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](const QString &reason)
{
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +42,7 @@
#include <QFutureWatcher> #include <QFutureWatcher>
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "modplatform/flame/PackManifest.h"
#include <nonstd/optional> #include <nonstd/optional>
@ -35,7 +56,14 @@ class InstanceImportTask : public InstanceTask
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceImportTask(const QUrl sourceUrl); explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
bool canAbort() const override { return true; }
bool abort() override;
const QVector<Flame::File> &getBlockedFiles() const
{
return m_blockedMods;
}
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
@ -44,8 +72,9 @@ protected:
private: private:
void processZipPack(); void processZipPack();
void processMultiMC(); void processMultiMC();
void processFlame();
void processTechnic(); void processTechnic();
void processFlame();
void processModrinth();
private slots: private slots:
void downloadSucceeded(); void downloadSucceeded();
@ -63,10 +92,15 @@ private: /* data */
std::unique_ptr<QuaZip> m_packZip; std::unique_ptr<QuaZip> m_packZip;
QFuture<nonstd::optional<QStringList>> m_extractFuture; QFuture<nonstd::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
QVector<Flame::File> m_blockedMods;
enum class ModpackType{ enum class ModpackType{
Unknown, Unknown,
MultiMC, MultiMC,
Technic,
Flame, Flame,
Technic Modrinth,
} m_modpackType = ModpackType::Unknown; } m_modpackType = ModpackType::Unknown;
//FIXME: nuke
QWidget* m_parent;
}; };

View File

@ -32,13 +32,16 @@
#include "BaseInstance.h" #include "BaseInstance.h"
#include "InstanceTask.h" #include "InstanceTask.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "ExponentialSeries.h" #include "ExponentialSeries.h"
#include "WatchLock.h" #include "WatchLock.h"
#ifdef Q_OS_WIN32
#include <Windows.h>
#endif
const static int GROUP_FILE_FORMAT_VERSION = 1; const static int GROUP_FILE_FORMAT_VERSION = 1;
InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent) InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent)
@ -545,18 +548,15 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg")); auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst; InstancePtr inst;
instanceSettings->registerSetting("InstanceType", "Legacy"); instanceSettings->registerSetting("InstanceType", "");
QString inst_type = instanceSettings->get("InstanceType").toString(); QString inst_type = instanceSettings->get("InstanceType").toString();
if (inst_type == "OneSix" || inst_type == "Nostalgia") // NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty())
{ {
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
} }
else if (inst_type == "Legacy")
{
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else else
{ {
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
@ -867,13 +867,18 @@ Task * InstanceList::wrapInstanceTask(InstanceTask * task)
QString InstanceList::getStagedInstancePath() QString InstanceList::getStagedInstancePath()
{ {
QString key = QUuid::createUuid().toString(); QString key = QUuid::createUuid().toString();
QString relPath = FS::PathCombine("_LAUNCHER_TEMP/" , key); QString tempDir = ".LAUNCHER_TEMP/";
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir); QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath); auto path = FS::PathCombine(m_instDir, relPath);
if(!rootPath.mkpath(relPath)) if(!rootPath.mkpath(relPath))
{ {
return QString(); return QString();
} }
#ifdef Q_OS_WIN32
auto tempPath = FS::PathCombine(m_instDir, tempDir);
SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
#endif
return path; return path;
} }

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include <FileSystem.h> #include <FileSystem.h>
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
#include "ui/pages/BasePageProvider.h" #include "ui/pages/BasePageProvider.h"
@ -14,7 +13,6 @@
#include "ui/pages/instance/ScreenshotsPage.h" #include "ui/pages/instance/ScreenshotsPage.h"
#include "ui/pages/instance/InstanceSettingsPage.h" #include "ui/pages/instance/InstanceSettingsPage.h"
#include "ui/pages/instance/OtherLogsPage.h" #include "ui/pages/instance/OtherLogsPage.h"
#include "ui/pages/instance/LegacyUpgradePage.h"
#include "ui/pages/instance/WorldListPage.h" #include "ui/pages/instance/WorldListPage.h"
#include "ui/pages/instance/ServersPage.h" #include "ui/pages/instance/ServersPage.h"
#include "ui/pages/instance/GameOptionsPage.h" #include "ui/pages/instance/GameOptionsPage.h"
@ -34,8 +32,6 @@ public:
QList<BasePage *> values; QList<BasePage *> values;
values.append(new LogPage(inst)); values.append(new LogPage(inst));
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst); std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
if(onesix)
{
values.append(new VersionPage(onesix.get())); values.append(new VersionPage(onesix.get()));
auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Mods"), "Loader-mods"); auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Mods"), "Loader-mods");
modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
@ -50,15 +46,6 @@ public:
// values.append(new GameOptionsPage(onesix.get())); // values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
values.append(new InstanceSettingsPage(onesix.get())); values.append(new InstanceSettingsPage(onesix.get()));
}
std::shared_ptr<LegacyInstance> legacy = std::dynamic_pointer_cast<LegacyInstance>(inst);
if(legacy)
{
values.append(new LegacyUpgradePage(legacy));
values.append(new NotesPage(legacy.get()));
values.append(new WorldListPage(legacy.get(), legacy->worldList()));
values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots")));
}
auto logMatcher = inst->getLogFileMatcher(); auto logMatcher = inst->getLogFileMatcher();
if(logMatcher) if(logMatcher)
{ {

View File

@ -17,6 +17,17 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)
QMessageBox::Warning)->exec(); QMessageBox::Warning)->exec();
return false; return false;
} }
// block lunacy with passing required version to the JVM
if (jvmargs.contains(QRegExp("-version:.*"))) {
auto warnStr = QObject::tr(
"You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be allowed.\n"
"This message will be displayed until you remove this from the JVM arguments.");
CustomMessageBox::selectable(
parent, QObject::tr("JVM arguments warning"),
warnStr,
QMessageBox::Warning)->exec();
return false;
}
return true; return true;
} }
@ -40,7 +51,7 @@ void JavaCommon::javaArgsWereBad(QWidget *parent, JavaCheckResult result)
auto htmlError = result.errorLog; auto htmlError = result.errorLog;
QString text; QString text;
htmlError.replace('\n', "<br />"); htmlError.replace('\n', "<br />");
text += QObject::tr("The specified java binary didn't work with the arguments you provided:<br />"); text += QObject::tr("The specified Java binary didn't work with the arguments you provided:<br />");
text += QString("<font color=\"red\">%1</font>").arg(htmlError); text += QString("<font color=\"red\">%1</font>").arg(htmlError);
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
} }
@ -49,8 +60,8 @@ void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result)
{ {
QString text; QString text;
text += QObject::tr( text += QObject::tr(
"The specified java binary didn't work.<br />You should use the auto-detect feature, " "The specified Java binary didn't work.<br />You should use the auto-detect feature, "
"or set the path to the java executable.<br />"); "or set the path to the Java executable.<br />");
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
} }

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "LaunchController.h" #include "LaunchController.h"
#include "minecraft/auth/AccountList.h" #include "minecraft/auth/AccountList.h"
#include "Application.h" #include "Application.h"
@ -36,7 +71,10 @@ void LaunchController::executeTask()
return; return;
} }
JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget); if(!JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget)) {
emitFailed(tr("Invalid Java arguments specified. Please fix this first."));
return;
}
login(); login();
} }
@ -55,7 +93,7 @@ void LaunchController::decideAccount()
auto reply = CustomMessageBox::selectable( auto reply = CustomMessageBox::selectable(
m_parentWidget, m_parentWidget,
tr("No Accounts"), tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " tr("In order to play Minecraft, you must have at least one Mojang or Microsoft "
"account logged in. " "account logged in. "
"Would you like to open the account manager to add an account now?"), "Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Information,
@ -67,6 +105,11 @@ void LaunchController::decideAccount()
// Open the account manager. // Open the account manager.
APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts"); APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts");
} }
else if (reply == QMessageBox::No)
{
// Do not open "profile select" dialog.
return;
}
} }
m_accountToUse = accounts->defaultAccount(); m_accountToUse = accounts->defaultAccount();
@ -131,13 +174,14 @@ void LaunchController::login() {
if(!m_session->wants_online) { if(!m_session->wants_online) {
// we ask the user for a player name // we ask the user for a player name
bool ok = false; bool ok = false;
QString usedname = m_session->player_name; QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName;
QString name = QInputDialog::getText( QString name = QInputDialog::getText(
m_parentWidget, m_parentWidget,
tr("Player name"), tr("Player name"),
tr("Choose your offline mode player name."), tr("Choose your offline mode player name."),
QLineEdit::Normal, QLineEdit::Normal,
m_session->player_name, usedname,
&ok &ok
); );
if (!ok) if (!ok)
@ -148,6 +192,7 @@ void LaunchController::login() {
if (name.length()) if (name.length())
{ {
usedname = name; usedname = name;
APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
} }
m_session->MakeOffline(usedname); m_session->MakeOffline(usedname);
// offline flavored game from here :3 // offline flavored game from here :3
@ -228,6 +273,18 @@ void LaunchController::login() {
emitFailed(errorString); emitFailed(errorString);
return; return;
} }
case AccountState::Disabled: {
auto errorString = tr("The launcher's client identification has changed. Please remove this account and add it again.");
QMessageBox::warning(
m_parentWidget,
tr("Client identification changed"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
case AccountState::Gone: { case AccountState::Gone: {
auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to."); auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to.");
QMessageBox::warning( QMessageBox::warning(

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <BaseInstance.h> #include <BaseInstance.h>

View File

@ -21,7 +21,7 @@ echo "Launcher Dir: ${LAUNCHER_DIR}"
# Set up env - filter out input LD_ variables but pass them in under different names # Set up env - filter out input LD_ variables but pass them in under different names
export GAME_LIBRARY_PATH=${GAME_LIBRARY_PATH-${LD_LIBRARY_PATH}} export GAME_LIBRARY_PATH=${GAME_LIBRARY_PATH-${LD_LIBRARY_PATH}}
export GAME_PRELOAD=${GAME_PRELOAD-${LD_PRELOAD}} export GAME_PRELOAD=${GAME_PRELOAD-${LD_PRELOAD}}
export LD_LIBRARY_PATH="${LAUNCHER_DIR}/bin":$LAUNCHER_LIBRARY_PATH export LD_LIBRARY_PATH="${LAUNCHER_DIR}/lib@LIB_SUFFIX@":$LAUNCHER_LIBRARY_PATH
export LD_PRELOAD=$LAUNCHER_PRELOAD export LD_PRELOAD=$LAUNCHER_PRELOAD
export QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins" export QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins"
export QT_FONTPATH="${LAUNCHER_DIR}/fonts" export QT_FONTPATH="${LAUNCHER_DIR}/fonts"

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -131,23 +151,23 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
continue; continue;
if (mod.type() == Mod::MOD_ZIPFILE) if (mod.type() == Mod::MOD_ZIPFILE)
{ {
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) if (!mergeZipFiles(&zipOut, mod.fileinfo(), addedFiles))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false; return false;
} }
} }
else if (mod.type() == Mod::MOD_SINGLEFILE) else if (mod.type() == Mod::MOD_SINGLEFILE)
{ {
// FIXME: buggy - does not work with addedFiles // FIXME: buggy - does not work with addedFiles
auto filename = mod.filename(); auto filename = mod.fileinfo();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false; return false;
} }
addedFiles.insert(filename.fileName()); addedFiles.insert(filename.fileName());
@ -156,7 +176,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
{ {
// untested, but seems to be unused / not possible to reach // untested, but seems to be unused / not possible to reach
// FIXME: buggy - does not work with addedFiles // FIXME: buggy - does not work with addedFiles
auto filename = mod.filename(); auto filename = mod.fileinfo();
QString what_to_zip = filename.absoluteFilePath(); QString what_to_zip = filename.absoluteFilePath();
QDir dir(what_to_zip); QDir dir(what_to_zip);
dir.cdUp(); dir.cdUp();
@ -173,7 +193,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false; return false;
} }
qDebug() << "Adding folder " << filename.fileName() << " from " qDebug() << "Adding folder " << filename.fileName() << " from "
@ -184,7 +204,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
// Make sure we do not continue launching when something is missing or undefined... // Make sure we do not continue launching when something is missing or undefined...
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar."; qCritical() << "Failed to add unknown mod type" << mod.fileinfo().fileName() << "to the jar.";
return false; return false;
} }
} }
@ -277,20 +297,40 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
{ {
continue; continue;
} }
name.remove(0, subdir.size()); name.remove(0, subdir.size());
QString absFilePath = directory.absoluteFilePath(name); auto original_name = name;
// Fix weird "folders with a single file get squashed" thing
QString path;
if(name.contains('/') && !name.endsWith('/')){
path = name.section('/', 0, -2) + "/";
FS::ensureFolderPathExists(path);
name = name.split('/').last();
}
QString absFilePath;
if(name.isEmpty()) if(name.isEmpty())
{ {
absFilePath += "/"; absFilePath = directory.absoluteFilePath(name) + "/";
} }
else
{
absFilePath = directory.absoluteFilePath(path + name);
}
if (!JlCompress::extractFile(zip, "", absFilePath)) if (!JlCompress::extractFile(zip, "", absFilePath))
{ {
qWarning() << "Failed to extract file" << name << "to" << absFilePath; qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
JlCompress::removeFile(extracted); JlCompress::removeFile(extracted);
return nonstd::nullopt; return nonstd::nullopt;
} }
extracted.append(absFilePath); extracted.append(absFilePath);
qDebug() << "Extracted file" << name; QFile::setPermissions(absFilePath, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser);
qDebug() << "Extracted file" << name << "to" << absFilePath;
} while (zip->goToNextFile()); } while (zip->goToNextFile());
return extracted; return extracted;
} }

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,24 +1,50 @@
#include "ModDownloadTask.h" // SPDX-License-Identifier: GPL-3.0-only
#include "Application.h" /*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
ModDownloadTask::ModDownloadTask(const QUrl sourceUrl,const QString filename, const std::shared_ptr<ModFolderModel> mods) #include "ModDownloadTask.h"
: m_sourceUrl(sourceUrl), mods(mods), filename(filename) {
#include "Application.h"
#include "minecraft/mod/ModFolderModel.h"
ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed)
: m_mod(mod), m_mod_version(version), mods(mods)
{
if (is_indexed) {
m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version));
addTask(m_update_task);
} }
void ModDownloadTask::executeTask() {
setStatus(tr("Downloading mod:\n%1").arg(m_sourceUrl.toString()));
m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeFile(m_sourceUrl, mods->dir().absoluteFilePath(filename))); m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl));
m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed);
m_filesNetJob->start();
addTask(m_filesNetJob);
} }
void ModDownloadTask::downloadSucceeded() void ModDownloadTask::downloadSucceeded()
{ {
emitSucceeded();
m_filesNetJob.reset(); m_filesNetJob.reset();
} }
@ -32,8 +58,3 @@ void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
{ {
emit progress(current, total); emit progress(current, total);
} }
bool ModDownloadTask::abort() {
return m_filesNetJob->abort();
}

View File

@ -1,27 +1,45 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once #pragma once
#include "QObjectPtr.h"
#include "tasks/Task.h"
#include "minecraft/mod/ModFolderModel.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include <QUrl> #include "tasks/SequentialTask.h"
#include "modplatform/ModIndex.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
class ModDownloadTask : public Task { class ModFolderModel;
class ModDownloadTask : public SequentialTask {
Q_OBJECT Q_OBJECT
public: public:
explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr<ModFolderModel> mods); explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed);
const QString& getFilename() const { return m_mod_version.fileName; }
public slots:
bool abort() override;
protected:
//! Entry point for tasks.
void executeTask() override;
private: private:
QUrl m_sourceUrl; ModPlatform::IndexedPack m_mod;
NetJob::Ptr m_filesNetJob; ModPlatform::IndexedVersion m_mod_version;
const std::shared_ptr<ModFolderModel> mods; const std::shared_ptr<ModFolderModel> mods;
const QString filename;
NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total); void downloadProgressChanged(qint64 current, qint64 total);

View File

@ -36,7 +36,7 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list) for (auto file_info : file_info_list)
{ {
builtinNames.insert(file_info.baseName()); builtinNames.insert(file_info.completeBaseName());
} }
} }
for(auto & builtinName : builtinNames) for(auto & builtinName : builtinNames)
@ -51,6 +51,18 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
directoryChanged(path); directoryChanged(path);
// Forces the UI to update, so that lengthy icon names are shown properly from the start
emit iconUpdated({});
}
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
reindex();
} }
void IconList::directoryChanged(const QString &path) void IconList::directoryChanged(const QString &path)
@ -94,7 +106,13 @@ void IconList::directoryChanged(const QString &path)
{ {
qDebug() << "Removing " << remove; qDebug() << "Removing " << remove;
QFileInfo rmfile(remove); QFileInfo rmfile(remove);
QString key = rmfile.baseName(); QString key = rmfile.completeBaseName();
QString suffix = rmfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
key = rmfile.fileName();
int idx = getIconIndex(key); int idx = getIconIndex(key);
if (idx == -1) if (idx == -1)
continue; continue;
@ -117,14 +135,23 @@ void IconList::directoryChanged(const QString &path)
for (auto add : to_add) for (auto add : to_add)
{ {
qDebug() << "Adding " << add; qDebug() << "Adding " << add;
QFileInfo addfile(add); QFileInfo addfile(add);
QString key = addfile.baseName(); QString key = addfile.completeBaseName();
QString suffix = addfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
key = addfile.fileName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
{ {
m_watcher->addPath(add); m_watcher->addPath(add);
emit iconUpdated(key); emit iconUpdated(key);
} }
} }
sortIconList();
} }
void IconList::fileChanged(const QString &path) void IconList::fileChanged(const QString &path)
@ -133,7 +160,7 @@ void IconList::fileChanged(const QString &path)
QFileInfo checkfile(path); QFileInfo checkfile(path);
if (!checkfile.exists()) if (!checkfile.exists())
return; return;
QString key = checkfile.baseName(); QString key = checkfile.completeBaseName();
int idx = getIconIndex(key); int idx = getIconIndex(key);
if (idx == -1) if (idx == -1)
return; return;
@ -257,7 +284,7 @@ void IconList::installIcons(const QStringList &iconFiles)
QFileInfo fileinfo(file); QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile()) if (!fileinfo.isReadable() || !fileinfo.isFile())
continue; continue;
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
QString suffix = fileinfo.suffix(); QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
@ -274,7 +301,7 @@ void IconList::installIcon(const QString &file, const QString &name)
if(!fileinfo.isReadable() || !fileinfo.isFile()) if(!fileinfo.isReadable() || !fileinfo.isFile())
return; return;
QString target = FS::PathCombine(m_dir.dirName(), name); QString target = FS::PathCombine(getDirectory(), name);
QFile::copy(file, target); QFile::copy(file, target);
} }

View File

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

View File

@ -129,7 +129,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto os_arch = results["os.arch"]; auto os_arch = results["os.arch"];
auto java_version = results["java.version"]; auto java_version = results["java.version"];
auto java_vendor = results["java.vendor"]; auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64";
result.validity = JavaCheckResult::Validity::Valid; result.validity = JavaCheckResult::Validity::Valid;

View File

@ -183,7 +183,7 @@ void JavaListLoadTask::javaCheckerFinished()
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion; javaVersion->id = result.javaVersion;
javaVersion->arch = result.mojangPlatform; javaVersion->arch = result.realPlatform;
javaVersion->path = result.path; javaVersion->path = result.path;
candidates.append(javaVersion); candidates.append(javaVersion);

View File

@ -153,7 +153,7 @@ QStringList addJavasFromEnv(QList<QString> javas)
{ {
QByteArray env = qgetenv("SNEEDMC_JAVA_PATHS"); QByteArray env = qgetenv("SNEEDMC_JAVA_PATHS");
#if defined(Q_OS_WIN32) #if defined(Q_OS_WIN32)
QList<QString> javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(";")); QList<QString> javaPaths = QString::fromLocal8Bit(env).replace("\\", "/").split(QLatin1String(";"));
#else #else
QList<QString> javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":")); QList<QString> javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":"));
#endif #endif
@ -355,7 +355,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
} }
return candidates; return addJavasFromEnv(candidates);
} }
#elif defined(Q_OS_MAC) #elif defined(Q_OS_MAC)

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -87,7 +107,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
// Error message displayed if java can't start // Error message displayed if java can't start
emit logLine(QString("Could not start java:"), MessageLevel::Error); emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine("\nCheck your SneedMC Java settings.", MessageLevel::Launcher); emit logLine(QString("\nCheck your SneedMC Java settings."), MessageLevel::Launcher);
printSystemInfo(false, false); printSystemInfo(false, false);
emitFailed(QString("Could not start java!")); emitFailed(QString("Could not start java!"));
return; return;
@ -104,7 +124,8 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
case JavaCheckResult::Validity::Valid: case JavaCheckResult::Validity::Valid:
{ {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.javaVendor); printJavaInfo(result.javaVersion.toString(), result.realPlatform, result.javaVendor);
printSystemInfo(true, result.is_64bit);
instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform); instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaVendor", result.javaVendor); instance->settings()->set("JavaVendor", result.javaVendor);
@ -117,8 +138,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor) void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor)
{ {
emit logLine(QString("Java is version %1, using %2-bit architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::Launcher); emit logLine(QString("Java is version %1, using %2 architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::Launcher);
printSystemInfo(true, architecture == "64");
} }
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit) void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 dada513 <dada513@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "QuitAfterGameStop.h"
#include <launch/LaunchTask.h>
#include "Application.h"
void QuitAfterGameStop::executeTask()
{
APPLICATION->quit();
}

View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 dada513 <dada513@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <launch/LaunchStep.h>
class QuitAfterGameStop: public LaunchStep
{
Q_OBJECT
public:
explicit QuitAfterGameStop(LaunchTask *parent) :LaunchStep(parent){};
virtual ~QuitAfterGameStop() {};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
};

View File

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

View File

@ -74,9 +74,18 @@ Meta::BaseEntity::~BaseEntity()
} }
QUrl Meta::BaseEntity::url() const QUrl Meta::BaseEntity::url() const
{
auto s = APPLICATION->settings();
QString metaOverride = s->get("MetaURLOverride").toString();
if(metaOverride.isEmpty())
{ {
return QUrl(BuildConfig.META_URL).resolved(localFilename()); return QUrl(BuildConfig.META_URL).resolved(localFilename());
} }
else
{
return QUrl(metaOverride).resolved(localFilename());
}
}
bool Meta::BaseEntity::loadLocalFile() bool Meta::BaseEntity::loadLocalFile()
{ {

View File

@ -0,0 +1,36 @@
#pragma once
#include <QString>
#include "Library.h"
class Agent;
typedef std::shared_ptr<Agent> AgentPtr;
class Agent {
public:
Agent(LibraryPtr library, QString &argument)
{
m_library = library;
m_argument = argument;
}
public: /* methods */
LibraryPtr library() {
return m_library;
}
QString argument() {
return m_argument;
}
protected: /* data */
/// The library pointing to the jar this Java agent is contained within
LibraryPtr m_library;
/// The argument to the Java agent, passed after an = if present
QString m_argument;
};

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -297,7 +317,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
auto rawHash = QByteArray::fromHex(hash.toLatin1()); auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
} }
objectDL->m_total_progress = size; objectDL->setProgress(objectDL->getProgress(), size);
return objectDL; return objectDL;
} }
return nullptr; return nullptr;

View File

@ -591,7 +591,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
{ {
component->m_version = "3.1.2"; component->m_version = "3.1.2";
} }
else if (add.uid == "net.fabricmc.intermediary") else if (add.uid == "net.fabricmc.intermediary" || add.uid == "org.quiltmc.hashed")
{ {
auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){ auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){
return cmp->getID() == "net.minecraft"; return cmp->getID() == "net.minecraft";

View File

@ -124,7 +124,7 @@ struct GradleSpecifier
} }
bool matchName(const GradleSpecifier & other) const bool matchName(const GradleSpecifier & other) const
{ {
return other.artifactId() == artifactId() && other.groupId() == groupId(); return other.artifactId() == artifactId() && other.groupId() == groupId() && other.classifier() == classifier();
} }
bool operator==(const GradleSpecifier & other) const bool operator==(const GradleSpecifier & other) const
{ {

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "LaunchProfile.h" #include "LaunchProfile.h"
#include <Version.h> #include <Version.h>
@ -7,11 +42,13 @@ void LaunchProfile::clear()
m_minecraftVersionType.clear(); m_minecraftVersionType.clear();
m_minecraftAssets.reset(); m_minecraftAssets.reset();
m_minecraftArguments.clear(); m_minecraftArguments.clear();
m_addnJvmArguments.clear();
m_tweakers.clear(); m_tweakers.clear();
m_mainClass.clear(); m_mainClass.clear();
m_appletClass.clear(); m_appletClass.clear();
m_libraries.clear(); m_libraries.clear();
m_mavenFiles.clear(); m_mavenFiles.clear();
m_agents.clear();
m_traits.clear(); m_traits.clear();
m_jarMods.clear(); m_jarMods.clear();
m_mainJar.reset(); m_mainJar.reset();
@ -45,6 +82,11 @@ void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments)
applyString(minecraftArguments, this->m_minecraftArguments); applyString(minecraftArguments, this->m_minecraftArguments);
} }
void LaunchProfile::applyAddnJvmArguments(const QStringList& addnJvmArguments)
{
this->m_addnJvmArguments.append(addnJvmArguments);
}
void LaunchProfile::applyMinecraftVersionType(const QString& type) void LaunchProfile::applyMinecraftVersionType(const QString& type)
{ {
applyString(type, this->m_minecraftVersionType); applyString(type, this->m_minecraftVersionType);
@ -126,6 +168,11 @@ void LaunchProfile::applyMods(const QList<LibraryPtr>& mods)
} }
} }
void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor)
{
m_compatibleJavaMajors.append(javaMajor);
}
void LaunchProfile::applyLibrary(LibraryPtr library) void LaunchProfile::applyLibrary(LibraryPtr library)
{ {
if(!library->isActive()) if(!library->isActive())
@ -174,6 +221,22 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile)
m_mavenFiles.append(Library::limitedCopy(mavenFile)); m_mavenFiles.append(Library::limitedCopy(mavenFile));
} }
void LaunchProfile::applyAgent(AgentPtr agent)
{
auto lib = agent->library();
if(!lib->isActive())
{
return;
}
if(lib->isNative())
{
return;
}
m_agents.append(agent);
}
const LibraryPtr LaunchProfile::getMainJar() const const LibraryPtr LaunchProfile::getMainJar() const
{ {
return m_mainJar; return m_mainJar;
@ -255,6 +318,11 @@ QString LaunchProfile::getMinecraftArguments() const
return m_minecraftArguments; return m_minecraftArguments;
} }
const QStringList & LaunchProfile::getAddnJvmArguments() const
{
return m_addnJvmArguments;
}
const QList<LibraryPtr> & LaunchProfile::getJarMods() const const QList<LibraryPtr> & LaunchProfile::getJarMods() const
{ {
return m_jarMods; return m_jarMods;
@ -275,6 +343,16 @@ const QList<LibraryPtr> & LaunchProfile::getMavenFiles() const
return m_mavenFiles; return m_mavenFiles;
} }
const QList<AgentPtr> & LaunchProfile::getAgents() const
{
return m_agents;
}
const QList<int> & LaunchProfile::getCompatibleJavaMajors() const
{
return m_compatibleJavaMajors;
}
void LaunchProfile::getLibraryFiles( void LaunchProfile::getLibraryFiles(
const QString& architecture, const QString& architecture,
QStringList& jars, QStringList& jars,

View File

@ -1,6 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QString> #include <QString>
#include "Library.h" #include "Library.h"
#include "Agent.h"
#include <ProblemProvider.h> #include <ProblemProvider.h>
class LaunchProfile: public ProblemProvider class LaunchProfile: public ProblemProvider
@ -13,6 +49,7 @@ public: /* application of profile variables from patches */
void applyMainClass(const QString& mainClass); void applyMainClass(const QString& mainClass);
void applyAppletClass(const QString& appletClass); void applyAppletClass(const QString& appletClass);
void applyMinecraftArguments(const QString& minecraftArguments); void applyMinecraftArguments(const QString& minecraftArguments);
void applyAddnJvmArguments(const QStringList& minecraftArguments);
void applyMinecraftVersionType(const QString& type); void applyMinecraftVersionType(const QString& type);
void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets);
void applyTraits(const QSet<QString> &traits); void applyTraits(const QSet<QString> &traits);
@ -21,6 +58,8 @@ public: /* application of profile variables from patches */
void applyMods(const QList<LibraryPtr> &jarMods); void applyMods(const QList<LibraryPtr> &jarMods);
void applyLibrary(LibraryPtr library); void applyLibrary(LibraryPtr library);
void applyMavenFile(LibraryPtr library); void applyMavenFile(LibraryPtr library);
void applyAgent(AgentPtr agent);
void applyCompatibleJavaMajors(QList<int>& javaMajor);
void applyMainJar(LibraryPtr jar); void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity); void applyProblemSeverity(ProblemSeverity severity);
/// clear the profile /// clear the profile
@ -33,12 +72,15 @@ public: /* getters for profile variables */
QString getMinecraftVersionType() const; QString getMinecraftVersionType() const;
MojangAssetIndexInfo::Ptr getMinecraftAssets() const; MojangAssetIndexInfo::Ptr getMinecraftAssets() const;
QString getMinecraftArguments() const; QString getMinecraftArguments() const;
const QStringList & getAddnJvmArguments() const;
const QSet<QString> & getTraits() const; const QSet<QString> & getTraits() const;
const QStringList & getTweakers() const; const QStringList & getTweakers() const;
const QList<LibraryPtr> & getJarMods() const; const QList<LibraryPtr> & getJarMods() const;
const QList<LibraryPtr> & getLibraries() const; const QList<LibraryPtr> & getLibraries() const;
const QList<LibraryPtr> & getNativeLibraries() const; const QList<LibraryPtr> & getNativeLibraries() const;
const QList<LibraryPtr> & getMavenFiles() const; const QList<LibraryPtr> & getMavenFiles() const;
const QList<AgentPtr> & getAgents() const;
const QList<int> & getCompatibleJavaMajors() const;
const LibraryPtr getMainJar() const; const LibraryPtr getMainJar() const;
void getLibraryFiles( void getLibraryFiles(
const QString & architecture, const QString & architecture,
@ -69,6 +111,12 @@ private:
*/ */
QString m_minecraftArguments; QString m_minecraftArguments;
/**
* Additional arguments to pass to the JVM in addition to those the user has configured,
* memory settings, etc.
*/
QStringList m_addnJvmArguments;
/// A list of all tweaker classes /// A list of all tweaker classes
QStringList m_tweakers; QStringList m_tweakers;
@ -84,6 +132,9 @@ private:
/// the list of maven files to be placed in the libraries folder, but not acted upon /// the list of maven files to be placed in the libraries folder, but not acted upon
QList<LibraryPtr> m_mavenFiles; QList<LibraryPtr> m_mavenFiles;
/// the list of java agents to add to JVM arguments
QList<AgentPtr> m_agents;
/// the main jar /// the main jar
LibraryPtr m_mainJar; LibraryPtr m_mainJar;
@ -99,6 +150,9 @@ private:
/// the list of mods /// the list of mods
QList<LibraryPtr> m_mods; QList<LibraryPtr> m_mods;
/// compatible java major versions
QList<int> m_compatibleJavaMajors;
ProblemSeverity m_problemSeverity = ProblemSeverity::None; ProblemSeverity m_problemSeverity = ProblemSeverity::None;
}; };

View File

@ -1,4 +1,41 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
#include "BuildConfig.h"
#include "minecraft/launch/CreateGameFolders.h" #include "minecraft/launch/CreateGameFolders.h"
#include "minecraft/launch/ExtractNatives.h" #include "minecraft/launch/ExtractNatives.h"
#include "minecraft/launch/PrintInstanceInfo.h" #include "minecraft/launch/PrintInstanceInfo.h"
@ -19,7 +56,11 @@
#include "launch/steps/PreLaunchCommand.h" #include "launch/steps/PreLaunchCommand.h"
#include "launch/steps/TextPrint.h" #include "launch/steps/TextPrint.h"
#include "launch/steps/CheckJava.h" #include "launch/steps/CheckJava.h"
<<<<<<< HEAD
#include "launch/steps/Update.h" #include "launch/steps/Update.h"
=======
#include "launch/steps/QuitAfterGameStop.h"
>>>>>>> upstream/develop
#include "minecraft/launch/LauncherPartLaunch.h" #include "minecraft/launch/LauncherPartLaunch.h"
#include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/DirectJavaLaunch.h"
@ -88,6 +129,7 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation);
m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs);
m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
// special! // special!
m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
@ -124,18 +166,14 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerSetting("JoinServerOnLaunch", false); m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", ""); m_settings->registerSetting("JoinServerOnLaunchAddress", "");
// DEPRECATED: Read what versions the user configuration thinks should be used // Miscellaneous
m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
m_settings->registerSetting("LWJGLVersion", ""); m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerSetting("ForgeVersion", ""); m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
m_settings->registerSetting("LiteloaderVersion", "");
m_settings->set("InstanceType", "OneSix");
m_components.reset(new PackProfile(this)); m_components.reset(new PackProfile(this));
m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString());
auto setting = m_settings->getSetting("LWJGLVersion");
m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString());
m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString());
m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString());
} }
void MinecraftInstance::saveNow() void MinecraftInstance::saveNow()
@ -303,6 +341,17 @@ QStringList MinecraftInstance::extraArguments() const
list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true",
"-Dfml.ignorePatchDiscrepancies=true"}); "-Dfml.ignorePatchDiscrepancies=true"});
} }
auto addn = m_components->getProfile()->getAddnJvmArguments();
if (!addn.isEmpty()) {
list.append(addn);
}
auto agents = m_components->getProfile()->getAgents();
for (auto agent : agents)
{
QStringList jar, temp1, temp2, temp3;
agent->library()->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, getLocalLibraryPath());
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
}
return list; return list;
} }
@ -444,9 +493,14 @@ QStringList MinecraftInstance::processMinecraftArgs(
} }
} }
<<<<<<< HEAD
// blatant self-promotion. // blatant self-promotion.
token_mapping["profile_name"] = token_mapping["version_name"] = "SneedMC"; token_mapping["profile_name"] = token_mapping["version_name"] = "SneedMC";
=======
token_mapping["profile_name"] = name();
token_mapping["version_name"] = profile->getMinecraftVersion();
>>>>>>> upstream/develop
token_mapping["version_type"] = profile->getMinecraftVersionType(); token_mapping["version_type"] = profile->getMinecraftVersionType();
QString absRootDir = QDir(gameRoot()).absolutePath(); QString absRootDir = QDir(gameRoot()).absolutePath();
@ -616,23 +670,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << QString("%1:").arg(label); out << QString("%1:").arg(label);
auto modList = model.allMods(); auto modList = model.allMods();
std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) { std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
auto aName = a.filename().completeBaseName(); auto aName = a.fileinfo().completeBaseName();
auto bName = b.filename().completeBaseName(); auto bName = b.fileinfo().completeBaseName();
return aName.localeAwareCompare(bName) < 0; return aName.localeAwareCompare(bName) < 0;
}); });
for(auto & mod: modList) for(auto & mod: modList)
{ {
if(mod.type() == Mod::MOD_FOLDER) if(mod.type() == Mod::MOD_FOLDER)
{ {
out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)"; out << u8" [📁] " + mod.fileinfo().completeBaseName() + " (folder)";
continue; continue;
} }
if(mod.enabled()) { if(mod.enabled()) {
out << u8" [✔️] " + mod.filename().completeBaseName(); out << u8" [✔️] " + mod.fileinfo().completeBaseName();
} }
else { else {
out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)"; out << u8" [❌] " + mod.fileinfo().completeBaseName() + " (disabled)";
} }
} }
@ -946,6 +1000,11 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
{ {
process->setCensorFilter(createCensorFilterFromSession(session)); process->setCensorFilter(createCensorFilterFromSession(session));
} }
if(m_settings->get("QuitAfterGameStop").toBool())
{
auto step = new QuitAfterGameStop(pptr);
process->appendStep(step);
}
m_launchProcess = process; m_launchProcess = process;
emit launchTaskChanged(m_launchProcess); emit launchTaskChanged(m_launchProcess);
return m_launchProcess; return m_launchProcess;
@ -965,7 +1024,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
{ {
if (!m_loader_mod_list) if (!m_loader_mod_list)
{ {
m_loader_mod_list.reset(new ModFolderModel(modsRoot())); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed));
m_loader_mod_list->disableInteraction(isRunning()); m_loader_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
} }
@ -976,7 +1036,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
{ {
if (!m_core_mod_list) if (!m_core_mod_list)
{ {
m_core_mod_list.reset(new ModFolderModel(coreModsDir())); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed));
m_core_mod_list->disableInteraction(isRunning()); m_core_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
} }

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MojangVersionFormat.h" #include "MojangVersionFormat.h"
#include "OneSixVersionFormat.h" #include "OneSixVersionFormat.h"
#include "MojangDownloadInfo.h" #include "MojangDownloadInfo.h"
@ -183,6 +218,15 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFi
); );
} }
} }
if (in.contains("compatibleJavaMajors"))
{
for (auto compatible : requireArray(in.value("compatibleJavaMajors")))
{
out->compatibleJavaMajors.append(requireInteger(compatible));
}
}
if(in.contains("downloads")) if(in.contains("downloads"))
{ {
auto downloadsObj = requireObject(in, "downloads"); auto downloadsObj = requireObject(in, "downloads");

View File

@ -1,5 +1,6 @@
#include "OneSixVersionFormat.h" #include "OneSixVersionFormat.h"
#include <Json.h> #include <Json.h>
#include "minecraft/Agent.h"
#include "minecraft/ParseUtils.h" #include "minecraft/ParseUtils.h"
#include <minecraft/MojangVersionFormat.h> #include <minecraft/MojangVersionFormat.h>
@ -108,6 +109,14 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
} }
} }
if (root.contains("+jvmArgs"))
{
for (auto arg : requireArray(root.value("+jvmArgs")))
{
out->addnJvmArguments.append(requireString(arg));
}
}
if (root.contains("jarMods")) if (root.contains("jarMods"))
{ {
@ -176,6 +185,21 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
readLibs("mavenFiles", out->mavenFiles); readLibs("mavenFiles", out->mavenFiles);
} }
if(root.contains("+agents")) {
for (auto agentVal : requireArray(root.value("+agents")))
{
QJsonObject agentObj = requireObject(agentVal);
auto lib = libraryFromJson(*out, agentObj, filename);
QString arg = "";
if (agentObj.contains("argument"))
{
readString(agentObj, "argument", arg);
}
AgentPtr agent(new Agent(lib, arg));
out->agents.append(agent);
}
}
// if we have mainJar, just use it // if we have mainJar, just use it
if(root.contains("mainJar")) if(root.contains("mainJar"))
{ {

View File

@ -36,6 +36,13 @@
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include "Application.h" #include "Application.h"
#include "modplatform/ModAPI.h"
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{
{"net.minecraftforge", ModAPI::Forge},
{"net.fabricmc.fabric-loader", ModAPI::Fabric},
{"org.quiltmc.quilt-loader", ModAPI::Quilt}
};
PackProfile::PackProfile(MinecraftInstance * instance) PackProfile::PackProfile(MinecraftInstance * instance)
: QAbstractListModel() : QAbstractListModel()
@ -272,18 +279,6 @@ void PackProfile::save_internal()
bool PackProfile::load() bool PackProfile::load()
{ {
auto filename = componentsFilePath(); auto filename = componentsFilePath();
QFile componentsFile(filename);
// migrate old config to new one, if needed
if(!componentsFile.exists())
{
if(!migratePreComponentConfig())
{
// FIXME: the user should be notified...
qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name();
return false;
}
}
// load the new component list and swap it with the current one... // load the new component list and swap it with the current one...
ComponentContainer newComponents; ComponentContainer newComponents;
@ -369,239 +364,6 @@ void PackProfile::updateFailed(const QString& error)
invalidateLaunchProfile(); invalidateLaunchProfile();
} }
// NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig).
static void upgradeDeprecatedFiles(QString root, QString instanceName)
{
auto versionJsonPath = FS::PathCombine(root, "version.json");
auto customJsonPath = FS::PathCombine(root, "custom.json");
auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json");
QString sourceFile;
QString renameFile;
// convert old crap.
if(QFile::exists(customJsonPath))
{
sourceFile = customJsonPath;
renameFile = versionJsonPath;
}
else if(QFile::exists(versionJsonPath))
{
sourceFile = versionJsonPath;
}
if(!sourceFile.isEmpty() && !QFile::exists(mcJson))
{
if(!FS::ensureFilePathExists(mcJson))
{
qWarning() << "Couldn't create patches folder for" << instanceName;
return;
}
if(!renameFile.isEmpty() && QFile::exists(renameFile))
{
if(!QFile::rename(renameFile, renameFile + ".old"))
{
qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName;
return;
}
}
auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false);
ProfileUtils::removeLwjglFromPatch(file);
file->uid = "net.minecraft";
file->version = file->minecraftVersion;
file->name = "Minecraft";
Meta::Require needsLwjgl;
needsLwjgl.uid = "org.lwjgl";
file->requires.insert(needsLwjgl);
if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson))
{
return;
}
if(!QFile::rename(sourceFile, sourceFile + ".old"))
{
qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName;
return;
}
}
}
/*
* Migrate old layout to the component based one...
* - Part of the version information is taken from `instance.cfg` (fed to this class from outside).
* - Part is taken from the old order.json file.
* - Part is loaded from loose json files in the instance's `patches` directory.
*/
bool PackProfile::migratePreComponentConfig()
{
// upgrade the very old files from the beginnings of MultiMC 5
upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name());
QList<ComponentPtr> components;
QSet<QString> loaded;
auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict)
{
auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json");
auto intendedVersion = d->getOldConfigVersion(uid);
// load up the base minecraft patch
ComponentPtr component;
if(QFile::exists(jsonFilePath))
{
if(intendedVersion.isEmpty())
{
intendedVersion = emptyVersion;
}
auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false);
// fix uid
file->uid = uid;
// if version is missing, add it from the outside.
if(file->version.isEmpty())
{
file->version = intendedVersion;
}
// if this is a dependency (LWJGL), mark it also as volatile
if(asDependency)
{
file->m_volatile = true;
}
// insert requirements if needed
if(!req.uid.isEmpty())
{
file->requires.insert(req);
}
// insert conflicts if needed
if(!conflict.uid.isEmpty())
{
file->conflicts.insert(conflict);
}
// FIXME: @QUALITY do not ignore return value
ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath);
component = new Component(this, uid, file);
component->m_version = intendedVersion;
}
else if(!intendedVersion.isEmpty())
{
auto metaVersion = APPLICATION->metadataIndex()->get(uid, intendedVersion);
component = new Component(this, metaVersion);
}
else
{
return;
}
component->m_dependencyOnly = asDependency;
component->m_important = !asDependency;
components.append(component);
};
// TODO: insert depends and conflicts here if these are customized files...
Meta::Require reqLwjgl;
reqLwjgl.uid = "org.lwjgl";
reqLwjgl.suggests = "2.9.1";
Meta::Require conflictLwjgl3;
conflictLwjgl3.uid = "org.lwjgl3";
Meta::Require nullReq;
addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3);
addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq);
// first, collect all other file-based patches and load them
QMap<QString, ComponentPtr> loadedComponents;
QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches"));
for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files))
{
// parse the file
qDebug() << "Reading" << info.fileName();
auto file = ProfileUtils::parseJsonFile(info, true);
// correct missing or wrong uid based on the file name
QString uid = info.completeBaseName();
// ignore builtins, they've been handled already
if (uid == "net.minecraft")
continue;
if (uid == "org.lwjgl")
continue;
// handle horrible corner cases
if(uid.isEmpty())
{
// if you have a file named '.json', make it just go away.
// FIXME: @QUALITY do not ignore return value
QFile::remove(info.absoluteFilePath());
continue;
}
file->uid = uid;
// FIXME: @QUALITY do not ignore return value
ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath());
auto component = new Component(this, file->uid, file);
auto version = d->getOldConfigVersion(file->uid);
if(!version.isEmpty())
{
component->m_version = version;
}
loadedComponents[file->uid] = component;
}
// try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files
auto loadSpecial = [&](const QString & uid, int order)
{
auto patchVersion = d->getOldConfigVersion(uid);
if(!patchVersion.isEmpty() && !loadedComponents.contains(uid))
{
auto patch = new Component(this, APPLICATION->metadataIndex()->get(uid, patchVersion));
patch->setOrder(order);
loadedComponents[uid] = patch;
}
};
loadSpecial("net.minecraftforge", 5);
loadSpecial("com.mumfrey.liteloader", 10);
// load the old order.json file, if present
ProfileUtils::PatchOrder userOrder;
ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder);
// now add all the patches by user sort order
for (auto uid : userOrder)
{
// ignore builtins
if (uid == "net.minecraft")
continue;
if (uid == "org.lwjgl")
continue;
// ordering has a patch that is gone?
if(!loadedComponents.contains(uid))
{
continue;
}
components.append(loadedComponents.take(uid));
}
// is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json
if(!loadedComponents.isEmpty())
{
// inserting into multimap by order number as key sorts the patches and detects duplicates
QMultiMap<int, ComponentPtr> files;
auto iter = loadedComponents.begin();
while(iter != loadedComponents.end())
{
files.insert((*iter)->getOrder(), *iter);
iter++;
}
// then just extract the patches and put them in the list
for (auto order : files.keys())
{
const auto &values = files.values(order);
for(auto &value: values)
{
// TODO: put back the insertion of problem messages here, so the user knows about the id duplication
components.append(value);
}
}
}
// new we have a complete list of components...
return savePackProfile(componentsFilePath(), components);
}
// END: save/load // END: save/load
void PackProfile::appendComponent(ComponentPtr component) void PackProfile::appendComponent(ComponentPtr component)
@ -1169,15 +931,6 @@ std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
return d->m_profile; return d->m_profile;
} }
void PackProfile::setOldConfigVersion(const QString& uid, const QString& version)
{
if(version.isEmpty())
{
return;
}
d->m_oldConfigVersions[uid] = version;
}
bool PackProfile::setComponentVersion(const QString& uid, const QString& version, bool important) bool PackProfile::setComponentVersion(const QString& uid, const QString& version, bool important)
{ {
auto iter = d->componentIndex.find(uid); auto iter = d->componentIndex.find(uid);
@ -1224,3 +977,20 @@ void PackProfile::disableInteraction(bool disable)
} }
} }
} }
ModAPI::ModLoaderTypes PackProfile::getModLoaders()
{
ModAPI::ModLoaderTypes result = ModAPI::Unspecified;
QMapIterator<QString, ModAPI::ModLoaderType> i(modloaderMapping);
while (i.hasNext())
{
i.next();
Component* c = getComponent(i.key());
if (c != nullptr && c->isEnabled()) {
result |= i.value();
}
}
return result;
}

View File

@ -28,6 +28,7 @@
#include "BaseVersion.h" #include "BaseVersion.h"
#include "MojangDownloadInfo.h" #include "MojangDownloadInfo.h"
#include "net/Mode.h" #include "net/Mode.h"
#include "modplatform/ModAPI.h"
class MinecraftInstance; class MinecraftInstance;
struct PackProfileData; struct PackProfileData;
@ -117,6 +118,8 @@ public:
// todo(merged): is this the best approach // todo(merged): is this the best approach
void appendComponent(ComponentPtr component); void appendComponent(ComponentPtr component);
ModAPI::ModLoaderTypes getModLoaders();
private: private:
void scheduleSave(); void scheduleSave();
bool saveIsScheduled() const; bool saveIsScheduled() const;
@ -143,8 +146,6 @@ private:
bool installCustomJar_internal(QString filepath); bool installCustomJar_internal(QString filepath);
bool removeComponent_internal(ComponentPtr patch); bool removeComponent_internal(ComponentPtr patch);
bool migratePreComponentConfig();
private: /* data */ private: /* data */
std::unique_ptr<PackProfileData> d; std::unique_ptr<PackProfileData> d;

View File

@ -18,18 +18,6 @@ struct PackProfileData
// the launch profile (volatile, temporary thing created on demand) // the launch profile (volatile, temporary thing created on demand)
std::shared_ptr<LaunchProfile> m_profile; std::shared_ptr<LaunchProfile> m_profile;
// version information migrated from instance.cfg file. Single use on migration!
std::map<QString, QString> m_oldConfigVersions;
QString getOldConfigVersion(const QString& uid) const
{
const auto iter = m_oldConfigVersions.find(uid);
if(iter != m_oldConfigVersions.cend())
{
return (*iter).second;
}
return QString();
}
// persistent list of components and related machinery // persistent list of components and related machinery
ComponentContainer components; ComponentContainer components;
ComponentIndex componentIndex; ComponentIndex componentIndex;

View File

@ -1,3 +1,39 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
@ -20,7 +56,7 @@ void VersionFile::applyTo(LaunchProfile *profile)
// Only real Minecraft can set those. Don't let anything override them. // Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid)) if (isMinecraftVersion(uid))
{ {
profile->applyMinecraftVersion(minecraftVersion); profile->applyMinecraftVersion(version);
profile->applyMinecraftVersionType(type); profile->applyMinecraftVersionType(type);
// HACK: ignore assets from other version files than Minecraft // HACK: ignore assets from other version files than Minecraft
// workaround for stupid assets issue caused by amazon: // workaround for stupid assets issue caused by amazon:
@ -32,10 +68,12 @@ void VersionFile::applyTo(LaunchProfile *profile)
profile->applyMainClass(mainClass); profile->applyMainClass(mainClass);
profile->applyAppletClass(appletClass); profile->applyAppletClass(appletClass);
profile->applyMinecraftArguments(minecraftArguments); profile->applyMinecraftArguments(minecraftArguments);
profile->applyAddnJvmArguments(addnJvmArguments);
profile->applyTweakers(addTweakers); profile->applyTweakers(addTweakers);
profile->applyJarMods(jarMods); profile->applyJarMods(jarMods);
profile->applyMods(mods); profile->applyMods(mods);
profile->applyTraits(traits); profile->applyTraits(traits);
profile->applyCompatibleJavaMajors(compatibleJavaMajors);
for (auto library : libraries) for (auto library : libraries)
{ {
@ -45,6 +83,10 @@ void VersionFile::applyTo(LaunchProfile *profile)
{ {
profile->applyMavenFile(mavenFile); profile->applyMavenFile(mavenFile);
} }
for (auto agent : agents)
{
profile->applyAgent(agent);
}
profile->applyProblemSeverity(getProblemSeverity()); profile->applyProblemSeverity(getProblemSeverity());
} }

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QString> #include <QString>
@ -10,6 +45,7 @@
#include "minecraft/Rule.h" #include "minecraft/Rule.h"
#include "ProblemProvider.h" #include "ProblemProvider.h"
#include "Library.h" #include "Library.h"
#include "Agent.h"
#include <meta/JsonFormat.h> #include <meta/JsonFormat.h>
class PackProfile; class PackProfile;
@ -57,6 +93,12 @@ public: /* data */
/// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution)
QString minecraftArguments; QString minecraftArguments;
/// PolyMC: Additional JVM launch arguments
QStringList addnJvmArguments;
/// Mojang: list of compatible java majors
QList<int> compatibleJavaMajors;
/// Mojang: type of the Minecraft version /// Mojang: type of the Minecraft version
QString type; QString type;
@ -78,6 +120,9 @@ public: /* data */
/// SneedMC: list of maven files to put in the libraries folder, but not in classpath /// SneedMC: list of maven files to put in the libraries folder, but not in classpath
QList<LibraryPtr> mavenFiles; QList<LibraryPtr> mavenFiles;
/// PolyMC: list of agents to add to JVM arguments
QList<AgentPtr> agents;
/// The main jar (Minecraft version library, normally) /// The main jar (Minecraft version library, normally)
LibraryPtr mainJar; LibraryPtr mainJar;

View File

@ -17,6 +17,7 @@
#include <QString> #include <QString>
#include <QDebug> #include <QDebug>
#include <QSaveFile> #include <QSaveFile>
#include <QDirIterator>
#include "World.h" #include "World.h"
#include "GZip.h" #include "GZip.h"
@ -187,6 +188,26 @@ bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data)
return f.commit(); return f.commit();
} }
int64_t calculateWorldSize(const QFileInfo &file)
{
if (file.isFile() && file.suffix() == "zip")
{
return file.size();
}
else if(file.isDir())
{
QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
int64_t total = 0;
while (it.hasNext())
{
total += it.fileInfo().size();
it.next();
}
return total;
}
return -1;
}
World::World(const QFileInfo &file) World::World(const QFileInfo &file)
{ {
repath(file); repath(file);
@ -196,6 +217,7 @@ void World::repath(const QFileInfo &file)
{ {
m_containerFile = file; m_containerFile = file;
m_folderName = file.fileName(); m_folderName = file.fileName();
m_size = calculateWorldSize(file);
if(file.isFile() && file.suffix() == "zip") if(file.isFile() && file.suffix() == "zip")
{ {
m_iconFile = QString(); m_iconFile = QString();
@ -482,6 +504,7 @@ void World::loadFromLevelDat(QByteArray data)
if(randomSeed) { if(randomSeed) {
qDebug() << "Seed:" << *randomSeed; qDebug() << "Seed:" << *randomSeed;
} }
qDebug() << "Size:" << m_size;
qDebug() << "GameType:" << m_gameType.toLogString(); qDebug() << "GameType:" << m_gameType.toLogString();
} }

View File

@ -52,6 +52,10 @@ public:
{ {
return m_iconFile; return m_iconFile;
} }
int64_t bytes() const
{
return m_size;
}
QDateTime lastPlayed() const QDateTime lastPlayed() const
{ {
return m_lastPlayed; return m_lastPlayed;
@ -105,6 +109,7 @@ protected:
QString m_iconFile; QString m_iconFile;
QDateTime levelDatTime; QDateTime levelDatTime;
QDateTime m_lastPlayed; QDateTime m_lastPlayed;
int64_t m_size;
int64_t m_randomSeed = 0; int64_t m_randomSeed = 0;
GameType m_gameType; GameType m_gameType;
bool is_valid = false; bool is_valid = false;

View File

@ -14,7 +14,10 @@
*/ */
#include "WorldList.h" #include "WorldList.h"
#include "Application.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <Qt>
#include <QMimeData> #include <QMimeData>
#include <QUrl> #include <QUrl>
#include <QUuid> #include <QUuid>
@ -150,7 +153,7 @@ bool WorldList::resetIcon(int row)
int WorldList::columnCount(const QModelIndex &parent) const int WorldList::columnCount(const QModelIndex &parent) const
{ {
return 3; return 4;
} }
QVariant WorldList::data(const QModelIndex &index, int role) const QVariant WorldList::data(const QModelIndex &index, int role) const
@ -164,6 +167,8 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
if (row < 0 || row >= worlds.size()) if (row < 0 || row >= worlds.size())
return QVariant(); return QVariant();
QLocale locale;
auto & world = worlds[row]; auto & world = worlds[row];
switch (role) switch (role)
{ {
@ -179,10 +184,23 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
case LastPlayedColumn: case LastPlayedColumn:
return world.lastPlayed(); return world.lastPlayed();
case SizeColumn:
return locale.formattedDataSize(world.bytes());
default: default:
return QVariant(); return QVariant();
} }
case Qt::UserRole:
switch (column)
{
case SizeColumn:
return qVariantFromValue<qlonglong>(world.bytes());
default:
return data(index, Qt::DisplayRole);
}
case Qt::ToolTipRole: case Qt::ToolTipRole:
{ {
return world.folderName(); return world.folderName();
@ -207,6 +225,10 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
{ {
return world.lastPlayed(); return world.lastPlayed();
} }
case SizeRole:
{
return qVariantFromValue<qlonglong>(world.bytes());
}
case IconFileRole: case IconFileRole:
{ {
return world.iconFile(); return world.iconFile();
@ -229,6 +251,9 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
return tr("Game Mode"); return tr("Game Mode");
case LastPlayedColumn: case LastPlayedColumn:
return tr("Last Played"); return tr("Last Played");
case SizeColumn:
//: World size on disk
return tr("Size");
default: default:
return QVariant(); return QVariant();
} }
@ -242,6 +267,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
return tr("Game mode of the world."); return tr("Game mode of the world.");
case LastPlayedColumn: case LastPlayedColumn:
return tr("Date and time the world was last played."); return tr("Date and time the world was last played.");
case SizeColumn:
return tr("Size of the world on disk.");
default: default:
return QVariant(); return QVariant();
} }

View File

@ -32,7 +32,8 @@ public:
{ {
NameColumn, NameColumn,
GameModeColumn, GameModeColumn,
LastPlayedColumn LastPlayedColumn,
SizeColumn
}; };
enum Roles enum Roles
@ -43,6 +44,7 @@ public:
NameRole, NameRole,
GameModeRole, GameModeRole,
LastPlayedRole, LastPlayedRole,
SizeRole,
IconFileRole IconFileRole
}; };

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AccountData.h" #include "AccountData.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
@ -327,6 +362,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
} }
if(type == AccountType::MSA) { if(type == AccountType::MSA) {
auto clientIDV = data.value("msa-client-id");
if (clientIDV.isString()) {
msaClientID = clientIDV.toString();
} // leave msaClientID empty if it doesn't exist or isn't a string
msaToken = tokenFromJSONV3(data, "msa"); msaToken = tokenFromJSONV3(data, "msa");
userToken = tokenFromJSONV3(data, "utoken"); userToken = tokenFromJSONV3(data, "utoken");
xboxApiToken = tokenFromJSONV3(data, "xrp-main"); xboxApiToken = tokenFromJSONV3(data, "xrp-main");
@ -360,6 +399,7 @@ QJsonObject AccountData::saveState() const {
} }
else if (type == AccountType::MSA) { else if (type == AccountType::MSA) {
output["type"] = "MSA"; output["type"] = "MSA";
output["msa-client-id"] = msaClientID;
tokenToJSONV3(output, msaToken, "msa"); tokenToJSONV3(output, msaToken, "msa");
tokenToJSONV3(output, userToken, "utoken"); tokenToJSONV3(output, userToken, "utoken");
tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, xboxApiToken, "xrp-main");

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QString> #include <QString>
#include <QByteArray> #include <QByteArray>
@ -47,6 +82,7 @@ enum class AccountState {
Offline, Offline,
Working, Working,
Online, Online,
Disabled,
Errored, Errored,
Expired, Expired,
Gone Gone
@ -81,6 +117,7 @@ struct AccountData {
bool legacy = false; bool legacy = false;
bool canMigrateToMSA = false; bool canMigrateToMSA = false;
QString msaClientID;
Katabasis::Token msaToken; Katabasis::Token msaToken;
Katabasis::Token userToken; Katabasis::Token userToken;
Katabasis::Token xboxApiToken; Katabasis::Token xboxApiToken;

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -291,6 +311,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
case AccountState::Expired: { case AccountState::Expired: {
return tr("Expired", "Account status"); return tr("Expired", "Account status");
} }
case AccountState::Disabled: {
return tr("Disabled", "Account status");
}
case AccountState::Gone: { case AccountState::Gone: {
return tr("Gone", "Account status"); return tr("Gone", "Account status");
} }

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -43,6 +63,8 @@ QString AccountTask::getStateMessage() const
return tr("Authentication task succeeded."); return tr("Authentication task succeeded.");
case AccountTaskState::STATE_OFFLINE: case AccountTaskState::STATE_OFFLINE:
return tr("Failed to contact the authentication server."); return tr("Failed to contact the authentication server.");
case AccountTaskState::STATE_DISABLED:
return tr("Client ID has changed. New session needs to be created.");
case AccountTaskState::STATE_FAILED_SOFT: case AccountTaskState::STATE_FAILED_SOFT:
return tr("Encountered an error during authentication."); return tr("Encountered an error during authentication.");
case AccountTaskState::STATE_FAILED_HARD: case AccountTaskState::STATE_FAILED_HARD:
@ -78,6 +100,12 @@ bool AccountTask::changeState(AccountTaskState newState, QString reason)
emitFailed(reason); emitFailed(reason);
return false; return false;
} }
case AccountTaskState::STATE_DISABLED: {
m_data->errorString = reason;
m_data->accountState = AccountState::Disabled;
emitFailed(reason);
return false;
}
case AccountTaskState::STATE_FAILED_SOFT: { case AccountTaskState::STATE_FAILED_SOFT: {
m_data->errorString = reason; m_data->errorString = reason;
m_data->accountState = AccountState::Errored; m_data->accountState = AccountState::Errored;

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -35,6 +55,7 @@ enum class AccountTaskState
STATE_CREATED, STATE_CREATED,
STATE_WORKING, STATE_WORKING,
STATE_SUCCEEDED, STATE_SUCCEEDED,
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *
@ -176,6 +196,9 @@ void MinecraftAccount::authFailed(QString reason)
{ {
switch (m_currentTask->taskState()) { switch (m_currentTask->taskState()) {
case AccountTaskState::STATE_OFFLINE: case AccountTaskState::STATE_OFFLINE:
case AccountTaskState::STATE_DISABLED: {
// NOTE: user will need to fix this themselves.
}
case AccountTaskState::STATE_FAILED_SOFT: { case AccountTaskState::STATE_FAILED_SOFT: {
// NOTE: this doesn't do much. There was an error of some sort. // NOTE: this doesn't do much. There was an error of some sort.
} }

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,4 +1,5 @@
#include "Parsers.h" #include "Parsers.h"
#include "Json.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonArray> #include <QJsonArray>
@ -212,6 +213,180 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
return true; return true;
} }
namespace {
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
// they are needed because the session server doesn't return skin urls for default skins
static const QString SKIN_URL_STEVE = "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
static const QString SKIN_URL_ALEX = "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
bool isDefaultModelSteve(QString uuid) {
// need to calculate *Java* hashCode of UUID
// if number is even, skin/model is steve, otherwise it is alex
// just in case dashes are in the id
uuid.remove('-');
if (uuid.size() != 32) {
return true;
}
// qulonglong is guaranteed to be 64 bits
// we need to use unsigned numbers to guarantee truncation below
qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
qulonglong xored = most ^ least;
return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
}
}
/**
Uses session server for skin/cape lookup instead of profile,
because locked Mojang accounts cannot access profile endpoint
(https://api.minecraftservices.com/minecraft/profile/)
ref: https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape
{
"id": "<profile identifier>",
"name": "<player name>",
"properties": [
{
"name": "textures",
"value": "<base64 string>"
}
]
}
decoded base64 "value":
{
"timestamp": <java time in ms>,
"profileId": "<profile uuid>",
"profileName": "<player name>",
"textures": {
"SKIN": {
"url": "<player skin URL>"
},
"CAPE": {
"url": "<player cape URL>"
}
}
}
*/
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
qDebug() << "Parsing Minecraft profile...";
#ifndef NDEBUG
qDebug() << data;
#endif
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
return false;
}
auto obj = Json::requireObject(doc, "mojang minecraft profile");
if(!getString(obj.value("id"), output.id)) {
qWarning() << "Minecraft profile id is not a string";
return false;
}
if(!getString(obj.value("name"), output.name)) {
qWarning() << "Minecraft profile name is not a string";
return false;
}
auto propsArray = obj.value("properties").toArray();
QByteArray texturePayload;
for( auto p : propsArray) {
auto pObj = p.toObject();
auto name = pObj.value("name");
if (!name.isString() || name.toString() != "textures") {
continue;
}
auto value = pObj.value("value");
if (value.isString()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors);
#else
texturePayload = QByteArray::fromBase64(value.toString().toUtf8());
#endif
}
if (!texturePayload.isEmpty()) {
break;
}
}
if (texturePayload.isNull()) {
qWarning() << "No texture payload data";
return false;
}
doc = QJsonDocument::fromJson(texturePayload, &jsonError);
if(jsonError.error) {
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
return false;
}
obj = Json::requireObject(doc, "session texture payload");
auto textures = obj.value("textures");
if (!textures.isObject()) {
qWarning() << "No textures array in response";
return false;
}
Skin skinOut;
// fill in default skin info ourselves, as this endpoint doesn't provide it
bool steve = isDefaultModelSteve(output.id);
skinOut.variant = steve ? "classic" : "slim";
skinOut.url = steve ? SKIN_URL_STEVE : SKIN_URL_ALEX;
// sadly we can't figure this out, but I don't think it really matters...
skinOut.id = "00000000-0000-0000-0000-000000000000";
Cape capeOut;
auto tObj = textures.toObject();
for (auto idx = tObj.constBegin(); idx != tObj.constEnd(); ++idx) {
if (idx->isObject()) {
if (idx.key() == "SKIN") {
auto skin = idx->toObject();
if (!getString(skin.value("url"), skinOut.url)) {
qWarning() << "Skin url is not a string";
return false;
}
auto maybeMeta = skin.find("metadata");
if (maybeMeta != skin.end() && maybeMeta->isObject()) {
auto meta = maybeMeta->toObject();
// might not be present
getString(meta.value("model"), skinOut.variant);
}
}
else if (idx.key() == "CAPE") {
auto cape = idx->toObject();
if (!getString(cape.value("url"), capeOut.url)) {
qWarning() << "Cape url is not a string";
return false;
}
// we don't know the cape ID as it is not returned from the session server
// so just fake it - changing capes is probably locked anyway :(
capeOut.alias = "cape";
}
}
}
output.skin = skinOut;
if (capeOut.alias == "cape") {
output.capes = QMap<QString, Cape>({{capeOut.alias, capeOut}});
output.currentCape = capeOut.alias;
}
output.validity = Katabasis::Validity::Certain;
return true;
}
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) { bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
qDebug() << "Parsing Minecraft entitlements..."; qDebug() << "Parsing Minecraft entitlements...";
#ifndef NDEBUG #ifndef NDEBUG

View File

@ -14,6 +14,7 @@ namespace Parsers
bool parseMojangResponse(QByteArray &data, Katabasis::Token &output); bool parseMojangResponse(QByteArray &data, Katabasis::Token &output);
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output); bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
bool parseMinecraftProfileMojang(QByteArray &data, MinecraftProfile &output);
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output); bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
bool parseRolloutResponse(QByteArray &data, bool& result); bool parseRolloutResponse(QByteArray &data, bool& result);
} }

View File

@ -209,6 +209,28 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
m_data->yggdrasilToken.validity = Katabasis::Validity::Certain; m_data->yggdrasilToken.validity = Katabasis::Validity::Certain;
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
// Get UUID here since we need it for later
auto profile = responseData.value("selectedProfile");
if (!profile.isObject()) {
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a selected profile."));
return;
}
auto profileObj = profile.toObject();
for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
if (i.key() == "name" && i.value().isString()) {
m_data->minecraftProfile.name = i->toString();
}
else if (i.key() == "id" && i.value().isString()) {
m_data->minecraftProfile.id = i->toString();
}
}
if (m_data->minecraftProfile.id.isEmpty()) {
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile."));
return;
}
// We've made it through the minefield of possible errors. Return true to indicate that // We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded. // we've succeeded.
qDebug() << "Finished reading authentication response."; qDebug() << "Finished reading authentication response.";

View File

@ -1,7 +1,7 @@
#include "Mojang.h" #include "Mojang.h"
#include "minecraft/auth/steps/YggdrasilStep.h" #include "minecraft/auth/steps/YggdrasilStep.h"
#include "minecraft/auth/steps/MinecraftProfileStep.h" #include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
#include "minecraft/auth/steps/MigrationEligibilityStep.h" #include "minecraft/auth/steps/MigrationEligibilityStep.h"
#include "minecraft/auth/steps/GetSkinStep.h" #include "minecraft/auth/steps/GetSkinStep.h"
@ -10,7 +10,7 @@ MojangRefresh::MojangRefresh(
QObject *parent QObject *parent
) : AuthFlow(data, parent) { ) : AuthFlow(data, parent) {
m_steps.append(new YggdrasilStep(m_data, QString())); m_steps.append(new YggdrasilStep(m_data, QString()));
m_steps.append(new MinecraftProfileStep(m_data)); m_steps.append(new MinecraftProfileStepMojang(m_data));
m_steps.append(new MigrationEligibilityStep(m_data)); m_steps.append(new MigrationEligibilityStep(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(new GetSkinStep(m_data));
} }
@ -21,7 +21,7 @@ MojangLogin::MojangLogin(
QObject *parent QObject *parent
): AuthFlow(data, parent), m_password(password) { ): AuthFlow(data, parent), m_password(password) {
m_steps.append(new YggdrasilStep(m_data, m_password)); m_steps.append(new YggdrasilStep(m_data, m_password));
m_steps.append(new MinecraftProfileStep(m_data)); m_steps.append(new MinecraftProfileStepMojang(m_data));
m_steps.append(new MigrationEligibilityStep(m_data)); m_steps.append(new MigrationEligibilityStep(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(new GetSkinStep(m_data));
} }

View File

@ -50,7 +50,9 @@ void LauncherLoginStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG
qDebug() << data; qDebug() << data;
#endif
if (error != QNetworkReply::NoError) { if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error; qWarning() << "Reply error:" << error;
#ifndef NDEBUG #ifndef NDEBUG

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MSAStep.h" #include "MSAStep.h"
#include <QNetworkRequest> #include <QNetworkRequest>
@ -12,9 +47,10 @@ using OAuth2 = Katabasis::DeviceFlow;
using Activity = Katabasis::Activity; using Activity = Katabasis::Activity;
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) { MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) {
m_clientId = APPLICATION->getMSAClientID();
OAuth2::Options opts; OAuth2::Options opts;
opts.scope = "XboxLive.signin offline_access"; opts.scope = "XboxLive.signin offline_access";
opts.clientIdentifier = APPLICATION->getMSAClientID(); opts.clientIdentifier = m_clientId;
opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"; opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
@ -48,6 +84,10 @@ void MSAStep::rehydrate() {
void MSAStep::perform() { void MSAStep::perform() {
switch(m_action) { switch(m_action) {
case Refresh: { case Refresh: {
if (m_data->msaClientID != m_clientId) {
emit hideVerificationUriAndCode();
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed."));
}
m_oauth2->refresh(); m_oauth2->refresh();
return; return;
} }
@ -57,6 +97,7 @@ void MSAStep::perform() {
m_oauth2->setExtraRequestParams(extraOpts); m_oauth2->setExtraRequestParams(extraOpts);
*m_data = AccountData(); *m_data = AccountData();
m_data->msaClientID = m_clientId;
m_oauth2->login(); m_oauth2->login();
return; return;
} }

View File

@ -1,3 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QObject> #include <QObject>
@ -29,4 +63,5 @@ private slots:
private: private:
Katabasis::DeviceFlow *m_oauth2 = nullptr; Katabasis::DeviceFlow *m_oauth2 = nullptr;
Action m_action; Action m_action;
QString m_clientId;
}; };

View File

@ -0,0 +1,94 @@
#include "MinecraftProfileStepMojang.h"
#include <QNetworkRequest>
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
}
MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
QString MinecraftProfileStepMojang::describe() {
return tr("Fetching the Minecraft profile.");
}
void MinecraftProfileStepMojang::perform() {
if (m_data->minecraftProfile.id.isEmpty()) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
return;
}
// use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
QNetworkRequest req = QNetworkRequest(url);
AuthRequest *request = new AuthRequest(this);
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
request->get(req);
}
void MinecraftProfileStepMojang::rehydrate() {
// NOOP, for now. We only save bools and there's nothing to check.
}
void MinecraftProfileStepMojang::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater();
#ifndef NDEBUG
qDebug() << data;
#endif
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) {
m_data->minecraftEntitlement.canPlayMinecraft = false;
m_data->minecraftEntitlement.ownsMinecraft = false;
}
m_data->minecraftProfile = MinecraftProfile();
emit finished(
AccountTaskState::STATE_SUCCEEDED,
tr("Account has no Minecraft profile.")
);
return;
}
if (error != QNetworkReply::NoError) {
qWarning() << "Error getting profile:";
qWarning() << " HTTP Status: " << requestor->httpStatus_;
qWarning() << " Internal error no.: " << error;
qWarning() << " Error string: " << requestor->errorString_;
qWarning() << " Response:";
qWarning() << QString::fromUtf8(data);
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed.")
);
return;
}
if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
m_data->minecraftProfile = MinecraftProfile();
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile response could not be parsed")
);
return;
}
if(m_data->type == AccountType::Mojang) {
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
}
emit finished(
AccountTaskState::STATE_WORKING,
tr("Minecraft Java profile acquisition succeeded.")
);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <QObject>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class MinecraftProfileStepMojang : public AuthStep {
Q_OBJECT
public:
explicit MinecraftProfileStepMojang(AccountData *data);
virtual ~MinecraftProfileStepMojang() noexcept;
void perform() override;
void rehydrate() override;
QString describe() override;
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -65,7 +65,7 @@ void XboxAuthorizationStep::onRequestDone(
if(!processSTSError(error, data, headers)) { if(!processSTSError(error, data, headers)) {
emit finished( emit finished(
AccountTaskState::STATE_FAILED_SOFT, AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get authorization for %1 services. Error %1.").arg(m_authorizationKind, error) tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error)
); );
} }
return; return;

View File

@ -88,7 +88,7 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state)
case LoggedProcess::FailedToStart: case LoggedProcess::FailedToStart:
{ {
//: Error message displayed if instance can't start //: Error message displayed if instance can't start
const char *reason = QT_TR_NOOP("Could not launch minecraft!"); const char *reason = QT_TR_NOOP("Could not launch Minecraft!");
emit logLine(reason, MessageLevel::Fatal); emit logLine(reason, MessageLevel::Fatal);
emitFailed(tr(reason)); emitFailed(tr(reason));
return; return;

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