From cac9b51d6307a5923bfc2590f9bc30384d440f40 Mon Sep 17 00:00:00 2001 From: Scribble Date: Mon, 23 Jun 2025 17:11:56 +0200 Subject: [PATCH 1/6] [Savestates] Started on fixing resourcepacks --- .../MixinResourcePackRepository.java | 46 +++++++++++++++++++ .../savestates/SavestateHandlerClient.java | 3 ++ .../savestates/SavestateHandlerServer.java | 2 + .../handlers/SavestatePlayerHandler.java | 9 ++++ .../com/minecrafttas/tasmod/util/Ducks.java | 4 ++ src/main/resources/tasmod.mixin.json | 1 + 6 files changed, 65 insertions(+) create mode 100644 src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java new file mode 100644 index 00000000..cacec9f5 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java @@ -0,0 +1,46 @@ +package com.minecrafttas.tasmod.mixin.savestates; + +import java.util.concurrent.locks.ReentrantLock; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import com.google.common.util.concurrent.ListenableFuture; +import com.minecrafttas.tasmod.util.Ducks.ResourcePackRepositoryDuck; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IResourcePack; +import net.minecraft.client.resources.ResourcePackRepository; + +@Mixin(ResourcePackRepository.class) +public class MixinResourcePackRepository implements ResourcePackRepositoryDuck { + + @Shadow + @Final + private ReentrantLock lock; + @Shadow + private ListenableFuture downloadingPacks; + @Shadow + private IResourcePack serverResourcePack; + + @Override + public void clearServerResourcePackBlocking() { + this.lock.lock(); + + try { + if (this.downloadingPacks != null) { + this.downloadingPacks.cancel(true); + } + + this.downloadingPacks = null; + if (this.serverResourcePack != null) { + this.serverResourcePack = null; + Minecraft.getMinecraft().refreshResources(); + } + } finally { + this.lock.unlock(); + } + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java index 15abee1e..0b3fa37d 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java @@ -28,6 +28,7 @@ import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import com.minecrafttas.tasmod.savestates.gui.GuiSavestateSavingScreen; import com.minecrafttas.tasmod.util.Ducks.ChunkProviderDuck; +import com.minecrafttas.tasmod.util.Ducks.ResourcePackRepositoryDuck; import com.minecrafttas.tasmod.util.Ducks.SubtickDuck; import com.minecrafttas.tasmod.util.Ducks.WorldClientDuck; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -151,6 +152,8 @@ public static void loadstate(String nameOfSavestate) throws Exception { return; } + ((ResourcePackRepositoryDuck) Minecraft.getMinecraft().getResourcePackRepository()).clearServerResourcePackBlocking(); + PlaybackControllerClient controller = TASmodClient.controller; TASstate state = controller.getState(); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 387078c5..e96f5f25 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -398,6 +398,8 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex worldHandler.sendChunksToClient(); + playerHandler.updateServerResourcePack(); + try { TASmod.server.sendToAll(new TASmodBufferBuilder(CLEAR_SCREEN)); } catch (Exception e) { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java index 1a1706a2..5248288b 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java @@ -225,4 +225,13 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws break; } } + + public void updateServerResourcePack() { + if (!this.server.getResourcePackUrl().isEmpty()) { + List players = server.getPlayerList().getPlayers(); + for (EntityPlayerMP player : players) { + player.loadResourcePack(this.server.getResourcePackUrl(), this.server.getResourcePackHash()); + } + } + } } diff --git a/src/main/java/com/minecrafttas/tasmod/util/Ducks.java b/src/main/java/com/minecrafttas/tasmod/util/Ducks.java index bd13bd34..ad8cb4ff 100644 --- a/src/main/java/com/minecrafttas/tasmod/util/Ducks.java +++ b/src/main/java/com/minecrafttas/tasmod/util/Ducks.java @@ -142,4 +142,8 @@ public static interface PlayerChunkMapDuck { */ public void forceTick(); } + + public static interface ResourcePackRepositoryDuck { + public void clearServerResourcePackBlocking(); + } } diff --git a/src/main/resources/tasmod.mixin.json b/src/main/resources/tasmod.mixin.json index 6f004f1d..52028c0a 100644 --- a/src/main/resources/tasmod.mixin.json +++ b/src/main/resources/tasmod.mixin.json @@ -32,6 +32,7 @@ // Savestates "savestates.MixinChunkProviderClient", "savestates.MixinWorldClient", + "savestates.MixinResourcePackRepository", // Interpolation "MixinFrustum", From a2a25e561cd3a17476b990ef98e59b49875b1434 Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 24 Jun 2025 21:37:15 +0200 Subject: [PATCH 2/6] [Savestates] Fixed world resource pack throwing errors during loadstate Co-authored-by: LudTAS <217164248+LudTAS@users.noreply.github.com> --- .../java/com/minecrafttas/tasmod/TASmod.java | 4 + .../tasmod/registries/TASmodPackets.java | 6 + .../savestates/SavestateHandlerServer.java | 12 +- .../SavestateResourcePackHandler.java | 108 ++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index 69fb8eb3..6cb78d10 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -29,6 +29,7 @@ import com.minecrafttas.tasmod.playback.metadata.builtin.StartpositionMetadataExtension; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.SavestateHandlerServer; +import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; import com.minecrafttas.tasmod.savestates.storage.builtin.SavestateMotionStorage; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer; import com.minecrafttas.tasmod.ticksync.TickSyncServer; @@ -119,6 +120,9 @@ public void onInitialize() { SavestateMotionStorage motionStorage = new SavestateMotionStorage(); PacketHandlerRegistry.register(motionStorage); EventListenerRegistry.register(motionStorage); + SavestateResourcePackHandler resourcepackHandler = new SavestateResourcePackHandler(); + PacketHandlerRegistry.register(resourcepackHandler); + EventListenerRegistry.register(resourcepackHandler); PacketHandlerRegistry.register(playUntil); EventListenerRegistry.register(playUntil); } diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java index 7c8b2094..42f4e093 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodPackets.java @@ -104,6 +104,12 @@ public enum TASmodPackets implements PacketID { Minecraft mc = Minecraft.getMinecraft(); ((ScoreboardDuck) mc.world.getScoreboard()).clearScoreboard(); }), + /** + *

