Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# 1.0.6
- Fix a crash on startup or during backup when backup data is corrupted. ([HeatherComputer/AdvancedBackups#110](https://github.com/HeatherComputer/AdvancedBackups/issues/110))

* * *

# 1.0.5
- Fix a rare crash when entering a singleplayer world. ([HeatherComputer/AdvancedBackups#110](https://github.com/HeatherComputer/AdvancedBackups/issues/110))
- Fix a rare crash when entering a singleplayer world. ([HeatherComputer/AdvancedBackups#87](https://github.com/HeatherComputer/AdvancedBackups/issues/87))
- Fix a potential crash during backup when a backup directory is missing.
- Fix a potential crash when cleaning up old backup files.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.github.gtexpert.advancedbackupspatch.mixin;

import computer.heather.advancedbackups.core.ABCore;
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

@Mixin(value = ABCore.class, remap = false)
public abstract class ABCoreMixin {

/**
* Prevent NPE when parsing a corrupted backup manifest in setActivity.
* <p>
* The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
* can return null or a manifest with null fields for certain malformed JSON,
* causing a {@link NullPointerException} when accessing {@code manifest.general.activity}.
* <p>
* This redirect converts the null case into a {@link JsonParseException},
* which is already caught and handled by the existing recovery logic.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
*/
@Redirect(method = "setActivity",
at = @At(value = "INVOKE",
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
private static Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
Object result = gson.fromJson(json, classOfT);
if (result == null) {
throw new JsonParseException("Backup manifest parsed as null");
}
BackupManifest manifest = (BackupManifest) result;
if (manifest.general == null) {
throw new JsonParseException("Backup manifest 'general' field is null");
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public abstract class AdvancedBackupsMixin {
/**
* Disable EVENT_BUS.register(this) in the constructor.
* The mod container is not yet established at construction time, so defer to preInit.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
*/
@Redirect(method = "<init>",
at = @At(value = "INVOKE",
Expand All @@ -43,6 +45,8 @@ private void redirectEventBusRegister(EventBus bus, Object target) {
/**
* Disable NetworkHandler.registerMessages() in the constructor.
* Defer network channel registration until the mod container is established.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
*/
@Redirect(method = "<init>",
at = @At(value = "INVOKE",
Expand All @@ -54,6 +58,8 @@ private void redirectNetworkRegister() {
/**
* Register the event bus and network messages at the beginning of preInit,
* where the mod container is properly established.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
*/
@Inject(method = "preInit", at = @At("HEAD"))
private void onPreInit(FMLPreInitializationEvent event, CallbackInfo ci) {
Expand All @@ -65,6 +71,8 @@ private void onPreInit(FMLPreInitializationEvent event, CallbackInfo ci) {
* Set ABCore loggers after preInit sets them on AdvancedBackups.
* Without this, ABCore.infoLogger is null on the client side (only set in onServerStarting),
* causing NPE in ClientConfigManager when receiving PacketToastTest.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
*/
@Inject(method = "preInit", at = @At("TAIL"))
private void onPreInitTail(FMLPreInitializationEvent event, CallbackInfo ci) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import java.io.File;

import computer.heather.advancedbackups.core.backups.BackupWrapper;
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
Expand All @@ -22,4 +26,32 @@ private static File[] safeListFiles(File directory) {
File[] result = directory.listFiles();
return result != null ? result : new File[0];
}

/**
* Prevent NPE when parsing a corrupted backup manifest in checkStartupBackups.
* <p>
* The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
* can return null (or a manifest with null fields) for certain malformed JSON,
* causing a {@link NullPointerException} when accessing {@code manifest.general.activity}.
* <p>
* This redirect converts the null case into a {@link JsonParseException},
* which is already caught and handled by the existing recovery logic
* (creates default manifest and writes it back to disk).
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
*/
@Redirect(method = "checkStartupBackups",
at = @At(value = "INVOKE",
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
private static Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
Object result = gson.fromJson(json, classOfT);
if (result == null) {
throw new JsonParseException("Backup manifest parsed as null");
}
BackupManifest manifest = (BackupManifest) result;
if (manifest.general == null) {
throw new JsonParseException("Backup manifest 'general' field is null");
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public abstract class ClientConfigManagerMixin {
* 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.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/87">Issue #87</a>
*/
@Inject(method = "loadOrCreateConfig", at = @At("HEAD"))
private static void ensureLoggers(CallbackInfo ci) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.github.gtexpert.advancedbackupspatch.mixin;

import computer.heather.advancedbackups.core.CoreCommandSystem;
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

@Mixin(value = CoreCommandSystem.class, remap = false)
public abstract class CoreCommandSystemMixin {

/**
* Prevent NPE when parsing a corrupted backup manifest in resetChainLength.
* <p>
* The JAR v3.7.1 only catches {@link JsonParseException}, but {@code gson.fromJson()}
* can return null or a manifest with null fields for certain malformed JSON,
* causing a {@link NullPointerException} when accessing manifest fields.
* <p>
* This redirect converts the null case into a {@link JsonParseException},
* which is already caught and handled by the existing recovery logic.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
*/
@Redirect(method = "resetChainLength",
at = @At(value = "INVOKE",
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
private static Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
Object result = gson.fromJson(json, classOfT);
if (result == null) {
throw new JsonParseException("Backup manifest parsed as null");
}
BackupManifest manifest = (BackupManifest) result;
if (manifest.incremental == null || manifest.differential == null) {
throw new JsonParseException("Backup manifest chain fields are null");
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import java.io.File;

import computer.heather.advancedbackups.core.backups.ThreadedBackup;
import computer.heather.advancedbackups.core.backups.gson.BackupManifest;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
Expand All @@ -14,6 +18,8 @@ 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.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/103">Issue #103</a>
*/
@Redirect(method = "performRename",
at = @At(value = "INVOKE",
Expand All @@ -26,6 +32,8 @@ private String[] safeListInRename(File file) {
/**
* Prevent NPE when File.list() returns null in performDelete.
* This happens when the directory does not exist or an I/O error occurs.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/103">Issue #103</a>
*/
@Redirect(method = "performDelete",
at = @At(value = "INVOKE",
Expand All @@ -34,4 +42,28 @@ private String[] safeListInDelete(File file) {
String[] result = file.list();
return result != null ? result : new String[0];
}

/**
* Prevent NPE when parsing a corrupted backup manifest in makeDifferentialOrIncrementalBackup.
* <p>
* Same issue as in {@code BackupWrapper.checkStartupBackups()} — the JAR v3.7.1
* only catches {@link JsonParseException}, but {@code gson.fromJson()} can return null
* or a manifest with null fields, causing a {@link NullPointerException}.
*
* @see <a href="https://github.com/HeatherComputer/AdvancedBackups/issues/110">Issue #110</a>
*/
@Redirect(method = "makeDifferentialOrIncrementalBackup",
at = @At(value = "INVOKE",
target = "Lcom/google/gson/Gson;fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"))
private Object safeFromJson(Gson gson, String json, Class<?> classOfT) {
Object result = gson.fromJson(json, classOfT);
if (result == null) {
throw new JsonParseException("Backup manifest parsed as null");
}
BackupManifest manifest = (BackupManifest) result;
if (manifest.differential == null || manifest.incremental == null) {
throw new JsonParseException("Backup manifest fields are null");
}
return result;
}
}
2 changes: 2 additions & 0 deletions src/main/resources/mixins.advancedbackupspatch.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"package": "com.github.gtexpert.advancedbackupspatch.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"ABCoreMixin",
"AdvancedBackupsMixin",
"BackupWrapperMixin",
"ClientConfigManagerMixin",
"CoreCommandSystemMixin",
"ThreadedBackupMixin"
]
}