From c9471d083b18de96c3f5095ac64367a7724bf2db Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 1 Feb 2025 20:25:57 +0200 Subject: [PATCH 001/181] change java on modpack update Signed-off-by: Trial97 --- launcher/BaseInstance.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index ccfd0b847..6c1c1a574 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -44,6 +44,7 @@ #include #include +#include "Application.h" #include "settings/INISettingsObject.h" #include "settings/OverrideSetting.h" #include "settings/Setting.h" @@ -174,6 +175,12 @@ void BaseInstance::copyManagedPack(BaseInstance& other) m_settings->set("ManagedPackName", other.getManagedPackName()); m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID()); m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName()); + + if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_settings->get("AutomaticJava").toBool() && + m_settings->get("OverrideJavaLocation").toBool()) { + m_settings->set("OverrideJavaLocation", false); + m_settings->set("JavaPath", ""); + } } int BaseInstance::getConsoleMaxLines() const From 58100328613bc7e1280ce0981f6a3ce66a34a2be Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 12 Feb 2025 19:04:48 +0200 Subject: [PATCH 002/181] Fix icon removal in icon picker Signed-off-by: Trial97 --- launcher/icons/IconList.cpp | 6 ++++-- launcher/ui/dialogs/IconPickerDialog.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index f4022e0fb..bdf57acaa 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -166,7 +166,8 @@ void IconList::directoryChanged(const QString& path) for (const MMCIcon& it : m_icons) { if (!it.has(IconType::FileBased)) continue; - currentSet.insert(it.m_images[IconType::FileBased].filename); + QFileInfo icon(it.getFilePath()); + currentSet.insert(icon.absoluteFilePath()); } QSet toRemove = currentSet - newSet; QSet toAdd = newSet - currentSet; @@ -174,7 +175,8 @@ void IconList::directoryChanged(const QString& path) for (const QString& removedPath : toRemove) { qDebug() << "Removing icon " << removedPath; QFileInfo removedFile(removedPath); - QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath()); + QString relativePath = m_dir.relativeFilePath(removedFile.absoluteFilePath()); + QString key = QFileInfo(relativePath).completeBaseName(); int idx = getIconIndex(key); if (idx == -1) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index b6e928a3d..8f53995f9 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -58,7 +58,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui contentsWidget->setTextElideMode(Qt::ElideRight); contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - contentsWidget->setItemDelegate(new ListViewDelegate()); + contentsWidget->setItemDelegate(new ListViewDelegate(contentsWidget)); // contentsWidget->setAcceptDrops(true); contentsWidget->setDropIndicatorShown(true); From 998bc660fa97f4527443dd826eede5e8eab23651 Mon Sep 17 00:00:00 2001 From: sticks Date: Fri, 28 Feb 2025 15:05:30 -0600 Subject: [PATCH 003/181] feat: add updater dialogues Signed-off-by: sticks --- .../ui/pages/instance/ManagedPackPage.cpp | 38 ++++++++++++------- launcher/ui/pages/instance/ManagedPackPage.h | 2 + 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index a909d10d1..f44e211f6 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -245,7 +245,6 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin } // MODRINTH - void ModrinthManagedPackPage::parseManagedPack() { qDebug() << "Parsing Modrinth pack"; @@ -338,6 +337,24 @@ void ModrinthManagedPackPage::suggestVersion() ManagedPackPage::suggestVersion(); } +/// @brief Called when the update task has completed. +/// Internally handles the closing of the instance window if the update was successful and shows a message box. +/// @param did_succeed Whether the update task was successful. +void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const +{ + // Close the window if the update was successful + if (m_instance_window && did_succeed) { + m_instance_window->close(); + CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) + ->show(); + } else if (!did_succeed) { + CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. -- Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) + ->show(); + qWarning() << "onUpdateTaskCompleted: unknown state encountered: did_succeed=" << did_succeed << " m_instance_window=" << m_instance_window; + } + +} + void ModrinthManagedPackPage::update() { auto index = ui->versionsComboBox->currentIndex(); @@ -363,10 +380,9 @@ void ModrinthManagedPackPage::update() extracted->setIcon(m_inst->iconKey()); extracted->setConfirmUpdate(false); + // Run our task then handle the result auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } void ModrinthManagedPackPage::updateFromFile() @@ -386,14 +402,12 @@ void ModrinthManagedPackPage::updateFromFile() extracted->setIcon(m_inst->iconKey()); extracted->setConfirmUpdate(false); + // Run our task then handle the result auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } // FLAME - FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent) : ManagedPackPage(inst, instance_window, parent) { @@ -531,9 +545,7 @@ void FlameManagedPackPage::update() extracted->setConfirmUpdate(false); auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } void FlameManagedPackPage::updateFromFile() @@ -555,8 +567,6 @@ void FlameManagedPackPage::updateFromFile() extracted->setConfirmUpdate(false); auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } #include "ManagedPackPage.moc" diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index c44f77070..e8d304c6b 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -94,6 +94,8 @@ class ManagedPackPage : public QWidget, public BasePage { BaseInstance* m_inst; bool m_loaded = false; + + void onUpdateTaskCompleted(bool did_succeed) const; }; /** Simple page for when we aren't a managed pack. */ From ea5458ed2253a4e8a9ef50d076fe72c57d2cb669 Mon Sep 17 00:00:00 2001 From: Sticks Date: Fri, 28 Feb 2025 21:06:34 -0600 Subject: [PATCH 004/181] fix: Grammar correction in failed message for update dialogues Co-authored-by: TheKodeToad Signed-off-by: Sticks --- launcher/ui/pages/instance/ManagedPackPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index f44e211f6..ce68c24d3 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -348,7 +348,7 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) ->show(); } else if (!did_succeed) { - CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. -- Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) + CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) ->show(); qWarning() << "onUpdateTaskCompleted: unknown state encountered: did_succeed=" << did_succeed << " m_instance_window=" << m_instance_window; } From d4e1851e6779d99e3ffde79c7e4e8edb568415ce Mon Sep 17 00:00:00 2001 From: Sticks Date: Tue, 4 Mar 2025 22:20:40 -0600 Subject: [PATCH 005/181] fix: remove qWarning for unknown state Co-authored-by: TheKodeToad Signed-off-by: Sticks --- launcher/ui/pages/instance/ManagedPackPage.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index ce68c24d3..0fccd1d33 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -343,14 +343,15 @@ void ModrinthManagedPackPage::suggestVersion() void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const { // Close the window if the update was successful - if (m_instance_window && did_succeed) { - m_instance_window->close(); + if (did_succeed) { + if (m_instance_window != nullptr) + m_instance_window->close(); + CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) ->show(); - } else if (!did_succeed) { + } else { CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) ->show(); - qWarning() << "onUpdateTaskCompleted: unknown state encountered: did_succeed=" << did_succeed << " m_instance_window=" << m_instance_window; } } From a65ced1598751d5838859a7c9a7b15b85ab310c2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 11 Mar 2025 17:01:58 -0700 Subject: [PATCH 006/181] ci: add a workflow to detect and check dependencies for blocked pull requests Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 236 ++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 .github/workflows/blocked_prs.yml diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml new file mode 100644 index 000000000..8f060dbb6 --- /dev/null +++ b/.github/workflows/blocked_prs.yml @@ -0,0 +1,236 @@ +name: Blocked/Stacked Pull Requests Automation + +on: + pull_request_target: + types: + - opened + - edited + workflow_dispatch: + inputs: + pr_id: + description: Local Pull Request number to work on + required: true + type: number + +jobs: + block_status: + name: Check Blocked Status + runs-on: ubuntu-latest + steps: + - name: Setup From Pull Request Vent + if: ${{ github.event_name != 'workflow_dispatch' }} + id: pr_event_setup + env: + REPO_L: ${{ github.event.pull_request.base.repo.name }} + OWNER_L: ${{ github.event.pull_request.base.repo.owner.login }} + REPO_URL_L: $ {{ github.event.pull_request.base.repo.html_url }} + PR_HEAD_SHA_L: ${{ github.event.pull_request.head.sha }} + PR_NUMBER_L: ${{ github.event.pull_request.number }} + PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} + PR_BODY_L: ${{ github.event.pull_request.body }} + PR_LABLES_L: ${{ github.event.pull_request.labels }} + run: | + # setup env for the rest of the workflow + { + echo "REPO=$REPO_L" + echo "OWNER=$OWNER_L" + echo "REPO_URL=$REPO_URL_L" + echo "PR_NUMBER=$PR_NUMBER_L" + echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" + echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" + echo "PR_BODY=$PR_BODY_L" + echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + } >> "$GITHUB_ENV" + + - name: Setup From Dispatch Event + if: ${{ github.event_name == 'workflow_dispatch' }} + id: dispatch_event_setup + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OWNER_REPO_L: ${{ github.repository }} + OWNER_L: ${{ github.repository_owner }} + REPO_URL_L: $ {{ github.repositoryUrl }} + PR_NUMBER_L: ${{ inputs.pr_id }} + run: | + # setup env for the rest of the workflow + owner_prefix="$OWNER_L/" + REPO_L="${OWNER_REPO_L#"$owner_prefix"}" + PR_L=$( + gh api \ + -H "Accept: application/vnd.github.raw+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER_L/$REPO_L/pulls/$PR_NUMBER_L" + ) + PR_HEAD_SHA_L=$(jq -r '.head.sha' <<< "$PR_L") + PR_HEAD_LABEL_L=$(jq -r '.head.label' <<< "$PR_L") + PR_BODY_L=$(jq -r '.body' <<< "$PR_L") + PR_LABELS_L=$(jq '.labels' <<< "$PR_L") + { + echo "REPO=$REPO_L" + echo "OWNER=$OWNER_L" + echo "REPO_URL=$REPO_URL_L" + echo "PR_NUMBER=$PR_NUMBER_L" + echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" + echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" + echo "PR_BODY=$PR_BODY_L" + echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + } >> "$GITHUB_ENV" + + + - name: Find Blocked/Stacked PRs in body + id: pr_ids + run: | + PRS=$( + jq ' + . as $body + | ( + $body | scan("blocked (?by)|(?on):? #(?[0-9]+)") + | map({ + "type": "Blocked on", + "number": ( . | tonumber ) + }) + ) as $bprs + | ( + $body | scan("stacked on:? #(?[0-9]+)") + | map({ + "type": "Stacked on", + "number": ( . | tonumber ) + }) + ) as $sprs + | ($bprs + $sprs) as $prs + | { + "blocking": $prs, + "numBlocking": ( $prs | length), + } + ' <<< "$PR_BODY" + ) + echo "prs=$PRS" >> "$GITHUB_OUPUT" + + - name: Collect Blocked PR Data + id: blocked_data + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BLOCKED_PR_DATA=$( + while read -r PR ; do + gh api \ + -H "Accept: application/vnd.github.raw+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$PR")" \ + | jq --arg type "$(jq -r '.type' <<< "$PR")" \ + ' + . | { + "type": $type, + "number": .number, + "merged": .merged, + "labels": (reduce .labels[].name as $l ([]; . + [$l])), + "basePrUrl": .html_url, + "baseRepoName": .head.repo.name, + "baseRepoOwner": .head.repo.owner.login, + "baseRepoUrl": .head.repo.html_url, + "baseSha": .head.sha, + "baseRefName": .head.ref, + } + ' + done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -s + ) + echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUPUT" + echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" + + - name: Apply Blocked Label if Missing + id: label_blocked + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels" \ + -f "labels[]=blocked" + + - name: Remove 'blocked' Label if All Dependencies Are Merged + id: unlabel_blocked + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api \ + --method DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels/blocked" + + - name: Apply 'blocking' Label to Dependencies if Missing + id: label_blocking + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # label pr dependencies with 'blocking' if not already + while read -r PR_DATA ; do + if jq -e 'all(.labels[]; . != "blocking")' <<< "$PR_DATA" > /dev/null ; then + PR=$(jq -r '.number' <<< "$PR_DATA") + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/issues/$PR/labels" \ + -f "labels[]=blocking" + fi + done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + + - name: Apply Blocking PR Status Check + id: blocked_check + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # create commit Status, overwrites previous identical context + while read -r PR_DATA ; do + DESC=$( + jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$PR_DATA" + ) + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${OWNER}/${REPO}/statuses/${PR_HEAD_SHA}" \ + -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$PR_DATA")" \ + -f "target_url=$(jq -r '.basePrUrl' <<< "$PR_DATA" )" \ + -f "description=$DESC" \ + -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$PR_DATA")" + done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + + - name: Context Comment + id: blocked_comment + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + run: | + COMMENT_PATH="$(pwd)/temp_comment_file.txt" + touch "$COMMENT_PATH" + echo "" > "$COMMENT_PATH" + while read -r PR_DATA ; do + BASE_PR=$(jq '.number' <<< "$PR_DATA") + BASE_REF_NAME=$(jq '.baseRefName' <<< "$PR_DATA") + COMPARE_URL="$REPO_URL/compare/$BASE_REF_NAME...$PR_HEAD_LABEL" + STATUS=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$PR_DATA") + TYPE=$(jq -r '.type' <<< "$PR_DATA") + echo " - $TYPE #$BASE_PR $STATUS [(compare)]($COMPARE_URL)" >> "$COMMENT_PATH" + done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" + + - name: 💬 PR Comment + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + uses: spicyparrot/pr-comment-action@v1.0.0 + with: + comment: "### PR Dependencies :pushpin:" + comment_path: ${{ steps.blocked_comment.outputs.file_path }} + comment_id: "block_pr_dependencies" From be1ce8dd9d0612559560a03c4e70f667430de939 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 11 Mar 2025 20:19:03 -0700 Subject: [PATCH 007/181] ci: add workflow to trigger refresh of dependants when a blocking pr is merged Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 40 ++++++++----- .github/workflows/merge_blocking_pr.yml | 80 +++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/merge_blocking_pr.yml diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 8f060dbb6..ecafc65a8 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -13,12 +13,19 @@ on: type: number jobs: - block_status: + blocked_status: name: Check Blocked Status runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + statuses: write + checks: write + steps: - name: Setup From Pull Request Vent - if: ${{ github.event_name != 'workflow_dispatch' }} + if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: REPO_L: ${{ github.event.pull_request.base.repo.name }} @@ -43,7 +50,7 @@ jobs: } >> "$GITHUB_ENV" - name: Setup From Dispatch Event - if: ${{ github.event_name == 'workflow_dispatch' }} + if: github.event_name == 'workflow_dispatch' id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -57,13 +64,13 @@ jobs: REPO_L="${OWNER_REPO_L#"$owner_prefix"}" PR_L=$( gh api \ - -H "Accept: application/vnd.github.raw+json" \ + -H "Accept: application/vnd.github.text+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER_L/$REPO_L/pulls/$PR_NUMBER_L" ) PR_HEAD_SHA_L=$(jq -r '.head.sha' <<< "$PR_L") PR_HEAD_LABEL_L=$(jq -r '.head.label' <<< "$PR_L") - PR_BODY_L=$(jq -r '.body' <<< "$PR_L") + PR_BODY_L=$(jq -r '.body_text' <<< "$PR_L") PR_LABELS_L=$(jq '.labels' <<< "$PR_L") { echo "REPO=$REPO_L" @@ -84,7 +91,7 @@ jobs: jq ' . as $body | ( - $body | scan("blocked (?by)|(?on):? #(?[0-9]+)") + $body | scan("blocked (?(?by)|(?on)):? #(?[0-9]+)") | map({ "type": "Blocked on", "number": ( . | tonumber ) @@ -104,18 +111,18 @@ jobs: } ' <<< "$PR_BODY" ) - echo "prs=$PRS" >> "$GITHUB_OUPUT" + echo "prs=$PRS" >> "$GITHUB_OUTPUT" - name: Collect Blocked PR Data id: blocked_data - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | BLOCKED_PR_DATA=$( while read -r PR ; do gh api \ - -H "Accept: application/vnd.github.raw+json" \ + -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$PR")" \ | jq --arg type "$(jq -r '.type' <<< "$PR")" \ @@ -135,12 +142,12 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -s ) - echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUPUT" + echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" - name: Apply Blocked Label if Missing id: label_blocked - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -154,7 +161,7 @@ jobs: - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -167,7 +174,7 @@ jobs: - name: Apply 'blocking' Label to Dependencies if Missing id: label_blocking - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -187,7 +194,7 @@ jobs: - name: Apply Blocking PR Status Check id: blocked_check - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -210,7 +217,7 @@ jobs: - name: Context Comment id: blocked_comment - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" @@ -227,10 +234,11 @@ jobs: echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" - name: 💬 PR Comment - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true uses: spicyparrot/pr-comment-action@v1.0.0 with: comment: "### PR Dependencies :pushpin:" comment_path: ${{ steps.blocked_comment.outputs.file_path }} comment_id: "block_pr_dependencies" + diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml new file mode 100644 index 000000000..fd52da295 --- /dev/null +++ b/.github/workflows/merge_blocking_pr.yml @@ -0,0 +1,80 @@ +name: Merged Blocking Pull Request Automation + +on: + pull_request: + types: + - closed + +jobs: + update_blocked_status: + name: Update Blocked Status + runs-on: ubuntu-latest + + if: github.event.pull_request.merged == true + + permissions: + issues: write + pull-requests: write + statuses: write + checks: write + + steps: + - name: Gather Dependent PRs + id: gather_deps + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + run: | + owner="${{ github.repository_owner }}" + owner_repo="${{ github.repository }}" + repo="${owner_repo#"${owner}/"}" + blocked_prs=$( + gh api graphql \ + -f repo="$repo" \ + -f owner="$owner" \ + -f query=' + query($repo: String!, $owner: String!, $endCursor: String) { + repository(name: $repo, owner: $owner) { + pullRequests(first: 100, after: $endCursor, states: [OPEN], labels: ["blocked"]) { + nodes { + number + bodyText + merged + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + ' \ + --paginate \ + --slurp \ + | jq --argjson pr "${{ github.event.pull_request.number }}" ' + [.[].data.repository.pullRequests.nodes[]] | .[] | select( + .bodyText | + scan("(?:blocked (?:by|on)|stacked on):? #(?[0-9]+)") | + map(tonumber) | + any(.[]; . == $pr) + ) + ' + ) + echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" + echo "numdeps='$(jq -r '. | length' <<< "$blocked_prs")" + + - name: Trigger Blocked PP Workflows for Dependants + if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + while read -r pr ; do + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${{ github.repository }}/actions/workflows/blocked_prs.yml/dispatches" \ + -f "ref=${{ github.ref_name }}" \ + -f "inputs[pr_id]=$pr" + done < <(jq -c '.[].number' <<< "${{steps.gather_deps.outputs.deps}}") + From cfc3c767791d5c2da170fd8ccd1199e1c8028d13 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:01:20 -0700 Subject: [PATCH 008/181] ci: fix labels detected as sequence Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index ecafc65a8..43915776d 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -24,7 +24,7 @@ jobs: checks: write steps: - - name: Setup From Pull Request Vent + - name: Setup From Pull Request Event if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: @@ -35,7 +35,7 @@ jobs: PR_NUMBER_L: ${{ github.event.pull_request.number }} PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} PR_BODY_L: ${{ github.event.pull_request.body }} - PR_LABLES_L: ${{ github.event.pull_request.labels }} + PR_LABLES_L: "${{ github.event.pull_request.labels }}" run: | # setup env for the rest of the workflow { From cbd1fc6f43eb4872cac250f1c074dc2c650f96a0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:26:02 -0700 Subject: [PATCH 009/181] ci: fix repo URL in blocked pr workflow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 43915776d..04ecd038e 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -30,7 +30,7 @@ jobs: env: REPO_L: ${{ github.event.pull_request.base.repo.name }} OWNER_L: ${{ github.event.pull_request.base.repo.owner.login }} - REPO_URL_L: $ {{ github.event.pull_request.base.repo.html_url }} + REPO_URL_L: ${{ github.event.pull_request.base.repo.html_url }} PR_HEAD_SHA_L: ${{ github.event.pull_request.head.sha }} PR_NUMBER_L: ${{ github.event.pull_request.number }} PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} @@ -56,7 +56,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} OWNER_REPO_L: ${{ github.repository }} OWNER_L: ${{ github.repository_owner }} - REPO_URL_L: $ {{ github.repositoryUrl }} + REPO_URL_L: https://github.com/${{ github.repository }} PR_NUMBER_L: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow From a2907dcaa356c22f6e58f82947bd1829e85abb7f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 14:05:46 -0700 Subject: [PATCH 010/181] ci: fix blocked PR workflow to preserve JSON in ENV Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 124 ++++++++++++++++-------------- 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 04ecd038e..ed07036af 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -28,25 +28,25 @@ jobs: if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: - REPO_L: ${{ github.event.pull_request.base.repo.name }} - OWNER_L: ${{ github.event.pull_request.base.repo.owner.login }} - REPO_URL_L: ${{ github.event.pull_request.base.repo.html_url }} - PR_HEAD_SHA_L: ${{ github.event.pull_request.head.sha }} - PR_NUMBER_L: ${{ github.event.pull_request.number }} - PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} - PR_BODY_L: ${{ github.event.pull_request.body }} - PR_LABLES_L: "${{ github.event.pull_request.labels }}" + PR_JSON: ${{ toJSON(github.event.pull_request) }} run: | # setup env for the rest of the workflow { - echo "REPO=$REPO_L" - echo "OWNER=$OWNER_L" - echo "REPO_URL=$REPO_URL_L" - echo "PR_NUMBER=$PR_NUMBER_L" - echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" - echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" - echo "PR_BODY=$PR_BODY_L" - echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" + echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" + echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" + echo "JOB_DATA=$(jq ' + { + "repo": .base.repo.name, + "owner": .base.repo.owner.login, + "repoUrl": .base.repo.html_url, + "prNumber": .number, + "prHeadSha": .head.sha, + "prHeadLabel": .head.label, + "prBody": .body, + "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + } + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - name: Setup From Dispatch Event @@ -54,33 +54,35 @@ jobs: id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OWNER_REPO_L: ${{ github.repository }} - OWNER_L: ${{ github.repository_owner }} - REPO_URL_L: https://github.com/${{ github.repository }} - PR_NUMBER_L: ${{ inputs.pr_id }} + OWNER_REPO: ${{ github.repository }} + OWNER: ${{ github.repository_owner }} + PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow - owner_prefix="$OWNER_L/" - REPO_L="${OWNER_REPO_L#"$owner_prefix"}" - PR_L=$( + owner_prefix="$OWNER/" + REPO="${OWNER_REPO#"$owner_prefix"}" + PR_JSON=$( gh api \ - -H "Accept: application/vnd.github.text+json" \ + -H "Accept: application/vnd.github.raw+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER_L/$REPO_L/pulls/$PR_NUMBER_L" + "/repos/$OWNER/$REPO/pulls/$PR_NUMBER" ) - PR_HEAD_SHA_L=$(jq -r '.head.sha' <<< "$PR_L") - PR_HEAD_LABEL_L=$(jq -r '.head.label' <<< "$PR_L") - PR_BODY_L=$(jq -r '.body_text' <<< "$PR_L") - PR_LABELS_L=$(jq '.labels' <<< "$PR_L") { - echo "REPO=$REPO_L" - echo "OWNER=$OWNER_L" - echo "REPO_URL=$REPO_URL_L" - echo "PR_NUMBER=$PR_NUMBER_L" - echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" - echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" - echo "PR_BODY=$PR_BODY_L" - echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" + echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" + echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" + echo "JOB_DATA=$(jq ' + { + "repo": .base.repo.name, + "owner": .base.repo.owner.login, + "repoUrl": .base.repo.html_url, + "prNumber": .number, + "prHeadSha": .head.sha, + "prHeadLabel": .head.label, + "prBody": .body, + "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + } + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" @@ -89,7 +91,7 @@ jobs: run: | PRS=$( jq ' - . as $body + .prBody as $body | ( $body | scan("blocked (?(?by)|(?on)):? #(?[0-9]+)") | map({ @@ -109,7 +111,7 @@ jobs: "blocking": $prs, "numBlocking": ( $prs | length), } - ' <<< "$PR_BODY" + ' <<< "$JOB_DATA" ) echo "prs=$PRS" >> "$GITHUB_OUTPUT" @@ -120,12 +122,12 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | BLOCKED_PR_DATA=$( - while read -r PR ; do + while read -r pr_data ; do gh api \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$PR")" \ - | jq --arg type "$(jq -r '.type' <<< "$PR")" \ + "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$pr_data")" \ + | jq --arg type "$(jq -r '.type' <<< "$pr_data")" \ ' . | { "type": $type, @@ -180,14 +182,14 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # label pr dependencies with 'blocking' if not already - while read -r PR_DATA ; do - if jq -e 'all(.labels[]; . != "blocking")' <<< "$PR_DATA" > /dev/null ; then - PR=$(jq -r '.number' <<< "$PR_DATA") + while read -r pr_data ; do + if jq -e 'all(.labels[]; . != "blocking")' <<< "$pr_data" > /dev/null ; then + pr=$(jq -r '.number' <<< "$pr_data") gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$PR/labels" \ + "/repos/$OWNER/$REPO/issues/$pr/labels" \ -f "labels[]=blocking" fi done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") @@ -199,20 +201,21 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") # create commit Status, overwrites previous identical context - while read -r PR_DATA ; do + while read -r pr_data ; do DESC=$( - jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$PR_DATA" + jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$pr_data" ) gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${OWNER}/${REPO}/statuses/${PR_HEAD_SHA}" \ - -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$PR_DATA")" \ - -f "target_url=$(jq -r '.basePrUrl' <<< "$PR_DATA" )" \ + "/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \ + -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \ + -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \ -f "description=$DESC" \ - -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$PR_DATA")" + -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$pr_data")" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") - name: Context Comment @@ -223,13 +226,16 @@ jobs: COMMENT_PATH="$(pwd)/temp_comment_file.txt" touch "$COMMENT_PATH" echo "" > "$COMMENT_PATH" - while read -r PR_DATA ; do - BASE_PR=$(jq '.number' <<< "$PR_DATA") - BASE_REF_NAME=$(jq '.baseRefName' <<< "$PR_DATA") - COMPARE_URL="$REPO_URL/compare/$BASE_REF_NAME...$PR_HEAD_LABEL" - STATUS=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$PR_DATA") - TYPE=$(jq -r '.type' <<< "$PR_DATA") - echo " - $TYPE #$BASE_PR $STATUS [(compare)]($COMPARE_URL)" >> "$COMMENT_PATH" + pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA") + while read -r pr_data ; do + base_pr=$(jq -r '.number' <<< "$pr_data") + base_ref_name=$(jq -r '.baseRefName' <<< "$pr_data") + base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data") + base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data") + compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label" + status=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") + type=$(jq -r '.type' <<< "$pr_data") + echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" From a6a172a1365e94c791ef457e47c7e9e34d693263 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:21:08 -0700 Subject: [PATCH 011/181] ci: ensure block pr scan always returns valid json Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index ed07036af..d6239eedb 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -44,7 +44,7 @@ jobs: "prHeadSha": .head.sha, "prHeadLabel": .head.label, "prBody": .body, - "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" @@ -80,7 +80,7 @@ jobs: "prHeadSha": .head.sha, "prHeadLabel": .head.label, "prBody": .body, - "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" @@ -93,18 +93,24 @@ jobs: jq ' .prBody as $body | ( - $body | scan("blocked (?(?by)|(?on)):? #(?[0-9]+)") - | map({ - "type": "Blocked on", - "number": ( . | tonumber ) - }) + $body | + reduce ( + . | scan("blocked (?:by|on):? #([0-9]+)") + | map({ + "type": "Blocked on", + "number": ( . | tonumber ) + }) + ) as $i ([]; . + [$i]) ) as $bprs | ( - $body | scan("stacked on:? #(?[0-9]+)") - | map({ - "type": "Stacked on", - "number": ( . | tonumber ) - }) + $body | + reduce ( + . | scan("stacked on:? #([0-9]+)") + | map({ + "type": "Stacked on", + "number": ( . | tonumber ) + }) + ) as $i ([]; . + [$i]) ) as $sprs | ($bprs + $sprs) as $prs | { From 0bbf529afb09dfcb9b08facc1b39aa4fe46312d3 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:36:34 -0700 Subject: [PATCH 012/181] ci(blocked_prs): quote json in env file Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index d6239eedb..bd8be64f3 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -28,14 +28,14 @@ jobs: if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: - PR_JSON: ${{ toJSON(github.event.pull_request) }} + PR_JSON: "${{ toJSON(github.event.pull_request) }}" run: | # setup env for the rest of the workflow { echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=$(jq ' + echo "JOB_DATA=\"$(jq ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -46,8 +46,9 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")" + ' <<< "$PR_JSON")\"" } >> "$GITHUB_ENV" + cat $GITHUB_ENV - name: Setup From Dispatch Event if: github.event_name == 'workflow_dispatch' @@ -71,7 +72,7 @@ jobs: echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=$(jq ' + echo "JOB_DATA=\"$(jq ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -82,8 +83,9 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")" + ' <<< "$PR_JSON")\"" } >> "$GITHUB_ENV" + cat $GITHUB_ENV - name: Find Blocked/Stacked PRs in body From eb11cde0f4b2966e4f3b632038f39c91800d3493 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:50:43 -0700 Subject: [PATCH 013/181] ci(blocked_prs): use compact jq output when outputting to ENV or step ouput Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 16 ++++++++-------- .github/workflows/merge_blocking_pr.yml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index bd8be64f3..6afe6e485 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -35,7 +35,7 @@ jobs: echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=\"$(jq ' + echo "JOB_DATA=$(jq -c ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -46,7 +46,7 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")\"" + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" cat $GITHUB_ENV @@ -72,7 +72,7 @@ jobs: echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=\"$(jq ' + echo "JOB_DATA=$(jq -c ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -83,7 +83,7 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")\"" + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" cat $GITHUB_ENV @@ -92,7 +92,7 @@ jobs: id: pr_ids run: | PRS=$( - jq ' + jq -c ' .prBody as $body | ( $body | @@ -135,7 +135,7 @@ jobs: -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$pr_data")" \ - | jq --arg type "$(jq -r '.type' <<< "$pr_data")" \ + | jq -c --arg type "$(jq -r '.type' <<< "$pr_data")" \ ' . | { "type": $type, @@ -150,7 +150,7 @@ jobs: "baseRefName": .head.ref, } ' - done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -s + done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -c -s ) echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" @@ -241,7 +241,7 @@ jobs: base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data") base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data") compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label" - status=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") + status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") type=$(jq -r '.type' <<< "$pr_data") echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index fd52da295..3123c83b3 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -51,7 +51,7 @@ jobs: ' \ --paginate \ --slurp \ - | jq --argjson pr "${{ github.event.pull_request.number }}" ' + | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' [.[].data.repository.pullRequests.nodes[]] | .[] | select( .bodyText | scan("(?:blocked (?:by|on)|stacked on):? #(?[0-9]+)") | From 14f042540c3b38c3463737a85676d9f6f69c6055 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:11:21 -0700 Subject: [PATCH 014/181] ci(blocking_pr): pass json expressions as env not direct. use internal compost comment action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 152 ++++++++++++++++++++++ .github/workflows/blocked_prs.yml | 31 +++-- .github/workflows/merge_blocking_pr.yml | 3 +- 3 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 .github/actions/create-comment/action.yml diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml new file mode 100644 index 000000000..5ba4bc5c9 --- /dev/null +++ b/.github/actions/create-comment/action.yml @@ -0,0 +1,152 @@ +name: Create Issue Comment +description: Create or updste an issue comment +inputs: + comment: + description: Comment Text + required: true + comment_path: + description: "Path to txt file to be parsed" + required: false + comment_id: + description: "Unique identifier for deduplicating comments" + default: "Create Issue Action" + required: false + issue_number: + description: Local Pull Request/Issue number to work on + required: true + gh_token: + description: gh api access token to use + required: true + repository: + description: the OWNER/REPOSITORY to operate on + required: true + +runs: + using: "composite" + steps: + - name: Generate Comment Text + shell: bash + env: + COMMENT_ID: ${{ inputs.comment_id }} + COMMENT_TEXT: ${{ inputs.comment }} + COMMENT_FILE: ${{ inputs.comment_path }} + run: | + comment_body="${COMMENT_TEXT}" + if [ -f "$COMMENT_FILE" ] ; then + echo "Reading comment file from ${COMMENT_FILE}" + comment_body="${comment_body}$(cat "$COMMENT_FILE")" + fi + echo "COMMENT_BODY=$comment_body" >> "$GITHUB_ENV" + + - name: Get Existing Comment Id + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + ISSUE_NUMBER: ${{ inputs.issue_number }} + REPOSITORY: ${{ inputs.repository }} + COMMENT_ID: ${{ inputs.comment_id }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + data=$( + gh api graphql \ + --paginate \ + -f owner="$owner" \ + -f repo="$repo" \ + -F issue="$ISSUE_NUMBER" \ + -f query=' + query($repo: String!, $owner: String!, $issue: Int!, $endCursor: String) { + repository(name: $repo, owner: $owner) { + issueOrPullRequest(number: $issue) { + ... on Issue { + id + number + comments(first: 100, after: $endCursor) { + nodes { + id + body + } + pageInfo { + hasNextPage + endCursor + } + } + } + ... on PullRequest { + id + number + comments(first: 100, after: $endCursor) { + nodes { + id + body + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + } + } + ' \ + --jq '' | jq -c --arg comment_id "" ' + .[0].data.repository.issueOrPullRequest.id as $id | + [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | + [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | + if ($id_comments | length) > 0 then + { "issueId": $id, "commentId": $id_comments[0].id } + else + { "issueId": $id, "commentId": "" } + end + ' + ) + echo "ISSUE_NODE_ID=$(jq -r '.issueId' <<< "$data")" >> "$GITHUB_ENV" + echo "COMMENT_NODE_ID=$(jq -r '.commentId' <<< "$data")" >> "$GITHUB_ENV" + + - name: Edit Existing Comment + if: env.COMMENT_NODE_ID != '' + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + run: | + gh api graphql \ + -f comment_id="$COMMENT_NODE_ID" \ + -f comment_body="$COMMENT_BODY" \ + -f query=' + mutation($comment_id: ID!, $comment_body: String!) { + updateIssueComment(input: { + id: $comment_id, + body: $comment_body, + }) { + issueComment { + lastEditedAt + } + } + } + ' + + - name: Create Comment + if: env.COMMENT_NODE_ID == '' + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + run: | + gh api graphql \ + -f issue_id="$ISSUE_NODE_ID" \ + -f comment_body="$COMMENT_BODY" \ + -f query=' + mutation ($issue_id: ID!, $comment_body: String!) { + addComment(input: { subjectId: $issue_id, body: $comment_body }) { + commentEdge { + node { + id + } + } + } + } + ' + + + + diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 6afe6e485..d12f139bc 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -55,13 +55,12 @@ jobs: id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OWNER_REPO: ${{ github.repository }} - OWNER: ${{ github.repository_owner }} + REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow - owner_prefix="$OWNER/" - REPO="${OWNER_REPO#"$owner_prefix"}" + OWNER=$(echo "$REPOSITORY" | cut -d '/' -f 1) + REPO=$(echo "$REPOSITORY" | cut -d '/' -f 2) PR_JSON=$( gh api \ -H "Accept: application/vnd.github.raw+json" \ @@ -124,10 +123,11 @@ jobs: echo "prs=$PRS" >> "$GITHUB_OUTPUT" - name: Collect Blocked PR Data - id: blocked_data + id: blocking_data if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }} run: | BLOCKED_PR_DATA=$( while read -r pr_data ; do @@ -150,14 +150,14 @@ jobs: "baseRefName": .head.ref, } ' - done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -c -s + done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" + echo "data=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" - name: Apply Blocked Label if Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -171,7 +171,7 @@ jobs: - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -188,6 +188,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} run: | # label pr dependencies with 'blocking' if not already while read -r pr_data ; do @@ -200,7 +201,7 @@ jobs: "/repos/$OWNER/$REPO/issues/$pr/labels" \ -f "labels[]=blocking" fi - done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Apply Blocking PR Status Check id: blocked_check @@ -208,6 +209,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} run: | pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") # create commit Status, overwrites previous identical context @@ -224,7 +226,7 @@ jobs: -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \ -f "description=$DESC" \ -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$pr_data")" - done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Context Comment id: blocked_comment @@ -250,9 +252,12 @@ jobs: - name: 💬 PR Comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - uses: spicyparrot/pr-comment-action@v1.0.0 + uses: ./.github/actions/create-comment with: comment: "### PR Dependencies :pushpin:" comment_path: ${{ steps.blocked_comment.outputs.file_path }} comment_id: "block_pr_dependencies" + issue_number: ${{ env.PR_NUMBER }} + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index 3123c83b3..8707dd95b 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -67,6 +67,7 @@ jobs: if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEPS: ${{ steps.gather_deps.outputs.deps }} run: | while read -r pr ; do gh api \ @@ -76,5 +77,5 @@ jobs: "/repos/${{ github.repository }}/actions/workflows/blocked_prs.yml/dispatches" \ -f "ref=${{ github.ref_name }}" \ -f "inputs[pr_id]=$pr" - done < <(jq -c '.[].number' <<< "${{steps.gather_deps.outputs.deps}}") + done < <(jq -c '.[].number' <<< "$DEPS") From 1c6ab1f0549e2b2d81ca19f4c83e631bb80b3677 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:28:44 -0700 Subject: [PATCH 015/181] ci(blocked_prs): unnest array of blocking prs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index d12f139bc..440fba16c 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -84,13 +84,11 @@ jobs: } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - cat $GITHUB_ENV - - name: Find Blocked/Stacked PRs in body id: pr_ids run: | - PRS=$( + prs=$( jq -c ' .prBody as $body | ( @@ -101,7 +99,7 @@ jobs: "type": "Blocked on", "number": ( . | tonumber ) }) - ) as $i ([]; . + [$i]) + ) as $i ([]; . + [$i[]]) ) as $bprs | ( $body | @@ -111,7 +109,7 @@ jobs: "type": "Stacked on", "number": ( . | tonumber ) }) - ) as $i ([]; . + [$i]) + ) as $i ([]; . + [$i[]]) ) as $sprs | ($bprs + $sprs) as $prs | { @@ -120,7 +118,7 @@ jobs: } ' <<< "$JOB_DATA" ) - echo "prs=$PRS" >> "$GITHUB_OUTPUT" + echo "prs=$prs" >> "$GITHUB_OUTPUT" - name: Collect Blocked PR Data id: blocking_data From 6fd70ad095e15b25b47a27575c6cabebfcd67a04 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:35:41 -0700 Subject: [PATCH 016/181] ci(blocked_prs): correct condition to use JOB_DATA from env Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 440fba16c..b16df71d7 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -155,7 +155,7 @@ jobs: - name: Apply Blocked Label if Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e00d93135f23d3f9dadbdc950099226832cec4bf Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:47:43 -0700 Subject: [PATCH 017/181] ci(blocked_prs): fix blocking_data refrences, add synchronize and reopend events Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index b16df71d7..b6169768c 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -4,7 +4,9 @@ on: pull_request_target: types: - opened + - reopened - edited + - synchronize workflow_dispatch: inputs: pr_id: @@ -151,7 +153,7 @@ jobs: done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) echo "data=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" >> "$GITHUB_OUTPUT" - name: Apply Blocked Label if Missing id: label_blocked @@ -186,7 +188,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | # label pr dependencies with 'blocking' if not already while read -r pr_data ; do @@ -207,7 +209,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") # create commit Status, overwrites previous identical context From 79283fd74446ba096004e66d900cf17913163318 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:57:24 -0700 Subject: [PATCH 018/181] ci(blocked_prs): checkout default branch for action access Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index b6169768c..e89cd9dde 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -26,6 +26,10 @@ jobs: checks: write steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} - name: Setup From Pull Request Event if: github.event_name != 'workflow_dispatch' id: pr_event_setup From 0474b03626e6aff15630e4965fe73a5136d21ea0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:01:29 -0700 Subject: [PATCH 019/181] ci(blocked_pr): fix comment action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml index 5ba4bc5c9..52b46d3b2 100644 --- a/.github/actions/create-comment/action.yml +++ b/.github/actions/create-comment/action.yml @@ -51,6 +51,7 @@ runs: data=$( gh api graphql \ --paginate \ + --slurp \ -f owner="$owner" \ -f repo="$repo" \ -F issue="$ISSUE_NUMBER" \ @@ -90,16 +91,16 @@ runs: } } ' \ - --jq '' | jq -c --arg comment_id "" ' - .[0].data.repository.issueOrPullRequest.id as $id | - [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | - [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | - if ($id_comments | length) > 0 then - { "issueId": $id, "commentId": $id_comments[0].id } - else - { "issueId": $id, "commentId": "" } - end - ' + | jq -c --arg comment_id "" ' + .[0].data.repository.issueOrPullRequest.id as $id | + [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | + [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | + if ($id_comments | length) > 0 then + { "issueId": $id, "commentId": $id_comments[0].id } + else + { "issueId": $id, "commentId": "" } + end + ' ) echo "ISSUE_NODE_ID=$(jq -r '.issueId' <<< "$data")" >> "$GITHUB_ENV" echo "COMMENT_NODE_ID=$(jq -r '.commentId' <<< "$data")" >> "$GITHUB_ENV" From 05356eff6939a9511d381f13e476713a188bbdac Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:12:02 -0700 Subject: [PATCH 020/181] ci(blocking_prs): fix generated comment Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index e89cd9dde..b83c01c0c 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -236,6 +236,8 @@ jobs: id: blocked_comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true + env: + BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" touch "$COMMENT_PATH" @@ -250,7 +252,7 @@ jobs: status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") type=$(jq -r '.type' <<< "$pr_data") echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" - done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + done < <(jq -c '.[]' <<< "$BLOCKING_DATA") echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" - name: 💬 PR Comment @@ -258,7 +260,7 @@ jobs: continue-on-error: true uses: ./.github/actions/create-comment with: - comment: "### PR Dependencies :pushpin:" + comment: "

PR Dependencies :pushpin:

" comment_path: ${{ steps.blocked_comment.outputs.file_path }} comment_id: "block_pr_dependencies" issue_number: ${{ env.PR_NUMBER }} From c2b7b4c82aebc88adf12fda75e2350a0e3fb6a05 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:18:17 -0700 Subject: [PATCH 021/181] ci(blocked_prs): fix multiline comment body in comment action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml index 52b46d3b2..5e5925504 100644 --- a/.github/actions/create-comment/action.yml +++ b/.github/actions/create-comment/action.yml @@ -36,7 +36,9 @@ runs: echo "Reading comment file from ${COMMENT_FILE}" comment_body="${comment_body}$(cat "$COMMENT_FILE")" fi - echo "COMMENT_BODY=$comment_body" >> "$GITHUB_ENV" + echo 'COMMENT_BODY<> "$GITHUB_ENV" + echo "$comment_body" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" - name: Get Existing Comment Id shell: bash From c75ae4170f3570c1577d088470a8eef8346e6638 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:40:33 -0700 Subject: [PATCH 022/181] ci(blocked_prs): fix merge workflow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/merge_blocking_pr.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index 8707dd95b..606ce7769 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -23,11 +23,9 @@ jobs: id: gather_deps env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - owner="${{ github.repository_owner }}" - owner_repo="${{ github.repository }}" - repo="${owner_repo#"${owner}/"}" + owner=$(echo "${{ github.repository }}" | cut -d '/' -f 1) + repo=$(echo "${{ github.repository }}" | cut -d '/' -f 2) blocked_prs=$( gh api graphql \ -f repo="$repo" \ @@ -52,16 +50,16 @@ jobs: --paginate \ --slurp \ | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' - [.[].data.repository.pullRequests.nodes[]] | .[] | select( + reduce ( .[].data.repository.pullRequests.nodes[] | select( .bodyText | - scan("(?:blocked (?:by|on)|stacked on):? #(?[0-9]+)") | + scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | map(tonumber) | any(.[]; . == $pr) - ) + )) as $i ([]; . + [$i]) ' ) echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" - echo "numdeps='$(jq -r '. | length' <<< "$blocked_prs")" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" - name: Trigger Blocked PP Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 From 0818e7068aaca50b12c7e6a8ec8be30cd18de4ae Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 21:01:58 -0700 Subject: [PATCH 023/181] ci(blocked_pres): ensure merge workflow has actions permission Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/merge_blocking_pr.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index 606ce7769..1127d2369 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -15,8 +15,7 @@ jobs: permissions: issues: write pull-requests: write - statuses: write - checks: write + actions: write steps: - name: Gather Dependent PRs From 7cbdb80f6ba17e1a85f61ecc408d61799b5c9273 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 10:26:19 -0700 Subject: [PATCH 024/181] ci(label-actions): composit actions to add and delete labels in bulk Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 238 +++++++++++++++++++++ .github/actions/delete-labels/action.yml | 99 +++++++++ .github/workflows/manual-add-label.yml | 44 ++++ .github/workflows/manual-delete-labels.yml | 29 +++ 4 files changed, 410 insertions(+) create mode 100644 .github/actions/add-labels/action.yml create mode 100644 .github/actions/delete-labels/action.yml create mode 100644 .github/workflows/manual-add-label.yml create mode 100644 .github/workflows/manual-delete-labels.yml diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml new file mode 100644 index 000000000..fdc8395b2 --- /dev/null +++ b/.github/actions/add-labels/action.yml @@ -0,0 +1,238 @@ +name: Add Label(s) +description: adds label(s) to labelable +inputs: + gh_token: + description: gh api access token to use + default: ${{ secrets.GITHUB_TOKEN }} + repository: + description: the OWNER/REPOSITORY to operate on + default: ${{ github.repository }} + issues: + description: a single or comma separated list of issue numbers + required: true + labels: + description: a single or comma separated list of labels to apply to all listed issues + required: true + colors: + description: | + A single or comma separated list of colors to create the labels with if needed. + the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default-color` + default-color: + description: default color to create labels with + default: "#D4C5F9" + +runs: + using: "composite" + steps: + - name: Collect Repo Labels + id: collect_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPOSITORY: ${{ inputs.repository }} + LABELS: ${{ inputs.labels }} + COLORS: ${{ inputs.colors }} + DEFAULT_COLOR: ${{ inputs.default-color }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + query=$( + jq -nr \ + --arg labels "$LABELS" \ + ' + (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels + | "query ($owner: String!, $repo: String!) {\n" + + " repository(owner: $owner, name: $repo) {\n" + + " id\n" + + ( + reduce $labels[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": label(name: \"" + $i + "\") {\n" + + " id\n" + + " name\n"+ + " }\n" + ) + ]) + | .[1] + )+ + " }\n" + + "}" + ' + ) + data=$( + gh api graphql \ + -f owner="$owner" \ + -f repo="$repo" \ + -f query="$query" \ + | jq -c \ + --arg labels "$LABELS" \ + --arg colors "$COLORS" \ + --arg defaultColor "$DEFAULT_COLOR" \ + ' + . as $in + | ($labels | split(", *"; null)) as $labels + | ($colors | split(", *"; null)) as $colors + | ( + reduce ( [$labels, $colors] | transpose)[] as $i ( + {}; + .[$i[0]] = ( + if ($i[1] and $i[1] != "") then + $i[1] + else + $defaultColor + end + | if startswith("#") then .[1:] else . end + ) + ) + ) as $colorMap + | ( + reduce ( + $in.data.repository[] + | select( objects | .name as $name | any($labels[]; . == $name ) ) + ) as $i ({}; .[$i.name] = {"id": $i.id}) + ) as $found + | ( + reduce ( + $labels[] + | select( . as $name | $found | has($name) | not ) + ) as $i ({}; .[$i] = {"color": $colorMap[$i]}) + ) as $missing + | { + "repoId": $in.data.repository.id, + "found": $found, + "missing": $missing, + } + ' + ) + echo "found=$(jq -c '.found' <<< "$data")" >> "$GITHUB_OUTPUT" + echo "missing=$(jq -c '.missing' <<< "$data")" >> "$GITHUB_OUTPUT" + echo "repo_id=$(jq -r '.repoId' <<< "$data")" >> "$GITHUB_OUTPUT" + + - name: Collect Item Node IDs + id: collect_ids + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPOSITORY: ${{ inputs.repository }} + ISSUES: ${{ inputs.labels }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + query=$( + jq -nr \ + --arg issues "$ISSUES" \ + ' + (reduce ($issues | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $issues + | + "query ($owner: String!, $repo: String!) {\n" + + " repository(owner: $owner, name: $repo) {\n" + + ( + reduce $issues[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": issueOrPullRequest(number: " + ($i | tostring) +") {\n" + + " ... on Issue {\n" + + " id\n" + + " }\n" + + " ... on PullRequest {\n"+ + " id\n"+ + " }\n" + + " }\n" + ) + ]) + | .[1] + )+ + " }\n" + + "}" + ' + ) + data=$( + gh api graphql \ + -f owner="$owner" \ + -f repo="$repo" \ + -f query="$query" \ + | jq -c 'reduce .data.repository[].id as $i ([]; . + [$i])' + ) + echo "issue_ids=$data" >> "$GITHUB_OUTPUT" + + - name: Create Missing Labels + id: create_missing + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPO_ID: ${{ steps.collect_labels.outputs.repo_id }} + LABELS: ${{ steps.collect_labels.outputs.labels }} + MISSING: ${{ steps.collect_labels.outputs.missing }} + run: | + query=$( + jq -nr \ + --argjson labels "$MISSING" \ + --arg repo "$REPO_ID" \ + ' + "mutation {\n" + ( + reduce ($labels | keys | .[] | [., $labels[.]]) as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": createLabel(input: {repositoryId: \"" + $repo + + "\", name: \"" + $i[0] + + "\", color: \"" + $i[1].color + + "\"}) {\n" + + " clientMutationId\n" + + " label {\n" + + " id\n" + + " name\n" + + " color\n" + + " }\n" + + " }\n" + ) + ]) + | .[1] + ) + + "}" + ' + ) + data=$( + gh api graphql -f query="$query" \ + | jq --argjson existing "$LABELS" \ + ' + reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) + | . + $existing + ' + ) + lable_ids=$(jq -c '[.[].id]' <<< "$data") + echo "label_ids=$lable_ids" >> "$GITHUB_OUTPUT" + + - name: Apply Labels + id: apply_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + ISSUES: ${{ steps.collect_ids.outputs.issue_ids }} + LABELS: ${{ steps.create_missing.outputs.label_ids }} + run: | + query=$( + jq -nr \ + --argjson labels "$LABELS" \ + --argjson issues "$ISSUES" \ + ' + "mutation {\n" + ( + reduce $issues[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": addLabelsToLabelable(input: {labelableId: \"" + + $i + "\", labelIds: " + ($labels | tojson) + "}) {\n" + + " clientMutationId\n" + + " }\n" + ) + ]) + | .[1] + ) + + "}" + ' + ) + gh api graphql -f query="$query" diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml new file mode 100644 index 000000000..9ca921283 --- /dev/null +++ b/.github/actions/delete-labels/action.yml @@ -0,0 +1,99 @@ +name: Delete Label(s) +description: delete Label(s) +inputs: + gh_token: + description: gh api access token to use + default: ${{ secrets.GITHUB_TOKEN }} + repository: + description: the OWNER/REPOSITORY to operate on + default: ${{ github.repository }} + labels: + description: a single or comma separated list of labels to delete + required: true + +runs: + using: "composite" + steps: + - name: Collect Repo Labels + id: collect_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPOSITORY: ${{ inputs.repository }} + LABELS: ${{ inputs.labels }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + query=$( + jq -nr \ + --arg labels "$LABELS" \ + ' + (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels + | "query ($owner: String!, $repo: String!) {\n" + + " repository(owner: $owner, name: $repo) {\n" + + ( + reduce $labels[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": label(name: \"" + $i + "\") {\n" + + " id\n" + + " name\n"+ + " }\n" + ) + ]) + | .[1] + )+ + " }\n" + + "}" + ' + ) + data=$( + gh api graphql \ + -f owner="$owner" \ + -f repo="$repo" \ + -f query="$query" \ + | jq -c \ + --arg labels "$LABELS" \ + --arg colors "$COLORS" \ + --arg defaultColor "$DEFAULT_COLOR" \ + ' + . as $in + | ($labels | split(", *"; null)) as $labels + | ( + reduce ( + $in.data.repository[] + | select( objects | .name as $name | any($labels[]; . == $name ) ) + ) as $i ({}; .[$i.name] = {"id": $i.id}) + ) as $found + | [.[].id] + ' + ) + echo "label_ids=$data" >>> "$GITHUB_OUTPUT" + + - name: Delete Labels + id: delete_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + LABELS: ${{ steps.collect_labels.outputs.label_ids }} + run: | + query=$(jq -r ' + . as $in + | ( + "mutation {\n" + ( + reduce $in[] as $id ([0, ""]; [ + .[0] + 1 , + .[1] + ( + " _" + (.[0] | tostring) + ": deleteLabel(input: {id: \"" + $id + "\"}) {\n" + + " clientMutationId\n" + + " }\n" + ) + ]) + | .[1] + ) + + "}" + ) + ' <<< "$LABELS" + ) + gh api graphql -f query="$query" diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml new file mode 100644 index 000000000..e6c768a7d --- /dev/null +++ b/.github/workflows/manual-add-label.yml @@ -0,0 +1,44 @@ +name: Manual workflow to apply labels in bulk + +on: + workflow_dispatch: + inputs: + issues: + description: a single or comma separated list of issue numbers + required: true + type: string + labels: + description: a single or comma separated list of labels to apply to all listed issues + required: true + type: string + colors: + description: | + A single or comma separated list of colors to create the labels with if needed. + the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default_color` + type: string + default-color: + description: default color to create labels with + default: "#D4C5F9" + type: string + +jobs: + apply-labels: + name: Apply Labels + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Run Label Action + uses: ./.github/actions/add-labels + with: + issues: ${{ inputs.issues }} + labels: ${{ inputs.labels }} + colors: ${{ inputs.colors }} + default-color: ${{ inputs.default-color }} diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml new file mode 100644 index 000000000..b575d59d8 --- /dev/null +++ b/.github/workflows/manual-delete-labels.yml @@ -0,0 +1,29 @@ + +name: Manual workflow to delete labels in bulk + +on: + workflow_dispatch: + inputs: + labels: + description: a single or comma separated list of labels to delete + required: true + type: string + +jobs: + delete-labels: + name: Delete Labels + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Run Label Action + uses: ./.github/actions/delete-labels + with: + labels: ${{ inputs.labels }} From da3f378d5d52b56ef5360d72e2fec7f466ec6190 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 10:35:35 -0700 Subject: [PATCH 025/181] ci(label-actions): actions can't directly access secrets Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 23 ++++++++++------------ .github/actions/delete-labels/action.yml | 6 +++--- .github/workflows/manual-add-label.yml | 3 ++- .github/workflows/manual-delete-labels.yml | 3 ++- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml index fdc8395b2..9adb87945 100644 --- a/.github/actions/add-labels/action.yml +++ b/.github/actions/add-labels/action.yml @@ -3,7 +3,7 @@ description: adds label(s) to labelable inputs: gh_token: description: gh api access token to use - default: ${{ secrets.GITHUB_TOKEN }} + required: true repository: description: the OWNER/REPOSITORY to operate on default: ${{ github.repository }} @@ -116,7 +116,7 @@ runs: env: GH_TOKEN: ${{ inputs.gh_token }} REPOSITORY: ${{ inputs.repository }} - ISSUES: ${{ inputs.labels }} + ISSUES: ${{ inputs.issues }} run: | owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) @@ -164,14 +164,13 @@ runs: env: GH_TOKEN: ${{ inputs.gh_token }} REPO_ID: ${{ steps.collect_labels.outputs.repo_id }} - LABELS: ${{ steps.collect_labels.outputs.labels }} + EXISTING: ${{ steps.collect_labels.outputs.found }} MISSING: ${{ steps.collect_labels.outputs.missing }} run: | query=$( jq -nr \ --argjson labels "$MISSING" \ - --arg repo "$REPO_ID" \ - ' + --arg repo "$REPO_ID" ' "mutation {\n" + ( reduce ($labels | keys | .[] | [., $labels[.]]) as $i ([0, ""]; [ .[0] + 1, @@ -196,15 +195,13 @@ runs: ' ) data=$( - gh api graphql -f query="$query" \ - | jq --argjson existing "$LABELS" \ - ' - reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) - | . + $existing - ' + gh api graphql -f query="$query" | jq --argjson existing "$EXISTING" ' + reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) + | . + $existing + ' ) - lable_ids=$(jq -c '[.[].id]' <<< "$data") - echo "label_ids=$lable_ids" >> "$GITHUB_OUTPUT" + label_ids=$(jq -c '[.[].id]' <<< "$data") + echo "label_ids=$label_ids" >> "$GITHUB_OUTPUT" - name: Apply Labels id: apply_labels diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml index 9ca921283..25a8d5fad 100644 --- a/.github/actions/delete-labels/action.yml +++ b/.github/actions/delete-labels/action.yml @@ -3,7 +3,7 @@ description: delete Label(s) inputs: gh_token: description: gh api access token to use - default: ${{ secrets.GITHUB_TOKEN }} + required: true repository: description: the OWNER/REPOSITORY to operate on default: ${{ github.repository }} @@ -66,10 +66,10 @@ runs: | select( objects | .name as $name | any($labels[]; . == $name ) ) ) as $i ({}; .[$i.name] = {"id": $i.id}) ) as $found - | [.[].id] + | [$found[].id] ' ) - echo "label_ids=$data" >>> "$GITHUB_OUTPUT" + echo "label_ids=$data" >> "$GITHUB_OUTPUT" - name: Delete Labels id: delete_labels diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml index e6c768a7d..8a06ae213 100644 --- a/.github/workflows/manual-add-label.yml +++ b/.github/workflows/manual-add-label.yml @@ -1,4 +1,4 @@ -name: Manual workflow to apply labels in bulk +name: Apply labels in bulk on: workflow_dispatch: @@ -38,6 +38,7 @@ jobs: - name: Run Label Action uses: ./.github/actions/add-labels with: + gh_token: ${{ secrets.GITHUB_TOKEN }} issues: ${{ inputs.issues }} labels: ${{ inputs.labels }} colors: ${{ inputs.colors }} diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml index b575d59d8..e8c4b985d 100644 --- a/.github/workflows/manual-delete-labels.yml +++ b/.github/workflows/manual-delete-labels.yml @@ -1,5 +1,5 @@ -name: Manual workflow to delete labels in bulk +name: Delete labels in bulk on: workflow_dispatch: @@ -26,4 +26,5 @@ jobs: - name: Run Label Action uses: ./.github/actions/delete-labels with: + gh_token: ${{ secrets.GITHUB_TOKEN }} labels: ${{ inputs.labels }} From b42b453f76f5381d39de3420f7e18066b8930640 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 12:58:42 -0700 Subject: [PATCH 026/181] ci(blocking-lables): cleanup lables with a pr closes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 2 +- .github/actions/create-comment/action.yml | 4 +- .github/actions/delete-labels/action.yml | 2 + .../{blocked_prs.yml => blocked-prs.yml} | 68 ++++++------------- .github/workflows/manual-add-label.yml | 2 +- .github/workflows/manual-delete-labels.yml | 2 +- ..._blocking_pr.yml => merge-blocking_pr.yml} | 66 ++++++++++++------ 7 files changed, 74 insertions(+), 72 deletions(-) rename .github/workflows/{blocked_prs.yml => blocked-prs.yml} (80%) rename .github/workflows/{merge_blocking_pr.yml => merge-blocking_pr.yml} (52%) diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml index 9adb87945..82b237497 100644 --- a/.github/actions/add-labels/action.yml +++ b/.github/actions/add-labels/action.yml @@ -19,7 +19,7 @@ inputs: the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default-color` default-color: description: default color to create labels with - default: "#D4C5F9" + default: "#ffffff" runs: using: "composite" diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml index 5e5925504..484f1e5af 100644 --- a/.github/actions/create-comment/action.yml +++ b/.github/actions/create-comment/action.yml @@ -1,5 +1,5 @@ name: Create Issue Comment -description: Create or updste an issue comment +description: Create or update an issue comment inputs: comment: description: Comment Text @@ -19,7 +19,7 @@ inputs: required: true repository: description: the OWNER/REPOSITORY to operate on - required: true + default: ${{ github.repository }} runs: using: "composite" diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml index 25a8d5fad..65d3ba3d4 100644 --- a/.github/actions/delete-labels/action.yml +++ b/.github/actions/delete-labels/action.yml @@ -70,10 +70,12 @@ runs: ' ) echo "label_ids=$data" >> "$GITHUB_OUTPUT" + echo "num_labels=$(jq -r 'length' <<< "$data")" >> "$GITHUB_OUTPUT" - name: Delete Labels id: delete_labels shell: bash + if: fromJSON( steps.collect_labels.outputs.num_labels ) > 0 env: GH_TOKEN: ${{ inputs.gh_token }} LABELS: ${{ steps.collect_labels.outputs.label_ids }} diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked-prs.yml similarity index 80% rename from .github/workflows/blocked_prs.yml rename to .github/workflows/blocked-prs.yml index b83c01c0c..3201e5f7a 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -54,7 +54,6 @@ jobs: } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - cat $GITHUB_ENV - name: Setup From Dispatch Event if: github.event_name == 'workflow_dispatch' @@ -133,7 +132,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }} run: | - BLOCKED_PR_DATA=$( + blocked_pr_data=$( while read -r pr_data ; do gh api \ -H "Accept: application/vnd.github+json" \ @@ -156,56 +155,33 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - echo "data=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" >> "$GITHUB_OUTPUT" + blocked_by_labels=$(jq -c 'map( select( .merged | not ) | "blocked-by:" + (.number | tostring))' <<< "$blocked_pr_data" ) + echo "data=$blocked_pr_data" >> "$GITHUB_OUTPUT" + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")" >> "$GITHUB_OUTPUT" + echo "blocked_by_labels=$blocked_by_labels" >> "$GITHUB_OUTPUT" + echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )" >> "$GITHUB_OUTPUT" - - name: Apply Blocked Label if Missing + - name: Apply Blocked by Labels id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && steps.blocking_data.outputs.blocked_by_labels != '' continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels" \ - -f "labels[]=blocked" + uses: ./.github/actions/add-labels + with: + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} + issues: ${{ env.PR_NUMBER }} + labels: ${{ join( fromJSON(steps.blocking_data.outputs.blocked_by_labels), ',' ) }} - - name: Remove 'blocked' Label if All Dependencies Are Merged - id: unlabel_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) - continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api \ - --method DELETE \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels/blocked" - - - name: Apply 'blocking' Label to Dependencies if Missing + - name: Apply 'blocking:' Label to Unmerged Dependencies id: label_blocking if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} - run: | - # label pr dependencies with 'blocking' if not already - while read -r pr_data ; do - if jq -e 'all(.labels[]; . != "blocking")' <<< "$pr_data" > /dev/null ; then - pr=$(jq -r '.number' <<< "$pr_data") - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$pr/labels" \ - -f "labels[]=blocking" - fi - done < <(jq -c '.[]' <<< "$BLOCKING_DATA") + uses: ./.github/actions/add-labels + with: + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} + issues: ${{ join( fromJSON(steps.blocking_data.outputs.current_blocking) , ',' ) }} + labels: ${{ format( 'blocking:{0}', env.PR_NUMBER ) }} - name: Apply Blocking PR Status Check id: blocked_check @@ -229,7 +205,7 @@ jobs: -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \ -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \ -f "description=$DESC" \ - -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$pr_data")" + -f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")" done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Context Comment diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml index 8a06ae213..fdb69e389 100644 --- a/.github/workflows/manual-add-label.yml +++ b/.github/workflows/manual-add-label.yml @@ -1,4 +1,4 @@ -name: Apply labels in bulk +name: "Manual: Apply Labels in Bulk" on: workflow_dispatch: diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml index e8c4b985d..300c0b52a 100644 --- a/.github/workflows/manual-delete-labels.yml +++ b/.github/workflows/manual-delete-labels.yml @@ -1,5 +1,5 @@ -name: Delete labels in bulk +name: "Manual: Delete labels in bulk" on: workflow_dispatch: diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge-blocking_pr.yml similarity index 52% rename from .github/workflows/merge_blocking_pr.yml rename to .github/workflows/merge-blocking_pr.yml index 1127d2369..a3f40a1a8 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge-blocking_pr.yml @@ -6,11 +6,13 @@ on: - closed jobs: - update_blocked_status: + update-blocked-status: name: Update Blocked Status runs-on: ubuntu-latest - if: github.event.pull_request.merged == true + # a pr that was a `blocking:` label was merged. + # find the open pr's it was blocked by and trigger a refresh of their state + if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking:" ) permissions: issues: write @@ -22,30 +24,32 @@ jobs: id: gather_deps env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} run: | owner=$(echo "${{ github.repository }}" | cut -d '/' -f 1) repo=$(echo "${{ github.repository }}" | cut -d '/' -f 2) + query=" + query(\$repo: String!, \$owner: String!, \$endCursor: String) { + repository(name: \$repo, owner: \$owner) { + pullRequests(first: 100, after: \$endCursor, states: [OPEN], labels: [\"blocked-by:${PR_NUMBER}\"]) { + nodes { + number + bodyText + merged + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + " blocked_prs=$( gh api graphql \ -f repo="$repo" \ -f owner="$owner" \ - -f query=' - query($repo: String!, $owner: String!, $endCursor: String) { - repository(name: $repo, owner: $owner) { - pullRequests(first: 100, after: $endCursor, states: [OPEN], labels: ["blocked"]) { - nodes { - number - bodyText - merged - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - ' \ + -f query="$query" \ --paginate \ --slurp \ | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' @@ -60,7 +64,7 @@ jobs: echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" - - name: Trigger Blocked PP Workflows for Dependants + - name: Trigger Blocked PR Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -71,8 +75,28 @@ jobs: --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${{ github.repository }}/actions/workflows/blocked_prs.yml/dispatches" \ + "/repos/${{ github.repository }}/actions/workflows/blocked-prs.yml/dispatches" \ -f "ref=${{ github.ref_name }}" \ -f "inputs[pr_id]=$pr" done < <(jq -c '.[].number' <<< "$DEPS") + label-cleanup: + # this pr is closed, no need for these anymore + name: "Cleanup Related blocking: or blocked-by: labels" + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Delete Related Labels + uses: ./.github/actions/delete-labels + with: + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} + labels: ${{ format('blocking:{0},blocked-by:{0}', github.event.pull_request.number ) }} From e8e81a762717b02af463baee61bda04a7d80c0d2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:56:46 -0700 Subject: [PATCH 027/181] use `basename` and `dirname` to split `owner/repo` Co-authored-by: Seth Flynn Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml index 82b237497..0f36aff91 100644 --- a/.github/actions/add-labels/action.yml +++ b/.github/actions/add-labels/action.yml @@ -34,8 +34,8 @@ runs: COLORS: ${{ inputs.colors }} DEFAULT_COLOR: ${{ inputs.default-color }} run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + owner="$(dirname "$REPOSITORY")" + repo="$(basename "$REPOSITORY")" query=$( jq -nr \ --arg labels "$LABELS" \ @@ -118,8 +118,8 @@ runs: REPOSITORY: ${{ inputs.repository }} ISSUES: ${{ inputs.issues }} run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + owner="$(dirname "$REPOSITORY")" + repo="$(basename "$REPOSITORY")" query=$( jq -nr \ --arg issues "$ISSUES" \ From 72aee5c9f63a7ba710dcd46111f7955b10470029 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:02:18 -0700 Subject: [PATCH 028/181] ci(blocked-pr): go back to single labels use gh cli actions directly insead of api where possible Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 235 --------------------- .github/actions/delete-labels/action.yml | 101 --------- .github/workflows/blocked-prs.yml | 44 ++-- .github/workflows/manual-add-label.yml | 45 ---- .github/workflows/manual-delete-labels.yml | 30 --- .github/workflows/merge-blocking-pr.yml | 52 +++++ .github/workflows/merge-blocking_pr.yml | 102 --------- 7 files changed, 76 insertions(+), 533 deletions(-) delete mode 100644 .github/actions/add-labels/action.yml delete mode 100644 .github/actions/delete-labels/action.yml delete mode 100644 .github/workflows/manual-add-label.yml delete mode 100644 .github/workflows/manual-delete-labels.yml create mode 100644 .github/workflows/merge-blocking-pr.yml delete mode 100644 .github/workflows/merge-blocking_pr.yml diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml deleted file mode 100644 index 0f36aff91..000000000 --- a/.github/actions/add-labels/action.yml +++ /dev/null @@ -1,235 +0,0 @@ -name: Add Label(s) -description: adds label(s) to labelable -inputs: - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - issues: - description: a single or comma separated list of issue numbers - required: true - labels: - description: a single or comma separated list of labels to apply to all listed issues - required: true - colors: - description: | - A single or comma separated list of colors to create the labels with if needed. - the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default-color` - default-color: - description: default color to create labels with - default: "#ffffff" - -runs: - using: "composite" - steps: - - name: Collect Repo Labels - id: collect_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - LABELS: ${{ inputs.labels }} - COLORS: ${{ inputs.colors }} - DEFAULT_COLOR: ${{ inputs.default-color }} - run: | - owner="$(dirname "$REPOSITORY")" - repo="$(basename "$REPOSITORY")" - query=$( - jq -nr \ - --arg labels "$LABELS" \ - ' - (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels - | "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - " id\n" + - ( - reduce $labels[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": label(name: \"" + $i + "\") {\n" + - " id\n" + - " name\n"+ - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c \ - --arg labels "$LABELS" \ - --arg colors "$COLORS" \ - --arg defaultColor "$DEFAULT_COLOR" \ - ' - . as $in - | ($labels | split(", *"; null)) as $labels - | ($colors | split(", *"; null)) as $colors - | ( - reduce ( [$labels, $colors] | transpose)[] as $i ( - {}; - .[$i[0]] = ( - if ($i[1] and $i[1] != "") then - $i[1] - else - $defaultColor - end - | if startswith("#") then .[1:] else . end - ) - ) - ) as $colorMap - | ( - reduce ( - $in.data.repository[] - | select( objects | .name as $name | any($labels[]; . == $name ) ) - ) as $i ({}; .[$i.name] = {"id": $i.id}) - ) as $found - | ( - reduce ( - $labels[] - | select( . as $name | $found | has($name) | not ) - ) as $i ({}; .[$i] = {"color": $colorMap[$i]}) - ) as $missing - | { - "repoId": $in.data.repository.id, - "found": $found, - "missing": $missing, - } - ' - ) - echo "found=$(jq -c '.found' <<< "$data")" >> "$GITHUB_OUTPUT" - echo "missing=$(jq -c '.missing' <<< "$data")" >> "$GITHUB_OUTPUT" - echo "repo_id=$(jq -r '.repoId' <<< "$data")" >> "$GITHUB_OUTPUT" - - - name: Collect Item Node IDs - id: collect_ids - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - ISSUES: ${{ inputs.issues }} - run: | - owner="$(dirname "$REPOSITORY")" - repo="$(basename "$REPOSITORY")" - query=$( - jq -nr \ - --arg issues "$ISSUES" \ - ' - (reduce ($issues | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $issues - | - "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - ( - reduce $issues[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": issueOrPullRequest(number: " + ($i | tostring) +") {\n" + - " ... on Issue {\n" + - " id\n" + - " }\n" + - " ... on PullRequest {\n"+ - " id\n"+ - " }\n" + - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c 'reduce .data.repository[].id as $i ([]; . + [$i])' - ) - echo "issue_ids=$data" >> "$GITHUB_OUTPUT" - - - name: Create Missing Labels - id: create_missing - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPO_ID: ${{ steps.collect_labels.outputs.repo_id }} - EXISTING: ${{ steps.collect_labels.outputs.found }} - MISSING: ${{ steps.collect_labels.outputs.missing }} - run: | - query=$( - jq -nr \ - --argjson labels "$MISSING" \ - --arg repo "$REPO_ID" ' - "mutation {\n" + ( - reduce ($labels | keys | .[] | [., $labels[.]]) as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": createLabel(input: {repositoryId: \"" + $repo + - "\", name: \"" + $i[0] + - "\", color: \"" + $i[1].color + - "\"}) {\n" + - " clientMutationId\n" + - " label {\n" + - " id\n" + - " name\n" + - " color\n" + - " }\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ' - ) - data=$( - gh api graphql -f query="$query" | jq --argjson existing "$EXISTING" ' - reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) - | . + $existing - ' - ) - label_ids=$(jq -c '[.[].id]' <<< "$data") - echo "label_ids=$label_ids" >> "$GITHUB_OUTPUT" - - - name: Apply Labels - id: apply_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - ISSUES: ${{ steps.collect_ids.outputs.issue_ids }} - LABELS: ${{ steps.create_missing.outputs.label_ids }} - run: | - query=$( - jq -nr \ - --argjson labels "$LABELS" \ - --argjson issues "$ISSUES" \ - ' - "mutation {\n" + ( - reduce $issues[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": addLabelsToLabelable(input: {labelableId: \"" + - $i + "\", labelIds: " + ($labels | tojson) + "}) {\n" + - " clientMutationId\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ' - ) - gh api graphql -f query="$query" diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml deleted file mode 100644 index 65d3ba3d4..000000000 --- a/.github/actions/delete-labels/action.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Delete Label(s) -description: delete Label(s) -inputs: - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - labels: - description: a single or comma separated list of labels to delete - required: true - -runs: - using: "composite" - steps: - - name: Collect Repo Labels - id: collect_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - LABELS: ${{ inputs.labels }} - run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) - query=$( - jq -nr \ - --arg labels "$LABELS" \ - ' - (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels - | "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - ( - reduce $labels[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": label(name: \"" + $i + "\") {\n" + - " id\n" + - " name\n"+ - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c \ - --arg labels "$LABELS" \ - --arg colors "$COLORS" \ - --arg defaultColor "$DEFAULT_COLOR" \ - ' - . as $in - | ($labels | split(", *"; null)) as $labels - | ( - reduce ( - $in.data.repository[] - | select( objects | .name as $name | any($labels[]; . == $name ) ) - ) as $i ({}; .[$i.name] = {"id": $i.id}) - ) as $found - | [$found[].id] - ' - ) - echo "label_ids=$data" >> "$GITHUB_OUTPUT" - echo "num_labels=$(jq -r 'length' <<< "$data")" >> "$GITHUB_OUTPUT" - - - name: Delete Labels - id: delete_labels - shell: bash - if: fromJSON( steps.collect_labels.outputs.num_labels ) > 0 - env: - GH_TOKEN: ${{ inputs.gh_token }} - LABELS: ${{ steps.collect_labels.outputs.label_ids }} - run: | - query=$(jq -r ' - . as $in - | ( - "mutation {\n" + ( - reduce $in[] as $id ([0, ""]; [ - .[0] + 1 , - .[1] + ( - " _" + (.[0] | tostring) + ": deleteLabel(input: {id: \"" + $id + "\"}) {\n" + - " clientMutationId\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ) - ' <<< "$LABELS" - ) - gh api graphql -f query="$query" diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 3201e5f7a..21e73660d 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -60,12 +60,11 @@ jobs: id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow - OWNER=$(echo "$REPOSITORY" | cut -d '/' -f 1) - REPO=$(echo "$REPOSITORY" | cut -d '/' -f 2) + OWNER=$(dirname "${{ github.repository }}") + REPO=$(basename "${{ github.repository }}") PR_JSON=$( gh api \ -H "Accept: application/vnd.github.raw+json" \ @@ -155,33 +154,38 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - blocked_by_labels=$(jq -c 'map( select( .merged | not ) | "blocked-by:" + (.number | tostring))' <<< "$blocked_pr_data" ) echo "data=$blocked_pr_data" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")" >> "$GITHUB_OUTPUT" - echo "blocked_by_labels=$blocked_by_labels" >> "$GITHUB_OUTPUT" echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )" >> "$GITHUB_OUTPUT" - - name: Apply Blocked by Labels + - name: Add 'blocked' Label is Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && steps.blocking_data.outputs.blocked_by_labels != '' + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true - uses: ./.github/actions/add-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ env.PR_NUMBER }} - labels: ${{ join( fromJSON(steps.blocking_data.outputs.blocked_by_labels), ',' ) }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh -R ${{ github.repository }} issue edit --add-label 'blocked' $PR_NUMBER - - name: Apply 'blocking:' Label to Unmerged Dependencies + - name: Remove 'blocked' Label if All Dependencies Are Merged + id: unlabel_blocked + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh -R ${{ github.repository }} issue edit --remove-label 'blocked' $PR_NUMBER + + - name: Apply 'blocking' Label to Unmerged Dependencies id: label_blocking if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - uses: ./.github/actions/add-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ join( fromJSON(steps.blocking_data.outputs.current_blocking) , ',' ) }} - labels: ${{ format( 'blocking:{0}', env.PR_NUMBER ) }} + env: + BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }} + run: | + while read -r pr ; do + gh -R ${{ github.repository }} issue edit --add-label 'blocking' "$pr" || true + done < <(jq -c '.[]' <<< "$BLOCKING_ISSUES") - name: Apply Blocking PR Status Check id: blocked_check diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml deleted file mode 100644 index fdb69e389..000000000 --- a/.github/workflows/manual-add-label.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: "Manual: Apply Labels in Bulk" - -on: - workflow_dispatch: - inputs: - issues: - description: a single or comma separated list of issue numbers - required: true - type: string - labels: - description: a single or comma separated list of labels to apply to all listed issues - required: true - type: string - colors: - description: | - A single or comma separated list of colors to create the labels with if needed. - the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default_color` - type: string - default-color: - description: default color to create labels with - default: "#D4C5F9" - type: string - -jobs: - apply-labels: - name: Apply Labels - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Run Label Action - uses: ./.github/actions/add-labels - with: - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ inputs.issues }} - labels: ${{ inputs.labels }} - colors: ${{ inputs.colors }} - default-color: ${{ inputs.default-color }} diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml deleted file mode 100644 index 300c0b52a..000000000 --- a/.github/workflows/manual-delete-labels.yml +++ /dev/null @@ -1,30 +0,0 @@ - -name: "Manual: Delete labels in bulk" - -on: - workflow_dispatch: - inputs: - labels: - description: a single or comma separated list of labels to delete - required: true - type: string - -jobs: - delete-labels: - name: Delete Labels - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Run Label Action - uses: ./.github/actions/delete-labels - with: - gh_token: ${{ secrets.GITHUB_TOKEN }} - labels: ${{ inputs.labels }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml new file mode 100644 index 000000000..6db3483d4 --- /dev/null +++ b/.github/workflows/merge-blocking-pr.yml @@ -0,0 +1,52 @@ +name: Merged Blocking Pull Request Automation + +on: + pull_request: + types: + - closed + +jobs: + update-blocked-status: + name: Update Blocked Status + runs-on: ubuntu-latest + + # a pr that was a `blocking:` label was merged. + # find the open pr's it was blocked by and trigger a refresh of their state + if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking" ) + + permissions: + issues: write + pull-requests: write + actions: write + + steps: + - name: Gather Dependent PRs + id: gather_deps + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + blocked_prs=$( + gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \ + | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' + reduce ( .[] | select( + .body | + scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | + map(tonumber) | + any(.[]; . == $pr) + )) as $i ([]; . + [$i]) + ' + ) + echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" + + - name: Trigger Blocked PR Workflows for Dependants + if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEPS: ${{ steps.gather_deps.outputs.deps }} + run: | + while read -r pr ; do + gh -R ${{ github.repository }} workflow run 'blocked-prs.yml' -r "${{ github.ref_name }}" -f pr_id="$pr" + done < <(jq -c '.[].number' <<< "$DEPS") + diff --git a/.github/workflows/merge-blocking_pr.yml b/.github/workflows/merge-blocking_pr.yml deleted file mode 100644 index a3f40a1a8..000000000 --- a/.github/workflows/merge-blocking_pr.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Merged Blocking Pull Request Automation - -on: - pull_request: - types: - - closed - -jobs: - update-blocked-status: - name: Update Blocked Status - runs-on: ubuntu-latest - - # a pr that was a `blocking:` label was merged. - # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking:" ) - - permissions: - issues: write - pull-requests: write - actions: write - - steps: - - name: Gather Dependent PRs - id: gather_deps - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - owner=$(echo "${{ github.repository }}" | cut -d '/' -f 1) - repo=$(echo "${{ github.repository }}" | cut -d '/' -f 2) - query=" - query(\$repo: String!, \$owner: String!, \$endCursor: String) { - repository(name: \$repo, owner: \$owner) { - pullRequests(first: 100, after: \$endCursor, states: [OPEN], labels: [\"blocked-by:${PR_NUMBER}\"]) { - nodes { - number - bodyText - merged - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - " - blocked_prs=$( - gh api graphql \ - -f repo="$repo" \ - -f owner="$owner" \ - -f query="$query" \ - --paginate \ - --slurp \ - | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' - reduce ( .[].data.repository.pullRequests.nodes[] | select( - .bodyText | - scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | - map(tonumber) | - any(.[]; . == $pr) - )) as $i ([]; . + [$i]) - ' - ) - echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" - echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" - - - name: Trigger Blocked PR Workflows for Dependants - if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DEPS: ${{ steps.gather_deps.outputs.deps }} - run: | - while read -r pr ; do - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${{ github.repository }}/actions/workflows/blocked-prs.yml/dispatches" \ - -f "ref=${{ github.ref_name }}" \ - -f "inputs[pr_id]=$pr" - done < <(jq -c '.[].number' <<< "$DEPS") - - label-cleanup: - # this pr is closed, no need for these anymore - name: "Cleanup Related blocking: or blocked-by: labels" - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Delete Related Labels - uses: ./.github/actions/delete-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - labels: ${{ format('blocking:{0},blocked-by:{0}', github.event.pull_request.number ) }} From e28dd30d87cd9779fc589c51b1519090c3e33ff5 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 19:26:27 -0700 Subject: [PATCH 029/181] ci(blocked_pr): merge env setup for dispatch and pull_request events Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 60 +++++++++---------------- .github/workflows/merge-blocking-pr.yml | 2 +- 2 files changed, 23 insertions(+), 39 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 21e73660d..96a8cfc03 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -30,13 +30,30 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} - - name: Setup From Pull Request Event - if: github.event_name != 'workflow_dispatch' - id: pr_event_setup + + - name: Setup From Dispatch Event + if: github.event_name == 'workflow_dispatch' + id: dispatch_event_setup env: - PR_JSON: "${{ toJSON(github.event.pull_request) }}" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow + OWNER=$(dirname "${{ github.repository }}") + REPO=$(basename "${{ github.repository }}") + PR_JSON=$( + gh api \ + -H "Accept: application/vnd.github.raw+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/pulls/$PR_NUMBER" + ) + echo "PR_JSON=$PR_JSON" >> "$GITHUB_ENV" + + - name: Setup Environment + id: env_setup + run: | + # setup env for the rest of the workflow + PR_JSON=${PR_JSON:-'${{ toJSON(github.event.pull_request) }}'} { echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" @@ -55,39 +72,6 @@ jobs: ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - - name: Setup From Dispatch Event - if: github.event_name == 'workflow_dispatch' - id: dispatch_event_setup - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ inputs.pr_id }} - run: | - # setup env for the rest of the workflow - OWNER=$(dirname "${{ github.repository }}") - REPO=$(basename "${{ github.repository }}") - PR_JSON=$( - gh api \ - -H "Accept: application/vnd.github.raw+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/pulls/$PR_NUMBER" - ) - { - echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" - echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" - echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=$(jq -c ' - { - "repo": .base.repo.name, - "owner": .base.repo.owner.login, - "repoUrl": .base.repo.html_url, - "prNumber": .number, - "prHeadSha": .head.sha, - "prHeadLabel": .head.label, - "prBody": .body, - "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) - } - ' <<< "$PR_JSON")" - } >> "$GITHUB_ENV" - name: Find Blocked/Stacked PRs in body id: pr_ids @@ -160,7 +144,7 @@ jobs: - name: Add 'blocked' Label is Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) + if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 6db3483d4..4ac85fa5f 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -12,7 +12,7 @@ jobs: # a pr that was a `blocking:` label was merged. # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking" ) + if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' ) permissions: issues: write From a501441e6ee6a4bba3bbf9c0847b6ee9c0007197 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Mar 2025 21:32:37 +0000 Subject: [PATCH 030/181] Paint project item backgrounds with native style Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 6946df41f..f313b58e8 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -2,6 +2,7 @@ #include "Common.h" +#include #include #include @@ -16,12 +17,12 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o auto rect = opt.rect; - if (opt.state & QStyle::State_Selected) { - painter->fillRect(rect, opt.palette.highlight()); + const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); + + if (option.state & QStyle::State_Selected) painter->setPen(opt.palette.highlightedText().color()); - } else if (opt.state & QStyle::State_MouseOver) { - painter->fillRect(rect, opt.palette.window()); - } // The default icon size will be a square (and height is usually the lower value). auto icon_width = rect.height(), icon_height = rect.height(); From 5f70335a07f8502f49a965f93f77fff49fd1bf1f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 18 Mar 2025 19:44:13 +0000 Subject: [PATCH 031/181] Render checkbox in project items Signed-off-by: TheKodeToad --- .../ui/pages/modplatform/ResourceModel.cpp | 2 + launcher/ui/widgets/ProjectItem.cpp | 46 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 6b8309fb7..b571ead0a 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -84,6 +84,8 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant return pack->description; case UserDataTypes::SELECTED: return pack->isAnyVersionSelected(); + case Qt::CheckStateRole: + return pack->isAnyVersionSelected() ? Qt::Checked : Qt::Unchecked; case UserDataTypes::INSTALLED: return this->isPackInstalled(pack); default: diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index f313b58e8..141a5191b 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -1,10 +1,11 @@ #include "ProjectItem.h" -#include "Common.h" - #include + +#include #include #include +#include "Common.h" ProjectItemDelegate::ProjectItemDelegate(QWidget* parent) : QStyledItemDelegate(parent) {} @@ -24,6 +25,27 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o if (option.state & QStyle::State_Selected) painter->setPen(opt.palette.highlightedText().color()); + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { + // 5px will be the typical margin with 48px icon size + // we don't want the checkbox to be all over the place + rect.translate(5, 0); + + QStyleOptionViewItem checkboxOpt = opt; + + checkboxOpt.state &= ~QStyle::State_HasFocus; + + if (checkboxOpt.checkState == Qt::Checked) + checkboxOpt.state |= QStyle::State_On; + + QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); + checkboxOpt.rect = + QRect(rect.x(), rect.y() + (rect.height() / 2 - checkboxRect.height() / 2), checkboxRect.width(), checkboxRect.height()); + + style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &checkboxOpt, painter, opt.widget); + + rect.setX(rect.x() + checkboxRect.width()); + } + // The default icon size will be a square (and height is usually the lower value). auto icon_width = rect.height(), icon_height = rect.height(); int icon_x_margin = (rect.height() - icon_width) / 2; @@ -43,6 +65,9 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o int x = rect.x() + icon_x_margin; int y = rect.y() + icon_y_margin; + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) + rect.translate(icon_x_margin / 2, 0); + // Prevent 'scaling null pixmap' warnings if (icon_width > 0 && icon_height > 0) opt.icon.paint(painter, x, y, icon_width, icon_height); @@ -60,23 +85,6 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->save(); auto font = opt.font; - if (index.data(UserDataTypes::SELECTED).toBool()) { - // Set nice font - font.setBold(true); - font.setUnderline(true); - } - if (index.data(UserDataTypes::INSTALLED).toBool()) { - auto hRect = opt.rect; - hRect.setX(hRect.x() + 1); - hRect.setY(hRect.y() + 1); - hRect.setHeight(hRect.height() - 2); - hRect.setWidth(hRect.width() - 2); - // Set nice font - font.setItalic(true); - font.setOverline(true); - painter->drawRect(hRect); - } - font.setPointSize(font.pointSize() + 2); painter->setFont(font); From 8577f58fe3b807c504656fdbaac2c64016e91f59 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 18 Mar 2025 20:44:01 +0000 Subject: [PATCH 032/181] Remove UserDataTypes::SELECTED Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourceModel.cpp | 3 --- launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp | 2 -- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 2 -- launcher/ui/pages/modplatform/import_ftb/ListModel.cpp | 2 -- launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp | 2 -- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 2 -- launcher/ui/pages/modplatform/technic/TechnicModel.cpp | 2 -- launcher/ui/widgets/ProjectItem.h | 1 - 8 files changed, 16 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index b571ead0a..7fcb6edb1 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -82,8 +82,6 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant return pack->name; case UserDataTypes::DESCRIPTION: return pack->description; - case UserDataTypes::SELECTED: - return pack->isAnyVersionSelected(); case Qt::CheckStateRole: return pack->isAnyVersionSelected() ? Qt::Checked : Qt::Unchecked; case UserDataTypes::INSTALLED: @@ -105,7 +103,6 @@ QHash ResourceModel::roleNames() const roles[Qt::UserRole] = "pack"; roles[UserDataTypes::TITLE] = "title"; roles[UserDataTypes::DESCRIPTION] = "description"; - roles[UserDataTypes::SELECTED] = "selected"; roles[UserDataTypes::INSTALLED] = "installed"; return roles; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp index f116ca915..e381f2a16 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -82,8 +82,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 18a2adc49..d501bf9f4 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -65,8 +65,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index f3c737977..eaec76a04 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -128,8 +128,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return tr("Minecraft %1").arg(pack.mcVersion); - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 98922123c..4ddc6da5d 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -195,8 +195,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 416c69d28..4681b1a7f 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -106,8 +106,6 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index f7e7f4433..c689ab0d2 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -89,8 +89,6 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h index c3d0dce70..96e62af12 100644 --- a/launcher/ui/widgets/ProjectItem.h +++ b/launcher/ui/widgets/ProjectItem.h @@ -6,7 +6,6 @@ enum UserDataTypes { TITLE = 257, // QString DESCRIPTION = 258, // QString - SELECTED = 259, // bool INSTALLED = 260 // bool }; From 900579eea6f0732239b5dd1c7907fb8cb7ee3f7e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 01:17:25 +0000 Subject: [PATCH 033/181] Handle checkbox toggle Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModModel.cpp | 4 +- launcher/ui/pages/modplatform/ModModel.h | 4 +- launcher/ui/pages/modplatform/ModPage.cpp | 1 - launcher/ui/pages/modplatform/ModPage.h | 2 +- .../ui/pages/modplatform/ResourceModel.cpp | 6 +- launcher/ui/pages/modplatform/ResourceModel.h | 14 +-- .../pages/modplatform/ResourcePackModel.cpp | 4 +- .../ui/pages/modplatform/ResourcePackModel.h | 4 +- .../ui/pages/modplatform/ResourcePackPage.cpp | 4 +- .../ui/pages/modplatform/ResourcePackPage.h | 2 +- .../ui/pages/modplatform/ResourcePage.cpp | 113 +++++++++++++----- launcher/ui/pages/modplatform/ResourcePage.h | 7 +- .../ui/pages/modplatform/ShaderPackModel.cpp | 4 +- .../ui/pages/modplatform/ShaderPackModel.h | 4 +- .../ui/pages/modplatform/ShaderPackPage.cpp | 5 +- .../ui/pages/modplatform/ShaderPackPage.h | 2 +- .../ui/pages/modplatform/TexturePackModel.cpp | 2 +- .../ui/pages/modplatform/TexturePackModel.h | 2 +- .../ui/pages/modplatform/TexturePackPage.h | 7 +- .../modplatform/flame/FlameResourceModels.cpp | 2 +- .../modplatform/flame/FlameResourceModels.h | 2 +- launcher/ui/widgets/ProjectItem.cpp | 70 ++++++++--- launcher/ui/widgets/ProjectItem.h | 10 +- tests/ResourceModel_test.cpp | 4 +- 24 files changed, 185 insertions(+), 94 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index cfc262b62..6e98a88bc 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -44,7 +44,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() }; } -ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = *m_packs[entry.row()]; auto profile = static_cast(m_base_instance).getPackProfile(); @@ -62,7 +62,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en return { pack, versions, loaders }; } -ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(const QModelIndex& entry) { auto& pack = *m_packs[entry.row()]; return { pack }; diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 5c994f373..bb9255cd0 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -39,8 +39,8 @@ class ModModel : public ResourceModel { public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index f0cc2df54..8b4919015 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -59,7 +59,6 @@ namespace ResourceDownload { ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) { connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); - connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected); } void ModPage::setFilterWidget(unique_qobject_ptr& widget) diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 5c9a82303..397b77d94 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -35,7 +35,7 @@ class ModPage : public ResourcePage { page->setFilterWidget(filter_widget); model->setFilter(page->getFilter()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 7fcb6edb1..8e3be5e8f 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -192,7 +192,7 @@ void ResourceModel::search() runSearchJob(job); } -void ResourceModel::loadEntry(QModelIndex& entry) +void ResourceModel::loadEntry(const QModelIndex& entry) { auto const& pack = m_packs[entry.row()]; @@ -503,7 +503,7 @@ void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::Ind return; } - emit versionListUpdated(); + emit versionListUpdated(index); } void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) @@ -530,7 +530,7 @@ void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::Indexe return; } - emit projectInfoUpdated(); + emit projectInfoUpdated(index); } void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 4c7ea33a0..3f1e633ec 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -80,17 +80,17 @@ class ResourceModel : public QAbstractListModel { virtual ResourceAPI::SearchArgs createSearchArguments() = 0; virtual ResourceAPI::SearchCallbacks createSearchCallbacks() { return {}; } - virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0; - virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) { return {}; } + virtual ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) = 0; + virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(const QModelIndex&) { return {}; } - virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0; - virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) { return {}; } + virtual ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) = 0; + virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(const QModelIndex&) { return {}; } /** Requests the API for more entries. */ virtual void search(); /** Applies any processing / extra requests needed to fully load the specified entry's information. */ - virtual void loadEntry(QModelIndex&); + virtual void loadEntry(const QModelIndex&); /** Schedule a refresh, clearing the current state. */ void refresh(); @@ -170,8 +170,8 @@ class ResourceModel : public QAbstractListModel { void infoRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&); signals: - void versionListUpdated(); - void projectInfoUpdated(); + void versionListUpdated(const QModelIndex& index); + void projectInfoUpdated(const QModelIndex& index); }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index d436f320f..0de980ed8 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -20,13 +20,13 @@ ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments() return { ModPlatform::ResourceType::RESOURCE_PACK, m_next_search_offset, m_search_term, sort }; } -ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; } -ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.h b/launcher/ui/pages/modplatform/ResourcePackModel.h index e2b4a1957..4f00808e8 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.h +++ b/launcher/ui/pages/modplatform/ResourcePackModel.h @@ -31,8 +31,8 @@ class ResourcePackResourceModel : public ResourceModel { public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: const BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp index 99039476e..8a7ed2720 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp @@ -14,9 +14,7 @@ namespace ResourceDownload { ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) -{ - connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePackResourcePage::onResourceSelected); -} +{} /******** Callbacks to events in the UI (set up in the derived classes) ********/ diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index 440d91ab0..3f925fd23 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -25,7 +25,7 @@ class ResourcePackResourcePage : public ResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 2dd5ccf0f..407684f8b 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -78,10 +78,14 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in m_ui->verticalLayout->insertWidget(1, &m_fetchProgress); - m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + auto delegate = new ProjectItemDelegate(this); + m_ui->packView->setItemDelegate(delegate); m_ui->packView->installEventFilter(this); connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); + + connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePage::onToggle); + connect(delegate, &ProjectItemDelegate::checkboxClicked, this, &ResourcePage::onToggle); } ResourcePage::~ResourcePage() @@ -177,8 +181,11 @@ ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); } -void ResourcePage::updateUi() +void ResourcePage::updateUi(const QModelIndex& index) { + if (index != m_ui->packView->currentIndex()) + return; + auto current_pack = getCurrentPack(); if (!current_pack) { m_ui->packDescription->setHtml({}); @@ -268,39 +275,48 @@ void ResourcePage::updateSelectionButton() } } -void ResourcePage::updateVersionList() +void ResourcePage::versionListUpdated(const QModelIndex& index) { - auto current_pack = getCurrentPack(); + if (index == m_ui->packView->currentIndex()) { + auto current_pack = getCurrentPack(); - m_ui->versionSelectionBox->blockSignals(true); - m_ui->versionSelectionBox->clear(); - m_ui->versionSelectionBox->blockSignals(false); + m_ui->versionSelectionBox->blockSignals(true); + m_ui->versionSelectionBox->clear(); + m_ui->versionSelectionBox->blockSignals(false); - if (current_pack) { - auto installedVersion = m_model->getInstalledPackVersion(current_pack); + if (current_pack) { + auto installedVersion = m_model->getInstalledPackVersion(current_pack); - for (int i = 0; i < current_pack->versions.size(); i++) { - auto& version = current_pack->versions[i]; - if (!m_model->checkVersionFilters(version)) - continue; + for (int i = 0; i < current_pack->versions.size(); i++) { + auto& version = current_pack->versions[i]; + if (!m_model->checkVersionFilters(version)) + continue; - auto versionText = version.version; - if (version.version_type.isValid()) { - versionText += QString(" [%1]").arg(version.version_type.toString()); + auto versionText = version.version; + if (version.version_type.isValid()) { + versionText += QString(" [%1]").arg(version.version_type.toString()); + } + if (version.fileId == installedVersion) { + versionText += tr(" [installed]", "Mod version select"); + } + + m_ui->versionSelectionBox->addItem(versionText, QVariant(i)); } - if (version.fileId == installedVersion) { - versionText += tr(" [installed]", "Mod version select"); - } - - m_ui->versionSelectionBox->addItem(versionText, QVariant(i)); } - } - if (m_ui->versionSelectionBox->count() == 0) { - m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); - m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); - } + if (m_ui->versionSelectionBox->count() == 0) { + m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); + m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); + } - updateSelectionButton(); + if (m_enableQueue.contains(index.row())) { + m_enableQueue.remove(index.row()); + onToggle(index); + } else + updateSelectionButton(); + } else if (m_enableQueue.contains(index.row())) { + m_enableQueue.remove(index.row()); + onToggle(index); + } } void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) @@ -318,16 +334,20 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI request_load = true; } else { - updateVersionList(); + versionListUpdated(curr); } if (current_pack && !current_pack->extraDataLoaded) request_load = true; + // we are already requesting this + if (m_enableQueue.contains(curr.row())) + request_load = false; + if (request_load) m_model->loadEntry(curr); - updateUi(); + updateUi(curr); } void ResourcePage::onVersionSelectionChanged(int index) @@ -385,6 +405,41 @@ void ResourcePage::onResourceSelected() m_ui->packView->repaint(); } +void ResourcePage::onToggle(const QModelIndex& index) +{ + const bool is_selected = index == m_ui->packView->currentIndex(); + auto pack = m_model->data(index, Qt::UserRole).value(); + + if (pack->versionsLoaded) { + if (pack->isAnyVersionSelected()) + removeResourceFromDialog(pack->name); + else { + auto version = std::find_if(pack->versions.begin(), pack->versions.end(), [this](const ModPlatform::IndexedVersion& version) { + return m_model->checkVersionFilters(version); + }); + + if (version != pack->versions.end()) + addResourceToDialog(pack, *version); + } + + if (is_selected) + updateSelectionButton(); + + // force update + QVariant variant; + variant.setValue(pack); + m_model->setData(index, variant, Qt::UserRole); + } else { + // the model is just 1 dimensional so this is fine + m_enableQueue.insert(index.row()); + + // we can't be sure that this hasn't already been requested... + // but this does the job well enough and there's not much point preventing edgecases + if (!is_selected) + m_model->loadEntry(index); + } +} + void ResourcePage::openUrl(const QUrl& url) { // do not allow other url schemes for security reasons diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 09c512df4..6a44dcf6d 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -71,9 +71,9 @@ class ResourcePage : public QWidget, public BasePage { void addSortings(); public slots: - virtual void updateUi(); + virtual void updateUi(const QModelIndex& index); virtual void updateSelectionButton(); - virtual void updateVersionList(); + virtual void versionListUpdated(const QModelIndex& index); void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); void removeResourceFromDialog(const QString& pack_name); @@ -91,6 +91,7 @@ class ResourcePage : public QWidget, public BasePage { void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(int index); void onResourceSelected(); + void onToggle(const QModelIndex& index); // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 @@ -115,6 +116,8 @@ class ResourcePage : public QWidget, public BasePage { QTimer m_searchTimer; bool m_doNotJumpToMod = false; + + QSet m_enableQueue; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index 8c913657a..efc6bfaf9 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -20,13 +20,13 @@ ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments() return { ModPlatform::ResourceType::SHADER_PACK, m_next_search_offset, m_search_term, sort }; } -ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; } -ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.h b/launcher/ui/pages/modplatform/ShaderPackModel.h index f3c695e9f..5bb9e58b1 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.h +++ b/launcher/ui/pages/modplatform/ShaderPackModel.h @@ -31,8 +31,8 @@ class ShaderPackResourceModel : public ResourceModel { public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: const BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 08acf361a..ace07db0e 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -15,10 +15,7 @@ namespace ResourceDownload { -ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) -{ - connect(m_ui->packView, &QListView::doubleClicked, this, &ShaderPackResourcePage::onResourceSelected); -} +ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) {} /******** Callbacks to events in the UI (set up in the derived classes) ********/ diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 4b92c33dc..68fdf6699 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -25,7 +25,7 @@ class ShaderPackResourcePage : public ResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; diff --git a/launcher/ui/pages/modplatform/TexturePackModel.cpp b/launcher/ui/pages/modplatform/TexturePackModel.cpp index cb4cafd41..d56f9334b 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.cpp +++ b/launcher/ui/pages/modplatform/TexturePackModel.cpp @@ -70,7 +70,7 @@ ResourceAPI::SearchArgs TexturePackResourceModel::createSearchArguments() return args; } -ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto args = ResourcePackResourceModel::createVersionsArguments(entry); if (!m_version_list->isLoaded()) { diff --git a/launcher/ui/pages/modplatform/TexturePackModel.h b/launcher/ui/pages/modplatform/TexturePackModel.h index 607a03be3..45b5734ee 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.h +++ b/launcher/ui/pages/modplatform/TexturePackModel.h @@ -18,7 +18,7 @@ class TexturePackResourceModel : public ResourcePackResourceModel { [[nodiscard]] inline ::Version maximumTexturePackVersion() const { return { "1.6" }; } ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; protected: Meta::VersionList::Ptr m_version_list; diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h index 42aa921c5..393ccc21e 100644 --- a/launcher/ui/pages/modplatform/TexturePackPage.h +++ b/launcher/ui/pages/modplatform/TexturePackPage.h @@ -27,7 +27,7 @@ class TexturePackResourcePage : public ResourcePackResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; @@ -39,10 +39,7 @@ class TexturePackResourcePage : public ResourcePackResourcePage { [[nodiscard]] inline QString resourceString() const override { return tr("texture pack"); } protected: - TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) - { - connect(m_ui->packView, &QListView::doubleClicked, this, &TexturePackResourcePage::onResourceSelected); - } + TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) {} }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index ae4562be4..d2dc29dfd 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -122,7 +122,7 @@ ResourceAPI::SearchArgs FlameTexturePackModel::createSearchArguments() return args; } -ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(const QModelIndex& entry) { auto args = TexturePackResourceModel::createVersionsArguments(entry); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 458fd85d0..9b86a0944 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -69,7 +69,7 @@ class FlameTexturePackModel : public TexturePackResourceModel { void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 141a5191b..b20a3e013 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -16,34 +16,20 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o QStyleOptionViewItem opt(option); initStyleOption(&opt, index); - auto rect = opt.rect; - const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + auto rect = opt.rect; + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); if (option.state & QStyle::State_Selected) painter->setPen(opt.palette.highlightedText().color()); if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { - // 5px will be the typical margin with 48px icon size - // we don't want the checkbox to be all over the place - rect.translate(5, 0); - - QStyleOptionViewItem checkboxOpt = opt; - - checkboxOpt.state &= ~QStyle::State_HasFocus; - - if (checkboxOpt.checkState == Qt::Checked) - checkboxOpt.state |= QStyle::State_On; - - QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); - checkboxOpt.rect = - QRect(rect.x(), rect.y() + (rect.height() / 2 - checkboxRect.height() / 2), checkboxRect.width(), checkboxRect.height()); - + QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &checkboxOpt, painter, opt.widget); - rect.setX(rect.x() + checkboxRect.width()); + rect.setX(checkboxOpt.rect.right()); } // The default icon size will be a square (and height is usually the lower value). @@ -141,3 +127,51 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->restore(); } + +bool ProjectItemDelegate::editorEvent(QEvent* event, + QAbstractItemModel* model, + const QStyleOptionViewItem& option, + const QModelIndex& index) +{ + if (!(event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonDblClick)) + return false; + + auto mouseEvent = (QMouseEvent*)event; + + QStyleOptionViewItem opt(option); + initStyleOption(&opt, index); + + const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + + const QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); + + if (!checkboxOpt.rect.contains(mouseEvent->x(), mouseEvent->y())) + return false; + + // swallow other events + // (prevents item being selected or double click action triggering) + if (event->type() != QEvent::MouseButtonRelease) + return true; + + emit checkboxClicked(index); + return true; +} + +QStyleOptionViewItem ProjectItemDelegate::makeCheckboxStyleOption(const QStyleOptionViewItem& opt, const QStyle* style) const +{ + QStyleOptionViewItem checkboxOpt = opt; + + checkboxOpt.state &= ~QStyle::State_HasFocus; + + if (checkboxOpt.checkState == Qt::Checked) + checkboxOpt.state |= QStyle::State_On; + + QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); + // 5px is the typical top margin for image + // we don't want the checkboxes to be all over the place :) + checkboxOpt.rect = QRect(opt.rect.x() + 5, opt.rect.y() + (opt.rect.height() / 2 - checkboxRect.height() / 2), checkboxRect.width(), + checkboxRect.height()); + + return checkboxOpt; +} diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h index 96e62af12..068358ade 100644 --- a/launcher/ui/widgets/ProjectItem.h +++ b/launcher/ui/widgets/ProjectItem.h @@ -6,7 +6,7 @@ enum UserDataTypes { TITLE = 257, // QString DESCRIPTION = 258, // QString - INSTALLED = 260 // bool + INSTALLED = 259 // bool }; /** This is an item delegate composed of: @@ -21,4 +21,12 @@ class ProjectItemDelegate final : public QStyledItemDelegate { ProjectItemDelegate(QWidget* parent); void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; + + bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override; + + signals: + void checkboxClicked(const QModelIndex& index); + + private: + QStyleOptionViewItem makeCheckboxStyleOption(const QStyleOptionViewItem& opt, const QStyle* style) const; }; diff --git a/tests/ResourceModel_test.cpp b/tests/ResourceModel_test.cpp index b589758aa..30bb99fb8 100644 --- a/tests/ResourceModel_test.cpp +++ b/tests/ResourceModel_test.cpp @@ -43,8 +43,8 @@ class DummyResourceModel : public ResourceModel { [[nodiscard]] auto metaEntryBase() const -> QString override { return ""; } ResourceAPI::SearchArgs createSearchArguments() override { return {}; } - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override { return {}; } - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override { return {}; } + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override { return {}; } + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override { return {}; } QJsonArray documentToArray(QJsonDocument& doc) const override { return doc.object().value("hits").toArray(); } From c6066fe6ecff2b2b374834894a7ab531ffa3b249 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 01:41:56 +0000 Subject: [PATCH 034/181] Add warning dialog if there are no versions available Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 407684f8b..1f2261e2c 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -418,7 +418,14 @@ void ResourcePage::onToggle(const QModelIndex& index) return m_model->checkVersionFilters(version); }); - if (version != pack->versions.end()) + if (version == pack->versions.end()) { + auto errorMessage = new QMessageBox( + QMessageBox::Warning, tr("No versions available"), + tr("No versions for '%1' are available.\nThe author likely blocked third-party launchers.").arg(pack->name), + QMessageBox::Ok, this); + + errorMessage->open(); + } else addResourceToDialog(pack, *version); } From a5c62e657aa95b50992b63d590e66d1a0d685c7c Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 01:43:43 +0000 Subject: [PATCH 035/181] Snek case Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 1f2261e2c..4e5de4933 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -407,7 +407,7 @@ void ResourcePage::onResourceSelected() void ResourcePage::onToggle(const QModelIndex& index) { - const bool is_selected = index == m_ui->packView->currentIndex(); + const bool isSelected = index == m_ui->packView->currentIndex(); auto pack = m_model->data(index, Qt::UserRole).value(); if (pack->versionsLoaded) { @@ -429,7 +429,7 @@ void ResourcePage::onToggle(const QModelIndex& index) addResourceToDialog(pack, *version); } - if (is_selected) + if (isSelected) updateSelectionButton(); // force update @@ -442,7 +442,7 @@ void ResourcePage::onToggle(const QModelIndex& index) // we can't be sure that this hasn't already been requested... // but this does the job well enough and there's not much point preventing edgecases - if (!is_selected) + if (!isSelected) m_model->loadEntry(index); } } From 6bda537633d0e3b9b2a5d4e6971b792ddfc23826 Mon Sep 17 00:00:00 2001 From: hanlie <48323966+HanlieChina@users.noreply.github.com> Date: Wed, 19 Mar 2025 00:24:58 +0800 Subject: [PATCH 036/181] Improvements to modlist export Added .replace("{mod_id}", modID) Signed-off-by: hanlie <48323966+HanlieChina@users.noreply.github.com> --- launcher/minecraft/mod/Mod.cpp | 9 +++++++++ launcher/minecraft/mod/Mod.h | 1 + launcher/minecraft/mod/Resource.h | 4 ++++ launcher/modplatform/helpers/ExportToModList.cpp | 2 ++ 4 files changed, 16 insertions(+) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 50fb45d77..99fc39ce0 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -138,6 +138,15 @@ auto Mod::name() const -> QString return Resource::name(); } +auto Mod::mod_id() const -> QString +{ + auto d_mod_id = details().mod_id; + if (!d_mod_id.isEmpty()) + return d_mod_id; + + return Resource::name(); +} + auto Mod::version() const -> QString { return details().version; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 8a352c66c..deb1859de 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -61,6 +61,7 @@ class Mod : public Resource { auto details() const -> const ModDetails&; auto name() const -> QString override; + auto mod_id() const -> QString; auto version() const -> QString; auto homepage() const -> QString override; auto description() const -> QString; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 42463fe8f..16d8c2a89 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -152,6 +152,9 @@ class Resource : public QObject { [[nodiscard]] bool isMoreThanOneHardLink() const; + [[nodiscard]] auto mod_id() const -> QString { return m_mod_id; } + void setModId(const QString& modId) { m_mod_id = modId; } + protected: /* The file corresponding to this resource. */ QFileInfo m_file_info; @@ -162,6 +165,7 @@ class Resource : public QObject { QString m_internal_id; /* Name as reported via the file name. In the absence of a better name, this is shown to the user. */ QString m_name; + QString m_mod_id; /* The type of file we're dealing with. */ ResourceType m_type = ResourceType::UNKNOWN; diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index bddc7e320..678f89917 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -203,6 +203,7 @@ QString exportToModList(QList mods, QString lineTemplate) for (auto mod : mods) { auto meta = mod->metadata(); auto modName = mod->name(); + auto modID = mod->mod_id(); auto url = mod->homepage(); auto ver = mod->version(); if (ver.isEmpty() && meta != nullptr) @@ -211,6 +212,7 @@ QString exportToModList(QList mods, QString lineTemplate) auto filename = mod->fileinfo().fileName(); lines << QString(lineTemplate) .replace("{name}", modName) + .replace("{mod_id}", modID) .replace("{url}", url) .replace("{version}", ver) .replace("{authors}", authors) From 75321722339ff6df854131623774a8e7420b6e76 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:31:55 +0000 Subject: [PATCH 037/181] Clear enableQueue on model reset Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModPage.h | 1 + launcher/ui/pages/modplatform/ResourcePackPage.h | 1 + launcher/ui/pages/modplatform/ResourcePage.cpp | 5 +++++ launcher/ui/pages/modplatform/ResourcePage.h | 2 ++ launcher/ui/pages/modplatform/ShaderPackPage.h | 1 + launcher/ui/pages/modplatform/TexturePackPage.h | 1 + 6 files changed, 11 insertions(+) diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 397b77d94..47fe21e0f 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -37,6 +37,7 @@ class ModPage : public ResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index 3f925fd23..8d967f73a 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -27,6 +27,7 @@ class ResourcePackResourcePage : public ResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 4e5de4933..5acfec5da 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -374,6 +374,11 @@ void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, m_model->addPack(pack, ver, base_model, is_indexed); } +void ResourcePage::modelReset() +{ + m_enableQueue.clear(); +} + void ResourcePage::removeResourceFromPage(const QString& name) { m_model->removePack(name); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 6a44dcf6d..23309333b 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -80,6 +80,8 @@ class ResourcePage : public QWidget, public BasePage { virtual void removeResourceFromPage(const QString& name); virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr); + virtual void modelReset(); + QList selectedPacks() { return m_model->selectedPacks(); } bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 68fdf6699..d436e218a 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -27,6 +27,7 @@ class ShaderPackResourcePage : public ResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h index 393ccc21e..27fd8bcfc 100644 --- a/launcher/ui/pages/modplatform/TexturePackPage.h +++ b/launcher/ui/pages/modplatform/TexturePackPage.h @@ -29,6 +29,7 @@ class TexturePackResourcePage : public ResourcePackResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } From 0f3ac57fddc7a364e7313759cc3784ce01fa4818 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:42:38 +0000 Subject: [PATCH 038/181] Trigger onToggle instead of onResourceSelected when pressing enter Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 5acfec5da..07d296d48 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -132,13 +132,9 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool m_searchTimer.start(350); } } else if (watched == m_ui->packView) { + // stop the event from going to the confirm button if (keyEvent->key() == Qt::Key_Return) { - onResourceSelected(); - - // To have the 'select mod' button outlined instead of the 'review and confirm' one - m_ui->resourceSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); - m_ui->packView->setFocus(Qt::FocusReason::NoFocusReason); - + onToggle(m_ui->packView->currentIndex()); keyEvent->accept(); return true; } From 6c44a3f6dfb629682a3741336d1a83faa973250c Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:46:32 +0000 Subject: [PATCH 039/181] onToggle -> onResourceToggle Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 12 ++++++------ launcher/ui/pages/modplatform/ResourcePage.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 07d296d48..153392075 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -84,8 +84,8 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); - connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePage::onToggle); - connect(delegate, &ProjectItemDelegate::checkboxClicked, this, &ResourcePage::onToggle); + connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePage::onResourceToggle); + connect(delegate, &ProjectItemDelegate::checkboxClicked, this, &ResourcePage::onResourceToggle); } ResourcePage::~ResourcePage() @@ -134,7 +134,7 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool } else if (watched == m_ui->packView) { // stop the event from going to the confirm button if (keyEvent->key() == Qt::Key_Return) { - onToggle(m_ui->packView->currentIndex()); + onResourceToggle(m_ui->packView->currentIndex()); keyEvent->accept(); return true; } @@ -306,12 +306,12 @@ void ResourcePage::versionListUpdated(const QModelIndex& index) if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); - onToggle(index); + onResourceToggle(index); } else updateSelectionButton(); } else if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); - onToggle(index); + onResourceToggle(index); } } @@ -406,7 +406,7 @@ void ResourcePage::onResourceSelected() m_ui->packView->repaint(); } -void ResourcePage::onToggle(const QModelIndex& index) +void ResourcePage::onResourceToggle(const QModelIndex& index) { const bool isSelected = index == m_ui->packView->currentIndex(); auto pack = m_model->data(index, Qt::UserRole).value(); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 23309333b..055db441a 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -93,7 +93,7 @@ class ResourcePage : public QWidget, public BasePage { void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(int index); void onResourceSelected(); - void onToggle(const QModelIndex& index); + void onResourceToggle(const QModelIndex& index); // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 From 6dc16c48a2b1c6056fa06762f5d4a34602adaab5 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:56:07 +0000 Subject: [PATCH 040/181] Append [installed] to installed mods Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index b20a3e013..d1634591f 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -68,6 +68,9 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o { // Title painting auto title = index.data(UserDataTypes::TITLE).toString(); + if (index.data(UserDataTypes::INSTALLED).toBool()) + title += " [installed]"; + painter->save(); auto font = opt.font; From 9f768f76bbf309a80685f5870c5d881cfb7900df Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 11:00:51 +0000 Subject: [PATCH 041/181] Translate installed indicator Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index d1634591f..1224b853b 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -69,7 +69,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o auto title = index.data(UserDataTypes::TITLE).toString(); if (index.data(UserDataTypes::INSTALLED).toBool()) - title += " [installed]"; + title = tr("%1 [installed]").arg(title); painter->save(); From 5832fb8b95b8853c66ddea114e3b9f3a888e287b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 11:09:25 +0000 Subject: [PATCH 042/181] Implement middle click Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 8 ++++++++ launcher/ui/widgets/ProjectItem.cpp | 3 +++ 2 files changed, 11 insertions(+) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 153392075..ea8e8d5e9 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -81,6 +81,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in auto delegate = new ProjectItemDelegate(this); m_ui->packView->setItemDelegate(delegate); m_ui->packView->installEventFilter(this); + m_ui->packView->viewport()->installEventFilter(this); connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); @@ -139,6 +140,13 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool return true; } } + } else if (watched == m_ui->packView->viewport() && event->type() == QEvent::MouseButtonPress) { + auto* mouseEvent = static_cast(event); + + if (mouseEvent->button() == Qt::MiddleButton) { + onResourceToggle(m_ui->packView->indexAt(mouseEvent->pos())); + return true; + } } return QWidget::eventFilter(watched, event); diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 1224b853b..fee743c23 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -142,6 +142,9 @@ bool ProjectItemDelegate::editorEvent(QEvent* event, auto mouseEvent = (QMouseEvent*)event; + if (mouseEvent->button() != Qt::LeftButton) + return false; + QStyleOptionViewItem opt(option); initStyleOption(&opt, index); From e89f96e9e9087c6b49cceb8e7e710d85ca46d68d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 11:48:31 +0000 Subject: [PATCH 043/181] Hack for broken windowsvista Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index fee743c23..c11939b00 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -22,7 +22,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); - if (option.state & QStyle::State_Selected) + if (option.state & QStyle::State_Selected && style->objectName() != "windowsvista") painter->setPen(opt.palette.highlightedText().color()); if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { From a108b5e9eb05ee0072fc02d2ab0ef482025a2820 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 12:35:24 +0000 Subject: [PATCH 044/181] Correctly set objectName for HintOverrideProxyStyle Signed-off-by: TheKodeToad --- launcher/ui/themes/HintOverrideProxyStyle.cpp | 4 ++++ launcher/ui/themes/HintOverrideProxyStyle.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/themes/HintOverrideProxyStyle.cpp b/launcher/ui/themes/HintOverrideProxyStyle.cpp index 80e821349..f31969fce 100644 --- a/launcher/ui/themes/HintOverrideProxyStyle.cpp +++ b/launcher/ui/themes/HintOverrideProxyStyle.cpp @@ -18,6 +18,10 @@ #include "HintOverrideProxyStyle.h" +HintOverrideProxyStyle::HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) { + setObjectName(style->objectName()); +} + int HintOverrideProxyStyle::styleHint(QStyle::StyleHint hint, const QStyleOption* option, const QWidget* widget, diff --git a/launcher/ui/themes/HintOverrideProxyStyle.h b/launcher/ui/themes/HintOverrideProxyStyle.h index 09b296018..e9c489d09 100644 --- a/launcher/ui/themes/HintOverrideProxyStyle.h +++ b/launcher/ui/themes/HintOverrideProxyStyle.h @@ -25,7 +25,7 @@ class HintOverrideProxyStyle : public QProxyStyle { Q_OBJECT public: - HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) {} + explicit HintOverrideProxyStyle(QStyle* style); int styleHint(QStyle::StyleHint hint, const QStyleOption* option = nullptr, From 947ca679520d4656a9002aaed1ac63acfbc59f42 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 13:32:14 +0000 Subject: [PATCH 045/181] Fix windows 9x and possibly other styles Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index c11939b00..950c5fe0a 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -172,6 +172,8 @@ QStyleOptionViewItem ProjectItemDelegate::makeCheckboxStyleOption(const QStyleOp if (checkboxOpt.checkState == Qt::Checked) checkboxOpt.state |= QStyle::State_On; + else + checkboxOpt.state |= QStyle::State_Off; QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); // 5px is the typical top margin for image From 187728c1f238d55d613796ae6139ffd122c5b80f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:19:13 -0700 Subject: [PATCH 046/181] ci(blocked-pr): use app token Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 25 +++++++++++++------------ .github/workflows/merge-blocking-pr.yml | 16 +++++++++------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 96a8cfc03..4c1840b01 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -19,13 +19,14 @@ jobs: name: Check Blocked Status runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - statuses: write - checks: write - steps: + - name: Generate token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.PULL_REQUEST_APP_ID }} + private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} + - name: Checkout Default Branch uses: actions/checkout@v4 with: @@ -35,7 +36,7 @@ jobs: if: github.event_name == 'workflow_dispatch' id: dispatch_event_setup env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow @@ -112,7 +113,7 @@ jobs: id: blocking_data if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }} run: | blocked_pr_data=$( @@ -147,7 +148,7 @@ jobs: if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | gh -R ${{ github.repository }} issue edit --add-label 'blocked' $PR_NUMBER @@ -156,7 +157,7 @@ jobs: if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | gh -R ${{ github.repository }} issue edit --remove-label 'blocked' $PR_NUMBER @@ -176,7 +177,7 @@ jobs: if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") @@ -229,5 +230,5 @@ jobs: comment_id: "block_pr_dependencies" issue_number: ${{ env.PR_NUMBER }} repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} + gh_token: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 4ac85fa5f..755cdcde9 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -14,16 +14,18 @@ jobs: # find the open pr's it was blocked by and trigger a refresh of their state if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' ) - permissions: - issues: write - pull-requests: write - actions: write - steps: + - name: Generate token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.PULL_REQUEST_APP_ID }} + private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} + - name: Gather Dependent PRs id: gather_deps env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | blocked_prs=$( @@ -43,7 +45,7 @@ jobs: - name: Trigger Blocked PR Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} DEPS: ${{ steps.gather_deps.outputs.deps }} run: | while read -r pr ; do From 436896d36524c6c625637c74bc5c1174c04eed58 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:30:26 -0700 Subject: [PATCH 047/181] ci(blocked-pr): use `gh issue comment --create-if-none --edit-last` with app token, not composit action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 155 ---------------------- .github/workflows/blocked-prs.yml | 31 ++--- 2 files changed, 14 insertions(+), 172 deletions(-) delete mode 100644 .github/actions/create-comment/action.yml diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml deleted file mode 100644 index 484f1e5af..000000000 --- a/.github/actions/create-comment/action.yml +++ /dev/null @@ -1,155 +0,0 @@ -name: Create Issue Comment -description: Create or update an issue comment -inputs: - comment: - description: Comment Text - required: true - comment_path: - description: "Path to txt file to be parsed" - required: false - comment_id: - description: "Unique identifier for deduplicating comments" - default: "Create Issue Action" - required: false - issue_number: - description: Local Pull Request/Issue number to work on - required: true - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - -runs: - using: "composite" - steps: - - name: Generate Comment Text - shell: bash - env: - COMMENT_ID: ${{ inputs.comment_id }} - COMMENT_TEXT: ${{ inputs.comment }} - COMMENT_FILE: ${{ inputs.comment_path }} - run: | - comment_body="${COMMENT_TEXT}" - if [ -f "$COMMENT_FILE" ] ; then - echo "Reading comment file from ${COMMENT_FILE}" - comment_body="${comment_body}$(cat "$COMMENT_FILE")" - fi - echo 'COMMENT_BODY<> "$GITHUB_ENV" - echo "$comment_body" >> "$GITHUB_ENV" - echo 'EOF' >> "$GITHUB_ENV" - - - name: Get Existing Comment Id - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - ISSUE_NUMBER: ${{ inputs.issue_number }} - REPOSITORY: ${{ inputs.repository }} - COMMENT_ID: ${{ inputs.comment_id }} - run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) - data=$( - gh api graphql \ - --paginate \ - --slurp \ - -f owner="$owner" \ - -f repo="$repo" \ - -F issue="$ISSUE_NUMBER" \ - -f query=' - query($repo: String!, $owner: String!, $issue: Int!, $endCursor: String) { - repository(name: $repo, owner: $owner) { - issueOrPullRequest(number: $issue) { - ... on Issue { - id - number - comments(first: 100, after: $endCursor) { - nodes { - id - body - } - pageInfo { - hasNextPage - endCursor - } - } - } - ... on PullRequest { - id - number - comments(first: 100, after: $endCursor) { - nodes { - id - body - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - } - } - ' \ - | jq -c --arg comment_id "" ' - .[0].data.repository.issueOrPullRequest.id as $id | - [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | - [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | - if ($id_comments | length) > 0 then - { "issueId": $id, "commentId": $id_comments[0].id } - else - { "issueId": $id, "commentId": "" } - end - ' - ) - echo "ISSUE_NODE_ID=$(jq -r '.issueId' <<< "$data")" >> "$GITHUB_ENV" - echo "COMMENT_NODE_ID=$(jq -r '.commentId' <<< "$data")" >> "$GITHUB_ENV" - - - name: Edit Existing Comment - if: env.COMMENT_NODE_ID != '' - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - run: | - gh api graphql \ - -f comment_id="$COMMENT_NODE_ID" \ - -f comment_body="$COMMENT_BODY" \ - -f query=' - mutation($comment_id: ID!, $comment_body: String!) { - updateIssueComment(input: { - id: $comment_id, - body: $comment_body, - }) { - issueComment { - lastEditedAt - } - } - } - ' - - - name: Create Comment - if: env.COMMENT_NODE_ID == '' - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - run: | - gh api graphql \ - -f issue_id="$ISSUE_NODE_ID" \ - -f comment_body="$COMMENT_BODY" \ - -f query=' - mutation ($issue_id: ID!, $comment_body: String!) { - addComment(input: { subjectId: $issue_id, body: $comment_body }) { - commentEdge { - node { - id - } - } - } - } - ' - - - - diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 4c1840b01..9a6caab7d 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -27,11 +27,6 @@ jobs: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Setup From Dispatch Event if: github.event_name == 'workflow_dispatch' id: dispatch_event_setup @@ -198,15 +193,14 @@ jobs: done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Context Comment - id: blocked_comment + id: generate-comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" - touch "$COMMENT_PATH" - echo "" > "$COMMENT_PATH" + echo '

PR Dependencies :pushpin:

' > "$COMMENT_PATH" pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA") while read -r pr_data ; do base_pr=$(jq -r '.number' <<< "$pr_data") @@ -218,17 +212,20 @@ jobs: type=$(jq -r '.type' <<< "$pr_data") echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" + + echo 'body<> "$GITHUB_OUTPUT" + cat "${COMMENT_PATH}" >> "$GITHUB_OUTPUT" + echo 'EOF' >> "$GITHUB_OUTPUT" - name: 💬 PR Comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - uses: ./.github/actions/create-comment - with: - comment: "

PR Dependencies :pushpin:

" - comment_path: ${{ steps.blocked_comment.outputs.file_path }} - comment_id: "block_pr_dependencies" - issue_number: ${{ env.PR_NUMBER }} - repository: ${{ github.repository }} - gh_token: ${{ steps.generate-token.outputs.token }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + COMMENT_BODY: ${{ steps.generate-comment.outputs.body }} + run: | + gh -R ${{ github.repository }} issue comment "$PR_NUMBER" \ + --body "$COMMENT_BODY" \ + --create-if-none \ + --edit-last From 815306f1d0e1155f27a650ea7bfbee89a857c0de Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:16:44 -0700 Subject: [PATCH 048/181] Use group redirections for multiple outputs, prevent glob expansion of PR_NUMBER Co-authored-by: Seth Flynn Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 20 ++++++++++++-------- .github/workflows/merge-blocking-pr.yml | 6 ++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 9a6caab7d..d32f53bb1 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -134,9 +134,11 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - echo "data=$blocked_pr_data" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")" >> "$GITHUB_OUTPUT" - echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )" >> "$GITHUB_OUTPUT" + { + echo "data=$blocked_pr_data"; + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")"; + echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )"; + } >> "$GITHUB_OUTPUT" - name: Add 'blocked' Label is Missing id: label_blocked @@ -145,7 +147,7 @@ jobs: env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | - gh -R ${{ github.repository }} issue edit --add-label 'blocked' $PR_NUMBER + gh -R ${{ github.repository }} issue edit --add-label 'blocked' "$PR_NUMBER" - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked @@ -154,7 +156,7 @@ jobs: env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | - gh -R ${{ github.repository }} issue edit --remove-label 'blocked' $PR_NUMBER + gh -R ${{ github.repository }} issue edit --remove-label 'blocked' "$PR_NUMBER" - name: Apply 'blocking' Label to Unmerged Dependencies id: label_blocking @@ -213,9 +215,11 @@ jobs: echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - echo 'body<> "$GITHUB_OUTPUT" - cat "${COMMENT_PATH}" >> "$GITHUB_OUTPUT" - echo 'EOF' >> "$GITHUB_OUTPUT" + { + echo 'body<> "$GITHUB_OUTPUT" - name: 💬 PR Comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 755cdcde9..6f85b9282 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -39,8 +39,10 @@ jobs: )) as $i ([]; . + [$i]) ' ) - echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" - echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" + { + echo "deps=$blocked_prs" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" + } >> "$GITHUB_OUTPUT" - name: Trigger Blocked PR Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 From f7fd6f566f364d6327b11bc89d27ef6d1258d661 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 4 Oct 2023 13:51:25 +0300 Subject: [PATCH 049/181] chore:fixed some codeql warnings Signed-off-by: Trial97 --- launcher/DataMigrationTask.cpp | 2 +- launcher/FileSystem.h | 12 +-- launcher/InstanceCopyTask.cpp | 6 +- launcher/InstanceCopyTask.h | 2 +- launcher/JavaCommon.h | 2 +- launcher/VersionProxyModel.cpp | 44 ++++----- launcher/minecraft/PackProfile.cpp | 10 +-- launcher/minecraft/WorldList.cpp | 10 +-- launcher/minecraft/auth/AccountList.cpp | 90 ++++++++----------- .../minecraft/gameoptions/GameOptions.cpp | 14 ++- launcher/minecraft/mod/DataPack.cpp | 21 ++--- launcher/minecraft/mod/ModFolderModel.cpp | 9 +- .../minecraft/mod/ResourceFolderModel.cpp | 9 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 9 +- .../atlauncher/ATLPackInstallTask.h | 2 +- .../modplatform/legacy_ftb/PackFetchTask.h | 2 +- .../legacy_ftb/PackInstallTask.cpp | 2 +- .../modplatform/legacy_ftb/PackInstallTask.h | 2 +- launcher/pathmatcher/MultiMatcher.h | 2 + launcher/pathmatcher/RegexpMatcher.h | 2 + launcher/ui/dialogs/ProgressDialog.cpp | 2 +- launcher/ui/dialogs/ProgressDialog.h | 2 +- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 14 +-- launcher/ui/pages/instance/ServersPage.cpp | 59 ++++++------ launcher/ui/pages/instance/WorldListPage.cpp | 9 +- .../atlauncher/AtlOptionalModDialog.cpp | 10 ++- .../atlauncher/AtlOptionalModDialog.h | 8 +- .../AtlUserInteractionSupportImpl.cpp | 2 +- .../AtlUserInteractionSupportImpl.h | 3 +- .../modplatform/import_ftb/ListModel.cpp | 3 - .../modplatform/legacy_ftb/ListModel.cpp | 2 +- .../pages/modplatform/legacy_ftb/ListModel.h | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.h | 2 +- launcher/ui/widgets/FocusLineEdit.h | 2 + tests/FileSystem_test.cpp | 19 ++-- 36 files changed, 176 insertions(+), 217 deletions(-) diff --git a/launcher/DataMigrationTask.cpp b/launcher/DataMigrationTask.cpp index c03302319..92e310a16 100644 --- a/launcher/DataMigrationTask.cpp +++ b/launcher/DataMigrationTask.cpp @@ -15,7 +15,7 @@ DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher) : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath) { - m_copy.matcher(m_pathMatcher.get()).whitelist(true); + m_copy.matcher(m_pathMatcher).whitelist(true); } void DataMigrationTask::executeTask() diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index c5beef7bd..bf91c603c 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -115,7 +115,7 @@ class copy : public QObject { m_followSymlinks = follow; return *this; } - copy& matcher(const IPathMatcher* filter) + copy& matcher(IPathMatcher::Ptr filter) { m_matcher = filter; return *this; @@ -147,7 +147,7 @@ class copy : public QObject { private: bool m_followSymlinks = true; - const IPathMatcher* m_matcher = nullptr; + IPathMatcher::Ptr m_matcher = nullptr; bool m_whitelist = false; bool m_overwrite = false; QDir m_src; @@ -209,7 +209,7 @@ class create_link : public QObject { m_useHardLinks = useHard; return *this; } - create_link& matcher(const IPathMatcher* filter) + create_link& matcher(IPathMatcher::Ptr filter) { m_matcher = filter; return *this; @@ -260,7 +260,7 @@ class create_link : public QObject { private: bool m_useHardLinks = false; - const IPathMatcher* m_matcher = nullptr; + IPathMatcher::Ptr m_matcher = nullptr; bool m_whitelist = false; bool m_recursive = true; @@ -488,7 +488,7 @@ class clone : public QObject { m_src.setPath(src); m_dst.setPath(dst); } - clone& matcher(const IPathMatcher* filter) + clone& matcher(IPathMatcher::Ptr filter) { m_matcher = filter; return *this; @@ -514,7 +514,7 @@ class clone : public QObject { bool operator()(const QString& offset, bool dryRun = false); private: - const IPathMatcher* m_matcher = nullptr; + IPathMatcher::Ptr m_matcher = nullptr; bool m_whitelist = false; QDir m_src; QDir m_dst; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index d335b11c4..fb5963532 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -43,7 +43,7 @@ void InstanceCopyTask::executeTask() m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { if (m_useClone) { FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); - folderClone.matcher(m_matcher.get()); + folderClone.matcher(m_matcher); folderClone(true); setProgress(0, folderClone.totalCloned()); @@ -72,7 +72,7 @@ void InstanceCopyTask::executeTask() } FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder - folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher); folderLink(true); setProgress(0, m_progressTotal + folderLink.totalToLink()); @@ -127,7 +127,7 @@ void InstanceCopyTask::executeTask() return !there_were_errors; } FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).matcher(m_matcher.get()); + folderCopy.followSymlinks(false).matcher(m_matcher); folderCopy(true); setProgress(0, folderCopy.totalCopied()); diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 0f7f1020d..3aba13e5c 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -28,7 +28,7 @@ class InstanceCopyTask : public InstanceTask { InstancePtr m_origInstance; QFuture m_copyFuture; QFutureWatcher m_copyFutureWatcher; - std::unique_ptr m_matcher; + IPathMatcher::Ptr m_matcher; bool m_keepPlaytime; bool m_useLinks = false; bool m_useHardLinks = false; diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index a21b5a494..0e4aa2b0a 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -24,7 +24,7 @@ class TestCheck : public QObject { TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen) : m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen) {} - virtual ~TestCheck() {}; + virtual ~TestCheck() = default; void run(); diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 7538ce08c..3d9d95eb6 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -193,8 +193,8 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const if (value.toBool()) { return tr("Recommended"); } else if (hasLatest) { - auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if (value.toBool()) { + auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if (latest.toBool()) { return tr("Latest"); } } @@ -203,33 +203,27 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const } } case Qt::DecorationRole: { - switch (column) { - case Name: { - if (hasRecommended) { - auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); - if (recommenced.toBool()) { - return APPLICATION->getThemedIcon("star"); - } else if (hasLatest) { - auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if (latest.toBool()) { - return APPLICATION->getThemedIcon("bug"); - } - } - QPixmap pixmap; - QPixmapCache::find("placeholder", &pixmap); - if (!pixmap) { - QPixmap px(16, 16); - px.fill(Qt::transparent); - QPixmapCache::insert("placeholder", px); - return px; - } - return pixmap; + if (column == Name && hasRecommended) { + auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); + if (recommenced.toBool()) { + return APPLICATION->getThemedIcon("star"); + } else if (hasLatest) { + auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if (latest.toBool()) { + return APPLICATION->getThemedIcon("bug"); } } - default: { - return QVariant(); + QPixmap pixmap; + QPixmapCache::find("placeholder", &pixmap); + if (!pixmap) { + QPixmap px(16, 16); + px.fill(Qt::transparent); + QPixmapCache::insert("placeholder", px); + return px; } + return pixmap; } + return QVariant(); } default: { if (roles.contains((BaseVersionList::ModelRoles)role)) { diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index d6534b910..4deee9712 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -517,13 +517,9 @@ QVariant PackProfile::data(const QModelIndex& index, int role) const switch (role) { case Qt::CheckStateRole: { - switch (column) { - case NameColumn: { - return patch->isEnabled() ? Qt::Checked : Qt::Unchecked; - } - default: - return QVariant(); - } + if (column == NameColumn) + return patch->isEnabled() ? Qt::Checked : Qt::Unchecked; + return QVariant(); } case Qt::DisplayRole: { switch (column) { diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 812b13c71..cf27be676 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -208,13 +208,9 @@ QVariant WorldList::data(const QModelIndex& index, int role) const } case Qt::UserRole: - switch (column) { - case SizeColumn: - return QVariant::fromValue(world.bytes()); - - default: - return data(index, Qt::DisplayRole); - } + if (column == SizeColumn) + return QVariant::fromValue(world.bytes()); + return data(index, Qt::DisplayRole); case Qt::ToolTipRole: { if (column == InfoColumn) { diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index d276d4c41..3cbbb2a74 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -260,6 +260,30 @@ int AccountList::count() const return m_accounts.count(); } +QString getAccountStatus(AccountState status) +{ + switch (status) { + case AccountState::Unchecked: + return QObject::tr("Unchecked", "Account status"); + case AccountState::Offline: + return QObject::tr("Offline", "Account status"); + case AccountState::Online: + return QObject::tr("Ready", "Account status"); + case AccountState::Working: + return QObject::tr("Working", "Account status"); + case AccountState::Errored: + return QObject::tr("Errored", "Account status"); + case AccountState::Expired: + return QObject::tr("Expired", "Account status"); + case AccountState::Disabled: + return QObject::tr("Disabled", "Account status"); + case AccountState::Gone: + return QObject::tr("Gone", "Account status"); + default: + return QObject::tr("Unknown", "Account status"); + } +} + QVariant AccountList::data(const QModelIndex& index, int role) const { if (!index.isValid()) @@ -273,13 +297,10 @@ QVariant AccountList::data(const QModelIndex& index, int role) const switch (role) { case Qt::DisplayRole: switch (index.column()) { - case ProfileNameColumn: { + case ProfileNameColumn: return account->profileName(); - } - case NameColumn: return account->accountDisplayString(); - case TypeColumn: { switch (account->accountType()) { case AccountType::MSA: { @@ -291,39 +312,8 @@ QVariant AccountList::data(const QModelIndex& index, int role) const } return tr("Unknown", "Account type"); } - - case StatusColumn: { - switch (account->accountState()) { - case AccountState::Unchecked: { - return tr("Unchecked", "Account status"); - } - case AccountState::Offline: { - return tr("Offline", "Account status"); - } - case AccountState::Online: { - return tr("Ready", "Account status"); - } - case AccountState::Working: { - return tr("Working", "Account status"); - } - case AccountState::Errored: { - return tr("Errored", "Account status"); - } - case AccountState::Expired: { - return tr("Expired", "Account status"); - } - case AccountState::Disabled: { - return tr("Disabled", "Account status"); - } - case AccountState::Gone: { - return tr("Gone", "Account status"); - } - default: { - return tr("Unknown", "Account status"); - } - } - } - + case StatusColumn: + return getAccountStatus(account->accountState()); default: return QVariant(); } @@ -335,11 +325,9 @@ QVariant AccountList::data(const QModelIndex& index, int role) const return QVariant::fromValue(account); case Qt::CheckStateRole: - if (index.column() == ProfileNameColumn) { + if (index.column() == ProfileNameColumn) return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked; - } else { - return QVariant(); - } + return QVariant(); default: return QVariant(); @@ -461,18 +449,14 @@ bool AccountList::loadList() // Make sure the format version matches. auto listVersion = root.value("formatVersion").toVariant().toInt(); - switch (listVersion) { - case AccountListVersion::MojangMSA: { - return loadV3(root); - } break; - default: { - QString newName = "accounts-old.json"; - qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName; - // Attempt to rename the old version. - file.rename(newName); - return false; - } - } + if (listVersion == AccountListVersion::MojangMSA) + return loadV3(root); + + QString newName = "accounts-old.json"; + qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName; + // Attempt to rename the old version. + file.rename(newName); + return false; } bool AccountList::loadV3(QJsonObject& root) diff --git a/launcher/minecraft/gameoptions/GameOptions.cpp b/launcher/minecraft/gameoptions/GameOptions.cpp index 4f4fb99a7..25f7074ec 100644 --- a/launcher/minecraft/gameoptions/GameOptions.cpp +++ b/launcher/minecraft/gameoptions/GameOptions.cpp @@ -87,16 +87,12 @@ QVariant GameOptions::data(const QModelIndex& index, int role) const if (row < 0 || row >= int(contents.size())) return QVariant(); - switch (role) { - case Qt::DisplayRole: - if (column == 0) { - return contents[row].key; - } else { - return contents[row].value; - } - default: - return QVariant(); + if (role == Qt::DisplayRole) { + if (column == 0) + return contents[row].key; + return contents[row].value; } + return QVariant(); } int GameOptions::rowCount(const QModelIndex&) const diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 4a9e77a70..580d5c714 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -105,19 +105,16 @@ std::pair DataPack::compatibleVersions() const int DataPack::compare(const Resource& other, SortType type) const { auto const& cast_other = static_cast(other); - switch (type) { - default: - return Resource::compare(other, type); - case SortType::PACK_FORMAT: { - auto this_ver = packFormat(); - auto other_ver = cast_other.packFormat(); + if (type == SortType::PACK_FORMAT) { + auto this_ver = packFormat(); + auto other_ver = cast_other.packFormat(); - if (this_ver > other_ver) - return 1; - if (this_ver < other_ver) - return -1; - break; - } + if (this_ver > other_ver) + return 1; + if (this_ver < other_ver) + return -1; + } else { + return Resource::compare(other, type); } return 0; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 027f3d4ca..43888ae27 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -145,12 +145,9 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const } return {}; case Qt::CheckStateRole: - switch (column) { - case ActiveColumn: - return at(row).enabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + return QVariant(); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 70555fa35..d4900616b 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -513,12 +513,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::CheckStateRole: - switch (column) { - case ActiveColumn: - return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; - default: - return {}; - } + if (column == ActiveColumn) + return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; + return {}; default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 94774c81f..d9f27a043 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -128,12 +128,9 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const } return {}; case Qt::CheckStateRole: - switch (column) { - case ActiveColumn: - return at(row).enabled() ? Qt::Checked : Qt::Unchecked; - default: - return {}; - } + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + return {}; default: return {}; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index ffc358fbb..ee5960e30 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -62,7 +62,7 @@ class UserInteractionSupport { /** * Requests a user interaction to select which optional mods should be installed. */ - virtual std::optional> chooseOptionalMods(PackVersion version, QVector mods) = 0; + virtual std::optional> chooseOptionalMods(const PackVersion& version, QVector mods) = 0; /** * Requests a user interaction to select a component version from a given version list diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.h b/launcher/modplatform/legacy_ftb/PackFetchTask.h index e37d949d5..4c7a8f6aa 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.h +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.h @@ -40,7 +40,7 @@ class PackFetchTask : public QObject { void failed(QString reason); void aborted(); - void privateFileDownloadFinished(Modpack modpack); + void privateFileDownloadFinished(const Modpack& modpack); void privateFileDownloadFailed(QString reason, QString packCode); }; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index d6252663f..c04c0b2f3 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -52,7 +52,7 @@ namespace LegacyFTB { -PackInstallTask::PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version) +PackInstallTask::PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version) { m_pack = pack; m_version = version; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index 30ff48597..42808a1a2 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -18,7 +18,7 @@ class PackInstallTask : public InstanceTask { Q_OBJECT public: - explicit PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version); + explicit PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version); virtual ~PackInstallTask() {} bool canAbort() const override { return true; } diff --git a/launcher/pathmatcher/MultiMatcher.h b/launcher/pathmatcher/MultiMatcher.h index 3e2bdb95d..ccd5a9163 100644 --- a/launcher/pathmatcher/MultiMatcher.h +++ b/launcher/pathmatcher/MultiMatcher.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include "IPathMatcher.h" diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h index a6a3e616d..18c42f887 100644 --- a/launcher/pathmatcher/RegexpMatcher.h +++ b/launcher/pathmatcher/RegexpMatcher.h @@ -1,3 +1,5 @@ +#pragma once + #include #include "IPathMatcher.h" diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 9897687e3..aa2f67bdb 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -92,7 +92,7 @@ ProgressDialog::~ProgressDialog() { for (auto conn : this->m_taskConnections) { disconnect(conn); - } + } delete ui; } diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 4a696a49d..50e4418da 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -93,7 +93,7 @@ class ProgressDialog : public QDialog { Ui::ProgressDialog* ui; Task* m_task; - + QList m_taskConnections; bool m_is_multi_step = false; diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 7e29e1192..aa4bbd294 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -282,6 +282,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool bool skip_rest = false; ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; + // adds resource to list based on provider auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: @@ -293,6 +294,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool } }; + // ask the user on what provider to seach for the mod first for (auto candidate : m_candidates) { if (candidate->status() != ResourceStatus::NO_METADATA) { onMetadataEnsured(candidate); @@ -335,6 +337,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool addToTmp(candidate, response.chosen); } + // prepare task for the modrinth mods if (!modrinth_tmp.empty()) { auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); @@ -350,6 +353,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool seq.addTask(modrinth_task); } + // prepare task for the flame mods if (!flame_tmp.empty()) { auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); @@ -367,6 +371,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool seq.addTask(m_second_try_metadata); + // execute all the tasks ProgressDialog checking_dialog(m_parent); checking_dialog.setSkipButton(true, tr("Abort")); checking_dialog.setWindowTitle(tr("Generating metadata...")); @@ -477,13 +482,8 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q auto changelog_area = new QTextBrowser(); QString text = info.changelog; - switch (info.provider) { - case ModPlatform::ResourceProvider::MODRINTH: { - text = markdownToHTML(info.changelog.toUtf8()); - break; - } - default: - break; + if (info.provider == ModPlatform::ResourceProvider::MODRINTH) { + text = markdownToHTML(info.changelog.toUtf8()); } changelog_area->setHtml(StringUtils::htmlListPatch(text)); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 4bc2e6998..136fb47c7 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -36,9 +36,9 @@ */ #include "ServersPage.h" +#include "ServerPingTask.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" -#include "ServerPingTask.h" #include #include @@ -49,10 +49,10 @@ #include #include +#include #include #include #include -#include static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things. @@ -113,8 +113,8 @@ struct Server { // Data - temporary bool m_checked = false; bool m_up = false; - QString m_motd; // https://mctools.org/motd-creator - std::optional m_currentPlayers; // nullopt if not calculated/calculating + QString m_motd; // https://mctools.org/motd-creator + std::optional m_currentPlayers; // nullopt if not calculated/calculating int m_maxPlayers = 0; }; @@ -317,10 +317,10 @@ class ServersModel : public QAbstractListModel { if (row < 0 || row >= m_servers.size()) return QVariant(); - switch (column) { - case 0: - switch (role) { - case Qt::DecorationRole: { + switch (role) { + case Qt::DecorationRole: { + switch (column) { + case 0: { auto& bytes = m_servers[row].m_icon; if (bytes.size()) { QPixmap px; @@ -329,31 +329,32 @@ class ServersModel : public QAbstractListModel { } return APPLICATION->getThemedIcon("unknown_server"); } - case Qt::DisplayRole: - return m_servers[row].m_name; - case ServerPtrRole: - return QVariant::fromValue((void*)&m_servers[row]); - default: - return QVariant(); - } - case 1: - switch (role) { - case Qt::DisplayRole: + case 1: return m_servers[row].m_address; default: return QVariant(); } - case 2: - switch (role) { - case Qt::DisplayRole: + case 2: + if (role == Qt::DisplayRole) { if (m_servers[row].m_currentPlayers) { return *m_servers[row].m_currentPlayers; } else { return "..."; } - default: + } else { return QVariant(); - } + } + } + case Qt::DisplayRole: + if (column == 0) + return m_servers[row].m_name; + else + return QVariant(); + case ServerPtrRole: + if (column == 0) + return QVariant::fromValue((void*)&m_servers[row]); + else + return QVariant(); default: return QVariant(); } @@ -447,22 +448,22 @@ class ServersModel : public QAbstractListModel { } m_currentQueryTask = ConcurrentTask::Ptr( - new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()) - ); + new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); int row = 0; - for (Server &server : m_servers) { + for (Server& server : m_servers) { // reset current players server.m_currentPlayers = {}; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); // Start task to query server status auto target = MinecraftTarget::parse(server.m_address, false); - auto *task = new ServerPingTask(target.address, target.port); + auto* task = new ServerPingTask(target.address, target.port); m_currentQueryTask->addTask(Task::Ptr(task)); // Update the model when the task is done connect(task, &Task::finished, this, [this, task, row]() { - if (m_servers.size() < row) return; + if (m_servers.size() < row) + return; m_servers[row].m_currentPlayers = task->m_outputOnlinePlayers; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); }); @@ -717,7 +718,7 @@ void ServersPage::openedImpl() ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); - // ping servers + // ping servers m_model->queryServersStatus(); } diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 4ed5f1f73..dd7486a6c 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -166,12 +166,9 @@ void WorldListPage::retranslate() bool WorldListPage::worldListFilter(QKeyEvent* keyEvent) { - switch (keyEvent->key()) { - case Qt::Key_Delete: - on_actionRemove_triggered(); - return true; - default: - break; + if (keyEvent->key() == Qt::Key_Delete) { + on_actionRemove_triggered(); + return true; } return QWidget::eventFilter(ui->worldTreeView, keyEvent); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 6fb867733..d84737bf5 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -45,7 +45,9 @@ #include "net/ApiDownload.h" -AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods) +AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, + const ATLauncher::PackVersion& version, + QVector mods) : QAbstractListModel(parent), m_version(version), m_mods(mods) { // fill mod index @@ -233,7 +235,7 @@ void AtlOptionalModListModel::clearAll() emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn)); } -void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) +void AtlOptionalModListModel::toggleMod(const ATLauncher::VersionMod& mod, int index) { auto enable = !m_selection[mod.name]; @@ -251,7 +253,7 @@ void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) setMod(mod, index, enable); } -void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) +void AtlOptionalModListModel::setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit) { if (m_selection[mod.name] == enable) return; @@ -313,7 +315,7 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool } } -AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector mods) +AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods) : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { ui->setupUi(this); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 767d277d9..0636715cc 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -55,7 +55,7 @@ class AtlOptionalModListModel : public QAbstractListModel { DescriptionColumn, }; - AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods); + AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); QVector getResult(); @@ -78,8 +78,8 @@ class AtlOptionalModListModel : public QAbstractListModel { void clearAll(); private: - void toggleMod(ATLauncher::VersionMod mod, int index); - void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true); + void toggleMod(const ATLauncher::VersionMod& mod, int index); + void setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit = true); private: NetJob::Ptr m_jobPtr; @@ -97,7 +97,7 @@ class AtlOptionalModDialog : public QDialog { Q_OBJECT public: - AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector mods); + AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); ~AtlOptionalModDialog() override; QVector getResult() { return listModel->getResult(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index 0c7257859..7550ff758 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -41,7 +41,7 @@ AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget* parent) : m_parent(parent) {} -std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, +std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version, QVector mods) { AtlOptionalModDialog optionalModDialog(m_parent, version, mods); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h index 52ced2615..7ff021105 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -48,7 +48,8 @@ class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInt private: QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override; - std::optional> chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; + std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version, + QVector mods) override; void displayMessage(QString message) override; private: diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index f3c737977..0fb83b6cb 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -106,9 +106,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const } auto pack = m_modpacks.at(pos); - if (role == Qt::ToolTipRole) { - } - switch (role) { case Qt::ToolTipRole: return tr("Minecraft %1").arg(pack.mcVersion); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 98922123c..bdb7c64d7 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -213,7 +213,7 @@ void ListModel::fill(ModpackList modpacks_) endResetModel(); } -void ListModel::addPack(Modpack modpack) +void ListModel::addPack(const Modpack& modpack) { beginResetModel(); this->modpacks.append(modpack); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h index f35012078..e4477c929 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h @@ -62,7 +62,7 @@ class ListModel : public QAbstractListModel { Qt::ItemFlags flags(const QModelIndex& index) const override; void fill(ModpackList modpacks); - void addPack(Modpack modpack); + void addPack(const Modpack& modpack); void clear(); void remove(int row); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 226a30ee3..5752b6c61 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -213,7 +213,7 @@ void Page::ftbPackDataDownloadAborted() CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); } -void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack) +void Page::ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack) { privateListModel->addPack(pack); } diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h index a2dee24e9..818000c05 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h @@ -85,7 +85,7 @@ class Page : public QWidget, public ModpackProviderBasePage { void ftbPackDataDownloadFailed(QString reason); void ftbPackDataDownloadAborted(); - void ftbPrivatePackDataDownloadSuccessfully(Modpack pack); + void ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack); void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); void onSortingSelectionChanged(QString data); diff --git a/launcher/ui/widgets/FocusLineEdit.h b/launcher/ui/widgets/FocusLineEdit.h index f5ea6602e..797969406 100644 --- a/launcher/ui/widgets/FocusLineEdit.h +++ b/launcher/ui/widgets/FocusLineEdit.h @@ -1,3 +1,5 @@ +#pragma once + #include class FocusLineEdit : public QLineEdit { diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index ca0313bb4..9f64f54ed 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -42,7 +43,7 @@ class LinkTask : public Task { ~LinkTask() { delete m_lnk; } - void matcher(const IPathMatcher* filter) { m_lnk->matcher(filter); } + void matcher(IPathMatcher::Ptr filter) { m_lnk->matcher(filter); } void linkRecursively(bool recursive) { @@ -203,8 +204,8 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - c.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + c.matcher(re); c(); for (auto entry : target_dir.entryList()) { @@ -236,8 +237,8 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - c.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + c.matcher(re); c.whitelist(true); c(); @@ -429,8 +430,8 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - lnk_tsk.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); @@ -476,8 +477,8 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - lnk_tsk.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); lnk_tsk.whitelist(true); QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { From 6ef59fe984777e2620b445a78493afb86074eb8b Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 24 Mar 2025 17:59:53 -0400 Subject: [PATCH 050/181] ci: use bundled linuxdeploy appimage plugin Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e459914..5a3e4f4ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -251,7 +251,6 @@ jobs: if: runner.os == 'Linux' && matrix.qt_ver != 5 run: | wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" - wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" From 5d5155bb22f9935e17e33008a644736404629928 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 24 Mar 2025 18:16:58 -0400 Subject: [PATCH 051/181] ci: pin appimage tooling Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a3e4f4ec..0e902a6ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,9 @@ jobs: qt_arch: "" qt_version: "6.5.3" qt_modules: "qt5compat qtimageformats qtnetworkauth" + linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" + linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" + appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" - os: windows-2022 name: "Windows-MinGW-w64" @@ -249,11 +252,19 @@ jobs: - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.qt_ver != 5 + env: + APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }} + LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }} + LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }} run: | - wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" - wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" + wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage" + wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage" - wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" + wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage" + + sha256sum -c - <<< "$LINUXDEPLOY_HASH" + sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH" + sha256sum -c - <<< "$APPIMAGEUPDATE_HASH" sudo apt install libopengl0 libfuse2 From 671aad88f52e3fbba872e80f2805c0c5c64b7a8d Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 24 Mar 2025 18:17:26 -0400 Subject: [PATCH 052/181] ci: don't install libfuse2 Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e902a6ce..59489d0f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -266,7 +266,7 @@ jobs: sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH" sha256sum -c - <<< "$APPIMAGEUPDATE_HASH" - sudo apt install libopengl0 libfuse2 + sudo apt install libopengl0 - name: Add QT_HOST_PATH var (Windows MSVC arm64) if: runner.os == 'Windows' && matrix.architecture == 'arm64' From 0da645594fbb0e8cd0b791e0901e9311d20d9b16 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 01:20:07 +0800 Subject: [PATCH 053/181] Adjust instance view sorting mode to QComboBox and add renaming behavior Signed-off-by: Yihe Li --- launcher/Application.cpp | 2 + launcher/ui/pages/global/LauncherPage.cpp | 48 +++++++++++++++--- launcher/ui/pages/global/LauncherPage.ui | 62 ++++++++++++++++------- 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c3477d331..816f7b8ab 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -709,7 +709,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("ToolbarsLocked", false); + // Instance m_settings->registerSetting("InstSortMode", "Name"); + m_settings->registerSetting("InstRenamingMode", "AskEverytime"); m_settings->registerSetting("SelectedInstance", QString()); // Window state and geometry diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 04ee01b00..fab1bfe38 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -65,13 +65,19 @@ enum InstSortMode { Sort_LastLaunch }; +enum InstRenamingMode { + // Rename metadata only. + Rename_Metadata, + // Rename physical directory too. + Rename_Physical, + // Ask everytime. + Rename_Ask +}; + LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) { ui->setupUi(this); - ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); - ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); - defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); m_languageModel = APPLICATION->translations(); @@ -234,7 +240,8 @@ void LauncherPage::applySettings() s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked()); - auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); + // Instance + auto sortMode = (InstSortMode) ui->viewSortingComboBox->currentIndex(); switch (sortMode) { case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); @@ -245,6 +252,20 @@ void LauncherPage::applySettings() break; } + auto renamingMode = (InstRenamingMode) ui->renamingBehaviorComboBox->currentIndex(); + switch (renamingMode) { + case Rename_Metadata: + s->set("InstRenamingMode", "MetadataOnly"); + break; + case Rename_Physical: + s->set("InstRenamingMode", "PhysicalDir"); + break; + case Rename_Ask: + default: + s->set("InstRenamingMode", "AskEverytime"); + break; + } + // Cat s->set("CatOpacity", ui->catOpacitySpinBox->value()); @@ -299,13 +320,26 @@ void LauncherPage::loadSettings() ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); ui->downloadsDirMoveCheckBox->setChecked(s->get("MoveModsFromDownloadsDir").toBool()); + // Instance QString sortMode = s->get("InstSortMode").toString(); - + InstSortMode sortModeEnum; if (sortMode == "LastLaunch") { - ui->sortLastLaunchedBtn->setChecked(true); + sortModeEnum = Sort_LastLaunch; } else { - ui->sortByNameBtn->setChecked(true); + sortModeEnum = Sort_Name; } + ui->viewSortingComboBox->setCurrentIndex(sortModeEnum); + + QString renamingMode = s->get("InstRenamingMode").toString(); + InstRenamingMode renamingModeEnum; + if (renamingMode == "MetadataOnly") { + renamingModeEnum = Rename_Metadata; + } else if (renamingMode == "PhysicalDir"){ + renamingModeEnum = Rename_Physical; + } else { + renamingModeEnum = Rename_Ask; + } + ui->renamingBehaviorComboBox->setCurrentIndex(renamingModeEnum); // Cat ui->catOpacitySpinBox->setValue(s->get("CatOpacity").toInt()); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 31c878f3e..01aad7af7 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -401,32 +401,59 @@ - + true - Instance view sorting mode + Instance - - - + + + - &By last launched + Instance view sorting mode - - sortingModeGroup - - - + + + + + By last launched + + + + + By name + + + + + + - By &name + Instance renaming behavior - - sortingModeGroup - + + + + + + + Rename metadata only + + + + + Rename physical directory + + + + + Ask everytime + + @@ -674,7 +701,7 @@ numberOfConcurrentDownloadsSpinBox numberOfManualRetriesSpinBox timeoutSecondsSpinBox - sortLastLaunchedBtn + sortLastLaunchedComboBox sortByNameBtn catOpacitySpinBox preferMenuBarCheckBox @@ -686,7 +713,4 @@ - - - \ No newline at end of file From 4b20e3bc3985c6de37120a462d32c8fa33e41fa2 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 03:10:25 +0800 Subject: [PATCH 054/181] Implement instance renaming Signed-off-by: Yihe Li --- launcher/InstanceList.cpp | 7 ++- launcher/InstanceList.h | 1 + launcher/ui/MainWindow.cpp | 63 +++++++++++++++++++++++ launcher/ui/MainWindow.h | 2 + launcher/ui/pages/global/LauncherPage.cpp | 6 +-- launcher/ui/pages/global/LauncherPage.ui | 2 - 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..a9e86cd7f 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -149,6 +149,11 @@ QStringList InstanceList::getLinkedInstancesById(const QString& id) const return linkedInstances; } +QString InstanceList::getInstanceRootById(const InstanceId& id) const +{ + return FS::PathCombine(m_instDir, id); +} + int InstanceList::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); @@ -627,7 +632,7 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) loadGroupList(); } - auto instanceRoot = FS::PathCombine(m_instDir, id); + auto instanceRoot = getInstanceRootById(id); auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); InstancePtr inst; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index c85fe55c7..b04a289d2 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -152,6 +152,7 @@ class InstanceList : public QAbstractListModel { QMimeData* mimeData(const QModelIndexList& indexes) const override; QStringList getLinkedInstancesById(const QString& id) const; + QString getInstanceRootById(const InstanceId& id) const; signals: void dataIsInvalid(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a9473ac15..f413ac05d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -564,6 +565,67 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos) myMenu.exec(view->mapToGlobal(pos)); } +void MainWindow::updateInstanceRoot() +{ + QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); + if (renamingMode == "MetadataOnly") + return; + + auto oldRoot = m_selectedInstance->instanceRoot(); + auto newID = m_selectedInstance->name(); + auto newRoot = APPLICATION->instances()->getInstanceRootById(newID); + if (oldRoot == newRoot) + return; + + // Check for conflict + if (QDir(newRoot).exists()) { + QMessageBox::warning(this, tr("Cannot rename instance"), + tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); + return; + } + + // Ask if we should rename + if (renamingMode == "AskEverytime") { + QMessageBox messageBox(this); + messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); + messageBox.setInformativeText(tr("The following renaming operation will be performed:
" + " - Old instance root: %1
" + " - New instance root: %2") + .arg(oldRoot, newRoot)); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::Yes); + messageBox.setIcon(QMessageBox::Question); + + auto checkBox = new QCheckBox(tr("&Remember my choice"), this); + checkBox->setChecked(true); + messageBox.setCheckBox(checkBox); + + auto res = messageBox.exec(); + if (checkBox->isChecked()) { + if (res == QMessageBox::Yes) + APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); + else + APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); + } + if (res == QMessageBox::No) + return; + } + + // Now we can confirm that a renaming is happening + auto ret = QFile::rename(oldRoot, newRoot); + if (!ret) { + QMessageBox::warning(this, tr("Cannot rename instance"), + tr("An error occurred when performing the following renaming operation:
" + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") + .arg(oldRoot, newRoot)); + return; + } + refreshInstances(); + setSelectedInstanceById(newID); +} + void MainWindow::updateMainToolBar() { ui->menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); @@ -1703,6 +1765,7 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); + updateInstanceRoot(); } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0e692eda7..9ac1d39e0 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -176,6 +176,8 @@ class MainWindow : public QMainWindow { void showInstanceContextMenu(const QPoint&); + void updateInstanceRoot(); + void updateMainToolBar(); void updateLaunchButton(); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index fab1bfe38..db37d548a 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -241,7 +241,7 @@ void LauncherPage::applySettings() s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked()); // Instance - auto sortMode = (InstSortMode) ui->viewSortingComboBox->currentIndex(); + auto sortMode = (InstSortMode)ui->viewSortingComboBox->currentIndex(); switch (sortMode) { case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); @@ -252,7 +252,7 @@ void LauncherPage::applySettings() break; } - auto renamingMode = (InstRenamingMode) ui->renamingBehaviorComboBox->currentIndex(); + auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex(); switch (renamingMode) { case Rename_Metadata: s->set("InstRenamingMode", "MetadataOnly"); @@ -334,7 +334,7 @@ void LauncherPage::loadSettings() InstRenamingMode renamingModeEnum; if (renamingMode == "MetadataOnly") { renamingModeEnum = Rename_Metadata; - } else if (renamingMode == "PhysicalDir"){ + } else if (renamingMode == "PhysicalDir") { renamingModeEnum = Rename_Physical; } else { renamingModeEnum = Rename_Ask; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 01aad7af7..c1ba1d428 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -701,8 +701,6 @@ numberOfConcurrentDownloadsSpinBox numberOfManualRetriesSpinBox timeoutSecondsSpinBox - sortLastLaunchedComboBox - sortByNameBtn catOpacitySpinBox preferMenuBarCheckBox lineLimitSpinBox From abac3db125ee462b117a378a84130a8a20476921 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 04:11:29 +0800 Subject: [PATCH 055/181] Make remember checkbox off by default Signed-off-by: Yihe Li --- launcher/ui/MainWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f413ac05d..0e6deacee 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -597,7 +597,6 @@ void MainWindow::updateInstanceRoot() messageBox.setIcon(QMessageBox::Question); auto checkBox = new QCheckBox(tr("&Remember my choice"), this); - checkBox->setChecked(true); messageBox.setCheckBox(checkBox); auto res = messageBox.exec(); From 7ea5b6173c14a269eb544de00661f38d3c6a8c33 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 04:51:13 +0800 Subject: [PATCH 056/181] Refactor updateInstanceRoot() to BaseInstance Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 60 ++++++++++++++++++++++++++++++++++ launcher/BaseInstance.h | 3 ++ launcher/InstanceList.cpp | 7 +--- launcher/InstanceList.h | 1 - launcher/ui/MainWindow.cpp | 66 +++----------------------------------- launcher/ui/MainWindow.h | 2 -- 6 files changed, 69 insertions(+), 70 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index ccfd0b847..886075624 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -37,11 +37,13 @@ #include "BaseInstance.h" +#include #include #include #include #include #include +#include #include #include "settings/INISettingsObject.h" @@ -327,6 +329,64 @@ QString BaseInstance::instanceRoot() const return m_rootDir; } +bool BaseInstance::updateInstanceRoot(QWidget* parent) +{ + QString renamingMode = globalSettings()->get("InstRenamingMode").toString(); + if (renamingMode == "MetadataOnly") + return false; + + auto oldRoot = instanceRoot(); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); + if (oldRoot == newRoot) + return false; + + // Check for conflict + if (QDir(newRoot).exists()) { + QMessageBox::warning(parent, tr("Cannot rename instance"), + tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); + return false; + } + + // Ask if we should rename + if (renamingMode == "AskEverytime") { + QMessageBox messageBox(parent); + messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); + messageBox.setInformativeText(tr("The following renaming operation will be performed:
" + " - Old instance root: %1
" + " - New instance root: %2") + .arg(oldRoot, newRoot)); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::Yes); + messageBox.setIcon(QMessageBox::Question); + + auto checkBox = new QCheckBox(tr("&Remember my choice"), parent); + messageBox.setCheckBox(checkBox); + + auto res = messageBox.exec(); + if (checkBox->isChecked()) { + if (res == QMessageBox::Yes) + globalSettings()->set("InstRenamingMode", "PhysicalDir"); + else + globalSettings()->set("InstRenamingMode", "MetadataOnly"); + } + if (res == QMessageBox::No) + return false; + } + + // Now we can confirm that a renaming is happening + auto ret = QFile::rename(oldRoot, newRoot); + if (!ret) { + QMessageBox::warning(parent, tr("Cannot rename instance"), + tr("An error occurred when performing the following renaming operation:
" + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") + .arg(oldRoot, newRoot)); + return false; + } + return true; +} + SettingsObjectPtr BaseInstance::settings() { loadSpecificSettings(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9827a08b4..8efb9e9d8 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -117,6 +117,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this(FS::PathCombine(instanceRoot, "instance.cfg")); InstancePtr inst; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index b04a289d2..c85fe55c7 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -152,7 +152,6 @@ class InstanceList : public QAbstractListModel { QMimeData* mimeData(const QModelIndexList& indexes) const override; QStringList getLinkedInstancesById(const QString& id) const; - QString getInstanceRootById(const InstanceId& id) const; signals: void dataIsInvalid(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 0e6deacee..17e5a0bc9 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -565,66 +565,6 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos) myMenu.exec(view->mapToGlobal(pos)); } -void MainWindow::updateInstanceRoot() -{ - QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); - if (renamingMode == "MetadataOnly") - return; - - auto oldRoot = m_selectedInstance->instanceRoot(); - auto newID = m_selectedInstance->name(); - auto newRoot = APPLICATION->instances()->getInstanceRootById(newID); - if (oldRoot == newRoot) - return; - - // Check for conflict - if (QDir(newRoot).exists()) { - QMessageBox::warning(this, tr("Cannot rename instance"), - tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); - return; - } - - // Ask if we should rename - if (renamingMode == "AskEverytime") { - QMessageBox messageBox(this); - messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); - messageBox.setInformativeText(tr("The following renaming operation will be performed:
" - " - Old instance root: %1
" - " - New instance root: %2") - .arg(oldRoot, newRoot)); - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::Yes); - messageBox.setIcon(QMessageBox::Question); - - auto checkBox = new QCheckBox(tr("&Remember my choice"), this); - messageBox.setCheckBox(checkBox); - - auto res = messageBox.exec(); - if (checkBox->isChecked()) { - if (res == QMessageBox::Yes) - APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); - else - APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); - } - if (res == QMessageBox::No) - return; - } - - // Now we can confirm that a renaming is happening - auto ret = QFile::rename(oldRoot, newRoot); - if (!ret) { - QMessageBox::warning(this, tr("Cannot rename instance"), - tr("An error occurred when performing the following renaming operation:
" - " - Old instance root: %1
" - " - New instance root: %2
" - "Only the metadata is renamed.") - .arg(oldRoot, newRoot)); - return; - } - refreshInstances(); - setSelectedInstanceById(newID); -} - void MainWindow::updateMainToolBar() { ui->menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); @@ -1764,7 +1704,11 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - updateInstanceRoot(); + if (m_selectedInstance && m_selectedInstance->updateInstanceRoot(this)) { + auto newID = m_selectedInstance->name(); + refreshInstances(); + setSelectedInstanceById(newID); + } } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 9ac1d39e0..0e692eda7 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -176,8 +176,6 @@ class MainWindow : public QMainWindow { void showInstanceContextMenu(const QPoint&); - void updateInstanceRoot(); - void updateMainToolBar(); void updateLaunchButton(); From b550a6c5c488ca441199f1be16e492302267805e Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 05:20:58 +0800 Subject: [PATCH 057/181] Revert the radio button changes Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 8 +- launcher/ui/pages/global/LauncherPage.cpp | 27 +- launcher/ui/pages/global/LauncherPage.ui | 287 +++++++++++----------- 3 files changed, 166 insertions(+), 156 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 886075624..92353da10 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -336,6 +336,7 @@ bool BaseInstance::updateInstanceRoot(QWidget* parent) return false; auto oldRoot = instanceRoot(); + auto oldName = QFileInfo(oldRoot).baseName(); auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); if (oldRoot == newRoot) return false; @@ -350,11 +351,8 @@ bool BaseInstance::updateInstanceRoot(QWidget* parent) // Ask if we should rename if (renamingMode == "AskEverytime") { QMessageBox messageBox(parent); - messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); - messageBox.setInformativeText(tr("The following renaming operation will be performed:
" - " - Old instance root: %1
" - " - New instance root: %2") - .arg(oldRoot, newRoot)); + messageBox.setText(tr("Would you also like to rename the instance folder?")); + messageBox.setInformativeText(tr("Renaming \'%1\' -> \'%2\'").arg(oldName, name())); messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); messageBox.setDefaultButton(QMessageBox::Yes); messageBox.setIcon(QMessageBox::Question); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index db37d548a..d89a65a85 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -67,17 +67,20 @@ enum InstSortMode { enum InstRenamingMode { // Rename metadata only. - Rename_Metadata, - // Rename physical directory too. - Rename_Physical, + Rename_Always, // Ask everytime. - Rename_Ask + Rename_Ask, + // Rename physical directory too. + Rename_Never }; LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) { ui->setupUi(this); + ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); + ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); + defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); m_languageModel = APPLICATION->translations(); @@ -241,7 +244,7 @@ void LauncherPage::applySettings() s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked()); // Instance - auto sortMode = (InstSortMode)ui->viewSortingComboBox->currentIndex(); + auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); switch (sortMode) { case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); @@ -254,10 +257,10 @@ void LauncherPage::applySettings() auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex(); switch (renamingMode) { - case Rename_Metadata: + case Rename_Always: s->set("InstRenamingMode", "MetadataOnly"); break; - case Rename_Physical: + case Rename_Never: s->set("InstRenamingMode", "PhysicalDir"); break; case Rename_Ask: @@ -322,20 +325,18 @@ void LauncherPage::loadSettings() // Instance QString sortMode = s->get("InstSortMode").toString(); - InstSortMode sortModeEnum; if (sortMode == "LastLaunch") { - sortModeEnum = Sort_LastLaunch; + ui->sortLastLaunchedBtn->setChecked(true); } else { - sortModeEnum = Sort_Name; + ui->sortByNameBtn->setChecked(true); } - ui->viewSortingComboBox->setCurrentIndex(sortModeEnum); QString renamingMode = s->get("InstRenamingMode").toString(); InstRenamingMode renamingModeEnum; if (renamingMode == "MetadataOnly") { - renamingModeEnum = Rename_Metadata; + renamingModeEnum = Rename_Always; } else if (renamingMode == "PhysicalDir") { - renamingModeEnum = Rename_Physical; + renamingModeEnum = Rename_Never; } else { renamingModeEnum = Rename_Ask; } diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index c1ba1d428..7942f4f4f 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -112,40 +112,76 @@ Folders - - + + - &Downloads: + I&nstances: - downloadsDirTextBox + instDirTextBox - - + + + + + Browse - - - - - - - - + + + - &Skins: - - - skinsDirTextBox + Rename instance folders + + + + + Never + + + + + Ask + + + + + Always + + + + + + + + &Mods: + + + modsDirTextBox + + + + + + + + + + Browse + + + + + &Icons: @@ -155,7 +191,81 @@ - + + + + + + + Browse + + + + + + + + &Java: + + + javaDirTextBox + + + + + + + + + + Browse + + + + + + + + &Skins: + + + skinsDirTextBox + + + + + + + + + + Browse + + + + + + + + &Downloads: + + + downloadsDirTextBox + + + + + + + + + + Browse + + + + + @@ -179,83 +289,6 @@ - - - - - - - &Java: - - - javaDirTextBox - - - - - - - &Mods: - - - modsDirTextBox - - - - - - - - - - - - - - - - Browse - - - - - - - Browse - - - - - - - Browse - - - - - - - I&nstances: - - - instDirTextBox - - - - - - - Browse - - - - - - - Browse - - -
@@ -401,59 +434,32 @@ - + true - Instance + Instance view sorting mode - - - + + + - Instance view sorting mode + &By last launched + + sortingModeGroup + - - - - - By last launched - - - - - By name - - - - - - + + - Instance renaming behavior + By &name - - - - - - - Rename metadata only - - - - - Rename physical directory - - - - - Ask everytime - - + + sortingModeGroup + @@ -701,6 +707,8 @@ numberOfConcurrentDownloadsSpinBox numberOfManualRetriesSpinBox timeoutSecondsSpinBox + sortLastLaunchedBtn + sortByNameBtn catOpacitySpinBox preferMenuBarCheckBox lineLimitSpinBox @@ -711,4 +719,7 @@ + + + \ No newline at end of file From 7b511f4c676392c077c9ae4b90de36464f0221a9 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 05:47:29 +0800 Subject: [PATCH 058/181] Refactor into InstanceDirUpdate.h Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 56 ------------------- launcher/BaseInstance.h | 3 -- launcher/CMakeLists.txt | 2 + launcher/InstanceDirUpdate.cpp | 99 ++++++++++++++++++++++++++++++++++ launcher/InstanceDirUpdate.h | 43 +++++++++++++++ launcher/ui/MainWindow.cpp | 3 +- 6 files changed, 146 insertions(+), 60 deletions(-) create mode 100644 launcher/InstanceDirUpdate.cpp create mode 100644 launcher/InstanceDirUpdate.h diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 92353da10..0087b44f0 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -329,62 +329,6 @@ QString BaseInstance::instanceRoot() const return m_rootDir; } -bool BaseInstance::updateInstanceRoot(QWidget* parent) -{ - QString renamingMode = globalSettings()->get("InstRenamingMode").toString(); - if (renamingMode == "MetadataOnly") - return false; - - auto oldRoot = instanceRoot(); - auto oldName = QFileInfo(oldRoot).baseName(); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); - if (oldRoot == newRoot) - return false; - - // Check for conflict - if (QDir(newRoot).exists()) { - QMessageBox::warning(parent, tr("Cannot rename instance"), - tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); - return false; - } - - // Ask if we should rename - if (renamingMode == "AskEverytime") { - QMessageBox messageBox(parent); - messageBox.setText(tr("Would you also like to rename the instance folder?")); - messageBox.setInformativeText(tr("Renaming \'%1\' -> \'%2\'").arg(oldName, name())); - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::Yes); - messageBox.setIcon(QMessageBox::Question); - - auto checkBox = new QCheckBox(tr("&Remember my choice"), parent); - messageBox.setCheckBox(checkBox); - - auto res = messageBox.exec(); - if (checkBox->isChecked()) { - if (res == QMessageBox::Yes) - globalSettings()->set("InstRenamingMode", "PhysicalDir"); - else - globalSettings()->set("InstRenamingMode", "MetadataOnly"); - } - if (res == QMessageBox::No) - return false; - } - - // Now we can confirm that a renaming is happening - auto ret = QFile::rename(oldRoot, newRoot); - if (!ret) { - QMessageBox::warning(parent, tr("Cannot rename instance"), - tr("An error occurred when performing the following renaming operation:
" - " - Old instance root: %1
" - " - New instance root: %2
" - "Only the metadata is renamed.") - .arg(oldRoot, newRoot)); - return false; - } - return true; -} - SettingsObjectPtr BaseInstance::settings() { loadSpecificSettings(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 8efb9e9d8..9827a08b4 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -117,9 +117,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this + * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2023 TheKodeToad + * + * 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 . + * + * 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 "InstanceDirUpdate.h" + +#include +#include + +#include "FileSystem.h" + +bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent) +{ + QString renamingMode = globalSettings->get("InstRenamingMode").toString(); + if (renamingMode == "MetadataOnly") + return false; + + auto oldRoot = instance->instanceRoot(); + auto oldName = QFileInfo(oldRoot).baseName(); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), instance->name()); + if (oldRoot == newRoot) + return false; + + // Check for conflict + if (QDir(newRoot).exists()) { + QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), + QObject::tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); + return false; + } + + // Ask if we should rename + if (renamingMode == "AskEverytime") { + QMessageBox messageBox(parent); + messageBox.setText(QObject::tr("Would you also like to rename the instance folder?")); + messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, instance->name())); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::Yes); + messageBox.setIcon(QMessageBox::Question); + + auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent); + messageBox.setCheckBox(checkBox); + + auto res = messageBox.exec(); + if (checkBox->isChecked()) { + if (res == QMessageBox::Yes) + globalSettings->set("InstRenamingMode", "PhysicalDir"); + else + globalSettings->set("InstRenamingMode", "MetadataOnly"); + } + if (res == QMessageBox::No) + return false; + } + + // Now we can confirm that a renaming is happening + auto ret = QFile::rename(oldRoot, newRoot); + if (!ret) { + QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), + QObject::tr("An error occurred when performing the following renaming operation:
" + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") + .arg(oldRoot, newRoot)); + return false; + } + return true; +} diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h new file mode 100644 index 000000000..fc0931779 --- /dev/null +++ b/launcher/InstanceDirUpdate.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2023 TheKodeToad + * + * 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 . + * + * 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 + +#include "BaseInstance.h" + +/// Update instanceRoot to make it sync with name/id; return true if a refresh is needed +bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 17e5a0bc9..23cd6f8c8 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -124,6 +124,7 @@ #include "KonamiCode.h" #include "InstanceCopyTask.h" +#include "InstanceDirUpdate.h" #include "Json.h" @@ -1704,7 +1705,7 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - if (m_selectedInstance && m_selectedInstance->updateInstanceRoot(this)) { + if (m_selectedInstance && askToUpdateInstanceDirName(APPLICATION->settings(), m_selectedInstance, this)) { auto newID = m_selectedInstance->name(); refreshInstances(); setSelectedInstanceById(newID); From ea44c2465c30ad048df7047007059d668edd6cfb Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 05:47:47 +0800 Subject: [PATCH 059/181] Add link instance detection Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 41 ++++++++++++++++++++++++++++------ launcher/InstanceDirUpdate.h | 5 ++++- launcher/ui/MainWindow.cpp | 18 +++------------ 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index f24adb642..0b61ddb88 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -40,11 +40,15 @@ #include #include +#include "Application.h" #include "FileSystem.h" -bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent) +#include "InstanceList.h" +#include "ui/dialogs/CustomMessageBox.h" + +bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) { - QString renamingMode = globalSettings->get("InstRenamingMode").toString(); + QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); if (renamingMode == "MetadataOnly") return false; @@ -76,24 +80,47 @@ bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr in auto res = messageBox.exec(); if (checkBox->isChecked()) { if (res == QMessageBox::Yes) - globalSettings->set("InstRenamingMode", "PhysicalDir"); + APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); else - globalSettings->set("InstRenamingMode", "MetadataOnly"); + APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); } if (res == QMessageBox::No) return false; } + // Check for linked instances + if (!checkLinkedInstances(instance->id(), parent)) + return false; + // Now we can confirm that a renaming is happening auto ret = QFile::rename(oldRoot, newRoot); if (!ret) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("An error occurred when performing the following renaming operation:
" - " - Old instance root: %1
" - " - New instance root: %2
" - "Only the metadata is renamed.") + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") .arg(oldRoot, newRoot)); return false; } return true; } + +bool checkLinkedInstances(const QString& id, QWidget* parent) +{ + auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); + if (!linkedInstances.empty()) { + auto response = CustomMessageBox::selectable(parent, QObject::tr("There are linked instances"), + QObject::tr("The following instance(s) might reference files in this instance:\n\n" + "%1\n\n" + "Deleting it could break the other instance(s), \n\n" + "Do you wish to proceed?", + nullptr, linkedInstances.count()) + .arg(linkedInstances.join("\n")), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + if (response != QMessageBox::Yes) + return false; + } + return true; +} diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index fc0931779..0059d8b7b 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -40,4 +40,7 @@ #include "BaseInstance.h" /// Update instanceRoot to make it sync with name/id; return true if a refresh is needed -bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent); +bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); + +/// Check if there are linked instances, and display a warning; return true if the operation should proceed +bool checkLinkedInstances(const QString& id, QWidget* parent); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 23cd6f8c8..d5a2b477d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1379,20 +1379,8 @@ void MainWindow::on_actionDeleteInstance_triggered() if (response != QMessageBox::Yes) return; - auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); - if (!linkedInstances.empty()) { - response = CustomMessageBox::selectable(this, tr("There are linked instances"), - tr("The following instance(s) might reference files in this instance:\n\n" - "%1\n\n" - "Deleting it could break the other instance(s), \n\n" - "Do you wish to proceed?", - nullptr, linkedInstances.count()) - .arg(linkedInstances.join("\n")), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); - if (response != QMessageBox::Yes) - return; - } + if (!checkLinkedInstances(id, this)) + return; if (APPLICATION->instances()->trashInstance(id)) { ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); @@ -1705,7 +1693,7 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - if (m_selectedInstance && askToUpdateInstanceDirName(APPLICATION->settings(), m_selectedInstance, this)) { + if (m_selectedInstance && askToUpdateInstanceDirName(m_selectedInstance, this)) { auto newID = m_selectedInstance->name(); refreshInstances(); setSelectedInstanceById(newID); From 8fea37b8b73f09d35d73e54f059d62dd7b6883ae Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 06:53:57 +0800 Subject: [PATCH 060/181] Only call on interactive uses Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 11 +++++++++++ launcher/BaseInstance.h | 3 +++ launcher/InstanceDirUpdate.cpp | 3 +-- launcher/ui/MainWindow.cpp | 12 +++++++----- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0087b44f0..5ffbb809b 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -388,6 +388,17 @@ void BaseInstance::setName(QString val) emit propertiesChanged(this); } +bool BaseInstance::syncInstanceDirName() const +{ + auto oldRoot = instanceRoot(); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); + if (oldRoot == newRoot) + return true; + if (!QFile::rename(oldRoot, newRoot)) + return false; + return true; +} + QString BaseInstance::name() const { return m_settings->get("name").toString(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9827a08b4..b19f1fd27 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -126,6 +126,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_thissyncInstanceDirName()) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("An error occurred when performing the following renaming operation:
" " - Old instance root: %1
" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d5a2b477d..f357b0765 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1428,6 +1428,13 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered() void MainWindow::on_actionRenameInstance_triggered() { if (m_selectedInstance) { + connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { + if (askToUpdateInstanceDirName(m_selectedInstance, this)) { + auto newID = m_selectedInstance->name(); + refreshInstances(); + setSelectedInstanceById(newID); + } + }, Qt::SingleShotConnection); view->edit(view->currentIndex()); } } @@ -1693,11 +1700,6 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - if (m_selectedInstance && askToUpdateInstanceDirName(m_selectedInstance, this)) { - auto newID = m_selectedInstance->name(); - refreshInstances(); - setSelectedInstanceById(newID); - } } } From 294448a01e07f44e0df99f67541f2a18e24069ac Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 06:54:10 +0800 Subject: [PATCH 061/181] Filter out invalid chars Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 3 +-- launcher/BaseInstance.h | 2 +- launcher/InstanceDirUpdate.cpp | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 5ffbb809b..92d41521b 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -388,10 +388,9 @@ void BaseInstance::setName(QString val) emit propertiesChanged(this); } -bool BaseInstance::syncInstanceDirName() const +bool BaseInstance::syncInstanceDirName(const QString& newRoot) const { auto oldRoot = instanceRoot(); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); if (oldRoot == newRoot) return true; if (!QFile::rename(oldRoot, newRoot)) diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index b19f1fd27..2a2b4dc4a 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -127,7 +127,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_thisinstanceRoot(); auto oldName = QFileInfo(oldRoot).baseName(); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), instance->name()); + auto newName = FS::DirNameFromString(instance->name(), QFileInfo(oldRoot).dir().absolutePath()); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName); if (oldRoot == newRoot) return false; @@ -69,7 +70,7 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) if (renamingMode == "AskEverytime") { QMessageBox messageBox(parent); messageBox.setText(QObject::tr("Would you also like to rename the instance folder?")); - messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, instance->name())); + messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, newName)); messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); messageBox.setDefaultButton(QMessageBox::Yes); messageBox.setIcon(QMessageBox::Question); @@ -93,7 +94,7 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) return false; // Now we can confirm that a renaming is happening - if (!instance->syncInstanceDirName()) { + if (!instance->syncInstanceDirName(newRoot)) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("An error occurred when performing the following renaming operation:
" " - Old instance root: %1
" From a7af120cf0635f048e5df18e6ca6c1e853071825 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 07:22:05 +0800 Subject: [PATCH 062/181] Fix interaction with invalid chars Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 19 +++++++++++-------- launcher/InstanceDirUpdate.h | 4 ++-- launcher/InstanceList.cpp | 17 +++++++++++++++++ launcher/InstanceList.h | 2 ++ launcher/ui/MainWindow.cpp | 25 ++++++++++++++++++------- launcher/ui/MainWindow.h | 1 + 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index a6ca6ca23..8064082fd 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -46,24 +46,27 @@ #include "InstanceList.h" #include "ui/dialogs/CustomMessageBox.h" -bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) +QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) { QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); if (renamingMode == "MetadataOnly") - return false; + return QString(); auto oldRoot = instance->instanceRoot(); auto oldName = QFileInfo(oldRoot).baseName(); + if (oldName == FS::RemoveInvalidFilenameChars(instance->name(), '-')) + return QString(); + auto newName = FS::DirNameFromString(instance->name(), QFileInfo(oldRoot).dir().absolutePath()); auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName); if (oldRoot == newRoot) - return false; + return QString(); // Check for conflict if (QDir(newRoot).exists()) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); - return false; + return QString(); } // Ask if we should rename @@ -86,12 +89,12 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); } if (res == QMessageBox::No) - return false; + return QString(); } // Check for linked instances if (!checkLinkedInstances(instance->id(), parent)) - return false; + return QString(); // Now we can confirm that a renaming is happening if (!instance->syncInstanceDirName(newRoot)) { @@ -101,9 +104,9 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) " - New instance root: %2
" "Only the metadata is renamed.") .arg(oldRoot, newRoot)); - return false; + return QString(); } - return true; + return newRoot; } bool checkLinkedInstances(const QString& id, QWidget* parent) diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 0059d8b7b..23738a19a 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -39,8 +39,8 @@ #include "BaseInstance.h" -/// Update instanceRoot to make it sync with name/id; return true if a refresh is needed -bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); +/// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened +QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); /// Check if there are linked instances, and display a warning; return true if the operation should proceed bool checkLinkedInstances(const QString& id, QWidget* parent); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..a4f694e2e 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -583,6 +583,18 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } +InstancePtr InstanceList::getInstanceByRoot(QString instanceRoot) const +{ + if (instanceRoot.isEmpty()) + return InstancePtr(); + for (auto& inst : m_instances) { + if (inst->instanceRoot() == instanceRoot) { + return inst; + } + } + return InstancePtr(); +} + InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const { if (managed_name.isEmpty()) @@ -601,6 +613,11 @@ QModelIndex InstanceList::getInstanceIndexById(const QString& id) const return index(getInstIndex(getInstanceById(id).get())); } +QModelIndex InstanceList::getInstanceIndexByRoot(const QString& instanceRoot) const +{ + return index(getInstIndex(getInstanceByRoot(instanceRoot).get())); +} + int InstanceList::getInstIndex(BaseInstance* inst) const { int count = m_instances.count(); diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index c85fe55c7..dace9e5cf 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -99,9 +99,11 @@ class InstanceList : public QAbstractListModel { /* O(n) */ InstancePtr getInstanceById(QString id) const; + InstancePtr getInstanceByRoot(QString instanceRoot) const; /* O(n) */ InstancePtr getInstanceByManagedName(const QString& managed_name) const; QModelIndex getInstanceIndexById(const QString& id) const; + QModelIndex getInstanceIndexByRoot(const QString& instanceRoot) const; QStringList getGroups(); bool isGroupCollapsed(const QString& groupName); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f357b0765..43fe5c067 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -294,6 +294,12 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi view->setFrameShape(QFrame::NoFrame); // do not show ugly blue border on the mac view->setAttribute(Qt::WA_MacShowFocusRect, false); + connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { + if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, this); !newRoot.isEmpty()) { + refreshInstances(); + setSelectedInstanceByRoot(newRoot); + } + }); view->installEventFilter(this); view->setContextMenuPolicy(Qt::CustomContextMenu); @@ -1133,6 +1139,18 @@ void MainWindow::setSelectedInstanceById(const QString& id) } } +void MainWindow::setSelectedInstanceByRoot(const QString& instanceRoot) +{ + if (instanceRoot.isNull()) + return; + const QModelIndex index = APPLICATION->instances()->getInstanceIndexByRoot(instanceRoot); + if (index.isValid()) { + QModelIndex selectionIndex = proxymodel->mapFromSource(index); + view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); + updateStatusCenter(); + } +} + void MainWindow::on_actionChangeInstGroup_triggered() { if (!m_selectedInstance) @@ -1428,13 +1446,6 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered() void MainWindow::on_actionRenameInstance_triggered() { if (m_selectedInstance) { - connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { - if (askToUpdateInstanceDirName(m_selectedInstance, this)) { - auto newID = m_selectedInstance->name(); - refreshInstances(); - setSelectedInstanceById(newID); - } - }, Qt::SingleShotConnection); view->edit(view->currentIndex()); } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0e692eda7..02e9bb31e 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -224,6 +224,7 @@ class MainWindow : public QMainWindow { void setCatBackground(bool enabled); void updateInstanceToolIcon(QString new_icon); void setSelectedInstanceById(const QString& id); + void setSelectedInstanceByRoot(const QString& instanceRoot); void updateStatusCenter(); void setInstanceActionsEnabled(bool enabled); From 45368fbf2f93aba8d224058d4ee53284e28e6f23 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 07:31:24 +0800 Subject: [PATCH 063/181] Get rid of unused includes Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 2 -- launcher/ui/MainWindow.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 92d41521b..114275b3c 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -37,13 +37,11 @@ #include "BaseInstance.h" -#include #include #include #include #include #include -#include #include #include "settings/INISettingsObject.h" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 43fe5c067..05afea0b4 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include From 11a0dbf81059a39521d12c43fde7c5f07175ada2 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 15:59:10 +0800 Subject: [PATCH 064/181] Make deleting a verb Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 9 +++++---- launcher/InstanceDirUpdate.h | 2 +- launcher/ui/MainWindow.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 8064082fd..49010e6c9 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -93,7 +93,7 @@ QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) } // Check for linked instances - if (!checkLinkedInstances(instance->id(), parent)) + if (!checkLinkedInstances(instance->id(), parent, QObject::tr("Renaming"))) return QString(); // Now we can confirm that a renaming is happening @@ -109,17 +109,18 @@ QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) return newRoot; } -bool checkLinkedInstances(const QString& id, QWidget* parent) +bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb) { auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); if (!linkedInstances.empty()) { auto response = CustomMessageBox::selectable(parent, QObject::tr("There are linked instances"), QObject::tr("The following instance(s) might reference files in this instance:\n\n" "%1\n\n" - "Deleting it could break the other instance(s), \n\n" + "%2 it could break the other instance(s), \n\n" "Do you wish to proceed?", nullptr, linkedInstances.count()) - .arg(linkedInstances.join("\n")), + .arg(linkedInstances.join("\n")) + .arg(verb), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); if (response != QMessageBox::Yes) diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 23738a19a..354eba465 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -43,4 +43,4 @@ QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); /// Check if there are linked instances, and display a warning; return true if the operation should proceed -bool checkLinkedInstances(const QString& id, QWidget* parent); +bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 05afea0b4..7e9c72da7 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1396,7 +1396,7 @@ void MainWindow::on_actionDeleteInstance_triggered() if (response != QMessageBox::Yes) return; - if (!checkLinkedInstances(id, this)) + if (!checkLinkedInstances(id, this, tr("Deleting"))) return; if (APPLICATION->instances()->trashInstance(id)) { From a2e44c0ef76a73befc9c31e243b675d8df6eb751 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 17:13:38 +0800 Subject: [PATCH 065/181] Use custom signals to record previous name Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 15 ++++++------- launcher/InstanceDirUpdate.h | 2 +- launcher/InstanceList.cpp | 17 --------------- launcher/InstanceList.h | 2 -- launcher/ui/MainWindow.cpp | 21 +++++-------------- launcher/ui/MainWindow.h | 1 - launcher/ui/instanceview/InstanceDelegate.cpp | 1 + launcher/ui/instanceview/InstanceDelegate.h | 3 +++ 8 files changed, 18 insertions(+), 44 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 49010e6c9..ac6dddc37 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -46,21 +46,22 @@ #include "InstanceList.h" #include "ui/dialogs/CustomMessageBox.h" -QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) +QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent) { + if (oldName == newName) + return QString(); + QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); if (renamingMode == "MetadataOnly") return QString(); auto oldRoot = instance->instanceRoot(); - auto oldName = QFileInfo(oldRoot).baseName(); - if (oldName == FS::RemoveInvalidFilenameChars(instance->name(), '-')) - return QString(); - - auto newName = FS::DirNameFromString(instance->name(), QFileInfo(oldRoot).dir().absolutePath()); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName); + auto newDirName = FS::DirNameFromString(newName, QFileInfo(oldRoot).dir().absolutePath()); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newDirName); if (oldRoot == newRoot) return QString(); + if (oldRoot == FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName)) + return QString(); // Check for conflict if (QDir(newRoot).exists()) { diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 354eba465..94dd3b933 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -40,7 +40,7 @@ #include "BaseInstance.h" /// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened -QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); +QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent); /// Check if there are linked instances, and display a warning; return true if the operation should proceed bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index a4f694e2e..918fa1073 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -583,18 +583,6 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } -InstancePtr InstanceList::getInstanceByRoot(QString instanceRoot) const -{ - if (instanceRoot.isEmpty()) - return InstancePtr(); - for (auto& inst : m_instances) { - if (inst->instanceRoot() == instanceRoot) { - return inst; - } - } - return InstancePtr(); -} - InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const { if (managed_name.isEmpty()) @@ -613,11 +601,6 @@ QModelIndex InstanceList::getInstanceIndexById(const QString& id) const return index(getInstIndex(getInstanceById(id).get())); } -QModelIndex InstanceList::getInstanceIndexByRoot(const QString& instanceRoot) const -{ - return index(getInstIndex(getInstanceByRoot(instanceRoot).get())); -} - int InstanceList::getInstIndex(BaseInstance* inst) const { int count = m_instances.count(); diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index dace9e5cf..c85fe55c7 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -99,11 +99,9 @@ class InstanceList : public QAbstractListModel { /* O(n) */ InstancePtr getInstanceById(QString id) const; - InstancePtr getInstanceByRoot(QString instanceRoot) const; /* O(n) */ InstancePtr getInstanceByManagedName(const QString& managed_name) const; QModelIndex getInstanceIndexById(const QString& id) const; - QModelIndex getInstanceIndexByRoot(const QString& instanceRoot) const; QStringList getGroups(); bool isGroupCollapsed(const QString& groupName); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 7e9c72da7..6c5f86d0a 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -289,14 +289,15 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi view->setSelectionMode(QAbstractItemView::SingleSelection); // FIXME: leaks ListViewDelegate - view->setItemDelegate(new ListViewDelegate(this)); + auto delegate = new ListViewDelegate(this); + view->setItemDelegate(delegate); view->setFrameShape(QFrame::NoFrame); // do not show ugly blue border on the mac view->setAttribute(Qt::WA_MacShowFocusRect, false); - connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { - if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, this); !newRoot.isEmpty()) { + connect(delegate, &ListViewDelegate::textChanged, this, [this](QString before, QString after) { + if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, before, after, this); !newRoot.isEmpty()) { refreshInstances(); - setSelectedInstanceByRoot(newRoot); + setSelectedInstanceById(QFileInfo(newRoot).fileName()); } }); @@ -1138,18 +1139,6 @@ void MainWindow::setSelectedInstanceById(const QString& id) } } -void MainWindow::setSelectedInstanceByRoot(const QString& instanceRoot) -{ - if (instanceRoot.isNull()) - return; - const QModelIndex index = APPLICATION->instances()->getInstanceIndexByRoot(instanceRoot); - if (index.isValid()) { - QModelIndex selectionIndex = proxymodel->mapFromSource(index); - view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); - updateStatusCenter(); - } -} - void MainWindow::on_actionChangeInstGroup_triggered() { if (!m_selectedInstance) diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 02e9bb31e..0e692eda7 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -224,7 +224,6 @@ class MainWindow : public QMainWindow { void setCatBackground(bool enabled); void updateInstanceToolIcon(QString new_icon); void setSelectedInstanceById(const QString& id); - void setSelectedInstanceByRoot(const QString& instanceRoot); void updateStatusCenter(); void setInstanceActionsEnabled(bool enabled); diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp index d947163bc..c7115801e 100644 --- a/launcher/ui/instanceview/InstanceDelegate.cpp +++ b/launcher/ui/instanceview/InstanceDelegate.cpp @@ -397,6 +397,7 @@ void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, // Prevent instance names longer than 128 chars text.truncate(128); if (text.size() != 0) { + emit textChanged(model->data(index).toString(), text); model->setData(index, text); } } diff --git a/launcher/ui/instanceview/InstanceDelegate.h b/launcher/ui/instanceview/InstanceDelegate.h index 69dd32ba7..98ff9a2fc 100644 --- a/launcher/ui/instanceview/InstanceDelegate.h +++ b/launcher/ui/instanceview/InstanceDelegate.h @@ -33,6 +33,9 @@ class ListViewDelegate : public QStyledItemDelegate { void setEditorData(QWidget* editor, const QModelIndex& index) const override; void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + signals: + void textChanged(QString before, QString after) const; + private slots: void editingDone(); }; From 694959ef7f56e5f8d0f7918b61bd059483707cab Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 20:25:30 +0800 Subject: [PATCH 066/181] Remove names from header comments Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 3 --- launcher/InstanceDirUpdate.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index ac6dddc37..65a62129b 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -1,9 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2023 TheKodeToad * * 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 diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 94dd3b933..b92a59c4c 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -1,9 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2023 TheKodeToad * * 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 From 02ca6bea8c4261776500862159d61817abf246cc Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sat, 29 Mar 2025 14:11:09 +0800 Subject: [PATCH 067/181] Sync group after updating instance Signed-off-by: Yihe Li --- launcher/ui/MainWindow.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 6c5f86d0a..ddf726373 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -296,8 +296,18 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi view->setAttribute(Qt::WA_MacShowFocusRect, false); connect(delegate, &ListViewDelegate::textChanged, this, [this](QString before, QString after) { if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, before, after, this); !newRoot.isEmpty()) { + auto oldID = m_selectedInstance->id(); + auto newID = QFileInfo(newRoot).fileName(); + QString origGroup(APPLICATION->instances()->getInstanceGroup(oldID)); + bool syncGroup = origGroup != GroupId() && oldID != newID; + if (syncGroup) + APPLICATION->instances()->setInstanceGroup(oldID, GroupId()); + refreshInstances(); - setSelectedInstanceById(QFileInfo(newRoot).fileName()); + setSelectedInstanceById(newID); + + if (syncGroup) + APPLICATION->instances()->setInstanceGroup(newID, origGroup); } }); From 66c6399adedcb52c2b7391cdbf38602f8b2814a6 Mon Sep 17 00:00:00 2001 From: LAHarbottle <87842870+LAHarbottle@users.noreply.github.com> Date: Sat, 29 Mar 2025 17:17:57 +0000 Subject: [PATCH 068/181] Fix typo in NetRequest.cpp Signed-off-by: LAHarbottle <87842870+LAHarbottle@users.noreply.github.com> --- launcher/net/NetRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index cf2e72858..310653508 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -80,7 +80,7 @@ void NetRequest::executeTask() emit finished(); return; case State::Running: - qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString(); + qCDebug(logCat) << getUid().toString() << "Running " << m_url.toString(); break; case State::Inactive: case State::Failed: From 5b12d3cfff9092c0364e455c211fc189d87039cd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 30 Mar 2025 00:46:59 +0200 Subject: [PATCH 069/181] fix themes leak Signed-off-by: Trial97 --- launcher/ui/themes/SystemTheme.cpp | 36 ++++++++++++++++-------------- launcher/ui/themes/SystemTheme.h | 6 ++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 59644ddde..c755b379b 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -42,16 +42,18 @@ SystemTheme::SystemTheme(const QString& styleName, bool isDefaultTheme) { - themeName = isDefaultTheme ? "system" : styleName; - widgetTheme = styleName; - colorPalette = QStyleFactory::create(styleName)->standardPalette(); + m_themeName = isDefaultTheme ? "system" : styleName; + m_widgetTheme = styleName; + auto style = QStyleFactory::create(styleName); + m_colorPalette = style->standardPalette(); + delete style; } void SystemTheme::apply(bool initial) { // See https://github.com/MultiMC/Launcher/issues/1790 // or https://github.com/PrismLauncher/PrismLauncher/issues/490 - if (initial && themeName == "system") { + if (initial && m_themeName == "system") { QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); return; } @@ -61,35 +63,35 @@ void SystemTheme::apply(bool initial) QString SystemTheme::id() { - return themeName; + return m_themeName; } QString SystemTheme::name() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Windows Vista"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("Windows 11"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("System"); } else { - return themeName; + return m_themeName; } } QString SystemTheme::tooltip() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Widget style trying to look like your win32 theme"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x inspired widget style"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("WinUI 3 inspired Qt widget style"); - } else if (themeName.toLower() == "fusion") { + } else if (m_themeName.toLower() == "fusion") { return QObject::tr("The default Qt widget style"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("Your current system theme"); } else { return ""; @@ -98,12 +100,12 @@ QString SystemTheme::tooltip() QString SystemTheme::qtTheme() { - return widgetTheme; + return m_widgetTheme; } QPalette SystemTheme::colorScheme() { - return colorPalette; + return m_colorPalette; } QString SystemTheme::appStyleSheet() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 09db1a322..54404d052 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -53,7 +53,7 @@ class SystemTheme : public ITheme { QColor fadeColor() override; private: - QPalette colorPalette; - QString widgetTheme; - QString themeName; + QPalette m_colorPalette; + QString m_widgetTheme; + QString m_themeName; }; From 0487ab375415e535daefa0c3f78c1042b3b5060c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 30 Mar 2025 00:27:40 +0000 Subject: [PATCH 070/181] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/a84ebe20c6bc2ecbcfb000a50776219f48d134cc?narHash=sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ%3D' (2025-03-19) → 'github:NixOS/nixpkgs/5e5402ecbcb27af32284d4a62553c019a3a49ea6?narHash=sha256-gWd4urRoLRe8GLVC/3rYRae1h%2BxfQzt09xOfb0PaHSk%3D' (2025-03-27) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index ef4c9f555..fa30cfd33 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1742422364, - "narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=", + "lastModified": 1743095683, + "narHash": "sha256-gWd4urRoLRe8GLVC/3rYRae1h+xfQzt09xOfb0PaHSk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc", + "rev": "5e5402ecbcb27af32284d4a62553c019a3a49ea6", "type": "github" }, "original": { From da007d62123b3022f1afd09a7a3577405e5e4310 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 31 Mar 2025 07:11:05 +0800 Subject: [PATCH 071/181] Use CustomMessageBox::selectable Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 6 +----- launcher/InstanceDirUpdate.cpp | 18 ++++++++---------- launcher/ui/dialogs/CustomMessageBox.cpp | 5 ++++- launcher/ui/dialogs/CustomMessageBox.h | 3 ++- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 114275b3c..eab91a5eb 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -389,11 +389,7 @@ void BaseInstance::setName(QString val) bool BaseInstance::syncInstanceDirName(const QString& newRoot) const { auto oldRoot = instanceRoot(); - if (oldRoot == newRoot) - return true; - if (!QFile::rename(oldRoot, newRoot)) - return false; - return true; + return oldRoot == newRoot || QFile::rename(oldRoot, newRoot); } QString BaseInstance::name() const diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 65a62129b..8be0dccac 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -35,7 +35,6 @@ #include "InstanceDirUpdate.h" #include -#include #include "Application.h" #include "FileSystem.h" @@ -69,17 +68,16 @@ QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, // Ask if we should rename if (renamingMode == "AskEverytime") { - QMessageBox messageBox(parent); - messageBox.setText(QObject::tr("Would you also like to rename the instance folder?")); - messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, newName)); - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::Yes); - messageBox.setIcon(QMessageBox::Question); - auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent); - messageBox.setCheckBox(checkBox); + auto dialog = + CustomMessageBox::selectable(parent, QObject::tr("Rename instance folder"), + QObject::tr("Would you also like to rename the instance folder?\n\n" + "Old name: %1\n" + "New name: %2") + .arg(oldName, newName), + QMessageBox::Question, QMessageBox::No | QMessageBox::Yes, QMessageBox::NoButton, checkBox); - auto res = messageBox.exec(); + auto res = dialog->exec(); if (checkBox->isChecked()) { if (res == QMessageBox::Yes) APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); diff --git a/launcher/ui/dialogs/CustomMessageBox.cpp b/launcher/ui/dialogs/CustomMessageBox.cpp index 1af47a449..ca0fe99e0 100644 --- a/launcher/ui/dialogs/CustomMessageBox.cpp +++ b/launcher/ui/dialogs/CustomMessageBox.cpp @@ -21,7 +21,8 @@ QMessageBox* selectable(QWidget* parent, const QString& text, QMessageBox::Icon icon, QMessageBox::StandardButtons buttons, - QMessageBox::StandardButton defaultButton) + QMessageBox::StandardButton defaultButton, + QCheckBox* checkBox) { QMessageBox* messageBox = new QMessageBox(parent); messageBox->setWindowTitle(title); @@ -31,6 +32,8 @@ QMessageBox* selectable(QWidget* parent, messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); messageBox->setIcon(icon); messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + if (checkBox) + messageBox->setCheckBox(checkBox); return messageBox; } diff --git a/launcher/ui/dialogs/CustomMessageBox.h b/launcher/ui/dialogs/CustomMessageBox.h index a9bc6a24a..1ee29903e 100644 --- a/launcher/ui/dialogs/CustomMessageBox.h +++ b/launcher/ui/dialogs/CustomMessageBox.h @@ -23,5 +23,6 @@ QMessageBox* selectable(QWidget* parent, const QString& text, QMessageBox::Icon icon = QMessageBox::NoIcon, QMessageBox::StandardButtons buttons = QMessageBox::Ok, - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, + QCheckBox* checkBox = nullptr); } From 99852c972c6e9fc6a8caca727946a71a85c1672f Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 16:41:19 -0400 Subject: [PATCH 072/181] Reapply "refactor(nix): nix-filter -> lib.fileset" After extensive (5 minutes) of testing, it seems we don't actually come across any Nix bugs with lib.fileset! (aside from those inherit to it...but :shrug:) This reverts commit a49a58bc4571f87f42f45bb9eeb1a957d5d12331. Signed-off-by: Seth Flynn --- flake.lock | 16 ---------------- flake.nix | 4 ---- nix/unwrapped.nix | 22 +++++++++++----------- 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/flake.lock b/flake.lock index fa30cfd33..24f04474e 100644 --- a/flake.lock +++ b/flake.lock @@ -32,21 +32,6 @@ "type": "github" } }, - "nix-filter": { - "locked": { - "lastModified": 1731533336, - "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", - "owner": "numtide", - "repo": "nix-filter", - "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "nix-filter", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1743095683, @@ -67,7 +52,6 @@ "inputs": { "flake-compat": "flake-compat", "libnbtplusplus": "libnbtplusplus", - "nix-filter": "nix-filter", "nixpkgs": "nixpkgs" } } diff --git a/flake.nix b/flake.nix index 150240c8b..517a4b2ff 100644 --- a/flake.nix +++ b/flake.nix @@ -16,8 +16,6 @@ flake = false; }; - nix-filter.url = "github:numtide/nix-filter"; - /* Inputs below this are optional and can be removed @@ -44,7 +42,6 @@ self, nixpkgs, libnbtplusplus, - nix-filter, ... }: let @@ -100,7 +97,6 @@ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { inherit libnbtplusplus - nix-filter self ; }; diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 7ba20b68b..0a4d2e253 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -11,7 +11,6 @@ kdePackages, libnbtplusplus, ninja, - nix-filter, self, stripJavaArchivesHook, tomlplusplus, @@ -29,17 +28,18 @@ stdenv.mkDerivation { pname = "prismlauncher-unwrapped"; version = self.shortRev or self.dirtyShortRev or "unknown"; - src = nix-filter.lib { - root = self; - include = [ - "buildconfig" - "cmake" - "launcher" - "libraries" - "program_info" - "tests" - ../COPYING.md + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ ../CMakeLists.txt + ../COPYING.md + + ../buildconfig + ../cmake + ../launcher + ../libraries + ../program_info + ../tests ]; }; From c367f48ec99f048f8d4bcaf0634370be33b29c62 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 16:43:49 -0400 Subject: [PATCH 073/181] refactor(nix): pin flake-compat in default.nix Avoids polluting downstream flake.locks Signed-off-by: Seth Flynn --- default.nix | 13 ++++--------- flake.lock | 17 ----------------- flake.nix | 20 -------------------- nix/README.md | 6 ------ 4 files changed, 4 insertions(+), 52 deletions(-) diff --git a/default.nix b/default.nix index 6466507b7..5ecef5590 100644 --- a/default.nix +++ b/default.nix @@ -1,9 +1,4 @@ -(import ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } -) { src = ./.; }).defaultNix +(import (fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz"; + sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU="; +}) { src = ./.; }).defaultNix diff --git a/flake.lock b/flake.lock index 24f04474e..d8c7c5248 100644 --- a/flake.lock +++ b/flake.lock @@ -1,21 +1,5 @@ { "nodes": { - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, "libnbtplusplus": { "flake": false, "locked": { @@ -50,7 +34,6 @@ }, "root": { "inputs": { - "flake-compat": "flake-compat", "libnbtplusplus": "libnbtplusplus", "nixpkgs": "nixpkgs" } diff --git a/flake.nix b/flake.nix index 517a4b2ff..b26773ed1 100644 --- a/flake.nix +++ b/flake.nix @@ -15,26 +15,6 @@ url = "github:PrismLauncher/libnbtplusplus"; flake = false; }; - - /* - Inputs below this are optional and can be removed - - ``` - { - inputs.prismlauncher = { - url = "github:PrismLauncher/PrismLauncher"; - inputs = { - flake-compat.follows = ""; - }; - }; - } - ``` - */ - - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; }; outputs = diff --git a/nix/README.md b/nix/README.md index 7c43658f9..21714d64a 100644 --- a/nix/README.md +++ b/nix/README.md @@ -44,9 +44,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; @@ -92,9 +89,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; From 32b49ecb84da5683bce42e2e888319f4517f1d53 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 17:11:51 -0400 Subject: [PATCH 074/181] refactor(nix): cleanup flake Signed-off-by: Seth Flynn --- flake.nix | 71 +++++++++++++++++++++++++++++++++++++++++--------- nix/checks.nix | 42 ----------------------------- 2 files changed, 58 insertions(+), 55 deletions(-) delete mode 100644 nix/checks.nix diff --git a/flake.nix b/flake.nix index b26773ed1..ea14f9048 100644 --- a/flake.nix +++ b/flake.nix @@ -22,8 +22,8 @@ self, nixpkgs, libnbtplusplus, - ... }: + let inherit (nixpkgs) lib; @@ -35,30 +35,71 @@ forAllSystems = lib.genAttrs systems; nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system}); in + { checks = forAllSystems ( system: + let - checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; }; + pkgs = nixpkgsFor.${system}; + llvm = pkgs.llvmPackages_19; in - lib.filterAttrs (_: lib.isDerivation) checks' + + { + formatting = + pkgs.runCommand "check-formatting" + { + nativeBuildInputs = with pkgs; [ + deadnix + llvm.clang-tools + markdownlint-cli + nixfmt-rfc-style + statix + ]; + } + '' + cd ${self} + + echo "Running clang-format...." + clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp} + + echo "Running deadnix..." + deadnix --fail + + echo "Running markdownlint..." + markdownlint --dot . + + echo "Running nixfmt..." + find -type f -name '*.nix' -exec nixfmt --check {} + + + echo "Running statix" + statix check . + + touch $out + ''; + } ); devShells = forAllSystems ( system: + let pkgs = nixpkgsFor.${system}; + llvm = pkgs.llvmPackages_19; + + packages' = self.packages.${system}; in + { default = pkgs.mkShell { - inputsFrom = [ self.packages.${system}.prismlauncher-unwrapped ]; - buildInputs = with pkgs; [ + inputsFrom = [ packages'.prismlauncher-unwrapped ]; + + nativeBuildInputs = with pkgs; [ ccache - ninja - llvmPackages_19.clang-tools + llvm.clang-tools ]; - cmakeFlags = self.packages.${system}.prismlauncher-unwrapped.cmakeFlags ++ [ + cmakeFlags = packages'.prismlauncher-unwrapped.cmakeFlags ++ [ "-GNinja" "-Bbuild" ]; @@ -86,6 +127,7 @@ packages = forAllSystems ( system: + let pkgs = nixpkgsFor.${system}; @@ -98,6 +140,7 @@ default = prismPackages.prismlauncher; }; in + # Only output them if they're available on the current system lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages ); @@ -105,16 +148,18 @@ # We put these under legacyPackages as they are meant for CI, not end user consumption legacyPackages = forAllSystems ( system: + let - prismPackages = self.packages.${system}; - legacyPackages = self.legacyPackages.${system}; + packages' = self.packages.${system}; + legacyPackages' = self.legacyPackages.${system}; in + { - prismlauncher-debug = prismPackages.prismlauncher.override { - prismlauncher-unwrapped = legacyPackages.prismlauncher-unwrapped-debug; + prismlauncher-debug = packages'.prismlauncher.override { + prismlauncher-unwrapped = legacyPackages'.prismlauncher-unwrapped-debug; }; - prismlauncher-unwrapped-debug = prismPackages.prismlauncher-unwrapped.overrideAttrs { + prismlauncher-unwrapped-debug = packages'.prismlauncher-unwrapped.overrideAttrs { cmakeBuildType = "Debug"; dontStrip = true; }; diff --git a/nix/checks.nix b/nix/checks.nix deleted file mode 100644 index ec219d6f8..000000000 --- a/nix/checks.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - runCommand, - deadnix, - llvmPackages_18, - markdownlint-cli, - nixfmt-rfc-style, - statix, - self, -}: -{ - formatting = - runCommand "check-formatting" - { - nativeBuildInputs = [ - deadnix - llvmPackages_18.clang-tools - markdownlint-cli - nixfmt-rfc-style - statix - ]; - } - '' - cd ${self} - - echo "Running clang-format...." - clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp} - - echo "Running deadnix..." - deadnix --fail - - echo "Running markdownlint..." - markdownlint --dot . - - echo "Running nixfmt..." - nixfmt --check . - - echo "Running statix" - statix check . - - touch $out - ''; -} From 58579539d071c569800ba0370a6d5918de025e33 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 17:19:20 -0400 Subject: [PATCH 075/181] fix(nix): only create compile_commands.json if it doesn't exist Signed-off-by: Seth Flynn --- flake.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index ea14f9048..ceb4bad4b 100644 --- a/flake.nix +++ b/flake.nix @@ -105,8 +105,10 @@ ]; shellHook = '' - cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug - ln -s {build/,}compile_commands.json + if [ ! -f compile_commands.json ]; then + cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug + ln -s {build/,}compile_commands.json + fi ''; }; } From 2d4bc09cb9bec621997e00c23acac0b7cf3e99d7 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 18:41:06 -0400 Subject: [PATCH 076/181] build(nix): properly wrap development shell Allows actually running the executables built in the development shell Signed-off-by: Seth Flynn --- flake.nix | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index ceb4bad4b..55d8b0464 100644 --- a/flake.nix +++ b/flake.nix @@ -88,13 +88,35 @@ llvm = pkgs.llvmPackages_19; packages' = self.packages.${system}; + + # Re-use our package wrapper to wrap our development environment + qt-wrapper-env = packages'.prismlauncher.overrideAttrs (old: { + name = "qt-wrapper-env"; + + # Required to use script-based makeWrapper below + strictDeps = true; + + # We don't need/want the unwrapped Prism package + paths = [ ]; + + nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [ + # Ensure the wrapper is script based so it can be sourced + pkgs.makeWrapper + ]; + + # Inspired by https://discourse.nixos.org/t/python-qt-woes/11808/10 + buildCommand = '' + makeQtWrapper ${lib.getExe pkgs.runtimeShellPackage} "$out" + sed -i '/^exec/d' "$out" + ''; + }); in { default = pkgs.mkShell { inputsFrom = [ packages'.prismlauncher-unwrapped ]; - nativeBuildInputs = with pkgs; [ + packages = with pkgs; [ ccache llvm.clang-tools ]; @@ -105,6 +127,9 @@ ]; shellHook = '' + echo "Sourcing ${qt-wrapper-env}" + source ${qt-wrapper-env} + if [ ! -f compile_commands.json ]; then cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug ln -s {build/,}compile_commands.json From de923a07d8a4b7c9f8cd688b9682587c78116e14 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:01:47 -0400 Subject: [PATCH 077/181] refactor(nix): rely more on setup hooks in dev shell Signed-off-by: Seth Flynn --- flake.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 55d8b0464..339a4026c 100644 --- a/flake.nix +++ b/flake.nix @@ -121,18 +121,18 @@ llvm.clang-tools ]; - cmakeFlags = packages'.prismlauncher-unwrapped.cmakeFlags ++ [ - "-GNinja" - "-Bbuild" - ]; + cmakeBuildType = "Debug"; + cmakeFlags = [ "-GNinja" ] ++ packages'.prismlauncher.cmakeFlags; + dontFixCmake = true; shellHook = '' echo "Sourcing ${qt-wrapper-env}" source ${qt-wrapper-env} if [ ! -f compile_commands.json ]; then - cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug - ln -s {build/,}compile_commands.json + cmakeConfigurePhase + cd .. + ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json fi ''; }; From 38ec7def324891c5062d9b58c1180d44494f91d1 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:01:47 -0400 Subject: [PATCH 078/181] chore(nix): add nice welcome message to dev shell Signed-off-by: Seth Flynn --- flake.nix | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/flake.nix b/flake.nix index 339a4026c..cf5f656d5 100644 --- a/flake.nix +++ b/flake.nix @@ -89,6 +89,24 @@ packages' = self.packages.${system}; + welcomeMessage = '' + Welcome to the Prism Launcher repository! 🌈 + + We just set some things up for you. To get building, you can run: + + ``` + $ cd "$cmakeBuildDir" + $ ninjaBuildPhase + $ ninjaInstallPhase + ``` + + Feel free to ask any questions in our Discord server or Matrix space: + - https://prismlauncher.org/discord + - https://matrix.to/#/#prismlauncher:matrix.org + + And thanks for helping out :) + ''; + # Re-use our package wrapper to wrap our development environment qt-wrapper-env = packages'.prismlauncher.overrideAttrs (old: { name = "qt-wrapper-env"; @@ -134,6 +152,8 @@ cd .. ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json fi + + echo ${lib.escapeShellArg welcomeMessage} ''; }; } From 9b38226f8cb0e2f82bcb017c686fd03520990dd1 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:01:47 -0400 Subject: [PATCH 079/181] chore(nix): clone git submodules automatically Signed-off-by: Seth Flynn --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index cf5f656d5..fd3003bc4 100644 --- a/flake.nix +++ b/flake.nix @@ -147,6 +147,8 @@ echo "Sourcing ${qt-wrapper-env}" source ${qt-wrapper-env} + git submodule update --init --force + if [ ! -f compile_commands.json ]; then cmakeConfigurePhase cd .. From e9cac2e0e37c4531f662545bdc45b72499654642 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:19:30 -0400 Subject: [PATCH 080/181] refactor(nix): use date for version Helps avoid needless rebuilds where only the revision changed. Also better conforms to Nixpkgs' version standards Signed-off-by: Seth Flynn --- nix/unwrapped.nix | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 0a4d2e253..1db414a53 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -24,9 +24,28 @@ assert lib.assertMsg ( gamemodeSupport -> stdenv.hostPlatform.isLinux ) "gamemodeSupport is only available on Linux."; +let + date = + let + # YYYYMMDD + date' = lib.substring 0 8 self.lastModifiedDate; + year = lib.substring 0 4 date'; + month = lib.substring 4 2 date'; + date = lib.substring 6 2 date'; + in + if (self ? "lastModifiedDate") then + lib.concatStringsSep "-" [ + year + month + date + ] + else + "unknown"; +in + stdenv.mkDerivation { pname = "prismlauncher-unwrapped"; - version = self.shortRev or self.dirtyShortRev or "unknown"; + version = "10.0-unstable-${date}"; src = lib.fileset.toSource { root = ../.; From de08d7c3644e96a3694f232b53c81f53c40c077a Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:48:17 -0400 Subject: [PATCH 081/181] chore(gitignore): add more nix-related files Signed-off-by: Seth Flynn --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c8f056eef..b563afbc7 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,12 @@ run/ # Nix/NixOS .direnv/ -.pre-commit-config.yaml +## Used when manually invoking stdenv phases +outputs/ +## Regular artifacts result +result-* +repl-result-* # Flatpak .flatpak-builder From b1c4e85806dd71136d8f5c655061a7140735c3a0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:53:22 +0000 Subject: [PATCH 082/181] chore(deps): update cachix/install-nix-action digest to d1ca217 --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 48a8418f5..cdd360589 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31 + - uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31 - uses: DeterminateSystems/update-flake-lock@v24 with: From f82b050bca8d0198e181765fafdd99dab931c1e9 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 1 Apr 2025 23:33:17 -0700 Subject: [PATCH 083/181] Fix blocked pr comment body (#3570) * Add newline to seperate header from comment body Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Seth Flynn Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --------- Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> Co-authored-by: Seth Flynn --- .github/workflows/blocked-prs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index d32f53bb1..94ec81317 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -163,6 +163,7 @@ jobs: if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }} run: | while read -r pr ; do @@ -202,7 +203,8 @@ jobs: BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" - echo '

PR Dependencies :pushpin:

' > "$COMMENT_PATH" + echo '

PR Dependencies :pushpin:

' > "$COMMENT_PATH" + echo >> "$COMMENT_PATH" pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA") while read -r pr_data ; do base_pr=$(jq -r '.number' <<< "$pr_data") From 8fd86e065ead6b44b967624cbf44940993d0d4ef Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 1 Apr 2025 23:46:49 -0700 Subject: [PATCH 084/181] push json expansion into env Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 94ec81317..9a5e84bea 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -47,9 +47,11 @@ jobs: - name: Setup Environment id: env_setup + env: + EVENT_PR_JSON: ${{ toJSON(github.event.pull_request) }} run: | # setup env for the rest of the workflow - PR_JSON=${PR_JSON:-'${{ toJSON(github.event.pull_request) }}'} + PR_JSON=${PR_JSON:-"$EVENT_PR_JSON"} { echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" From 0518c5095819589573d2679e51baa0b31c1b3a71 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Wed, 2 Apr 2025 16:01:26 +0800 Subject: [PATCH 085/181] Add checkbox for LiteLoader in mod filter Signed-off-by: Yihe Li --- .../ui/pages/modplatform/flame/FlamePage.cpp | 2 +- .../modplatform/flame/FlameResourcePages.cpp | 2 +- .../modplatform/modrinth/ModrinthPage.cpp | 2 +- .../modrinth/ModrinthResourcePages.cpp | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 21 ++++++++++++------- launcher/ui/widgets/ModFilterWidget.h | 7 +++++-- launcher/ui/widgets/ModFilterWidget.ui | 7 +++++++ 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index de6b3d633..0d61032d1 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -341,7 +341,7 @@ void FlamePage::setSearchTerm(QString term) void FlamePage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, false, this); + auto widget = ModFilterWidget::create(nullptr, false, ModPlatform::ResourceProvider::FLAME, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 4e01f3a65..b10a5b062 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -209,7 +209,7 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr FlameModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), false, ModPlatform::ResourceProvider::FLAME, this); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 7d70abec4..b91d50d3d 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,7 +391,7 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, true, this); + auto widget = ModFilterWidget::create(nullptr, true, ModPlatform::ResourceProvider::MODRINTH, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 4ee620677..be6343504 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -144,7 +144,7 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr ModrinthModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), true, ModPlatform::ResourceProvider::MODRINTH, this); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 37211693f..4973514a4 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -49,9 +49,12 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, + bool extended, + ModPlatform::ResourceProvider provider, + QWidget* parent) { - return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); + return unique_qobject_ptr(new ModFilterWidget(instance, extended, provider, parent)); } class VersionBasicModel : public QIdentityProxyModel { @@ -107,7 +110,7 @@ class AllVersionProxyModel : public QSortFilterProxyModel { } }; -ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, ModPlatform::ResourceProvider provider, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { ui->setupUi(this); @@ -148,11 +151,10 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - - connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + if (provider == ModPlatform::ResourceProvider::FLAME) + ui->liteLoader->setEnabled(false); + else + connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); if (extended) { connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); @@ -224,6 +226,7 @@ void ModFilterWidget::prepareBasicFilter() ui->forge->setChecked(loaders & ModPlatform::Forge); ui->fabric->setChecked(loaders & ModPlatform::Fabric); ui->quilt->setChecked(loaders & ModPlatform::Quilt); + ui->liteLoader->setChecked(loaders & ModPlatform::LiteLoader); m_filter->loaders = loaders; auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); m_filter->versions.emplace_front(def); @@ -269,6 +272,8 @@ void ModFilterWidget::onLoadersFilterChanged() loaders |= ModPlatform::Fabric; if (ui->quilt->isChecked()) loaders |= ModPlatform::Quilt; + if (ui->liteLoader->isChecked()) + loaders |= ModPlatform::LiteLoader; m_filter_changed = loaders != m_filter->loaders; m_filter->loaders = loaders; if (m_filter_changed) diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 41a2f1bbd..bedde51a9 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -83,7 +83,10 @@ class ModFilterWidget : public QTabWidget { } }; - static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); + static unique_qobject_ptr create(MinecraftInstance* instance, + bool extended, + ModPlatform::ResourceProvider provider, + QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -96,7 +99,7 @@ class ModFilterWidget : public QTabWidget { void setCategories(const QList&); private: - ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, ModPlatform::ResourceProvider provider, QWidget* parent = nullptr); void loadVersionList(); void prepareBasicFilter(); diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 807a0019a..788202714 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -121,6 +121,13 @@
+ + + + LiteLoader + + +
From b0c85fd539a3fa1301738a659430dc0d99068fb6 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Wed, 2 Apr 2025 16:22:52 +0800 Subject: [PATCH 086/181] Hide checkbox for CurseForge Signed-off-by: Yihe Li --- launcher/ui/widgets/ModFilterWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 4973514a4..4cedec472 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -152,7 +152,7 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, Mod connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); if (provider == ModPlatform::ResourceProvider::FLAME) - ui->liteLoader->setEnabled(false); + ui->liteLoader->setVisible(false); else connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); From 3ae68114f6d5a4a5a2137c792e793bb5d30d99a8 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Wed, 2 Apr 2025 16:28:49 +0800 Subject: [PATCH 087/181] Remove extra argument from ModFilterWidget::create Signed-off-by: Yihe Li --- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 2 +- .../modplatform/flame/FlameResourcePages.cpp | 2 +- .../pages/modplatform/modrinth/ModrinthPage.cpp | 2 +- .../modrinth/ModrinthResourcePages.cpp | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 15 ++++++--------- launcher/ui/widgets/ModFilterWidget.h | 7 ++----- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 0d61032d1..de6b3d633 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -341,7 +341,7 @@ void FlamePage::setSearchTerm(QString term) void FlamePage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, false, ModPlatform::ResourceProvider::FLAME, this); + auto widget = ModFilterWidget::create(nullptr, false, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index b10a5b062..4e01f3a65 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -209,7 +209,7 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr FlameModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), false, ModPlatform::ResourceProvider::FLAME, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index b91d50d3d..7d70abec4 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,7 +391,7 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, true, ModPlatform::ResourceProvider::MODRINTH, this); + auto widget = ModFilterWidget::create(nullptr, true, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index be6343504..4ee620677 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -144,7 +144,7 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr ModrinthModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), true, ModPlatform::ResourceProvider::MODRINTH, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 4cedec472..0fda7933e 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -49,12 +49,9 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, - bool extended, - ModPlatform::ResourceProvider provider, - QWidget* parent) +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) { - return unique_qobject_ptr(new ModFilterWidget(instance, extended, provider, parent)); + return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); } class VersionBasicModel : public QIdentityProxyModel { @@ -110,7 +107,7 @@ class AllVersionProxyModel : public QSortFilterProxyModel { } }; -ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, ModPlatform::ResourceProvider provider, QWidget* parent) +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { ui->setupUi(this); @@ -151,10 +148,10 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, Mod connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - if (provider == ModPlatform::ResourceProvider::FLAME) - ui->liteLoader->setVisible(false); - else + if (extended) connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + else + ui->liteLoader->setVisible(false); if (extended) { connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index bedde51a9..41a2f1bbd 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -83,10 +83,7 @@ class ModFilterWidget : public QTabWidget { } }; - static unique_qobject_ptr create(MinecraftInstance* instance, - bool extended, - ModPlatform::ResourceProvider provider, - QWidget* parent = nullptr); + static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -99,7 +96,7 @@ class ModFilterWidget : public QTabWidget { void setCategories(const QList&); private: - ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, ModPlatform::ResourceProvider provider, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); void loadVersionList(); void prepareBasicFilter(); From dc4cb8b9d51efab8de8993108b0cd133717d00ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:22:42 +0000 Subject: [PATCH 088/181] chore(deps): update actions/create-github-app-token action to v2 --- .github/workflows/blocked-prs.yml | 2 +- .github/workflows/merge-blocking-pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 9a5e84bea..bd49b7230 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Generate token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 6f85b9282..d6410f997 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Generate token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} From 12c266f8bde96e4e613856b1f8482cde02fee3f7 Mon Sep 17 00:00:00 2001 From: hanlie <48323966+HanlieChina@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:22:00 +0800 Subject: [PATCH 089/181] Update ExportToModListDialog.ui Signed-off-by: hanlie <48323966+HanlieChina@users.noreply.github.com> --- launcher/ui/dialogs/ExportToModListDialog.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui index 3afda2fa8..ec049d7e7 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.ui +++ b/launcher/ui/dialogs/ExportToModListDialog.ui @@ -79,6 +79,9 @@ 0 + + This text supports the following placeholders: {name} - Mod name {mod_id} - Mod ID {url} - Mod URL {version} - Mod version {authors} - Mod authors +
From aec410cb28bb844f3e3bc0e1a2040a90baf09b71 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Apr 2025 00:27:19 +0000 Subject: [PATCH 090/181] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/5e5402ecbcb27af32284d4a62553c019a3a49ea6?narHash=sha256-gWd4urRoLRe8GLVC/3rYRae1h%2BxfQzt09xOfb0PaHSk%3D' (2025-03-27) → 'github:NixOS/nixpkgs/2c8d3f48d33929642c1c12cd243df4cc7d2ce434?narHash=sha256-F7n4%2BKOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE%3D' (2025-04-02) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index d8c7c5248..996d79e22 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1743095683, - "narHash": "sha256-gWd4urRoLRe8GLVC/3rYRae1h+xfQzt09xOfb0PaHSk=", + "lastModified": 1743583204, + "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5e5402ecbcb27af32284d4a62553c019a3a49ea6", + "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", "type": "github" }, "original": { From e5861129ad58a8b5cdf55ed69bb9a7ce92811b87 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 6 Apr 2025 06:40:43 -0400 Subject: [PATCH 091/181] fix(SystemTheme): use default palette on all system themes Signed-off-by: Seth Flynn --- launcher/ui/themes/SystemTheme.cpp | 23 ++++++++++++++++------- launcher/ui/themes/SystemTheme.h | 2 +- launcher/ui/themes/ThemeManager.cpp | 6 ++++-- launcher/ui/themes/ThemeManager.h | 1 + 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index c755b379b..7fba08026 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -40,20 +40,29 @@ #include "HintOverrideProxyStyle.h" #include "ThemeManager.h" -SystemTheme::SystemTheme(const QString& styleName, bool isDefaultTheme) +// See https://github.com/MultiMC/Launcher/issues/1790 +// or https://github.com/PrismLauncher/PrismLauncher/issues/490 +static const QStringList S_NATIVE_STYLES{ "windows11", "windowsvista", "macos", "system", "windows" }; + +SystemTheme::SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme) { m_themeName = isDefaultTheme ? "system" : styleName; m_widgetTheme = styleName; - auto style = QStyleFactory::create(styleName); - m_colorPalette = style->standardPalette(); - delete style; + // NOTE: SystemTheme is reconstructed on page refresh. We can't accurately determine the system palette here + // See also S_NATIVE_STYLES comment + if (S_NATIVE_STYLES.contains(m_themeName)) { + m_colorPalette = defaultPalette; + } else { + auto style = QStyleFactory::create(styleName); + m_colorPalette = style->standardPalette(); + delete style; + } } void SystemTheme::apply(bool initial) { - // See https://github.com/MultiMC/Launcher/issues/1790 - // or https://github.com/PrismLauncher/PrismLauncher/issues/490 - if (initial && m_themeName == "system") { + // See S_NATIVE_STYLES comment + if (initial && S_NATIVE_STYLES.contains(m_themeName)) { QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); return; } diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 54404d052..7ae24c3db 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -38,7 +38,7 @@ class SystemTheme : public ITheme { public: - SystemTheme(const QString& styleName, bool isDefaultTheme); + SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme); virtual ~SystemTheme() {} void apply(bool initial) override; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 6c50d7409..30a1fe7be 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -44,6 +44,8 @@ ThemeManager::ThemeManager() m_defaultStyle = style->objectName(); themeDebugLog() << "System theme seems to be:" << m_defaultStyle; + m_defaultPalette = QApplication::palette(); + initializeThemes(); initializeCatPacks(); } @@ -126,7 +128,7 @@ void ThemeManager::initializeIcons() void ThemeManager::initializeWidgets() { themeDebugLog() << "<> Initializing Widget Themes"; - themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique(m_defaultStyle, true)); + themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique(m_defaultStyle, m_defaultPalette, true)); auto darkThemeId = addTheme(std::make_unique()); themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); @@ -139,7 +141,7 @@ void ThemeManager::initializeWidgets() continue; } #endif - themeDebugLog() << "Loading System Theme:" << addTheme(std::make_unique(st, false)); + themeDebugLog() << "Loading System Theme:" << addTheme(std::make_unique(st, m_defaultPalette, false)); } // TODO: need some way to differentiate same name themes in different subdirectories diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index 9c9e818e5..8de7562d1 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -68,6 +68,7 @@ class ThemeManager { QDir m_applicationThemeFolder{ "themes" }; QDir m_catPacksFolder{ "catpacks" }; std::map> m_catPacks; + QPalette m_defaultPalette; QString m_defaultStyle; LogColors m_logColors; From b9a1fa36459c91d7aa4d6155f1ccced83214453f Mon Sep 17 00:00:00 2001 From: Soup <43444191+Soup-64@users.noreply.github.com> Date: Sat, 5 Apr 2025 22:26:58 -0400 Subject: [PATCH 092/181] Implement popup for metacache someone in the Discord ran into an issue somewhat related to the metacache button not working (folder in use err), so this warning makes it more obvious when this happens, though it would be better to find out why it ran into a process conflict Signed-off-by: Soup <43444191+Soup-64@users.noreply.github.com> --- launcher/FileSystem.cpp | 1 + launcher/net/HttpMetaCache.cpp | 6 ++++-- launcher/net/HttpMetaCache.h | 2 +- launcher/ui/MainWindow.cpp | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 954e7936e..14b50768a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -55,6 +55,7 @@ #include "DesktopServices.h" #include "PSaveFile.h" #include "StringUtils.h" +#include "ui/dialogs/CustomMessageBox.h" #if defined Q_OS_WIN32 #define NOMINMAX diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 4985ad080..f57aad1ba 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -166,8 +166,9 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool return true; } -void HttpMetaCache::evictAll() +bool HttpMetaCache::evictAll() { + bool ret; for (QString& base : m_entries.keys()) { EntryMap& map = m_entries[base]; qCDebug(taskHttpMetaCacheLogC) << "Evicting base" << base; @@ -176,8 +177,9 @@ void HttpMetaCache::evictAll() qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath; } map.entry_list.clear(); - FS::deletePath(map.base_path); + ret = FS::deletePath(map.base_path); } + return ret; } auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 036a8dd94..144012ae5 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -113,7 +113,7 @@ class HttpMetaCache : public QObject { // evict selected entry from cache auto evictEntry(MetaEntryPtr entry) -> bool; - void evictAll(); + bool evictAll(); void addBase(QString base, QString base_root); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ddf726373..2b43af2b8 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1318,7 +1318,10 @@ void MainWindow::on_actionReportBug_triggered() void MainWindow::on_actionClearMetadata_triggered() { - APPLICATION->metacache()->evictAll(); + if(!APPLICATION->metacache()->evictAll()){ + CustomMessageBox::selectable(this, tr("Error"), tr("Metadata cache clear Failed!\n To clear the metadata cache manually, press Folders -> View Launcher Root Folder, and after closing the launcher delete the folder named \"meta\"\n"), QMessageBox::Warning)->show(); + } + APPLICATION->metacache()->SaveNow(); } From 0c90530f8859125ea762b6460e180c32096e8987 Mon Sep 17 00:00:00 2001 From: Soup <43444191+Soup-64@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:27:49 -0400 Subject: [PATCH 093/181] cleanup Fix formatting and fix a typo in the return code check Signed-off-by: Soup <43444191+Soup-64@users.noreply.github.com> --- launcher/net/HttpMetaCache.cpp | 8 +++++--- launcher/net/HttpMetaCache.h | 2 +- launcher/ui/MainWindow.cpp | 9 +++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index f57aad1ba..5a3a451b7 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -166,9 +166,10 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool return true; } -bool HttpMetaCache::evictAll() +//returns true on success, false otherwise +auto HttpMetaCache::evictAll() -> bool { - bool ret; + bool ret = true; for (QString& base : m_entries.keys()) { EntryMap& map = m_entries[base]; qCDebug(taskHttpMetaCacheLogC) << "Evicting base" << base; @@ -177,7 +178,8 @@ bool HttpMetaCache::evictAll() qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath; } map.entry_list.clear(); - ret = FS::deletePath(map.base_path); + //AND all return codes together so the result is true iff all runs of deletePath() are true + ret &= FS::deletePath(map.base_path); } return ret; } diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 144012ae5..5db41259f 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -113,7 +113,7 @@ class HttpMetaCache : public QObject { // evict selected entry from cache auto evictEntry(MetaEntryPtr entry) -> bool; - bool evictAll(); + auto evictAll() -> bool; void addBase(QString base, QString base_root); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 2b43af2b8..887d89006 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1318,8 +1318,13 @@ void MainWindow::on_actionReportBug_triggered() void MainWindow::on_actionClearMetadata_triggered() { - if(!APPLICATION->metacache()->evictAll()){ - CustomMessageBox::selectable(this, tr("Error"), tr("Metadata cache clear Failed!\n To clear the metadata cache manually, press Folders -> View Launcher Root Folder, and after closing the launcher delete the folder named \"meta\"\n"), QMessageBox::Warning)->show(); + //This if contains side effects! + if (!APPLICATION->metacache()->evictAll()) { + CustomMessageBox::selectable(this, tr("Error"), + tr("Metadata cache clear Failed!\nTo clear the metadata cache manually, press Folders -> View " + "Launcher Root Folder, and after closing the launcher delete the folder named \"meta\"\n"), + QMessageBox::Warning) + ->show(); } APPLICATION->metacache()->SaveNow(); From 25d7db207d3ddaca7d812754453c1e5385a3f80e Mon Sep 17 00:00:00 2001 From: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:15:02 -0400 Subject: [PATCH 094/181] Update FileSystem.cpp fix oopsie Signed-off-by: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> --- launcher/FileSystem.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 14b50768a..954e7936e 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -55,7 +55,6 @@ #include "DesktopServices.h" #include "PSaveFile.h" #include "StringUtils.h" -#include "ui/dialogs/CustomMessageBox.h" #if defined Q_OS_WIN32 #define NOMINMAX From 9b3fa591d3d6d0d09a5a164bfcc6356ae74932a7 Mon Sep 17 00:00:00 2001 From: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:24:09 -0400 Subject: [PATCH 095/181] Update launcher/net/HttpMetaCache.h Co-authored-by: Alexandru Ionut Tripon Signed-off-by: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> --- launcher/net/HttpMetaCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 5db41259f..144012ae5 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -113,7 +113,7 @@ class HttpMetaCache : public QObject { // evict selected entry from cache auto evictEntry(MetaEntryPtr entry) -> bool; - auto evictAll() -> bool; + bool evictAll(); void addBase(QString base, QString base_root); From da3a4984909b4b1d801018bf80eb93feadc97100 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:11:59 -0400 Subject: [PATCH 096/181] ci(nix): run on tags Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 6c2b13de7..315d63667 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -2,6 +2,8 @@ name: Nix on: push: + tags: + - "*" paths-ignore: - "**.md" - "**/LICENSE" From 1a5a162727c7d9d91aefd3852c127d60179844ae Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:11:59 -0400 Subject: [PATCH 097/181] ci(nix): ignore more paths Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 315d63667..1317a1a05 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -5,18 +5,27 @@ on: tags: - "*" paths-ignore: + - ".github/**" + - "!.github/workflows/nix.yml" + - "flatpak/" + - "scripts/" + + - ".git*" + - ".envrc" - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "flatpak/**" + - "!COPYING.md" + - "renovate.json" pull_request_target: paths-ignore: + - ".github/**" + - "flatpak/" + - "scripts/" + + - ".git*" + - ".envrc" - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "flatpak/**" + - "!COPYING.md" + - "renovate.json" workflow_dispatch: permissions: From 76bec385d6803e94899028695226d872185b3d39 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:19:58 -0400 Subject: [PATCH 098/181] ci(nix): correctly parse action env vars as bools Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 1317a1a05..99bb4cb7d 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -71,7 +71,7 @@ jobs: # For PRs - name: Setup Nix Magic Cache - if: ${{ env.USE_DETERMINATE }} + if: ${{ env.USE_DETERMINATE == 'true' }} uses: DeterminateSystems/flakehub-cache-action@v1 # For in-tree builds @@ -87,14 +87,14 @@ jobs: nix flake check --print-build-logs --show-trace - name: Build debug package - if: ${{ env.DEBUG }} + if: ${{ env.DEBUG == 'true' }} run: | nix build \ --no-link --print-build-logs --print-out-paths \ .#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY" - name: Build release package - if: ${{ !env.DEBUG }} + if: ${{ env.DEBUG == 'false' }} run: | nix build \ --no-link --print-build-logs --print-out-paths \ From 180292098d9c4ad87ed0d9babed92e152feb5475 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:19:58 -0400 Subject: [PATCH 099/181] ci(nix): pin release packages in cachix Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 99bb4cb7d..fea0df6ce 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -95,7 +95,10 @@ jobs: - name: Build release package if: ${{ env.DEBUG == 'false' }} + env: + TAG: ${{ github.ref_name }} + SYSTEM: ${{ matrix.system }} run: | - nix build \ - --no-link --print-build-logs --print-out-paths \ - .#prismlauncher >> "$GITHUB_STEP_SUMMARY" + nix build --no-link --print-out-paths .#prismlauncher \ + | tee -a "$GITHUB_STEP_SUMMARY" \ + | xargs cachix pin prismlauncher "$TAG"-"$SYSTEM" From 6386d044e3fc5ec056479e508ade4da993a81cd4 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:32:30 -0400 Subject: [PATCH 100/181] revert: "feat: publish on flakehub" Flakehub requires semver. We don't use that (yet) Refs: 86cc6d3 Signed-off-by: Seth Flynn --- .github/workflows/publish.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 034a8548b..8a7da812e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,28 +8,6 @@ permissions: contents: read jobs: - flakehub: - name: FlakeHub - - runs-on: ubuntu-latest - - permissions: - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - - name: Install Nix - uses: cachix/install-nix-action@v31 - - - name: Publish on FlakeHub - uses: determinatesystems/flakehub-push@v5 - with: - visibility: "public" - winget: name: Winget From d92f7b3c9710679550111037fb7e41fe64443927 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 6 Apr 2025 17:08:25 -0400 Subject: [PATCH 101/181] ci: use ninja with msvc on x64 Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59489d0f7..6e4d1fbf3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -171,6 +171,12 @@ jobs: with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} + - 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 MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' uses: actions/cache@v4.2.3 @@ -190,11 +196,17 @@ jobs: ccache -p # Show config ccache -z # Zero stats - - name: Use ccache on Debug builds only - if: inputs.build_type == 'Debug' - shell: bash + - name: Configure ccache (Windows MSVC) + if: ${{ runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' }} run: | - echo "CCACHE_VAR=ccache" >> $GITHUB_ENV + # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) + Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe + echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV + echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV + echo "TrackFileAccess=false" >> $env:GITHUB_ENV + # Needed for ccache, but also speeds up compile + echo "UseMultiToolTask=true" >> $env:GITHUB_ENV + - name: Set short version shell: bash @@ -300,19 +312,14 @@ jobs: cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja - name: Configure CMake (Windows MSVC) - if: runner.os == 'Windows' && matrix.msystem == '' + if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64' + run: | + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja + + - name: Configure CMake (Windows MSVC arm64) + if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture == 'arm64' run: | cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} - # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) - if ("${{ env.CCACHE_VAR }}") - { - Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe - echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV - echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV - echo "TrackFileAccess=false" >> $env:GITHUB_ENV - } - # Needed for ccache, but also speeds up compile - echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - name: Configure CMake (Linux) if: runner.os == 'Linux' From b579cae5c28be2ef8342246be56655452ce141bb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 8 Apr 2025 04:17:10 -0700 Subject: [PATCH 102/181] feat(server): start using semver for launcher Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- CMakeLists.txt | 13 +++---- buildconfig/BuildConfig.cpp.in | 36 +++++++------------ buildconfig/BuildConfig.h | 2 ++ .../updater/prismupdater/PrismUpdater.cpp | 12 ++++++- launcher/updater/prismupdater/PrismUpdater.h | 1 + program_info/win_install.nsi.in | 1 + 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 138049018..f80c675bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,7 +102,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}" # Export compile commands for debug builds if we can (useful in LSPs like clangd) # https://cmake.org/cmake/help/v3.31/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html if(CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR STREQUAL "Ninja" AND CMAKE_BUILD_TYPE STREQUAL "Debug") - set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF) @@ -195,10 +195,11 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 10) set(Launcher_VERSION_MINOR 0) +set(Launcher_VERSION_PATCH 0) -set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") -set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0") -set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0") +set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") +set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0") +set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_PATCH},0") # Build platform. set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.") @@ -242,7 +243,7 @@ set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON) # differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this # feature if they know it will work with their distribution. if(UNIX AND NOT APPLE) - set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF) + set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF) endif() # Java downloader @@ -383,7 +384,7 @@ set(Launcher_ENABLE_UPDATER NO) set(Launcher_BUILD_UPDATER NO) if (NOT APPLE AND (NOT Launcher_UPDATER_GITHUB_REPO STREQUAL "" AND NOT Launcher_BUILD_ARTIFACT STREQUAL "")) - set(Launcher_BUILD_UPDATER YES) + set(Launcher_BUILD_UPDATER YES) endif() if(NOT (UNIX AND APPLE)) diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 2124d02ae..6bebcb80e 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -34,8 +34,8 @@ */ #include -#include "BuildConfig.h" #include +#include "BuildConfig.h" const Config BuildConfig; @@ -58,6 +58,7 @@ Config::Config() // Version information VERSION_MAJOR = @Launcher_VERSION_MAJOR@; VERSION_MINOR = @Launcher_VERSION_MINOR@; + VERSION_PATCH = @Launcher_VERSION_PATCH@; BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@"; BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@"; @@ -74,14 +75,13 @@ Config::Config() MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@"; - if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) - { + if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) { UPDATER_ENABLED = true; - } else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) { + } else if (!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) { UPDATER_ENABLED = true; } - #cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER +#cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER; GIT_COMMIT = "@Launcher_GIT_COMMIT@"; @@ -89,27 +89,19 @@ Config::Config() GIT_REFSPEC = "@Launcher_GIT_REFSPEC@"; // Assume that builds outside of Git repos are "stable" - if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") - || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND") - || GIT_REFSPEC == QStringLiteral("") - || GIT_TAG == QStringLiteral("GIT-NOTFOUND")) - { + if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND") || + GIT_REFSPEC == QStringLiteral("") || GIT_TAG == QStringLiteral("GIT-NOTFOUND")) { GIT_REFSPEC = "refs/heads/stable"; GIT_TAG = versionString(); GIT_COMMIT = ""; } - if (GIT_REFSPEC.startsWith("refs/heads/")) - { + if (GIT_REFSPEC.startsWith("refs/heads/")) { VERSION_CHANNEL = GIT_REFSPEC; - VERSION_CHANNEL.remove("refs/heads/"); - } - else if (!GIT_COMMIT.isEmpty()) - { + VERSION_CHANNEL.remove("refs/heads/"); + } else if (!GIT_COMMIT.isEmpty()) { VERSION_CHANNEL = GIT_COMMIT.mid(0, 8); - } - else - { + } else { VERSION_CHANNEL = "unknown"; } @@ -136,7 +128,7 @@ Config::Config() QString Config::versionString() const { - return QString("%1.%2").arg(VERSION_MAJOR).arg(VERSION_MINOR); + return QString("%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_PATCH); } QString Config::printableVersionString() const @@ -144,8 +136,7 @@ QString Config::printableVersionString() const QString vstr = versionString(); // If the build is not a main release, append the channel - if(VERSION_CHANNEL != "stable" && GIT_TAG != vstr) - { + if (VERSION_CHANNEL != "stable" && GIT_TAG != vstr) { vstr += "-" + VERSION_CHANNEL; } return vstr; @@ -162,4 +153,3 @@ QString Config::systemID() const { return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR); } - diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 099d9b5ca..b59adcb57 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -59,6 +59,8 @@ class Config { int VERSION_MAJOR; /// The minor version number. int VERSION_MINOR; + /// The patch version number. + int VERSION_PATCH; /** * The version channel diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 8bf8cb473..96172b0bc 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -298,6 +298,10 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar auto version_parts = version.split('.'); m_prismVersionMajor = version_parts.takeFirst().toInt(); m_prismVersionMinor = version_parts.takeFirst().toInt(); + if (!version_parts.isEmpty()) + m_prismVersionPatch = version_parts.takeFirst().toInt(); + else + m_prismVersionPatch = 0; } m_allowPreRelease = parser.isSet("pre-release"); @@ -556,6 +560,7 @@ void PrismUpdaterApp::run() m_prismVersion = BuildConfig.printableVersionString(); m_prismVersionMajor = BuildConfig.VERSION_MAJOR; m_prismVersionMinor = BuildConfig.VERSION_MINOR; + m_prismVersionPatch = BuildConfig.VERSION_PATCH; m_prsimVersionChannel = BuildConfig.VERSION_CHANNEL; m_prismGitCommit = BuildConfig.GIT_COMMIT; } @@ -564,6 +569,7 @@ void PrismUpdaterApp::run() qDebug() << "Executable reports as:" << m_prismBinaryName << "version:" << m_prismVersion; qDebug() << "Version major:" << m_prismVersionMajor; qDebug() << "Version minor:" << m_prismVersionMinor; + qDebug() << "Version minor:" << m_prismVersionPatch; qDebug() << "Version channel:" << m_prsimVersionChannel; qDebug() << "Git Commit:" << m_prismGitCommit; @@ -1277,6 +1283,10 @@ bool PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path) return false; m_prismVersionMajor = version_parts.takeFirst().toInt(); m_prismVersionMinor = version_parts.takeFirst().toInt(); + if (!version_parts.isEmpty()) + m_prismVersionPatch = version_parts.takeFirst().toInt(); + else + m_prismVersionPatch = 0; m_prismGitCommit = lines.takeFirst().simplified(); return true; } @@ -1400,7 +1410,7 @@ GitHubRelease PrismUpdaterApp::getLatestRelease() bool PrismUpdaterApp::needUpdate(const GitHubRelease& release) { - auto current_ver = Version(QString("%1.%2").arg(QString::number(m_prismVersionMajor)).arg(QString::number(m_prismVersionMinor))); + auto current_ver = Version(QString("%1.%2.%3").arg(m_prismVersionMajor).arg(m_prismVersionMinor).arg(m_prismVersionPatch)); return current_ver < release.version; } diff --git a/launcher/updater/prismupdater/PrismUpdater.h b/launcher/updater/prismupdater/PrismUpdater.h index f3dd6e062..a904cbb6f 100644 --- a/launcher/updater/prismupdater/PrismUpdater.h +++ b/launcher/updater/prismupdater/PrismUpdater.h @@ -121,6 +121,7 @@ class PrismUpdaterApp : public QApplication { QString m_prismVersion; int m_prismVersionMajor = -1; int m_prismVersionMinor = -1; + int m_prismVersionPatch = -1; QString m_prsimVersionChannel; QString m_prismGitCommit; diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 24f6ee4e8..dfdce2e1c 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -398,6 +398,7 @@ Section "@Launcher_DisplayName@" WriteRegStr HKCU Software\Classes\prismlauncher\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"' ; Write the uninstall keys for Windows + ; https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key ${GetParameters} $R0 ${GetOptions} $R0 "/NoUninstaller" $R1 ${If} ${Errors} From c89a8a676e9540797dcdbb61cba07d438811ce8d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Apr 2025 20:43:41 +0300 Subject: [PATCH 103/181] increment linux qt version Signed-off-by: Trial97 --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e4d1fbf3..cb92ca270 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: qt_ver: 6 qt_host: linux qt_arch: "" - qt_version: "6.5.3" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" @@ -207,7 +207,6 @@ jobs: # Needed for ccache, but also speeds up compile echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - - name: Set short version shell: bash run: | From 5ba25a147627646a957282e9ebc60179d6686328 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Apr 2025 20:33:02 +0300 Subject: [PATCH 104/181] remove mac legacy build Signed-off-by: Trial97 --- .github/workflows/build.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e459914..e21dad106 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,14 +106,6 @@ jobs: qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" - - os: macos-14 - name: macOS-Legacy - macosx_deployment_target: 10.13 - qt_ver: 5 - qt_host: mac - qt_version: "5.15.2" - qt_modules: "qtnetworkauth" - runs-on: ${{ matrix.os }} env: @@ -278,11 +270,6 @@ jobs: run: | cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja - - name: Configure CMake (macOS-Legacy) - if: runner.os == 'macOS' && matrix.qt_ver == 5 - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja - - name: Configure CMake (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' shell: msys2 {0} From 1a3cc00438c9551992992f369ff506a28f717752 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Apr 2025 21:37:34 +0300 Subject: [PATCH 105/181] deprecate ubuntu 20.04 runner Signed-off-by: Trial97 --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e4d1fbf3..f75875dce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 qt_ver: 5 qt_host: linux qt_arch: "" @@ -207,7 +207,6 @@ jobs: # Needed for ccache, but also speeds up compile echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - - name: Set short version shell: bash run: | From 4361aaa094820e07e9d35d96865b3851f184d49a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 7 Apr 2025 13:17:48 +0300 Subject: [PATCH 106/181] remove ghc_filesystem Signed-off-by: Trial97 --- .gitmodules | 3 --- CMakeLists.txt | 9 -------- COPYING.md | 22 ------------------- launcher/CMakeLists.txt | 3 --- launcher/FileSystem.cpp | 16 -------------- launcher/filelink/FileLink.cpp | 16 -------------- .../updater/prismupdater/PrismUpdater.cpp | 16 -------------- libraries/README.md | 8 ------- libraries/filesystem | 1 - nix/unwrapped.nix | 2 -- tests/FileSystem_test.cpp | 16 -------------- 11 files changed, 112 deletions(-) delete mode 160000 libraries/filesystem diff --git a/.gitmodules b/.gitmodules index 0f437d277..7ad40becb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "libraries/tomlplusplus"] path = libraries/tomlplusplus url = https://github.com/marzer/tomlplusplus.git -[submodule "libraries/filesystem"] - path = libraries/filesystem - url = https://github.com/gulrak/filesystem [submodule "libraries/libnbtplusplus"] path = libraries/libnbtplusplus url = https://github.com/PrismLauncher/libnbtplusplus.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 138049018..deb14fdf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,9 +363,6 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) - # Find ghc_filesystem - find_package(ghc_filesystem QUIET) - # Find cmark find_package(cmark QUIET) endif() @@ -560,12 +557,6 @@ else() endif() add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API -if (NOT ghc_filesystem_FOUND) - message(STATUS "Using bundled ghc_filesystem") - add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS -else() - message(STATUS "Using system ghc_filesystem") -endif() add_subdirectory(libraries/qdcss) # css parser ############################### Built Artifacts ############################### diff --git a/COPYING.md b/COPYING.md index 0ea3437d3..f1f0b3a70 100644 --- a/COPYING.md +++ b/COPYING.md @@ -362,28 +362,6 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -## gulrak/filesystem - - Copyright (c) 2018, Steffen Schümann - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - ## Breeze icons Copyright (C) 2014 Uri Herrera and others diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0871675d0..30d657f9e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1289,7 +1289,6 @@ target_link_libraries(Launcher_logic qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets - ghcFilesystem::ghc_filesystem ) if (UNIX AND NOT CYGWIN AND NOT APPLE) @@ -1376,7 +1375,6 @@ if(Launcher_BUILD_UPDATER) ${ZLIB_LIBRARIES} systeminfo BuildConfig - ghcFilesystem::ghc_filesystem Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network @@ -1415,7 +1413,6 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) target_link_libraries(filelink_logic systeminfo BuildConfig - ghcFilesystem::ghc_filesystem Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 954e7936e..5d3008aae 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -77,24 +77,8 @@ #include #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif // clone #if defined(Q_OS_LINUX) diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index b641b41d5..a082b4b5b 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -40,24 +40,8 @@ #include "WindowsConsole.h" #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 8bf8cb473..1a0d08ebf 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -53,24 +53,8 @@ #include #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif #include "DesktopServices.h" diff --git a/libraries/README.md b/libraries/README.md index 67d78dade..3c5f5a4ab 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -2,14 +2,6 @@ This folder has third-party or otherwise external libraries needed for other parts to work. -## filesystem - -Gulrak's implementation of C++17 std::filesystem for C++11 /C++14/C++17/C++20 on Windows, macOS, Linux and FreeBSD. - -See [github repo](https://github.com/gulrak/filesystem). - -MIT licensed. - ## gamemode A performance optimization daemon. diff --git a/libraries/filesystem b/libraries/filesystem deleted file mode 160000 index 076592ce6..000000000 --- a/libraries/filesystem +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 076592ce6e64568521b88a11881aa36b3d3f7048 diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 1db414a53..93cda8e1a 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -6,7 +6,6 @@ apple-sdk_11, extra-cmake-modules, gamemode, - ghc_filesystem, jdk17, kdePackages, libnbtplusplus, @@ -78,7 +77,6 @@ stdenv.mkDerivation { buildInputs = [ cmark - ghc_filesystem kdePackages.qtbase kdePackages.qtnetworkauth kdePackages.quazip diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 9f64f54ed..995867e46 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -9,24 +9,8 @@ #include #include -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif #include From d1eebbceff0a263697bdaf6376af6a9e910004de Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Apr 2025 08:33:27 +0300 Subject: [PATCH 107/181] chore: remove release macos Legacy reference Signed-off-by: Trial97 --- .github/workflows/trigger_release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 134281b2c..411a5bbeb 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -49,7 +49,6 @@ jobs: mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync - mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} @@ -104,5 +103,4 @@ jobs: PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe PrismLauncher-macOS-${{ env.VERSION }}.zip - PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip PrismLauncher-${{ env.VERSION }}.tar.gz From cc69a59f368e3db90a1e63ff9025abcf4d3aaf90 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Apr 2025 08:38:05 +0300 Subject: [PATCH 108/181] fix: crash when the instance window is closed before download dialog is open Signed-off-by: Trial97 --- launcher/ui/pages/instance/ModFolderPage.cpp | 19 +++++++++++-------- .../ui/pages/instance/ResourcePackPage.cpp | 18 +++++++++++------- launcher/ui/pages/instance/ShaderPackPage.cpp | 18 +++++++++++------- .../ui/pages/instance/TexturePackPage.cpp | 18 +++++++++++------- launcher/ui/widgets/ModFilterWidget.cpp | 1 - 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 95507ac22..026f0c140 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -145,9 +145,10 @@ void ModFolderPage::downloadMods() QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - - ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -165,7 +166,7 @@ void ModFolderPage::downloadMods() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -300,9 +301,11 @@ void ModFolderPage::changeModVersion() if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) return; - ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata((*mods_list.begin())->metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata((*mods_list.begin())->metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -320,7 +323,7 @@ void ModFolderPage::changeModVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 79e677765..ae5eb8fac 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -84,8 +84,10 @@ void ResourcePackPage::downloadResourcePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -103,7 +105,7 @@ void ResourcePackPage::downloadResourcePacks() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -235,9 +237,11 @@ void ResourcePackPage::changeResourcePackVersion() if (resource.metadata() == nullptr) return; - ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata(resource.metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -255,7 +259,7 @@ void ResourcePackPage::changeResourcePackVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index a287d3edf..45bb02030 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -81,8 +81,10 @@ void ShaderPackPage::downloadShaderPack() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -100,7 +102,7 @@ void ShaderPackPage::downloadShaderPack() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -232,9 +234,11 @@ void ShaderPackPage::changeShaderPackVersion() if (resource.metadata() == nullptr) return; - ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata(resource.metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -252,7 +256,7 @@ void ShaderPackPage::changeShaderPackVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index fd1e0a2fc..6d000a486 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -90,8 +90,10 @@ void TexturePackPage::downloadTexturePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -109,7 +111,7 @@ void TexturePackPage::downloadTexturePacks() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -241,9 +243,11 @@ void TexturePackPage::changeTexturePackVersion() if (resource.metadata() == nullptr) return; - ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata(resource.metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -261,7 +265,7 @@ void TexturePackPage::changeTexturePackVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 0fda7933e..03522bc19 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -291,7 +291,6 @@ void ModFilterWidget::onSideFilterChanged() side = ""; } - m_filter_changed = side != m_filter->side; m_filter->side = side; if (m_filter_changed) From 8bb9b168fb996df9209e1e34be854235eda3d42a Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sat, 12 Apr 2025 01:59:07 +0800 Subject: [PATCH 109/181] Use explicit construction for QFile from QString Signed-off-by: Yihe Li --- launcher/FileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 5d3008aae..7189ca841 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -934,7 +934,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri QDir content = application.path() + "/Contents/"; QDir resources = content.path() + "/Resources/"; QDir binaryDir = content.path() + "/MacOS/"; - QFile info = content.path() + "/Info.plist"; + QFile info(content.path() + "/Info.plist"); if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) { qWarning() << "Couldn't create directories within application"; From 6812d137e67b323fdec32c091cb4f408f8f5874b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 13 Apr 2025 00:51:01 +0000 Subject: [PATCH 110/181] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/2c8d3f48d33929642c1c12cd243df4cc7d2ce434?narHash=sha256-F7n4%2BKOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE%3D' (2025-04-02) → 'github:NixOS/nixpkgs/2631b0b7abcea6e640ce31cd78ea58910d31e650?narHash=sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR%2BXhw3kr/3Xd0GPTM%3D' (2025-04-12) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 996d79e22..2d79b8335 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1743583204, - "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", + "lastModified": 1744463964, + "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", + "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", "type": "github" }, "original": { From 7d4034cfa55275edc08cef2c6dd9d9f8f5d063d1 Mon Sep 17 00:00:00 2001 From: Kenneth Chew <79120643+kthchew@users.noreply.github.com> Date: Sun, 13 Apr 2025 00:50:19 -0400 Subject: [PATCH 111/181] Shorten LocalPeer socket names On most systems supporting Unix sockets, the maximum length of a socket name is quite low (e.g. on macOS 104 characters and on Linux 108). If the name is too long, the sockets will not work and thus sending messages to a running instance of the launcher will not work. Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com> --- launcher/Application.cpp | 17 ++++++++++++----- libraries/LocalPeer/src/LocalPeer.cpp | 8 ++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 816f7b8ab..d773d9a1c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -375,19 +375,20 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_peerInstance = new LocalPeer(this, appID); connect(m_peerInstance, &LocalPeer::messageReceived, this, &Application::messageReceived); if (m_peerInstance->isClient()) { + bool sentMessage = false; int timeout = 2000; if (m_instanceIdToLaunch.isEmpty()) { ApplicationMessage activate; activate.command = "activate"; - m_peerInstance->sendMessage(activate.serialize(), timeout); + sentMessage = m_peerInstance->sendMessage(activate.serialize(), timeout); if (!m_urlsToImport.isEmpty()) { for (auto url : m_urlsToImport) { ApplicationMessage import; import.command = "import"; import.args.insert("url", url.toString()); - m_peerInstance->sendMessage(import.serialize(), timeout); + sentMessage = m_peerInstance->sendMessage(import.serialize(), timeout); } } } else { @@ -407,10 +408,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) launch.args["offline_enabled"] = "true"; launch.args["offline_name"] = m_offlineName; } - m_peerInstance->sendMessage(launch.serialize(), timeout); + sentMessage = m_peerInstance->sendMessage(launch.serialize(), timeout); + } + if (sentMessage) { + m_status = Application::Succeeded; + return; + } else { + std::cerr << "Unable to redirect command to already running instance\n"; + // C function not Qt function - event loop not started yet + ::exit(1); } - m_status = Application::Succeeded; - return; } } diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index bd407042f..c1875bf98 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -76,7 +76,7 @@ ApplicationId ApplicationId::fromTraditionalApp() prefix.truncate(6); QByteArray idc = protoId.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); - auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); + auto socketName = QLatin1String("pl") + prefix + QLatin1Char('-') + QString::number(idNum, 16).left(12); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib("kernel32"); @@ -98,12 +98,12 @@ ApplicationId ApplicationId::fromPathAndVersion(const QString& dataPath, const Q QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1); QString result = dataPath + QLatin1Char('-') + version; shasum.addData(result.toUtf8()); - return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex())); + return ApplicationId(QLatin1String("pl") + QString::fromLatin1(shasum.result().toHex()).left(12)); } ApplicationId ApplicationId::fromCustomId(const QString& id) { - return ApplicationId(QLatin1String("qtsingleapp-") + id); + return ApplicationId(QLatin1String("pl") + id); } ApplicationId ApplicationId::fromRawString(const QString& id) @@ -139,7 +139,7 @@ bool LocalPeer::isClient() #if defined(Q_OS_UNIX) // ### Workaround if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath()) + QLatin1Char('/') + socketName); + QLocalServer::removeServer(socketName); res = server->listen(socketName); } #endif From bd304eee947847b1d8630568933e5d95046d349c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 13 Apr 2025 12:16:07 -0700 Subject: [PATCH 112/181] chore: use nix-shell over nix develop in .envrc (brakes less things) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .envrc | 2 +- flake.nix | 2 ++ shell.nix | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 shell.nix diff --git a/.envrc b/.envrc index 190b5b2b3..1d11c5354 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1,2 @@ -use flake +use nix watch_file nix/*.nix diff --git a/flake.nix b/flake.nix index fd3003bc4..594a82d91 100644 --- a/flake.nix +++ b/flake.nix @@ -132,6 +132,8 @@ { default = pkgs.mkShell { + name = "prism-launcher"; + inputsFrom = [ packages'.prismlauncher-unwrapped ]; packages = with pkgs; [ diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..21bab1009 --- /dev/null +++ b/shell.nix @@ -0,0 +1,4 @@ +(import (fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz"; + sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU="; +}) { src = ./.; }).shellNix From 4ac6a0629b7c52aca9272cfbff8908d28b2b1763 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 03:42:28 +0800 Subject: [PATCH 113/181] Use LogView to implement level highlighting for other logs Signed-off-by: Yihe Li --- launcher/InstancePageProvider.h | 2 +- launcher/ui/pages/instance/LogPage.cpp | 135 +++++++++---------- launcher/ui/pages/instance/LogPage.h | 14 +- launcher/ui/pages/instance/OtherLogsPage.cpp | 77 ++++++++--- launcher/ui/pages/instance/OtherLogsPage.h | 7 +- launcher/ui/pages/instance/OtherLogsPage.ui | 18 ++- 6 files changed, 158 insertions(+), 95 deletions(-) diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 1d7c193f8..acc7fce58 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -46,7 +46,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { values.append(new InstanceSettingsPage(onesix)); auto logMatcher = inst->getLogFileMatcher(); if (logMatcher) { - values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); + values.append(new OtherLogsPage(inst, logMatcher)); } return values; } diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 4962f90ce..f050212b0 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -52,90 +52,81 @@ #include -class LogFormatProxyModel : public QIdentityProxyModel { - public: - LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} - QVariant data(const QModelIndex& index, int role) const override - { - const LogColors& colors = APPLICATION->themeManager()->getLogColors(); +QVariant LogFormatProxyModel::data(const QModelIndex& index, int role) const +{ + const LogColors& colors = APPLICATION->themeManager()->getLogColors(); - switch (role) { - case Qt::FontRole: - return m_font; - case Qt::ForegroundRole: { - auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); - QColor result = colors.foreground.value(level); + switch (role) { + case Qt::FontRole: + return m_font; + case Qt::ForegroundRole: { + auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); + QColor result = colors.foreground.value(level); - if (result.isValid()) - return result; + if (result.isValid()) + return result; - break; - } - case Qt::BackgroundRole: { - auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); - QColor result = colors.background.value(level); - - if (result.isValid()) - return result; - - break; - } + break; } + case Qt::BackgroundRole: { + auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); + QColor result = colors.background.value(level); - return QIdentityProxyModel::data(index, role); + if (result.isValid()) + return result; + + break; + } } - void setFont(QFont font) { m_font = font; } + return QIdentityProxyModel::data(index, role); +} - QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const - { - QModelIndex parentIndex = parent(start); - auto compare = [this, start, parentIndex, value](int r) -> QModelIndex { - QModelIndex idx = index(r, start.column(), parentIndex); - if (!idx.isValid() || idx == start) { - return QModelIndex(); - } - QVariant v = data(idx, Qt::DisplayRole); - QString t = v.toString(); - if (t.contains(value, Qt::CaseInsensitive)) - return idx; +QModelIndex LogFormatProxyModel::find(const QModelIndex& start, const QString& value, bool reverse) const +{ + QModelIndex parentIndex = parent(start); + auto compare = [this, start, parentIndex, value](int r) -> QModelIndex { + QModelIndex idx = index(r, start.column(), parentIndex); + if (!idx.isValid() || idx == start) { return QModelIndex(); - }; - if (reverse) { - int from = start.row(); - int to = 0; - - for (int i = 0; i < 2; ++i) { - for (int r = from; (r >= to); --r) { - auto idx = compare(r); - if (idx.isValid()) - return idx; - } - // prepare for the next iteration - from = rowCount() - 1; - to = start.row(); - } - } else { - int from = start.row(); - int to = rowCount(parentIndex); - - for (int i = 0; i < 2; ++i) { - for (int r = from; (r < to); ++r) { - auto idx = compare(r); - if (idx.isValid()) - return idx; - } - // prepare for the next iteration - from = 0; - to = start.row(); - } } + QVariant v = data(idx, Qt::DisplayRole); + QString t = v.toString(); + if (t.contains(value, Qt::CaseInsensitive)) + return idx; return QModelIndex(); - } + }; + if (reverse) { + int from = start.row(); + int to = 0; - private: - QFont m_font; -}; + for (int i = 0; i < 2; ++i) { + for (int r = from; (r >= to); --r) { + auto idx = compare(r); + if (idx.isValid()) + return idx; + } + // prepare for the next iteration + from = rowCount() - 1; + to = start.row(); + } + } else { + int from = start.row(); + int to = rowCount(parentIndex); + + for (int i = 0; i < 2; ++i) { + for (int r = from; (r < to); ++r) { + auto idx = compare(r); + if (idx.isValid()) + return idx; + } + // prepare for the next iteration + from = 0; + to = start.row(); + } + } + return QModelIndex(); +} LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index 6c259891d..1295410ea 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -35,6 +35,7 @@ #pragma once +#include #include #include @@ -46,7 +47,18 @@ namespace Ui { class LogPage; } class QTextCharFormat; -class LogFormatProxyModel; + +class LogFormatProxyModel : public QIdentityProxyModel { + public: + LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} + QVariant data(const QModelIndex& index, int role) const override; + QFont getFont() const { return m_font; } + void setFont(QFont font) { m_font = font; } + QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const; + + private: + QFont m_font; +}; class LogPage : public QWidget, public BasePage { Q_OBJECT diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index ed8ef68d9..6fa7cb4c1 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -46,12 +46,38 @@ #include #include "RecursiveFileSystemWatcher.h" -OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget* parent) - : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter), m_watcher(new RecursiveFileSystemWatcher(this)) +OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent) + : QWidget(parent) + , ui(new Ui::OtherLogsPage) + , m_instance(instance) + , m_path(instance->getLogFileRoot()) + , m_fileFilter(fileFilter) + , m_watcher(new RecursiveFileSystemWatcher(this)) + , m_model(new LogModel()) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); + m_proxy = new LogFormatProxyModel(this); + + // set up fonts in the log proxy + { + QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if (!conversionOk) { + fontSize = 11; + } + m_proxy->setFont(QFont(fontFamily, fontSize)); + } + + ui->text->setModel(m_proxy); + + m_model->setMaxLines(m_instance->getConsoleMaxLines()); + m_model->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); + m_model->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(m_model->getMaxLines())); + m_proxy->setSourceModel(m_model.get()); + m_watcher->setMatcher(fileFilter); m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); @@ -139,14 +165,8 @@ void OtherLogsPage::on_btnReload_clicked() QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2").arg(m_currentFile, file.errorString())); } else { auto setPlainText = [this](const QString& text) { - QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if (!conversionOk) { - fontSize = 11; - } QTextDocument* doc = ui->text->document(); - doc->setDefaultFont(QFont(fontFamily, fontSize)); + doc->setDefaultFont(m_proxy->getFont()); ui->text->setPlainText(text); }; auto showTooBig = [setPlainText, &file]() { @@ -173,7 +193,32 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - setPlainText(content); + + // If the file is not too big for display, but too slow for syntax highlighting, just show content as plain text + if (content.size() >= 10000000ll || content.isEmpty()) { + setPlainText(content); + return; + } + + // Try to determine a level for each line + if (content.back() == '\n') + content = content.removeLast(); + for (auto& line : content.split('\n')) { + MessageLevel::Enum level = MessageLevel::Unknown; + + // if the launcher part set a log level, use it + auto innerLevel = MessageLevel::fromLine(line); + if (innerLevel != MessageLevel::Unknown) { + level = innerLevel; + } + + // If the level is still undetermined, guess level + if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { + level = m_instance->guessLevel(line, level); + } + + m_model->append(level, line); + } } } @@ -273,27 +318,21 @@ void OtherLogsPage::setControlsEnabled(const bool enabled) ui->btnClean->setEnabled(enabled); } -// FIXME: HACK, use LogView instead? -static void findNext(QPlainTextEdit* _this, const QString& what, bool reverse) -{ - _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); -} - void OtherLogsPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); bool reverse = modifiers & Qt::ShiftModifier; - findNext(ui->text, ui->searchBar->text(), reverse); + ui->text->findNext(ui->searchBar->text(), reverse); } void OtherLogsPage::findNextActivated() { - findNext(ui->text, ui->searchBar->text(), false); + ui->text->findNext(ui->searchBar->text(), false); } void OtherLogsPage::findPreviousActivated() { - findNext(ui->text, ui->searchBar->text(), true); + ui->text->findNext(ui->searchBar->text(), true); } void OtherLogsPage::findActivated() diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index 85a3a2dbc..d65ed6456 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -39,6 +39,7 @@ #include #include +#include "LogPage.h" #include "ui/pages/BasePage.h" namespace Ui { @@ -51,7 +52,7 @@ class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); + explicit OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); ~OtherLogsPage(); QString id() const override { return "logs"; } @@ -82,8 +83,12 @@ class OtherLogsPage : public QWidget, public BasePage { private: Ui::OtherLogsPage* ui; + InstancePtr m_instance; QString m_path; QString m_currentFile; IPathMatcher::Ptr m_fileFilter; RecursiveFileSystemWatcher* m_watcher; + + LogFormatProxyModel* m_proxy; + shared_qobject_ptr m_model; }; diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 3fdb023fe..de3091917 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -44,16 +44,25 @@ - + false + + false + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + false + @@ -130,6 +139,13 @@ + + + LogView + QPlainTextEdit +
ui/widgets/LogView.h
+
+
tabWidget selectLogBox From 1ee1bab067d94035b01c76c8728b3d4e09b702ef Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 05:07:56 +0800 Subject: [PATCH 114/181] Add color lines button Signed-off-by: Yihe Li --- launcher/launch/LogModel.cpp | 12 ++++++++++++ launcher/launch/LogModel.h | 3 +++ launcher/ui/pages/instance/LogPage.cpp | 16 ++++++++++++++++ launcher/ui/pages/instance/LogPage.h | 1 + launcher/ui/pages/instance/LogPage.ui | 11 +++++++++++ launcher/ui/widgets/LogView.cpp | 12 ++++++++++-- launcher/ui/widgets/LogView.h | 2 ++ 7 files changed, 55 insertions(+), 2 deletions(-) diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 23a33ae18..dd32d46a2 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -149,3 +149,15 @@ bool LogModel::wrapLines() const { return m_lineWrap; } + +void LogModel::setColorLines(bool state) +{ + if (m_colorLines != state) { + m_colorLines = state; + } +} + +bool LogModel::colorLines() const +{ + return m_colorLines; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 167f74190..6c2a8cff3 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -27,6 +27,8 @@ class LogModel : public QAbstractListModel { void setLineWrap(bool state); bool wrapLines() const; + void setColorLines(bool state); + bool colorLines() const; enum Roles { LevelRole = Qt::UserRole }; @@ -47,6 +49,7 @@ class LogModel : public QAbstractListModel { QString m_overflowMessage = "OVERFLOW"; bool m_suspended = false; bool m_lineWrap = true; + bool m_colorLines = true; private: Q_DISABLE_COPY(LogModel) diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index f050212b0..7897a2932 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -180,6 +180,13 @@ void LogPage::modelStateToUI() ui->text->setWordWrap(false); ui->wrapCheckbox->setCheckState(Qt::Unchecked); } + if (m_model->colorLines()) { + ui->text->setColorLines(true); + ui->colorCheckbox->setCheckState(Qt::Checked); + } else { + ui->text->setColorLines(false); + ui->colorCheckbox->setCheckState(Qt::Unchecked); + } if (m_model->suspended()) { ui->trackLogCheckbox->setCheckState(Qt::Unchecked); } else { @@ -193,6 +200,7 @@ void LogPage::UIToModelState() return; } m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); + m_model->setColorLines(ui->colorCheckbox->checkState() == Qt::Checked); m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); } @@ -282,6 +290,14 @@ void LogPage::on_wrapCheckbox_clicked(bool checked) m_model->setLineWrap(checked); } +void LogPage::on_colorCheckbox_clicked(bool checked) +{ + ui->text->setColorLines(checked); + if (!m_model) + return; + m_model->setColorLines(checked); +} + void LogPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index 1295410ea..b4d74fb9c 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -82,6 +82,7 @@ class LogPage : public QWidget, public BasePage { void on_trackLogCheckbox_clicked(bool checked); void on_wrapCheckbox_clicked(bool checked); + void on_colorCheckbox_clicked(bool checked); void on_findButton_clicked(); void findActivated(); diff --git a/launcher/ui/pages/instance/LogPage.ui b/launcher/ui/pages/instance/LogPage.ui index 31bb368c8..fb8690581 100644 --- a/launcher/ui/pages/instance/LogPage.ui +++ b/launcher/ui/pages/instance/LogPage.ui @@ -74,6 +74,16 @@ + + + + Color lines + + + true + + + @@ -170,6 +180,7 @@ tabWidget trackLogCheckbox wrapCheckbox + colorCheckbox btnCopy btnPaste btnClear diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 6578b1f12..181893af4 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -60,6 +60,14 @@ void LogView::setWordWrap(bool wrapping) } } +void LogView::setColorLines(bool colorLines) +{ + if (m_colorLines == colorLines) + return; + m_colorLines = colorLines; + repopulate(); +} + void LogView::setModel(QAbstractItemModel* model) { if (m_model) { @@ -130,11 +138,11 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) format.setFont(font.value()); } auto fg = m_model->data(idx, Qt::ForegroundRole); - if (fg.isValid()) { + if (fg.isValid() && m_colorLines) { format.setForeground(fg.value()); } auto bg = m_model->data(idx, Qt::BackgroundRole); - if (bg.isValid()) { + if (bg.isValid() && m_colorLines) { format.setBackground(bg.value()); } cursor.movePosition(QTextCursor::End); diff --git a/launcher/ui/widgets/LogView.h b/launcher/ui/widgets/LogView.h index dde5f8f76..69ca332bb 100644 --- a/launcher/ui/widgets/LogView.h +++ b/launcher/ui/widgets/LogView.h @@ -15,6 +15,7 @@ class LogView : public QPlainTextEdit { public slots: void setWordWrap(bool wrapping); + void setColorLines(bool colorLines); void findNext(const QString& what, bool reverse); void scrollToBottom(); @@ -32,4 +33,5 @@ class LogView : public QPlainTextEdit { QTextCharFormat* m_defaultFormat = nullptr; bool m_scroll = false; bool m_scrolling = false; + bool m_colorLines = true; }; From 5634723ecd7e966b4722b7f62692de18cbde631b Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 06:00:49 +0800 Subject: [PATCH 115/181] Harmonizing other log controls with minecraft log Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 25 +++ launcher/ui/pages/instance/OtherLogsPage.h | 4 + launcher/ui/pages/instance/OtherLogsPage.ui | 173 +++++++++++++------ 3 files changed, 147 insertions(+), 55 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 6fa7cb4c1..40a5d9d94 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -203,6 +203,8 @@ void OtherLogsPage::on_btnReload_clicked() // Try to determine a level for each line if (content.back() == '\n') content = content.removeLast(); + ui->text->clear(); + m_model->clear(); for (auto& line : content.split('\n')) { MessageLevel::Enum level = MessageLevel::Unknown; @@ -232,6 +234,11 @@ void OtherLogsPage::on_btnCopy_clicked() GuiUtil::setClipboardText(ui->text->toPlainText()); } +void OtherLogsPage::on_btnBottom_clicked() +{ + ui->text->scrollToBottom(); +} + void OtherLogsPage::on_btnDelete_clicked() { if (m_currentFile.isEmpty()) { @@ -308,6 +315,24 @@ void OtherLogsPage::on_btnClean_clicked() } } +void OtherLogsPage::on_wrapCheckbox_clicked(bool checked) +{ + ui->text->setWordWrap(checked); + if (!m_model) + return; + m_model->setLineWrap(checked); + ui->text->scrollToBottom(); +} + +void OtherLogsPage::on_colorCheckbox_clicked(bool checked) +{ + ui->text->setColorLines(checked); + if (!m_model) + return; + m_model->setColorLines(checked); + ui->text->scrollToBottom(); +} + void OtherLogsPage::setControlsEnabled(const bool enabled) { ui->btnReload->setEnabled(enabled); diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index d65ed6456..9394ab9b8 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -72,6 +72,10 @@ class OtherLogsPage : public QWidget, public BasePage { void on_btnCopy_clicked(); void on_btnDelete_clicked(); void on_btnClean_clicked(); + void on_btnBottom_clicked(); + + void on_wrapCheckbox_clicked(bool checked); + void on_colorCheckbox_clicked(bool checked); void on_findButton_clicked(); void findActivated(); diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index de3091917..ca700e103 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -33,17 +33,41 @@ Tab 1 + + + + Search: + + + - Find + &Find - + + + + Qt::Vertical + + + + + + + Scroll all the way to bottom + + + &Bottom + + + + false @@ -65,54 +89,98 @@ - + - - - - Copy the whole log into the clipboard - - - &Copy - - + + + + + + Delete the selected log + + + &Delete This + + + + + + + Delete all the logs + + + Delete &All + + + + - - - - Clear the log - - - Delete - - - - - - - Upload the log to the paste service configured in preferences. - - - Upload - - - - - - - Clear the log - - - Clean - - - - - - - Reload - - + + + + + + Wrap lines + + + true + + + + + + + Color lines + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy the whole log into the clipboard + + + &Copy + + + + + + + Upload the log to the paste service configured in preferences + + + Upload + + + + + + + Reload the contents of the log from the disk + + + &Reload + + + + @@ -126,13 +194,6 @@ - - - - Search: - - - @@ -154,6 +215,8 @@ btnPaste btnDelete btnClean + wrapCheckbox + colorCheckbox text searchBar findButton From de66fe4eda954165a9d00f4fb94aad562c70a21a Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 06:16:47 +0800 Subject: [PATCH 116/181] Apparently removeLast() only comes in Qt 6.5+ Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 40a5d9d94..2b4bcb59b 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -202,7 +202,7 @@ void OtherLogsPage::on_btnReload_clicked() // Try to determine a level for each line if (content.back() == '\n') - content = content.removeLast(); + content = content.remove(content.size() - 1, 1); ui->text->clear(); m_model->clear(); for (auto& line : content.split('\n')) { From 521302a96251b2f55de2f15736c5ff3093fa4e22 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 20:22:21 +0800 Subject: [PATCH 117/181] Move delete buttons to the same line & set model to nullptr before adding lines Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 3 +++ launcher/ui/pages/instance/OtherLogsPage.ui | 28 ++++++++++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 2b4bcb59b..974118626 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -204,6 +204,7 @@ void OtherLogsPage::on_btnReload_clicked() if (content.back() == '\n') content = content.remove(content.size() - 1, 1); ui->text->clear(); + ui->text->setModel(nullptr); m_model->clear(); for (auto& line : content.split('\n')) { MessageLevel::Enum level = MessageLevel::Unknown; @@ -221,6 +222,8 @@ void OtherLogsPage::on_btnReload_clicked() m_model->append(level, line); } + ui->text->setModel(m_proxy); + ui->text->scrollToBottom(); } } diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index ca700e103..b4bb25b08 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -91,15 +91,25 @@ - + + + + + + 0 + 0 + + + + Delete the selected log - &Delete This + &Delete Selected @@ -115,7 +125,7 @@ - + @@ -166,7 +176,7 @@ Upload the log to the paste service configured in preferences - Upload + &Upload @@ -182,16 +192,6 @@ - - - - - 0 - 0 - - - - From be803b32791e4ed2b34867354df3f19293cff604 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 15 Apr 2025 15:27:47 +0100 Subject: [PATCH 118/181] Optimise guessLevel Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d1780d497..b155e535a 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -36,6 +36,7 @@ */ #include "MinecraftInstance.h" +#include #include "Application.h" #include "BuildConfig.h" #include "QObjectPtr.h" @@ -1014,8 +1015,18 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level) { - QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); - auto match = re.match(line); + if (line.contains("overwriting existing")) + return MessageLevel::Fatal; + + // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * + static const QRegularExpression JAVA_EXCEPTION( + R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\\d_$]*\.)+[a-zA-Z_$][a-zA-Z\\d_$]*)"); + + if (line.contains(JAVA_EXCEPTION)) + return MessageLevel::Error; + + static const QRegularExpression LINE_WITH_LEVEL("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + auto match = LINE_WITH_LEVEL.match(line); if (match.hasMatch()) { // New style logs from log4j QString timestamp = match.captured("timestamp"); @@ -1042,15 +1053,6 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev if (line.contains("[DEBUG]")) level = MessageLevel::Debug; } - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; - if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at " + javaSymbol)) || - line.contains(QRegularExpression("Caused by: " + javaSymbol)) || - line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) || - line.contains(QRegularExpression("... \\d+ more$"))) - return MessageLevel::Error; return level; } From ce76320b23282c678f64d92312af450d8635faa7 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 15 Apr 2025 15:58:11 +0100 Subject: [PATCH 119/181] Remove unnecessary import Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index b155e535a..7b02e93ba 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -36,7 +36,6 @@ */ #include "MinecraftInstance.h" -#include #include "Application.h" #include "BuildConfig.h" #include "QObjectPtr.h" From 29b81e7163019f45c77d668afe17794286011e24 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 23:11:54 +0800 Subject: [PATCH 120/181] Set parent for LogModel Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 974118626..0d94843c0 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -53,7 +53,7 @@ OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, , m_path(instance->getLogFileRoot()) , m_fileFilter(fileFilter) , m_watcher(new RecursiveFileSystemWatcher(this)) - , m_model(new LogModel()) + , m_model(new LogModel(this)) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); From 5ce6ad604b06c3274f24bb49984f904c1fa5bdb2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 16:52:45 +0300 Subject: [PATCH 121/181] chore: sync cmake version with the one used in the launcher Signed-off-by: Trial97 --- libraries/LocalPeer/CMakeLists.txt | 2 +- libraries/gamemode/CMakeLists.txt | 2 +- libraries/javacheck/CMakeLists.txt | 2 +- libraries/launcher/CMakeLists.txt | 2 +- libraries/murmur2/CMakeLists.txt | 2 +- libraries/qdcss/CMakeLists.txt | 2 +- libraries/rainbow/CMakeLists.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index b736cefcb..f6de6581c 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(LocalPeer) if(QT_VERSION_MAJOR EQUAL 5) diff --git a/libraries/gamemode/CMakeLists.txt b/libraries/gamemode/CMakeLists.txt index 9e07f34ac..61195ac21 100644 --- a/libraries/gamemode/CMakeLists.txt +++ b/libraries/gamemode/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(gamemode VERSION 1.6.1) diff --git a/libraries/javacheck/CMakeLists.txt b/libraries/javacheck/CMakeLists.txt index fd545d2bc..b9bcb121a 100644 --- a/libraries/javacheck/CMakeLists.txt +++ b/libraries/javacheck/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(launcher Java) find_package(Java 1.7 REQUIRED COMPONENTS Development) diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 4cd1ba58b..dfc4ebb32 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(launcher Java) find_package(Java 1.7 REQUIRED COMPONENTS Development) diff --git a/libraries/murmur2/CMakeLists.txt b/libraries/murmur2/CMakeLists.txt index f3068201d..be989ee36 100644 --- a/libraries/murmur2/CMakeLists.txt +++ b/libraries/murmur2/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(murmur2) set(MURMUR_SOURCES diff --git a/libraries/qdcss/CMakeLists.txt b/libraries/qdcss/CMakeLists.txt index 0afdef321..ab8aaef94 100644 --- a/libraries/qdcss/CMakeLists.txt +++ b/libraries/qdcss/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(qdcss) if(QT_VERSION_MAJOR EQUAL 5) diff --git a/libraries/rainbow/CMakeLists.txt b/libraries/rainbow/CMakeLists.txt index b6bbe7101..0867b2d27 100644 --- a/libraries/rainbow/CMakeLists.txt +++ b/libraries/rainbow/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(rainbow) if(QT_VERSION_MAJOR EQUAL 5) From c3f4735808c28e1db66b1dbacdad731fdf9cb465 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 17:02:22 +0300 Subject: [PATCH 122/181] fix: compile warning regarding duplicate object name Signed-off-by: Trial97 --- launcher/ui/pages/instance/OtherLogsPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index b4bb25b08..6d1a46139 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -126,7 +126,7 @@ - + From b70d9d6537bf530594a3c69644283b231bb2d5e2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 17:56:01 +0300 Subject: [PATCH 123/181] chore: update submodules Signed-off-by: Trial97 --- flatpak/shared-modules | 2 +- libraries/extra-cmake-modules | 2 +- libraries/libnbtplusplus | 2 +- libraries/quazip | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flatpak/shared-modules b/flatpak/shared-modules index f5d368a31..1f8e591b2 160000 --- a/flatpak/shared-modules +++ b/flatpak/shared-modules @@ -1 +1 @@ -Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e +Subproject commit 1f8e591b263eef8a0dc04929f2da135af59fac3c diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules index a3d9394ab..1f820dc98 160000 --- a/libraries/extra-cmake-modules +++ b/libraries/extra-cmake-modules @@ -1 +1 @@ -Subproject commit a3d9394aba4b35789293378e04fb7473d65edf97 +Subproject commit 1f820dc98d0a520c175433bcbb0098327d82aac6 diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus index 23b955121..531449ba1 160000 --- a/libraries/libnbtplusplus +++ b/libraries/libnbtplusplus @@ -1 +1 @@ -Subproject commit 23b955121b8217c1c348a9ed2483167a6f3ff4ad +Subproject commit 531449ba1c930c98e0bcf5d332b237a8566f9d78 diff --git a/libraries/quazip b/libraries/quazip index 8aeb3f7d8..3fd3b299b 160000 --- a/libraries/quazip +++ b/libraries/quazip @@ -1 +1 @@ -Subproject commit 8aeb3f7d8254f4bf1f7c6cf2a8f59c2ca141a552 +Subproject commit 3fd3b299b875fbd2beac4894b8a870d80022cad7 From 6e00f94a578838337f93d39c80d7f6edc0f031ea Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 17 Apr 2025 22:43:32 +0300 Subject: [PATCH 124/181] chore: rename varibales to match code standards Signed-off-by: Trial97 --- launcher/minecraft/World.cpp | 36 +++++++++++++-------------- launcher/minecraft/World.h | 6 ++--- launcher/minecraft/WorldList.cpp | 42 ++++++++++++++++---------------- launcher/minecraft/WorldList.h | 10 ++++---- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index bd28f9e9a..fc67fbb13 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -252,41 +252,41 @@ void World::readFromFS(const QFileInfo& file) { auto bytes = getLevelDatDataFromFS(file); if (bytes.isEmpty()) { - is_valid = false; + m_isValid = false; return; } loadFromLevelDat(bytes); - levelDatTime = file.lastModified(); + m_levelDatTime = file.lastModified(); } void World::readFromZip(const QFileInfo& file) { QuaZip zip(file.absoluteFilePath()); - is_valid = zip.open(QuaZip::mdUnzip); - if (!is_valid) { + m_isValid = zip.open(QuaZip::mdUnzip); + if (!m_isValid) { return; } auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - is_valid = !location.isEmpty(); - if (!is_valid) { + m_isValid = !location.isEmpty(); + if (!m_isValid) { return; } m_containerOffsetPath = location; QuaZipFile zippedFile(&zip); // read the install profile - is_valid = zip.setCurrentFile(location + "level.dat"); - if (!is_valid) { + m_isValid = zip.setCurrentFile(location + "level.dat"); + if (!m_isValid) { return; } - is_valid = zippedFile.open(QIODevice::ReadOnly); + m_isValid = zippedFile.open(QIODevice::ReadOnly); QuaZipFileInfo64 levelDatInfo; zippedFile.getFileInfo(&levelDatInfo); auto modTime = levelDatInfo.getNTFSmTime(); if (!modTime.isValid()) { modTime = levelDatInfo.dateTime; } - levelDatTime = modTime; - if (!is_valid) { + m_levelDatTime = modTime; + if (!m_isValid) { return; } loadFromLevelDat(zippedFile.readAll()); @@ -430,7 +430,7 @@ void World::loadFromLevelDat(QByteArray data) { auto levelData = parseLevelDat(data); if (!levelData) { - is_valid = false; + m_isValid = false; return; } @@ -439,20 +439,20 @@ void World::loadFromLevelDat(QByteArray data) valPtr = &levelData->at("Data"); } catch (const std::out_of_range& e) { qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what(); - is_valid = false; + m_isValid = false; return; } nbt::value& val = *valPtr; - is_valid = val.get_type() == nbt::tag_type::Compound; - if (!is_valid) + m_isValid = val.get_type() == nbt::tag_type::Compound; + if (!m_isValid) return; auto name = read_string(val, "LevelName"); m_actualName = name ? *name : m_folderName; auto timestamp = read_long(val, "LastPlayed"); - m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime; + m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : m_levelDatTime; m_gameType = read_gametype(val, "GameType"); @@ -490,7 +490,7 @@ bool World::replace(World& with) bool World::destroy() { - if (!is_valid) + if (!m_isValid) return false; if (FS::trash(m_containerFile.filePath())) @@ -508,7 +508,7 @@ bool World::destroy() bool World::operator==(const World& other) const { - return is_valid == other.is_valid && folderName() == other.folderName(); + return m_isValid == other.m_isValid && folderName() == other.folderName(); } bool World::isSymLinkUnder(const QString& instPath) const diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 4303dc553..6a4e1f092 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -39,7 +39,7 @@ class World { QDateTime lastPlayed() const { return m_lastPlayed; } GameType gameType() const { return m_gameType; } int64_t seed() const { return m_randomSeed; } - bool isValid() const { return is_valid; } + bool isValid() const { return m_isValid; } bool isOnFS() const { return m_containerFile.isDir(); } QFileInfo container() const { return m_containerFile; } // delete all the files of this world @@ -83,10 +83,10 @@ class World { QString m_folderName; QString m_actualName; QString m_iconFile; - QDateTime levelDatTime; + QDateTime m_levelDatTime; QDateTime m_lastPlayed; int64_t m_size; int64_t m_randomSeed = 0; GameType m_gameType; - bool is_valid = false; + bool m_isValid = false; }; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index cf27be676..f06eddb66 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -51,18 +51,18 @@ WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractList m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); m_watcher = new QFileSystemWatcher(this); - is_watching = false; + m_isWatching = false; connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged); } void WorldList::startWatching() { - if (is_watching) { + if (m_isWatching) { return; } update(); - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) { + m_isWatching = m_watcher->addPath(m_dir.absolutePath()); + if (m_isWatching) { qDebug() << "Started watching " << m_dir.absolutePath(); } else { qDebug() << "Failed to start watching " << m_dir.absolutePath(); @@ -71,11 +71,11 @@ void WorldList::startWatching() void WorldList::stopWatching() { - if (!is_watching) { + if (!m_isWatching) { return; } - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) { + m_isWatching = !m_watcher->removePath(m_dir.absolutePath()); + if (!m_isWatching) { qDebug() << "Stopped watching " << m_dir.absolutePath(); } else { qDebug() << "Failed to stop watching " << m_dir.absolutePath(); @@ -101,12 +101,12 @@ bool WorldList::update() } } beginResetModel(); - worlds.swap(newWorlds); + m_worlds.swap(newWorlds); endResetModel(); return true; } -void WorldList::directoryChanged(QString path) +void WorldList::directoryChanged(QString) { update(); } @@ -123,12 +123,12 @@ QString WorldList::instDirPath() const bool WorldList::deleteWorld(int index) { - if (index >= worlds.size() || index < 0) + if (index >= m_worlds.size() || index < 0) return false; - World& m = worlds[index]; + World& m = m_worlds[index]; if (m.destroy()) { beginRemoveRows(QModelIndex(), index, index); - worlds.removeAt(index); + m_worlds.removeAt(index); endRemoveRows(); emit changed(); return true; @@ -139,11 +139,11 @@ bool WorldList::deleteWorld(int index) bool WorldList::deleteWorlds(int first, int last) { for (int i = first; i <= last; i++) { - World& m = worlds[i]; + World& m = m_worlds[i]; m.destroy(); } beginRemoveRows(QModelIndex(), first, last); - worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); + m_worlds.erase(m_worlds.begin() + first, m_worlds.begin() + last + 1); endRemoveRows(); emit changed(); return true; @@ -151,9 +151,9 @@ bool WorldList::deleteWorlds(int first, int last) bool WorldList::resetIcon(int row) { - if (row >= worlds.size() || row < 0) + if (row >= m_worlds.size() || row < 0) return false; - World& m = worlds[row]; + World& m = m_worlds[row]; if (m.resetIcon()) { emit dataChanged(index(row), index(row), { WorldList::IconFileRole }); return true; @@ -174,12 +174,12 @@ QVariant WorldList::data(const QModelIndex& index, int role) const int row = index.row(); int column = index.column(); - if (row < 0 || row >= worlds.size()) + if (row < 0 || row >= m_worlds.size()) return QVariant(); QLocale locale; - auto& world = worlds[row]; + auto& world = m_worlds[row]; switch (role) { case Qt::DisplayRole: switch (column) { @@ -339,9 +339,9 @@ QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const if (idx.column() != 0) continue; int row = idx.row(); - if (row < 0 || row >= this->worlds.size()) + if (row < 0 || row >= this->m_worlds.size()) continue; - worlds_.append(this->worlds[row]); + worlds_.append(this->m_worlds[row]); } if (!worlds_.size()) { return new QMimeData(); @@ -393,7 +393,7 @@ bool WorldList::dropMimeData(const QMimeData* data, return false; // files dropped from outside? if (data->hasUrls()) { - bool was_watching = is_watching; + bool was_watching = m_isWatching; if (was_watching) stopWatching(); auto urls = data->urls(); diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index bea24bb9a..45e2eecac 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -40,9 +40,9 @@ class WorldList : public QAbstractListModel { virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual int columnCount(const QModelIndex& parent) const; - size_t size() const { return worlds.size(); }; + size_t size() const { return m_worlds.size(); }; bool empty() const { return size() == 0; } - World& operator[](size_t index) { return worlds[index]; } + World& operator[](size_t index) { return m_worlds[index]; } /// Reloads the mod list and returns true if the list changed. virtual bool update(); @@ -82,7 +82,7 @@ class WorldList : public QAbstractListModel { QString instDirPath() const; - const QList& allWorlds() const { return worlds; } + const QList& allWorlds() const { return m_worlds; } private slots: void directoryChanged(QString path); @@ -93,7 +93,7 @@ class WorldList : public QAbstractListModel { protected: BaseInstance* m_instance; QFileSystemWatcher* m_watcher; - bool is_watching; + bool m_isWatching; QDir m_dir; - QList worlds; + QList m_worlds; }; From efeaefbf2e50b06d7c09a20e384d63b94b1f0699 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 17 Apr 2025 23:31:25 +0300 Subject: [PATCH 125/181] fix: load world size async Signed-off-by: Trial97 --- launcher/minecraft/World.cpp | 22 ++++------------ launcher/minecraft/World.h | 2 ++ launcher/minecraft/WorldList.cpp | 44 +++++++++++++++++++++++++++++++- launcher/minecraft/WorldList.h | 1 + 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index fc67fbb13..8ae097bad 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -198,22 +198,6 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data) 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()) { - it.next(); - total += it.fileInfo().size(); - } - return total; - } - return -1; -} - World::World(const QFileInfo& file) { repath(file); @@ -223,7 +207,6 @@ void World::repath(const QFileInfo& file) { m_containerFile = file; m_folderName = file.fileName(); - m_size = calculateWorldSize(file); if (file.isFile() && file.suffix() == "zip") { m_iconFile = QString(); readFromZip(file); @@ -531,3 +514,8 @@ bool World::isMoreThanOneHardLink() const } return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1; } + +void World::setSize(int64_t size) +{ + m_size = size; +} diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 6a4e1f092..34d418e79 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -54,6 +54,8 @@ class World { bool rename(const QString& to); bool install(const QString& to, const QString& name = QString()); + void setSize(int64_t size); + // WEAK compare operator - used for replacing worlds bool operator==(const World& other) const; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index f06eddb66..5f192740e 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -37,13 +37,14 @@ #include #include +#include #include #include #include +#include #include #include #include -#include "Application.h" WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir) { @@ -103,6 +104,7 @@ bool WorldList::update() beginResetModel(); m_worlds.swap(newWorlds); endResetModel(); + loadWorldsAsync(); return true; } @@ -416,4 +418,44 @@ bool WorldList::dropMimeData(const QMimeData* data, return false; } +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()) { + it.next(); + total += it.fileInfo().size(); + } + return total; + } + return -1; +} + +void WorldList::loadWorldsAsync() +{ + for (int i = 0; i < m_worlds.size(); ++i) { + auto file = m_worlds.at(i).container(); + int row = i; + QThreadPool::globalInstance()->start([this, file, row]() mutable { + auto size = calculateWorldSize(file); + + QMetaObject::invokeMethod( + this, + [this, size, row, file]() { + if (row < m_worlds.size() && m_worlds[row].container() == file) { + m_worlds[row].setSize(size); + + // Notify views + QModelIndex modelIndex = index(row); + emit dataChanged(modelIndex, modelIndex, { SizeRole }); + } + }, + Qt::QueuedConnection); + }); + } +} + #include "WorldList.moc" diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 45e2eecac..93fecf1f5 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -86,6 +86,7 @@ class WorldList : public QAbstractListModel { private slots: void directoryChanged(QString path); + void loadWorldsAsync(); signals: void changed(); From 8a3aafc2743a2137480a6adf7fc02b9d14eb16eb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 17 Apr 2025 22:17:17 +0300 Subject: [PATCH 126/181] chore: remove qt5 from github actions Signed-off-by: Trial97 --- .github/workflows/build.yml | 28 +++++++--------------------- .github/workflows/codeql.yml | 25 ++++++++++++++++++------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 11ef262ea..5d5cbc893 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,13 +52,6 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 - qt_ver: 5 - qt_host: linux - qt_arch: "" - qt_version: "5.15.2" - qt_modules: "qtnetworkauth" - - os: ubuntu-22.04 qt_ver: 6 qt_host: linux @@ -254,7 +247,7 @@ jobs: arch: ${{ matrix.vcvars_arch }} - name: Prepare AppImage (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' env: APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }} LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }} @@ -287,7 +280,7 @@ jobs: ## - name: Configure CMake (macOS) - if: runner.os == 'macOS' && matrix.qt_ver == 6 + 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 -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja @@ -502,7 +495,7 @@ jobs: } - name: Package AppImage (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' shell: bash env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} @@ -598,29 +591,22 @@ jobs: name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher-Setup.exe - - name: Upload binary tarball (Linux, portable, Qt 5) - if: runner.os == 'Linux' && matrix.qt_ver != 6 - uses: actions/upload-artifact@v4 - with: - name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }} - path: PrismLauncher-portable.tar.gz - - - name: Upload binary tarball (Linux, portable, Qt 6) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + - name: Upload binary tarball (Linux, portable) + if: runner.os == 'Linux' uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher-portable.tar.gz - name: Upload AppImage (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - name: Upload AppImage Zsync (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d1d810374..e3243097d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,16 +1,16 @@ name: "CodeQL Code Scanning" -on: [ push, pull_request, workflow_dispatch ] +on: [push, pull_request, workflow_dispatch] jobs: CodeQL: runs-on: ubuntu-latest - + steps: - name: Checkout repository uses: actions/checkout@v4 with: - submodules: 'true' + submodules: "true" - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -20,14 +20,25 @@ jobs: languages: cpp, java - name: Install Dependencies - run: - sudo apt-get -y update + run: sudo apt-get -y update - sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev libqt5opengl5 libqt5opengl5-dev + sudo apt-get -y install ninja-build extra-cmake-modules scdoc + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + aqtversion: "==3.1.*" + py7zrversion: ">=0.20.2" + version: "6.8.1" + host: "linux" + target: "desktop" + arch: "" + modules: "qt5compat qtimageformats qtnetworkauth" + tools: "" - name: Configure and Build run: | - cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja + cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -G Ninja cmake --build build From 59bd6a915bba933ea2e9a61827b65a2c2f0d9c3e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Apr 2025 10:54:28 +0300 Subject: [PATCH 127/181] chore: remove qt5 from cmake files Signed-off-by: Trial97 --- CMakeLists.txt | 25 ++----------------------- libraries/LocalPeer/CMakeLists.txt | 4 +--- libraries/qdcss/CMakeLists.txt | 4 +--- libraries/rainbow/CMakeLists.txt | 4 +--- libraries/systeminfo/CMakeLists.txt | 4 +--- 5 files changed, 6 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 10153c3ec..e2ea079bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -310,23 +310,7 @@ endif() # Find the required Qt parts include(QtVersionlessBackport) -if(Launcher_QT_VERSION_MAJOR EQUAL 5) - set(QT_VERSION_MAJOR 5) - find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth OpenGL) - find_package(Qt5 COMPONENTS DBus) - list(APPEND Launcher_QT_DBUS Qt5::DBus) - - if(NOT Launcher_FORCE_BUNDLED_LIBS) - find_package(QuaZip-Qt5 1.3 QUIET) - endif() - if (NOT QuaZip-Qt5_FOUND) - set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) - set(FORCE_BUNDLED_QUAZIP 1) - endif() - - # Qt 6 sets these by default. Notably causes Windows APIs to use UNICODE strings. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE") -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth OpenGL) find_package(Qt6 COMPONENTS DBus) @@ -344,12 +328,7 @@ else() message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") endif() -if(Launcher_QT_VERSION_MAJOR EQUAL 5) - include(ECMQueryQt) - ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS) - ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS) - ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS) -else() +if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_PLUGINS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_PLUGINS}) set(QT_LIBS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBS}) set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS}) diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index f6de6581c..dd78647c0 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -1,9 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(LocalPeer) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core Network REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Network Core5Compat REQUIRED) list(APPEND LocalPeer_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) endif() diff --git a/libraries/qdcss/CMakeLists.txt b/libraries/qdcss/CMakeLists.txt index ab8aaef94..7e497feca 100644 --- a/libraries/qdcss/CMakeLists.txt +++ b/libraries/qdcss/CMakeLists.txt @@ -1,9 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(qdcss) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) list(APPEND qdcss_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) endif() diff --git a/libraries/rainbow/CMakeLists.txt b/libraries/rainbow/CMakeLists.txt index 0867b2d27..c971889d8 100644 --- a/libraries/rainbow/CMakeLists.txt +++ b/libraries/rainbow/CMakeLists.txt @@ -1,9 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(rainbow) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core Gui REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Gui REQUIRED) endif() diff --git a/libraries/systeminfo/CMakeLists.txt b/libraries/systeminfo/CMakeLists.txt index 33d246050..80b6b8094 100644 --- a/libraries/systeminfo/CMakeLists.txt +++ b/libraries/systeminfo/CMakeLists.txt @@ -1,8 +1,6 @@ project(systeminfo) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) list(APPEND systeminfo_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) endif() From 442aae88ce793ac1e0d686f0839ad1f7ff46666b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Apr 2025 12:03:50 +0300 Subject: [PATCH 128/181] chore: remove qt version checks from code Signed-off-by: Trial97 --- launcher/DataMigrationTask.cpp | 8 -------- launcher/FileIgnoreProxy.cpp | 4 ---- launcher/FileSystem.cpp | 12 ----------- launcher/InstanceList.cpp | 4 ---- launcher/Version.h | 8 -------- launcher/icons/IconList.cpp | 6 +----- launcher/java/JavaChecker.cpp | 8 -------- launcher/launch/steps/PostLaunchCommand.cpp | 4 ---- launcher/launch/steps/PreLaunchCommand.cpp | 4 ---- launcher/minecraft/MinecraftInstance.cpp | 8 -------- launcher/minecraft/PackProfile.cpp | 4 ---- launcher/minecraft/WorldList.cpp | 4 ---- launcher/minecraft/auth/MinecraftAccount.cpp | 9 --------- launcher/minecraft/auth/Parsers.cpp | 4 ---- launcher/minecraft/auth/steps/MSAStep.cpp | 7 +------ .../minecraft/mod/ResourceFolderModel.cpp | 5 ----- .../atlauncher/ATLPackInstallTask.cpp | 10 ---------- .../legacy_ftb/PackInstallTask.cpp | 5 ----- .../legacy_ftb/PrivatePackManager.cpp | 4 ---- launcher/net/NetRequest.cpp | 10 ---------- launcher/net/PasteUpload.cpp | 4 ---- launcher/ui/MainWindow.cpp | 4 ---- launcher/ui/dialogs/NewInstanceDialog.cpp | 4 ---- launcher/ui/instanceview/InstanceView.cpp | 20 ------------------- launcher/ui/instanceview/VisualGroup.cpp | 4 ---- launcher/ui/pages/instance/ServersPage.cpp | 8 -------- libraries/systeminfo/src/distroutils.cpp | 12 ----------- 27 files changed, 2 insertions(+), 182 deletions(-) diff --git a/launcher/DataMigrationTask.cpp b/launcher/DataMigrationTask.cpp index 92e310a16..18decc7c3 100644 --- a/launcher/DataMigrationTask.cpp +++ b/launcher/DataMigrationTask.cpp @@ -37,11 +37,7 @@ void DataMigrationTask::dryRunFinished() disconnect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::dryRunFinished); disconnect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &DataMigrationTask::dryRunAborted); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) if (!m_copyFuture.isValid() || !m_copyFuture.result()) { -#else - if (!m_copyFuture.result()) { -#endif emitFailed(tr("Failed to scan source path.")); return; } @@ -75,11 +71,7 @@ void DataMigrationTask::copyFinished() disconnect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::copyFinished); disconnect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &DataMigrationTask::copyAborted); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) if (!m_copyFuture.isValid() || !m_copyFuture.result()) { -#else - if (!m_copyFuture.result()) { -#endif emitFailed(tr("Some paths could not be copied!")); return; } diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 89c91ec1d..0314057d1 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -282,11 +282,7 @@ void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName) } auto ignoreData = ignoreFile.readAll(); auto string = QString::fromUtf8(ignoreData); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) setBlockedPaths(string.split('\n', Qt::SkipEmptyParts)); -#else - setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); -#endif } void FileIgnoreProxy::saveBlockedPathsToFile(const QString& fileName) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 7189ca841..08dc7d2cc 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -679,9 +679,6 @@ bool deletePath(QString path) bool trash(QString path, QString* pathInTrash) { -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - return false; -#else // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal if (DesktopServices::isFlatpak()) return false; @@ -690,7 +687,6 @@ bool trash(QString path, QString* pathInTrash) return false; #endif return QFile::moveToTrash(path, pathInTrash); -#endif } QString PathCombine(const QString& path1, const QString& path2) @@ -724,11 +720,7 @@ int pathDepth(const QString& path) QFileInfo info(path); -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), QString::SkipEmptyParts); -#else auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts); -#endif int numParts = parts.length(); numParts -= parts.count("."); @@ -748,11 +740,7 @@ QString pathTruncate(const QString& path, int depth) return pathTruncate(trunc, depth); } -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), QString::SkipEmptyParts); -#else auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts); -#endif if (parts.startsWith(".") && !path.startsWith(".")) { parts.removeFirst(); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..f76f8599a 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -449,11 +449,7 @@ QList InstanceList::discoverInstances() out.append(id); qDebug() << "Found instance ID" << id; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) instanceSet = QSet(out.begin(), out.end()); -#else - instanceSet = out.toSet(); -#endif m_instancesProbed = true; return out; } diff --git a/launcher/Version.h b/launcher/Version.h index b06e256aa..12e7f0832 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -72,22 +72,14 @@ class Version { } } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto numPart = QStringView{ m_fullString }.left(cutoff); -#else - auto numPart = m_fullString.leftRef(cutoff); -#endif if (!numPart.isEmpty()) { m_isNull = false; m_numPart = numPart.toInt(); } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto stringPart = QStringView{ m_fullString }.mid(cutoff); -#else - auto stringPart = m_fullString.midRef(cutoff); -#endif if (!stringPart.isEmpty()) { m_isNull = false; diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 7369d8b4b..8a2a482e1 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -137,11 +137,7 @@ QString formatName(const QDir& iconsDir, const QFileInfo& iconFile) /// Split into a separate function because the preprocessing impedes readability QSet toStringSet(const QList& list) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QSet set(list.begin(), list.end()); -#else - QSet set = list.toSet(); -#endif return set; } @@ -477,4 +473,4 @@ QString IconList::iconDirectory(const QString& key) const } } return getDirectory(); -} +} \ No newline at end of file diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 07b5d7b40..0aa725705 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -137,11 +137,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) QMap results; -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList lines = m_stdout.split("\n", Qt::SkipEmptyParts); -#else - QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); -#endif for (QString line : lines) { line = line.trimmed(); // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux @@ -149,11 +145,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) continue; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto parts = line.split('=', Qt::SkipEmptyParts); -#else - auto parts = line.split('=', QString::SkipEmptyParts); -#endif if (parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) { continue; } else { diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp index 5d893c71f..6b960974e 100644 --- a/launcher/launch/steps/PostLaunchCommand.cpp +++ b/launcher/launch/steps/PostLaunchCommand.cpp @@ -49,14 +49,10 @@ void PostLaunchCommand::executeTask() { auto cmd = m_parent->substituteVariables(m_command); emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) auto args = QProcess::splitCommand(cmd); const QString program = args.takeFirst(); m_process.start(program, args); -#else - m_process.start(cmd); -#endif } void PostLaunchCommand::on_state(LoggedProcess::State state) diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp index 318237e99..7e843ca3f 100644 --- a/launcher/launch/steps/PreLaunchCommand.cpp +++ b/launcher/launch/steps/PreLaunchCommand.cpp @@ -49,13 +49,9 @@ void PreLaunchCommand::executeTask() { auto cmd = m_parent->substituteVariables(m_command); emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) auto args = QProcess::splitCommand(cmd); const QString program = args.takeFirst(); m_process.start(program, args); -#else - m_process.start(cmd); -#endif } void PreLaunchCommand::on_state(LoggedProcess::State state) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d1780d497..463523a72 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -757,11 +757,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_index_name"] = assets->id; -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts); -#else - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); -#endif for (int i = 0; i < parts.length(); i++) { parts[i] = replaceTokensIn(parts[i], token_mapping); } @@ -816,11 +812,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT auto mainWindow = qobject_cast(w); if (mainWindow) { auto m = mainWindow->windowHandle()->frameMargins(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) screenGeometry = screenGeometry.shrunkBy(m); -#else - screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() }; -#endif break; } } diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 4deee9712..8475a1f32 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -645,11 +645,7 @@ void PackProfile::move(const int index, const MoveDirection direction) return; } beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) d->components.swapItemsAt(index, theirIndex); -#else - d->components.swap(index, theirIndex); -#endif endMoveRows(); invalidateLaunchProfile(); scheduleSave(); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index cf27be676..ae6f918e5 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -307,11 +307,7 @@ class WorldMimeData : public QMimeData { QStringList formats() const { return QMimeData::formats() << "text/uri-list"; } protected: -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QVariant retrieveData(const QString& mimetype, QMetaType type) const -#else - QVariant retrieveData(const QString& mimetype, QVariant::Type type) const -#endif { QList urls; for (auto& world : m_worlds) { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 1ed39b5ca..1613a42b1 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -106,11 +106,7 @@ QPixmap MinecraftAccount::getFace() const return QPixmap(); } QPixmap skin = QPixmap(8, 8); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) skin.fill(QColorConstants::Transparent); -#else - skin.fill(QColor(0, 0, 0, 0)); -#endif QPainter painter(&skin); painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8)); painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8)); @@ -290,13 +286,8 @@ QUuid MinecraftAccount::uuidFromUsername(QString username) // basically a reimplementation of Java's UUID#nameUUIDFromBytes QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5); -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - auto bOr = [](QByteArray& array, int index, char value) { array[index] = array.at(index) | value; }; - auto bAnd = [](QByteArray& array, int index, char value) { array[index] = array.at(index) & value; }; -#else auto bOr = [](QByteArray& array, qsizetype index, char value) { array[index] |= value; }; auto bAnd = [](QByteArray& array, qsizetype index, char value) { array[index] &= value; }; -#endif bAnd(digest, 6, (char)0x0f); // clear version bOr(digest, 6, (char)0x30); // set to version 3 bAnd(digest, 8, (char)0x3f); // clear variant diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index f9d89baa2..de1ffda86 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -315,11 +315,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) 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()) { diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 87a0f8f08..aa972be71 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -168,13 +168,8 @@ void MSAStep::perform() m_oauth2.setRefreshToken(m_data->msaToken.refresh_token); m_oauth2.refreshAccessToken(); } else { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 m_oauth2.setModifyParametersFunction( [](QAbstractOAuth::Stage stage, QMultiMap* map) { map->insert("prompt", "select_account"); }); -#else - m_oauth2.setModifyParametersFunction( - [](QAbstractOAuth::Stage stage, QMap* map) { map->insert("prompt", "select_account"); }); -#endif *m_data = AccountData(); m_data->msaClientID = m_clientId; @@ -182,4 +177,4 @@ void MSAStep::perform() } } -#include "MSAStep.moc" \ No newline at end of file +#include "MSAStep.moc" diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index d4900616b..601df84bd 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -363,16 +363,11 @@ void ResourceFolderModel::onUpdateSucceeded() auto& new_resources = update_results->resources; -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto current_list = m_resources_index.keys(); QSet current_set(current_list.begin(), current_list.end()); auto new_list = new_resources.keys(); QSet new_set(new_list.begin(), new_list.end()); -#else - QSet current_set(m_resources_index.keys().toSet()); - QSet new_set(new_resources.keys().toSet()); -#endif applyUpdates(current_set, new_set, new_resources); } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a0898edbd..a9706a768 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -678,13 +678,8 @@ void PackInstallTask::extractConfigs() return; } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/minecraft"); -#else - m_extractFuture = - QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft"); -#endif connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, [this]() { downloadMods(); }); connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, [this]() { emitAborted(); }); m_extractFutureWatcher.setFuture(m_extractFuture); @@ -897,13 +892,8 @@ void PackInstallTask::onModsDownloaded() jobPtr.reset(); if (!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), &PackInstallTask::extractMods, this, modsToExtract, modsToDecomp, modsToCopy); -#else - m_modExtractFuture = - QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy); -#endif connect(&m_modExtractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onModsExtracted); connect(&m_modExtractFutureWatcher, &QFutureWatcher::canceled, this, &PackInstallTask::emitAborted); m_modExtractFutureWatcher.setFuture(m_modExtractFuture); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index c04c0b2f3..c8d04828c 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -108,13 +108,8 @@ void PackInstallTask::unzip() return; } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip"); -#else - m_extractFuture = - QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); -#endif connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onUnzipFinished); connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &PackInstallTask::onUnzipCanceled); m_extractFutureWatcher.setFuture(m_extractFuture); diff --git a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp index 2ae351329..17e9f7d76 100644 --- a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp +++ b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp @@ -44,12 +44,8 @@ namespace LegacyFTB { void PrivatePackManager::load() { try { -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto foo = QString::fromUtf8(FS::read(m_filename)).split('\n', Qt::SkipEmptyParts); currentPacks = QSet(foo.begin(), foo.end()); -#else - currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet(); -#endif dirty = false; } catch (...) { diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 310653508..ef533f599 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -104,12 +104,10 @@ void NetRequest::executeTask() header_proxy->writeHeaders(request); } -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #if defined(LAUNCHER_APPLICATION) request.setTransferTimeout(APPLICATION->settings()->get("RequestTimeout").toInt() * 1000); #else request.setTransferTimeout(); -#endif #endif m_last_progress_time = m_clock.now(); @@ -122,11 +120,7 @@ void NetRequest::executeTask() connect(rep, &QNetworkReply::uploadProgress, this, &NetRequest::onProgress); connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::onProgress); connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &NetRequest::downloadError); -#endif connect(rep, &QNetworkReply::sslErrors, this, &NetRequest::sslErrors); connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead); } @@ -323,11 +317,7 @@ auto NetRequest::abort() -> bool { m_state = State::AbortedByUser; if (m_reply) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 disconnect(m_reply.get(), &QNetworkReply::errorOccurred, nullptr, nullptr); -#else - disconnect(m_reply.get(), QOverload::of(&QNetworkReply::error), nullptr, nullptr); -#endif m_reply->abort(); } return true; diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index c67d3b23c..86a44669e 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -130,11 +130,7 @@ void PasteUpload::executeTask() connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &PasteUpload::downloadError); -#endif m_reply = std::shared_ptr(rep); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ddf726373..53e87b57e 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -814,11 +814,7 @@ void MainWindow::updateNewsLabel() QList stringToIntList(const QString& string) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList split = string.split(',', Qt::SkipEmptyParts); -#else - QStringList split = string.split(',', QString::SkipEmptyParts); -#endif QList out; for (int i = 0; i < split.size(); ++i) { out.append(split.at(i).toInt()); diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index d9ea0aafb..6036663ba 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -136,11 +136,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, if (APPLICATION->settings()->get("NewInstanceGeometry").isValid()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray())); } else { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) auto screen = parent->screen(); -#else - auto screen = QGuiApplication::primaryScreen(); -#endif auto geometry = screen->availableSize(); resize(width(), qMin(geometry.height() - 50, 710)); } diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index c677f3951..fa1af4266 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -400,12 +400,8 @@ void InstanceView::mouseReleaseEvent(QMouseEvent* event) if (event->button() == Qt::LeftButton) { emit clicked(index); } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif if (m_pressedAlreadySelected) { option.state |= QStyle::State_Selected; } @@ -431,12 +427,8 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent* event) QPersistentModelIndex persistent = index; emit doubleClicked(persistent); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) { emit activated(index); } @@ -472,12 +464,8 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) painter.setOpacity(1.0); } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif option.widget = this; if (model()->rowCount() == 0) { @@ -732,12 +720,8 @@ QRect InstanceView::geometryRect(const QModelIndex& index) const int x = pos.first; // int y = pos.second; -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif QRect out; out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); @@ -784,12 +768,8 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList& indices, QRect* r) c QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { option.rect = paintPairs.at(j).first.translated(-r->topLeft()); diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 83103c502..089db8ad7 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -73,12 +73,8 @@ void VisualGroup::update() positionInRow = 0; maxRowHeight = 0; } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem viewItemOption; view->initViewItemOption(&viewItemOption); -#else - QStyleOptionViewItem viewItemOption = view->viewOptions(); -#endif auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height(); if (itemHeight > maxRowHeight) { diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 136fb47c7..a93966ae9 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -255,11 +255,7 @@ class ServersModel : public QAbstractListModel { return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) m_servers.swapItemsAt(row - 1, row); -#else - m_servers.swap(row - 1, row); -#endif endMoveRows(); scheduleSave(); return true; @@ -275,11 +271,7 @@ class ServersModel : public QAbstractListModel { return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) m_servers.swapItemsAt(row + 1, row); -#else - m_servers.swap(row + 1, row); -#endif endMoveRows(); scheduleSave(); return true; diff --git a/libraries/systeminfo/src/distroutils.cpp b/libraries/systeminfo/src/distroutils.cpp index 57e6c8320..5891282fe 100644 --- a/libraries/systeminfo/src/distroutils.cpp +++ b/libraries/systeminfo/src/distroutils.cpp @@ -145,11 +145,7 @@ void Sys::lsb_postprocess(Sys::LsbInfo& lsb, Sys::DistributionInfo& out) vers = lsb.codename; } else { // ubuntu, debian, gentoo, scientific, slackware, ... ? -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto parts = dist.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); -#else - auto parts = dist.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); -#endif if (parts.size()) { dist = parts[0]; } @@ -182,11 +178,7 @@ QString Sys::_extract_distribution(const QString& x) if (release.startsWith("suse linux enterprise")) { return "sles"; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList list = release.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); -#else - QStringList list = release.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); -#endif if (list.size()) { return list[0]; } @@ -196,11 +188,7 @@ QString Sys::_extract_distribution(const QString& x) QString Sys::_extract_version(const QString& x) { QRegularExpression versionish_string(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList list = x.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); -#else - QStringList list = x.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); -#endif for (int i = list.size() - 1; i >= 0; --i) { QString chunk = list[i]; if (versionish_string.match(chunk).hasMatch()) { From 5fee4e3f8bbab3063e15fcc4f1563433d7001c1b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 00:17:29 +0300 Subject: [PATCH 129/181] chore: remove qt5 from release and copyright Signed-off-by: Trial97 --- .github/workflows/trigger_release.yml | 2 -- CMakeLists.txt | 10 ---------- COPYING.md | 2 +- nix/unwrapped.nix | 3 --- 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 411a5bbeb..96f616a43 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -46,7 +46,6 @@ jobs: run: | mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }} mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz - mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip @@ -89,7 +88,6 @@ jobs: draft: true prerelease: false files: | - PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-x86_64.AppImage PrismLauncher-Linux-x86_64.AppImage.zsync PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index e2ea079bf..321232378 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,11 +88,6 @@ else() endif() endif() -# 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_DISABLE_DEPRECATED_BEFORE=0x050C00") - # Fix aarch64 build for toml++ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") @@ -334,11 +329,6 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS}) endif() -# NOTE: Qt 6 already sets this by default -if (Qt5_POSITION_INDEPENDENT_CODE) - SET(CMAKE_POSITION_INDEPENDENT_CODE ON) -endif() - if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) diff --git a/COPYING.md b/COPYING.md index f1f0b3a70..818c13c78 100644 --- a/COPYING.md +++ b/COPYING.md @@ -108,7 +108,7 @@ Information on third party licenses used in MinGW-w64 can be found in its COPYING.MinGW-w64-runtime.txt. -## Qt 5/6 +## Qt 6 Copyright (C) 2022 The Qt Company Ltd and other contributors. Contact: https://www.qt.io/licensing diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 93cda8e1a..14882e893 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -96,9 +96,6 @@ stdenv.mkDerivation { ++ lib.optionals (msaClientID != null) [ (lib.cmakeFeature "Launcher_MSA_CLIENT_ID" (toString msaClientID)) ] - ++ lib.optionals (lib.versionOlder kdePackages.qtbase.version "6") [ - (lib.cmakeFeature "Launcher_QT_VERSION_MAJOR" "5") - ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ # we wrap our binary manually (lib.cmakeFeature "INSTALL_BUNDLE" "nodeps") From 4a2b5c72dc75e03695566af399c504c19170d042 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:54:44 -0700 Subject: [PATCH 130/181] feat(color-console): support ansi colors in console Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 2 +- launcher/CMakeLists.txt | 8 ++++---- launcher/{ => console}/WindowsConsole.cpp | 3 +++ launcher/{ => console}/WindowsConsole.h | 3 +++ 4 files changed, 11 insertions(+), 5 deletions(-) rename launcher/{ => console}/WindowsConsole.cpp (99%) rename launcher/{ => console}/WindowsConsole.h (93%) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index d773d9a1c..f98112641 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -155,7 +155,7 @@ #if defined Q_OS_WIN32 #include #include -#include "WindowsConsole.h" +#include "console/WindowsConsole.h" #endif #define STRINGIFY(x) #x diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 30d657f9e..5d757a130 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -589,8 +589,8 @@ set(ATLAUNCHER_SOURCES ) set(LINKEXE_SOURCES - WindowsConsole.cpp - WindowsConsole.h + console/WindowsConsole.cpp + console/WindowsConsole.h filelink/FileLink.h filelink/FileLink.cpp @@ -1164,8 +1164,8 @@ endif() if(WIN32) set(LAUNCHER_SOURCES - WindowsConsole.cpp - WindowsConsole.h + console/WindowsConsole.cpp + console/WindowsConsole.h ${LAUNCHER_SOURCES} ) endif() diff --git a/launcher/WindowsConsole.cpp b/launcher/console/WindowsConsole.cpp similarity index 99% rename from launcher/WindowsConsole.cpp rename to launcher/console/WindowsConsole.cpp index 83cad5afa..f388bd3b1 100644 --- a/launcher/WindowsConsole.cpp +++ b/launcher/console/WindowsConsole.cpp @@ -19,6 +19,7 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif +#include "WindowsConsole.h" #include #include #include @@ -126,3 +127,5 @@ bool AttachWindowsConsole() return false; } + +std::error_code diff --git a/launcher/WindowsConsole.h b/launcher/console/WindowsConsole.h similarity index 93% rename from launcher/WindowsConsole.h rename to launcher/console/WindowsConsole.h index ab53864b4..4c1f3ee28 100644 --- a/launcher/WindowsConsole.h +++ b/launcher/console/WindowsConsole.h @@ -21,5 +21,8 @@ #pragma once +#include + void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr); bool AttachWindowsConsole(); +std::error_code EnableAnsiSupport(); From 45b6454222a83b932e0505cbd815d697226080cd Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:18:57 -0700 Subject: [PATCH 131/181] feat(ansi-console): Format console with ansi excapes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 181 +++++++++++++----- launcher/CMakeLists.txt | 31 ++- launcher/InstanceList.cpp | 6 +- launcher/console/Console.h | 33 ++++ launcher/console/WindowsConsole.cpp | 34 +++- launcher/filelink/FileLink.cpp | 5 +- .../updater/prismupdater/PrismUpdater.cpp | 109 +---------- 7 files changed, 225 insertions(+), 174 deletions(-) create mode 100644 launcher/console/Console.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index f98112641..70ffce12f 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -125,6 +125,7 @@ #include #include +#include #include #include #include "SysInfo.h" @@ -153,11 +154,16 @@ #endif #if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif #include #include #include "console/WindowsConsole.h" #endif +#include "console/Console.h" + #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) @@ -165,6 +171,69 @@ static const QLatin1String liveCheckFile("live.check"); PixmapCache* PixmapCache::s_instance = nullptr; +static bool isANSIColorConsole; + +static QString defaultLogFormat = QStringLiteral( + "%{time process}" + " " + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + " " + "|" + " " + "%{function}:%{line}" + " " + "|" + " " + "%{if-category}[%{category}]: %{endif}" + "%{message}"); + +#define ansi_reset "\x1b[0m" +#define ansi_bold "\x1b[1m" +#define ansi_faint "\x1b[2m" +#define ansi_italic "\x1b[3m" +#define ansi_red_fg "\x1b[31m" +#define ansi_green_fg "\x1b[32m" +#define ansi_yellow_fg "\x1b[33m" +#define ansi_blue_fg "\x1b[34m" +#define ansi_purple_fg "\x1b[35m" + +static QString ansiLogFormat = QStringLiteral( + "%{time process}" + " " + "%{if-debug}" ansi_bold ansi_blue_fg "D" ansi_reset + "%{endif}" + "%{if-info}" ansi_bold ansi_green_fg "I" ansi_reset + "%{endif}" + "%{if-warning}" ansi_bold ansi_yellow_fg "W" ansi_reset + "%{endif}" + "%{if-critical}" ansi_bold ansi_red_fg "C" ansi_reset + "%{endif}" + "%{if-fatal}" ansi_bold ansi_red_fg "F" ansi_reset + "%{endif}" + " " + "|" + " " ansi_faint ansi_italic "%{function}:%{line}" ansi_reset + " " + "|" + " " + "%{if-category}[" ansi_bold ansi_purple_fg "%{category}" ansi_reset + "]: %{endif}" + "%{message}"); + +#undef ansi_purple_fg +#undef ansi_blue_fg +#undef ansi_yellow_fg +#undef ansi_green_fg +#undef ansi_red_fg +#undef ansi_italic +#undef ansi_faint +#undef ansi_bold +#undef ansi_reset + namespace { /** This is used so that we can output to the log file in addition to the CLI. */ @@ -173,11 +242,24 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt static std::mutex loggerMutex; const std::lock_guard lock(loggerMutex); // synchronized, QFile logFile is not thread-safe + if (isANSIColorConsole) { + // ensure default is set for log file + qSetMessagePattern(defaultLogFormat); + } + QString out = qFormatLogMessage(type, context, msg); out += QChar::LineFeed; APPLICATION->logFile->write(out.toUtf8()); APPLICATION->logFile->flush(); + + if (isANSIColorConsole) { + // format ansi for console; + qSetMessagePattern(ansiLogFormat); + out = qFormatLogMessage(type, context, msg); + out += QChar::LineFeed; + } + QTextStream(stderr) << out.toLocal8Bit(); fflush(stderr); } @@ -218,8 +300,18 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // attach the parent console if stdout not already captured if (AttachWindowsConsole()) { consoleAttached = true; + if (auto err = EnableAnsiSupport(); !err) { + isANSIColorConsole = true; + } else { + std::cout << "Error setting up ansi console" << err.message() << std::endl; + } + } +#else + if (console::isConsole()) { + isANSIColorConsole = true; } #endif + setOrganizationName(BuildConfig.LAUNCHER_NAME); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setApplicationName(BuildConfig.LAUNCHER_NAME); @@ -448,27 +540,14 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) return; } qInstallMessageHandler(appDebugOutput); - - qSetMessagePattern( - "%{time process}" - " " - "%{if-debug}D%{endif}" - "%{if-info}I%{endif}" - "%{if-warning}W%{endif}" - "%{if-critical}C%{endif}" - "%{if-fatal}F%{endif}" - " " - "|" - " " - "%{if-category}[%{category}]: %{endif}" - "%{message}"); + qSetMessagePattern(defaultLogFormat); bool foundLoggingRules = false; auto logRulesFile = QStringLiteral("qtlogging.ini"); auto logRulesPath = FS::PathCombine(dataPath, logRulesFile); - qDebug() << "Testing" << logRulesPath << "..."; + qInfo() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); // search the dataPath() @@ -476,7 +555,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (!foundLoggingRules && !isPortable() && dirParam.isEmpty() && dataDirEnv.isEmpty()) { logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile)); if (!logRulesPath.isEmpty()) { - qDebug() << "Found" << logRulesPath << "..."; + qInfo() << "Found" << logRulesPath << "..."; foundLoggingRules = true; } } @@ -487,28 +566,28 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) #else logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); #endif - qDebug() << "Testing" << logRulesPath << "..."; + qInfo() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); } if (foundLoggingRules) { // load and set logging rules - qDebug() << "Loading logging rules from:" << logRulesPath; + qInfo() << "Loading logging rules from:" << logRulesPath; QSettings loggingRules(logRulesPath, QSettings::IniFormat); loggingRules.beginGroup("Rules"); QStringList rule_names = loggingRules.childKeys(); QStringList rules; - qDebug() << "Setting log rules:"; + qInfo() << "Setting log rules:"; for (auto rule_name : rule_names) { auto rule = QString("%1=%2").arg(rule_name).arg(loggingRules.value(rule_name).toString()); rules.append(rule); - qDebug() << " " << rule; + qInfo() << " " << rule; } auto rules_str = rules.join("\n"); QLoggingCategory::setFilterRules(rules_str); } - qDebug() << "<> Log initialized."; + qInfo() << "<> Log initialized."; } { @@ -525,33 +604,33 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } { - qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); - qDebug() << "Version : " << BuildConfig.printableVersionString(); - qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM; - qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; - qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; - qDebug() << "Compiled for : " << BuildConfig.systemID(); - qDebug() << "Compiled by : " << BuildConfig.compilerID(); - qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT; - qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No"); + qInfo() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); + qInfo() << "Version : " << BuildConfig.printableVersionString(); + qInfo() << "Platform : " << BuildConfig.BUILD_PLATFORM; + qInfo() << "Git commit : " << BuildConfig.GIT_COMMIT; + qInfo() << "Git refspec : " << BuildConfig.GIT_REFSPEC; + qInfo() << "Compiled for : " << BuildConfig.systemID(); + qInfo() << "Compiled by : " << BuildConfig.compilerID(); + qInfo() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT; + qInfo() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No"); if (adjustedBy.size()) { - qDebug() << "Work dir before adjustment : " << origcwdPath; - qDebug() << "Work dir after adjustment : " << QDir::currentPath(); - qDebug() << "Adjusted by : " << adjustedBy; + qInfo() << "Work dir before adjustment : " << origcwdPath; + qInfo() << "Work dir after adjustment : " << QDir::currentPath(); + qInfo() << "Adjusted by : " << adjustedBy; } else { - qDebug() << "Work dir : " << QDir::currentPath(); + qInfo() << "Work dir : " << QDir::currentPath(); } - qDebug() << "Binary path : " << binPath; - qDebug() << "Application root path : " << m_rootPath; + qInfo() << "Binary path : " << binPath; + qInfo() << "Application root path : " << m_rootPath; if (!m_instanceIdToLaunch.isEmpty()) { - qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; + qInfo() << "ID of instance to launch : " << m_instanceIdToLaunch; } if (!m_serverToJoin.isEmpty()) { - qDebug() << "Address of server to join :" << m_serverToJoin; + qInfo() << "Address of server to join :" << m_serverToJoin; } else if (!m_worldToJoin.isEmpty()) { - qDebug() << "Name of the world to join :" << m_worldToJoin; + qInfo() << "Name of the world to join :" << m_worldToJoin; } - qDebug() << "<> Paths set."; + qInfo() << "<> Paths set."; } if (m_liveCheck) { @@ -818,7 +897,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) PixmapCache::setInstance(new PixmapCache(this)); - qDebug() << "<> Settings loaded."; + qInfo() << "<> Settings loaded."; } #ifndef QT_NO_ACCESSIBILITY @@ -834,7 +913,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) QString user = settings()->get("ProxyUser").toString(); QString pass = settings()->get("ProxyPass").toString(); updateProxySettings(proxyTypeStr, addr, port, user, pass); - qDebug() << "<> Network done."; + qInfo() << "<> Network done."; } // load translations @@ -842,8 +921,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_translations.reset(new TranslationsModel("translations")); auto bcp47Name = m_settings->get("Language").toString(); m_translations->selectLanguage(bcp47Name); - qDebug() << "Your language is" << bcp47Name; - qDebug() << "<> Translations loaded."; + qInfo() << "Your language is" << bcp47Name; + qInfo() << "<> Translations loaded."; } // Instance icons @@ -854,7 +933,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_icons.reset(new IconList(instFolders, setting->get().toString())); connect(setting.get(), &Setting::SettingChanged, [this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); }); - qDebug() << "<> Instance icons initialized."; + qInfo() << "<> Instance icons initialized."; } // Themes @@ -866,25 +945,25 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // instance path: check for problems with '!' in instance path and warn the user in the log // and remember that we have to show him a dialog when the gui starts (if it does so) QString instDir = InstDirSetting->get().toString(); - qDebug() << "Instance path : " << instDir; + qInfo() << "Instance path : " << instDir; if (FS::checkProblemticPathJava(QDir(instDir))) { qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!"; } m_instances.reset(new InstanceList(m_settings, instDir, this)); connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged); - qDebug() << "Loading Instances..."; + qInfo() << "Loading Instances..."; m_instances->loadList(); - qDebug() << "<> Instances loaded."; + qInfo() << "<> Instances loaded."; } // and accounts { m_accounts.reset(new AccountList(this)); - qDebug() << "Loading accounts..."; + qInfo() << "Loading accounts..."; m_accounts->setListFilePath("accounts.json", true); m_accounts->loadList(); m_accounts->fillQueue(); - qDebug() << "<> Accounts loaded."; + qInfo() << "<> Accounts loaded."; } // init the http meta cache @@ -905,7 +984,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("java", QDir("cache/java").absolutePath()); m_metacache->Load(); - qDebug() << "<> Cache initialized."; + qInfo() << "<> Cache initialized."; } // now we have network, download translation updates diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5d757a130..f60d7960e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -99,7 +99,7 @@ set(CORE_SOURCES MTPixmapCache.h ) if (UNIX AND NOT CYGWIN AND NOT APPLE) -set(CORE_SOURCES + set(CORE_SOURCES ${CORE_SOURCES} # MangoHud @@ -589,8 +589,8 @@ set(ATLAUNCHER_SOURCES ) set(LINKEXE_SOURCES - console/WindowsConsole.cpp console/WindowsConsole.h + console/WindowsConsole.cpp filelink/FileLink.h filelink/FileLink.cpp @@ -659,6 +659,14 @@ set(PRISMUPDATER_SOURCES ) +if(WIN32) + set(PRISMUPDATER_SOURCES + console/WindowsConsole.h + console/WindowsConsole.cpp + ${PRISMUPDATER_SOURCES} + ) +endif() + ######## Logging categories ######## ecm_qt_declare_logging_category(CORE_SOURCES @@ -786,6 +794,9 @@ SET(LAUNCHER_SOURCES SysInfo.h SysInfo.cpp + # console utils + console/Console.h + # GUI - general utilities DesktopServices.h DesktopServices.cpp @@ -926,7 +937,7 @@ SET(LAUNCHER_SOURCES ui/pages/instance/McResolver.h ui/pages/instance/ServerPingTask.cpp ui/pages/instance/ServerPingTask.h - + # GUI - global settings pages ui/pages/global/AccountListPage.cpp ui/pages/global/AccountListPage.h @@ -1154,7 +1165,7 @@ SET(LAUNCHER_SOURCES ) if (NOT Apple) -set(LAUNCHER_SOURCES + set(LAUNCHER_SOURCES ${LAUNCHER_SOURCES} ui/dialogs/UpdateAvailableDialog.h @@ -1164,8 +1175,8 @@ endif() if(WIN32) set(LAUNCHER_SOURCES - console/WindowsConsole.cpp console/WindowsConsole.h + console/WindowsConsole.cpp ${LAUNCHER_SOURCES} ) endif() @@ -1324,11 +1335,11 @@ if(APPLE) set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/") if(Launcher_ENABLE_UPDATER) - file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256}) - file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle) + file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256}) + file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle) - find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle") - add_compile_definitions(SPARKLE_ENABLED) + find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle") + add_compile_definitions(SPARKLE_ENABLED) endif() target_link_libraries(Launcher_logic @@ -1338,7 +1349,7 @@ if(APPLE) "-framework ApplicationServices" ) if(Launcher_ENABLE_UPDATER) - target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK}) + target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK}) endif() endif() diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..3df44f408 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -428,7 +428,7 @@ static QMap getIdMapping(const QList& QList InstanceList::discoverInstances() { - qDebug() << "Discovering instances in" << m_instDir; + qInfo() << "Discovering instances in" << m_instDir; QList out; QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); while (iter.hasNext()) { @@ -447,7 +447,7 @@ QList InstanceList::discoverInstances() } auto id = dirInfo.fileName(); out.append(id); - qDebug() << "Found instance ID" << id; + qInfo() << "Found instance ID" << id; } #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) instanceSet = QSet(out.begin(), out.end()); @@ -468,7 +468,7 @@ InstanceList::InstListError InstanceList::loadList() if (existingIds.contains(id)) { auto instPair = existingIds[id]; existingIds.remove(id); - qDebug() << "Should keep and soft-reload" << id; + qInfo() << "Should keep and soft-reload" << id; } else { InstancePtr instPtr = loadInstance(id); if (instPtr) { diff --git a/launcher/console/Console.h b/launcher/console/Console.h new file mode 100644 index 000000000..7aaf83dcc --- /dev/null +++ b/launcher/console/Console.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#include +#endif + +namespace console { + +inline bool isConsole() +{ +#if defined Q_OS_WIN32 + DWORD procIDs[2]; + DWORD maxCount = 2; + DWORD result = GetConsoleProcessList((LPDWORD)procIDs, maxCount); + return result > 1; +#else + if (isatty(fileno(stdout))) { + return true; + } + return false; +#endif +} + +} // namespace console diff --git a/launcher/console/WindowsConsole.cpp b/launcher/console/WindowsConsole.cpp index f388bd3b1..4a0eb3d3d 100644 --- a/launcher/console/WindowsConsole.cpp +++ b/launcher/console/WindowsConsole.cpp @@ -16,14 +16,18 @@ * */ +#include "WindowsConsole.h" +#include + #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include "WindowsConsole.h" +#include + #include #include #include -#include +#include #include void RedirectHandle(DWORD handle, FILE* stream, const char* mode) @@ -128,4 +132,28 @@ bool AttachWindowsConsole() return false; } -std::error_code +std::error_code EnableAnsiSupport() +{ + // ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + // Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected + HANDLE console_handle = CreateFileW(L"CONOUT$", FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); + if (console_handle == INVALID_HANDLE_VALUE) { + return std::error_code(GetLastError(), std::system_category()); + } + + // ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode + DWORD console_mode; + if (0 == GetConsoleMode(console_handle, &console_mode)) { + return std::error_code(GetLastError(), std::system_category()); + } + + // VT processing not already enabled? + if ((console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) { + // https://docs.microsoft.com/en-us/windows/console/setconsolemode + if (0 == SetConsoleMode(console_handle, console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { + return std::error_code(GetLastError(), std::system_category()); + } + } + + return {}; +} diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index a082b4b5b..1494fa8cc 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -37,7 +37,10 @@ #include #if defined Q_OS_WIN32 -#include "WindowsConsole.h" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "console/WindowsConsole.h" #endif #include diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index f9ffeb658..365647db9 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -46,11 +46,8 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include -#include -#include #include -#include +#include "console/WindowsConsole.h" #endif #include @@ -87,112 +84,12 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt } } -#if defined Q_OS_WIN32 - -// taken from https://stackoverflow.com/a/25927081 -// getting a proper output to console with redirection support on windows is apparently hell -void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr) -{ - // Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been - // observed that the file number of our standard handle file objects can be assigned internally to a value of -2 - // when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our - // call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value - // before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to - // use the "nul" device, which will place them into a valid state, after which we can redirect them to our target - // using the "_dup2" function. - if (bindStdIn) { - FILE* dummyFile; - freopen_s(&dummyFile, "nul", "r", stdin); - } - if (bindStdOut) { - FILE* dummyFile; - freopen_s(&dummyFile, "nul", "w", stdout); - } - if (bindStdErr) { - FILE* dummyFile; - freopen_s(&dummyFile, "nul", "w", stderr); - } - - // Redirect unbuffered stdin from the current standard input handle - if (bindStdIn) { - HANDLE stdHandle = GetStdHandle(STD_INPUT_HANDLE); - if (stdHandle != INVALID_HANDLE_VALUE) { - int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT); - if (fileDescriptor != -1) { - FILE* file = _fdopen(fileDescriptor, "r"); - if (file != NULL) { - int dup2Result = _dup2(_fileno(file), _fileno(stdin)); - if (dup2Result == 0) { - setvbuf(stdin, NULL, _IONBF, 0); - } - } - } - } - } - - // Redirect unbuffered stdout to the current standard output handle - if (bindStdOut) { - HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE); - if (stdHandle != INVALID_HANDLE_VALUE) { - int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT); - if (fileDescriptor != -1) { - FILE* file = _fdopen(fileDescriptor, "w"); - if (file != NULL) { - int dup2Result = _dup2(_fileno(file), _fileno(stdout)); - if (dup2Result == 0) { - setvbuf(stdout, NULL, _IONBF, 0); - } - } - } - } - } - - // Redirect unbuffered stderr to the current standard error handle - if (bindStdErr) { - HANDLE stdHandle = GetStdHandle(STD_ERROR_HANDLE); - if (stdHandle != INVALID_HANDLE_VALUE) { - int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT); - if (fileDescriptor != -1) { - FILE* file = _fdopen(fileDescriptor, "w"); - if (file != NULL) { - int dup2Result = _dup2(_fileno(file), _fileno(stderr)); - if (dup2Result == 0) { - setvbuf(stderr, NULL, _IONBF, 0); - } - } - } - } - } - - // Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the - // standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In - // versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything - // has been read from or written to the targets or not. - if (bindStdIn) { - std::wcin.clear(); - std::cin.clear(); - } - if (bindStdOut) { - std::wcout.clear(); - std::cout.clear(); - } - if (bindStdErr) { - std::wcerr.clear(); - std::cerr.clear(); - } -} -#endif - PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, argv) { #if defined Q_OS_WIN32 // attach the parent console if stdout not already captured - auto stdout_type = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)); - if (stdout_type == FILE_TYPE_CHAR || stdout_type == FILE_TYPE_UNKNOWN) { - if (AttachConsole(ATTACH_PARENT_PROCESS)) { - BindCrtHandlesToStdHandles(true, true, true); - consoleAttached = true; - } + if (AttachWindowsConsole()) { + consoleAttached = true; } #endif setOrganizationName(BuildConfig.LAUNCHER_NAME); From 2271a05b19e259f9e1e727c09e1c68542dcc02e2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 12:37:54 +0300 Subject: [PATCH 132/181] chore: add back deprecation warnings and disable all API deprecated before 6.0 Signed-off-by: Trial97 --- CMakeLists.txt | 3 +++ launcher/StringUtils.cpp | 2 +- launcher/modplatform/legacy_ftb/PackFetchTask.cpp | 2 +- launcher/modplatform/legacy_ftb/PackInstallTask.cpp | 2 +- launcher/translations/TranslationsModel.cpp | 2 +- launcher/ui/MainWindow.cpp | 2 +- launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp | 4 ++-- launcher/ui/instanceview/InstanceView.cpp | 8 ++++---- libraries/LocalPeer/src/LocalPeer.cpp | 2 +- 9 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 321232378..22e3978d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,9 @@ else() endif() endif() +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_WARN_DEPRECATED_UP_TO=0x060200") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_UP_TO=0x060000") + # Fix aarch64 build for toml++ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index edda9f247..2ea67762e 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -53,7 +53,7 @@ static inline QChar getNextChar(const QString& s, int location) int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs) { int l1 = 0, l2 = 0; - while (l1 <= s1.count() && l2 <= s2.count()) { + while (l1 <= s1.size() && l2 <= s2.size()) { // skip spaces, tabs and 0's QChar c1 = getNextChar(s1, l1); while (c1.isSpace()) diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index a0beeddcc..aea9cefad 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -79,7 +79,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch) QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] { ModpackList packs; parseAndAddPacks(*data, PackType::Private, packs); - foreach (Modpack currentPack, packs) { + for (auto& currentPack : packs) { currentPack.packCode = packCode; emit privateFileDownloadFinished(currentPack); } diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index c8d04828c..2b9bd127a 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -160,7 +160,7 @@ void PackInstallTask::install() // we only care about the libs QJsonArray libs = doc.object().value("libraries").toArray(); - foreach (const QJsonValue& value, libs) { + for (const auto& value : libs) { QString nameValue = value.toObject().value("name").toString(); if (!nameValue.startsWith("net.minecraftforge")) { continue; diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 429ead47d..e863dfef4 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -480,7 +480,7 @@ bool TranslationsModel::selectLanguage(QString key) bool successful = false; // FIXME: this is likely never present. FIX IT. d->m_qt_translator.reset(new QTranslator()); - if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { + if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) { qCritical() << "Loading Qt Language File failed."; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 53e87b57e..2d8e4ebf5 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -724,7 +724,7 @@ void MainWindow::changeActiveAccount() QAction* sAction = (QAction*)sender(); // Profile's associated Mojang username - if (sAction->data().type() != QVariant::Type::Int) + if (sAction->data().typeId() != QMetaType::Int) return; QVariant action_data = sAction->data(); diff --git a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp index 97fe44175..e1e539050 100644 --- a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp +++ b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp @@ -76,8 +76,8 @@ void SkinOpenGLWindow::mousePressEvent(QMouseEvent* e) void SkinOpenGLWindow::mouseMoveEvent(QMouseEvent* event) { if (m_isMousePressed) { - int dx = event->x() - m_mousePosition.x(); - int dy = event->y() - m_mousePosition.y(); + int dx = event->position().x() - m_mousePosition.x(); + int dy = event->position().y() - m_mousePosition.y(); m_yaw += dx * 0.5f; m_pitch += dy * 0.5f; diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index fa1af4266..f52c994d3 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -418,7 +418,7 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent* event) QModelIndex index = indexAt(event->pos()); if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) { - QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), event->screenPos(), event->button(), + QMouseEvent me(QEvent::MouseButtonPress, event->position(), event->scenePosition(), event->globalPosition(), event->button(), event->buttons(), event->modifiers()); mousePressEvent(&me); return; @@ -598,7 +598,7 @@ void InstanceView::dragEnterEvent(QDragEnterEvent* event) if (!isDragEventAccepted(event)) { return; } - m_lastDragPosition = event->pos() + offset(); + m_lastDragPosition = event->position().toPoint() + offset(); viewport()->update(); event->accept(); } @@ -610,7 +610,7 @@ void InstanceView::dragMoveEvent(QDragMoveEvent* event) if (!isDragEventAccepted(event)) { return; } - m_lastDragPosition = event->pos() + offset(); + m_lastDragPosition = event->position().toPoint() + offset(); viewport()->update(); event->accept(); } @@ -636,7 +636,7 @@ void InstanceView::dropEvent(QDropEvent* event) if (event->source() == this) { if (event->possibleActions() & Qt::MoveAction) { - std::pair dropPos = rowDropPos(event->pos()); + std::pair dropPos = rowDropPos(event->position().toPoint()); const VisualGroup* group = dropPos.first; auto hitResult = dropPos.second; diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index c1875bf98..3761c109e 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -75,7 +75,7 @@ ApplicationId ApplicationId::fromTraditionalApp() prefix.remove(QRegularExpression("[^a-zA-Z]")); prefix.truncate(6); QByteArray idc = protoId.toUtf8(); - quint16 idNum = qChecksum(idc.constData(), idc.size()); + quint16 idNum = qChecksum(idc); auto socketName = QLatin1String("pl") + prefix + QLatin1Char('-') + QString::number(idNum, 16).left(12); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { From cddf00c61bae89885ac802f048fdcf36aa77baf5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 14:10:00 +0300 Subject: [PATCH 133/181] fix: beginResetModel called before endResetModel Signed-off-by: Trial97 --- launcher/VersionProxyModel.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 3d9d95eb6..165dd4cb7 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -295,13 +295,11 @@ void VersionProxyModel::sourceDataChanged(const QModelIndex& source_top_left, co void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) { auto replacing = dynamic_cast(replacingRaw); - beginResetModel(); m_columns.clear(); if (!replacing) { roles.clear(); filterModel->setSourceModel(replacing); - endResetModel(); return; } @@ -343,8 +341,6 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) hasLatest = true; } filterModel->setSourceModel(replacing); - - endResetModel(); } QModelIndex VersionProxyModel::getRecommended() const From cb591ba52ece20b79db70ee7e6d5919aab5c9a41 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 31 Dec 2024 17:25:44 +0200 Subject: [PATCH 134/181] chore:add qr code support for login Signed-off-by: Trial97 --- .gitmodules | 3 ++ CMakeLists.txt | 1 + COPYING.md | 9 ++++++ flake.lock | 19 ++++++++++- flake.nix | 7 ++++ launcher/CMakeLists.txt | 1 + launcher/resources/documents/documents.qrc | 1 - launcher/resources/documents/login-qr.svg | 8 ----- launcher/ui/dialogs/MSALoginDialog.cpp | 26 +++++++++------ libraries/README.md | 8 +++++ libraries/qt-qrcodegenerator/CMakeLists.txt | 32 +++++++++++++++++++ .../qt-qrcodegenerator/QR-Code-generator | 1 + libraries/qt-qrcodegenerator/qr.cpp | 29 +++++++++++++++++ libraries/qt-qrcodegenerator/qr.h | 8 +++++ nix/unwrapped.nix | 6 ++-- 15 files changed, 138 insertions(+), 21 deletions(-) delete mode 100644 launcher/resources/documents/login-qr.svg create mode 100644 libraries/qt-qrcodegenerator/CMakeLists.txt create mode 160000 libraries/qt-qrcodegenerator/QR-Code-generator create mode 100644 libraries/qt-qrcodegenerator/qr.cpp create mode 100644 libraries/qt-qrcodegenerator/qr.h diff --git a/.gitmodules b/.gitmodules index 7ad40becb..0c56d8768 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "flatpak/shared-modules"] path = flatpak/shared-modules url = https://github.com/flathub/shared-modules.git +[submodule "libraries/qt-qrcodegenerator/QR-Code-generator"] + path = libraries/qt-qrcodegenerator/QR-Code-generator + url = https://github.com/nayuki/QR-Code-generator diff --git a/CMakeLists.txt b/CMakeLists.txt index 10153c3ec..91e10daf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -503,6 +503,7 @@ add_subdirectory(libraries/libnbtplusplus) add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker +add_subdirectory(libraries/qt-qrcodegenerator) # qr code generator if(FORCE_BUNDLED_ZLIB) message(STATUS "Using bundled zlib") diff --git a/COPYING.md b/COPYING.md index f1f0b3a70..f742c0132 100644 --- a/COPYING.md +++ b/COPYING.md @@ -403,3 +403,12 @@ You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . + +## qt-qrcodegenerator (`libraries/qt-qrcodegenerator`) + + Copyright © 2024 Project Nayuki. (MIT License) + https://www.nayuki.io/page/qr-code-generator-library + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + - The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. diff --git a/flake.lock b/flake.lock index 2d79b8335..070d069e5 100644 --- a/flake.lock +++ b/flake.lock @@ -32,10 +32,27 @@ "type": "github" } }, + "qt-qrcodegenerator": { + "flake": false, + "locked": { + "lastModified": 1731907326, + "narHash": "sha256-5+iYwsbX8wjKZPCy7ENj5HCYgOqzeSNLs/YrX2Vc7CQ=", + "owner": "nayuki", + "repo": "QR-Code-generator", + "rev": "f40366c40d8d1956081f7ec643d240c02a81df52", + "type": "github" + }, + "original": { + "owner": "nayuki", + "repo": "QR-Code-generator", + "type": "github" + } + }, "root": { "inputs": { "libnbtplusplus": "libnbtplusplus", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "qt-qrcodegenerator": "qt-qrcodegenerator" } } }, diff --git a/flake.nix b/flake.nix index fd3003bc4..2c7c69b3d 100644 --- a/flake.nix +++ b/flake.nix @@ -15,6 +15,11 @@ url = "github:PrismLauncher/libnbtplusplus"; flake = false; }; + + qt-qrcodegenerator = { + url = "github:nayuki/QR-Code-generator"; + flake = false; + }; }; outputs = @@ -22,6 +27,7 @@ self, nixpkgs, libnbtplusplus, + qt-qrcodegenerator, }: let @@ -167,6 +173,7 @@ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { inherit libnbtplusplus + qt-qrcodegenerator self ; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 30d657f9e..e10921d3e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1289,6 +1289,7 @@ target_link_libraries(Launcher_logic qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets + qrcode ) if (UNIX AND NOT CYGWIN AND NOT APPLE) diff --git a/launcher/resources/documents/documents.qrc b/launcher/resources/documents/documents.qrc index 489d1d5a2..007efcde3 100644 --- a/launcher/resources/documents/documents.qrc +++ b/launcher/resources/documents/documents.qrc @@ -2,7 +2,6 @@ ../../../COPYING.md - login-qr.svg diff --git a/launcher/resources/documents/login-qr.svg b/launcher/resources/documents/login-qr.svg deleted file mode 100644 index 1b88e3c83..000000000 --- a/launcher/resources/documents/login-qr.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 40d1eff1e..83f46294d 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -36,6 +36,7 @@ #include "MSALoginDialog.h" #include "Application.h" +#include "qr.h" #include "ui_MSALoginDialog.h" #include "DesktopServices.h" @@ -60,7 +61,6 @@ MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MS ui->code->setFont(font); connect(ui->copyCode, &QPushButton::clicked, this, [this] { QApplication::clipboard()->setText(ui->code->text()); }); - ui->qr->setPixmap(QIcon((":/documents/login-qr.svg")).pixmap(QSize(150, 150))); connect(ui->loginButton, &QPushButton::clicked, this, [this] { if (m_url.isValid()) { if (!DesktopServices::openUrl(m_url)) { @@ -139,19 +139,27 @@ void MSALoginDialog::authorizeWithBrowser(const QUrl& url) m_url = url; } -void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn) +void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, [[maybe_unused]] int expiresIn) { ui->stackedWidget->setCurrentIndex(1); const auto linkString = QString("%2").arg(url, url); - ui->code->setText(code); - auto isDefaultUrl = url == "https://www.microsoft.com/link"; - ui->qr->setVisible(isDefaultUrl); - if (isDefaultUrl) { - ui->qrMessage->setText(tr("Open %1 or scan the QR and enter the above code.").arg(linkString)); - } else { - ui->qrMessage->setText(tr("Open %1 and enter the above code.").arg(linkString)); + if (url == "https://www.microsoft.com/link" && !code.isEmpty()) { + url += QString("?otc=%1").arg(code); } + ui->code->setText(code); + + auto size = QSize(150, 150); + QPixmap pixmap(size); + pixmap.fill(Qt::white); + + QPainter painter(&pixmap); + paintQR(painter, size, url, Qt::black); + + // Set the generated pixmap to the label + ui->qr->setPixmap(pixmap); + + ui->qrMessage->setText(tr("Open %1 or scan the QR and enter the above code if needed.").arg(linkString)); } void MSALoginDialog::onDeviceFlowStatus(QString status) diff --git a/libraries/README.md b/libraries/README.md index 3c5f5a4ab..5f7b685e5 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -99,6 +99,14 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith Public domain (the author disclaimed the copyright). +## qt-qrcodegenerator + +A simple library for generating QR codes + +See [github repo](https://github.com/nayuki/QR-Code-generator). + +MIT + ## quazip A zip manipulation library. diff --git a/libraries/qt-qrcodegenerator/CMakeLists.txt b/libraries/qt-qrcodegenerator/CMakeLists.txt new file mode 100644 index 000000000..e18da0e71 --- /dev/null +++ b/libraries/qt-qrcodegenerator/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.6) + +project(qrcode) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_CXX_STANDARD_REQUIRED true) +set(CMAKE_C_STANDARD_REQUIRED true) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_STANDARD 11) + + +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core Gui REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Gui Core5Compat REQUIRED) + list(APPEND systeminfo_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) +endif() + +add_library(qrcode STATIC qr.h qr.cpp QR-Code-generator/cpp/qrcodegen.cpp QR-Code-generator/cpp/qrcodegen.hpp ) + +target_link_libraries(qrcode Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui ${systeminfo_LIBS}) + + +# needed for statically linked qrcode in shared libs on x86_64 +set_target_properties(qrcode + PROPERTIES POSITION_INDEPENDENT_CODE TRUE +) + +target_include_directories(qrcode PUBLIC ./ PRIVATE QR-Code-generator/cpp/) + diff --git a/libraries/qt-qrcodegenerator/QR-Code-generator b/libraries/qt-qrcodegenerator/QR-Code-generator new file mode 160000 index 000000000..f40366c40 --- /dev/null +++ b/libraries/qt-qrcodegenerator/QR-Code-generator @@ -0,0 +1 @@ +Subproject commit f40366c40d8d1956081f7ec643d240c02a81df52 diff --git a/libraries/qt-qrcodegenerator/qr.cpp b/libraries/qt-qrcodegenerator/qr.cpp new file mode 100644 index 000000000..69bfb6da5 --- /dev/null +++ b/libraries/qt-qrcodegenerator/qr.cpp @@ -0,0 +1,29 @@ + +#include "qr.h" +#include "qrcodegen.hpp" + +void paintQR(QPainter& painter, const QSize sz, const QString& data, QColor fg) +{ + // NOTE: At this point you will use the API to get the encoding and format you want, instead of my hardcoded stuff: + qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.toUtf8().constData(), qrcodegen::QrCode::Ecc::LOW); + const int s = qr.getSize() > 0 ? qr.getSize() : 1; + const double w = sz.width(); + const double h = sz.height(); + const double aspect = w / h; + const double size = ((aspect > 1.0) ? h : w); + const double scale = size / (s + 2); + // NOTE: For performance reasons my implementation only draws the foreground parts in supplied color. + // It expects background to be prepared already (in white or whatever is preferred). + painter.setPen(Qt::NoPen); + painter.setBrush(fg); + for (int y = 0; y < s; y++) { + for (int x = 0; x < s; x++) { + const int color = qr.getModule(x, y); // 0 for white, 1 for black + if (0 != color) { + const double rx1 = (x + 1) * scale, ry1 = (y + 1) * scale; + QRectF r(rx1, ry1, scale, scale); + painter.drawRects(&r, 1); + } + } + } +} \ No newline at end of file diff --git a/libraries/qt-qrcodegenerator/qr.h b/libraries/qt-qrcodegenerator/qr.h new file mode 100644 index 000000000..290d49001 --- /dev/null +++ b/libraries/qt-qrcodegenerator/qr.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +// https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c +void paintQR(QPainter& painter, const QSize sz, const QString& data, QColor fg); diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 93cda8e1a..060518242 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -9,16 +9,15 @@ jdk17, kdePackages, libnbtplusplus, + qt-qrcodegenerator, ninja, self, stripJavaArchivesHook, tomlplusplus, zlib, - msaClientID ? null, gamemodeSupport ? stdenv.hostPlatform.isLinux, }: - assert lib.assertMsg ( gamemodeSupport -> stdenv.hostPlatform.isLinux ) "gamemodeSupport is only available on Linux."; @@ -64,6 +63,9 @@ stdenv.mkDerivation { postUnpack = '' rm -rf source/libraries/libnbtplusplus ln -s ${libnbtplusplus} source/libraries/libnbtplusplus + + rm -rf source/libraries/qt-qrcodegenerator/QR-Code-generator + ln -s ${qt-qrcodegenerator} source/libraries/qt-qrcodegenerator/QR-Code-generator ''; nativeBuildInputs = [ From 5af06dec8522ea12719600deaa014137509ee8e5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Apr 2025 22:49:29 +0300 Subject: [PATCH 135/181] chore: add getOrRegisterSetting function Signed-off-by: Trial97 --- launcher/minecraft/mod/ResourceFolderModel.cpp | 6 ++---- launcher/settings/SettingsObject.cpp | 5 +++++ launcher/settings/SettingsObject.h | 10 ++++++++++ launcher/ui/MainWindow.cpp | 5 +---- launcher/ui/pages/instance/ExternalResourcesPage.cpp | 5 +---- launcher/ui/pages/instance/ScreenshotsPage.cpp | 5 +---- launcher/ui/pages/instance/ServersPage.cpp | 5 +---- launcher/ui/pages/instance/VersionPage.cpp | 5 +---- launcher/ui/pages/instance/WorldListPage.cpp | 5 +---- 9 files changed, 23 insertions(+), 28 deletions(-) diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index d4900616b..5f4748b92 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -593,8 +593,7 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column) void ResourceFolderModel::saveColumns(QTreeView* tree) { auto const setting_name = QString("UI/%1_Page/Columns").arg(id()); - auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name) - : m_instance->settings()->registerSetting(setting_name); + auto setting = m_instance->settings()->getOrRegisterSetting(setting_name); setting->set(tree->header()->saveState()); } @@ -606,8 +605,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) } auto const setting_name = QString("UI/%1_Page/Columns").arg(id()); - auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name) - : m_instance->settings()->registerSetting(setting_name); + auto setting = m_instance->settings()->getOrRegisterSetting(setting_name); tree->header()->restoreState(setting->get().toByteArray()); } diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp index 1e5dce251..0df22b42d 100644 --- a/launcher/settings/SettingsObject.cpp +++ b/launcher/settings/SettingsObject.cpp @@ -124,3 +124,8 @@ void SettingsObject::connectSignals(const Setting& setting) connect(&setting, &Setting::settingReset, this, &SettingsObject::resetSetting); connect(&setting, SIGNAL(settingReset(Setting)), this, SIGNAL(settingReset(const Setting&))); } + +std::shared_ptr SettingsObject::getOrRegisterSetting(const QString& id, QVariant defVal) +{ + return contains(id) ? getSetting(id) : registerSetting(id, defVal); +} diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h index f133f2f7f..bd3f71b36 100644 --- a/launcher/settings/SettingsObject.h +++ b/launcher/settings/SettingsObject.h @@ -103,6 +103,16 @@ class SettingsObject : public QObject { */ std::shared_ptr getSetting(const QString& id) const; + /*! + * \brief Gets the setting with the given ID. + * \brief if is not registered yet it does that + * \param id The ID of the setting to get. + * \return A pointer to the setting with the given ID. + * Returns null if there is no setting with the given ID. + * \sa operator []() + */ + std::shared_ptr getOrRegisterSetting(const QString& id, QVariant defVal = QVariant()); + /*! * \brief Gets the value of the setting with the given ID. * \param id The ID of the setting to get. diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ddf726373..0111d1253 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -176,10 +176,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // restore the instance toolbar settings auto const setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); - if (!APPLICATION->settings()->contains(setting_name)) - instanceToolbarSetting = APPLICATION->settings()->registerSetting(setting_name); - else - instanceToolbarSetting = APPLICATION->settings()->getSetting(setting_name); + instanceToolbarSetting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->instanceToolBar->setVisibilityState(instanceToolbarSetting->get().toByteArray()); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 50217f982..be65e6948 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -146,10 +146,7 @@ void ExternalResourcesPage::openedImpl() m_model->startWatching(); auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->actionsToolbar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index b619a07b8..fa568c794 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -559,10 +559,7 @@ void ScreenshotsPage::openedImpl() } auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 136fb47c7..f8cd8304f 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -711,10 +711,7 @@ void ServersPage::openedImpl() m_model->observe(); auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 975c44de2..a1eeb3d25 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -124,10 +124,7 @@ void VersionPage::retranslate() void VersionPage::openedImpl() { auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index dd7486a6c..9e1a0fb55 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -119,10 +119,7 @@ void WorldListPage::openedImpl() } auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } From 0948d3598b9dff7b7ad0979f42f65c3302d037ad Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 16:39:36 +0300 Subject: [PATCH 136/181] chore: improve log display Signed-off-by: Trial97 --- launcher/GZip.cpp | 64 +++++++++++++++++ launcher/GZip.h | 20 ++++++ launcher/launch/LogModel.cpp | 5 ++ launcher/launch/LogModel.h | 1 + launcher/ui/pages/instance/OtherLogsPage.cpp | 74 ++++++++++++++------ launcher/ui/widgets/LogView.cpp | 5 +- 6 files changed, 145 insertions(+), 24 deletions(-) diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index 1c2539e08..eaf1c9035 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -36,6 +36,8 @@ #include "GZip.h" #include #include +#include +#include bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes) { @@ -136,3 +138,65 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes) } return true; } + +GZipStream::GZipStream(const QString& filePath) : GZipStream(new QFile(filePath)) {} + +GZipStream::GZipStream(QFile* file) : m_file(file) {} + +bool GZipStream::initStream() +{ + memset(&m_strm, 0, sizeof(m_strm)); + return (inflateInit2(&m_strm, 16 + MAX_WBITS) == Z_OK); +} + +bool GZipStream::unzipBlockByBlock(QByteArray& uncompressedBytes) +{ + uncompressedBytes.clear(); + if (!m_file->isOpen()) { + if (!m_file->open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open file:" << (m_file->fileName()); + return false; + } + } + + if (!m_strm.state && !initStream()) { + return false; + } + + QByteArray compressedBlock; + unsigned int blockSize = 4096; + + compressedBlock = m_file->read(blockSize); + if (compressedBlock.isEmpty()) { + return true; // End of file reached + } + + bool done = processBlock(compressedBlock, uncompressedBytes); + if (inflateEnd(&m_strm) != Z_OK || !done) { + return false; + } + return done; +} + +bool GZipStream::processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes) +{ + m_strm.next_in = (Bytef*)compressedBlock.data(); + m_strm.avail_in = compressedBlock.size(); + + unsigned int uncompLength = uncompressedBytes.size(); + if (m_strm.total_out >= uncompLength) { + uncompressedBytes.resize(uncompLength * 2); + uncompLength *= 2; + } + + m_strm.next_out = reinterpret_cast(uncompressedBytes.data() + m_strm.total_out); + m_strm.avail_out = uncompLength - m_strm.total_out; + + int err = inflate(&m_strm, Z_NO_FLUSH); + if (err != Z_OK && err != Z_STREAM_END) { + qWarning() << "Decompression failed with error code" << err; + return false; + } + + return true; +} diff --git a/launcher/GZip.h b/launcher/GZip.h index 0bdb70407..dd4162839 100644 --- a/launcher/GZip.h +++ b/launcher/GZip.h @@ -1,8 +1,28 @@ #pragma once +#include #include +#include class GZip { public: static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes); static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes); }; + +class GZipStream { + public: + explicit GZipStream(const QString& filePath); + explicit GZipStream(QFile* file); + + // Decompress the next block and return the decompressed data + bool unzipBlockByBlock(QByteArray& uncompressedBytes); + + private: + bool initStream(); + + bool processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes); + + private: + QFile* m_file; + z_stream m_strm; +}; diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index dd32d46a2..45aac6099 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -161,3 +161,8 @@ bool LogModel::colorLines() const { return m_colorLines; } + +bool LogModel::isOverFlow() +{ + return m_numLines >= m_maxLines && m_stopOnOverflow; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 6c2a8cff3..ba7b14487 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -24,6 +24,7 @@ class LogModel : public QAbstractListModel { void setMaxLines(int maxLines); void setStopOnOverflow(bool stop); void setOverflowMessage(const QString& overflowMessage); + bool isOverFlow(); void setLineWrap(bool state); bool wrapLines() const; diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 0d94843c0..b7d10a41a 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -151,6 +151,48 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) } } +class ReadLineAbstract { + public: + ReadLineAbstract(QFile* file) : m_file(file) + { + if (file->fileName().endsWith(".gz")) + m_gz = new GZipStream(file); + } + ~ReadLineAbstract() { delete m_gz; } + + QString readLine() + { + if (!m_gz) + return QString::fromUtf8(m_file->readLine()); + QString line; + for (;;) { + if (!m_decodedData.isEmpty()) { + int newlineIndex = m_decodedData.indexOf('\n'); + if (newlineIndex != -1) { + line += QString::fromUtf8(m_decodedData).left(newlineIndex); + m_decodedData.remove(0, newlineIndex + 1); + return line; + } + + line += QString::fromUtf8(m_decodedData); + m_decodedData.clear(); + } + + if (!m_gz->unzipBlockByBlock(m_decodedData)) { // If error occurs during unzipping + m_decodedData.clear(); + return QObject::tr("The content of the file(%1) could not be decoded.").arg(m_file->fileName()); + } + } + } + + bool done() { return m_gz ? m_decodedData.isEmpty() : m_file->atEnd(); } + + private: + QFile* m_file; + GZipStream* m_gz = nullptr; + QByteArray m_decodedData; +}; + void OtherLogsPage::on_btnReload_clicked() { if (m_currentFile.isEmpty()) { @@ -178,35 +220,17 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - QString content; - if (file.fileName().endsWith(".gz")) { - QByteArray temp; - if (!GZip::unzip(file.readAll(), temp)) { - setPlainText(tr("The file (%1) is not readable.").arg(file.fileName())); - return; - } - content = QString::fromUtf8(temp); - } else { - content = QString::fromUtf8(file.readAll()); - } - if (content.size() >= 50000000ll) { - showTooBig(); - return; - } - // If the file is not too big for display, but too slow for syntax highlighting, just show content as plain text - if (content.size() >= 10000000ll || content.isEmpty()) { - setPlainText(content); - return; - } + ReadLineAbstract stream(&file); // Try to determine a level for each line - if (content.back() == '\n') - content = content.remove(content.size() - 1, 1); ui->text->clear(); ui->text->setModel(nullptr); m_model->clear(); - for (auto& line : content.split('\n')) { + auto line = stream.readLine(); + while (!stream.done()) { // just read until the model is full or the file ended + if (line.back() == '\n') + line = line.remove(line.size() - 1, 1); MessageLevel::Enum level = MessageLevel::Unknown; // if the launcher part set a log level, use it @@ -221,6 +245,10 @@ void OtherLogsPage::on_btnReload_clicked() } m_model->append(level, line); + if (m_model->isOverFlow()) + break; + + line = stream.readLine(); } ui->text->setModel(m_proxy); ui->text->scrollToBottom(); diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 181893af4..df25a2434 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -42,6 +42,7 @@ LogView::LogView(QWidget* parent) : QPlainTextEdit(parent) { setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); m_defaultFormat = new QTextCharFormat(currentCharFormat()); + setUndoRedoEnabled(false); } LogView::~LogView() @@ -129,6 +130,8 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) QTextDocument document; QTextCursor cursor(&document); + cursor.movePosition(QTextCursor::End); + cursor.beginEditBlock(); for (int i = first; i <= last; i++) { auto idx = m_model->index(i, 0, parent); auto text = m_model->data(idx, Qt::DisplayRole).toString(); @@ -145,10 +148,10 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) if (bg.isValid() && m_colorLines) { format.setBackground(bg.value()); } - cursor.movePosition(QTextCursor::End); cursor.insertText(text, format); cursor.insertBlock(); } + cursor.endEditBlock(); QTextDocumentFragment fragment(&document); QTextCursor workCursor = textCursor(); From 166b2cb286e1af10ca38df6e7b398216fc39ad6a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 12:26:47 +0100 Subject: [PATCH 137/181] Tweak log formatting Signed-off-by: TheKodeToad --- launcher/Application.cpp | 56 ++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 70ffce12f..018309e63 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -125,9 +125,9 @@ #include #include -#include #include #include +#include #include "SysInfo.h" #ifdef Q_OS_LINUX @@ -176,23 +176,20 @@ static bool isANSIColorConsole; static QString defaultLogFormat = QStringLiteral( "%{time process}" " " - "%{if-debug}D%{endif}" - "%{if-info}I%{endif}" - "%{if-warning}W%{endif}" - "%{if-critical}C%{endif}" - "%{if-fatal}F%{endif}" + "%{if-debug}Debug:%{endif}" + "%{if-info}Info:%{endif}" + "%{if-warning}Warning:%{endif}" + "%{if-critical}Critical:%{endif}" + "%{if-fatal}Fatal:%{endif}" " " - "|" + "%{if-category}[%{category}] %{endif}" + "%{message}" " " - "%{function}:%{line}" - " " - "|" - " " - "%{if-category}[%{category}]: %{endif}" - "%{message}"); + "(%{function}:%{line})"); #define ansi_reset "\x1b[0m" #define ansi_bold "\x1b[1m" +#define ansi_reset_bold "\x1b[22m" #define ansi_faint "\x1b[2m" #define ansi_italic "\x1b[3m" #define ansi_red_fg "\x1b[31m" @@ -200,30 +197,26 @@ static QString defaultLogFormat = QStringLiteral( #define ansi_yellow_fg "\x1b[33m" #define ansi_blue_fg "\x1b[34m" #define ansi_purple_fg "\x1b[35m" +#define ansi_inverse "\x1b[7m" +// clang-format off static QString ansiLogFormat = QStringLiteral( - "%{time process}" + ansi_faint "%{time process}" ansi_reset " " - "%{if-debug}" ansi_bold ansi_blue_fg "D" ansi_reset - "%{endif}" - "%{if-info}" ansi_bold ansi_green_fg "I" ansi_reset - "%{endif}" - "%{if-warning}" ansi_bold ansi_yellow_fg "W" ansi_reset - "%{endif}" - "%{if-critical}" ansi_bold ansi_red_fg "C" ansi_reset - "%{endif}" - "%{if-fatal}" ansi_bold ansi_red_fg "F" ansi_reset - "%{endif}" + "%{if-debug}" ansi_bold ansi_green_fg "D:" ansi_reset "%{endif}" + "%{if-info}" ansi_bold ansi_blue_fg "I:" ansi_reset "%{endif}" + "%{if-warning}" ansi_bold ansi_yellow_fg "W:" ansi_reset_bold "%{endif}" + "%{if-critical}" ansi_bold ansi_red_fg "C:" ansi_reset_bold "%{endif}" + "%{if-fatal}" ansi_bold ansi_inverse ansi_red_fg "F:" ansi_reset_bold "%{endif}" " " - "|" - " " ansi_faint ansi_italic "%{function}:%{line}" ansi_reset + "%{if-category}" ansi_bold "[%{category}]" ansi_reset_bold " %{endif}" + "%{message}" " " - "|" - " " - "%{if-category}[" ansi_bold ansi_purple_fg "%{category}" ansi_reset - "]: %{endif}" - "%{message}"); + ansi_reset ansi_faint "(%{function}:%{line})" ansi_reset +); +// clang-format on +#undef ansi_inverse #undef ansi_purple_fg #undef ansi_blue_fg #undef ansi_yellow_fg @@ -232,6 +225,7 @@ static QString ansiLogFormat = QStringLiteral( #undef ansi_italic #undef ansi_faint #undef ansi_bold +#undef ansi_reset_bold #undef ansi_reset namespace { From f3073406908cd318751fd393c78b265c8b3a8085 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 15:56:20 +0100 Subject: [PATCH 138/181] Fix formatting Signed-off-by: TheKodeToad --- launcher/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 018309e63..cfe028279 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -125,9 +125,9 @@ #include #include +#include #include #include -#include #include "SysInfo.h" #ifdef Q_OS_LINUX From 0f847d66820c73701e336c59f7740d90e8b4cae8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 19:40:19 +0100 Subject: [PATCH 139/181] Fix mistakes Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 7b02e93ba..6f367a1bd 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1019,7 +1019,7 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * static const QRegularExpression JAVA_EXCEPTION( - R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\\d_$]*\.)+[a-zA-Z_$][a-zA-Z\\d_$]*)"); + R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); if (line.contains(JAVA_EXCEPTION)) return MessageLevel::Error; From d5db974008f6991c1d7aaec4fb47cfddbdebf04f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 19:52:30 +0100 Subject: [PATCH 140/181] Shallow search and lazy loading for Other Logs page Signed-off-by: TheKodeToad --- launcher/BaseInstance.h | 7 +- launcher/InstancePageProvider.h | 5 +- launcher/NullInstance.h | 4 +- launcher/minecraft/MinecraftInstance.cpp | 14 +--- launcher/minecraft/MinecraftInstance.h | 4 +- launcher/ui/pages/instance/OtherLogsPage.cpp | 85 ++++++++++++++------ launcher/ui/pages/instance/OtherLogsPage.h | 13 ++- 7 files changed, 78 insertions(+), 54 deletions(-) diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 2a2b4dc4a..f93321922 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -198,15 +198,10 @@ class BaseInstance : public QObject, public std::enable_shared_from_thisgameRoot(), "screenshots"))); values.append(new InstanceSettingsPage(onesix)); - auto logMatcher = inst->getLogFileMatcher(); - if (logMatcher) { - values.append(new OtherLogsPage(inst, logMatcher)); - } + values.append(new OtherLogsPage(inst)); return values; } diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 3d01c9d33..93bab6c8b 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -35,6 +35,7 @@ */ #pragma once +#include #include "BaseInstance.h" #include "launch/LaunchTask.h" @@ -57,8 +58,7 @@ class NullInstance : public BaseInstance { QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); } QMap getVariables() override { return QMap(); } - IPathMatcher::Ptr getLogFileMatcher() override { return nullptr; } - QString getLogFileRoot() override { return instanceRoot(); } + QStringList getLogFileSearchPaths() override { return {}; } QString typeName() const override { return "Null"; } bool canExport() const override { return false; } bool canEdit() const override { return false; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 6f367a1bd..121b8035c 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1055,19 +1055,9 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev return level; } -IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() +QStringList MinecraftInstance::getLogFileSearchPaths() { - auto combined = std::make_shared(); - combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); - combined->add(std::make_shared("crash-.*\\.txt")); - combined->add(std::make_shared("IDMap dump.*\\.txt$")); - combined->add(std::make_shared("ModLoader\\.txt(\\..*)?$")); - return combined; -} - -QString MinecraftInstance::getLogFileRoot() -{ - return gameRoot(); + return { FS::PathCombine(gameRoot(), "logs"), FS::PathCombine(gameRoot(), "crash-reports"), gameRoot() }; } QString MinecraftInstance::getStatusbarDescription() diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 5d9bb45ef..677ea5b84 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -142,9 +142,7 @@ class MinecraftInstance : public BaseInstance { /// guess log level from a line of minecraft log MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level) override; - IPathMatcher::Ptr getLogFileMatcher() override; - - QString getLogFileRoot() override; + QStringList getLogFileSearchPaths() override; QString getStatusbarDescription() override; diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index b7d10a41a..671e0ab7f 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -43,16 +43,18 @@ #include #include +#include +#include +#include +#include #include -#include "RecursiveFileSystemWatcher.h" -OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent) +OtherLogsPage::OtherLogsPage(InstancePtr instance, QWidget* parent) : QWidget(parent) , ui(new Ui::OtherLogsPage) , m_instance(instance) - , m_path(instance->getLogFileRoot()) - , m_fileFilter(fileFilter) - , m_watcher(new RecursiveFileSystemWatcher(this)) + , m_basePath(instance->gameRoot()) + , m_logSearchPaths(instance->getLogFileSearchPaths()) , m_model(new LogModel(this)) { ui->setupUi(this); @@ -78,11 +80,7 @@ OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, m_model->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(m_model->getMaxLines())); m_proxy->setSourceModel(m_model.get()); - m_watcher->setMatcher(fileFilter); - m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); - - connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox); - populateSelectLogBox(); + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &OtherLogsPage::populateSelectLogBox); auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated); @@ -108,22 +106,39 @@ void OtherLogsPage::retranslate() void OtherLogsPage::openedImpl() { - m_watcher->enable(); + const QStringList failedPaths = m_watcher.addPaths(m_logSearchPaths); + + for (const QString& path : m_logSearchPaths) { + if (failedPaths.contains(path)) + qDebug() << "Failed to start watching" << path; + else + qDebug() << "Started watching" << path; + } + + populateSelectLogBox(); } + void OtherLogsPage::closedImpl() { - m_watcher->disable(); + const QStringList failedPaths = m_watcher.removePaths(m_logSearchPaths); + + for (const QString& path : m_logSearchPaths) { + if (failedPaths.contains(path)) + qDebug() << "Failed to stop watching" << path; + else + qDebug() << "Stopped watching" << path; + } } void OtherLogsPage::populateSelectLogBox() { + QString prevCurrentFile = m_currentFile; + ui->selectLogBox->clear(); - ui->selectLogBox->addItems(m_watcher->files()); - if (m_currentFile.isEmpty()) { - setControlsEnabled(false); - ui->selectLogBox->setCurrentIndex(-1); - } else { - const int index = ui->selectLogBox->findText(m_currentFile); + ui->selectLogBox->addItems(getPaths()); + + if (!prevCurrentFile.isEmpty()) { + const int index = ui->selectLogBox->findText(prevCurrentFile); if (index != -1) { ui->selectLogBox->setCurrentIndex(index); setControlsEnabled(true); @@ -140,7 +155,7 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) file = ui->selectLogBox->itemText(index); } - if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_path, file))) { + if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_basePath, file))) { m_currentFile = QString(); ui->text->clear(); setControlsEnabled(false); @@ -199,7 +214,7 @@ void OtherLogsPage::on_btnReload_clicked() setControlsEnabled(false); return; } - QFile file(FS::PathCombine(m_path, m_currentFile)); + QFile file(FS::PathCombine(m_basePath, m_currentFile)); if (!file.open(QFile::ReadOnly)) { setControlsEnabled(false); ui->btnReload->setEnabled(true); // allow reload @@ -284,7 +299,7 @@ void OtherLogsPage::on_btnDelete_clicked() QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { return; } - QFile file(FS::PathCombine(m_path, m_currentFile)); + QFile file(FS::PathCombine(m_basePath, m_currentFile)); if (FS::trash(file.fileName())) { return; @@ -297,7 +312,7 @@ void OtherLogsPage::on_btnDelete_clicked() void OtherLogsPage::on_btnClean_clicked() { - auto toDelete = m_watcher->files(); + auto toDelete = getPaths(); if (toDelete.isEmpty()) { return; } @@ -320,7 +335,9 @@ void OtherLogsPage::on_btnClean_clicked() } QStringList failed; for (auto item : toDelete) { - QFile file(FS::PathCombine(m_path, item)); + QString absolutePath = FS::PathCombine(m_basePath, item); + QFile file(absolutePath); + qDebug() << "Deleting log" << absolutePath; if (FS::trash(file.fileName())) { continue; } @@ -374,6 +391,28 @@ void OtherLogsPage::setControlsEnabled(const bool enabled) ui->btnClean->setEnabled(enabled); } +QStringList OtherLogsPage::getPaths() +{ + QDir baseDir(m_basePath); + + QStringList result; + + for (QString searchPath : m_logSearchPaths) { + QDirIterator iterator(searchPath, QDir::Files | QDir::Readable); + + while (iterator.hasNext()) { + const QString name = iterator.next(); + + if (!name.endsWith(".log") && !name.endsWith(".log.gz")) + continue; + + result.append(baseDir.relativeFilePath(name)); + } + } + + return result; +} + void OtherLogsPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index 9394ab9b8..fedb2506c 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -39,6 +39,8 @@ #include #include +#include +#include #include "LogPage.h" #include "ui/pages/BasePage.h" @@ -52,7 +54,7 @@ class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); + explicit OtherLogsPage(InstancePtr instance, QWidget* parent = 0); ~OtherLogsPage(); QString id() const override { return "logs"; } @@ -85,13 +87,16 @@ class OtherLogsPage : public QWidget, public BasePage { private: void setControlsEnabled(bool enabled); + QStringList getPaths(); + private: Ui::OtherLogsPage* ui; InstancePtr m_instance; - QString m_path; + /** Path to display log paths relative to. */ + QString m_basePath; + QStringList m_logSearchPaths; QString m_currentFile; - IPathMatcher::Ptr m_fileFilter; - RecursiveFileSystemWatcher* m_watcher; + QFileSystemWatcher m_watcher; LogFormatProxyModel* m_proxy; shared_qobject_ptr m_model; From 01efd5b5d8b7f37b2e227cc8c448a78cccd84a24 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 21:24:25 +0100 Subject: [PATCH 141/181] Fix double load (again lol) Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 671e0ab7f..ec597497d 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -132,20 +132,25 @@ void OtherLogsPage::closedImpl() void OtherLogsPage::populateSelectLogBox() { - QString prevCurrentFile = m_currentFile; + const QString prevCurrentFile = m_currentFile; + ui->selectLogBox->blockSignals(true); ui->selectLogBox->clear(); ui->selectLogBox->addItems(getPaths()); + ui->selectLogBox->blockSignals(false); if (!prevCurrentFile.isEmpty()) { const int index = ui->selectLogBox->findText(prevCurrentFile); if (index != -1) { ui->selectLogBox->setCurrentIndex(index); setControlsEnabled(true); + return; } else { setControlsEnabled(false); } } + + on_selectLogBox_currentIndexChanged(ui->selectLogBox->currentIndex()); } void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) From 7f2f62afa876d5fe40bdf5a4d08bf52a7d56624c Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Fri, 18 Apr 2025 16:50:25 -0400 Subject: [PATCH 142/181] ci(blocked-prs): use pull_request_target This needs to run in the context of our repo to have access to it's secrets Signed-off-by: Seth Flynn --- .github/workflows/merge-blocking-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index d6410f997..b77313b09 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -1,7 +1,7 @@ name: Merged Blocking Pull Request Automation on: - pull_request: + pull_request_target: types: - closed From e9f7ba188bfb40785de59dee269dbe7b8f9e0dd0 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Fri, 18 Apr 2025 16:53:16 -0400 Subject: [PATCH 143/181] ci(blocked-prs): use object filter to check pr label https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#example-using-an-object-filter Signed-off-by: Seth Flynn --- .github/workflows/merge-blocking-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index b77313b09..fe99e1c14 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -12,7 +12,7 @@ jobs: # a pr that was a `blocking:` label was merged. # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' ) + if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') steps: - name: Generate token From b6e48ac641b684c9f977454c8c4c9d90c5aea408 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Fri, 18 Apr 2025 16:56:19 -0400 Subject: [PATCH 144/181] ci(blocked-prs): allow workflow_dispatch on blocking prs Signed-off-by: Seth Flynn --- .github/workflows/merge-blocking-pr.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index fe99e1c14..d37c33761 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -4,6 +4,12 @@ on: pull_request_target: types: - closed + workflow_dispatch: + inputs: + pr_id: + description: Local Pull Request number to work on + required: true + type: number jobs: update-blocked-status: @@ -12,7 +18,7 @@ jobs: # a pr that was a `blocking:` label was merged. # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') + if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') }} steps: - name: Generate token @@ -26,11 +32,11 @@ jobs: id: gather_deps env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} + PR_NUMBER: ${{ inputs.pr_id || github.event.pull_request.number }} run: | blocked_prs=$( gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \ - | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' + | jq -c --argjson pr "$PR_NUMBER" ' reduce ( .[] | select( .body | scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | From 95492cdeef86ec92da141a951c29037132715191 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:57:31 +0000 Subject: [PATCH 145/181] chore(deps): update cachix/install-nix-action digest to 754537a --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index cdd360589..f4b1c4f5d 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31 + - uses: cachix/install-nix-action@754537aaedb35f72ab11a60cc162c49ef3016495 # v31 - uses: DeterminateSystems/update-flake-lock@v24 with: From d1c71075756845efe3c1730874f20f71ca52408d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 19 Apr 2025 00:41:24 +0300 Subject: [PATCH 146/181] fix: gzip file parsing as a stream Signed-off-by: Trial97 --- launcher/GZip.cpp | 114 +++++++++++-------- launcher/GZip.h | 27 +---- launcher/ui/pages/instance/OtherLogsPage.cpp | 91 ++++++--------- 3 files changed, 107 insertions(+), 125 deletions(-) diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index eaf1c9035..29c71c012 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -139,64 +139,80 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes) return true; } -GZipStream::GZipStream(const QString& filePath) : GZipStream(new QFile(filePath)) {} - -GZipStream::GZipStream(QFile* file) : m_file(file) {} - -bool GZipStream::initStream() +int inf(QFile* source, std::function handleBlock) { - memset(&m_strm, 0, sizeof(m_strm)); - return (inflateInit2(&m_strm, 16 + MAX_WBITS) == Z_OK); -} + constexpr auto CHUNK = 16384; + int ret; + unsigned have; + z_stream strm; + memset(&strm, 0, sizeof(strm)); + char in[CHUNK]; + unsigned char out[CHUNK]; -bool GZipStream::unzipBlockByBlock(QByteArray& uncompressedBytes) -{ - uncompressedBytes.clear(); - if (!m_file->isOpen()) { - if (!m_file->open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open file:" << (m_file->fileName()); - return false; + ret = inflateInit2(&strm, (16 + MAX_WBITS)); + if (ret != Z_OK) + return ret; + + /* decompress until deflate stream ends or end of file */ + do { + strm.avail_in = source->read(in, CHUNK); + if (source->error()) { + (void)inflateEnd(&strm); + return Z_ERRNO; } - } + if (strm.avail_in == 0) + break; + strm.next_in = reinterpret_cast(in); - if (!m_strm.state && !initStream()) { - return false; - } + /* run inflate() on input until output buffer not full */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return ret; + } + have = CHUNK - strm.avail_out; + if (!handleBlock(QByteArray(reinterpret_cast(out), have))) { + (void)inflateEnd(&strm); + return Z_OK; + } - QByteArray compressedBlock; - unsigned int blockSize = 4096; + } while (strm.avail_out == 0); - compressedBlock = m_file->read(blockSize); - if (compressedBlock.isEmpty()) { - return true; // End of file reached - } + /* done when inflate() says it's done */ + } while (ret != Z_STREAM_END); - bool done = processBlock(compressedBlock, uncompressedBytes); - if (inflateEnd(&m_strm) != Z_OK || !done) { - return false; - } - return done; + /* clean up and return */ + (void)inflateEnd(&strm); + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; } -bool GZipStream::processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes) +QString zerr(int ret) { - m_strm.next_in = (Bytef*)compressedBlock.data(); - m_strm.avail_in = compressedBlock.size(); - - unsigned int uncompLength = uncompressedBytes.size(); - if (m_strm.total_out >= uncompLength) { - uncompressedBytes.resize(uncompLength * 2); - uncompLength *= 2; + switch (ret) { + case Z_ERRNO: + return QObject::tr("error handling file"); + case Z_STREAM_ERROR: + return QObject::tr("invalid compression level"); + case Z_DATA_ERROR: + return QObject::tr("invalid or incomplete deflate data"); + case Z_MEM_ERROR: + return QObject::tr("out of memory"); + case Z_VERSION_ERROR: + return QObject::tr("zlib version mismatch!"); } - - m_strm.next_out = reinterpret_cast(uncompressedBytes.data() + m_strm.total_out); - m_strm.avail_out = uncompLength - m_strm.total_out; - - int err = inflate(&m_strm, Z_NO_FLUSH); - if (err != Z_OK && err != Z_STREAM_END) { - qWarning() << "Decompression failed with error code" << err; - return false; - } - - return true; + return {}; } + +QString GZip::readGzFileByBlocks(QFile* source, std::function handleBlock) +{ + auto ret = inf(source, handleBlock); + return zerr(ret); +} \ No newline at end of file diff --git a/launcher/GZip.h b/launcher/GZip.h index dd4162839..b736ca93f 100644 --- a/launcher/GZip.h +++ b/launcher/GZip.h @@ -1,28 +1,11 @@ #pragma once -#include #include #include -class GZip { - public: - static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes); - static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes); -}; +namespace GZip { -class GZipStream { - public: - explicit GZipStream(const QString& filePath); - explicit GZipStream(QFile* file); +bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes); +bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes); +QString readGzFileByBlocks(QFile* source, std::function handleBlock); - // Decompress the next block and return the decompressed data - bool unzipBlockByBlock(QByteArray& uncompressedBytes); - - private: - bool initStream(); - - bool processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes); - - private: - QFile* m_file; - z_stream m_strm; -}; +} // namespace GZip diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index b7d10a41a..0aeb942a8 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -151,54 +151,13 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) } } -class ReadLineAbstract { - public: - ReadLineAbstract(QFile* file) : m_file(file) - { - if (file->fileName().endsWith(".gz")) - m_gz = new GZipStream(file); - } - ~ReadLineAbstract() { delete m_gz; } - - QString readLine() - { - if (!m_gz) - return QString::fromUtf8(m_file->readLine()); - QString line; - for (;;) { - if (!m_decodedData.isEmpty()) { - int newlineIndex = m_decodedData.indexOf('\n'); - if (newlineIndex != -1) { - line += QString::fromUtf8(m_decodedData).left(newlineIndex); - m_decodedData.remove(0, newlineIndex + 1); - return line; - } - - line += QString::fromUtf8(m_decodedData); - m_decodedData.clear(); - } - - if (!m_gz->unzipBlockByBlock(m_decodedData)) { // If error occurs during unzipping - m_decodedData.clear(); - return QObject::tr("The content of the file(%1) could not be decoded.").arg(m_file->fileName()); - } - } - } - - bool done() { return m_gz ? m_decodedData.isEmpty() : m_file->atEnd(); } - - private: - QFile* m_file; - GZipStream* m_gz = nullptr; - QByteArray m_decodedData; -}; - void OtherLogsPage::on_btnReload_clicked() { if (m_currentFile.isEmpty()) { setControlsEnabled(false); return; } + QFile file(FS::PathCombine(m_path, m_currentFile)); if (!file.open(QFile::ReadOnly)) { setControlsEnabled(false); @@ -220,15 +179,9 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - - ReadLineAbstract stream(&file); - - // Try to determine a level for each line - ui->text->clear(); - ui->text->setModel(nullptr); - m_model->clear(); - auto line = stream.readLine(); - while (!stream.done()) { // just read until the model is full or the file ended + auto handleLine = [this](QString line) { + if (line.isEmpty()) + return false; if (line.back() == '\n') line = line.remove(line.size() - 1, 1); MessageLevel::Enum level = MessageLevel::Unknown; @@ -245,10 +198,40 @@ void OtherLogsPage::on_btnReload_clicked() } m_model->append(level, line); - if (m_model->isOverFlow()) - break; + return m_model->isOverFlow(); + }; - line = stream.readLine(); + // Try to determine a level for each line + ui->text->clear(); + ui->text->setModel(nullptr); + m_model->clear(); + if (file.fileName().endsWith(".gz")) { + QString line; + auto error = GZip::readGzFileByBlocks(&file, [&line, handleLine](const QByteArray& d) { + auto block = d; + int newlineIndex = block.indexOf('\n'); + while (newlineIndex != -1) { + line += QString::fromUtf8(block).left(newlineIndex); + block.remove(0, newlineIndex + 1); + if (handleLine(line)) { + line.clear(); + return false; + } + line.clear(); + newlineIndex = block.indexOf('\n'); + } + line += QString::fromUtf8(block); + return true; + }); + if (!error.isEmpty()) { + setPlainText(tr("The file (%1) encountered an error when reading: %2.").arg(file.fileName(), error)); + return; + } else if (!line.isEmpty()) { + handleLine(line); + } + } else { + while (!file.atEnd() && !handleLine(QString::fromUtf8(file.readLine()))) { + } } ui->text->setModel(m_proxy); ui->text->scrollToBottom(); From 49f0e8ef6b8fe1f18380b5ffe1d7c565c0d20b85 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 26 Mar 2025 08:29:40 +0200 Subject: [PATCH 147/181] replace qvector with qlist Signed-off-by: Trial97 --- launcher/Json.h | 20 ++++++++--------- launcher/icons/IconList.h | 2 +- launcher/launch/LogModel.cpp | 2 +- launcher/launch/LogModel.h | 2 +- launcher/meta/Index.cpp | 4 ++-- launcher/meta/Index.h | 6 ++--- launcher/meta/JsonFormat.cpp | 8 +++---- launcher/meta/Version.h | 2 +- launcher/meta/VersionList.cpp | 4 ++-- launcher/meta/VersionList.h | 6 ++--- launcher/minecraft/auth/AccountData.h | 2 +- launcher/minecraft/auth/AuthFlow.h | 1 - launcher/minecraft/skins/SkinList.cpp | 2 +- launcher/minecraft/skins/SkinList.h | 2 +- launcher/modplatform/ModIndex.h | 3 +-- .../modplatform/atlauncher/ATLPackIndex.h | 4 ++-- .../atlauncher/ATLPackInstallTask.cpp | 4 ++-- .../atlauncher/ATLPackInstallTask.h | 2 +- .../modplatform/atlauncher/ATLPackManifest.h | 16 +++++++------- .../modplatform/atlauncher/ATLShareCode.h | 4 ++-- launcher/modplatform/flame/FlameAPI.cpp | 2 +- launcher/modplatform/flame/FlameAPI.h | 2 +- launcher/modplatform/flame/FlameModIndex.cpp | 4 ++-- launcher/modplatform/flame/FlamePackIndex.cpp | 2 +- launcher/modplatform/flame/FlamePackIndex.h | 3 +-- launcher/modplatform/flame/PackManifest.h | 4 ++-- .../modrinth/ModrinthPackIndex.cpp | 4 ++-- .../modrinth/ModrinthPackManifest.cpp | 2 +- .../modrinth/ModrinthPackManifest.h | 4 ++-- .../modplatform/technic/SolderPackManifest.h | 6 ++--- launcher/translations/TranslationsModel.cpp | 4 ++-- launcher/translations/TranslationsModel.h | 2 +- .../ui/dialogs/skins/draw/BoxGeometry.cpp | 22 +++++++++---------- launcher/ui/dialogs/skins/draw/Scene.h | 6 ++--- launcher/ui/instanceview/InstanceView.cpp | 2 +- launcher/ui/instanceview/InstanceView.h | 2 +- launcher/ui/instanceview/VisualGroup.cpp | 2 +- launcher/ui/instanceview/VisualGroup.h | 4 ++-- .../atlauncher/AtlOptionalModDialog.cpp | 8 +++---- .../atlauncher/AtlOptionalModDialog.h | 12 +++++----- .../AtlUserInteractionSupportImpl.cpp | 4 ++-- .../AtlUserInteractionSupportImpl.h | 3 +-- .../modplatform/flame/FlameResourceModels.cpp | 2 +- .../pages/modplatform/technic/TechnicData.h | 3 +-- libraries/LocalPeer/src/LockedFile.h | 4 ++-- 45 files changed, 102 insertions(+), 107 deletions(-) diff --git a/launcher/Json.h b/launcher/Json.h index 28891f398..c13be6470 100644 --- a/launcher/Json.h +++ b/launcher/Json.h @@ -188,10 +188,10 @@ T ensureIsType(const QJsonObject& parent, const QString& key, const T default_ = } template -QVector requireIsArrayOf(const QJsonDocument& doc) +QList requireIsArrayOf(const QJsonDocument& doc) { const QJsonArray array = requireArray(doc); - QVector out; + QList out; for (const QJsonValue val : array) { out.append(requireIsType(val, "Document")); } @@ -199,10 +199,10 @@ QVector requireIsArrayOf(const QJsonDocument& doc) } template -QVector ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value") +QList ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value") { const QJsonArray array = ensureIsType(value, QJsonArray(), what); - QVector out; + QList out; for (const QJsonValue val : array) { out.append(requireIsType(val, what)); } @@ -210,7 +210,7 @@ QVector ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value } template -QVector ensureIsArrayOf(const QJsonValue& value, const QVector default_, const QString& what = "Value") +QList ensureIsArrayOf(const QJsonValue& value, const QList default_, const QString& what = "Value") { if (value.isUndefined()) { return default_; @@ -220,7 +220,7 @@ QVector ensureIsArrayOf(const QJsonValue& value, const QVector default_, c /// @throw JsonException template -QVector requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__") +QList requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__") { const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); if (!parent.contains(key)) { @@ -230,10 +230,10 @@ QVector requireIsArrayOf(const QJsonObject& parent, const QString& key, const } template -QVector ensureIsArrayOf(const QJsonObject& parent, - const QString& key, - const QVector& default_ = QVector(), - const QString& what = "__placeholder__") +QList ensureIsArrayOf(const QJsonObject& parent, + const QString& key, + const QList& default_ = QList(), + const QString& what = "__placeholder__") { const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); if (!parent.contains(key)) { diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h index 8936195c3..d2f904448 100644 --- a/launcher/icons/IconList.h +++ b/launcher/icons/IconList.h @@ -106,6 +106,6 @@ class IconList : public QAbstractListModel { shared_qobject_ptr m_watcher; bool m_isWatching; QMap m_nameIndex; - QVector m_icons; + QList m_icons; QDir m_dir; }; diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 45aac6099..32a266428 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -100,7 +100,7 @@ void LogModel::setMaxLines(int maxLines) return; } // otherwise, we need to reorganize the data because it crosses the wrap boundary - QVector newContent; + QList newContent; newContent.resize(maxLines); if (m_numLines <= maxLines) { // if it all fits in the new buffer, just copy it over diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index ba7b14487..bf178e35f 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -40,7 +40,7 @@ class LogModel : public QAbstractListModel { }; private: /* data */ - QVector m_content; + QList m_content; int m_maxLines = 1000; // first line in the circular buffer int m_firstLine = 0; diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index 1707854be..25a4cd146 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -23,7 +23,7 @@ namespace Meta { Index::Index(QObject* parent) : QAbstractListModel(parent) {} -Index::Index(const QVector& lists, QObject* parent) : QAbstractListModel(parent), m_lists(lists) +Index::Index(const QList& lists, QObject* parent) : QAbstractListModel(parent), m_lists(lists) { for (int i = 0; i < m_lists.size(); ++i) { m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i)); @@ -103,7 +103,7 @@ void Index::parse(const QJsonObject& obj) void Index::merge(const std::shared_ptr& other) { - const QVector lists = other->m_lists; + const QList lists = other->m_lists; // initial load, no need to merge if (m_lists.isEmpty()) { beginResetModel(); diff --git a/launcher/meta/Index.h b/launcher/meta/Index.h index 026a00c07..fe5bf2170 100644 --- a/launcher/meta/Index.h +++ b/launcher/meta/Index.h @@ -29,7 +29,7 @@ class Index : public QAbstractListModel, public BaseEntity { Q_OBJECT public: explicit Index(QObject* parent = nullptr); - explicit Index(const QVector& lists, QObject* parent = nullptr); + explicit Index(const QList& lists, QObject* parent = nullptr); virtual ~Index() = default; enum { UidRole = Qt::UserRole, NameRole, ListPtrRole }; @@ -46,7 +46,7 @@ class Index : public QAbstractListModel, public BaseEntity { Version::Ptr get(const QString& uid, const QString& version); bool hasUid(const QString& uid) const; - QVector lists() const { return m_lists; } + QList lists() const { return m_lists; } Task::Ptr loadVersion(const QString& uid, const QString& version = {}, Net::Mode mode = Net::Mode::Online, bool force = false); @@ -60,7 +60,7 @@ class Index : public QAbstractListModel, public BaseEntity { void parse(const QJsonObject& obj) override; private: - QVector m_lists; + QList m_lists; QHash m_uids; void connectVersionList(int row, const VersionList::Ptr& list); diff --git a/launcher/meta/JsonFormat.cpp b/launcher/meta/JsonFormat.cpp index 86af7277e..8d8466c87 100644 --- a/launcher/meta/JsonFormat.cpp +++ b/launcher/meta/JsonFormat.cpp @@ -35,8 +35,8 @@ MetadataVersion currentFormatVersion() // Index static std::shared_ptr parseIndexInternal(const QJsonObject& obj) { - const QVector objects = requireIsArrayOf(obj, "packages"); - QVector lists; + const QList objects = requireIsArrayOf(obj, "packages"); + QList lists; lists.reserve(objects.size()); std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject& obj) { VersionList::Ptr list = std::make_shared(requireString(obj, "uid")); @@ -79,8 +79,8 @@ static VersionList::Ptr parseVersionListInternal(const QJsonObject& obj) { const QString uid = requireString(obj, "uid"); - const QVector versionsRaw = requireIsArrayOf(obj, "versions"); - QVector versions; + const QList versionsRaw = requireIsArrayOf(obj, "versions"); + QList versions; versions.reserve(versionsRaw.size()); std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject& vObj) { auto version = parseCommonVersion(uid, vObj); diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index 46dc740da..2327879a1 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -19,8 +19,8 @@ #include "BaseVersion.h" #include +#include #include -#include #include #include "minecraft/VersionFile.h" diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 1de4e7f36..1f4a969fa 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -169,7 +169,7 @@ void VersionList::setName(const QString& name) emit nameChanged(name); } -void VersionList::setVersions(const QVector& versions) +void VersionList::setVersions(const QList& versions) { beginResetModel(); m_versions = versions; @@ -265,7 +265,7 @@ void VersionList::setupAddedVersion(const int row, const Version::Ptr& version) disconnect(version.get(), &Version::typeChanged, this, nullptr); connect(version.get(), &Version::requiresChanged, this, - [this, row]() { emit dataChanged(index(row), index(row), QVector() << RequiresRole); }); + [this, row]() { emit dataChanged(index(row), index(row), QList() << RequiresRole); }); connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TimeRole, SortRole }); }); connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TypeRole }); }); diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 4215439db..21c86b751 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -61,14 +61,14 @@ class VersionList : public BaseVersionList, public BaseEntity { Version::Ptr getVersion(const QString& version); bool hasVersion(QString version) const; - QVector versions() const { return m_versions; } + QList versions() const { return m_versions; } // this blocks until the version list is loaded void waitToLoad(); public: // for usage only by parsers void setName(const QString& name); - void setVersions(const QVector& versions); + void setVersions(const QList& versions); void merge(const VersionList::Ptr& other); void mergeFromIndex(const VersionList::Ptr& other); void parse(const QJsonObject& obj) override; @@ -82,7 +82,7 @@ class VersionList : public BaseVersionList, public BaseEntity { void updateListData(QList) override {} private: - QVector m_versions; + QList m_versions; QStringList m_externalRecommendsVersions; QHash m_lookup; QString m_uid; diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 1ada4e38a..df7d569da 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -36,8 +36,8 @@ #pragma once #include #include +#include #include -#include #include #include diff --git a/launcher/minecraft/auth/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h index bff4c04e4..710509d8e 100644 --- a/launcher/minecraft/auth/AuthFlow.h +++ b/launcher/minecraft/auth/AuthFlow.h @@ -5,7 +5,6 @@ #include #include #include -#include #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AuthStep.h" diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 124b69c85..56379aaab 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -67,7 +67,7 @@ void SkinList::stopWatching() bool SkinList::update() { - QVector newSkins; + QList newSkins; m_dir.refresh(); auto manifestInfo = QFileInfo(m_dir.absoluteFilePath("index.json")); diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h index e77269d57..5a160909a 100644 --- a/launcher/minecraft/skins/SkinList.h +++ b/launcher/minecraft/skins/SkinList.h @@ -74,7 +74,7 @@ class SkinList : public QAbstractListModel { private: shared_qobject_ptr m_watcher; bool m_isWatching; - QVector m_skinList; + QList m_skinList; QDir m_dir; MinecraftAccountPtr m_acct; }; \ No newline at end of file diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 8fae1bf6c..523358e4e 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -23,7 +23,6 @@ #include #include #include -#include #include class QIODevice; @@ -141,7 +140,7 @@ struct IndexedPack { QString side; bool versionsLoaded = false; - QVector versions; + QList versions; // Don't load by default, since some modplatform don't have that info bool extraDataLoaded = true; diff --git a/launcher/modplatform/atlauncher/ATLPackIndex.h b/launcher/modplatform/atlauncher/ATLPackIndex.h index 8d18c671d..187bc05ec 100644 --- a/launcher/modplatform/atlauncher/ATLPackIndex.h +++ b/launcher/modplatform/atlauncher/ATLPackIndex.h @@ -18,9 +18,9 @@ #include "ATLPackManifest.h" +#include #include #include -#include namespace ATLauncher { @@ -34,7 +34,7 @@ struct IndexedPack { int position; QString name; PackType type; - QVector versions; + QList versions; bool system; QString description; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a9706a768..fa3b1be3a 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -689,7 +689,7 @@ void PackInstallTask::downloadMods() { qDebug() << "PackInstallTask::installMods: " << QThread::currentThreadId(); - QVector optionalMods; + QList optionalMods; for (const auto& mod : m_version.mods) { if (mod.optional) { optionalMods.push_back(mod); @@ -697,7 +697,7 @@ void PackInstallTask::downloadMods() } // Select optional mods, if pack contains any - QVector selectedMods; + QList selectedMods; if (!optionalMods.isEmpty()) { setStatus(tr("Selecting optional mods...")); auto mods = m_support->chooseOptionalMods(m_version, optionalMods); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index ee5960e30..ce8bb636d 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -62,7 +62,7 @@ class UserInteractionSupport { /** * Requests a user interaction to select which optional mods should be installed. */ - virtual std::optional> chooseOptionalMods(const PackVersion& version, QVector mods) = 0; + virtual std::optional> chooseOptionalMods(const PackVersion& version, QList mods) = 0; /** * Requests a user interaction to select a component version from a given version list diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 8db91087d..b6c3b7a84 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -36,9 +36,9 @@ #pragma once #include +#include #include #include -#include namespace ATLauncher { @@ -113,7 +113,7 @@ struct VersionMod { bool hidden; bool library; QString group; - QVector depends; + QStringList depends; QString colour; QString warning; @@ -139,8 +139,8 @@ struct VersionKeep { }; struct VersionKeeps { - QVector files; - QVector folders; + QList files; + QList folders; }; struct VersionDelete { @@ -149,8 +149,8 @@ struct VersionDelete { }; struct VersionDeletes { - QVector files; - QVector folders; + QList files; + QList folders; }; struct PackVersionMainClass { @@ -171,8 +171,8 @@ struct PackVersion { PackVersionExtraArguments extraArguments; VersionLoader loader; - QVector libraries; - QVector mods; + QList libraries; + QList mods; VersionConfigs configs; QMap colours; diff --git a/launcher/modplatform/atlauncher/ATLShareCode.h b/launcher/modplatform/atlauncher/ATLShareCode.h index 531945bce..9b56c6d7c 100644 --- a/launcher/modplatform/atlauncher/ATLShareCode.h +++ b/launcher/modplatform/atlauncher/ATLShareCode.h @@ -19,8 +19,8 @@ #pragma once #include +#include #include -#include namespace ATLauncher { @@ -32,7 +32,7 @@ struct ShareCodeMod { struct ShareCode { QString pack; QString version; - QVector mods; + QList mods; }; struct ShareCodeResponse { diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index a06793de0..15eb7a696 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -215,7 +215,7 @@ QList FlameAPI::loadModCategories(std::shared_ptr FlameAPI::getLatestVersion(QVector versions, +std::optional FlameAPI::getLatestVersion(QList versions, QList instanceLoaders, ModPlatform::ModLoaderTypes modLoaders) { diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 509e1abcd..f85a08eb1 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -15,7 +15,7 @@ class FlameAPI : public NetworkResourceAPI { QString getModFileChangelog(int modId, int fileId); QString getModDescription(int modId); - std::optional getLatestVersion(QVector versions, + std::optional getLatestVersion(QList versions, QList instanceLoaders, ModPlatform::ModLoaderTypes fallback); diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index ff9d2d9ce..c1b9e67af 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -78,7 +78,7 @@ static QString enumToString(int hash_algorithm) void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) { - QVector unsortedVersions; + QList unsortedVersions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); @@ -208,7 +208,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform:: auto profile = (dynamic_cast(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); auto loaders = profile->getSupportedModLoaders(); - QVector versions; + QList versions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 8c25b0482..8a7734be5 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -77,7 +77,7 @@ void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj) void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) { - QVector unsortedVersions; + QList unsortedVersions; for (auto versionIter : arr) { auto version = Json::requireObject(versionIter); Flame::IndexedVersion file; diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index 11633deee..30391288b 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -3,7 +3,6 @@ #include #include #include -#include #include "modplatform/ModIndex.h" namespace Flame { @@ -39,7 +38,7 @@ struct IndexedPack { QString logoUrl; bool versionsLoaded = false; - QVector versions; + QList versions; bool extraInfoLoaded = false; ModpackExtra extra; diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 7af3b9d6b..ebb3ed5cc 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -36,10 +36,10 @@ #pragma once #include +#include #include #include #include -#include #include "minecraft/mod/tasks/LocalResourceParse.h" #include "modplatform/ModIndex.h" @@ -66,7 +66,7 @@ struct Modloader { struct Minecraft { QString version; QString libraries; - QVector modLoaders; + QList modLoaders; }; struct Manifest { diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 16b300b02..744b058c0 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -114,7 +114,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) { - QVector unsortedVersions; + QList unsortedVersions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); auto file = loadIndexedPackVersion(obj); @@ -253,7 +253,7 @@ ModPlatform::IndexedVersion Modrinth::loadDependencyVersions([[maybe_unused]] co QString mcVersion = profile->getComponentVersion("net.minecraft"); auto loaders = profile->getSupportedModLoaders(); - QVector versions; + QList versions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); auto file = loadIndexedPackVersion(obj); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index 89ef6e4c4..be565bf11 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -99,7 +99,7 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj) void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) { - QVector unsortedVersions; + QList unsortedVersions; auto arr = Json::requireArray(doc); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index 2e5e2da84..97b8ab712 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -40,10 +40,10 @@ #include #include +#include #include #include #include -#include #include "modplatform/ModIndex.h" @@ -110,7 +110,7 @@ struct Modpack { bool extraInfoLoaded = false; ModpackExtra extra; - QVector versions; + QList versions; }; void loadIndexedPack(Modpack&, QJsonObject&); diff --git a/launcher/modplatform/technic/SolderPackManifest.h b/launcher/modplatform/technic/SolderPackManifest.h index 1a06d7037..3a5947515 100644 --- a/launcher/modplatform/technic/SolderPackManifest.h +++ b/launcher/modplatform/technic/SolderPackManifest.h @@ -19,15 +19,15 @@ #pragma once #include +#include #include -#include namespace TechnicSolder { struct Pack { QString recommended; QString latest; - QVector builds; + QList builds; }; void loadPack(Pack& v, QJsonObject& obj); @@ -41,7 +41,7 @@ struct PackBuildMod { struct PackBuild { QString minecraft; - QVector mods; + QList mods; }; void loadPackBuild(PackBuild& v, QJsonObject& obj); diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index e863dfef4..75fc93b3b 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -153,7 +153,7 @@ struct TranslationsModel::Private { QDir m_dir; // initial state is just english - QVector m_languages = { Language(defaultLangCode) }; + QList m_languages = { Language(defaultLangCode) }; QString m_selectedLanguage = defaultLangCode; std::unique_ptr m_qt_translator; @@ -417,7 +417,7 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c return 2; } -QVector::Iterator TranslationsModel::findLanguage(const QString& key) +QList::Iterator TranslationsModel::findLanguage(const QString& key) { return std::find_if(d->m_languages.begin(), d->m_languages.end(), [key](Language& lang) { return lang.key == key; }); } diff --git a/launcher/translations/TranslationsModel.h b/launcher/translations/TranslationsModel.h index 96a0e9f8b..945e689fc 100644 --- a/launcher/translations/TranslationsModel.h +++ b/launcher/translations/TranslationsModel.h @@ -41,7 +41,7 @@ class TranslationsModel : public QAbstractListModel { void setUseSystemLocale(bool useSystemLocale); private: - QVector::Iterator findLanguage(const QString& key); + QList::Iterator findLanguage(const QString& key); std::optional findLanguageAsOptional(const QString& key); void reloadLocalFiles(); void downloadTranslation(QString key); diff --git a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp index 9a5ad1ce2..b4ab8d4cc 100644 --- a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp +++ b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp @@ -18,10 +18,10 @@ #include "BoxGeometry.h" +#include #include #include #include -#include struct VertexData { QVector4D position; @@ -32,7 +32,7 @@ struct VertexData { // For cube we would need only 8 vertices but we have to // duplicate vertex for each face because texture coordinate // is different. -static const QVector vertices = { +static const QList vertices = { // Vertex data for face 0 QVector4D(-0.5f, -0.5f, 0.5f, 1.0f), // v0 QVector4D(0.5f, -0.5f, 0.5f, 1.0f), // v1 @@ -76,7 +76,7 @@ static const QVector vertices = { // index of the second strip needs to be duplicated. If // connecting strips have same vertex order then only last // index of the first strip needs to be duplicated. -static const QVector indices = { +static const QList indices = { 0, 1, 2, 3, 3, // Face 0 - triangle strip ( v0, v1, v2, v3) 4, 4, 5, 6, 7, 7, // Face 1 - triangle strip ( v4, v5, v6, v7) 8, 8, 9, 10, 11, 11, // Face 2 - triangle strip ( v8, v9, v10, v11) @@ -85,19 +85,19 @@ static const QVector indices = { 20, 20, 21, 22, 23 // Face 5 - triangle strip (v20, v21, v22, v23) }; -static const QVector planeVertices = { +static const QList planeVertices = { { QVector4D(-1.0f, -1.0f, -0.5f, 1.0f), QVector2D(0.0f, 0.0f) }, // Bottom-left { QVector4D(1.0f, -1.0f, -0.5f, 1.0f), QVector2D(1.0f, 0.0f) }, // Bottom-right { QVector4D(-1.0f, 1.0f, -0.5f, 1.0f), QVector2D(0.0f, 1.0f) }, // Top-left { QVector4D(1.0f, 1.0f, -0.5f, 1.0f), QVector2D(1.0f, 1.0f) }, // Top-right }; -static const QVector planeIndices = { +static const QList planeIndices = { 0, 1, 2, 3, 3 // Face 0 - triangle strip ( v0, v1, v2, v3) }; -QVector transformVectors(const QMatrix4x4& matrix, const QVector& vectors) +QList transformVectors(const QMatrix4x4& matrix, const QList& vectors) { - QVector transformedVectors; + QList transformedVectors; transformedVectors.reserve(vectors.size()); for (const QVector4D& vec : vectors) { @@ -113,9 +113,9 @@ QVector transformVectors(const QMatrix4x4& matrix, const QVector getCubeUVs(float u, float v, float width, float height, float depth, float textureWidth, float textureHeight) +QList getCubeUVs(float u, float v, float width, float height, float depth, float textureWidth, float textureHeight) { - auto toFaceVertices = [textureHeight, textureWidth](float x1, float y1, float x2, float y2) -> QVector { + auto toFaceVertices = [textureHeight, textureWidth](float x1, float y1, float x2, float y2) -> QList { return { QVector2D(x1 / textureWidth, 1.0 - y2 / textureHeight), QVector2D(x2 / textureWidth, 1.0 - y2 / textureHeight), @@ -168,7 +168,7 @@ QVector getCubeUVs(float u, float v, float width, float height, float back[2], }; // Create a new array to hold the modified UV data - QVector uvData; + QList uvData; uvData.reserve(24); // Iterate over the arrays and copy the data to newUVData @@ -237,7 +237,7 @@ void BoxGeometry::initGeometry(float u, float v, float width, float height, floa transformation.scale(m_size); auto positions = transformVectors(transformation, vertices); - QVector verticesData; + QList verticesData; verticesData.reserve(positions.size()); // Reserve space for efficiency for (int i = 0; i < positions.size(); ++i) { diff --git a/launcher/ui/dialogs/skins/draw/Scene.h b/launcher/ui/dialogs/skins/draw/Scene.h index de683a659..3560d1d74 100644 --- a/launcher/ui/dialogs/skins/draw/Scene.h +++ b/launcher/ui/dialogs/skins/draw/Scene.h @@ -34,9 +34,9 @@ class Scene { void setCapeVisible(bool visible); private: - QVector m_staticComponents; - QVector m_normalArms; - QVector m_slimArms; + QList m_staticComponents; + QList m_normalArms; + QList m_slimArms; BoxGeometry* m_cape = nullptr; QOpenGLTexture* m_skinTexture = nullptr; QOpenGLTexture* m_capeTexture = nullptr; diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index f52c994d3..2349c684d 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -89,7 +89,7 @@ void InstanceView::setModel(QAbstractItemModel* model) void InstanceView::dataChanged([[maybe_unused]] const QModelIndex& topLeft, [[maybe_unused]] const QModelIndex& bottomRight, - [[maybe_unused]] const QVector& roles) + [[maybe_unused]] const QList& roles) { scheduleDelayedItemsLayout(); } diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index 30be411a8..dea8b1212 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -83,7 +83,7 @@ class InstanceView : public QAbstractItemView { virtual void updateGeometries() override; protected slots: - virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) override; + virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& roles) override; virtual void rowsInserted(const QModelIndex& parent, int start, int end) override; virtual void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; void modelReset(); diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 089db8ad7..4f7a61eb5 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -55,7 +55,7 @@ void VisualGroup::update() auto itemsPerRow = view->itemsPerRow(); int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow)); - rows = QVector(numRows); + rows = QList(numRows); int maxRowHeight = 0; int positionInRow = 0; diff --git a/launcher/ui/instanceview/VisualGroup.h b/launcher/ui/instanceview/VisualGroup.h index 8c6f06bcc..7210e0dfc 100644 --- a/launcher/ui/instanceview/VisualGroup.h +++ b/launcher/ui/instanceview/VisualGroup.h @@ -35,10 +35,10 @@ #pragma once +#include #include #include #include -#include class InstanceView; class QPainter; @@ -61,7 +61,7 @@ struct VisualGroup { InstanceView* view = nullptr; QString text; bool collapsed = false; - QVector rows; + QList rows; int firstItemIndex = 0; int m_verticalPosition = 0; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index d84737bf5..5ee8d2c04 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -47,7 +47,7 @@ AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, - QVector mods) + QList mods) : QAbstractListModel(parent), m_version(version), m_mods(mods) { // fill mod index @@ -64,9 +64,9 @@ AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, } } -QVector AtlOptionalModListModel::getResult() +QList AtlOptionalModListModel::getResult() { - QVector result; + QList result; for (const auto& mod : m_mods) { if (m_selection[mod.name]) { @@ -315,7 +315,7 @@ void AtlOptionalModListModel::setMod(const ATLauncher::VersionMod& mod, int inde } } -AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods) +AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QList mods) : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { ui->setupUi(this); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 0636715cc..fa39e997c 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -55,9 +55,9 @@ class AtlOptionalModListModel : public QAbstractListModel { DescriptionColumn, }; - AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); + AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QList mods); - QVector getResult(); + QList getResult(); int rowCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override; @@ -86,21 +86,21 @@ class AtlOptionalModListModel : public QAbstractListModel { std::shared_ptr m_response = std::make_shared(); ATLauncher::PackVersion m_version; - QVector m_mods; + QList m_mods; QMap m_selection; QMap m_index; - QMap> m_dependents; + QMap> m_dependents; }; class AtlOptionalModDialog : public QDialog { Q_OBJECT public: - AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); + AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QList mods); ~AtlOptionalModDialog() override; - QVector getResult() { return listModel->getResult(); } + QList getResult() { return listModel->getResult(); } void useShareCode(); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index 7550ff758..dc9a4758f 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -41,8 +41,8 @@ AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget* parent) : m_parent(parent) {} -std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version, - QVector mods) +std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version, + QList mods) { AtlOptionalModDialog optionalModDialog(m_parent, version, mods); auto result = optionalModDialog.exec(); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h index 7ff021105..99f907a19 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -48,8 +48,7 @@ class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInt private: QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override; - std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version, - QVector mods) override; + std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version, QList mods) override; void displayMessage(QString message) override; private: diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index fea1fc27a..a2a36141b 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -95,7 +95,7 @@ void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, { FlameMod::loadIndexedPackVersions(m, arr); - QVector filtered_versions(m.versions.size()); + QList filtered_versions(m.versions.size()); // FIXME: Client-side version filtering. This won't take into account any user-selected filtering. for (auto const& version : m.versions) { diff --git a/launcher/ui/pages/modplatform/technic/TechnicData.h b/launcher/ui/pages/modplatform/technic/TechnicData.h index fc7fa4d39..11d57f071 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicData.h +++ b/launcher/ui/pages/modplatform/technic/TechnicData.h @@ -37,7 +37,6 @@ #include #include -#include namespace Technic { struct Modpack { @@ -61,7 +60,7 @@ struct Modpack { bool versionsLoaded = false; QString recommended; - QVector versions; + QList versions; }; } // namespace Technic diff --git a/libraries/LocalPeer/src/LockedFile.h b/libraries/LocalPeer/src/LockedFile.h index e8023251c..0d3539708 100644 --- a/libraries/LocalPeer/src/LockedFile.h +++ b/libraries/LocalPeer/src/LockedFile.h @@ -42,7 +42,7 @@ #include #ifdef Q_OS_WIN -#include +#include #endif class LockedFile : public QFile { @@ -64,7 +64,7 @@ class LockedFile : public QFile { #ifdef Q_OS_WIN Qt::HANDLE wmutex; Qt::HANDLE rmutex; - QVector rmutexes; + QList rmutexes; QString mutexname; Qt::HANDLE getMutexHandle(int idx, bool doCreate); From 96a4b78e2edcc5225d4f831e6ef5c534e2c14d46 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 23:45:46 +0100 Subject: [PATCH 148/181] Remove accidental return Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index ec597497d..63dcf5cff 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -43,11 +43,11 @@ #include #include -#include #include -#include -#include +#include +#include #include +#include OtherLogsPage::OtherLogsPage(InstancePtr instance, QWidget* parent) : QWidget(parent) @@ -144,7 +144,6 @@ void OtherLogsPage::populateSelectLogBox() if (index != -1) { ui->selectLogBox->setCurrentIndex(index); setControlsEnabled(true); - return; } else { setControlsEnabled(false); } From 8ea5eac29cd9340d9c2d1da6dd8e35355fdbce59 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 23:48:56 +0100 Subject: [PATCH 149/181] Make requested changes Signed-off-by: TheKodeToad --- launcher/NullInstance.h | 1 - launcher/ui/pages/instance/OtherLogsPage.h | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 93bab6c8b..e603b1634 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -35,7 +35,6 @@ */ #pragma once -#include #include "BaseInstance.h" #include "launch/LaunchTask.h" diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index fedb2506c..70eb145fb 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -39,8 +39,7 @@ #include #include -#include -#include +#include #include "LogPage.h" #include "ui/pages/BasePage.h" From 19b241fd31d89cd159ab075475ab1270eb8b2799 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 23:59:35 +0100 Subject: [PATCH 150/181] Include txt too Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 63dcf5cff..4e401aa9c 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -404,13 +404,17 @@ QStringList OtherLogsPage::getPaths() for (QString searchPath : m_logSearchPaths) { QDirIterator iterator(searchPath, QDir::Files | QDir::Readable); + const bool isRoot = searchPath == m_basePath; + while (iterator.hasNext()) { const QString name = iterator.next(); - if (!name.endsWith(".log") && !name.endsWith(".log.gz")) + QString relativePath = baseDir.relativeFilePath(name); + + if (!(name.endsWith(".log") || name.endsWith(".log.gz") || (!isRoot && name.endsWith(".txt")))) continue; - result.append(baseDir.relativeFilePath(name)); + result.append(relativePath); } } From 0aa3341d5827c4a1529323cb682592891641488d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 00:04:30 +0100 Subject: [PATCH 151/181] Fix other weird import Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 4e401aa9c..caacea83e 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -43,8 +43,8 @@ #include #include -#include #include +#include #include #include #include From 92ba13cfdb839ece93cea2429193a8c6c3982cd8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 00:12:38 +0100 Subject: [PATCH 152/181] Fix catastrophic regex mistake Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ec136ede0..638979578 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1011,7 +1011,7 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * static const QRegularExpression JAVA_EXCEPTION( - R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); + R"((Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable))"); if (line.contains(JAVA_EXCEPTION)) return MessageLevel::Error; From 111cdc240ef0232c155a9dbdd0bd87a077fcdf86 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 09:34:12 +0100 Subject: [PATCH 153/181] Disable auto-reload of files Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index caacea83e..f457195d8 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -142,8 +142,11 @@ void OtherLogsPage::populateSelectLogBox() if (!prevCurrentFile.isEmpty()) { const int index = ui->selectLogBox->findText(prevCurrentFile); if (index != -1) { + ui->selectLogBox->blockSignals(true); ui->selectLogBox->setCurrentIndex(index); + ui->selectLogBox->blockSignals(false); setControlsEnabled(true); + return; } else { setControlsEnabled(false); } From 47295da3907a5be82f93ffc49165d0ce6ba452e0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 16 Apr 2025 00:37:35 -0700 Subject: [PATCH 154/181] feat(logs): parse log4j xml events in logs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 2 + launcher/MessageLevel.cpp | 2 + launcher/MessageLevel.h | 1 + launcher/launch/LaunchTask.cpp | 55 +++++- launcher/launch/LaunchTask.h | 6 + launcher/logs/LogParser.cpp | 322 +++++++++++++++++++++++++++++++++ launcher/logs/LogParser.h | 74 ++++++++ 7 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 launcher/logs/LogParser.cpp create mode 100644 launcher/logs/LogParser.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ca5a5bea9..4f8b9018a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -175,6 +175,8 @@ set(LAUNCH_SOURCES launch/LogModel.h launch/TaskStepWrapper.cpp launch/TaskStepWrapper.h + logs/LogParser.cpp + logs/LogParser.h ) # Old update system diff --git a/launcher/MessageLevel.cpp b/launcher/MessageLevel.cpp index 116e70c4b..d0e8809ec 100644 --- a/launcher/MessageLevel.cpp +++ b/launcher/MessageLevel.cpp @@ -4,6 +4,8 @@ MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) { if (levelName == "Launcher") return MessageLevel::Launcher; + else if (levelName == "Trace") + return MessageLevel::Trace; else if (levelName == "Debug") return MessageLevel::Debug; else if (levelName == "Info") diff --git a/launcher/MessageLevel.h b/launcher/MessageLevel.h index fd12583f2..321af9d92 100644 --- a/launcher/MessageLevel.h +++ b/launcher/MessageLevel.h @@ -12,6 +12,7 @@ enum Enum { StdOut, /**< Undetermined stderr messages */ StdErr, /**< Undetermined stdout messages */ Launcher, /**< Launcher Messages */ + Trace, /**< Trace Messages */ Debug, /**< Debug Messages */ Info, /**< Info Messages */ Message, /**< Standard Messages */ diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 9ec746641..e1eaf8b1f 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -37,11 +37,14 @@ #include "launch/LaunchTask.h" #include +#include +#include #include #include #include #include #include +#include #include "MessageLevel.h" #include "tasks/Task.h" @@ -213,6 +216,52 @@ shared_qobject_ptr LaunchTask::getLogModel() return m_logModel; } +bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level) +{ + LogParser* parser; + switch (level) { + case MessageLevel::StdErr: + parser = &m_stderrParser; + break; + case MessageLevel::StdOut: + parser = &m_stdoutParser; + break; + default: + return false; + } + + parser->appendLine(line); + auto items = parser->parseAvailable(); + if (auto err = parser->getError(); err.has_value()) { + auto& model = *getLogModel(); + model.append(MessageLevel::Error, tr("[Log4j Parse Error] Failed to parse log4j log event: %1").arg(err.value().errMessage)); + return false; + } else { + if (items.has_value()) { + auto& model = *getLogModel(); + for (auto const& item : items.value()) { + if (std::holds_alternative(item)) { + auto entry = std::get(item); + auto msg = QString("[%1] [%2/%3] [%4]: %5") + .arg(entry.timestamp.toString("HH:mm:ss")) + .arg(entry.thread) + .arg(entry.levelText) + .arg(entry.logger) + .arg(entry.message); + msg = censorPrivateInfo(msg); + model.append(entry.level, msg); + } else if (std::holds_alternative(item)) { + auto msg = std::get(item).message; + level = m_instance->guessLevel(msg, level); + msg = censorPrivateInfo(msg); + model.append(level, msg); + } + } + } + } + return true; +} + void LaunchTask::onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel) { for (auto& line : lines) { @@ -222,6 +271,10 @@ void LaunchTask::onLogLines(const QStringList& lines, MessageLevel::Enum default void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) { + if (parseXmlLogs(line, level)) { + return; + } + // if the launcher part set a log level, use it auto innerLevel = MessageLevel::fromLine(line); if (innerLevel != MessageLevel::Unknown) { @@ -229,7 +282,7 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) } // If the level is still undetermined, guess level - if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { + if (level == MessageLevel::Unknown) { level = m_instance->guessLevel(line, level); } diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index 2e87ece95..5effab980 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -43,6 +43,7 @@ #include "LaunchStep.h" #include "LogModel.h" #include "MessageLevel.h" +#include "logs/LogParser.h" class LaunchTask : public Task { Q_OBJECT @@ -114,6 +115,9 @@ class LaunchTask : public Task { private: /*methods */ void finalizeSteps(bool successful, const QString& error); + protected: + bool parseXmlLogs(QString const& line, MessageLevel::Enum level); + protected: /* data */ MinecraftInstancePtr m_instance; shared_qobject_ptr m_logModel; @@ -122,4 +126,6 @@ class LaunchTask : public Task { int currentStep = -1; State state = NotStarted; qint64 m_pid = -1; + LogParser m_stdoutParser; + LogParser m_stderrParser; }; diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp new file mode 100644 index 000000000..294036134 --- /dev/null +++ b/launcher/logs/LogParser.cpp @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.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 . + * + */ + +#include "LogParser.h" + +void LogParser::appendLine(QAnyStringView data) +{ + if (!m_partialData.isEmpty()) { + m_buffer = QString(m_partialData); + m_buffer.append("\n"); + m_buffer.append(data.toString()); + m_partialData.clear(); + } else { + m_buffer.append(data.toString()); + } +} + +std::optional LogParser::getError() +{ + return m_error; +} + +MessageLevel::Enum LogParser::parseLogLevel(const QString& level) +{ + auto test = level.trimmed().toUpper(); + if (test == "TRACE") { + return MessageLevel::Trace; + } else if (test == "DEBUG") { + return MessageLevel::Debug; + } else if (test == "INFO") { + return MessageLevel::Info; + } else if (test == "WARN") { + return MessageLevel::Warning; + } else if (test == "ERROR") { + return MessageLevel::Error; + } else if (test == "FATAL") { + return MessageLevel::Fatal; + } else { + return MessageLevel::Unknown; + } +} + +std::optional LogParser::parseAttributes() +{ + LogParser::LogEntry entry{ + "", + MessageLevel::Info, + }; + auto attributes = m_parser.attributes(); + + for (const auto& attr : attributes) { + auto name = attr.name(); + auto value = attr.value(); + if (name == "logger") { + entry.logger = value.trimmed().toString(); + } else if (name == "timestamp") { + if (value.trimmed().isEmpty()) { + m_parser.raiseError("log4j:Event Missing required attribute: timestamp"); + return {}; + } + entry.timestamp = QDateTime::fromSecsSinceEpoch(value.trimmed().toLongLong()); + } else if (name == "level") { + entry.levelText = value.trimmed().toString(); + entry.level = parseLogLevel(entry.levelText); + } else if (name == "thread") { + entry.thread = value.trimmed().toString(); + } + } + if (entry.logger.isEmpty()) { + m_parser.raiseError("log4j:Event Missing required attribute: logger"); + return {}; + } + + return entry; +} + +void LogParser::setError() +{ + m_error = { + m_parser.errorString(), + m_parser.error(), + }; +} + +void LogParser::clearError() +{ + m_error = {}; // clear previous error +} + +bool isPotentialLog4JStart(QStringView buffer) +{ + static QString target = QStringLiteral(" LogParser::parseNext() +{ + clearError(); + + if (m_buffer.isEmpty()) { + return {}; + } + + if (m_buffer.trimmed().isEmpty()) { + m_buffer.clear(); + return {}; + } + + // check if we have a full xml log4j event + bool isCompleteLog4j = false; + m_parser.clear(); + m_parser.setNamespaceProcessing(false); + m_parser.addData(m_buffer); + if (m_parser.readNextStartElement()) { + if (m_parser.qualifiedName() == "log4j:Event") { + int depth = 1; + bool eod = false; + while (depth > 0 && !eod) { + auto tok = m_parser.readNext(); + switch (tok) { + case QXmlStreamReader::TokenType::StartElement: { + depth += 1; + } break; + case QXmlStreamReader::TokenType::EndElement: { + depth -= 1; + } break; + case QXmlStreamReader::TokenType::EndDocument: { + eod = true; // break outer while loop + } break; + default: { + // no op + } + } + if (m_parser.hasError()) { + break; + } + } + + isCompleteLog4j = depth == 0; + } + } + + if (isCompleteLog4j) { + return parseLog4J(); + } else { + if (isPotentialLog4JStart(m_buffer)) { + m_partialData = QString(m_buffer); + return LogParser::Partial{ QString(m_buffer) }; + } + + int start = 0; + auto bufView = QStringView(m_buffer); + while (start < bufView.length()) { + if (qsizetype pos = bufView.right(bufView.length() - start).indexOf('<'); pos != -1) { + auto slicestart = start + pos; + auto slice = bufView.right(bufView.length() - slicestart); + if (isPotentialLog4JStart(slice)) { + if (slicestart > 0) { + auto text = m_buffer.left(slicestart); + m_buffer = m_buffer.right(m_buffer.length() - slicestart); + if (!text.trimmed().isEmpty()) { + return LogParser::PlainText{ text }; + } + } + m_partialData = QString(m_buffer); + return LogParser::Partial{ QString(m_buffer) }; + } + start = slicestart + 1; + } else { + break; + } + } + + // no log4j found, all plain text + auto text = QString(m_buffer); + m_buffer.clear(); + if (text.trimmed().isEmpty()) { + return {}; + } else { + return LogParser::PlainText{ text }; + } + } +} + +std::optional> LogParser::parseAvailable() +{ + QList items; + bool doNext = true; + while (doNext) { + auto item_ = parseNext(); + if (m_error.has_value()) { + return {}; + } + if (item_.has_value()) { + auto item = item_.value(); + if (std::holds_alternative(item)) { + break; + } else { + items.push_back(item); + } + } else { + doNext = false; + } + } + return items; +} + +std::optional LogParser::parseLog4J() +{ + m_parser.clear(); + m_parser.setNamespaceProcessing(false); + m_parser.addData(m_buffer); + + m_parser.readNextStartElement(); + if (m_parser.qualifiedName() == "log4j:Event") { + auto entry_ = parseAttributes(); + if (!entry_.has_value()) { + setError(); + return {}; + } + auto entry = entry_.value(); + + bool foundMessage = false; + int depth = 1; + + while (!m_parser.atEnd()) { + auto tok = m_parser.readNext(); + switch (tok) { + case QXmlStreamReader::TokenType::StartElement: { + depth += 1; + if (m_parser.qualifiedName() == "log4j:Message") { + QString message; + bool messageComplete = false; + + while (!messageComplete) { + auto tok = m_parser.readNext(); + + switch (tok) { + case QXmlStreamReader::TokenType::Characters: { + message.append(m_parser.text()); + } break; + case QXmlStreamReader::TokenType::EndElement: { + if (m_parser.qualifiedName() == "log4j:Message") { + messageComplete = true; + } + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return {}; // parse fail + } break; + default: { + // no op + } + } + + if (m_parser.hasError()) { + return {}; + } + } + + entry.message = message; + foundMessage = true; + depth -= 1; + } + break; + case QXmlStreamReader::TokenType::EndElement: { + depth -= 1; + if (depth == 0 && m_parser.qualifiedName() == "log4j:Event") { + if (foundMessage) { + auto consumed = m_parser.characterOffset(); + if (consumed > 0 && consumed <= m_buffer.length()) { + m_buffer = m_buffer.right(m_buffer.length() - consumed); + + if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { + // only whitespace, dump it + m_buffer.clear(); + } + } + clearError(); + return entry; + } + m_parser.raiseError("log4j:Event Missing required attribute: message"); + setError(); + return {}; + } + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return {}; + } break; + default: { + // no op + } + } + } + + if (m_parser.hasError()) { + return {}; + } + } + } + + throw std::runtime_error("unreachable: already verified this was a complete log4j:Event"); +} diff --git a/launcher/logs/LogParser.h b/launcher/logs/LogParser.h new file mode 100644 index 000000000..462ea43cf --- /dev/null +++ b/launcher/logs/LogParser.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "MessageLevel.h" + +class LogParser { + public: + struct LogEntry { + QString logger; + MessageLevel::Enum level; + QString levelText; + QDateTime timestamp; + QString thread; + QString message; + }; + struct Partial { + QString data; + }; + struct PlainText { + QString message; + }; + struct Error { + QString errMessage; + QXmlStreamReader::Error error; + }; + + using ParsedItem = std::variant; + + public: + LogParser() = default; + + void appendLine(QAnyStringView data); + std::optional parseNext(); + std::optional> parseAvailable(); + std::optional getError(); + + protected: + MessageLevel::Enum parseLogLevel(const QString& level); + std::optional parseAttributes(); + void setError(); + void clearError(); + + std::optional parseLog4J(); + + private: + QString m_buffer; + QString m_partialData; + QXmlStreamReader m_parser; + std::optional m_error; +}; From bfdc77665d8b38db22dc1bf4f49b4772993a8766 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:21:14 -0700 Subject: [PATCH 155/181] feat(xml-logs): add tests Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- tests/XmlLogs_test.cpp | 85 + .../TerraFirmaGreg-Modern-forge.text.log | 947 ++++++ .../TerraFirmaGreg-Modern-forge.xml.log | 2854 +++++++++++++++++ .../testdata/TestLogs/vanilla-1.21.5.text.log | 25 + .../testdata/TestLogs/vanilla-1.21.5.xml.log | 75 + 5 files changed, 3986 insertions(+) create mode 100644 tests/XmlLogs_test.cpp create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log create mode 100644 tests/testdata/TestLogs/vanilla-1.21.5.text.log create mode 100644 tests/testdata/TestLogs/vanilla-1.21.5.xml.log diff --git a/tests/XmlLogs_test.cpp b/tests/XmlLogs_test.cpp new file mode 100644 index 000000000..072448c4e --- /dev/null +++ b/tests/XmlLogs_test.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.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 . + */ + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +class XmlLogParseTest : public QObject { + Q_OBJECT + + private slots: + + void parseXml_data() + { + QString source = QFINDTESTDATA("testdata/TestLogs"); + + QString shortXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.xml.log"))); + QString shortText = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.text.log"))); + + QString longXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.xml.log"))); + QString longText = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.text.log"))); + QTest::addColumn("text"); + QTest::addColumn("xml"); + QTest::newRow("short-vanilla") << shortText << shortXml; + QTest::newRow("long-forge") << longText << longXml; + } + + void parseXml() { QFETCH(QString, ) } + + private: + LogParser m_parser; + + QList> parseLines(const QStringList& lines) + { + QList> out; + for (const auto& line : lines) + m_parser.appendLine(line); + auto items = m_parser.parseAvailable(); + for (const auto& item : items) { + if (std::holds_alternative(item)) { + auto entry = std::get(item); + auto msg = QString("[%1] [%2/%3] [%4]: %5") + .arg(entry.timestamp.toString("HH:mm:ss")) + .arg(entry.thread) + .arg(entry.levelText) + .arg(entry.logger) + .arg(entry.message); + msg = censorPrivateInfo(msg); + out.append(std::make_pair(entry.level, msg)); + } else if (std::holds_alternative(item)) { + auto msg = std::get(item).message; + level = m_instance->guessLevel(msg, level); + msg = censorPrivateInfo(msg); + out.append(std::make_pair(entry.level, msg)); + } + } + return out; + } +}; diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log new file mode 100644 index 000000000..c0775ebb7 --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log @@ -0,0 +1,947 @@ +Checking: MC_SLIM +Checking: MERGED_MAPPINGS +Checking: MAPPINGS +Checking: MC_EXTRA +Checking: MOJMAPS +Checking: PATCHED +Checking: MC_SRG +2025-04-18 12:47:23,932 main WARN Advanced terminal features are not available in this environment +[12:47:24] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, Ryexandrite, --version, 1.20.1, --gameDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft, --assetsDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/assets, --assetIndex, 5, --uuid, , --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --launchTarget, forgeclient, --fml.forgeVersion, 47.2.6, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --width, 854, --height, 480] +[12:47:24] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.8 by Microsoft; OS Linux arch amd64 version 6.6.85 +[12:47:24] [main/INFO] [ne.mi.fm.lo.ImmediateWindowHandler/]: Loading ImmediateWindowProvider fmlearlywindow +[12:47:24] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.6 +[12:47:24] [main/INFO] [EARLYDISPLAY/]: Requested GL version 4.6 got version 4.6 +[12:47:24] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%23140!/ Service=ModLauncher Env=CLIENT +[12:47:24] [pool-2-thread-1/INFO] [EARLYDISPLAY/]: GL info: AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) GL version 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f), AMD +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/fmlcore/1.20.1-47.2.6/fmlcore-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/javafmllanguage/1.20.1-47.2.6/javafmllanguage-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/lowcodelanguage/1.20.1-47.2.6/lowcodelanguage-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/mclanguage/1.20.1-47.2.6/mclanguage-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.ja.se.JarSelector/]: Attempted to select two dependency jars from JarJar which have the same identification: Mod File: and Mod File: . Using Mod File: +[12:47:25] [main/INFO] [ne.mi.fm.lo.mo.JarInJarDependencyLocator/]: Found 28 dependencies adding them to mods collection +[12:47:28] [main/ERROR] [mixin/]: Mixin config dynamiclightsreforged.mixins.json does not specify "minVersion" property +[12:47:28] [main/INFO] [mixin/]: Compatibility level set to JAVA_17 +[12:47:28] [main/ERROR] [mixin/]: Mixin config mixins.satin.client.json does not specify "minVersion" property +[12:47:28] [main/ERROR] [mixin/]: Mixin config firstperson.mixins.json does not specify "minVersion" property +[12:47:28] [main/ERROR] [mixin/]: Mixin config yacl.mixins.json does not specify "minVersion" property +[12:47:28] [main/INFO] [cp.mo.mo.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgeclient' with arguments [--version, 1.20.1, --gameDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft, --assetsDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/assets, --uuid, , --username, Ryexandrite, --assetIndex, 5, --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --width, 854, --height, 480] +[12:47:28] [main/INFO] [co.ab.sa.co.Saturn/]: Loaded Saturn config file with 4 configurable options +[12:47:28] [main/INFO] [ModernFix/]: Loaded configuration file for ModernFix 5.18.1+mc1.20.1: 83 options available, 1 override(s) found +[12:47:28] [main/WARN] [ModernFix/]: Option 'mixin.perf.thread_priorities' overriden (by mods [smoothboot]) to 'false' +[12:47:28] [main/INFO] [ModernFix/]: Applying Nashorn fix +[12:47:28] [main/INFO] [ModernFix/]: Applied Forge config corruption patch +[12:47:28] [main/INFO] [fpsreducer/]: OptiFine was NOT detected. +[12:47:28] [main/INFO] [fpsreducer/]: OptiFabric was NOT detected. +[12:47:28] [main/WARN] [EmbeddiumConfig/]: Mod 'tfc' attempted to override option 'mixin.features.fast_biome_colors', which doesn't exist, ignoring +[12:47:28] [main/INFO] [Embeddium/]: Loaded configuration file for Embeddium: 205 options available, 3 override(s) found +[12:47:28] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Searching for graphics cards... +[12:47:28] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=AMD, name=Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT], version=unknown] +[12:47:28] [main/WARN] [Embeddium-Workarounds/]: Sodium has applied one or more workarounds to prevent crashes or other issues on your system: [NO_ERROR_CONTEXT_UNSUPPORTED] +[12:47:28] [main/WARN] [Embeddium-Workarounds/]: This is not necessarily an issue, but it may result in certain features or optimizations being disabled. You can sometimes fix these issues by upgrading your graphics driver. +[12:47:28] [main/INFO] [Radium Config/]: Loaded configuration file for Radium: 125 options available, 7 override(s) found +[12:47:28] [main/WARN] [mixin/]: Reference map 'carpeted-common-refmap.json' for carpeted-common.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'carpeted-forge-refmap.json' for carpeted.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'emi-forge-refmap.json' for emi-forge.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'ftb-filter-system-common-refmap.json' for ftbfiltersystem-common.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'ftb-filter-system-forge-refmap.json' for ftbfiltersystem.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/INFO] [Puzzles Lib/]: Loading 160 mods: + - additionalplacements 1.8.0 + - ae2 15.2.13 + - ae2insertexportcard 1.20.1-1.3.0 + - ae2netanalyser 1.20-1.0.6-forge + - ae2wtlib 15.2.3-forge + - aiimprovements 0.5.2 + - ambientsounds 6.1.1 + - architectury 9.2.14 + - astikorcarts 1.1.8 + - attributefix 21.0.4 + - balm 7.3.9 + \-- kuma_api 20.1.8 + - barrels_2012 2.1 + - betterf3 7.0.2 + - betterfoliage 5.0.2 + - betterpingdisplay 1.1 + - betterthirdperson 1.9.0 + - blur 3.1.1 + \-- satin 1.20.1+1.15.0-SNAPSHOT + - carpeted 1.20-1.4 + - carryon 2.1.2.7 + \-- mixinextras 0.2.0-beta.6 + - catalogue 1.8.0 + - chat_heads 0.13.9 + - cherishedworlds 6.1.7+1.20.1 + - clienttweaks 11.1.0 + - cloth_config 11.1.136 + - clumps 12.0.0.4 + - computercraft 1.113.1 + - controlling 12.0.2 + - coralstfc 1.0.0 + - corpse 1.20.1-1.0.19 + - cosmeticarmorreworked 1.20.1-v1a + - craftingtweaks 18.2.5 + - craftpresence 2.5.0 + - create 0.5.1.f + \-- flywheel 0.6.10-7 + - create_connected 0.8.2-mc1.20.1 + - createaddition 1.20.1-1.2.4c + - creativecore 2.12.15 + - cucumber 7.0.12 + - cupboard 1.20.1-2.7 + - curios 5.10.0+1.20.1 + - defaultoptions 18.0.1 + - do_a_barrel_roll 3.5.6+1.20.1 + - drippyloadingscreen 3.0.1 + - dynamiclightsreforged 1.20.1_v1.6.0 + - embeddium 0.3.19+mc1.20.1 + \-- rubidium 0.7.1 + - embeddiumplus 1.2.12 + - emi 1.1.7+1.20.1+forge + - enhancedvisuals 1.8.1 + - etched 3.0.2 + - everycomp 1.20-2.7.12 + - expatternprovider 1.20-1.1.14-forge + - exposure 1.7.7 + - fallingtrees 0.12.7 + - fancymenu 3.2.3 + - ferritecore 6.0.1 + - firmaciv 0.2.10-alpha-1.20.1 + - firmalife 2.1.15 + - firstperson 2.4.5 + - flickerfix 4.0.1 + - forge 47.2.6 + - fpsreducer 1.20-2.5 + - framedblocks 9.3.1 + - ftbbackups2 1.0.23 + - ftbessentials 2001.2.2 + - ftbfiltersystem 1.0.2 + - ftblibrary 2001.2.4 + - ftbquests 2001.4.8 + - ftbranks 2001.1.3 + - ftbteams 2001.3.0 + - ftbxmodcompat 2.1.1 + - gcyr 0.1.8 + - getittogetherdrops 1.3 + - glodium 1.20-1.5-forge + - gtceu 1.2.3.a + |-- configuration 2.2.0 + \-- ldlib 1.0.25.j + - hangglider 8.0.1 + - immediatelyfast 1.2.18+1.20.4 + - inventoryhud 3.4.26 + - invtweaks 1.1.0 + - itemphysiclite 1.6.5 + - jade 11.9.4+forge + - jadeaddons 5.2.2 + - jei 15.3.0.8 + - konkrete 1.8.0 + - ksyxis 1.3.2 + - kubejs 2001.6.5-build.14 + - kubejs_create 2001.2.5-build.2 + - kubejs_tfc 1.20.1-1.1.3 + - letmedespawn 1.3.2b + - lootjs 1.20.1-2.12.0 + - megacells 2.4.4-1.20.1 + - melody 1.0.2 + - memoryleakfix 1.1.5 + - merequester 1.20.1-1.1.5 + - minecraft 1.20.1 + - modelfix 1.15 + - modernfix 5.18.1+mc1.20.1 + - moonlight 1.20-2.13.51 + - morered 4.0.0.4 + |-- jumbofurnace 4.0.0.5 + \-- useitemonblockevent 1.0.0.2 + - mousetweaks 2.25.1 + - myserveriscompatible 1.0 + - nanhealthfixer 1.20.1-0.0.1 + - nerb 0.4.1 + - noisium 2.3.0+mc1.20-1.20.1 + - noreportbutton 1.5.0 + - notenoughanimations 1.7.6 + - octolib 0.4.2 + - oculus 1.7.0 + - openpartiesandclaims 0.23.2 + - packetfixer 1.4.2 + - pandalib 0.4.2 + - patchouli 1.20.1-84-FORGE + - pickupnotifier 8.0.0 + - placebo 8.6.2 + - playerrevive 2.0.27 + - polylib 2000.0.3-build.143 + - puzzleslib 8.1.23 + \-- puzzlesaccessapi 8.0.7 + - radium 0.12.3+git.50c5c33 + - railways 1.6.4+forge-mc1.20.1 + - recipeessentials 1.20.1-3.6 + - rhino 2001.2.2-build.18 + - saturn 0.1.3 + - searchables 1.0.3 + - shimmer 1.20.1-0.2.4 + - showcaseitem 1.20.1-1.2 + - simplylight 1.20.1-1.4.6-build.50 + - smoothboot 0.0.4 + - sophisticatedbackpacks 3.20.5.1044 + - sophisticatedcore 0.6.22.611 + - supermartijn642configlib 1.1.8 + - supermartijn642corelib 1.1.17 + - tfc 3.2.12 + - tfc_tumbleweed 1.2.2 + - tfcagedalcohol 2.1 + - tfcambiental 1.20.1-3.3.0 + - tfcastikorcarts 1.1.8.2 + - tfcchannelcasting 0.2.3-beta + - tfcea 0.0.2 + - tfcgroomer 1.20.1-0.1.2 + - tfchotornot 1.0.4 + - tfcvesseltooltip 1.1 + - tfg 0.5.9 + - toofast 0.4.3.5 + - toolbelt 1.20.01 + - treetap 1.20.1-0.4.0 + - tumbleweed 0.5.5 + - unilib 1.0.2 + - uteamcore 5.1.4.312 + - waterflasks 3.0.3 + - xaerominimap 24.4.0 + - xaeroworldmap 1.39.0 + - yeetusexperimentus 2.3.1-build.6+mc1.20.1 + - yet_another_config_lib_v3 3.5.0+1.20.1-forge +[12:47:28] [main/WARN] [mixin/]: Reference map 'packetfixer-forge-forge-refmap.json' for packetfixer-forge.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'tfchotornot.refmap.json' for tfchotornot.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:29] [main/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:29] [main/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:29] [main/WARN] [mixin/]: Error loading class: mezz/modnametooltip/TooltipEventHandler (java.lang.ClassNotFoundException: mezz.modnametooltip.TooltipEventHandler) +[12:47:29] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/ClientHelperImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.ClientHelperImpl) +[12:47:29] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl) +[12:47:29] [main/INFO] [co.cu.Cupboard/]: Loaded config for: recipeessentials.json +[12:47:30] [main/WARN] [mixin/]: Error loading class: loaderCommon/forge/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/ChunkLoader (java.lang.ClassNotFoundException: loaderCommon.forge.com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.ChunkLoader) +[12:47:30] [main/INFO] [fpsreducer/]: bre2el.fpsreducer.mixin.RenderSystemMixin will be applied. +[12:47:30] [main/INFO] [fpsreducer/]: bre2el.fpsreducer.mixin.WindowMixin will NOT be applied because OptiFine was NOT detected. +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ChatComponentMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ChatComponentMixin2 false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ChatListenerMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ClientPacketListenerMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.CommandSuggestionSuggestionsListMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ConnectionMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.DownloadedPackSourceMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.FontStringRenderOutputMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.GuiMessageLineMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.GuiMessageMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.HttpTextureMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.PlayerChatMessageMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.SkinManagerMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.compat.EmojifulMixin true false +[12:47:30] [main/WARN] [mixin/]: Error loading class: dan200/computercraft/shared/integration/jei/JEIComputerCraft (java.lang.ClassNotFoundException: dan200.computercraft.shared.integration.jei.JEIComputerCraft) +[12:47:30] [main/WARN] [mixin/]: @Mixin target dan200.computercraft.shared.integration.jei.JEIComputerCraft was not found tfg.mixins.json:common.cc.JEIComputerCraftMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: com/copycatsplus/copycats/content/copycat/slab/CopycatSlabBlock (java.lang.ClassNotFoundException: com.copycatsplus.copycats.content.copycat.slab.CopycatSlabBlock) +[12:47:30] [main/WARN] [mixin/]: @Mixin target com.copycatsplus.copycats.content.copycat.slab.CopycatSlabBlock was not found create_connected.mixins.json:compat.CopycatBlockMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: com/copycatsplus/copycats/content/copycat/board/CopycatBoardBlock (java.lang.ClassNotFoundException: com.copycatsplus.copycats.content.copycat.board.CopycatBoardBlock) +[12:47:30] [main/WARN] [mixin/]: @Mixin target com.copycatsplus.copycats.content.copycat.board.CopycatBoardBlock was not found create_connected.mixins.json:compat.CopycatBlockMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: me/jellysquid/mods/lithium/common/ai/pathing/PathNodeDefaults (java.lang.ClassNotFoundException: me.jellysquid.mods.lithium.common.ai.pathing.PathNodeDefaults) +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'alloc.blockstate.StateMixin' as option 'mixin.alloc.blockstate' (added by mods [ferritecore]) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.fluid.EntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.intersection.WorldMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.movement.EntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.AbstractMinecartEntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.BoatEntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.EntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.EntityPredicatesMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.EntityTrackingSectionMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.LivingEntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'world.player_chunk_tick.ThreadedAnvilChunkStorageMixin' as option 'mixin.world.player_chunk_tick' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [mixin/]: Error loading class: org/cyclops/integrateddynamics/block/BlockCable (java.lang.ClassNotFoundException: org.cyclops.integrateddynamics.block.BlockCable) +[12:47:30] [main/WARN] [mixin/]: @Mixin target org.cyclops.integrateddynamics.block.BlockCable was not found mixins.epp.json:MixinBlockCable +[12:47:30] [main/WARN] [mixin/]: Error loading class: blusunrize/immersiveengineering/api/wires/GlobalWireNetwork (java.lang.ClassNotFoundException: blusunrize.immersiveengineering.api.wires.GlobalWireNetwork) +[12:47:30] [main/WARN] [mixin/]: @Mixin target blusunrize.immersiveengineering.api.wires.GlobalWireNetwork was not found mixins.epp.json:MixinGlobalWireNetwork +[12:47:30] [main/WARN] [mixin/]: Error loading class: weather2/weathersystem/storm/TornadoHelper (java.lang.ClassNotFoundException: weather2.weathersystem.storm.TornadoHelper) +[12:47:30] [main/WARN] [mixin/]: @Mixin target weather2.weathersystem.storm.TornadoHelper was not found tfc_tumbleweed.mixins.json:TornadoHelperMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: weather2/weathersystem/storm/TornadoHelper (java.lang.ClassNotFoundException: weather2.weathersystem.storm.TornadoHelper) +[12:47:30] [main/WARN] [mixin/]: @Mixin target weather2.weathersystem.storm.TornadoHelper was not found tfc_tumbleweed.mixins.json:client.TornadoHelperMixin +[12:47:30] [main/INFO] [memoryleakfix/]: [MemoryLeakFix] Will be applying 3 memory leak fixes! +[12:47:30] [main/INFO] [memoryleakfix/]: [MemoryLeakFix] Currently enabled memory leak fixes: [targetEntityLeak, biomeTemperatureLeak, hugeScreenshotLeak] +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.world.sky.WorldRendererMixin' as rule 'mixin.features.render.world.sky' (added by mods [oculus, tfc]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.world.sky.ClientWorldMixin' as rule 'mixin.features.render.world.sky' (added by mods [oculus, tfc]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.world.sky.BackgroundRendererMixin' as rule 'mixin.features.render.world.sky' (added by mods [oculus, tfc]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.gui.font.GlyphRendererMixin' as rule 'mixin.features.render.gui.font' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.gui.font.FontSetMixin' as rule 'mixin.features.render.gui.font' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.shadows.EntityRenderDispatcherMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.fast_render.ModelPartMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.fast_render.CuboidMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.cull.EntityRendererMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [mixin/]: Error loading class: org/jetbrains/annotations/ApiStatus$Internal (java.lang.ClassNotFoundException: org.jetbrains.annotations.ApiStatus$Internal) +[12:47:31] [main/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.4.1). +[12:47:31] [main/INFO] [Smooth Boot (Reloaded)/]: Smooth Boot (Reloaded) config initialized +[12:47:31] [main/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method m_216202_ in modernfix-forge.mixins.json:perf.tag_id_caching.TagOrElementLocationMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12:47:32] [pool-4-thread-1/INFO] [minecraft/Bootstrap]: ModernFix reached bootstrap stage (9.773 s after launch) +[12:47:32] [pool-4-thread-1/WARN] [mixin/]: @Final field delegatesByName:Ljava/util/Map; in modernfix-forge.mixins.json:perf.forge_registry_alloc.ForgeRegistryMixin should be final +[12:47:32] [pool-4-thread-1/WARN] [mixin/]: @Final field delegatesByValue:Ljava/util/Map; in modernfix-forge.mixins.json:perf.forge_registry_alloc.ForgeRegistryMixin should be final +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:32] [pool-4-thread-1/INFO] [ModernFix/]: Injecting BlockStateBase cache population hook into getNeighborPathNodeType from me.jellysquid.mods.lithium.mixin.ai.pathing.AbstractBlockStateMixin +[12:47:32] [pool-4-thread-1/INFO] [ModernFix/]: Injecting BlockStateBase cache population hook into getPathNodeType from me.jellysquid.mods.lithium.mixin.ai.pathing.AbstractBlockStateMixin +[12:47:32] [pool-4-thread-1/INFO] [ModernFix/]: Injecting BlockStateBase cache population hook into getAllFlags from me.jellysquid.mods.lithium.mixin.util.block_tracking.AbstractBlockStateMixin +[12:47:32] [pool-4-thread-1/WARN] [mixin/]: Method overwrite conflict for m_6104_ in embeddium.mixins.json:features.options.render_layers.LeavesBlockMixin, previously written by me.srrapero720.embeddiumplus.mixins.impl.leaves_culling.LeavesBlockMixin. Skipping method. +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:33] [pool-4-thread-1/INFO] [minecraft/Bootstrap]: Vanilla bootstrap took 779 milliseconds +[12:47:34] [pool-4-thread-1/WARN] [mixin/]: Method overwrite conflict for m_47505_ in lithium.mixins.json:world.temperature_cache.BiomeMixin, previously written by org.embeddedt.modernfix.common.mixin.perf.remove_biome_temperature_cache.BiomeMixin. Skipping method. +[12:47:34] [pool-4-thread-1/INFO] [co.al.me.MERequester/]: Registering content +[12:47:35] [Render thread/WARN] [minecraft/VanillaPackResourcesBuilder]: Assets URL 'union:/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraft/client/1.20.1-20230612.114412/client-1.20.1-20230612.114412-srg.jar%23444!/assets/.mcassetsroot' uses unexpected schema +[12:47:35] [Render thread/WARN] [minecraft/VanillaPackResourcesBuilder]: Assets URL 'union:/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraft/client/1.20.1-20230612.114412/client-1.20.1-20230612.114412-srg.jar%23444!/data/.mcassetsroot' uses unexpected schema +[12:47:35] [Render thread/INFO] [mojang/YggdrasilAuthenticationService]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD' +[12:47:35] [Render thread/INFO] [minecraft/Minecraft]: Setting user: Ryexandrite +[12:47:35] [Render thread/INFO] [ModernFix/]: Bypassed Mojang DFU +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant is searching for constants in method with descriptor (Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/GuiMessageTag;)V +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = , stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 0 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = \\r, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 1 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn \\r +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = +, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 2 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn + +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = \\n, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 3 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn \\n +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found CLASS constant: value = Ljava/lang/String;, typeValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = [{}] [CHAT] {}, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 4 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn [{}] [CHAT] {} +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = [CHAT] {}, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 5 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn [CHAT] {} +[12:47:35] [Render thread/INFO] [defaultoptions/]: Loaded default options for extra-folder +[12:47:35] [Render thread/INFO] [ModernFix/]: Instantiating Mojang DFU +[12:47:36] [Render thread/INFO] [minecraft/Minecraft]: Backend library: LWJGL version 3.3.1 build 7 +[12:47:36] [Render thread/INFO] [KubeJS/]: Loaded client.properties +[12:47:36] [Render thread/INFO] [Embeddium-PostlaunchChecks/]: OpenGL Vendor: AMD +[12:47:36] [Render thread/INFO] [Embeddium-PostlaunchChecks/]: OpenGL Renderer: AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) +[12:47:36] [Render thread/INFO] [Embeddium-PostlaunchChecks/]: OpenGL Version: 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f) +[12:47:36] [Render thread/WARN] [Embeddium++/Config]: Loading Embeddium++Config +[12:47:36] [Render thread/INFO] [Embeddium++/Config]: Updating config cache +[12:47:36] [Render thread/INFO] [Embeddium++/Config]: Cache updated successfully +[12:47:36] [Render thread/INFO] [ImmediatelyFast/]: Initializing ImmediatelyFast 1.2.18+1.20.4 on AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) (AMD) with OpenGL 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f) +[12:47:36] [Render thread/INFO] [ImmediatelyFast/]: AMD GPU detected. Enabling coherent buffer mapping +[12:47:36] [Datafixer Bootstrap/INFO] [mojang/DataFixerBuilder]: 188 Datafixer optimizations took 85 milliseconds +[12:47:36] [Render thread/INFO] [ImmediatelyFast/]: Found Iris/Oculus 1.7.0. Enabling compatibility. +[12:47:36] [Render thread/INFO] [Oculus/]: Debug functionality is disabled. +[12:47:36] [Render thread/INFO] [Oculus/]: OpenGL 4.5 detected, enabling DSA. +[12:47:36] [Render thread/INFO] [Oculus/]: Shaders are disabled because no valid shaderpack is selected +[12:47:36] [Render thread/INFO] [Oculus/]: Shaders are disabled +[12:47:36] [modloading-worker-0/INFO] [dynamiclightsreforged/]: [LambDynLights] Initializing Dynamic Lights Reforged... +[12:47:36] [modloading-worker-0/INFO] [LowDragLib/]: LowDragLib is initializing on platform: Forge +[12:47:36] [modloading-worker-0/INFO] [in.u_.u_.ut.ve.JarSignVerifier/]: Mod uteamcore is signed with a valid certificate. +[12:47:36] [modloading-worker-0/INFO] [de.ke.me.Melody/]: [MELODY] Loading Melody background audio library.. +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing common components for pickupnotifier:main +[12:47:36] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method m_109501_ in embeddium.mixins.json:core.render.world.WorldRendererMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12:47:36] [modloading-worker-0/INFO] [de.ke.ko.Konkrete/]: [KONKRETE] Successfully initialized! +[12:47:36] [modloading-worker-0/INFO] [de.ke.ko.Konkrete/]: [KONKRETE] Server-side libs ready to use! +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing client components for pickupnotifier:main +[12:47:36] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method m_215924_ in modernfix-forge.mixins.json:perf.tag_id_caching.TagEntryMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12:47:36] [modloading-worker-0/INFO] [Additional Placements/]: Attempting to manually load Additional Placements config early. +[12:47:36] [modloading-worker-0/INFO] [Additional Placements/]: manual config load successful. +[12:47:36] [modloading-worker-0/WARN] [Additional Placements/]: During block registration you may recieve several reports of "Potentially Dangerous alternative prefix `additionalplacements`". Ignore these, they are intended. +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing common components for hangglider:main +[12:47:36] [modloading-worker-0/INFO] [noisium/]: Loading Noisium. +[12:47:36] [modloading-worker-0/INFO] [co.cu.Cupboard/]: Loaded config for: cupboard.json +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id architectury:sync_ids +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id architectury:sync_ids +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id pandalib:config_sync +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id pandalib:config_sync +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully loaded config 'fallingtrees_client' +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully saved config 'fallingtrees_client' +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing client components for hangglider:main +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id polylib:container_to_client +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id polylib:tile_to_client +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id polylib:container_packet_server +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id polylib:tile_data_server +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id polylib:tile_packet_server +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftblibrary:edit_nbt +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftblibrary:edit_nbt_response +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully loaded config 'fallingtrees_common' +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully saved config 'fallingtrees_common' +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftblibrary:sync_known_server_registries +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftblibrary:edit_config +[12:47:37] [UniLib/INFO] [unilib/]: Starting version check for "craftpresence" (MC 1.20.1) at "https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/CraftPresence/update.json" +[12:47:37] [UniLib/INFO] [unilib/]: Starting version check for "unilib" (MC 1.20.1) at "https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/UniLib/update.json" +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbessentials:update_tab_name +[12:47:37] [modloading-worker-0/INFO] [invtweaks/]: Registered 2 network packets +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Loaded common.properties +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Loaded dev.properties +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Looking for KubeJS plugins... +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:sync_teams +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:sync_message_history +[12:47:37] [modloading-worker-0/INFO] [GregTechCEu/]: GregTechCEu is initializing on platform: Forge +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source kubejs +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:open_gui +[12:47:37] [CraftPresence/INFO] [craftpresence/]: Configuration settings have been saved and reloaded successfully! +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:open_my_team_gui +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:update_settings +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin dev.latvian.mods.kubejs.integration.forge.gamestages.GameStagesIntegration does not have required mod gamestages loaded, skipping +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source ldlib +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source exposure +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source tfg +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:update_settings_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:send_message +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source ftbxmodcompat +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:send_message_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:update_presence +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:create_party +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:player_gui_operation +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin dev.ftb.mods.ftbxmodcompat.ftbchunks.kubejs.FTBChunksKubeJSPlugin does not have required mod ftbchunks loaded, skipping +[12:47:37] [modloading-worker-0/INFO] [de.ke.dr.DrippyLoadingScreen/]: [DRIPPY LOADING SCREEN] Loading v3.0.1 in client-side mode on FORGE! +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source lootjs +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source cucumber +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source gtceu +[12:47:37] [modloading-worker-0/INFO] [ne.dr.tf.TerraFirmaCraft/]: Initializing TerraFirmaCraft +[12:47:37] [modloading-worker-0/INFO] [ne.dr.tf.TerraFirmaCraft/]: Options: Assertions Enabled = false, Boostrap = false, Test = false, Debug Logging = true +[12:47:37] [CraftPresence/INFO] [craftpresence/]: Checking Discord for available assets with Client Id: 1182610212121743470 +[12:47:37] [CraftPresence/INFO] [craftpresence/]: Originally coded by paulhobbel - https://github.com/paulhobbel +[12:47:37] [modloading-worker-0/INFO] [ne.mi.co.ForgeMod/FORGEMOD]: Forge mod loading, version 47.2.6, for MC 1.20.1 with MCP 20230612.114412 +[12:47:37] [modloading-worker-0/INFO] [ne.mi.co.MinecraftForge/FORGE]: MinecraftForge v47.2.6 Initialized +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source gcyr +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source kubejs_tfc +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin com.notenoughmail.kubejs_tfc.addons.precpros.PrecProsPlugin does not have required mod precisionprospecting loaded, skipping +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin com.notenoughmail.kubejs_tfc.addons.afc.AFCPlugin does not have required mod afc loaded, skipping +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source kubejs_create +[Mouse Tweaks] Main.initialize() +[Mouse Tweaks] Initialized. +[12:47:37] [modloading-worker-0/INFO] [Every Compat/]: Loaded EveryCompat Create Module +[12:47:37] [modloading-worker-0/INFO] [Configuration/FileWatching]: Registered gtceu config for auto-sync function +[12:47:37] [modloading-worker-0/INFO] [Configuration/FileWatching]: Registered gtceu config for auto-sync function +[12:47:37] [modloading-worker-0/INFO] [Configuration/FileWatching]: Registered gcyr config for auto-sync function +[12:47:37] [modloading-worker-0/INFO] [GregTechCEu/]: High-Tier is Disabled. +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.im.StdSchedulerFactory/]: Using default implementation for ThreadExecutor +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Done in 309.0 ms +[12:47:37] [CraftPresence/INFO] [craftpresence/]: 3 total assets detected! +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_quests +[12:47:37] [modloading-worker-0/INFO] [Ksyxis/]: Ksyxis: Booting... (platform: Forge, manual: false) +[12:47:37] [modloading-worker-0/INFO] [Ksyxis/]: Ksyxis: Found Mixin library. (version: 0.8.5) +[12:47:37] [modloading-worker-0/INFO] [Ksyxis/]: Ksyxis: Ready. As always, this mod will speed up your world loading and might or might not break it. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_team_data +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.SchedulerSignalerImpl/]: Initialized Scheduler Signaller of type: class net.creeperhost.ftbbackups.repack.org.quartz.core.SchedulerSignalerImpl +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.QuartzScheduler/]: Quartz Scheduler v.2.0.2 created. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:update_task_progress +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.si.RAMJobStore/]: RAMJobStore initialized. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:submit_task +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.QuartzScheduler/]: Scheduler meta-data: Quartz Scheduler (v2.0.2) 'ftbbackups2' with instanceId 'NON_CLUSTERED' + Scheduler class: 'net.creeperhost.ftbbackups.repack.org.quartz.core.QuartzScheduler' - running locally. + NOT STARTED. + Currently in standby mode. + Number of jobs executed: 0 + Using thread pool 'net.creeperhost.ftbbackups.repack.org.quartz.simpl.SimpleThreadPool' - with 1 threads. + Using job-store 'net.creeperhost.ftbbackups.repack.org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. + +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.im.StdSchedulerFactory/]: Quartz scheduler 'ftbbackups2' initialized from an externally provided properties instance. +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.im.StdSchedulerFactory/]: Quartz scheduler version: 2.0.2 +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:claim_reward +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:claim_reward_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_editing_mode +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:get_emergency_items +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:create_other_team_data +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:claim_all_rewards +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:claim_choice_reward +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:display_completion_toast +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.QuartzScheduler/]: Scheduler ftbbackups2_$_NON_CLUSTERED started. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:display_reward_toast +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:display_item_reward_toast +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:toggle_pinned +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:toggle_pinned_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:toggle_chapter_pinned +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:toggle_chapter_pinned_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:toggle_editing_mode +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:force_save +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:update_team_data +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:set_custom_image +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_started +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_completed +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_started_reset +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_completed_reset +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_lock +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:reset_reward +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:team_data_changed +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:task_screen_config_req +[12:47:37] [modloading-worker-0/INFO] [co.jo.fl.ba.Backend/]: Oculus detected. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:task_screen_config_resp +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:change_progress +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:create_object +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:create_object_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:create_task_at +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:delete_object +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:delete_object_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:edit_object +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:edit_object_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:move_chapter +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:move_chapter_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:move_quest +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:move_quest_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:change_chapter_group +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:change_chapter_group_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:move_chapter_group +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:move_chapter_group_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_reward_blocking +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:copy_quest +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:copy_chapter_image +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:sync_structures_request +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_structures_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:request_team_data +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_editor_permission +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:open_quest_book +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:clear_display_cache +[12:47:37] [modloading-worker-0/INFO] [me.je.li.lo.PluginCaller/]: Sending ConfigManager... +[12:47:37] [modloading-worker-0/INFO] [me.je.li.lo.PluginCaller/]: Sending ConfigManager took 11.32 ms +[12:47:37] [modloading-worker-0/INFO] [MEGA Cells/]: Initialised items. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbfiltersystem:sync_filter +[12:47:37] [modloading-worker-0/INFO] [MEGA Cells/]: Initialised blocks. +[12:47:37] [modloading-worker-0/INFO] [MEGA Cells/]: Initialised block entities. +[12:47:37] [modloading-worker-0/INFO] [de.ke.fa.FancyMenu/]: [FANCYMENU] Loading v3.2.3 in client-side mode on FORGE! +[12:47:37] [modloading-worker-0/INFO] [showcaseitem/]: Loading config: /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/showcaseitem-common.toml +[12:47:37] [modloading-worker-0/INFO] [showcaseitem/]: Built config: /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/showcaseitem-common.toml +[12:47:37] [modloading-worker-0/INFO] [showcaseitem/]: Loaded config: /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/showcaseitem-common.toml +[12:47:37] [modloading-worker-0/INFO] [FTB XMod Compat/]: [FTB Quests] Enabled KubeJS integration +[12:47:37] [modloading-worker-0/INFO] [me.tr.be.BetterF3Forge/]: [BetterF3] Starting... +[12:47:37] [modloading-worker-0/INFO] [me.tr.be.BetterF3Forge/]: [BetterF3] Loading... +[12:47:37] [modloading-worker-0/INFO] [de.to.pa.PacketFixer/]: Packet Fixer has been initialized successfully +[12:47:37] [modloading-worker-0/INFO] [YetAnotherConfigLib/]: Deserializing YACLConfig from '/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/yacl.json5' +[12:47:37] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing common components for puzzleslib:main +[12:47:37] [modloading-worker-0/INFO] [me.tr.be.BetterF3Forge/]: [BetterF3] All done! +[12:47:37] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing client components for puzzleslib:main +[12:47:37] [modloading-worker-0/INFO] [Rhino Script Remapper/]: Loading Rhino Minecraft remapper... +[12:47:37] [modloading-worker-0/INFO] [de.la.mo.rh.mo.ut.RhinoProperties/]: Rhino properties loaded. +[12:47:37] [modloading-worker-0/INFO] [Rhino Script Remapper/]: Loading mappings for 1.20.1 +[12:47:37] [modloading-worker-0/WARN] [mixin/]: @Inject(@At("INVOKE")) Shift.BY=2 on create_connected.mixins.json:sequencedgearshift.SequencedGearshiftScreenMixin::handler$cfa000$updateParamsOfRow exceeds the maximum allowed value: 0. Increase the value of maxShiftBy to suppress this warning. +[12:47:37] [modloading-worker-0/INFO] [Rhino Script Remapper/]: Done in 0.090 s +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registered bogey styles from railways +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering data fixers +[12:47:38] [modloading-worker-0/WARN] [Railways/]: Skipping Datafixer Registration due to it being disabled in the config. +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Hex Casting +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Oh The Biomes You'll Go +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Blue Skies +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Twilight Forest +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Biomes O' Plenty +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Nature's Spirit +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Dreams and Desires +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Quark +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for TerraFirmaCraft +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:main_startup_script.js in 0.058 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfc/constants.js in 0.024 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:horornot/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:minecraft/constants.js in 0.004 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:railways/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/machines.js in 0.008 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/material_info.js in 0.002 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/recipe_types.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/blocks.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/items.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:framedblocks/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:firmalife/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:sophisticated_backpacks/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:more_red/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:mega_cells/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:create/constants.js in 0.002 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:ae2/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:create_additions/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:chisel_and_bits/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:extended_ae2/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:asticor_carts/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:firmaciv/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:firmaciv/blocks.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:ftb_quests/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/fluids.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/materials.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/blocks.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/items.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:computer_craft/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded 30/30 KubeJS startup scripts in 0.721 s with 0 errors and 0 warnings +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: example.js#3: TerraFirmaGreg the best modpack in the world :) +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: Loaded script client_scripts:example.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: Loaded script client_scripts:tooltips.js in 0.003 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: Loaded 2/2 KubeJS client scripts in 0.022 s with 0 errors and 0 warnings +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: tfg/blocks.js#48: Loaded Java class 'net.minecraft.world.level.block.AmethystClusterBlock' +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: tfg/blocks.js#49: Loaded Java class 'net.minecraft.world.level.block.Blocks' +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: tfg/blocks.js#50: Loaded Java class 'net.minecraft.world.level.block.state.BlockBehaviour$Properties' +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id kubejs:send_data_from_client +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:send_data_from_server +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:paint +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:add_stage +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:remove_stage +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:sync_stages +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id kubejs:first_click +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:toast +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:reload_startup_scripts +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:display_server_errors +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:display_client_errors +[12:47:38] [Render thread/INFO] [GregTechCEu/]: GTCEu common proxy init! +[12:47:38] [Render thread/INFO] [GregTechCEu/]: Registering material registries +[12:47:38] [Render thread/INFO] [GregTechCEu/]: Registering GTCEu Materials +[12:47:38] [CraftPresence/INFO] [craftpresence/]: Attempting to connect to Discord (1/10)... +[12:47:39] [Render thread/INFO] [GregTechCEu/]: Registering addon Materials +[12:47:39] [Render thread/WARN] [GregTechCEu/]: FluidStorageKey{gtceu:liquid} already has an associated fluid for material gtceu:water +[12:47:39] [Render thread/WARN] [GregTechCEu/]: FluidStorageKey{gtceu:liquid} already has an associated fluid for material gtceu:lava +[12:47:39] [CraftPresence/INFO] [craftpresence/]: Loaded display data with Client Id: 1182610212121743470 (Logged in as RyRy) +[12:47:39] [Render thread/INFO] [GregTechCEu/]: Registering KeyBinds +[12:47:39] [Render thread/WARN] [ne.mi.fm.DeferredWorkQueue/LOADING]: Mod 'gtceu' took 1.043 s to run a deferred task. +[12:47:42] [Render thread/WARN] [ne.mi.re.ForgeRegistry/REGISTRIES]: Registry minecraft:menu: The object net.minecraft.world.inventory.MenuType@67141ef8 has been registered twice for the same name ae2:export_card. +[12:47:42] [Render thread/WARN] [ne.mi.re.ForgeRegistry/REGISTRIES]: Registry minecraft:menu: The object net.minecraft.world.inventory.MenuType@f4864e9 has been registered twice for the same name ae2:insert_card. +[12:47:42] [Render thread/INFO] [Moonlight/]: Initialized block sets in 21ms +[12:47:42] [Render thread/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ae2wtlib:cycle_terminal +[12:47:43] [Render thread/INFO] [Every Compat/]: Registering Compat WoodType Blocks +[12:47:43] [Render thread/INFO] [Every Compat/]: EveryCompat Create Module: registered 42 WoodType blocks +[12:47:43] [Render thread/INFO] [tf.TFCTumbleweed/]: Injecting TFC Tumbleweed override pack +[12:47:43] [Render thread/INFO] [co.ee.fi.FirmaLife/]: Injecting firmalife override pack +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) +[12:47:43] [Render thread/INFO] [Oculus/]: Hardware information: +[12:47:43] [Render thread/INFO] [Oculus/]: CPU: 16x AMD Ryzen 7 3700X 8-Core Processor +[12:47:43] [Render thread/INFO] [Oculus/]: GPU: AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) (Supports OpenGL 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f)) +[12:47:43] [Render thread/INFO] [Oculus/]: OS: Linux (6.6.85) +[12:47:44] [Render thread/WARN] [mixin/]: Method overwrite conflict for isHidden in mixins.oculus.compat.sodium.json:copyEntity.ModelPartMixin, previously written by dev.tr7zw.firstperson.mixins.ModelPartMixin. Skipping method. +[12:47:44] [Render thread/INFO] [minecraft/Minecraft]: [FANCYMENU] Registering resource reload listener.. +[12:47:44] [Render thread/INFO] [de.ke.fa.cu.ScreenCustomization/]: [FANCYMENU] Initializing screen customization engine! Addons should NOT REGISTER TO REGISTRIES anymore now! +[12:47:44] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] Minecraft resource reload: STARTING +[12:47:44] [Render thread/INFO] [ModernFix/]: Invalidating pack caches +[12:47:44] [Render thread/INFO] [minecraft/ReloadableResourceManager]: Reloading ResourceManager: Additional Placements blockstate redirection pack, vanilla, mod_resources, gtceu:dynamic_assets, Moonlight Mods Dynamic Assets, Firmalife-1.20.1-2.1.15.jar:overload, TFCTumbleweed-1.20.1-1.2.2.jar:overload, KubeJS Resource Pack [assets], ldlib +[12:47:44] [Finalizer/WARN] [ModernFix/]: One or more BufferBuilders have been leaked, ModernFix will attempt to correct this. +[12:47:45] [Render thread/INFO] [Every Compat/]: Generated runtime CLIENT_RESOURCES for pack Moonlight Mods Dynamic Assets (everycomp) in: 597 ms +[12:47:45] [Render thread/INFO] [Moonlight/]: Generated runtime CLIENT_RESOURCES for pack Moonlight Mods Dynamic Assets (moonlight) in: 0 ms +[12:47:45] [modloading-worker-0/INFO] [Puzzles Lib/]: Loading client config for pickupnotifier +[12:47:45] [modloading-worker-0/INFO] [Puzzles Lib/]: Loading client config for hangglider +[12:47:45] [Worker-ResourceReload-4/INFO] [minecraft/UnihexProvider]: Found unifont_all_no_pua-15.0.06.hex, loading +[12:47:45] [Worker-ResourceReload-3/INFO] [xa.pa.OpenPartiesAndClaims/]: Loading Open Parties and Claims! +[12:47:45] [Worker-ResourceReload-1/INFO] [co.re.RecipeEssentials/]: recipeessentials mod initialized +[12:47:45] [Worker-ResourceReload-10/INFO] [ne.dr.tf.TerraFirmaCraft/]: TFC Common Setup +[12:47:45] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [saturn] Starting version check at https://github.com/AbdElAziz333/Saturn/raw/mc1.20.1/dev/updates.json +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB Library/]: Setting game stages provider implementation to: KubeJS Stages +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: Chose [KubeJS Stages] as the active game stages implementation +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB Library/]: Setting permissions provider implementation to: FTB Ranks +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: Chose [FTB Ranks] as the active permissions implementation +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: [FTB Quests] recipe helper provider is [JEI] +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: [FTB Quests] Enabled FTB Filter System integration +[12:47:45] [Render thread/INFO] [GregTechCEu/]: GregTech Model loading took 520ms +[12:47:46] [Render thread/INFO] [minecraft/LoadingOverlay]: [DRIPPY LOADING SCREEN] Initializing fonts for text rendering.. +[12:47:46] [Render thread/INFO] [minecraft/LoadingOverlay]: [DRIPPY LOADING SCREEN] Calculating animation sizes for FancyMenu.. +[12:47:46] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] ScreenCustomizationLayer registered: drippy_loading_overlay +[12:47:46] [Render thread/INFO] [de.ke.fa.cu.an.AnimationHandler/]: [FANCYMENU] Preloading animations! This could cause the loading screen to freeze for a while.. +[12:47:46] [Render thread/INFO] [de.ke.fa.cu.an.AnimationHandler/]: [FANCYMENU] Finished preloading animations! +[12:47:46] [Render thread/INFO] [de.ke.fa.FancyMenu/]: [FANCYMENU] Starting late client initialization phase.. +[12:47:46] [Forge Version Check/WARN] [ne.mi.fm.VersionChecker/]: Failed to process update information +com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 9 column 1 path $ + at com.google.gson.Gson.fromJson(Gson.java:1226) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1124) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1034) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:969) ~[gson-2.10.jar%2388!/:?] {} + at net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:183) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} + at java.lang.Iterable.forEach(Iterable.java:75) ~[?:?] {re:mixin} + at net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} +Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 9 column 1 path $ + at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:393) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:182) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:144) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1214) ~[gson-2.10.jar%2388!/:?] {} + ... 6 more +[12:47:46] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [controlling] Starting version check at https://updates.blamejared.com/get?n=controlling&gv=1.20.1 +[12:47:46] [Worker-ResourceReload-6/ERROR] [minecraft/SimpleJsonResourceReloadListener]: Couldn't parse data file tfc:field_guide/ru_ru/entries/tfg_ores/surface_copper from tfc:patchouli_books/field_guide/ru_ru/entries/tfg_ores/surface_copper.json +com.google.gson.JsonParseException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 55 column 6 path $.pages[5] + at net.minecraft.util.GsonHelper.m_13780_(GsonHelper.java:526) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + at net.minecraft.util.GsonHelper.m_263475_(GsonHelper.java:531) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + at net.minecraft.util.GsonHelper.m_13776_(GsonHelper.java:581) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + at net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener.m_278771_(SimpleJsonResourceReloadListener.java:41) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,re:classloading} + at net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener.m_5944_(SimpleJsonResourceReloadListener.java:29) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,re:classloading} + at net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener.m_5944_(SimpleJsonResourceReloadListener.java:17) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,re:classloading} + at net.minecraft.server.packs.resources.SimplePreparableReloadListener.m_10786_(SimplePreparableReloadListener.java:11) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:moonlight.mixins.json:ConditionHackMixin,pl:mixin:A} + at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) ~[?:?] {} + at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760) ~[?:?] {} + at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] {} + at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) ~[?:?] {} + at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] {re:mixin} +Caused by: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 55 column 6 path $.pages[5] + at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1657) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.stream.JsonReader.checkLenient(JsonReader.java:1463) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:569) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:422) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.TypeAdapters$28.read(TypeAdapters.java:779) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.TypeAdapters$28.read(TypeAdapters.java:725) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.TypeAdapters$34$1.read(TypeAdapters.java:1007) ~[gson-2.10.jar%2388!/:?] {} + at net.minecraft.util.GsonHelper.m_13780_(GsonHelper.java:524) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + ... 13 more +[12:47:46] [Render thread/INFO] [de.ke.fa.ut.wi.WindowHandler/]: [FANCYMENU] Custom window icon successfully updated! +[12:47:46] [Render thread/INFO] [KubeJS Client/]: Client resource reload complete! +[12:47:46] [Render thread/INFO] [defaultoptions/]: Loaded default options for keymappings +[12:47:46] [Render thread/INFO] [de.ke.fa.ut.wi.WindowHandler/]: [FANCYMENU] Custom window icon successfully updated! +[12:47:46] [Worker-Main-6/INFO] [minecraft/UnihexProvider]: Found unifont_all_no_pua-15.0.06.hex, loading +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [controlling] Found status: BETA Current: 12.0.2 Target: 12.0.2 +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [uteamcore] Starting version check at https://api.u-team.info/update/uteamcore.json +[12:47:47] [FTB Backups Config Watcher 0/INFO] [ne.cr.ft.FTBBackups/]: Config at /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/ftbbackups2.json has changed, reloaded! +[12:47:47] [Worker-ResourceReload-14/WARN] [minecraft/SpriteLoader]: Texture create_connected:block/fluid_container_window_debug with size 40x32 limits mip level from 4 to 3 +[12:47:47] [UniLib/INFO] [unilib/]: Received update status for "unilib" -> Outdated (Target version: "v1.0.5") +[12:47:47] [UniLib/INFO] [unilib/]: Received update status for "craftpresence" -> Outdated (Target version: "v2.5.4") +[12:47:47] [Render thread/INFO] [Every Compat/]: Registered 42 compat blocks making up 0.31% of total blocks registered +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [uteamcore] Found status: OUTDATED Current: 5.1.4.312 Target: 5.1.4.346 +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [pickupnotifier] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/pickupnotifier.json +[12:47:47] [Render thread/INFO] [Moonlight/]: Initialized color sets in 104ms +[12:47:47] [Render thread/INFO] [co.no.ku.KubeJSTFC/]: KubeJS TFC configuration: +[12:47:47] [Render thread/INFO] [co.no.ku.KubeJSTFC/]: Debug mode enabled: false +[12:47:47] [Render thread/INFO] [MEGA Cells/]: Initialised AE2WT integration. +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [pickupnotifier] Found status: UP_TO_DATE Current: 8.0.0 Target: null +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [corpse] Starting version check at https://update.maxhenkel.de/forge/corpse +[12:47:47] [Worker-ResourceReload-0/INFO] [FirstPersonModel/]: Loading FirstPerson Mod +[12:47:47] [Worker-ResourceReload-4/INFO] [xa.ma.WorldMap/]: Loading Xaero's World Map - Stage 1/2 +[12:47:47] [Placebo Patreon Trail Loader/INFO] [placebo/]: Loading patreon trails data... +[12:47:47] [Placebo Patreon Wing Loader/INFO] [placebo/]: Loading patreon wing data... +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ke.ko.Konkrete/]: [KONKRETE] Client-side libs ready to use! +[12:47:47] [Placebo Patreon Trail Loader/INFO] [placebo/]: Loaded 45 patreon trails. +[12:47:47] [Placebo Patreon Wing Loader/INFO] [placebo/]: Loaded 21 patreon wings. +[12:47:47] [Worker-ResourceReload-7/INFO] [EMI/]: [EMI] Discovered Sodium +[12:47:47] [Worker-ResourceReload-14/INFO] [xa.mi.XaeroMinimap/]: Loading Xaero's Minimap - Stage 1/2 +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ae2wtlib:update_wut +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ae2wtlib:update_restock +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ae2wtlib:restock_amounts +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [corpse] Found status: OUTDATED Current: 1.20.1-1.0.19 Target: 1.20.1-1.0.20 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [create_connected] Starting version check at https://raw.githubusercontent.com/hlysine/create_connected/main/update.json +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [create_connected] Found status: OUTDATED Current: 0.8.2-mc1.20.1 Target: 1.0.1-mc1.20.1 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [blur] Starting version check at https://api.modrinth.com/updates/rubidium-extra/forge_updates.json +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [blur] Found status: AHEAD Current: 3.1.1 Target: null +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [hangglider] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/hangglider.json +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [hangglider] Found status: UP_TO_DATE Current: 8.0.1 Target: null +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [searchables] Starting version check at https://updates.blamejared.com/get?n=searchables&gv=1.20.1 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [searchables] Found status: BETA Current: 1.0.3 Target: 1.0.3 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [computercraft] Starting version check at https://api.modrinth.com/updates/cc-tweaked/forge_updates.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [computercraft] Found status: OUTDATED Current: 1.113.1 Target: 1.115.1 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [unilib] Starting version check at https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/UniLib/update.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [unilib] Found status: AHEAD Current: 1.0.2 Target: null +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [craftpresence] Starting version check at https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/CraftPresence/update.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [craftpresence] Found status: AHEAD Current: 2.5.0 Target: null +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [radium] Starting version check at https://api.modrinth.com/updates/radium/forge_updates.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [radium] Found status: OUTDATED Current: 0.12.3+git.50c5c33 Target: 0.12.4 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [attributefix] Starting version check at https://updates.blamejared.com/get?n=attributefix&gv=1.20.1 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [attributefix] Found status: BETA Current: 21.0.4 Target: 21.0.4 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [clumps] Starting version check at https://updates.blamejared.com/get?n=clumps&gv=1.20.1 +[12:47:49] [Worker-ResourceReload-4/WARN] [xa.hu.mi.MinimapLogs/]: io exception while checking patreon: Online mod data expired! Date: Thu Apr 17 01:03:51 MST 2025 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [clumps] Found status: BETA Current: 12.0.0.4 Target: 12.0.0.4 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [catalogue] Starting version check at https://mrcrayfish.com/modupdatejson?id=catalogue +[12:47:50] [Forge Version Check/WARN] [ne.mi.fm.VersionChecker/]: Failed to process update information +com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ + at com.google.gson.Gson.fromJson(Gson.java:1226) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1124) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1034) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:969) ~[gson-2.10.jar%2388!/:?] {} + at net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:183) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} + at java.lang.Iterable.forEach(Iterable.java:75) ~[?:?] {re:mixin} + at net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} +Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ + at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:393) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:182) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:144) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1214) ~[gson-2.10.jar%2388!/:?] {} + ... 6 more +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzlesaccessapi] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/puzzlesaccessapi.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzlesaccessapi] Found status: BETA Current: 8.0.7 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [forge] Starting version check at https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json +[12:47:50] [Worker-ResourceReload-5/WARN] [minecraft/ModelBakery]: tfcambiental:snowshoes#inventory +java.io.FileNotFoundException: tfcambiental:models/item/snowshoes.json + at net.minecraft.client.resources.model.ModelBakery.m_119364_(ModelBakery.java:417) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119362_(ModelBakery.java:266) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119341_(ModelBakery.java:243) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119306_(ModelBakery.java:384) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.(ModelBakery.java:150) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) ~[?:?] {re:mixin} + at java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) ~[?:?] {} + at java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) ~[?:?] {} + at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] {} + at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) ~[?:?] {} + at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] {re:mixin} +[12:47:50] [Worker-ResourceReload-5/WARN] [minecraft/ModelBakery]: carpeted:block/label +java.io.FileNotFoundException: carpeted:models/block/label.json + at net.minecraft.client.resources.model.ModelBakery.m_119364_(ModelBakery.java:417) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119362_(ModelBakery.java:262) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119341_(ModelBakery.java:243) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.(ModelBakery.java:159) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) ~[?:?] {re:mixin} + at java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) ~[?:?] {} + at java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) ~[?:?] {} + at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] {} + at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) ~[?:?] {} + at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] {re:mixin} +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [forge] Found status: OUTDATED Current: 47.2.6 Target: 47.4.0 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [moonlight] Starting version check at https://raw.githubusercontent.com/MehVahdJukaar/Moonlight/multi-loader/forge/update.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [moonlight] Found status: BETA Current: 1.20-2.13.51 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [configuration] Starting version check at https://raw.githubusercontent.com/Toma1O6/UpdateSchemas/master/configuration-forge.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [configuration] Found status: OUTDATED Current: 2.2.0 Target: 2.2.1 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [smoothboot] Starting version check at https://github.com/AbdElAziz333/SmoothBoot-Reloaded/raw/mc1.20.1/dev/updates.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [smoothboot] Found status: UP_TO_DATE Current: 0.0.4 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [ksyxis] Starting version check at https://raw.githubusercontent.com/VidTu/Ksyxis/main/updater_ksyxis_forge.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [ksyxis] Found status: OUTDATED Current: 1.3.2 Target: 1.3.3 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [flywheel] Starting version check at https://api.modrinth.com/updates/flywheel/forge_updates.json +[12:47:50] [Worker-ResourceReload-4/ERROR] [xa.ma.WorldMap/]: io exception while checking versions: Online mod data expired! Date: Thu Apr 17 01:03:51 MST 2025 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [flywheel] Found status: BETA Current: 0.6.10-7 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [inventoryhud] Starting version check at https://raw.githubusercontent.com/DmitryLovin/pluginUpdate/master/invupdate.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [inventoryhud] Found status: UP_TO_DATE Current: 3.4.26 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzleslib] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/puzzleslib.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzleslib] Found status: OUTDATED Current: 8.1.23 Target: 8.1.32 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [betterf3] Starting version check at https://api.modrinth.com/updates/betterf3/forge_updates.json +[12:47:50] [Render thread/INFO] [xa.mi.XaeroMinimap/]: Loading Xaero's Minimap - Stage 2/2 +[12:47:51] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [betterf3] Found status: UP_TO_DATE Current: 7.0.2 Target: null +[12:47:51] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [packetfixer] Starting version check at https://api.modrinth.com/updates/packet-fixer/forge_updates.json +[12:47:51] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [packetfixer] Found status: OUTDATED Current: 1.4.2 Target: 2.0.0 +[12:47:51] [Render thread/WARN] [xa.hu.mi.MinimapLogs/]: io exception while checking versions: Online mod data expired! Date: Thu Apr 17 01:03:51 MST 2025 +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Registered player tracker system: minimap_synced +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: World Map found! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Registered player tracker system: openpartiesandclaims +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: Open Parties And Claims found! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: No Optifine! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: No Vivecraft! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: Framed Blocks found! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: Iris found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Loading Xaero's World Map - Stage 2/2 +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: New world map region cache hash code: -815523079 +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Registered player tracker system: map_synced +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's WorldMap Mod: Xaero's minimap found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Registered player tracker system: minimap_synced +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Registered player tracker system: openpartiesandclaims +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's WorldMap Mod: Open Parties And Claims found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: No Optifine! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's World Map: No Vivecraft! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's World Map: Framed Blocks found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's World Map: Iris found! +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model firmaciv:firmaciv_compass#inventory: + minecraft:textures/atlas/blocks.png:minecraft:item/compass +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model gtceu:tin_double_ingot#inventory: + minecraft:textures/atlas/blocks.png:gtceu:item/material_sets/dull/ingot_double_overlay +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model createaddition:small_light_connector#facing=west,mode=push,powered=true,rotation=x_clockwise_180,variant=default: + minecraft:textures/atlas/blocks.png:create:block/chute_block +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model gtceu:copper_double_ingot#inventory: + minecraft:textures/atlas/blocks.png:gtceu:item/material_sets/dull/ingot_double_overlay +Reloading Dynamic Lights +[12:47:57] [Render thread/INFO] [co.jo.fl.ba.Backend/]: Loaded all shader sources. +Create Crafts & Additions Initialized! +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Loaded values for 19 compatible attributes. +[12:47:57] [Worker-ResourceReload-2/ERROR] [AttributeFix/]: Attribute ID 'minecolonies:mc_mob_damage' does not belong to a known attribute. This entry will be ignored. +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Loaded 20 values from config. +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Saving config file. 20 entries. +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Applying changes for 20 attributes. +[12:47:57] [Worker-ResourceReload-11/INFO] [de.me.as.AstikorCarts/]: Automatic pull animal configuration: +pull_animals = [ + "minecraft:camel", + "minecraft:donkey", + "minecraft:horse", + "minecraft:mule", + "minecraft:skeleton_horse", + "minecraft:zombie_horse", + "minecraft:player", + "tfc:donkey", + "tfc:mule", + "tfc:horse" + ] +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at io.github.mortuusars.exposure.integration.jade.ExposureJadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at de.maxhenkel.corpse.integration.waila.PluginCorpse +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at xfacthd.framedblocks.common.compat.jade.FramedJadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.general.GeneralPlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.create.CreatePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at me.pandamods.fallingtrees.compat.JadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.gregtechceu.gtceu.integration.jade.GTJadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at net.dries007.tfc.compat.jade.JadeIntegration +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.vanilla.VanillaPlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.universal.UniversalPlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.core.CorePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at appeng.integration.modules.jade.JadeModule +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.glodblock.github.extendedae.xmod.jade.JadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at cy.jdkdigital.treetap.compat.jade.JadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.eerussianguy.firmalife.compat.tooltip.JadeIntegration +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.ljuangbminecraft.tfcchannelcasting.compat.JadeIntegration +[12:47:59] [Render thread/WARN] [ne.mi.fm.DeferredWorkQueue/LOADING]: Mod 'create_connected' took 1.342 s to run a deferred task. +[12:47:59] [Render thread/INFO] [defaultoptions/]: Loaded default options for keymappings +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) +[12:47:59] [Render thread/INFO] [mojang/Library]: OpenAL initialized on device Starship/Matisse HD Audio Controller Analog Stereo +[12:47:59] [Render thread/INFO] [minecraft/SoundEngine]: Sound engine started +[12:47:59] [Render thread/INFO] [minecraft/SoundEngine]: [FANCYMENU] Reloading AudioResourceHandler after Minecraft SoundEngine reload.. +[12:47:59] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 4096x2048x4 minecraft:textures/atlas/blocks.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 1024x512x4 minecraft:textures/atlas/signs.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 2048x1024x4 minecraft:textures/atlas/armor_trims.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 1024x1024x4 minecraft:textures/atlas/chest.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 128x64x4 minecraft:textures/atlas/decorated_pot.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh particle. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_solid. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader fsh rendertype_solid. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_cutout_mipped. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader fsh rendertype_cutout_mipped. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader fsh rendertype_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_translucent. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_armor_cutout_no_cull. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_solid. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_cutout_no_cull. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_cutout_no_cull_z_offset. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_translucent_cull. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_translucent. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader rendertype_entity_translucent_emissive could not find sampler named Sampler2 in the specified shader program. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_smooth_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_decal. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_no_outline. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader moonlight:text_alpha_color could not find sampler named Sampler2 in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader moonlight:text_alpha_color could not find uniform named IViewRotMat in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader moonlight:text_alpha_color could not find uniform named FogShape in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader shimmer:rendertype_armor_cutout_no_cull could not find sampler named Sampler2 in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader shimmer:rendertype_armor_cutout_no_cull could not find uniform named Light0_Direction in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader shimmer:rendertype_armor_cutout_no_cull could not find uniform named Light1_Direction in the specified shader program. +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 2048x1024x0 minecraft:textures/atlas/particles.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas +[12:48:00] [Render thread/INFO] [xa.ma.WorldMap/]: Successfully reloaded the world map shaders! +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Loading exposure filters: +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:light_blue_pane, exposure:shaders/light_blue_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:orange_pane, exposure:shaders/orange_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:red_pane, exposure:shaders/red_filter.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:purple_pane, exposure:shaders/purple_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:blue_pane, exposure:shaders/blue_filter.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:light_gray_pane, exposure:shaders/light_gray_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:magenta_pane, exposure:shaders/magenta_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:gray_pane, exposure:shaders/gray_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:lime_pane, exposure:shaders/lime_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:green_pane, exposure:shaders/green_filter.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:pink_pane, exposure:shaders/pink_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:yellow_pane, exposure:shaders/yellow_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:white_pane, exposure:shaders/white_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:brown_pane, exposure:shaders/brown_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:glass_pane, exposure:shaders/crisp.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:interplanar_projector, exposure:shaders/invert.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:black_pane, exposure:shaders/black_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:cyan_pane, exposure:shaders/cyan_tint.json] added. +[12:48:00] [Render thread/INFO] [patchouli/]: BookContentResourceListenerLoader preloaded 1073 jsons +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 128x128x0 computercraft:textures/atlas/gui.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x256x0 polylib:textures/atlas/gui.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x128x0 jei:textures/atlas/gui.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x256x0 moonlight:textures/atlas/map_markers.png-atlas +[12:48:00] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Successfully reloaded the minimap shaders! +[12:48:00] [Render thread/INFO] [Shimmer/]: buildIn shimmer configuration is enabled, this can be disabled by config file +[12:48:00] [Render thread/INFO] [Shimmer/]: mod jar and resource pack discovery: file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_iron_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_iron_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/INFO] [de.ke.fa.ut.re.ResourceHandlers/]: [FANCYMENU] Reloading resources.. +[12:48:00] [Render thread/INFO] [de.ke.fa.ut.re.pr.ResourcePreLoader/]: [FANCYMENU] Pre-loading resources.. +[12:48:00] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] Updating animation sizes.. +[12:48:00] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] Minecraft resource reload: FINISHED +[12:48:00] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] ScreenCustomizationLayer registered: title_screen +[12:48:00] [Render thread/INFO] [Oculus/]: Creating pipeline for dimension NamespacedId{namespace='minecraft', name='overworld'} +[12:48:01] [Render thread/INFO] [ambientsounds/]: Loaded AmbientEngine 'basic' v3.1.0. 11 dimension(s), 11 features, 11 blockgroups, 2 sound collections, 37 regions, 58 sounds, 11 sound categories, 5 solids and 2 biome types +[12:48:01] [Render thread/INFO] [FirstPersonModel/]: PlayerAnimator not found! +[12:48:01] [Render thread/INFO] [FirstPersonModel/]: Loaded Vanilla Hands items: [] +[12:48:01] [Render thread/INFO] [FirstPersonModel/]: Loaded Auto Disable items: [camera] +[12:48:02] [Render thread/WARN] [ModernFix/]: Game took 40.304 seconds to start diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log new file mode 100644 index 000000000..51e5ec546 --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log @@ -0,0 +1,2854 @@ +Checking: MC_SLIM +Checking: MERGED_MAPPINGS +Checking: MAPPINGS +Checking: MC_EXTRA +Checking: MOJMAPS +Checking: PATCHED +Checking: MC_SRG + + , --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --launchTarget, forgeclient, --fml.forgeVersion, 47.2.6, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --width, 854, --height, 480]]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , --username, Ryexandrite, --assetIndex, 5, --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --width, 854, --height, 480]]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Outdated (Target version: "v2.5.4")]]> + +[Mouse Tweaks] Main.initialize() +[Mouse Tweaks] Initialized. + + + + + Outdated (Target version: "v1.0.5")]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (ModelBakery.java:150) + at TRANSFORMER/minecraft@1.20.1/net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) + at java.base/java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) + at java.base/java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) + at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) + at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) + at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) + at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) + at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) + at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) +]]> + + + + (ModelBakery.java:159) + at TRANSFORMER/minecraft@1.20.1/net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) + at java.base/java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) + at java.base/java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) + at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) + at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) + at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) + at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) + at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) + at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Reloading Dynamic Lights + + + +Create Crafts & Additions Initialized! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/testdata/TestLogs/vanilla-1.21.5.text.log b/tests/testdata/TestLogs/vanilla-1.21.5.text.log new file mode 100644 index 000000000..e78702858 --- /dev/null +++ b/tests/testdata/TestLogs/vanilla-1.21.5.text.log @@ -0,0 +1,25 @@ +[12:50:56] [Datafixer Bootstrap/INFO]: 263 Datafixer optimizations took 908 milliseconds +[12:50:58] [Render thread/INFO]: Environment: Environment[sessionHost=https://sessionserver.mojang.com, servicesHost=https://api.minecraftservices.com, name=PROD] +[12:50:58] [Render thread/INFO]: Setting user: Ryexandrite +[12:50:58] [Render thread/INFO]: Backend library: LWJGL version 3.3.3+5 +[12:50:58] [Render thread/INFO]: Using optional rendering extensions: GL_KHR_debug, GL_ARB_vertex_attrib_binding, GL_ARB_direct_state_access +[12:50:58] [Render thread/INFO]: Reloading ResourceManager: vanilla +[12:50:59] [Worker-Main-6/INFO]: Found unifont_all_no_pua-16.0.01.hex, loading +[12:50:59] [Worker-Main-7/INFO]: Found unifont_jp_patch-16.0.01.hex, loading +[12:50:59] [Render thread/WARN]: minecraft:pipeline/entity_translucent_emissive shader program does not use sampler Sampler2 defined in the pipeline. This might be a bug. +[12:50:59] [Render thread/INFO]: OpenAL initialized on device Starship/Matisse HD Audio Controller Analog Stereo +[12:50:59] [Render thread/INFO]: Sound engine started +[12:50:59] [Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/blocks.png-atlas +[12:50:59] [Render thread/INFO]: Created: 256x256x4 minecraft:textures/atlas/signs.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas +[12:50:59] [Render thread/INFO]: Created: 2048x1024x4 minecraft:textures/atlas/armor_trims.png-atlas +[12:50:59] [Render thread/INFO]: Created: 256x256x4 minecraft:textures/atlas/chest.png-atlas +[12:50:59] [Render thread/INFO]: Created: 128x64x4 minecraft:textures/atlas/decorated_pot.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas +[12:50:59] [Render thread/INFO]: Created: 64x64x0 minecraft:textures/atlas/map_decorations.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x256x0 minecraft:textures/atlas/particles.png-atlas +[12:51:00] [Render thread/INFO]: Created: 512x256x0 minecraft:textures/atlas/paintings.png-atlas +[12:51:00] [Render thread/INFO]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas +[12:51:00] [Render thread/INFO]: Created: 1024x512x0 minecraft:textures/atlas/gui.png-atlas diff --git a/tests/testdata/TestLogs/vanilla-1.21.5.xml.log b/tests/testdata/TestLogs/vanilla-1.21.5.xml.log new file mode 100644 index 000000000..24bfe0b7f --- /dev/null +++ b/tests/testdata/TestLogs/vanilla-1.21.5.xml.log @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 21570a03fbb2973712f1ffe5f1bb4d6095e53e4c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:22:39 -0700 Subject: [PATCH 156/181] feat(xml-logs): finish tests Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/BaseInstance.h | 3 - launcher/MessageLevel.cpp | 17 +- launcher/launch/LaunchTask.cpp | 12 +- launcher/launch/LogModel.cpp | 8 + launcher/launch/LogModel.h | 2 + launcher/logs/LogParser.cpp | 50 +- launcher/logs/LogParser.h | 5 +- launcher/minecraft/MinecraftInstance.cpp | 43 - launcher/minecraft/MinecraftInstance.h | 3 - launcher/ui/pages/instance/OtherLogsPage.cpp | 7 +- tests/CMakeLists.txt | 4 + tests/XmlLogs_test.cpp | 104 +- .../TestLogs/TerraFirmaGreg-Modern-levels.txt | 945 ++++++++++++++++++ .../TerraFirmaGreg-Modern-xml-levels.txt | 869 ++++++++++++++++ .../TestLogs/vanilla-1.21.5-levels.txt | 25 + 15 files changed, 2006 insertions(+), 91 deletions(-) create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt create mode 100644 tests/testdata/TestLogs/vanilla-1.21.5-levels.txt diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 2a2b4dc4a..99ce49a62 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -151,9 +151,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this -#include #include #include #include @@ -237,9 +236,9 @@ bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level) model.append(MessageLevel::Error, tr("[Log4j Parse Error] Failed to parse log4j log event: %1").arg(err.value().errMessage)); return false; } else { - if (items.has_value()) { + if (!items.isEmpty()) { auto& model = *getLogModel(); - for (auto const& item : items.value()) { + for (auto const& item : items) { if (std::holds_alternative(item)) { auto entry = std::get(item); auto msg = QString("[%1] [%2/%3] [%4]: %5") @@ -252,7 +251,7 @@ bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level) model.append(entry.level, msg); } else if (std::holds_alternative(item)) { auto msg = std::get(item).message; - level = m_instance->guessLevel(msg, level); + level = LogParser::guessLevel(msg, model.previousLevel()); msg = censorPrivateInfo(msg); model.append(level, msg); } @@ -281,15 +280,16 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) level = innerLevel; } + auto& model = *getLogModel(); + // If the level is still undetermined, guess level if (level == MessageLevel::Unknown) { - level = m_instance->guessLevel(line, level); + level = LogParser::guessLevel(line, model.previousLevel()); } // censor private user info line = censorPrivateInfo(line); - auto& model = *getLogModel(); model.append(level, line); } diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 45aac6099..5d32be9a2 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -166,3 +166,11 @@ bool LogModel::isOverFlow() { return m_numLines >= m_maxLines && m_stopOnOverflow; } + + +MessageLevel::Enum LogModel::previousLevel() { + if (!m_content.isEmpty()) { + return m_content.last().level; + } + return MessageLevel::Unknown; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index ba7b14487..f4664c47c 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -31,6 +31,8 @@ class LogModel : public QAbstractListModel { void setColorLines(bool state); bool colorLines() const; + MessageLevel::Enum previousLevel(); + enum Roles { LevelRole = Qt::UserRole }; private /* types */: diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 294036134..c5d2a8647 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -19,6 +19,9 @@ #include "LogParser.h" +#include +#include "MessageLevel.h" + void LogParser::appendLine(QAnyStringView data) { if (!m_partialData.isEmpty()) { @@ -202,7 +205,7 @@ std::optional LogParser::parseNext() } } -std::optional> LogParser::parseAvailable() +QList LogParser::parseAvailable() { QList items; bool doNext = true; @@ -320,3 +323,48 @@ std::optional LogParser::parseLog4J() throw std::runtime_error("unreachable: already verified this was a complete log4j:Event"); } + +MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum level) +{ + static const QRegularExpression LINE_WITH_LEVEL("^\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + auto match = LINE_WITH_LEVEL.match(line); + if (match.hasMatch()) { + // New style logs from log4j + QString timestamp = match.captured("timestamp"); + QString levelStr = match.captured("level"); + if (levelStr == "INFO") + level = MessageLevel::Info; + if (levelStr == "WARN") + level = MessageLevel::Warning; + if (levelStr == "ERROR") + level = MessageLevel::Error; + if (levelStr == "FATAL") + level = MessageLevel::Fatal; + if (levelStr == "TRACE" || levelStr == "DEBUG") + level = MessageLevel::Debug; + } else { + // Old style forge logs + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || + line.contains("[FINEST]")) + level = MessageLevel::Info; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + } + if (level != MessageLevel::Unknown) + return level; + + if (line.contains("overwriting existing")) + return MessageLevel::Fatal; + + // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * + // static const QRegularExpression JAVA_EXCEPTION( + // R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); + // + // if (line.contains(JAVA_EXCEPTION)) + // return MessageLevel::Error; + return MessageLevel::Info; +} diff --git a/launcher/logs/LogParser.h b/launcher/logs/LogParser.h index 462ea43cf..8b23754ac 100644 --- a/launcher/logs/LogParser.h +++ b/launcher/logs/LogParser.h @@ -55,9 +55,12 @@ class LogParser { void appendLine(QAnyStringView data); std::optional parseNext(); - std::optional> parseAvailable(); + QList parseAvailable(); std::optional getError(); + /// guess log level from a line of game log + static MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level); + protected: MessageLevel::Enum parseLogLevel(const QString& level); std::optional parseAttributes(); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ec136ede0..e8db24b10 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1004,49 +1004,6 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess return filter; } -MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level) -{ - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - - // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QRegularExpression JAVA_EXCEPTION( - R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); - - if (line.contains(JAVA_EXCEPTION)) - return MessageLevel::Error; - - static const QRegularExpression LINE_WITH_LEVEL("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); - auto match = LINE_WITH_LEVEL.match(line); - if (match.hasMatch()) { - // New style logs from log4j - QString timestamp = match.captured("timestamp"); - QString levelStr = match.captured("level"); - if (levelStr == "INFO") - level = MessageLevel::Message; - if (levelStr == "WARN") - level = MessageLevel::Warning; - if (levelStr == "ERROR") - level = MessageLevel::Error; - if (levelStr == "FATAL") - level = MessageLevel::Fatal; - if (levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; - } else { - // Old style forge logs - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || - line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - } - return level; -} - IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() { auto combined = std::make_shared(); diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 5d9bb45ef..cd5cd1ddc 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -139,9 +139,6 @@ class MinecraftInstance : public BaseInstance { QProcessEnvironment createEnvironment() override; QProcessEnvironment createLaunchEnvironment() override; - /// guess log level from a line of minecraft log - MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level) override; - IPathMatcher::Ptr getLogFileMatcher() override; QString getLogFileRoot() override; diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 0aeb942a8..8b8c64c6d 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -179,7 +179,9 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - auto handleLine = [this](QString line) { + MessageLevel::Enum last = MessageLevel::Unknown; + + auto handleLine = [this, &last](QString line) { if (line.isEmpty()) return false; if (line.back() == '\n') @@ -194,9 +196,10 @@ void OtherLogsPage::on_btnReload_clicked() // If the level is still undetermined, guess level if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { - level = m_instance->guessLevel(line, level); + level = LogParser::guessLevel(line, last); } + last = level; m_model->append(level, line); return m_model->isOverFlow(); }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2dedb47cc..31b887ff1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -62,3 +62,7 @@ ecm_add_test(MetaComponentParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VE ecm_add_test(CatPack_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME CatPack) + + +ecm_add_test(XmlLogs_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME XmlLogs) diff --git a/tests/XmlLogs_test.cpp b/tests/XmlLogs_test.cpp index 072448c4e..e01238570 100644 --- a/tests/XmlLogs_test.cpp +++ b/tests/XmlLogs_test.cpp @@ -23,9 +23,11 @@ #include #include +#include #include -#include +#include +#include #include #include @@ -42,16 +44,62 @@ class XmlLogParseTest : public QObject { QString shortXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.xml.log"))); QString shortText = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.text.log"))); + QStringList shortTextLevels_s = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5-levels.txt"))) + .split(QRegularExpression("\n|\r\n|\r"), Qt::SkipEmptyParts); + + QList shortTextLevels; + shortTextLevels.reserve(24); + std::transform(shortTextLevels_s.cbegin(), shortTextLevels_s.cend(), std::back_inserter(shortTextLevels), + [](const QString& line) { return MessageLevel::getLevel(line.trimmed()); }); QString longXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.xml.log"))); QString longText = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.text.log"))); - QTest::addColumn("text"); - QTest::addColumn("xml"); - QTest::newRow("short-vanilla") << shortText << shortXml; - QTest::newRow("long-forge") << longText << longXml; + QStringList longTextLevels_s = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-levels.txt"))) + .split(QRegularExpression("\n|\r\n|\r"), Qt::SkipEmptyParts); + QStringList longTextLevelsXml_s = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-xml-levels.txt"))) + .split(QRegularExpression("\n|\r\n|\r"), Qt::SkipEmptyParts); + + QList longTextLevelsPlain; + longTextLevelsPlain.reserve(974); + std::transform(longTextLevels_s.cbegin(), longTextLevels_s.cend(), std::back_inserter(longTextLevelsPlain), + [](const QString& line) { return MessageLevel::getLevel(line.trimmed()); }); + QList longTextLevelsXml; + longTextLevelsXml.reserve(896); + std::transform(longTextLevelsXml_s.cbegin(), longTextLevelsXml_s.cend(), std::back_inserter(longTextLevelsXml), + [](const QString& line) { return MessageLevel::getLevel(line.trimmed()); }); + + QTest::addColumn("log"); + QTest::addColumn("num_entries"); + QTest::addColumn>("entry_levels"); + + QTest::newRow("short-vanilla-plain") << shortText << 25 << shortTextLevels; + QTest::newRow("short-vanilla-xml") << shortXml << 25 << shortTextLevels; + QTest::newRow("long-forge-plain") << longText << 945 << longTextLevelsPlain; + QTest::newRow("long-forge-xml") << longXml << 869 << longTextLevelsXml; } - void parseXml() { QFETCH(QString, ) } + void parseXml() + { + QFETCH(QString, log); + QFETCH(int, num_entries); + QFETCH(QList, entry_levels); + + QList> entries = {}; + + QBENCHMARK + { + entries = parseLines(log.split(QRegularExpression("\n|\r\n|\r"))); + } + + QCOMPARE(entries.length(), num_entries); + + QList levels = {}; + + std::transform(entries.cbegin(), entries.cend(), std::back_inserter(levels), + [](std::pair entry) { return entry.first; }); + + QCOMPARE(levels, entry_levels); + } private: LogParser m_parser; @@ -59,27 +107,35 @@ class XmlLogParseTest : public QObject { QList> parseLines(const QStringList& lines) { QList> out; - for (const auto& line : lines) + MessageLevel::Enum last = MessageLevel::Unknown; + + for (const auto& line : lines) { m_parser.appendLine(line); - auto items = m_parser.parseAvailable(); - for (const auto& item : items) { - if (std::holds_alternative(item)) { - auto entry = std::get(item); - auto msg = QString("[%1] [%2/%3] [%4]: %5") - .arg(entry.timestamp.toString("HH:mm:ss")) - .arg(entry.thread) - .arg(entry.levelText) - .arg(entry.logger) - .arg(entry.message); - msg = censorPrivateInfo(msg); - out.append(std::make_pair(entry.level, msg)); - } else if (std::holds_alternative(item)) { - auto msg = std::get(item).message; - level = m_instance->guessLevel(msg, level); - msg = censorPrivateInfo(msg); - out.append(std::make_pair(entry.level, msg)); + + auto items = m_parser.parseAvailable(); + for (const auto& item : items) { + if (std::holds_alternative(item)) { + auto entry = std::get(item); + auto msg = QString("[%1] [%2/%3] [%4]: %5") + .arg(entry.timestamp.toString("HH:mm:ss")) + .arg(entry.thread) + .arg(entry.levelText) + .arg(entry.logger) + .arg(entry.message); + out.append(std::make_pair(entry.level, msg)); + last = entry.level; + } else if (std::holds_alternative(item)) { + auto msg = std::get(item).message; + auto level = LogParser::guessLevel(msg, last); + out.append(std::make_pair(level, msg)); + last = level; + } } } return out; } }; + +QTEST_GUILESS_MAIN(XmlLogParseTest) + +#include "XmlLogs_test.moc" diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt new file mode 100644 index 000000000..1b3002117 --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt @@ -0,0 +1,945 @@ +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +INFO +ERROR +INFO +ERROR +ERROR +ERROR +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +INFO +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +WARN +WARN +WARN +INFO +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +ERROR +ERROR +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt new file mode 100644 index 000000000..941e5e3fe --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt @@ -0,0 +1,869 @@ +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +INFO +ERROR +ERROR +ERROR +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +INFO +WARN +WARN +WARN +WARN +WARN +INFO +WARN +WARN +INFO +INFO +WARN +WARN +WARN +INFO +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +ERROR +ERROR +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO diff --git a/tests/testdata/TestLogs/vanilla-1.21.5-levels.txt b/tests/testdata/TestLogs/vanilla-1.21.5-levels.txt new file mode 100644 index 000000000..02734e56f --- /dev/null +++ b/tests/testdata/TestLogs/vanilla-1.21.5-levels.txt @@ -0,0 +1,25 @@ +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO From 266031df816b432589bdb9e2905ce143cc0d9715 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 10:45:12 +0100 Subject: [PATCH 157/181] Fix compilation on Qt 6.4.2 Is this an EOL version lol Signed-off-by: TheKodeToad --- launcher/logs/LogParser.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index c5d2a8647..59a1ff3c3 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -22,6 +22,8 @@ #include #include "MessageLevel.h" +using namespace Qt::Literals::StringLiterals; + void LogParser::appendLine(QAnyStringView data) { if (!m_partialData.isEmpty()) { @@ -70,18 +72,18 @@ std::optional LogParser::parseAttributes() for (const auto& attr : attributes) { auto name = attr.name(); auto value = attr.value(); - if (name == "logger") { + if (name == "logger"_L1) { entry.logger = value.trimmed().toString(); - } else if (name == "timestamp") { + } else if (name == "timestamp"_L1) { if (value.trimmed().isEmpty()) { m_parser.raiseError("log4j:Event Missing required attribute: timestamp"); return {}; } entry.timestamp = QDateTime::fromSecsSinceEpoch(value.trimmed().toLongLong()); - } else if (name == "level") { + } else if (name == "level"_L1) { entry.levelText = value.trimmed().toString(); entry.level = parseLogLevel(entry.levelText); - } else if (name == "thread") { + } else if (name == "thread"_L1) { entry.thread = value.trimmed().toString(); } } @@ -135,7 +137,7 @@ std::optional LogParser::parseNext() m_parser.setNamespaceProcessing(false); m_parser.addData(m_buffer); if (m_parser.readNextStartElement()) { - if (m_parser.qualifiedName() == "log4j:Event") { + if (m_parser.qualifiedName() == "log4j:Event"_L1) { int depth = 1; bool eod = false; while (depth > 0 && !eod) { @@ -235,7 +237,7 @@ std::optional LogParser::parseLog4J() m_parser.addData(m_buffer); m_parser.readNextStartElement(); - if (m_parser.qualifiedName() == "log4j:Event") { + if (m_parser.qualifiedName() == "log4j:Event"_L1) { auto entry_ = parseAttributes(); if (!entry_.has_value()) { setError(); @@ -251,7 +253,7 @@ std::optional LogParser::parseLog4J() switch (tok) { case QXmlStreamReader::TokenType::StartElement: { depth += 1; - if (m_parser.qualifiedName() == "log4j:Message") { + if (m_parser.qualifiedName() == "log4j:Message"_L1) { QString message; bool messageComplete = false; @@ -263,7 +265,7 @@ std::optional LogParser::parseLog4J() message.append(m_parser.text()); } break; case QXmlStreamReader::TokenType::EndElement: { - if (m_parser.qualifiedName() == "log4j:Message") { + if (m_parser.qualifiedName() == "log4j:Message"_L1) { messageComplete = true; } } break; @@ -287,7 +289,7 @@ std::optional LogParser::parseLog4J() break; case QXmlStreamReader::TokenType::EndElement: { depth -= 1; - if (depth == 0 && m_parser.qualifiedName() == "log4j:Event") { + if (depth == 0 && m_parser.qualifiedName() == "log4j:Event"_L1) { if (foundMessage) { auto consumed = m_parser.characterOffset(); if (consumed > 0 && consumed <= m_buffer.length()) { @@ -362,7 +364,8 @@ MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * // static const QRegularExpression JAVA_EXCEPTION( - // R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); + // R"(Exception in thread|...\d more$|(\s+at |Caused by: + // )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); // // if (line.contains(JAVA_EXCEPTION)) // return MessageLevel::Error; From 11015a22d253d76f2d40838073c1b0a9c20831d4 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 10:47:32 +0100 Subject: [PATCH 158/181] Remove commented out code Signed-off-by: TheKodeToad --- launcher/logs/LogParser.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 59a1ff3c3..4c4eae5aa 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -362,12 +362,5 @@ MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum if (line.contains("overwriting existing")) return MessageLevel::Fatal; - // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - // static const QRegularExpression JAVA_EXCEPTION( - // R"(Exception in thread|...\d more$|(\s+at |Caused by: - // )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); - // - // if (line.contains(JAVA_EXCEPTION)) - // return MessageLevel::Error; return MessageLevel::Info; } From 1bd1245d860e3547085533860c6c6e0725a09528 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 19 Apr 2025 13:31:33 -0700 Subject: [PATCH 159/181] feat(xml-logs): Case insisitive xml parseing + cleaner switch Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/logs/LogParser.cpp | 186 +++++++++++++++++------------------- launcher/logs/LogParser.h | 1 - 2 files changed, 89 insertions(+), 98 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 4c4eae5aa..7e7d30ffc 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -29,11 +29,9 @@ void LogParser::appendLine(QAnyStringView data) if (!m_partialData.isEmpty()) { m_buffer = QString(m_partialData); m_buffer.append("\n"); - m_buffer.append(data.toString()); m_partialData.clear(); - } else { - m_buffer.append(data.toString()); } + m_buffer.append(data.toString()); } std::optional LogParser::getError() @@ -41,26 +39,6 @@ std::optional LogParser::getError() return m_error; } -MessageLevel::Enum LogParser::parseLogLevel(const QString& level) -{ - auto test = level.trimmed().toUpper(); - if (test == "TRACE") { - return MessageLevel::Trace; - } else if (test == "DEBUG") { - return MessageLevel::Debug; - } else if (test == "INFO") { - return MessageLevel::Info; - } else if (test == "WARN") { - return MessageLevel::Warning; - } else if (test == "ERROR") { - return MessageLevel::Error; - } else if (test == "FATAL") { - return MessageLevel::Fatal; - } else { - return MessageLevel::Unknown; - } -} - std::optional LogParser::parseAttributes() { LogParser::LogEntry entry{ @@ -82,7 +60,7 @@ std::optional LogParser::parseAttributes() entry.timestamp = QDateTime::fromSecsSinceEpoch(value.trimmed().toLongLong()); } else if (name == "level"_L1) { entry.levelText = value.trimmed().toString(); - entry.level = parseLogLevel(entry.levelText); + entry.level = MessageLevel::getLevel(entry.levelText); } else if (name == "thread"_L1) { entry.thread = value.trimmed().toString(); } @@ -137,7 +115,7 @@ std::optional LogParser::parseNext() m_parser.setNamespaceProcessing(false); m_parser.addData(m_buffer); if (m_parser.readNextStartElement()) { - if (m_parser.qualifiedName() == "log4j:Event"_L1) { + if (m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) { int depth = 1; bool eod = false; while (depth > 0 && !eod) { @@ -237,7 +215,7 @@ std::optional LogParser::parseLog4J() m_parser.addData(m_buffer); m_parser.readNextStartElement(); - if (m_parser.qualifiedName() == "log4j:Event"_L1) { + if (m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) { auto entry_ = parseAttributes(); if (!entry_.has_value()) { setError(); @@ -248,72 +226,95 @@ std::optional LogParser::parseLog4J() bool foundMessage = false; int depth = 1; + enum parseOp { noOp, entryReady, parseError }; + + auto foundStart = [&]() -> parseOp { + depth += 1; + if (m_parser.qualifiedName().compare("log4j:Message"_L1, Qt::CaseInsensitive) == 0) { + QString message; + bool messageComplete = false; + + while (!messageComplete) { + auto tok = m_parser.readNext(); + + switch (tok) { + case QXmlStreamReader::TokenType::Characters: { + message.append(m_parser.text()); + } break; + case QXmlStreamReader::TokenType::EndElement: { + if (m_parser.qualifiedName().compare("log4j:Message"_L1, Qt::CaseInsensitive) == 0) { + messageComplete = true; + } + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return parseError; // parse fail + } break; + default: { + // no op + } + } + + if (m_parser.hasError()) { + return parseError; + } + } + + entry.message = message; + foundMessage = true; + depth -= 1; + } + return noOp; + }; + + auto foundEnd = [&]() -> parseOp { + depth -= 1; + if (depth == 0 && m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) { + if (foundMessage) { + auto consumed = m_parser.characterOffset(); + if (consumed > 0 && consumed <= m_buffer.length()) { + m_buffer = m_buffer.right(m_buffer.length() - consumed); + + if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { + // only whitespace, dump it + m_buffer.clear(); + } + } + clearError(); + return entryReady; + } + m_parser.raiseError("log4j:Event Missing required attribute: message"); + setError(); + return parseError; + } + return noOp; + }; + while (!m_parser.atEnd()) { auto tok = m_parser.readNext(); + parseOp op = noOp; switch (tok) { case QXmlStreamReader::TokenType::StartElement: { - depth += 1; - if (m_parser.qualifiedName() == "log4j:Message"_L1) { - QString message; - bool messageComplete = false; + op = foundStart(); + } break; + case QXmlStreamReader::TokenType::EndElement: { + op = foundEnd(); + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return {}; + } break; + default: { + // no op + } + } - while (!messageComplete) { - auto tok = m_parser.readNext(); - - switch (tok) { - case QXmlStreamReader::TokenType::Characters: { - message.append(m_parser.text()); - } break; - case QXmlStreamReader::TokenType::EndElement: { - if (m_parser.qualifiedName() == "log4j:Message"_L1) { - messageComplete = true; - } - } break; - case QXmlStreamReader::TokenType::EndDocument: { - return {}; // parse fail - } break; - default: { - // no op - } - } - - if (m_parser.hasError()) { - return {}; - } - } - - entry.message = message; - foundMessage = true; - depth -= 1; - } - break; - case QXmlStreamReader::TokenType::EndElement: { - depth -= 1; - if (depth == 0 && m_parser.qualifiedName() == "log4j:Event"_L1) { - if (foundMessage) { - auto consumed = m_parser.characterOffset(); - if (consumed > 0 && consumed <= m_buffer.length()) { - m_buffer = m_buffer.right(m_buffer.length() - consumed); - - if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { - // only whitespace, dump it - m_buffer.clear(); - } - } - clearError(); - return entry; - } - m_parser.raiseError("log4j:Event Missing required attribute: message"); - setError(); - return {}; - } - } break; - case QXmlStreamReader::TokenType::EndDocument: { - return {}; - } break; - default: { - // no op - } + switch (op) { + case parseError: + return {}; // parse fail or error + case entryReady: + return entry; + case noOp: + default: { + // no op } } @@ -334,16 +335,7 @@ MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum // New style logs from log4j QString timestamp = match.captured("timestamp"); QString levelStr = match.captured("level"); - if (levelStr == "INFO") - level = MessageLevel::Info; - if (levelStr == "WARN") - level = MessageLevel::Warning; - if (levelStr == "ERROR") - level = MessageLevel::Error; - if (levelStr == "FATAL") - level = MessageLevel::Fatal; - if (levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; + level = MessageLevel::getLevel(levelStr); } else { // Old style forge logs if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || diff --git a/launcher/logs/LogParser.h b/launcher/logs/LogParser.h index 8b23754ac..1a1d86dd1 100644 --- a/launcher/logs/LogParser.h +++ b/launcher/logs/LogParser.h @@ -62,7 +62,6 @@ class LogParser { static MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level); protected: - MessageLevel::Enum parseLogLevel(const QString& level); std::optional parseAttributes(); void setError(); void clearError(); From 3fd5557f89de1269f024b9bd146ff019170795ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 20 Apr 2025 00:28:02 +0000 Subject: [PATCH 160/181] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'libnbtplusplus': 'github:PrismLauncher/libnbtplusplus/23b955121b8217c1c348a9ed2483167a6f3ff4ad?narHash=sha256-yy0q%2Bbky80LtK1GWzz7qpM%2BaAGrOqLuewbid8WT1ilk%3D' (2023-11-06) → 'github:PrismLauncher/libnbtplusplus/531449ba1c930c98e0bcf5d332b237a8566f9d78?narHash=sha256-qhmjaRkt%2BO7A%2Bgu6HjUkl7QzOEb4r8y8vWZMG2R/C6o%3D' (2025-04-16) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/2631b0b7abcea6e640ce31cd78ea58910d31e650?narHash=sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR%2BXhw3kr/3Xd0GPTM%3D' (2025-04-12) → 'github:NixOS/nixpkgs/b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef?narHash=sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU%3D' (2025-04-17) • Updated input 'qt-qrcodegenerator': 'github:nayuki/QR-Code-generator/f40366c40d8d1956081f7ec643d240c02a81df52?narHash=sha256-5%2BiYwsbX8wjKZPCy7ENj5HCYgOqzeSNLs/YrX2Vc7CQ%3D' (2024-11-18) → 'github:nayuki/QR-Code-generator/2c9044de6b049ca25cb3cd1649ed7e27aa055138?narHash=sha256-6SugPt0lp1Gz7nV23FLmsmpfzgFItkSw7jpGftsDPWc%3D' (2025-01-23) --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 070d069e5..07fa5117a 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "libnbtplusplus": { "flake": false, "locked": { - "lastModified": 1699286814, - "narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=", + "lastModified": 1744811532, + "narHash": "sha256-qhmjaRkt+O7A+gu6HjUkl7QzOEb4r8y8vWZMG2R/C6o=", "owner": "PrismLauncher", "repo": "libnbtplusplus", - "rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad", + "rev": "531449ba1c930c98e0bcf5d332b237a8566f9d78", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1744463964, - "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", + "lastModified": 1744932701, + "narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", + "rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef", "type": "github" }, "original": { @@ -35,11 +35,11 @@ "qt-qrcodegenerator": { "flake": false, "locked": { - "lastModified": 1731907326, - "narHash": "sha256-5+iYwsbX8wjKZPCy7ENj5HCYgOqzeSNLs/YrX2Vc7CQ=", + "lastModified": 1737616857, + "narHash": "sha256-6SugPt0lp1Gz7nV23FLmsmpfzgFItkSw7jpGftsDPWc=", "owner": "nayuki", "repo": "QR-Code-generator", - "rev": "f40366c40d8d1956081f7ec643d240c02a81df52", + "rev": "2c9044de6b049ca25cb3cd1649ed7e27aa055138", "type": "github" }, "original": { From e03870d3f2455d5db7ba701187af41abcda66d73 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 20 Apr 2025 16:44:47 -0400 Subject: [PATCH 161/181] ci(get-merge-commit): init Signed-off-by: Seth Flynn --- .github/actions/get-merge-commit.yml | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/actions/get-merge-commit.yml diff --git a/.github/actions/get-merge-commit.yml b/.github/actions/get-merge-commit.yml new file mode 100644 index 000000000..8c67fdfc9 --- /dev/null +++ b/.github/actions/get-merge-commit.yml @@ -0,0 +1,103 @@ +# This file incorporates work covered by the following copyright and +# permission notice +# +# Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +name: Get merge commit +description: Get a merge commit of a given pull request + +inputs: + repository: + description: Repository containing the pull request + required: false + pull-request-id: + description: ID of a pull request + required: true + +outputs: + merge-commit-sha: + description: Git SHA of a merge commit + value: ${{ steps.query.outputs.merge-commit-sha }} + +runs: + using: composite + + steps: + - name: Wait for GitHub to report merge commit + id: query + shell: bash + env: + GITHUB_REPO: ${{ inputs.repository || github.repository }} + PR_ID: ${{ inputs.pull-request-id }} + # https://github.com/NixOS/nixpkgs/blob/8f77f3600f1ee775b85dc2c72fd842768e486ec9/ci/get-merge-commit.sh + run: | + set -euo pipefail + + log() { + echo "$@" >&2 + } + + # Retry the API query this many times + retryCount=5 + # Start with 5 seconds, but double every retry + retryInterval=5 + + while true; do + log "Checking whether the pull request can be merged" + prInfo=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$GITHUB_REPO/pulls/$PR_ID") + + # Non-open PRs won't have their mergeability computed no matter what + state=$(jq -r .state <<<"$prInfo") + if [[ "$state" != open ]]; then + log "PR is not open anymore" + exit 1 + fi + + mergeable=$(jq -r .mergeable <<<"$prInfo") + if [[ "$mergeable" == "null" ]]; then + if ((retryCount == 0)); then + log "Not retrying anymore. It's likely that GitHub is having internal issues: check https://www.githubstatus.com/" + exit 3 + else + ((retryCount -= 1)) || true + + # null indicates that GitHub is still computing whether it's mergeable + # Wait a couple seconds before trying again + log "GitHub is still computing whether this PR can be merged, waiting $retryInterval seconds before trying again ($retryCount retries left)" + sleep "$retryInterval" + + ((retryInterval *= 2)) || true + fi + else + break + fi + done + + if [[ "$mergeable" == "true" ]]; then + echo "merge-commit-sha=$(jq -r .merge_commit_sha <<<"$prInfo")" >> "$GITHUB_OUTPUT" + else + echo "# 🚨 The PR has a merge conflict!" >>> "$GITHUB_STEP_SUMMARY" + exit 2 + fi From f2a601f8153f974c73ad547198650de83889bd9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:58:13 +0000 Subject: [PATCH 162/181] chore(deps): update determinatesystems/nix-installer-action action to v17 --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index fea0df6ce..765aa4ca8 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -65,7 +65,7 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v16 + uses: DeterminateSystems/nix-installer-action@v17 with: determinate: ${{ env.USE_DETERMINATE }} From 83ebb5984b47dbf393d4e2ad63bb6d9024c13f26 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 25 Apr 2025 19:18:28 -0700 Subject: [PATCH 163/181] fix: nullptr access style can't always be created Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/themes/SystemTheme.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 7fba08026..c9b2e5cfd 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -54,7 +54,7 @@ SystemTheme::SystemTheme(const QString& styleName, const QPalette& defaultPalett m_colorPalette = defaultPalette; } else { auto style = QStyleFactory::create(styleName); - m_colorPalette = style->standardPalette(); + m_colorPalette = style != nullptr ? style->standardPalette() : defaultPalette; delete style; } } From abe18fb144f707b57ee56dda996ca034c47a3bd6 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 20 Apr 2025 16:54:44 -0400 Subject: [PATCH 164/181] ci(nix): checkout merge commit of pull request Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index fea0df6ce..816e2a7aa 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -61,8 +61,17 @@ jobs: id-token: write steps: + - name: Get merge commit + if: ${{ github.event_name == 'pull_request_target' }} + id: merge-commit + uses: ./.github/actions/get-merge-commit.yml + with: + pull-request-id: ${{ github.event.pull_request.id }} + - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ steps.merge-commit.outputs.merge-commit-sha || github.sha }} - name: Install Nix uses: DeterminateSystems/nix-installer-action@v16 From a702d06cd85737728a1ec15538127e4c418bde8e Mon Sep 17 00:00:00 2001 From: Xander Date: Sat, 26 Apr 2025 21:41:14 +0100 Subject: [PATCH 165/181] Pass mainclass and gameargs to the main game via system properties Signed-off-by: Xander --- .../launcher/impl/StandardLauncher.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index 084fbc849..af9b41533 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -97,6 +97,18 @@ public final class StandardLauncher extends AbstractLauncher { gameArgs.add(worldName); } + StringBuilder joinedGameArgs = new StringBuilder(); + for (String gameArg : gameArgs) { + if (joinedGameArgs.length() > 0) { + joinedGameArgs.append(" "); + } + joinedGameArgs.append(gameArg); + } + + // pass the real main class and game arguments in so mods can access them + System.setProperty("org.prismlauncher.launch.mainclass", mainClassName); + System.setProperty("org.prismlauncher.launch.gameargs", joinedGameArgs.toString()); + // find and invoke the main method MethodHandle method = ReflectionUtils.findMainMethod(mainClassName); method.invokeExact(gameArgs.toArray(new String[0])); From a92e114236b54a23561fa4071cc1ec8486b43119 Mon Sep 17 00:00:00 2001 From: Xander Date: Sat, 26 Apr 2025 21:57:28 +0100 Subject: [PATCH 166/181] Use \u001F instead of a space as a delimeter for game args Signed-off-by: Xander --- .../org/prismlauncher/launcher/impl/StandardLauncher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index af9b41533..96a809dfe 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -100,7 +100,7 @@ public final class StandardLauncher extends AbstractLauncher { StringBuilder joinedGameArgs = new StringBuilder(); for (String gameArg : gameArgs) { if (joinedGameArgs.length() > 0) { - joinedGameArgs.append(" "); + joinedGameArgs.append('\u001F'); // unit separator, designed for this purpose } joinedGameArgs.append(gameArg); } From 02106ab29a8761e44aab5c8e73fd8842c4802b32 Mon Sep 17 00:00:00 2001 From: Xander Date: Sat, 26 Apr 2025 23:42:22 +0100 Subject: [PATCH 167/181] comment on property about delimeter Signed-off-by: Xander --- .../org/prismlauncher/launcher/impl/StandardLauncher.java | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index 96a809dfe..968499ff6 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -107,6 +107,7 @@ public final class StandardLauncher extends AbstractLauncher { // pass the real main class and game arguments in so mods can access them System.setProperty("org.prismlauncher.launch.mainclass", mainClassName); + // unit separator ('\u001F') delimited list of game args System.setProperty("org.prismlauncher.launch.gameargs", joinedGameArgs.toString()); // find and invoke the main method From 47cb58d326f5177c7337b531a8aec75b07edf1d7 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 27 Apr 2025 07:16:20 -0400 Subject: [PATCH 168/181] ci(nix): fix get-merge-commit action call Signed-off-by: Seth Flynn --- .../{get-merge-commit.yml => get-merge-commit/action.yml} | 0 .github/workflows/nix.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename .github/actions/{get-merge-commit.yml => get-merge-commit/action.yml} (100%) diff --git a/.github/actions/get-merge-commit.yml b/.github/actions/get-merge-commit/action.yml similarity index 100% rename from .github/actions/get-merge-commit.yml rename to .github/actions/get-merge-commit/action.yml diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 816e2a7aa..03d5f2089 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -64,7 +64,7 @@ jobs: - name: Get merge commit if: ${{ github.event_name == 'pull_request_target' }} id: merge-commit - uses: ./.github/actions/get-merge-commit.yml + uses: PrismLauncher/PrismLauncher/.github/actions/get-merge-commit@develop with: pull-request-id: ${{ github.event.pull_request.id }} From 3b7b9fa03c330aa8844b380be614dce9fde90097 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 27 Apr 2025 06:59:19 -0400 Subject: [PATCH 169/181] ci: better filter workflow runs Signed-off-by: Seth Flynn --- .github/workflows/codeql.yml | 35 ++++++++++++++++++++- .github/workflows/trigger_builds.yml | 46 +++++++++++++++++++--------- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e3243097d..a5ac537f1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,6 +1,39 @@ name: "CodeQL Code Scanning" -on: [push, pull_request, workflow_dispatch] +on: + push: + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/codeql.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" + pull_request: + # See above + paths: + - "**" + - "!.github/**" + - ".github/workflows/codeql.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" + workflow_dispatch: jobs: CodeQL: diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index 9efafc8cc..e4c90ef0b 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -4,21 +4,39 @@ on: push: branches-ignore: - "renovate/**" - paths-ignore: - - "**.md" - - "**/LICENSE" - - "flake.lock" - - "packages/**" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/build.yml" + - ".github/workflows/trigger_builds.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" pull_request: - paths-ignore: - - "**.md" - - "**/LICENSE" - - "flake.lock" - - "packages/**" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" + # See above + paths: + - "**" + - "!.github/**" + - ".github/workflows/build.yml" + - ".github/workflows/trigger_builds.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" workflow_dispatch: jobs: From 57a2ef1aed5a673f7772005e0dc4c4aa100e3c4d Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 27 Apr 2025 07:00:26 -0400 Subject: [PATCH 170/181] ci: fix improper paths-ignore usage Signed-off-by: Seth Flynn --- .github/workflows/flatpak.yml | 40 +++++++++++++++++++++---------- .github/workflows/nix.yml | 44 ++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 41cc2a51d..8caba46fa 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -2,22 +2,38 @@ name: Flatpak on: push: - paths-ignore: - - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "nix/**" # We don't do anything with these artifacts on releases. They go to Flathub tags-ignore: - "*" + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/flatpak.yml" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" pull_request: - paths-ignore: - - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "nix/**" + # See above + paths: + - "**" + - "!.github/**" + - ".github/workflows/flatpak.yml" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" workflow_dispatch: permissions: diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 816e2a7aa..b968062c9 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -4,28 +4,34 @@ on: push: tags: - "*" - paths-ignore: - - ".github/**" - - "!.github/workflows/nix.yml" - - "flatpak/" - - "scripts/" + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/nix.yml" + - "!flatpak/" + - "!scripts/" - - ".git*" - - ".envrc" - - "**.md" - - "!COPYING.md" - - "renovate.json" + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" pull_request_target: - paths-ignore: - - ".github/**" - - "flatpak/" - - "scripts/" + paths: + - "**" + - "!.github/**" + - ".github/workflows/nix.yml" + - "!flatpak/" + - "!scripts/" - - ".git*" - - ".envrc" - - "**.md" - - "!COPYING.md" - - "renovate.json" + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" workflow_dispatch: permissions: From 20626e6606ba07317fb4283f0814ecef4e1ea136 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 28 Apr 2025 10:28:57 +0100 Subject: [PATCH 171/181] Fix log sorting Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 2 +- launcher/ui/pages/instance/OtherLogsPage.cpp | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 121b8035c..1009d7c42 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1057,7 +1057,7 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev QStringList MinecraftInstance::getLogFileSearchPaths() { - return { FS::PathCombine(gameRoot(), "logs"), FS::PathCombine(gameRoot(), "crash-reports"), gameRoot() }; + return { FS::PathCombine(gameRoot(), "crash-reports"), FS::PathCombine(gameRoot(), "logs"), gameRoot() }; } QString MinecraftInstance::getStatusbarDescription() diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index f457195d8..80a9c0fdc 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -146,6 +146,7 @@ void OtherLogsPage::populateSelectLogBox() ui->selectLogBox->setCurrentIndex(index); ui->selectLogBox->blockSignals(false); setControlsEnabled(true); + // don't refresh file return; } else { setControlsEnabled(false); @@ -405,20 +406,17 @@ QStringList OtherLogsPage::getPaths() QStringList result; for (QString searchPath : m_logSearchPaths) { - QDirIterator iterator(searchPath, QDir::Files | QDir::Readable); + QDir searchDir(searchPath); - const bool isRoot = searchPath == m_basePath; + QStringList filters{ "*.log", "*.log.gz" }; - while (iterator.hasNext()) { - const QString name = iterator.next(); + if (searchPath != m_basePath) + filters.append("*.txt"); - QString relativePath = baseDir.relativeFilePath(name); + QStringList entries = searchDir.entryList(filters, QDir::Files | QDir::Readable, QDir::SortFlag::Time); - if (!(name.endsWith(".log") || name.endsWith(".log.gz") || (!isRoot && name.endsWith(".txt")))) - continue; - - result.append(relativePath); - } + for (const QString& name : entries) + result.append(baseDir.relativeFilePath(searchDir.filePath(name))); } return result; From 2c943a003d798aeaa76ab630979953ac7aff51eb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 28 Apr 2025 03:08:33 -0700 Subject: [PATCH 172/181] feat(xml-logs): preserve whitespace lines in logs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/logs/LogParser.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 7e7d30ffc..6e33b24dd 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -105,8 +105,9 @@ std::optional LogParser::parseNext() } if (m_buffer.trimmed().isEmpty()) { + auto text = QString(m_buffer); m_buffer.clear(); - return {}; + return LogParser::PlainText { text }; } // check if we have a full xml log4j event @@ -177,11 +178,7 @@ std::optional LogParser::parseNext() // no log4j found, all plain text auto text = QString(m_buffer); m_buffer.clear(); - if (text.trimmed().isEmpty()) { - return {}; - } else { - return LogParser::PlainText{ text }; - } + return LogParser::PlainText{ text }; } } @@ -273,11 +270,7 @@ std::optional LogParser::parseLog4J() auto consumed = m_parser.characterOffset(); if (consumed > 0 && consumed <= m_buffer.length()) { m_buffer = m_buffer.right(m_buffer.length() - consumed); - - if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { - // only whitespace, dump it - m_buffer.clear(); - } + // potential whitespace preserved for next item } clearError(); return entryReady; From d0ccd110a15c31e3bb03454518ea2c78fe38a07c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 19 Apr 2025 06:16:57 -0700 Subject: [PATCH 173/181] fix: use after free begone! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 2 +- launcher/qtlogging.ini | 3 +++ launcher/ui/pages/instance/ModFolderPage.cpp | 24 ++++++++++++++----- launcher/ui/pages/instance/ModFolderPage.h | 4 ++++ .../ui/pages/instance/ResourcePackPage.cpp | 24 +++++++++++++------ launcher/ui/pages/instance/ResourcePackPage.h | 5 ++++ launcher/ui/pages/instance/ShaderPackPage.cpp | 21 +++++++++++----- launcher/ui/pages/instance/ShaderPackPage.h | 4 ++++ .../ui/pages/instance/TexturePackPage.cpp | 20 +++++++++++----- launcher/ui/pages/instance/TexturePackPage.h | 5 ++++ 10 files changed, 86 insertions(+), 26 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index cfe028279..33d700772 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -96,6 +96,7 @@ #include #include #include +#include #include #include #include @@ -125,7 +126,6 @@ #include #include -#include #include #include #include "SysInfo.h" diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini index c12d1e109..10f724163 100644 --- a/launcher/qtlogging.ini +++ b/launcher/qtlogging.ini @@ -3,6 +3,9 @@ # prevent log spam and strange bugs # qt.qpa.drawing in particular causes theme artifacts on MacOS qt.*.debug=false +# supress image format noise +kf.imageformats.plugins.hdr=false +kf.imageformats.plugins.xcf=false # don't log credentials by default launcher.auth.credentials.debug=false # remove the debug lines, other log levels still get through diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 026f0c140..8508e7908 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include "Application.h" @@ -145,10 +146,17 @@ void ModFolderPage::downloadMods() QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); + m_downloadDialog->open(); +} + +void ModFolderPage::downloadDialogFinished(int result) +{ + if (result) { auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -166,8 +174,12 @@ void ModFolderPage::downloadMods() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index a7d078f50..8996b1615 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -38,7 +38,9 @@ #pragma once +#include #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" class ModFolderPage : public ExternalResourcesPage { Q_OBJECT @@ -63,6 +65,7 @@ class ModFolderPage : public ExternalResourcesPage { void removeItems(const QItemSelection& selection) override; void downloadMods(); + void downloadDialogFinished(int result); void updateMods(bool includeDeps = false); void deleteModMetadata(); void exportModMetadata(); @@ -70,6 +73,7 @@ class ModFolderPage : public ExternalResourcesPage { protected: std::shared_ptr m_model; + QPointer m_downloadDialog; }; class CoreModFolderPage : public ModFolderPage { diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index ae5eb8fac..0d9e643b1 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -84,10 +84,16 @@ void ResourcePackPage::downloadResourcePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); + m_downloadDialog->open(); +} + +void ResourcePackPage::downloadDialogFinished(int result) +{ + if (result) { auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -105,8 +111,12 @@ void ResourcePackPage::downloadResourcePacks() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); @@ -269,4 +279,4 @@ void ResourcePackPage::changeResourcePackVersion() m_model->update(); } -} \ No newline at end of file +} diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 55abe007c..e39d417c9 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -37,7 +37,10 @@ #pragma once +#include + #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui_ExternalResourcesPage.h" #include "minecraft/mod/ResourcePackFolderModel.h" @@ -62,10 +65,12 @@ class ResourcePackPage : public ExternalResourcesPage { private slots: void downloadResourcePacks(); + void downloadDialogFinished(int result); void updateResourcePacks(); void deleteResourcePackMetadata(); void changeResourcePackVersion(); protected: std::shared_ptr m_model; + QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 45bb02030..829a75a72 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -81,10 +81,15 @@ void ShaderPackPage::downloadShaderPack() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); + m_downloadDialog->open(); +} + +void ShaderPackPage::downloadDialogFinished(int result) { + if (result) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -102,8 +107,12 @@ void ShaderPackPage::downloadShaderPack() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index ebf7f1d58..f2b141329 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -37,7 +37,9 @@ #pragma once +#include #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" class ShaderPackPage : public ExternalResourcesPage { Q_OBJECT @@ -54,10 +56,12 @@ class ShaderPackPage : public ExternalResourcesPage { public slots: void downloadShaderPack(); + void downloadDialogFinished(int result); void updateShaderPacks(); void deleteShaderPackMetadata(); void changeShaderPackVersion(); private: std::shared_ptr m_model; + QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 6d000a486..ada29d94b 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -90,10 +90,14 @@ void TexturePackPage::downloadTexturePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + auto m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); +} + +void TexturePackPage::downloadDialogFinished(int result) +{ + if (result) { auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -111,8 +115,12 @@ void TexturePackPage::downloadTexturePacks() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index 28d7ba209..3ebca3e87 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -37,7 +37,10 @@ #pragma once +#include + #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui_ExternalResourcesPage.h" #include "minecraft/mod/TexturePackFolderModel.h" @@ -57,10 +60,12 @@ class TexturePackPage : public ExternalResourcesPage { public slots: void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; void downloadTexturePacks(); + void downloadDialogFinished(int result); void updateTexturePacks(); void deleteTexturePackMetadata(); void changeTexturePackVersion(); private: std::shared_ptr m_model; + QPointer m_downloadDialog; }; From 07a6606c9c549b70ce91225303abe90982973a72 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 19 Apr 2025 06:48:04 -0700 Subject: [PATCH 174/181] fix: cover both usages of the download dialog Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/instance/ModFolderPage.cpp | 42 ++++-------------- .../ui/pages/instance/ResourcePackPage.cpp | 40 ++++------------- launcher/ui/pages/instance/ShaderPackPage.cpp | 43 +++++-------------- .../ui/pages/instance/TexturePackPage.cpp | 43 +++++-------------- 4 files changed, 37 insertions(+), 131 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8508e7908..dad2da8a4 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -146,11 +146,11 @@ void ModFolderPage::downloadMods() QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); + m_downloadDialog->open(); } @@ -188,6 +188,8 @@ void ModFolderPage::downloadDialogFinished(int result) m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void ModFolderPage::updateMods(bool includeDeps) @@ -313,38 +315,12 @@ void ModFolderPage::changeModVersion() if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) return; - auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata((*mods_list.begin())->metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata((*mods_list.begin())->metadata()); + m_downloadDialog->open(); } void ModFolderPage::exportModMetadata() diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 0d9e643b1..f37b3baf9 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -85,9 +85,9 @@ void ResourcePackPage::downloadResourcePacks() return; // this is a null instance or a legacy instance m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); + m_downloadDialog->open(); } @@ -125,6 +125,8 @@ void ResourcePackPage::downloadDialogFinished(int result) m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void ResourcePackPage::updateResourcePacks() @@ -247,36 +249,10 @@ void ResourcePackPage::changeResourcePackVersion() if (resource.metadata() == nullptr) return; - auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata(resource.metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata(resource.metadata()); + m_downloadDialog->open(); } diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 829a75a72..930b0b9da 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -82,13 +82,14 @@ void ShaderPackPage::downloadShaderPack() return; // this is a null instance or a legacy instance m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); + m_downloadDialog->open(); } -void ShaderPackPage::downloadDialogFinished(int result) { +void ShaderPackPage::downloadDialogFinished(int result) +{ if (result) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { @@ -121,6 +122,8 @@ void ShaderPackPage::downloadDialogFinished(int result) { m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void ShaderPackPage::updateShaderPacks() @@ -243,36 +246,10 @@ void ShaderPackPage::changeShaderPackVersion() if (resource.metadata() == nullptr) return; - auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata(resource.metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata(resource.metadata()); + m_downloadDialog->open(); } diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index ada29d94b..2886decb4 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -90,9 +90,10 @@ void TexturePackPage::downloadTexturePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &TexturePackPage::downloadDialogFinished); + m_downloadDialog->open(); } void TexturePackPage::downloadDialogFinished(int result) @@ -129,6 +130,8 @@ void TexturePackPage::downloadDialogFinished(int result) m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void TexturePackPage::updateTexturePacks() @@ -251,36 +254,10 @@ void TexturePackPage::changeTexturePackVersion() if (resource.metadata() == nullptr) return; - auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata(resource.metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &TexturePackPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata(resource.metadata()); + m_downloadDialog->open(); } From bcdbe79c592894ab9e47908a5b06d7b3c5568430 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 28 Apr 2025 17:37:22 +0300 Subject: [PATCH 175/181] fix: add github token for gh cli Signed-off-by: Trial97 --- .github/workflows/nix.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index a11389e6c..a77b33521 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -73,7 +73,9 @@ jobs: uses: PrismLauncher/PrismLauncher/.github/actions/get-merge-commit@develop with: pull-request-id: ${{ github.event.pull_request.id }} - + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout repository uses: actions/checkout@v4 with: From 0ccb4059a028615543bc232c3f87a7ba0b71e1ca Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 28 Apr 2025 17:44:23 +0300 Subject: [PATCH 176/181] chore: update submodules Signed-off-by: Trial97 --- flatpak/shared-modules | 2 +- libraries/extra-cmake-modules | 2 +- libraries/libnbtplusplus | 2 +- libraries/quazip | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flatpak/shared-modules b/flatpak/shared-modules index f5d368a31..73f08ed2c 160000 --- a/flatpak/shared-modules +++ b/flatpak/shared-modules @@ -1 +1 @@ -Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e +Subproject commit 73f08ed2c3187f6648ca04ebef030930a6c9f0be diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules index a3d9394ab..1f820dc98 160000 --- a/libraries/extra-cmake-modules +++ b/libraries/extra-cmake-modules @@ -1 +1 @@ -Subproject commit a3d9394aba4b35789293378e04fb7473d65edf97 +Subproject commit 1f820dc98d0a520c175433bcbb0098327d82aac6 diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus index 23b955121..531449ba1 160000 --- a/libraries/libnbtplusplus +++ b/libraries/libnbtplusplus @@ -1 +1 @@ -Subproject commit 23b955121b8217c1c348a9ed2483167a6f3ff4ad +Subproject commit 531449ba1c930c98e0bcf5d332b237a8566f9d78 diff --git a/libraries/quazip b/libraries/quazip index 8aeb3f7d8..3fd3b299b 160000 --- a/libraries/quazip +++ b/libraries/quazip @@ -1 +1 @@ -Subproject commit 8aeb3f7d8254f4bf1f7c6cf2a8f59c2ca141a552 +Subproject commit 3fd3b299b875fbd2beac4894b8a870d80022cad7 From c5fd5e6ac1ad69ecc753495e5da0039d5ce1ba32 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 18:55:26 +0300 Subject: [PATCH 177/181] chore: make all the regexes static const Signed-off-by: Trial97 --- launcher/BaseInstance.cpp | 1 - launcher/InstanceImportTask.cpp | 4 ++-- launcher/JavaCommon.cpp | 6 ++++-- launcher/LaunchController.cpp | 3 ++- launcher/RecursiveFileSystemWatcher.cpp | 1 - launcher/StringUtils.cpp | 7 +++---- launcher/Version.cpp | 1 - launcher/java/JavaVersion.cpp | 8 ++++++-- launcher/launch/LaunchTask.cpp | 1 - launcher/minecraft/GradleSpecifier.h | 4 ++-- launcher/minecraft/MinecraftInstance.cpp | 5 ++--- launcher/minecraft/OneSixVersionFormat.cpp | 4 ++-- launcher/minecraft/ProfileUtils.cpp | 1 - launcher/minecraft/auth/AccountData.cpp | 1 - launcher/minecraft/auth/MinecraftAccount.cpp | 11 +++++++---- .../minecraft/launch/LauncherPartLaunch.cpp | 17 +++++++++-------- launcher/minecraft/mod/Resource.cpp | 4 ++-- launcher/minecraft/mod/ResourcePack.cpp | 2 -- launcher/minecraft/mod/ShaderPack.cpp | 2 -- launcher/minecraft/mod/TexturePack.cpp | 2 -- .../minecraft/mod/tasks/LocalModParseTask.cpp | 4 ++-- .../modplatform/atlauncher/ATLPackIndex.cpp | 3 ++- .../atlauncher/ATLPackInstallTask.cpp | 6 ++++-- .../flame/FlameInstanceCreationTask.cpp | 3 ++- launcher/net/MetaCacheSink.cpp | 4 ++-- launcher/pathmatcher/FSTreeMatcher.h | 1 - launcher/pathmatcher/MultiMatcher.h | 1 - launcher/pathmatcher/RegexpMatcher.h | 2 ++ launcher/pathmatcher/SimplePrefixMatcher.h | 1 - launcher/ui/dialogs/NewComponentDialog.cpp | 3 ++- launcher/ui/dialogs/ProfileSetupDialog.cpp | 5 ++--- launcher/ui/pages/global/APIPage.cpp | 14 +++++++------- launcher/ui/pages/instance/ScreenshotsPage.cpp | 3 ++- launcher/updater/prismupdater/PrismUpdater.cpp | 13 ++++++------- libraries/LocalPeer/src/LocalPeer.cpp | 3 ++- libraries/qdcss/src/qdcss.cpp | 8 ++++---- libraries/systeminfo/src/distroutils.cpp | 12 +++++++----- 37 files changed, 87 insertions(+), 84 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index eab91a5eb..6fbe5eea6 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include "settings/INISettingsObject.h" #include "settings/OverrideSetting.h" diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 71630656d..633382404 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -378,8 +378,8 @@ void InstanceImportTask::processModrinth() } else { QString pack_id; if (!m_sourceUrl.isEmpty()) { - QRegularExpression regex(R"(data\/([^\/]*)\/versions)"); - pack_id = regex.match(m_sourceUrl.toString()).captured(1); + static const QRegularExpression s_regex(R"(data\/([^\/]*)\/versions)"); + pack_id = s_regex.match(m_sourceUrl.toString()).captured(1); } // FIXME: Find a way to get the ID in directly imported ZIPs diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 188edb943..b71000054 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -41,7 +41,9 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent) { - if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]")) || jvmargs.contains("-XX-MaxHeapSize") || + static const QRegularExpression s_memRegex("-Xm[sx]"); + static const QRegularExpression s_versionRegex("-version:.*"); + if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(s_memRegex) || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) { auto warnStr = QObject::tr( "You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" " @@ -52,7 +54,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent) return false; } // block lunacy with passing required version to the JVM - if (jvmargs.contains(QRegularExpression("-version:.*"))) { + if (jvmargs.contains(s_versionRegex)) { 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" diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 07047bf67..b1a956b49 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -182,7 +182,8 @@ void LaunchController::login() auto name = askOfflineName("Player", m_demo, ok); if (ok) { m_session = std::make_shared(); - m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(QRegularExpression("[{}-]"))); + static const QRegularExpression s_removeChars("[{}-]"); + m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(s_removeChars)); launchInstance(); return; } diff --git a/launcher/RecursiveFileSystemWatcher.cpp b/launcher/RecursiveFileSystemWatcher.cpp index 8b28a03f1..5cb3cd0be 100644 --- a/launcher/RecursiveFileSystemWatcher.cpp +++ b/launcher/RecursiveFileSystemWatcher.cpp @@ -1,7 +1,6 @@ #include "RecursiveFileSystemWatcher.h" #include -#include RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject* parent) : QObject(parent), m_watcher(new QFileSystemWatcher(this)) { diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 2ea67762e..b9e875482 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -213,11 +213,10 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular return qMakePair(left, right); } -static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>"); - QString StringUtils::htmlListPatch(QString htmlStr) { - int pos = htmlStr.indexOf(ulMatcher); + static const QRegularExpression s_ulMatcher("<\\s*/\\s*ul\\s*>"); + int pos = htmlStr.indexOf(s_ulMatcher); int imgPos; while (pos != -1) { pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the tag. Add one for zeroeth index @@ -230,7 +229,7 @@ QString StringUtils::htmlListPatch(QString htmlStr) if (textBetween.isEmpty()) htmlStr.insert(pos, "
"); - pos = htmlStr.indexOf(ulMatcher, pos); + pos = htmlStr.indexOf(s_ulMatcher, pos); } return htmlStr; } \ No newline at end of file diff --git a/launcher/Version.cpp b/launcher/Version.cpp index 03a16e8a0..bffe5d58a 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -1,7 +1,6 @@ #include "Version.h" #include -#include #include #include diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index bca50f2c9..e9a160ea7 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -19,9 +19,13 @@ JavaVersion& JavaVersion::operator=(const QString& javaVersionString) QRegularExpression pattern; if (javaVersionString.startsWith("1.")) { - pattern = QRegularExpression("1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); + static const QRegularExpression s_withOne( + "1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); + pattern = s_withOne; } else { - pattern = QRegularExpression("(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); + static const QRegularExpression s_withoutOne( + "(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); + pattern = s_withoutOne; } auto match = pattern.match(m_string); diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index e3e8eaec2..b67df7631 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include "MessageLevel.h" diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h index 22db7d641..a2588064f 100644 --- a/launcher/minecraft/GradleSpecifier.h +++ b/launcher/minecraft/GradleSpecifier.h @@ -54,11 +54,11 @@ struct GradleSpecifier { 4 "jdk15" 5 "jar" */ - QRegularExpression matcher( + static const QRegularExpression s_matcher( QRegularExpression::anchoredPattern("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?")); - QRegularExpressionMatch match = matcher.match(value); + QRegularExpressionMatch match = s_matcher.match(value); m_valid = match.hasMatch(); if (!m_valid) { m_invalidValue = value; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 83141158e..b8ab93d5b 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -53,7 +53,6 @@ #include "MMCTime.h" #include "java/JavaVersion.h" #include "pathmatcher/MultiMatcher.h" -#include "pathmatcher/RegexpMatcher.h" #include "launch/LaunchTask.h" #include "launch/TaskStepWrapper.h" @@ -689,9 +688,9 @@ static QString replaceTokensIn(QString text, QMap with) { // TODO: does this still work?? QString result; - QRegularExpression token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption); + static const QRegularExpression s_token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption); QStringList list; - QRegularExpressionMatchIterator i = token_regexp.globalMatch(text); + QRegularExpressionMatchIterator i = s_token_regexp.globalMatch(text); int lastCapturedEnd = 0; while (i.hasNext()) { QRegularExpressionMatch match = i.next(); diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 684869c8d..32dd1875c 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -114,9 +114,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc out->uid = root.value("fileId").toString(); } - const QRegularExpression valid_uid_regex{ QRegularExpression::anchoredPattern( + static const QRegularExpression s_validUidRegex{ QRegularExpression::anchoredPattern( QStringLiteral(R"([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*)")) }; - if (!valid_uid_regex.match(out->uid).hasMatch()) { + if (!s_validUidRegex.match(out->uid).hasMatch()) { qCritical() << "The component's 'uid' contains illegal characters! UID:" << out->uid; out->addProblem(ProblemSeverity::Error, QObject::tr("The component's 'uid' contains illegal characters! This can cause security issues.")); diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp index 08ec0fac3..a79f89529 100644 --- a/launcher/minecraft/ProfileUtils.cpp +++ b/launcher/minecraft/ProfileUtils.cpp @@ -41,7 +41,6 @@ #include #include -#include #include namespace ProfileUtils { diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index fd2082035..9dbe7ffc0 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include namespace { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 1613a42b1..86e9cc511 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -55,7 +55,8 @@ MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { - data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); + static const QRegularExpression s_removeChars("[{}-]"); + data.internalId = QUuid::createUuid().toString().remove(s_removeChars); } MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) @@ -76,14 +77,15 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username) { + static const QRegularExpression s_removeChars("[{}-]"); auto account = makeShared(); account->data.type = AccountType::Offline; account->data.yggdrasilToken.token = "0"; account->data.yggdrasilToken.validity = Validity::Certain; account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); - account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]")); + account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(s_removeChars); + account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(s_removeChars); account->data.minecraftProfile.name = username; account->data.minecraftProfile.validity = Validity::Certain; return account; @@ -231,6 +233,7 @@ bool MinecraftAccount::shouldRefresh() const void MinecraftAccount::fillSession(AuthSessionPtr session) { + static const QRegularExpression s_removeChars("[{}-]"); if (ownsMinecraft() && !hasProfile()) { session->status = AuthSession::RequiresProfileSetup; } else { @@ -248,7 +251,7 @@ void MinecraftAccount::fillSession(AuthSessionPtr session) // profile ID session->uuid = data.profileId(); if (session->uuid.isEmpty()) - session->uuid = uuidFromUsername(session->player_name).toString().remove(QRegularExpression("[{}-]")); + session->uuid = uuidFromUsername(session->player_name).toString().remove(s_removeChars); // 'legacy' or 'mojang', depending on account type session->user_type = typeString(); if (!session->access_token.isEmpty()) { diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 49d91e433..388d55628 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -53,15 +53,16 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale()) { if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) { + static const QRegularExpression s_settingUser(".*Setting user.+", QRegularExpression::CaseInsensitiveOption); std::shared_ptr connection{ new QMetaObject::Connection }; - *connection = connect( - &m_process, &LoggedProcess::log, this, [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) { - qDebug() << lines; - if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) { - APPLICATION->closeAllWindows(); - disconnect(*connection); - } - }); + *connection = connect(&m_process, &LoggedProcess::log, this, + [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) { + qDebug() << lines; + if (lines.filter(s_settingUser).length() != 0) { + APPLICATION->closeAllWindows(); + disconnect(*connection); + } + }); } connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index d1a7b8f9c..6f2efe740 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -82,8 +82,8 @@ auto Resource::name() const -> QString static void removeThePrefix(QString& string) { - QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); - string.remove(regex); + static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); + string.remove(s_regex); string = string.trimmed(); } diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 4ed3c67e3..cf8ec871c 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -3,8 +3,6 @@ #include #include #include -#include - #include "MTPixmapCache.h" #include "Version.h" diff --git a/launcher/minecraft/mod/ShaderPack.cpp b/launcher/minecraft/mod/ShaderPack.cpp index ccb344cb5..99e51fcae 100644 --- a/launcher/minecraft/mod/ShaderPack.cpp +++ b/launcher/minecraft/mod/ShaderPack.cpp @@ -22,8 +22,6 @@ #include "ShaderPack.h" -#include - void ShaderPack::setPackFormat(ShaderPackFormat new_format) { QMutexLocker locker(&m_data_lock); diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp index 04cc36310..a1ef7f525 100644 --- a/launcher/minecraft/mod/TexturePack.cpp +++ b/launcher/minecraft/mod/TexturePack.cpp @@ -21,8 +21,6 @@ #include #include -#include - #include "MTPixmapCache.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index b0e8eb101..952115bed 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -16,7 +16,7 @@ #include "minecraft/mod/ModDetails.h" #include "settings/INIFile.h" -static QRegularExpression newlineRegex("\r\n|\n|\r"); +static const QRegularExpression s_newlineRegex("\r\n|\n|\r"); namespace ModUtils { @@ -494,7 +494,7 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) } // quick and dirty line-by-line parser - auto manifestLines = QString(file.readAll()).split(newlineRegex); + auto manifestLines = QString(file.readAll()).split(s_newlineRegex); QString manifestVersion = ""; for (auto& line : manifestLines) { if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) { diff --git a/launcher/modplatform/atlauncher/ATLPackIndex.cpp b/launcher/modplatform/atlauncher/ATLPackIndex.cpp index 678db63cc..c35569d45 100644 --- a/launcher/modplatform/atlauncher/ATLPackIndex.cpp +++ b/launcher/modplatform/atlauncher/ATLPackIndex.cpp @@ -43,5 +43,6 @@ void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack& m, QJsonObject& obj) m.system = Json::ensureBoolean(obj, QString("system"), false); m.description = Json::ensureString(obj, "description", ""); - m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "").toLower() + ".png"; + static const QRegularExpression s_regex("[^A-Za-z0-9]"); + m.safeName = Json::requireString(obj, "name").replace(s_regex, "").toLower() + ".png"; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a9706a768..e22e6135d 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -69,7 +69,8 @@ PackInstallTask::PackInstallTask(UserInteractionSupport* support, QString packNa { m_support = support; m_pack_name = packName; - m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), ""); + static const QRegularExpression s_regex("[^A-Za-z0-9]"); + m_pack_safe_name = packName.replace(s_regex, ""); m_version_name = version; m_install_mode = installMode; } @@ -938,7 +939,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, QString folderToExtract = ""; if (mod.type == ModType::Extract) { folderToExtract = mod.extractFolder; - folderToExtract.remove(QRegularExpression("^/")); + static const QRegularExpression s_regex("^/"); + folderToExtract.remove(s_regex); } qDebug() << "Extracting " + mod.file + " to " + extractToDir; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 22c9e603b..c30ba5249 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -391,7 +391,8 @@ bool FlameCreationTask::createInstance() // Hack to correct some 'special sauce'... if (mcVersion.endsWith('.')) { - mcVersion.remove(QRegularExpression("[.]+$")); + static const QRegularExpression s_regex("[.]+$"); + mcVersion.remove(s_regex); logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); } diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 432c0c84b..8896f10e3 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -98,8 +98,8 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply& reply) auto cache_control_header = reply.rawHeader("Cache-Control"); qCDebug(taskMetaCacheLogC) << "Parsing 'Cache-Control' header with" << cache_control_header; - QRegularExpression max_age_expr("max-age=([0-9]+)"); - qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong(); + static const QRegularExpression s_maxAgeExpr("max-age=([0-9]+)"); + qint64 max_age = s_maxAgeExpr.match(cache_control_header).captured(1).toLongLong(); m_entry->setMaximumAge(max_age); } else if (reply.hasRawHeader("Expires")) { diff --git a/launcher/pathmatcher/FSTreeMatcher.h b/launcher/pathmatcher/FSTreeMatcher.h index 689f11979..d8d36d2c3 100644 --- a/launcher/pathmatcher/FSTreeMatcher.h +++ b/launcher/pathmatcher/FSTreeMatcher.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "IPathMatcher.h" class FSTreeMatcher : public IPathMatcher { diff --git a/launcher/pathmatcher/MultiMatcher.h b/launcher/pathmatcher/MultiMatcher.h index ccd5a9163..3ad07b643 100644 --- a/launcher/pathmatcher/MultiMatcher.h +++ b/launcher/pathmatcher/MultiMatcher.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "IPathMatcher.h" class MultiMatcher : public IPathMatcher { diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h index 18c42f887..e36516386 100644 --- a/launcher/pathmatcher/RegexpMatcher.h +++ b/launcher/pathmatcher/RegexpMatcher.h @@ -12,6 +12,8 @@ class RegexpMatcher : public IPathMatcher { m_onlyFilenamePart = !regexp.contains('/'); } + RegexpMatcher(const QRegularExpression& regex) : m_regexp(regex) { m_onlyFilenamePart = !regex.pattern().contains('/'); } + RegexpMatcher& caseSensitive(bool cs = true) { if (cs) { diff --git a/launcher/pathmatcher/SimplePrefixMatcher.h b/launcher/pathmatcher/SimplePrefixMatcher.h index ff3805179..57bf63a30 100644 --- a/launcher/pathmatcher/SimplePrefixMatcher.h +++ b/launcher/pathmatcher/SimplePrefixMatcher.h @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -#include #include "IPathMatcher.h" class SimplePrefixMatcher : public IPathMatcher { diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp index b5f8ff889..d1e420864 100644 --- a/launcher/ui/dialogs/NewComponentDialog.cpp +++ b/launcher/ui/dialogs/NewComponentDialog.cpp @@ -83,7 +83,8 @@ NewComponentDialog::~NewComponentDialog() void NewComponentDialog::updateDialogState() { auto protoUid = ui->nameTextBox->text().toLower(); - protoUid.remove(QRegularExpression("[^a-z]")); + static const QRegularExpression s_removeChars("[^a-z]"); + protoUid.remove(s_removeChars); if (protoUid.isEmpty()) { ui->uidTextBox->setPlaceholderText(originalPlaceholderText); } else { diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index dd87b249c..0b5e1a784 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -59,9 +59,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg yellowIcon = APPLICATION->getThemedIcon("status-yellow"); badIcon = APPLICATION->getThemedIcon("status-bad"); - QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}"); + static const QRegularExpression s_permittedNames("[a-zA-Z0-9_]{3,16}"); auto nameEdit = ui->nameEdit; - nameEdit->setValidator(new QRegularExpressionValidator(permittedNames)); + nameEdit->setValidator(new QRegularExpressionValidator(s_permittedNames)); nameEdit->setClearButtonEnabled(true); validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition); connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited); @@ -268,7 +268,6 @@ void ProfileSetupDialog::setupProfileFinished() QString errorMessage = tr("Network Error: %1\nHTTP Status: %2").arg(m_profile_task->errorString(), QString::number(m_profile_task->replyStatusCode())); - if (parsedError.fullyParsed) { errorMessage += "Path: " + parsedError.path + "\n"; errorMessage += "Error: " + parsedError.error + "\n"; diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index a137c4cde..a030bf316 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -59,10 +59,10 @@ APIPage::APIPage(QWidget* parent) : QWidget(parent), ui(new Ui::APIPage) int comboBoxEntries[] = { PasteUpload::PasteType::Mclogs, PasteUpload::PasteType::NullPointer, PasteUpload::PasteType::PasteGG, PasteUpload::PasteType::Hastebin }; - static QRegularExpression validUrlRegExp("https?://.+"); - static QRegularExpression validMSAClientID( + static const QRegularExpression s_validUrlRegExp("https?://.+"); + static const QRegularExpression s_validMSAClientID( QRegularExpression::anchoredPattern("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}")); - static QRegularExpression validFlameKey(QRegularExpression::anchoredPattern("\\$2[ayb]\\$.{56}")); + static const QRegularExpression s_validFlameKey(QRegularExpression::anchoredPattern("\\$2[ayb]\\$.{56}")); ui->setupUi(this); @@ -75,10 +75,10 @@ APIPage::APIPage(QWidget* parent) : QWidget(parent), ui(new Ui::APIPage) // This function needs to be called even when the ComboBox's index is still in its default state. updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); // NOTE: this allows http://, but we replace that with https later anyway - ui->metaURL->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->metaURL)); - ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); - ui->msaClientID->setValidator(new QRegularExpressionValidator(validMSAClientID, ui->msaClientID)); - ui->flameKey->setValidator(new QRegularExpressionValidator(validFlameKey, ui->flameKey)); + ui->metaURL->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->metaURL)); + ui->baseURLEntry->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->baseURLEntry)); + ui->msaClientID->setValidator(new QRegularExpressionValidator(s_validMSAClientID, ui->msaClientID)); + ui->flameKey->setValidator(new QRegularExpressionValidator(s_validFlameKey, ui->flameKey)); ui->metaURL->setPlaceholderText(BuildConfig.META_URL); ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index fa568c794..e59002a15 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -150,7 +150,8 @@ class FilterModel : public QIdentityProxyModel { return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) { QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - return result.toString().remove(QRegularExpression("\\.png$")); + static const QRegularExpression s_removeChars("\\.png$"); + return result.toString().remove(s_removeChars); } if (role == Qt::DecorationRole) { QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 365647db9..815924d52 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -721,8 +721,8 @@ QList PrismUpdaterApp::validReleaseArtifacts(const GitHubRel for_platform = false; } - auto qt_pattern = QRegularExpression("-qt(\\d+)"); - auto qt_match = qt_pattern.match(asset_name); + static const QRegularExpression s_qtPattern("-qt(\\d+)"); + auto qt_match = s_qtPattern.match(asset_name); if (for_platform && qt_match.hasMatch()) { if (platform_qt_ver.isEmpty() || platform_qt_ver.toInt() != qt_match.captured(1).toInt()) { qDebug() << "Rejecting" << asset.name << "because it is not for the correct qt version" << platform_qt_ver.toInt() << "vs" @@ -1018,12 +1018,11 @@ void PrismUpdaterApp::backupAppDir() logUpdate("manifest.txt empty or missing. making best guess at files to back up."); } logUpdate(tr("Backing up:\n %1").arg(file_list.join(",\n "))); + static const QRegularExpression s_replaceRegex("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"); auto app_dir = QDir(m_rootPath); - auto backup_dir = FS::PathCombine( - app_dir.absolutePath(), - QStringLiteral("backup_") + - QString(m_prismVersion).replace(QRegularExpression("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"), QString("_")) + "-" + - m_prismGitCommit); + auto backup_dir = + FS::PathCombine(app_dir.absolutePath(), + QStringLiteral("backup_") + QString(m_prismVersion).replace(s_replaceRegex, QString("_")) + "-" + m_prismGitCommit); FS::ensureFolderPathExists(backup_dir); auto backup_marker_path = FS::PathCombine(m_dataPath, ".prism_launcher_update_backup_path.txt"); FS::write(backup_marker_path, backup_dir.toUtf8()); diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index 3761c109e..cb74c031b 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -72,7 +72,8 @@ ApplicationId ApplicationId::fromTraditionalApp() protoId = protoId.toLower(); #endif auto prefix = protoId.section(QLatin1Char('/'), -1); - prefix.remove(QRegularExpression("[^a-zA-Z]")); + static const QRegularExpression s_removeChars("[^a-zA-Z]"); + prefix.remove(s_removeChars); prefix.truncate(6); QByteArray idc = protoId.toUtf8(); quint16 idNum = qChecksum(idc); diff --git a/libraries/qdcss/src/qdcss.cpp b/libraries/qdcss/src/qdcss.cpp index c531fb63d..bf0ef63cb 100644 --- a/libraries/qdcss/src/qdcss.cpp +++ b/libraries/qdcss/src/qdcss.cpp @@ -8,19 +8,19 @@ #include #include -QRegularExpression ruleset_re = QRegularExpression(R"([#.]?(@?\w+?)\s*\{(.*?)\})", QRegularExpression::DotMatchesEverythingOption); -QRegularExpression rule_re = QRegularExpression(R"((\S+?)\s*:\s*(?:\"(.*?)(? +static const QRegularExpression s_distoSplitRegex("\\s+"); + Sys::DistributionInfo Sys::read_os_release() { Sys::DistributionInfo out; @@ -145,7 +147,7 @@ void Sys::lsb_postprocess(Sys::LsbInfo& lsb, Sys::DistributionInfo& out) vers = lsb.codename; } else { // ubuntu, debian, gentoo, scientific, slackware, ... ? - auto parts = dist.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); + auto parts = dist.split(s_distoSplitRegex, Qt::SkipEmptyParts); if (parts.size()) { dist = parts[0]; } @@ -178,7 +180,7 @@ QString Sys::_extract_distribution(const QString& x) if (release.startsWith("suse linux enterprise")) { return "sles"; } - QStringList list = release.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); + QStringList list = release.split(s_distoSplitRegex, Qt::SkipEmptyParts); if (list.size()) { return list[0]; } @@ -187,11 +189,11 @@ QString Sys::_extract_distribution(const QString& x) QString Sys::_extract_version(const QString& x) { - QRegularExpression versionish_string(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); - QStringList list = x.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); + static const QRegularExpression s_versionishString(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); + QStringList list = x.split(s_distoSplitRegex, Qt::SkipEmptyParts); for (int i = list.size() - 1; i >= 0; --i) { QString chunk = list[i]; - if (versionish_string.match(chunk).hasMatch()) { + if (s_versionishString.match(chunk).hasMatch()) { return chunk; } } From 71da130fe4cfbc64ced56fe27a89af49b6715494 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 28 Apr 2025 23:11:52 +0300 Subject: [PATCH 178/181] ci(nix): fix the PR number Signed-off-by: Trial97 --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index a77b33521..75ef7c65a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -72,7 +72,7 @@ jobs: id: merge-commit uses: PrismLauncher/PrismLauncher/.github/actions/get-merge-commit@develop with: - pull-request-id: ${{ github.event.pull_request.id }} + pull-request-id: ${{ github.event.number }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 1c223997db10c5459b58fa93be541edfac925ac5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 21:33:12 +0000 Subject: [PATCH 179/181] chore(deps): update hendrikmuhs/ccache-action action to v1.2.18 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d5cbc893..952b7c515 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -152,7 +152,7 @@ jobs: - name: Setup ccache if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} From 93552277fe52c31eb03f4eb8ae08969f61a01891 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 09:02:58 +0300 Subject: [PATCH 180/181] fix: build error introduced in #3516 Signed-off-by: Trial97 --- launcher/ui/widgets/ProjectItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 950c5fe0a..03fa659c9 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -152,7 +152,7 @@ bool ProjectItemDelegate::editorEvent(QEvent* event, const QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); - if (!checkboxOpt.rect.contains(mouseEvent->x(), mouseEvent->y())) + if (!checkboxOpt.rect.contains(mouseEvent->pos().x(), mouseEvent->pos().y())) return false; // swallow other events From 7da32af1b26ff9695e52c19622989e1dc755b498 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 10:42:42 +0300 Subject: [PATCH 181/181] ci(nix): remove addtional > Signed-off-by: Trial97 --- .github/actions/get-merge-commit/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/get-merge-commit/action.yml b/.github/actions/get-merge-commit/action.yml index 8c67fdfc9..534d138e1 100644 --- a/.github/actions/get-merge-commit/action.yml +++ b/.github/actions/get-merge-commit/action.yml @@ -98,6 +98,6 @@ runs: if [[ "$mergeable" == "true" ]]; then echo "merge-commit-sha=$(jq -r .merge_commit_sha <<<"$prInfo")" >> "$GITHUB_OUTPUT" else - echo "# 🚨 The PR has a merge conflict!" >>> "$GITHUB_STEP_SUMMARY" + echo "# 🚨 The PR has a merge conflict!" >> "$GITHUB_STEP_SUMMARY" exit 2 fi