Clears the resourcepack on the client side + *

SIDE: Client
+ * ARGS: none + */ + SAVESTATE_CLEAR_RESOURCEPACK, /** *

Notifies the client to clear all inputs from the input buffer in {@link PlaybackControllerClient} *

SIDE: Both
diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index e96f5f25..884e9e0b 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -40,6 +40,7 @@ import com.minecrafttas.tasmod.savestates.files.SavestateDataFile.DataValues; import com.minecrafttas.tasmod.savestates.files.SavestateTrackerFile; import com.minecrafttas.tasmod.savestates.handlers.SavestatePlayerHandler; +import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler; import com.minecrafttas.tasmod.util.LoggerMarkers; import com.minecrafttas.tasmod.util.Scheduler.Task; @@ -386,6 +387,9 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex // Reenable level saving worldHandler.enableLevelSaving(); + // Refresh server resourcepacks on the client + SavestateResourcePackHandler.refreshServerResourcepack(server); + // Incrementing info file SavestateTrackerFile tracker = new SavestateTrackerFile(savestateDirectory.resolve(worldname + "-info.txt")); tracker.increaseLoadstateCount(); @@ -755,9 +759,9 @@ private int legacyIndexFile(Path savestateDat) { public PacketID[] getAcceptedPacketIDs() { return new TASmodPackets[] { //@formatter:off - TASmodPackets.SAVESTATE_SAVE, - TASmodPackets.SAVESTATE_LOAD, - TASmodPackets.SAVESTATE_SCREEN, + TASmodPackets.SAVESTATE_SAVE, + TASmodPackets.SAVESTATE_LOAD, + TASmodPackets.SAVESTATE_SCREEN, TASmodPackets.SAVESTATE_UNLOAD_CHUNKS //@formatter:on }; @@ -772,7 +776,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws switch (packet) { case SAVESTATE_SAVE: - Integer index = TASmodBufferBuilder.readInt(buf); + int index = TASmodBufferBuilder.readInt(buf); Task savestateTask = () -> { try { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java new file mode 100644 index 00000000..d8f203c0 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java @@ -0,0 +1,108 @@ +package com.minecrafttas.tasmod.savestates.handlers; + +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.minecrafttas.mctcommon.networking.Client.Side; +import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException; +import com.minecrafttas.mctcommon.networking.exception.WrongSideException; +import com.minecrafttas.mctcommon.networking.interfaces.ClientPacketHandler; +import com.minecrafttas.mctcommon.networking.interfaces.PacketID; +import com.minecrafttas.mctcommon.networking.interfaces.ServerPacketHandler; +import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.tasmod.TASmodClient; +import com.minecrafttas.tasmod.events.EventSavestate; +import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; +import com.minecrafttas.tasmod.registries.TASmodPackets; +import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; +import com.minecrafttas.tasmod.util.Ducks.ResourcePackRepositoryDuck; +import com.minecrafttas.tasmod.util.LoggerMarkers; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; + +public class SavestateResourcePackHandler implements EventSavestate.EventServerLoadstate, ServerPacketHandler, ClientPacketHandler { + + private CompletableFuture future; + + @Override + public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) { + if (server.getResourcePackUrl().isEmpty() || server.isDedicatedServer()) + return; + + String serverOwnerName = server.getServerOwner(); + + try { + TASmod.server.sendTo(serverOwnerName, new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + future = new CompletableFuture<>(); + + String playername = null; + try { + playername = future.get(2L, TimeUnit.MINUTES); + } catch (TimeoutException e) { + throw new SavestateException(e, "Clearing resourcepacks %s timed out!", serverOwnerName); + } catch (ExecutionException | InterruptedException e) { + throw new SavestateException(e, "Clearing resourcepacks %s", serverOwnerName); + } + + TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Cleared resourcepack for player {}", playername); + } + + @Override + public PacketID[] getAcceptedPacketIDs() { + return new TASmodPackets[] { TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK }; + } + + @Override + public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + TASmodPackets packetId = (TASmodPackets) id; + + Minecraft mc = Minecraft.getMinecraft(); + switch (packetId) { + case SAVESTATE_CLEAR_RESOURCEPACK: + mc.addScheduledTask(() -> { + ResourcePackRepositoryDuck duck = (ResourcePackRepositoryDuck) mc.getResourcePackRepository(); + duck.clearServerResourcePackBlocking(); + try { + TASmodClient.client.send(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } + }); + break; + + default: + throw new WrongSideException(packetId, Side.CLIENT); + } + } + + @Override + public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { + TASmodPackets packetId = (TASmodPackets) id; + + switch (packetId) { + case SAVESTATE_CLEAR_RESOURCEPACK: + future.complete(username); + break; + + default: + throw new WrongSideException(packetId, Side.SERVER); + } + } + + public static void refreshServerResourcepack(MinecraftServer server) { + List players = server.getPlayerList().getPlayers(); + players.forEach((player) -> { + player.loadResourcePack(server.getResourcePackUrl(), server.getResourcePackHash()); + }); + } +} From 157c30e48aea0da6a266a5e82f1442766dcfa436 Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 15 Jul 2025 16:36:48 +0200 Subject: [PATCH 3/6] [Savestates] Fix loading savestates with RPs from commands softlocking --- .../tasmod/commands/CommandSavestate.java | 32 ++++++++++++++++--- .../savestates/MixinMinecraftServer.java | 22 +++++++++++++ .../SavestateResourcePackHandler.java | 11 +++++++ src/main/resources/tasmod.mixin.json | 1 + 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraftServer.java diff --git a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java index 7abd7317..63d66b46 100644 --- a/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java +++ b/src/main/java/com/minecrafttas/tasmod/commands/CommandSavestate.java @@ -42,17 +42,41 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } else if (args.length >= 1) { if ("save".equals(args[0])) { if (args.length == 1) { - saveLatest(); + TASmod.gameLoopSchedulerServer.add(() -> { + try { + saveLatest(); + } catch (CommandException e) { + e.printStackTrace(); + } + }); } else if (args.length == 2) { - saveWithIndex(args); + TASmod.gameLoopSchedulerServer.add(() -> { + try { + saveWithIndex(args); + } catch (CommandException e) { + e.printStackTrace(); + } + }); } else { throw new CommandException("Too many arguments!", new Object[] {}); } } else if ("load".equals(args[0])) { if (args.length == 1) { - loadLatest(); + TASmod.gameLoopSchedulerServer.add(() -> { + try { + loadLatest(); + } catch (CommandException e) { + e.printStackTrace(); + } + }); } else if (args.length == 2) { - loadLatest(args); + TASmod.gameLoopSchedulerServer.add(() -> { + try { + loadLatest(args); + } catch (CommandException e) { + e.printStackTrace(); + } + }); } else { throw new CommandException("Too many arguments!", new Object[] {}); } diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraftServer.java b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraftServer.java new file mode 100644 index 00000000..caea3783 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraftServer.java @@ -0,0 +1,22 @@ +package com.minecrafttas.tasmod.mixin.savestates; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; + +import net.minecraft.server.MinecraftServer; + +@Mixin(MinecraftServer.class) +public class MixinMinecraftServer { + + @Inject(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;reloadResources()V")) + public void inject_reload(CallbackInfo ci) { + if (SavestateResourcePackHandler.clientRPLatch != null && SavestateResourcePackHandler.clientRPLatch.getCount() > 0) { + System.out.println("Countdown"); + SavestateResourcePackHandler.clientRPLatch.countDown(); + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java index d8f203c0..cf7cf64d 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -31,6 +32,8 @@ public class SavestateResourcePackHandler implements EventSavestate.EventServerL private CompletableFuture future; + public static CountDownLatch clientRPLatch; + @Override public void onServerLoadstate(MinecraftServer server, int index, Path target, Path current) { if (server.getResourcePackUrl().isEmpty() || server.isDedicatedServer()) @@ -70,8 +73,16 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws switch (packetId) { case SAVESTATE_CLEAR_RESOURCEPACK: mc.addScheduledTask(() -> { + clientRPLatch = new CountDownLatch(1); ResourcePackRepositoryDuck duck = (ResourcePackRepositoryDuck) mc.getResourcePackRepository(); duck.clearServerResourcePackBlocking(); + + try { + clientRPLatch.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + try { TASmodClient.client.send(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK)); } catch (Exception e) { diff --git a/src/main/resources/tasmod.mixin.json b/src/main/resources/tasmod.mixin.json index 52028c0a..206695fb 100644 --- a/src/main/resources/tasmod.mixin.json +++ b/src/main/resources/tasmod.mixin.json @@ -16,6 +16,7 @@ "savestates.MixinChunkProviderServer", "savestates.MixinNetHandlerPlayServer", "savestates.MixinScoreboard", + "savestates.MixinMinecraftServer", // Events "events.MixinEntityPlayerMP", From 79bcbc20ddd080b70807df51dcb6ef68ec547b52 Mon Sep 17 00:00:00 2001 From: Scribble Date: Wed, 16 Jul 2025 11:37:56 +0200 Subject: [PATCH 4/6] [Savestates] Fix link to file not being cleared --- ...necraftServer.java => MixinMinecraft.java} | 10 +-- .../MixinResourcePackRepository.java | 46 ---------- .../tasmod/registries/TASmodKeybinds.java | 2 + .../savestates/SavestateHandlerClient.java | 3 - .../SavestateResourcePackHandler.java | 84 ++++++++++++++----- .../com/minecrafttas/tasmod/util/Ducks.java | 4 - src/main/resources/tasmod.mixin.json | 3 +- 7 files changed, 70 insertions(+), 82 deletions(-) rename src/main/java/com/minecrafttas/tasmod/mixin/savestates/{MixinMinecraftServer.java => MixinMinecraft.java} (66%) delete mode 100644 src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraftServer.java b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java similarity index 66% rename from src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraftServer.java rename to src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java index caea3783..9d530925 100644 --- a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraftServer.java +++ b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java @@ -7,13 +7,13 @@ import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; -import net.minecraft.server.MinecraftServer; +import net.minecraft.client.Minecraft; -@Mixin(MinecraftServer.class) -public class MixinMinecraftServer { +@Mixin(Minecraft.class) +public class MixinMinecraft { - @Inject(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;reloadResources()V")) - public void inject_reload(CallbackInfo ci) { + @Inject(method = "refreshResources", at = @At(value = "RETURN")) + public void inject_refreshResources(CallbackInfo ci) { if (SavestateResourcePackHandler.clientRPLatch != null && SavestateResourcePackHandler.clientRPLatch.getCount() > 0) { System.out.println("Countdown"); SavestateResourcePackHandler.clientRPLatch.countDown(); diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java deleted file mode 100644 index cacec9f5..00000000 --- a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinResourcePackRepository.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.minecrafttas.tasmod.mixin.savestates; - -import java.util.concurrent.locks.ReentrantLock; - -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import com.google.common.util.concurrent.ListenableFuture; -import com.minecrafttas.tasmod.util.Ducks.ResourcePackRepositoryDuck; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.IResourcePack; -import net.minecraft.client.resources.ResourcePackRepository; - -@Mixin(ResourcePackRepository.class) -public class MixinResourcePackRepository implements ResourcePackRepositoryDuck { - - @Shadow - @Final - private ReentrantLock lock; - @Shadow - private ListenableFuture downloadingPacks; - @Shadow - private IResourcePack serverResourcePack; - - @Override - public void clearServerResourcePackBlocking() { - this.lock.lock(); - - try { - if (this.downloadingPacks != null) { - this.downloadingPacks.cancel(true); - } - - this.downloadingPacks = null; - if (this.serverResourcePack != null) { - this.serverResourcePack = null; - Minecraft.getMinecraft().refreshResources(); - } - } finally { - this.lock.unlock(); - } - } - -} diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java index f36acdb2..7fbdc872 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java @@ -7,6 +7,7 @@ import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; +import com.minecrafttas.tasmod.savestates.gui.GuiResourcepackWarn; import com.minecrafttas.tasmod.virtual.VirtualKeybindings; import net.minecraft.client.Minecraft; @@ -40,6 +41,7 @@ public enum TASmodKeybinds { }), TEST1("Various Testing", "TASmod", Keyboard.KEY_F12, () -> { + Minecraft.getMinecraft().displayGuiScreen(new GuiResourcepackWarn()); }, VirtualKeybindings::isKeyDown), TEST2("Various Testing2", "TASmod", Keyboard.KEY_F7, () -> { }, VirtualKeybindings::isKeyDown); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java index 0b3fa37d..15abee1e 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerClient.java @@ -28,7 +28,6 @@ import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; import com.minecrafttas.tasmod.savestates.gui.GuiSavestateSavingScreen; import com.minecrafttas.tasmod.util.Ducks.ChunkProviderDuck; -import com.minecrafttas.tasmod.util.Ducks.ResourcePackRepositoryDuck; import com.minecrafttas.tasmod.util.Ducks.SubtickDuck; import com.minecrafttas.tasmod.util.Ducks.WorldClientDuck; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -152,8 +151,6 @@ public static void loadstate(String nameOfSavestate) throws Exception { return; } - ((ResourcePackRepositoryDuck) Minecraft.getMinecraft().getResourcePackRepository()).clearServerResourcePackBlocking(); - PlaybackControllerClient controller = TASmodClient.controller; TASstate state = controller.getState(); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java index cf7cf64d..418bd08b 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java @@ -21,17 +21,29 @@ import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; -import com.minecrafttas.tasmod.util.Ducks.ResourcePackRepositoryDuck; import com.minecrafttas.tasmod.util.LoggerMarkers; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.MinecraftServer; +/** + * Handles reloading server resourcepacks when loadstating. + * + * @author Scribble + */ public class SavestateResourcePackHandler implements EventSavestate.EventServerLoadstate, ServerPacketHandler, ClientPacketHandler { - private CompletableFuture future; + /** + * The server future for waiting until the client is done unloading the RP + */ + private CompletableFuture serverRPFuture; + /** + * The latch for waiting until the client RP is unloaded + */ public static CountDownLatch clientRPLatch; @Override @@ -46,17 +58,18 @@ public void onServerLoadstate(MinecraftServer server, int index, Path target, Pa } catch (Exception e) { TASmod.LOGGER.catching(e); } - future = new CompletableFuture<>(); + serverRPFuture = new CompletableFuture<>(); String playername = null; try { - playername = future.get(2L, TimeUnit.MINUTES); + playername = serverRPFuture.get(2L, TimeUnit.MINUTES); } catch (TimeoutException e) { throw new SavestateException(e, "Clearing resourcepacks %s timed out!", serverOwnerName); } catch (ExecutionException | InterruptedException e) { throw new SavestateException(e, "Clearing resourcepacks %s", serverOwnerName); } + server.setResourcePack("", ""); TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Cleared resourcepack for player {}", playername); } @@ -65,6 +78,7 @@ public PacketID[] getAcceptedPacketIDs() { return new TASmodPackets[] { TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK }; } + @Environment(EnvType.CLIENT) @Override public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws PacketNotImplementedException, WrongSideException, Exception { TASmodPackets packetId = (TASmodPackets) id; @@ -72,23 +86,43 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws Minecraft mc = Minecraft.getMinecraft(); switch (packetId) { case SAVESTATE_CLEAR_RESOURCEPACK: - mc.addScheduledTask(() -> { - clientRPLatch = new CountDownLatch(1); - ResourcePackRepositoryDuck duck = (ResourcePackRepositoryDuck) mc.getResourcePackRepository(); - duck.clearServerResourcePackBlocking(); - - try { - clientRPLatch.await(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - try { - TASmodClient.client.send(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK)); - } catch (Exception e) { - TASmod.LOGGER.catching(e); - } - }); + + TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Clearing server resource pack"); + + /** + * Using a countdown latch here, which is counted down in + * savestates.MixinMinecraft. + * + * Clearing the resourcepack is scheduled multiple times + * so for simplicity, I use a latch here. + */ + clientRPLatch = new CountDownLatch(1); + mc.getResourcePackRepository().clearResourcePack(); + + try { + clientRPLatch.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + /** + * At this point, "clearResourcePack" did remove the server RP + * however, the file association with the "resources.zip" in the + * save folder is still there, which causes loadstating to fail, + * as the system still thinks that the RP is still "in use". + * + * We have to run the garbage collector to remove it. + */ + System.gc(); + + /** + * Notify the server that savestates have been cleared and that savestating can continue + */ + try { + TASmodClient.client.send(new TASmodBufferBuilder(TASmodPackets.SAVESTATE_CLEAR_RESOURCEPACK)); + } catch (Exception e) { + TASmod.LOGGER.catching(e); + } break; default: @@ -102,7 +136,7 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws switch (packetId) { case SAVESTATE_CLEAR_RESOURCEPACK: - future.complete(username); + serverRPFuture.complete(username); break; default: @@ -110,7 +144,13 @@ public void onServerPacket(PacketID id, ByteBuffer buf, String username) throws } } + /** + * Notifies all clients that a new server resourcepack should be downloaded if available + * + * @param server The Minecraft server + */ public static void refreshServerResourcepack(MinecraftServer server) { + TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Refreshing resourcepack"); List players = server.getPlayerList().getPlayers(); players.forEach((player) -> { player.loadResourcePack(server.getResourcePackUrl(), server.getResourcePackHash()); diff --git a/src/main/java/com/minecrafttas/tasmod/util/Ducks.java b/src/main/java/com/minecrafttas/tasmod/util/Ducks.java index ad8cb4ff..bd13bd34 100644 --- a/src/main/java/com/minecrafttas/tasmod/util/Ducks.java +++ b/src/main/java/com/minecrafttas/tasmod/util/Ducks.java @@ -142,8 +142,4 @@ public static interface PlayerChunkMapDuck { */ public void forceTick(); } - - public static interface ResourcePackRepositoryDuck { - public void clearServerResourcePackBlocking(); - } } diff --git a/src/main/resources/tasmod.mixin.json b/src/main/resources/tasmod.mixin.json index 206695fb..b500e7d6 100644 --- a/src/main/resources/tasmod.mixin.json +++ b/src/main/resources/tasmod.mixin.json @@ -16,7 +16,6 @@ "savestates.MixinChunkProviderServer", "savestates.MixinNetHandlerPlayServer", "savestates.MixinScoreboard", - "savestates.MixinMinecraftServer", // Events "events.MixinEntityPlayerMP", @@ -33,7 +32,7 @@ // Savestates "savestates.MixinChunkProviderClient", "savestates.MixinWorldClient", - "savestates.MixinResourcePackRepository", + "savestates.MixinMinecraft", // Interpolation "MixinFrustum", From 5b91d9e36c2d988d34923bac4b4ce9a1d29605d0 Mon Sep 17 00:00:00 2001 From: Scribble Date: Wed, 16 Jul 2025 11:37:56 +0200 Subject: [PATCH 5/6] [Savestates] Add warning screen when resources.zip is present Specifically, if the resources.zip and the user is loadstating is present, then it will show a message saying that the loadstating takes severely longer Added a MessageUtils helper that can split lines with "\n" into multiple lines automatically --- .../mixin/savestates/MixinMinecraft.java | 1 - .../tasmod/registries/TASmodKeybinds.java | 2 - .../savestates/gui/GuiResourcepackWarn.java | 43 +++++++++++++++++++ .../SavestateResourcePackHandler.java | 3 ++ .../tasmod/util/MessageUtils.java | 23 ++++++++++ .../resources/assets/tasmod/lang/en_us.json | 5 ++- 6 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiResourcepackWarn.java create mode 100644 src/main/java/com/minecrafttas/tasmod/util/MessageUtils.java diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java index 9d530925..2662e459 100644 --- a/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java +++ b/src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinMinecraft.java @@ -15,7 +15,6 @@ public class MixinMinecraft { @Inject(method = "refreshResources", at = @At(value = "RETURN")) public void inject_refreshResources(CallbackInfo ci) { if (SavestateResourcePackHandler.clientRPLatch != null && SavestateResourcePackHandler.clientRPLatch.getCount() > 0) { - System.out.println("Countdown"); SavestateResourcePackHandler.clientRPLatch.countDown(); } } diff --git a/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java b/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java index 7fbdc872..f36acdb2 100644 --- a/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java +++ b/src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java @@ -7,7 +7,6 @@ import com.minecrafttas.tasmod.TASmodClient; import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.playback.PlaybackControllerClient.TASstate; -import com.minecrafttas.tasmod.savestates.gui.GuiResourcepackWarn; import com.minecrafttas.tasmod.virtual.VirtualKeybindings; import net.minecraft.client.Minecraft; @@ -41,7 +40,6 @@ public enum TASmodKeybinds { }), TEST1("Various Testing", "TASmod", Keyboard.KEY_F12, () -> { - Minecraft.getMinecraft().displayGuiScreen(new GuiResourcepackWarn()); }, VirtualKeybindings::isKeyDown), TEST2("Various Testing2", "TASmod", Keyboard.KEY_F7, () -> { }, VirtualKeybindings::isKeyDown); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiResourcepackWarn.java b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiResourcepackWarn.java new file mode 100644 index 00000000..bcf3f560 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/gui/GuiResourcepackWarn.java @@ -0,0 +1,43 @@ +package com.minecrafttas.tasmod.savestates.gui; + +import com.minecrafttas.tasmod.util.MessageUtils; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.resources.I18n; + +/** + * Screen for warning the player that a "reources.zip" is present in the world folder,
+ * which significantly slows down savestates + */ +public class GuiResourcepackWarn extends GuiScreen { + + /** + * Screen for warning the player that a "reources.zip" is present in the world folder,
+ * which significantly slows down savestates + */ + public GuiResourcepackWarn() { + this.mc = Minecraft.getMinecraft(); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + this.drawDefaultBackground(); + + ScaledResolution scaled = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaled.getScaledWidth(); + int height = scaled.getScaledHeight(); + + MessageUtils.splitNewline(I18n.format("gui.tasmod.savestate.resourcepack"), 15, (line, y) -> { + drawCenteredString(fontRenderer, line, width / 2, height / 4 + 40 + y, 0xFF5555); + }); + + super.drawScreen(mouseX, mouseY, partialTicks); + } + + @Override + public boolean doesGuiPauseGame() { + return false; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java index 418bd08b..520280b5 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateResourcePackHandler.java @@ -21,6 +21,7 @@ import com.minecrafttas.tasmod.networking.TASmodBufferBuilder; import com.minecrafttas.tasmod.registries.TASmodPackets; import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; +import com.minecrafttas.tasmod.savestates.gui.GuiResourcepackWarn; import com.minecrafttas.tasmod.util.LoggerMarkers; import net.fabricmc.api.EnvType; @@ -89,6 +90,8 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws TASmod.LOGGER.debug(LoggerMarkers.Savestate, "Clearing server resource pack"); + mc.displayGuiScreen(new GuiResourcepackWarn()); + /** * Using a countdown latch here, which is counted down in * savestates.MixinMinecraft. diff --git a/src/main/java/com/minecrafttas/tasmod/util/MessageUtils.java b/src/main/java/com/minecrafttas/tasmod/util/MessageUtils.java new file mode 100644 index 00000000..6fc5c3ae --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/util/MessageUtils.java @@ -0,0 +1,23 @@ +package com.minecrafttas.tasmod.util; + +public class MessageUtils { + + public static void splitNewline(String phrase, GuiInterface method) { + splitNewline(phrase, 10, method); + } + + public static void splitNewline(String phrase, int distance, GuiInterface method) { + int y = 0; + String[] lines = phrase.split("\r?\n"); + for (String line : lines) { + method.draw(line, y); + y += distance; + } + } + + @FunctionalInterface + public interface GuiInterface { + + public void draw(String line, int y); + } +} diff --git a/src/main/resources/assets/tasmod/lang/en_us.json b/src/main/resources/assets/tasmod/lang/en_us.json index 42434be2..8c785e0a 100644 --- a/src/main/resources/assets/tasmod/lang/en_us.json +++ b/src/main/resources/assets/tasmod/lang/en_us.json @@ -1,4 +1,5 @@ { - "tickratechanger.tasmod.command.show": "Current tickrate: %s", - "tickratechanger.tasmod.command.usage": "/tickrate " + "tickratechanger.tasmod.command.show":"Current tickrate: %s", + "tickratechanger.tasmod.command.usage":"/tickrate ", + "gui.tasmod.savestate.resourcepack":"You have a resource pack (resources.zip) in your world folder.\nLoadstating will take severely longer!" } \ No newline at end of file From 4274829459b4ce0a2f2ec15440942b90b32e4a23 Mon Sep 17 00:00:00 2001 From: Scribble Date: Wed, 16 Jul 2025 13:43:15 +0200 Subject: [PATCH 6/6] [Savestates] Fix resourcepacks being refreshed twice --- .../tasmod/savestates/SavestateHandlerServer.java | 2 -- .../savestates/handlers/SavestatePlayerHandler.java | 9 --------- 2 files changed, 11 deletions(-) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java index 884e9e0b..05f91369 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java @@ -402,8 +402,6 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex worldHandler.sendChunksToClient(); - playerHandler.updateServerResourcePack(); - try { TASmod.server.sendToAll(new TASmodBufferBuilder(CLEAR_SCREEN)); } catch (Exception e) { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java index 5248288b..1a1706a2 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestatePlayerHandler.java @@ -225,13 +225,4 @@ public void onClientPacket(PacketID id, ByteBuffer buf, String username) throws break; } } - - public void updateServerResourcePack() { - if (!this.server.getResourcePackUrl().isEmpty()) { - List players = server.getPlayerList().getPlayers(); - for (EntityPlayerMP player : players) { - player.loadResourcePack(this.server.getResourcePackUrl(), this.server.getResourcePackHash()); - } - } - } }