From b0583a32cbd5207f953ee596649c4eac6574798f Mon Sep 17 00:00:00 2001 From: Guillaume Mercier <10gui-gui10@live.ca> Date: Sat, 22 Mar 2025 23:12:20 -0400 Subject: [PATCH 1/5] Make the fluid monitor more resilient. - Add checks via the can drain and can fill methods of fluid tanks to prevent dupe bugs like the one encountered with the blockhead sink the blockhead sink doesn't check the fluid passed to it during the drain or fill calls so it ended up draining itself instead of the required fluid when ever it was working Adding some some sort of logging if the fluid extracted and the fluid requested aren't the same would be a good idea, and since there is no caching from the simulate phase, it's going to have to be done in both phases. It shouldn't just void the output though since the changes will have already been applied to the tank by the time we void out the values and we can't assume the fill function would work properly if the drain doesn't. --- .../glodblock/github/inventory/MEMonitorIFluidHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java index 54a8305f..f5d8fea6 100644 --- a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java +++ b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java @@ -57,6 +57,10 @@ public class MEMonitorIFluidHandler implements IMEMonitor { } public IAEFluidStack injectItems(IAEFluidStack input, Actionable type, BaseActionSource src) { + if (!this.handler.canFill(this.side, input.getFluid())) { + return null; + } + int filled = this.handler.fill(this.side, input.getFluidStack(), type == Actionable.MODULATE); if (type == Actionable.MODULATE) { @@ -73,6 +77,9 @@ public class MEMonitorIFluidHandler implements IMEMonitor { } public IAEFluidStack extractItems(IAEFluidStack request, Actionable type, BaseActionSource src) { + if (!this.handler.canDrain(this.side, request.getFluid())) { + return null; + } FluidStack removed = this.handler.drain(this.side, request.getFluidStack(), type == Actionable.MODULATE); if (removed != null && removed.amount != 0) { IAEFluidStack o = request.copy(); From 5d2b7cd305e8f8858d5294c1ffa72645b5839705 Mon Sep 17 00:00:00 2001 From: Guillaume Mercier Date: Tue, 25 Mar 2025 18:13:57 -0400 Subject: [PATCH 2/5] fix unintentional voiding thx cocain --- .../com/glodblock/github/inventory/MEMonitorIFluidHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java index f5d8fea6..02abc3de 100644 --- a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java +++ b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java @@ -58,7 +58,7 @@ public class MEMonitorIFluidHandler implements IMEMonitor { public IAEFluidStack injectItems(IAEFluidStack input, Actionable type, BaseActionSource src) { if (!this.handler.canFill(this.side, input.getFluid())) { - return null; + return input; } int filled = this.handler.fill(this.side, input.getFluidStack(), type == Actionable.MODULATE); From 4b66de3ddbcc9ddf84a5d06d9df30f0b8a5a90a9 Mon Sep 17 00:00:00 2001 From: Guillaume Mercier <10gui-gui10@live.ca> Date: Tue, 25 Mar 2025 21:33:23 -0400 Subject: [PATCH 3/5] Add one time log if removed fluid isn't the same as the requested fluid. This should help us diagnose similar issues in the future. --- .../github/inventory/MEMonitorIFluidHandler.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java index 02abc3de..06bf49e8 100644 --- a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java +++ b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java @@ -25,10 +25,13 @@ import appeng.api.storage.IMEMonitorHandlerReceiver; import appeng.api.storage.StorageChannel; import appeng.api.storage.data.IAEFluidStack; import appeng.api.storage.data.IItemList; +import appeng.core.AELog; import appeng.util.item.AEFluidStack; public class MEMonitorIFluidHandler implements IMEMonitor { + private static boolean WrongFluidRemovedWarnIssued = false; + private final IFluidHandler handler; private final ForgeDirection side; private IItemList cache = AEApi.instance().storage().createFluidList(); @@ -82,6 +85,16 @@ public class MEMonitorIFluidHandler implements IMEMonitor { } FluidStack removed = this.handler.drain(this.side, request.getFluidStack(), type == Actionable.MODULATE); if (removed != null && removed.amount != 0) { + // perform a one time log if the removed fluid isn't what's reported since this will create a dupe exploit + if (!WrongFluidRemovedWarnIssued && !removed.isFluidEqual(request.getFluidStack())) { + WrongFluidRemovedWarnIssued = true; + String msg = String.format( + "[AE2FC] MEMonitorIFluidHandler.extractItems got the wrong fluids while extracting from %s. expected %s got %s. Please report this message if seen: https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/new", + this.handler.getClass().getSimpleName(), + request.getFluidStack().getUnlocalizedName(), + removed.getUnlocalizedName()); + AELog.warn(msg); + } IAEFluidStack o = request.copy(); o.setStackSize(removed.amount); if (type == Actionable.MODULATE) { From ba822d614d2392e458c510b9d0501137ab7641df Mon Sep 17 00:00:00 2001 From: Guillaume Mercier <10gui-gui10@live.ca> Date: Tue, 25 Mar 2025 21:35:50 -0400 Subject: [PATCH 4/5] Add chat log to help stop future dupes early This will make it easier for players to report potential dupes in the future. --- .../inventory/MEMonitorIFluidHandler.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java index 06bf49e8..1a3aa105 100644 --- a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java +++ b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java @@ -9,6 +9,12 @@ import java.util.Map; import javax.annotation.Nonnull; +import net.minecraft.event.ClickEvent; +import net.minecraft.event.HoverEvent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.ChatStyle; +import net.minecraft.util.EnumChatFormatting; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTankInfo; @@ -88,12 +94,32 @@ public class MEMonitorIFluidHandler implements IMEMonitor { // perform a one time log if the removed fluid isn't what's reported since this will create a dupe exploit if (!WrongFluidRemovedWarnIssued && !removed.isFluidEqual(request.getFluidStack())) { WrongFluidRemovedWarnIssued = true; + String issuesUrl = "https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/new"; String msg = String.format( - "[AE2FC] MEMonitorIFluidHandler.extractItems got the wrong fluids while extracting from %s. expected %s got %s. Please report this message if seen: https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/new", + "[AE2FC] MEMonitorIFluidHandler.extractItems got the wrong fluids while extracting from %s. expected %s got %s. Please report this message if seen: ", this.handler.getClass().getSimpleName(), request.getFluidStack().getUnlocalizedName(), removed.getUnlocalizedName()); - AELog.warn(msg); + // elevate log to error if chat msg is removed to make it easier to find. + AELog.warn(msg + issuesUrl); + + ChatComponentText chatTxt = new ChatComponentText(msg); + chatTxt.setChatStyle(new ChatStyle().setColor(EnumChatFormatting.RED).setBold(true)); + + ChatComponentText chatUrl = new ChatComponentText(issuesUrl); + chatUrl.setChatStyle( + new ChatStyle().setUnderlined(true) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, issuesUrl)) + .setChatHoverEvent( + new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + new ChatComponentText("Click to open link")))); + + ChatComponentText chat = new ChatComponentText(""); + chat.appendSibling(chatTxt); + chat.appendSibling(chatUrl); + + MinecraftServer.getServer().getConfigurationManager().sendChatMsg(chat); } IAEFluidStack o = request.copy(); o.setStackSize(removed.amount); From bde2c04322c05104d0cc9593e02a3c74f62fa649 Mon Sep 17 00:00:00 2001 From: Guillaume Mercier <10gui-gui10@live.ca> Date: Tue, 25 Mar 2025 21:38:02 -0400 Subject: [PATCH 5/5] cleanup decompiled code I'm honestly surprised this hasn't caused some sort of concurrency issue yet. --- .../inventory/MEMonitorIFluidHandler.java | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java index 1a3aa105..e77e099b 100644 --- a/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java +++ b/src/main/java/com/glodblock/github/inventory/MEMonitorIFluidHandler.java @@ -1,10 +1,8 @@ package com.glodblock.github.inventory; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import javax.annotation.Nonnull; @@ -144,8 +142,6 @@ public class MEMonitorIFluidHandler implements IMEMonitor { // *Decompiled Stuff*// public TickRateModulation onTick() { - boolean changed = false; - List changes = new ArrayList<>(); FluidTankInfo[] tankProperties = this.handler.getTankInfo(this.side); IItemList currentlyOnStorage = AEApi.instance().storage().createFluidList(); @@ -157,37 +153,32 @@ public class MEMonitorIFluidHandler implements IMEMonitor { } } - Iterator var9 = this.cache.iterator(); - - IAEFluidStack is; - while (var9.hasNext()) { - is = (IAEFluidStack) var9.next(); - is.setStackSize(-is.getStackSize()); + // make diff between cache and new contents + IItemList changes = AEApi.instance().storage().createFluidList(); + // using non-enhanced for to prevent concurrency errors + for (Iterator iter = this.cache.iterator(); iter.hasNext();) { + IAEFluidStack copy = iter.next().copy(); + copy.setStackSize(-copy.getStackSize()); + changes.add(copy); } - - var9 = currentlyOnStorage.iterator(); - - while (var9.hasNext()) { - is = (IAEFluidStack) var9.next(); - this.cache.add(is); + for (IAEFluidStack is : currentlyOnStorage) { + changes.add(is); } - - var9 = this.cache.iterator(); - - while (var9.hasNext()) { - is = (IAEFluidStack) var9.next(); - if (is.getStackSize() != 0L) { - changes.add(is); + // update cache as soon as possible + this.cache = currentlyOnStorage; + // remove unchanged values + for (Iterator iter = changes.iterator(); iter.hasNext();) { + if (iter.next().getStackSize() == 0L) { + iter.remove(); } } - this.cache = currentlyOnStorage; if (!changes.isEmpty()) { this.postDifference(changes); - changed = true; + return TickRateModulation.URGENT; } - return changed ? TickRateModulation.URGENT : TickRateModulation.SLOWER; + return TickRateModulation.SLOWER; } private void postDifference(Iterable a) {