From 3a8b2a355121bab158323ec73dc1b4578a270b92 Mon Sep 17 00:00:00 2001 From: tier940 Date: Thu, 26 Mar 2026 12:09:40 +0900 Subject: [PATCH] Fix rare singleplayer crash and potential NPE in backup operations --- .github/workflows/build_and_test.yml | 28 +------------ .github/workflows/publish.yml | 4 +- CHANGELOG.md | 7 ++++ .../mixin/BackupWrapperMixin.java | 25 +++++++++++ .../mixin/ClientConfigManagerMixin.java | 41 +++++++++++++++++++ .../mixin/ThreadedBackupMixin.java | 37 +++++++++++++++++ .../mixins.advancedbackupspatch.json | 5 ++- 7 files changed, 118 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/BackupWrapperMixin.java create mode 100644 src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java create mode 100644 src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index f35b8c1..f568af3 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -42,7 +42,7 @@ jobs: - name: Upload Jars uses: actions/upload-artifact@v7 with: - name: GTExpert-Core + name: AdvancedBackups-Patch path: build/libs/*.jar retention-days: 31 @@ -50,30 +50,6 @@ jobs: id: build_mod run: ./gradlew --info build - - name: Attempt to make a PR fixing spotless errors - if: ${{ failure() && steps.build_mod.conclusion == 'failure' && github.event_name == 'pull_request' && !github.event.pull_request.draft }} - run: | - git reset --hard - git checkout "${github.ref_name}" - ./gradlew --info spotlessApply || exit 1 - git diff --exit-code && exit 1 - git config user.name "GitHub Actions" - git config user.email "<>" - git switch -c "${FIXED_BRANCH}" - git commit -am "spotlessApply" - git push --force-with-lease origin "${FIXED_BRANCH}" - gh pr create \ - --head "${FIXED_BRANCH}" \ - --base "${github.ref_name}" \ - --title "Spotless apply for branch ${{ github.event.pull_request.head.ref }} for #${{ github.event.pull_request.number }}" \ - --body "Automatic spotless apply to fix formatting errors, applies to PR #${{ github.event.pull_request.number }}" \ - 2>&1 | tee pr-message.log || true - gh pr comment "${github.ref_name}" -F pr-message.log || true - shell: bash # ensures set -eo pipefail - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - FIXED_BRANCH: ${{ github.head_ref }}-spotless-fixes - - name: Run server for ${{ inputs.timeout }} seconds if: ${{ !inputs.client-only }} run: | @@ -81,7 +57,7 @@ jobs: echo "eula=true" > run/server/eula.txt # Set a constant seed with a village at spawn echo "stop" > run/server/stop.txt - timeout ${{ inputs.timeout }} ./gradlew runServer 2>&1 < run/stop.txt | tee -a server.log || true + timeout ${{ inputs.timeout }} ./gradlew runServer 2>&1 < run/server/stop.txt | tee -a server.log || true - name: Test no errors reported during server run if: ${{ !inputs.client-only }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 794601c..88a6689 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -49,8 +49,8 @@ jobs: - name: Check if tag already exists run: | - if git rev-parse --verify --quiet "v${{ github.event.inputs.version }}"; then - echo "Version ${{ github.event.inputs.version }} already exists, aborting workflow." + if git rev-parse --verify --quiet "v${{ env.FULL_VERSION }}"; then + echo "Version ${{ env.FULL_VERSION }} already exists, aborting workflow." exit 1 fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 05dbbdb..c0a378f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.0.5 +- Fix a rare crash when entering a singleplayer world. ([HeatherComputer/AdvancedBackups#110](https://github.com/HeatherComputer/AdvancedBackups/issues/110)) +- Fix a potential crash during backup when a backup directory is missing. +- Fix a potential crash when cleaning up old backup files. + +* * * + # 1.0.4 - Fix mod info. diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/BackupWrapperMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/BackupWrapperMixin.java new file mode 100644 index 0000000..76e0534 --- /dev/null +++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/BackupWrapperMixin.java @@ -0,0 +1,25 @@ +package com.github.gtexpert.advancedbackupspatch.mixin; + +import java.io.File; + +import computer.heather.advancedbackups.core.backups.BackupWrapper; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = BackupWrapper.class, remap = false) +public abstract class BackupWrapperMixin { + + /** + * Prevent NPE when File.listFiles() returns null in deleteDirectoryContents. + * This happens when the path is not a directory or an I/O error occurs. + */ + @Redirect(method = "deleteDirectoryContents", + at = @At(value = "INVOKE", + target = "Ljava/io/File;listFiles()[Ljava/io/File;")) + private static File[] safeListFiles(File directory) { + File[] result = directory.listFiles(); + return result != null ? result : new File[0]; + } +} diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java new file mode 100644 index 0000000..3c095c7 --- /dev/null +++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ClientConfigManagerMixin.java @@ -0,0 +1,41 @@ +package com.github.gtexpert.advancedbackupspatch.mixin; + +import computer.heather.advancedbackups.core.ABCore; +import computer.heather.advancedbackups.core.config.ClientConfigManager; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ClientConfigManager.class, remap = false) +public abstract class ClientConfigManagerMixin { + + @Unique + private static final Logger advancedbackupspatch$LOGGER = LogManager.getLogger("advancedbackups"); + + /** + * Ensure ABCore loggers are non-null before ClientConfigManager uses them. + *

+ * In singleplayer, PacketToastTest is handled on the Netty thread, + * which may not see logger writes from the IntegratedServer thread + * due to the Java Memory Model (non-volatile fields). + * This provides a fallback to prevent NPE. + */ + @Inject(method = "loadOrCreateConfig", at = @At("HEAD")) + private static void ensureLoggers(CallbackInfo ci) { + if (ABCore.infoLogger == null) { + ABCore.infoLogger = advancedbackupspatch$LOGGER::info; + } + if (ABCore.warningLogger == null) { + ABCore.warningLogger = advancedbackupspatch$LOGGER::warn; + } + if (ABCore.errorLogger == null) { + ABCore.errorLogger = advancedbackupspatch$LOGGER::error; + } + } +} diff --git a/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java new file mode 100644 index 0000000..6d12167 --- /dev/null +++ b/src/main/java/com/github/gtexpert/advancedbackupspatch/mixin/ThreadedBackupMixin.java @@ -0,0 +1,37 @@ +package com.github.gtexpert.advancedbackupspatch.mixin; + +import java.io.File; + +import computer.heather.advancedbackups.core.backups.ThreadedBackup; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = ThreadedBackup.class, remap = false) +public abstract class ThreadedBackupMixin { + + /** + * Prevent NPE when File.list() returns null in performRename. + * This happens when the directory does not exist or an I/O error occurs. + */ + @Redirect(method = "performRename", + at = @At(value = "INVOKE", + target = "Ljava/io/File;list()[Ljava/lang/String;")) + private String[] safeListInRename(File file) { + String[] result = file.list(); + return result != null ? result : new String[0]; + } + + /** + * Prevent NPE when File.list() returns null in performDelete. + * This happens when the directory does not exist or an I/O error occurs. + */ + @Redirect(method = "performDelete", + at = @At(value = "INVOKE", + target = "Ljava/io/File;list()[Ljava/lang/String;")) + private String[] safeListInDelete(File file) { + String[] result = file.list(); + return result != null ? result : new String[0]; + } +} diff --git a/src/main/resources/mixins.advancedbackupspatch.json b/src/main/resources/mixins.advancedbackupspatch.json index 80e0da9..5ae7042 100644 --- a/src/main/resources/mixins.advancedbackupspatch.json +++ b/src/main/resources/mixins.advancedbackupspatch.json @@ -3,6 +3,9 @@ "package": "com.github.gtexpert.advancedbackupspatch.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ - "AdvancedBackupsMixin" + "AdvancedBackupsMixin", + "BackupWrapperMixin", + "ClientConfigManagerMixin", + "ThreadedBackupMixin" ] }