From f506d5ac652a000f24ed418ef976b9a045ff8253 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 18 Oct 2023 12:09:59 +0200 Subject: [PATCH 1/8] Fix missing networking-api-v1 on 1.16.4/5 We recently bumped the fabric-api version there, and newer versions of the v0 api just delegate to the v1 one now, so we need to include that there as well. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5337e60a..f0661b4e 100644 --- a/build.gradle +++ b/build.gradle @@ -312,10 +312,10 @@ dependencies { } if (mcVersion >= 11604) { fabricApiModules.add("screen-api-v1") + fabricApiModules.add("networking-api-v1") } if (mcVersion >= 11700) { fabricApiModules.remove("networking-v0") - fabricApiModules.add("networking-api-v1") } fabricApiModules.each { module -> modImplementation fabricApi.module("fabric-$module", fabricApiVersion) From 329ac5130937c803d2d8ef9b0378ee784b6bc4d5 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 13 Nov 2023 13:51:47 +0100 Subject: [PATCH 2/8] Fix rubber-banding on rotation of entity in vehicle (fixes #846) --- .../mixin/entity_tracking/Mixin_FixPartialUpdates.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java b/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java index 2ffd980a..c86350e5 100644 --- a/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java +++ b/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java @@ -59,20 +59,24 @@ public class Mixin_FixPartialUpdates { // // Use correct position for rotation-only updates // + // Except for the special case of the entity riding another entity because of a second vanilla bug where it won't + // send tracked position updates to the client while the entity is riding... + // Nothing we can do in that case, fixing that would require modifying the server. + // @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getX()D")) private double getTrackedX(Entity instance) { - return instance.getTrackedPosition().getX(); + return instance.hasVehicle() ? instance.getX() : instance.getTrackedPosition().getX(); } @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getY()D")) private double getTrackedY(Entity instance) { - return instance.getTrackedPosition().getY(); + return instance.hasVehicle() ? instance.getY() : instance.getTrackedPosition().getY(); } @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getZ()D")) private double getTrackedZ(Entity instance) { - return instance.getTrackedPosition().getZ(); + return instance.hasVehicle() ? instance.getZ() : instance.getTrackedPosition().getZ(); } //#endif From e8f9234bf056f275e9387f305ad32f028dbd155f Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 27 Nov 2023 17:02:02 +0100 Subject: [PATCH 3/8] Fix EXR rendering failing on Windows on MC 1.20+ --- .../java/com/replaymod/render/utils/Lwjgl3Loader.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/replaymod/render/utils/Lwjgl3Loader.java b/src/main/java/com/replaymod/render/utils/Lwjgl3Loader.java index 8c832acd..916a1d90 100644 --- a/src/main/java/com/replaymod/render/utils/Lwjgl3Loader.java +++ b/src/main/java/com/replaymod/render/utils/Lwjgl3Loader.java @@ -33,9 +33,15 @@ public class Lwjgl3Loader extends URLClassLoader { Path nativesDir = ReplayMod.instance.folders.getCacheFolder().resolve("lwjgl-natives"); Class configClass = Class.forName("org.lwjgl.system.Configuration", true, this); - Object extractDirField = configClass.getField("SHARED_LIBRARY_EXTRACT_DIRECTORY").get(null); Method setMethod = configClass.getMethod("set", Object.class); - setMethod.invoke(extractDirField, nativesDir.toAbsolutePath().toString()); + setMethod.invoke( + configClass.getField("SHARED_LIBRARY_EXTRACT_DIRECTORY").get(null), + nativesDir.toAbsolutePath().toString() + ); + setMethod.invoke( + configClass.getField("SHARED_LIBRARY_EXTRACT_PATH").get(null), + (Object) null + ); } private boolean canBeSharedWithMc(String name) { From b8ab7aa5a71cc9d8e94f4cffc9bcb7d2d30fad3c Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 27 Nov 2023 18:34:19 +0100 Subject: [PATCH 4/8] Port to 1.20.2 --- build.gradle | 9 +- jGui | 2 +- root.gradle.kts | 2 + settings.gradle.kts | 2 + .../replaymod/core/gui/RestoreReplayGui.java | 3 +- .../replaymod/core/utils/Restrictions.java | 7 +- .../com/replaymod/core/versions/MCVer.java | 35 ++++++ .../com/replaymod/core/versions/Patterns.java | 66 ++++++++++ .../replaymod/editor/gui/MarkerProcessor.java | 3 +- .../handler/ConnectionEventHandler.java | 4 + .../handler/RecordingEventHandler.java | 11 +- .../recording/mixin/MixinMouseHelper.java | 7 ++ .../mixin/MixinNetHandlerPlayClient.java | 3 +- .../recording/packet/PacketListener.java | 43 +++++-- .../ChunkRenderingDataPreparerAccessor.java | 1 + .../render/rendering/VideoRenderer.java | 12 +- .../replaymod/replay/FullReplaySender.java | 109 +++++++++++++--- .../replaymod/replay/QuickReplaySender.java | 15 ++- .../com/replaymod/replay/ReplayHandler.java | 79 ++++++++---- .../replaymod/replay/camera/CameraEntity.java | 17 +++ .../resources/mixins.render.replaymod.json | 4 + .../render/mixin/ChunkInfoAccessor.java | 2 +- versions/1.20.2/logs/latest.log | 0 .../render/mixin/ChunkInfoAccessor.java | 1 + .../ChunkRenderingDataPreparerAccessor.java | 21 ++++ .../render/mixin/Mixin_ForceChunkLoading.java | 119 ++++++++++++++++++ .../Mixin_FixPartialUpdates.java | 9 ++ versions/mapping-fabric-1.20.3-1.20.2.txt | 0 28 files changed, 526 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java create mode 100644 versions/1.20.2/logs/latest.log create mode 100644 versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java create mode 100644 versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java create mode 100644 versions/1.20.2/src/main/java/com/replaymod/render/mixin/Mixin_ForceChunkLoading.java create mode 100644 versions/1.20.2/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java create mode 100644 versions/mapping-fabric-1.20.3-1.20.2.txt diff --git a/build.gradle b/build.gradle index f0661b4e..813b9460 100644 --- a/build.gradle +++ b/build.gradle @@ -264,6 +264,7 @@ dependencies { 11903: '1.19.3-rc3', 11904: '1.19.4', 12001: '1.20.1', + 12002: '1.20.2', ][mcVersion] mappings 'net.fabricmc:yarn:' + [ 11404: '1.14.4+build.16', @@ -281,6 +282,7 @@ dependencies { 11903: '1.19.3-rc3+build.1:v2', 11904: '1.19.4+build.1:v2', 12001: '1.20.1+build.2:v2', + 12002: '1.20.2+build.4:v2', ][mcVersion] modImplementation 'net.fabricmc:fabric-loader:0.14.11' def fabricApiVersion = [ @@ -299,6 +301,7 @@ dependencies { 11903: '0.68.1+1.19.3', 11904: '0.76.0+1.19.4', 12001: '0.83.1+1.20.1', + 12002: '0.90.7+1.20.2', ][mcVersion] def fabricApiModules = [ "api-base", @@ -379,7 +382,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:a1e2b83", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:e7a8ff7", shadeExclusions // FIXME this should be pulled in by ReplayStudio, and IntelliJ sees it, but javac for some reason does not implementation 'com.github.viaversion:opennbt:0a02214' // 2.0-SNAPSHOT (ViaVersion Edition) @@ -390,7 +393,9 @@ dependencies { shadow 'com.github.ReplayMod:lwjgl-utils:27dcd66' if (FABRIC) { - if (mcVersion >= 12000) { + if (mcVersion >= 12002) { + modImplementation 'com.terraformersmc:modmenu:8.0.0' + } else if (mcVersion >= 12000) { modImplementation 'com.terraformersmc:modmenu:7.0.1' } else if (mcVersion >= 11904) { modImplementation 'com.terraformersmc:modmenu:6.1.0-rc.4' diff --git a/jGui b/jGui index eb3297f5..d4f6bc60 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit eb3297f5a62f3b4c8b8445be85b95205d54763b7 +Subproject commit d4f6bc60c9f4368a4db1465465dfd1b9d2a06430 diff --git a/root.gradle.kts b/root.gradle.kts index 522c0c30..632364cd 100755 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -193,6 +193,7 @@ val doRelease by tasks.registering { defaultTasks("bundleJar") preprocess { + val mc12002 = createNode("1.20.2", 12002, "yarn") val mc12001 = createNode("1.20.1", 12001, "yarn") val mc11904 = createNode("1.19.4", 11904, "yarn") val mc11903 = createNode("1.19.3", 11903, "yarn") @@ -218,6 +219,7 @@ preprocess { val mc10800 = createNode("1.8", 10800, "srg") val mc10710 = createNode("1.7.10", 10710, "srg") + mc12002.link(mc12001) mc12001.link(mc11904) mc11904.link(mc11903) mc11903.link(mc11902, file("versions/mapping-fabric-1.19.3-1.19.2.txt")) diff --git a/settings.gradle.kts b/settings.gradle.kts index 6d230a13..05dbe159 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,6 +38,7 @@ val jGuiVersions = listOf( "1.19.3", "1.19.4", "1.20.1", + "1.20.2", ) val replayModVersions = listOf( // "1.7.10", @@ -64,6 +65,7 @@ val replayModVersions = listOf( "1.19.3", "1.19.4", "1.20.1", + "1.20.2", ) rootProject.buildFileName = "root.gradle.kts" diff --git a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java index c68a3210..2f615533 100644 --- a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java +++ b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java @@ -7,6 +7,7 @@ import com.replaymod.core.utils.Utils; import com.replaymod.core.versions.MCVer; import com.replaymod.replaystudio.io.ReplayInputStream; import com.replaymod.replaystudio.io.ReplayOutputStream; +import com.replaymod.replaystudio.lib.viaversion.api.protocol.packet.State; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; @@ -122,7 +123,7 @@ public class RestoreReplayGui extends AbstractGuiScreen { if (metaData != null && metaData.getDuration() == 0) { // Try to restore replay duration // We need to re-write the packet data in case there are any incomplete packets dangling at the end - try (ReplayInputStream in = replayFile.getPacketData(MCVer.getPacketTypeRegistry(true)); + try (ReplayInputStream in = replayFile.getPacketData(MCVer.getPacketTypeRegistry(State.LOGIN)); ReplayOutputStream out = replayFile.writePacketData()) { while (true) { // To prevent failing at un-parsable packets and to support recovery in minimal mode, diff --git a/src/main/java/com/replaymod/core/utils/Restrictions.java b/src/main/java/com/replaymod/core/utils/Restrictions.java index 359eaf77..0fc0e9f2 100644 --- a/src/main/java/com/replaymod/core/utils/Restrictions.java +++ b/src/main/java/com/replaymod/core/utils/Restrictions.java @@ -6,7 +6,7 @@ import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Identifier; //#endif -//#if MC<=10710 +//#if MC<=10710 || MC>=12002 //$$ import io.netty.buffer.Unpooled; //#endif @@ -26,7 +26,10 @@ public class Restrictions { private boolean onlyRecordingPlayer; public String handle(CustomPayloadS2CPacket packet) { - //#if MC>=10800 + //#if MC>=12002 + //$$ PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); + //$$ packet.write(buffer); + //#elseif MC>=10800 PacketByteBuf buffer = packet.getData(); //#else //$$ PacketBuffer buffer = new PacketBuffer(Unpooled.wrappedBuffer(packet.func_149168_d())); diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index ff348bfa..a8cbb328 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -10,6 +10,7 @@ import de.johni0702.minecraft.gui.utils.lwjgl.vector.Vector3f; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.render.BufferBuilder; +import net.minecraft.network.NetworkState; import net.minecraft.util.Identifier; import net.minecraft.util.Util; import net.minecraft.util.math.Vec3d; @@ -103,6 +104,40 @@ public class MCVer { //#endif } + public static NetworkState asMc(State state) { + switch (state) { + case HANDSHAKE: return NetworkState.HANDSHAKING; + case STATUS: return NetworkState.STATUS; + case LOGIN: return NetworkState.LOGIN; + //#if MC>=12002 + //$$ case CONFIGURATION: return NetworkState.CONFIGURATION; + //#endif + case PLAY: return NetworkState.PLAY; + } + throw new IllegalArgumentException("Unexpected value: " + state); + } + + public static State fromMc(NetworkState mcState) { + switch (mcState) { + case HANDSHAKING: return State.HANDSHAKE; + case STATUS: return State.STATUS; + case LOGIN: return State.LOGIN; + //#if MC>=12002 + //$$ case CONFIGURATION: return State.CONFIGURATION; + //#endif + case PLAY: return State.PLAY; + } + throw new IllegalArgumentException("Unexpected value: " + mcState); + } + + public static PacketTypeRegistry getPacketTypeRegistry(NetworkState state) { + return getPacketTypeRegistry(fromMc(state)); + } + + public static PacketTypeRegistry getPacketTypeRegistry(State state) { + return PacketTypeRegistry.get(ProtocolVersion.getProtocol(getProtocolVersion()), state); + } + public static PacketTypeRegistry getPacketTypeRegistry(boolean loginPhase) { return PacketTypeRegistry.get( ProtocolVersion.getProtocol(getProtocolVersion()), diff --git a/src/main/java/com/replaymod/core/versions/Patterns.java b/src/main/java/com/replaymod/core/versions/Patterns.java index bc52be00..984b3fb8 100644 --- a/src/main/java/com/replaymod/core/versions/Patterns.java +++ b/src/main/java/com/replaymod/core/versions/Patterns.java @@ -6,6 +6,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.core.mixin.MinecraftAccessor; import com.replaymod.gradle.remap.Pattern; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.options.GameOptions; import net.minecraft.client.options.KeyBinding; import net.minecraft.client.render.VertexFormat; @@ -16,6 +17,10 @@ import net.minecraft.client.render.entity.EntityRenderDispatcher; import net.minecraft.client.sound.PositionedSoundInstance; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; +import net.minecraft.network.NetworkSide; +import net.minecraft.network.NetworkState; +import net.minecraft.network.Packet; +import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; import net.minecraft.resource.Resource; import net.minecraft.resource.ResourceManager; import net.minecraft.sound.SoundCategory; @@ -59,6 +64,7 @@ import net.minecraft.util.collection.DefaultedList; //#endif //#if MC>=10904 +import net.minecraft.network.packet.s2c.play.UnloadChunkS2CPacket; import net.minecraft.sound.SoundEvent; import net.minecraft.util.crash.CrashCallable; //#else @@ -906,4 +912,64 @@ class Patterns { return entity.world; //#endif } + + @Pattern + public Object channel(CustomPayloadS2CPacket packet) { + //#if MC>=12002 + //$$ return packet.payload().id(); + //#else + return packet.getChannel(); + //#endif + } + + //#if MC>=10904 + @Pattern + public Integer getPacketId(NetworkState state, NetworkSide side, Packet packet) throws Exception { + //#if MC>=12002 + //$$ return state.getHandler(side).getId(packet); + //#else + return state.getPacketId(side, packet); + //#endif + } + + @Pattern + public int UnloadChunkPacket_getX(UnloadChunkS2CPacket packet) { + //#if MC>=12002 + //$$ return packet.pos().x; + //#else + return packet.getX(); + //#endif + } + + @Pattern + public int UnloadChunkPacket_getZ(UnloadChunkS2CPacket packet) { + //#if MC>=12002 + //$$ return packet.pos().z; + //#else + return packet.getZ(); + //#endif + } + //#else + //$$ @Pattern public void getPacketId() {} + //$$ @Pattern public void UnloadChunkPacket_getZ() {} + //$$ @Pattern public void UnloadChunkPacket_getX() {} + //#endif + + @Pattern + public Identifier getSkinTexture(AbstractClientPlayerEntity player) { + //#if MC>=12002 + //$$ return player.getSkinTextures().texture(); + //#else + return player.getSkinTexture(); + //#endif + } + + @Pattern + public boolean isDebugHudEnabled(MinecraftClient mc) { + //#if MC>=12002 + //$$ return mc.getDebugHud().shouldShowDebugHud(); + //#else + return mc.options.debugEnabled; + //#endif + } } diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java index 54940eb3..c3881f55 100644 --- a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -9,6 +9,7 @@ import com.replaymod.replaystudio.filter.SquashFilter; import com.replaymod.replaystudio.filter.StreamFilter; import com.replaymod.replaystudio.io.ReplayInputStream; import com.replaymod.replaystudio.io.ReplayOutputStream; +import com.replaymod.replaystudio.lib.viaversion.api.protocol.packet.State; import com.replaymod.replaystudio.protocol.PacketTypeRegistry; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; @@ -122,7 +123,7 @@ public class MarkerProcessor { String replayName = FilenameUtils.getBaseName(path.getFileName().toString()); int splitCounter = 0; - PacketTypeRegistry registry = MCVer.getPacketTypeRegistry(true); + PacketTypeRegistry registry = MCVer.getPacketTypeRegistry(State.LOGIN); DimensionTracker dimensionTracker = new DimensionTracker(); SquashFilter squashFilter = new SquashFilter(null, null, null); diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index 1137485f..ef71a08b 100644 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -112,7 +112,11 @@ public class ConnectionEventHandler { //#endif serverName = worldName; //#if MC>=11100 + //#if MC>=12002 + //$$ } else if (serverInfo != null && serverInfo.isRealm()) { + //#else } else if (mc.isConnectedToRealms()) { + //#endif // we can't access the server name without tapping too deep in the Realms Library worldName = "A Realms Server"; //#endif diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index fe2772e6..8d948069 100644 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -16,7 +16,6 @@ import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket; import net.minecraft.network.packet.s2c.play.EntityS2CPacket; import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket; import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket; -import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.network.Packet; @@ -30,6 +29,12 @@ import net.minecraft.server.integrated.IntegratedServer; //$$ import net.minecraftforge.event.entity.player.PlayerEvent.ItemPickupEvent; //#endif +//#if MC>=12002 +//$$ import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; +//#else +import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; +//#endif + //#if MC>=11600 import com.mojang.datafixers.util.Pair; //#endif @@ -103,7 +108,11 @@ public class RecordingEventHandler extends EventRegistrations { try { ClientPlayerEntity player = mc.player; assert player != null; + //#if MC>=12002 + //$$ packetListener.save(new EntitySpawnS2CPacket(player)); + //#else packetListener.save(new PlayerSpawnS2CPacket(player)); + //#endif //#if MC>=11903 //$$ packetListener.save(new EntityTrackerUpdateS2CPacket(player.getId(), player.getDataTracker().getChangedEntries())); //#elseif MC>=11500 diff --git a/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java index c7e8f5d4..f696f3f3 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java @@ -32,6 +32,12 @@ public abstract class MixinMouseHelper { private void handleReplayModScroll( long _p0, double _p1, double _p2, CallbackInfo ci, + //#if MC>=12002 + //$$ boolean discreteScroll, + //$$ double sensitivity, + //$$ double xOffsetAccumulated, + //$$ double yOffsetAccumulated + //#else double _l1, //#if MC>=11802 //$$ int yOffsetAccumulated @@ -40,6 +46,7 @@ public abstract class MixinMouseHelper { //#else //$$ double yOffsetAccumulated //#endif + //#endif ) { if (ReplayModReplay.instance.getReplayHandler() != null) { InputReplayTimer.handleScroll((int) (yOffsetAccumulated * 120)); diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java index 8be310c4..b4ac0a8b 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java @@ -2,6 +2,7 @@ package com.replaymod.recording.mixin; import com.replaymod.core.versions.MCVer; import com.replaymod.recording.handler.RecordingEventHandler; +import com.replaymod.replaystudio.lib.viaversion.api.protocol.packet.State; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket; @@ -78,7 +79,7 @@ public abstract class MixinNetHandlerPlayClient { byteBuf.readBytes(array); for (PacketPlayerListEntry data : PacketPlayerListEntry.read(new Packet( - MCVer.getPacketTypeRegistry(false), 0, PacketType.PlayerListEntry, + MCVer.getPacketTypeRegistry(State.PLAY), 0, PacketType.PlayerListEntry, com.github.steveice10.netty.buffer.Unpooled.wrappedBuffer(array) ))) { if (data.getUuid() == null) continue; diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index be72bedb..6f06a823 100644 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -30,7 +30,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.network.ClientConnection; import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; import net.minecraft.network.packet.s2c.play.DisconnectS2CPacket; -import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; import net.minecraft.entity.Entity; import net.minecraft.network.NetworkState; import net.minecraft.network.PacketByteBuf; @@ -41,6 +40,13 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +//#if MC>=12002 +//$$ import net.minecraft.entity.EntityType; +//$$ import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; +//#else +import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; +//#endif + //#if MC>=10800 //#if MC<10904 //$$ import net.minecraft.network.play.server.S46PacketSetCompressionLevel; @@ -93,6 +99,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter { private static final int PACKET_ID_RESOURCE_PACK_SEND = getPacketId(NetworkState.PLAY, new ResourcePackSendS2CPacket()); private static final int PACKET_ID_LOGIN_COMPRESSION = getPacketId(NetworkState.LOGIN, new LoginCompressionS2CPacket()); //#endif + //#if MC>=12002 + //$$ private static final int PACKET_ID_CONFIG_RESOURCE_PACK_SEND = getPacketId(NetworkState.CONFIGURATION, new ResourcePackSendS2CPacket("", "", false, null)); + //#endif //#if MC<10904 //$$ private static final int PACKET_ID_PLAY_COMPRESSION = getPacketId(EnumConnectionState.PLAY, new S46PacketSetCompressionLevel()); //#endif @@ -152,7 +161,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter { out.write(json.getBytes()); } } else { - replayFile.writeMetaData(MCVer.getPacketTypeRegistry(true), metaData); + replayFile.writeMetaData(MCVer.getPacketTypeRegistry(State.LOGIN), metaData); } } } catch (IOException e) { @@ -347,7 +356,11 @@ public class PacketListener extends ChannelInboundHandlerAdapter { // for integrated server connections MC is passing the packet objects directly, so we need to encode them // ourselves to be able to store them //#if MC>=11904 + //#if MC>=12002 + //$$ PacketBundleHandler bundleHandler = ctx.channel().attr(ClientConnection.CLIENTBOUND_PROTOCOL_KEY).get().getBundler(); + //#else //$$ PacketBundleHandler bundleHandler = ctx.channel().attr(PacketBundleHandler.KEY).get().getBundler(NetworkSide.CLIENTBOUND); + //#endif //$$ List packets = new ArrayList<>(1); //$$ bundleHandler.forEachPacket((net.minecraft.network.packet.Packet) msg, unbundledPacket -> { //$$ try { @@ -368,7 +381,11 @@ public class PacketListener extends ChannelInboundHandlerAdapter { } if (packet != null) { - if (connectionState == NetworkState.PLAY && packet.getId() == PACKET_ID_RESOURCE_PACK_SEND) { + if (connectionState == NetworkState.PLAY && packet.getId() == PACKET_ID_RESOURCE_PACK_SEND + //#if MC>=12002 + //$$ || connectionState == NetworkState.CONFIGURATION && packet.getId() == PACKET_ID_CONFIG_RESOURCE_PACK_SEND + //#endif + ) { ClientConnection connection = ctx.pipeline().get(ClientConnection.class); save(resourcePackRecorder.handleResourcePack(connection, (ResourcePackSendS2CPacket) decodeMcPacket(packet))); return; @@ -385,8 +402,13 @@ public class PacketListener extends ChannelInboundHandlerAdapter { if (ctx == null) { return NetworkState.LOGIN; } + //#if MC>=12002 + //$$ AttributeKey> key = ClientConnection.CLIENTBOUND_PROTOCOL_KEY; + //$$ return ctx.channel().attr(key).get().getState(); + //#else AttributeKey key = ClientConnection.ATTR_KEY_PROTOCOL; return ctx.channel().attr(key).get(); + //#endif } private static Packet encodeMcPacket(NetworkState connectionState, net.minecraft.network.Packet packet) throws Exception { @@ -402,7 +424,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter { try { packet.write(new PacketByteBuf(byteBuf)); return new Packet( - MCVer.getPacketTypeRegistry(connectionState == NetworkState.LOGIN), + MCVer.getPacketTypeRegistry(connectionState), packetId, com.github.steveice10.netty.buffer.Unpooled.wrappedBuffer( byteBuf.array(), @@ -416,11 +438,13 @@ public class PacketListener extends ChannelInboundHandlerAdapter { } private static net.minecraft.network.Packet decodeMcPacket(Packet packet) throws IOException, IllegalAccessException, InstantiationException { - NetworkState connectionState = packet.getRegistry().getState() == State.LOGIN ? NetworkState.LOGIN : NetworkState.PLAY; + NetworkState connectionState = asMc(packet.getRegistry().getState()); int packetId = packet.getId(); PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.wrappedBuffer(packet.getBuf().nioBuffer())); - //#if MC>=11700 + //#if MC>=12002 + //$$ return connectionState.getHandler(NetworkSide.CLIENTBOUND).createPacket(packetId, packetBuf); + //#elseif MC>=11700 //$$ return connectionState.getPacketHandler(NetworkSide.CLIENTBOUND, packetId, packetBuf); //#else //#if MC>=10800 @@ -439,7 +463,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter { byte[] bytes = new byte[packetBuf.readableBytes()]; packetBuf.readBytes(bytes); return new Packet( - MCVer.getPacketTypeRegistry(connectionState == NetworkState.LOGIN), + MCVer.getPacketTypeRegistry(connectionState), packetId, com.github.steveice10.netty.buffer.Unpooled.wrappedBuffer(bytes) ); @@ -503,12 +527,17 @@ public class PacketListener extends ChannelInboundHandlerAdapter { } } + //#if MC>=12002 + //$$ if (msg instanceof EntitySpawnS2CPacket packet && packet.getEntityType() == EntityType.PLAYER) { + //$$ UUID uuid = packet.getUuid(); + //#else if (msg instanceof PlayerSpawnS2CPacket) { //#if MC>=10800 UUID uuid = ((PlayerSpawnS2CPacket) msg).getPlayerUuid(); //#else //$$ UUID uuid = ((S0CPacketSpawnPlayer) msg).func_148948_e().getId(); //#endif + //#endif Set uuids = new HashSet<>(Arrays.asList(metaData.getPlayers())); uuids.add(uuid.toString()); metaData.setPlayers(uuids.toArray(new String[uuids.size()])); diff --git a/src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java b/src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java new file mode 100644 index 00000000..472c6be9 --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java @@ -0,0 +1 @@ +// 1.20.2+ diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index b1b57737..09743225 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -321,7 +321,11 @@ public class VideoRenderer implements RenderInfo { //#endif if (mc.options.debugEnabled) { debugInfoWasShown = true; + //#if MC>=12002 + //$$ mc.getDebugHud().toggleDebugHud(); + //#else mc.options.debugEnabled = false; + //#endif } //#if MC>=11400 if (mc.mouse.isCursorLocked()) { @@ -385,7 +389,13 @@ public class VideoRenderer implements RenderInfo { //$$ Display.setResizable(true); //$$ } //#endif - mc.options.debugEnabled = debugInfoWasShown; + if (debugInfoWasShown) { + //#if MC>=12002 + //$$ mc.getDebugHud().toggleDebugHud(); + //#else + mc.options.debugEnabled = true; + //#endif + } if (mouseWasGrabbed) { //#if MC>=11400 mc.mouse.lockCursor(); diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index f6c2af20..7272c7a4 100644 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -10,6 +10,8 @@ import com.replaymod.core.mixin.TimerAccessor; import com.replaymod.core.utils.Restrictions; import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replaystudio.io.ReplayInputStream; +import com.replaymod.replaystudio.lib.viaversion.api.protocol.packet.State; +import com.replaymod.replaystudio.protocol.PacketTypeRegistry; import com.replaymod.replaystudio.replay.ReplayFile; import de.johni0702.minecraft.gui.utils.EventRegistrations; import de.johni0702.minecraft.gui.versions.callbacks.PreTickCallback; @@ -48,7 +50,6 @@ import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerAbilitiesS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket; -import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; import net.minecraft.network.packet.s2c.play.SignEditorOpenS2CPacket; import net.minecraft.network.packet.s2c.play.StatisticsS2CPacket; import net.minecraft.text.Text; @@ -57,6 +58,14 @@ import net.minecraft.util.math.Vec3d; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +//#if MC>=12002 +//$$ import net.minecraft.network.packet.s2c.config.ReadyS2CPacket; +//$$ import net.minecraft.network.packet.s2c.play.CommonPlayerSpawnInfo; +//$$ import net.minecraft.network.packet.s2c.play.EnterReconfigurationS2CPacket; +//#else +import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; +//#endif + //#if MC>=11904 //$$ import net.minecraft.network.packet.s2c.play.PositionFlag; //#endif @@ -238,9 +247,9 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend protected PacketData nextPacket; /** - * Whether we're currently reading packets from the login phase. + * Which protocol (state) we're currently in. */ - private boolean loginPhase = true; + private PacketTypeRegistry registry = getPacketTypeRegistry(State.LOGIN); /** * Whether we need to restart the current replay. E.g. when jumping backwards in time @@ -470,8 +479,10 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend int i = pb.readVarInt(); - NetworkState state = loginPhase ? NetworkState.LOGIN : NetworkState.PLAY; - //#if MC>=11700 + NetworkState state = asMc(registry.getState()); + //#if MC>=12002 + //$$ Packet p = state.getHandler(NetworkSide.CLIENTBOUND).createPacket(i, pb); + //#elseif MC>=11700 //$$ Packet p = state.getPacketHandler(NetworkSide.CLIENTBOUND, i, pb); //#else //#if MC>=10800 @@ -493,8 +504,10 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend return; // MC should have enough time to tick } - boolean relevantPacket = packet instanceof PlayerSpawnS2CPacket - || packet instanceof EntitySpawnS2CPacket + boolean relevantPacket = packet instanceof EntitySpawnS2CPacket + //#if MC<12002 + || packet instanceof PlayerSpawnS2CPacket + //#endif //#if MC<11900 || packet instanceof MobSpawnS2CPacket || packet instanceof PaintingSpawnS2CPacket @@ -557,9 +570,20 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend */ protected Packet processPacket(Packet p) throws Exception { if (p instanceof LoginSuccessS2CPacket) { - loginPhase = false; + registry = registry.withLoginSuccess(); return p; } + //#if MC>=12002 + //$$ if (p instanceof ReadyS2CPacket) { + //$$ registry = registry.withState(State.PLAY); + //$$ return p; + //$$ } + //$$ if (p instanceof EnterReconfigurationS2CPacket) { + //$$ registry = registry.withState(State.CONFIGURATION); + //$$ hasWorldLoaded = false; + //$$ return p; + //$$ } + //#endif if (p instanceof CustomPayloadS2CPacket) { CustomPayloadS2CPacket packet = (CustomPayloadS2CPacket) p; @@ -663,6 +687,17 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#if MC>=11400 p = new GameJoinS2CPacket( entId, + //#if MC>=12002 + //$$ packet.hardcore(), + //$$ packet.dimensionIds(), + //$$ packet.maxPlayers(), + //$$ packet.viewDistance(), + //$$ packet.simulationDistance(), + //$$ packet.reducedDebugInfo(), + //$$ packet.showDeathScreen(), + //$$ packet.doLimitedCrafting(), + //$$ withSpectatorMode(packet.commonPlayerSpawnInfo()) + //#else //#if MC>=11800 //$$ packet.hardcore(), //#endif @@ -719,6 +754,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#if MC>=12000 //$$ , packet.portalCooldown() //#endif + //#endif ); //#else //#if MC>=10800 @@ -758,6 +794,10 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend PlayerRespawnS2CPacket respawn = (PlayerRespawnS2CPacket) p; //#if MC>=11400 p = new PlayerRespawnS2CPacket( + //#if MC>=12002 + //$$ withSpectatorMode(respawn.commonPlayerSpawnInfo()), + //$$ (byte) 0 + //#else //#if MC>=11600 respawn.method_29445(), //#endif @@ -785,6 +825,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#if MC>=12000 //$$ , respawn.getPortalCooldown() //#endif + //#endif ); //#else //#if MC>=10809 @@ -916,6 +957,22 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend } } + //#if MC>=12002 + //$$ private CommonPlayerSpawnInfo withSpectatorMode(CommonPlayerSpawnInfo org) { + //$$ return new CommonPlayerSpawnInfo( + //$$ org.dimensionType(), + //$$ org.dimension(), + //$$ org.seed(), + //$$ GameMode.SPECTATOR, + //$$ GameMode.SPECTATOR, + //$$ org.isDebug(), + //$$ org.isFlat(), + //$$ org.lastDeathLocation(), + //$$ org.portalCooldown() + //$$ ); + //$$ } + //#endif + @Override @SuppressWarnings("unchecked") public void channelActive(ChannelHandlerContext ctx) throws Exception { @@ -1010,7 +1067,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend while (!terminate) { synchronized (FullReplaySender.this) { if (replayIn == null) { - replayIn = replayFile.getPacketData(getPacketTypeRegistry(true)); + replayIn = replayFile.getPacketData(getPacketTypeRegistry(State.LOGIN)); } // Packet loop while (true) { @@ -1037,7 +1094,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Read the next packet if we don't already have one if (nextPacket == null) { - nextPacket = new PacketData(replayIn, loginPhase); + nextPacket = new PacketData(replayIn); } int nextTimeStamp = nextPacket.timestamp; @@ -1060,6 +1117,17 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend lastTimeStamp = nextTimeStamp; + // MC as of 1.20.2 relies on autoRead, so it can update the connection state on the main + // thread before the next packet is read. As such, we need to stall if that was just + // enabled. + // Might be safe to do the same on older versions too, but I'd rather not poke the + // monster that is Forge networking. + //#if MC>=12002 + //$$ while (!ctx.channel().config().isAutoRead()) { + //$$ Thread.sleep(0, 100_000); + //$$ } + //#endif + // In case we finished jumping // We need to check that we aren't planing to restart so we don't accidentally run this // code before we actually restarted @@ -1092,7 +1160,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Restart the replay. hasWorldLoaded = false; lastTimeStamp = 0; - loginPhase = true; + registry = getPacketTypeRegistry(State.LOGIN); startFromBeginning = false; nextPacket = null; realTimeStart = System.currentTimeMillis(); @@ -1232,14 +1300,14 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend replayIn.close(); replayIn = null; } - loginPhase = true; + registry = getPacketTypeRegistry(State.LOGIN); startFromBeginning = false; nextPacket = null; ReplayMod.instance.runSync(replayHandler::restartedReplay); } if (replayIn == null) { - replayIn = replayFile.getPacketData(getPacketTypeRegistry(true)); + replayIn = replayFile.getPacketData(getPacketTypeRegistry(State.LOGIN)); } while (true) { // Send packets @@ -1251,7 +1319,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend nextPacket = null; } else { // Otherwise read one from the input stream - pd = new PacketData(replayIn, loginPhase); + pd = new PacketData(replayIn); } int nextTimeStamp = pd.timestamp; @@ -1263,6 +1331,17 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Process packet channelRead(ctx, pd.bytes); + + // MC as of 1.20.2 relies on autoRead, so it can update the connection state on the main + // thread before the next packet is read. As such, we need to stall if that was just + // enabled. + // Might be safe to do the same on older versions too, but I'd rather not poke the + // monster that is Forge networking. + //#if MC>=12002 + //$$ while (!ctx.channel().config().isAutoRead()) { + //$$ Thread.sleep(0, 100_000); + //$$ } + //#endif } catch (EOFException eof) { // Shit! We hit the end before finishing our job! What shall we do now? // well, let's just pretend we're done... @@ -1471,7 +1550,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend private final int timestamp; private final byte[] bytes; - PacketData(ReplayInputStream in, boolean loginPhase) throws IOException { + PacketData(ReplayInputStream in) throws IOException { if (ReplayMod.isMinimalMode()) { // Minimal mode, we can only read our exact protocol version and cannot use ReplayStudio timestamp = readInt(in); diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 6592b47d..6641ddc5 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -7,6 +7,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.replaymod.core.mixin.MinecraftAccessor; import com.replaymod.core.mixin.TimerAccessor; +import com.replaymod.replaystudio.lib.viaversion.api.protocol.packet.State; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.rar.RandomAccessReplay; import de.johni0702.minecraft.gui.utils.EventRegistrations; @@ -30,6 +31,7 @@ import java.util.function.Consumer; import com.replaymod.core.utils.WrappedTimer; //#endif +import static com.replaymod.core.versions.MCVer.asMc; import static com.replaymod.core.versions.MCVer.getMinecraft; import static com.replaymod.core.versions.MCVer.getPacketTypeRegistry; import static com.replaymod.replay.ReplayModReplay.LOGGER; @@ -63,7 +65,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe public QuickReplaySender(ReplayModReplay mod, ReplayFile replayFile) { this.mod = mod; - this.replay = new RandomAccessReplay(replayFile, getPacketTypeRegistry(false)) { + this.replay = new RandomAccessReplay(replayFile, getPacketTypeRegistry(State.PLAY)) { private byte[] buf = new byte[0]; @Override @@ -78,18 +80,21 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe wrappedBuf.writerIndex(size); PacketByteBuf packetByteBuf = new PacketByteBuf(wrappedBuf); + NetworkState state = asMc(packet.getRegistry().getState()); //#if MC>=10809 Packet mcPacket; //#else //$$ Packet mcPacket; //#endif - //#if MC>=11700 - //$$ mcPacket = NetworkState.PLAY.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId(), packetByteBuf); + //#if MC>=12002 + //$$ mcPacket = state.getHandler(NetworkSide.CLIENTBOUND).createPacket(packet.getId(), packetByteBuf); + //#elseif MC>=11700 + //$$ mcPacket = state.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId(), packetByteBuf); //#elseif MC>=11500 - mcPacket = NetworkState.PLAY.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId()); + mcPacket = state.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId()); //#else //$$ try { - //$$ mcPacket = NetworkState.PLAY.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId()); + //$$ mcPacket = state.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId()); //$$ } catch (IllegalAccessException | InstantiationException e) { //$$ e.printStackTrace(); //$$ return; diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 5718557b..c0475556 100644 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -42,6 +42,13 @@ import net.minecraft.network.ClientConnection; import java.io.IOException; import java.util.*; +//#if MC>=12002 +//$$ import io.netty.channel.ChannelDuplexHandler; +//$$ import io.netty.channel.ChannelPromise; +//$$ import net.minecraft.network.handler.NetworkStateTransitionHandler; +//$$ import net.minecraft.network.packet.Packet; +//#endif + //#if MC>=12000 //$$ import com.mojang.blaze3d.systems.VertexSorter; //$$ import net.minecraft.client.gui.DrawContext; @@ -301,6 +308,36 @@ public class ReplayHandler { //$$ FMLClientHandler.instance().connectToRealmsServer(null, 0); // just to init the playClientBlock latch //#endif + + //#if MC>=11200 + channel = new EmbeddedChannel(); + //#else + //$$ ChannelOutboundHandlerAdapter dummyHandler = new ChannelOutboundHandlerAdapter(); + //$$ channel = new EmbeddedChannel(dummyHandler); + //$$ channel.pipeline().remove(dummyHandler); + //#endif + //#if MC>=10800 + channel.pipeline().addLast("ReplayModReplay_quickReplaySender", quickReplaySender); + //#endif + channel.pipeline().addLast("ReplayModReplay_replaySender", fullReplaySender); + //#if MC>=12002 + //$$ channel.pipeline().addLast("ReplayModReplay_transition", new DummyNetworkStateTransitionHandler()); + //$$ channel.pipeline().addLast("bundler", new PacketBundler(ClientConnection.CLIENTBOUND_PROTOCOL_KEY)); + //#elseif MC>=11904 + //$$ channel.pipeline().addLast("bundler", new PacketBundler(NetworkSide.CLIENTBOUND)); + //#endif + channel.pipeline().addLast("packet_handler", networkManager); + channel.pipeline().fireChannelActive(); + + // MC usually transitions from handshake to login via the packets it sends. + // We don't send any packets (there is no server to receive them), so we need to switch manually. + //#if MC>=12002 + //$$ channel.attr(ClientConnection.CLIENTBOUND_PROTOCOL_KEY).set(NetworkState.LOGIN.getHandler(NetworkSide.CLIENTBOUND)); + //$$ channel.attr(ClientConnection.SERVERBOUND_PROTOCOL_KEY).set(NetworkState.LOGIN.getHandler(NetworkSide.SERVERBOUND)); + //#else + networkManager.setState(NetworkState.LOGIN); + //#endif + networkManager.setPacketListener(new ClientLoginNetworkHandler( networkManager, mc, @@ -315,28 +352,6 @@ public class ReplayHandler { //#endif )); - - //#if MC>=11200 - channel = new EmbeddedChannel(); - //#else - //$$ ChannelOutboundHandlerAdapter dummyHandler = new ChannelOutboundHandlerAdapter(); - //$$ channel = new EmbeddedChannel(dummyHandler); - //$$ channel.pipeline().remove(dummyHandler); - //#endif - //#if MC>=10800 - channel.pipeline().addLast("ReplayModReplay_quickReplaySender", quickReplaySender); - //#endif - channel.pipeline().addLast("ReplayModReplay_replaySender", fullReplaySender); - //#if MC>=11904 - //$$ channel.pipeline().addLast("bundler", new PacketBundler(NetworkSide.CLIENTBOUND)); - //#endif - channel.pipeline().addLast("packet_handler", networkManager); - channel.pipeline().fireChannelActive(); - - // MC usually transitions from handshake to login via the packets it sends. - // We don't send any packets (there is no server to receive them), so we need to switch manually. - networkManager.setState(NetworkState.LOGIN); - //#if MC>=11400 ((MinecraftAccessor) mc).setConnection(networkManager); //#endif @@ -787,4 +802,24 @@ public class ReplayHandler { //$$ } //#endif } + + //#if MC>=12002 + //$$ private static class DummyNetworkStateTransitionHandler extends ChannelDuplexHandler { + //$$ @Override + //$$ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + //$$ if (msg instanceof Packet packet) { + //$$ NetworkStateTransitionHandler.handle(ctx.channel().attr(ClientConnection.CLIENTBOUND_PROTOCOL_KEY), packet); + //$$ } + //$$ super.channelRead(ctx, msg); + //$$ } + //$$ + //$$ @Override + //$$ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + //$$ if (msg instanceof Packet packet) { + //$$ NetworkStateTransitionHandler.handle(ctx.channel().attr(ClientConnection.SERVERBOUND_PROTOCOL_KEY), packet); + //$$ } + //$$ super.write(ctx, msg, promise); + //$$ } + //$$ } + //#endif } diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index aef121e9..f6c22940 100644 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -40,6 +40,10 @@ import net.minecraft.util.math.Box; //$$ import net.minecraftforge.eventbus.api.SubscribeEvent; //#endif +//#if MC>=12002 +//$$ import net.minecraft.client.util.SkinTextures; +//#endif + //#if MC>=11400 import net.minecraft.client.world.ClientWorld; import net.minecraft.fluid.Fluid; @@ -475,6 +479,16 @@ public class CameraEntity return super.isInvisible(); } + //#if MC>=12002 + //$$ @Override + //$$ public SkinTextures getSkinTextures() { + //$$ Entity view = this.client.getCameraEntity(); + //$$ if (view != this && view instanceof AbstractClientPlayerEntity) { + //$$ return ((AbstractClientPlayerEntity) view).getSkinTextures(); + //$$ } + //$$ return super.getSkinTextures(); + //$$ } + //#else @Override public Identifier getSkinTexture() { Entity view = this.client.getCameraEntity(); @@ -493,7 +507,10 @@ public class CameraEntity } return super.getModel(); } + //#endif + //#endif + //#if MC>=10800 @Override public boolean isPartVisible(PlayerModelPart modelPart) { Entity view = this.client.getCameraEntity(); diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index 2759769b..bd3761e4 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -5,8 +5,12 @@ "server": [], "client": [ //#if MC>=11800 + //#if MC>=12002 + //$$ "ChunkRenderingDataPreparerAccessor", + //#else //$$ "ChunkInfoAccessor", //#endif + //#endif "Mixin_ChromaKeyColorSky", "Mixin_ChromaKeyDisableFog", "Mixin_ChromaKeyForceSky", diff --git a/versions/1.18.1/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java b/versions/1.18.1/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java index c4fbdd96..d20bd726 100644 --- a/versions/1.18.1/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java +++ b/versions/1.18.1/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java @@ -1,4 +1,4 @@ -// 1.18+ +// 1.18 - 1.20.1 package com.replaymod.render.mixin; import net.minecraft.client.render.chunk.ChunkBuilder; diff --git a/versions/1.20.2/logs/latest.log b/versions/1.20.2/logs/latest.log new file mode 100644 index 00000000..e69de29b diff --git a/versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java b/versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java new file mode 100644 index 00000000..42f3f3b7 --- /dev/null +++ b/versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkInfoAccessor.java @@ -0,0 +1 @@ +// 1.18 - 1.20.1 diff --git a/versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java b/versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java new file mode 100644 index 00000000..986d08d1 --- /dev/null +++ b/versions/1.20.2/src/main/java/com/replaymod/render/mixin/ChunkRenderingDataPreparerAccessor.java @@ -0,0 +1,21 @@ +package com.replaymod.render.mixin; + +import net.minecraft.client.render.BuiltChunkStorage; +import net.minecraft.client.render.ChunkRenderingDataPreparer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + +@Mixin(ChunkRenderingDataPreparer.class) +public interface ChunkRenderingDataPreparerAccessor { + @Accessor("field_45623") + BuiltChunkStorage builtChunkStorage(); + + @Accessor("field_45621") + boolean shouldUpdate(); + + @Accessor("field_45622") + Future fullUpdateFuture(); +} diff --git a/versions/1.20.2/src/main/java/com/replaymod/render/mixin/Mixin_ForceChunkLoading.java b/versions/1.20.2/src/main/java/com/replaymod/render/mixin/Mixin_ForceChunkLoading.java new file mode 100644 index 00000000..4843023e --- /dev/null +++ b/versions/1.20.2/src/main/java/com/replaymod/render/mixin/Mixin_ForceChunkLoading.java @@ -0,0 +1,119 @@ +package com.replaymod.render.mixin; + +import com.replaymod.render.hooks.ForceChunkLoadingHook; +import com.replaymod.render.hooks.IForceChunkLoading; +import com.replaymod.render.utils.FlawlessFrames; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.ChunkRenderingDataPreparer; +import net.minecraft.client.render.Frustum; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.render.chunk.ChunkBuilder; +import net.minecraft.client.render.chunk.ChunkRendererRegionBuilder; +import net.minecraft.client.util.math.MatrixStack; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@Mixin(WorldRenderer.class) +public abstract class Mixin_ForceChunkLoading implements IForceChunkLoading { + private ForceChunkLoadingHook replayModRender_hook; + + @Override + public void replayModRender_setHook(ForceChunkLoadingHook hook) { + this.replayModRender_hook = hook; + } + + @Shadow private ChunkBuilder field_45614; + + @Shadow @Final private ChunkRenderingDataPreparer field_45615; + + @Shadow protected abstract void setupTerrain(Camera par1, Frustum par2, boolean par3, boolean par4); + + @Shadow private Frustum frustum; + + @Shadow private Frustum capturedFrustum; + + @Shadow @Final private MinecraftClient client; + + @Shadow protected abstract void applyFrustum(Frustum par1); + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;setupTerrain(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;ZZ)V")) + private void forceAllChunks(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) { + if (replayModRender_hook == null) { + return; + } + if (FlawlessFrames.hasSodium()) { + return; + } + + assert this.client.player != null; + + ChunkRenderingDataPreparer renderingData = this.field_45615; + ChunkRenderingDataPreparerAccessor renderingDataAcc = (ChunkRenderingDataPreparerAccessor) renderingData; + ChunkRendererRegionBuilder chunkRendererRegionBuilder = new ChunkRendererRegionBuilder(); + + do { + boolean areWeDoneYet = true; + + // Determine which chunks shall be visible + setupTerrain(camera, this.frustum, this.capturedFrustum != null, this.client.player.isSpectator()); + + // Wait for async processing to be complete + Future fullUpdateFuture = renderingDataAcc.fullUpdateFuture(); + if (fullUpdateFuture != null) { + try { + fullUpdateFuture.get(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (TimeoutException e) { + e.printStackTrace(); + } + } + + // If that async processing did change the chunk graph, we need to re-apply the frustum (otherwise this is + // only done in the next setupTerrain call, which not happen this frame) + if (renderingData.method_52836()) { + this.applyFrustum((new Frustum(frustum)).coverBoxAroundSetPosition(8)); // call based on the one in setupTerrain + } + + // Schedule all chunks which need rebuilding (we schedule even important rebuilds because we wait for + // all of them anyway and this way we can take advantage of threading) + for (ChunkBuilder.BuiltChunk builtChunk : renderingDataAcc.builtChunkStorage().chunks) { + if (!builtChunk.needsRebuild()) { + continue; + } + // MC sometimes schedules invalid chunks when you're outside of loaded chunks (e.g. y > 256) + if (builtChunk.shouldBuild()) { + builtChunk.scheduleRebuild(this.field_45614, chunkRendererRegionBuilder); + areWeDoneYet = false; + } + builtChunk.cancelRebuild(); + } + + // Upload all chunks + if (((ForceChunkLoadingHook.IBlockOnChunkRebuilds) this.field_45614).uploadEverythingBlocking()) { + areWeDoneYet = false; + } + + // Repeat until no more updates are needed + if (!areWeDoneYet) { + renderingData.method_52817(); // sets shouldUpdate to true + } + } while (renderingDataAcc.shouldUpdate()); + } +} diff --git a/versions/1.20.2/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java b/versions/1.20.2/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java new file mode 100644 index 00000000..8dbc9b05 --- /dev/null +++ b/versions/1.20.2/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java @@ -0,0 +1,9 @@ +package com.replaymod.replay.mixin.entity_tracking; + +import net.minecraft.client.network.ClientPlayNetworkHandler; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ClientPlayNetworkHandler.class) +public class Mixin_FixPartialUpdates { + // Looks like this has finally been fixed in 1.20.2 +} diff --git a/versions/mapping-fabric-1.20.3-1.20.2.txt b/versions/mapping-fabric-1.20.3-1.20.2.txt new file mode 100644 index 00000000..e69de29b From 43576efeb3e1be862a678a16efa89a881b33eba8 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 3 Dec 2023 16:28:28 +0100 Subject: [PATCH 5/8] Convert JPG images to PNG Minecraft 1.20.3 no longer supports loading JPG images. --- .../java/com/replaymod/core/utils/Utils.java | 2 +- .../replaymod/render/gui/GuiVideoRenderer.java | 2 +- src/main/resources/assets/replaymod/logo.jpg | Bin 27743 -> 0 bytes src/main/resources/assets/replaymod/logo.png | Bin 0 -> 34846 bytes src/main/resources/default_thumb.jpg | Bin 27743 -> 0 bytes src/main/resources/default_thumb.png | Bin 0 -> 34846 bytes 6 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 src/main/resources/assets/replaymod/logo.jpg create mode 100644 src/main/resources/assets/replaymod/logo.png delete mode 100644 src/main/resources/default_thumb.jpg create mode 100644 src/main/resources/default_thumb.png diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 826f78fa..e9706634 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -80,7 +80,7 @@ public class Utils { static { Image thumbnail; try { - thumbnail = Image.read(getResourceAsStream("/default_thumb.jpg")); + thumbnail = Image.read(getResourceAsStream("/default_thumb.png")); } catch (Exception e) { thumbnail = new Image(1, 1); e.printStackTrace(); diff --git a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java index cd9c6268..448cf5d0 100644 --- a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java +++ b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java @@ -28,7 +28,7 @@ import net.minecraft.client.texture.NativeImage; import java.nio.ByteBuffer; public class GuiVideoRenderer extends GuiScreen implements Tickable { - private static final Identifier NO_PREVIEW_TEXTURE = new Identifier("replaymod", "logo.jpg"); + private static final Identifier NO_PREVIEW_TEXTURE = new Identifier("replaymod", "logo.png"); private final VideoRenderer renderer; diff --git a/src/main/resources/assets/replaymod/logo.jpg b/src/main/resources/assets/replaymod/logo.jpg deleted file mode 100644 index fbe127624187dbc22bb1c99e42ff41875abcb90a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27743 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%0D~*}gRDOoS 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/default_thumb.jpg b/src/main/resources/default_thumb.jpg deleted file mode 100644 index fbe127624187dbc22bb1c99e42ff41875abcb90a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27743 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%0D~*}gRDOoS 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 g9GYw#{9LU39PIzC#GLE22vP Date: Sun, 3 Dec 2023 16:30:00 +0100 Subject: [PATCH 6/8] Port to 1.20.3-rc1 --- build.gradle | 9 +++-- jGui | 2 +- root.gradle.kts | 2 ++ settings.gradle.kts | 2 ++ .../replaymod/core/utils/WrappedTimer.java | 4 ++- .../com/replaymod/core/versions/MCVer.java | 2 ++ .../replaymod/pathing/player/ReplayTimer.java | 4 ++- .../recording/packet/PacketListener.java | 25 ++++++++++--- .../packet/ResourcePackRecorder.java | 36 +++++++++++++++---- .../mixin/Mixin_BlockOnChunkRebuilds.java | 25 ++++++++++--- .../replaymod/replay/FullReplaySender.java | 8 +++++ .../com/replaymod/replay/ReplayHandler.java | 8 +++++ .../mixin/MixinDownloadingPackFinder.java | 29 +++++++++++++++ versions/mapping-fabric-1.20.3-1.20.2.txt | 1 + 14 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 versions/1.20.3/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java diff --git a/build.gradle b/build.gradle index 813b9460..2bbff83a 100644 --- a/build.gradle +++ b/build.gradle @@ -265,6 +265,7 @@ dependencies { 11904: '1.19.4', 12001: '1.20.1', 12002: '1.20.2', + 12003: '1.20.3-rc1', ][mcVersion] mappings 'net.fabricmc:yarn:' + [ 11404: '1.14.4+build.16', @@ -283,6 +284,7 @@ dependencies { 11904: '1.19.4+build.1:v2', 12001: '1.20.1+build.2:v2', 12002: '1.20.2+build.4:v2', + 12003: '1.20.3-rc1+build.2:v2', ][mcVersion] modImplementation 'net.fabricmc:fabric-loader:0.14.11' def fabricApiVersion = [ @@ -302,6 +304,7 @@ dependencies { 11904: '0.76.0+1.19.4', 12001: '0.83.1+1.20.1', 12002: '0.90.7+1.20.2', + 12003: '0.91.1+1.20.3', ][mcVersion] def fabricApiModules = [ "api-base", @@ -382,7 +385,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:e7a8ff7", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:5052121", shadeExclusions // FIXME this should be pulled in by ReplayStudio, and IntelliJ sees it, but javac for some reason does not implementation 'com.github.viaversion:opennbt:0a02214' // 2.0-SNAPSHOT (ViaVersion Edition) @@ -393,7 +396,9 @@ dependencies { shadow 'com.github.ReplayMod:lwjgl-utils:27dcd66' if (FABRIC) { - if (mcVersion >= 12002) { + if (mcVersion >= 12003) { + modImplementation 'com.terraformersmc:modmenu:9.0.0-pre.1' + } else if (mcVersion >= 12002) { modImplementation 'com.terraformersmc:modmenu:8.0.0' } else if (mcVersion >= 12000) { modImplementation 'com.terraformersmc:modmenu:7.0.1' diff --git a/jGui b/jGui index d4f6bc60..b4a618ae 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit d4f6bc60c9f4368a4db1465465dfd1b9d2a06430 +Subproject commit b4a618aef9f4a9260e8bb75368d75d95b6a72e0c diff --git a/root.gradle.kts b/root.gradle.kts index 632364cd..cef238dd 100755 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -193,6 +193,7 @@ val doRelease by tasks.registering { defaultTasks("bundleJar") preprocess { + val mc12003 = createNode("1.20.3", 12003, "yarn") val mc12002 = createNode("1.20.2", 12002, "yarn") val mc12001 = createNode("1.20.1", 12001, "yarn") val mc11904 = createNode("1.19.4", 11904, "yarn") @@ -219,6 +220,7 @@ preprocess { val mc10800 = createNode("1.8", 10800, "srg") val mc10710 = createNode("1.7.10", 10710, "srg") + mc12003.link(mc12002, file("versions/mapping-fabric-1.20.3-1.20.2.txt")) mc12002.link(mc12001) mc12001.link(mc11904) mc11904.link(mc11903) diff --git a/settings.gradle.kts b/settings.gradle.kts index 05dbe159..3ab38e6b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,6 +39,7 @@ val jGuiVersions = listOf( "1.19.4", "1.20.1", "1.20.2", + "1.20.3", ) val replayModVersions = listOf( // "1.7.10", @@ -66,6 +67,7 @@ val replayModVersions = listOf( "1.19.4", "1.20.1", "1.20.2", + "1.20.3", ) rootProject.buildFileName = "root.gradle.kts" diff --git a/src/main/java/com/replaymod/core/utils/WrappedTimer.java b/src/main/java/com/replaymod/core/utils/WrappedTimer.java index 2db83188..754db425 100644 --- a/src/main/java/com/replaymod/core/utils/WrappedTimer.java +++ b/src/main/java/com/replaymod/core/utils/WrappedTimer.java @@ -9,7 +9,9 @@ public class WrappedTimer extends RenderTickCounter { protected final RenderTickCounter wrapped; public WrappedTimer(RenderTickCounter wrapped) { - //#if MC>=11400 + //#if MC>=12003 + //$$ super(0, 0, f -> f); + //#elseif MC>=11400 super(0, 0); //#else //$$ super(0); diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index a8cbb328..44e64f14 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -168,6 +168,7 @@ public class MCVer { //$$ } //#endif + //#if MC<12003 public static //#if MC>=11400 CompletableFuture @@ -195,6 +196,7 @@ public class MCVer { //#endif //#endif } + //#endif public static void addCallback( //#if MC>=11400 diff --git a/src/main/java/com/replaymod/pathing/player/ReplayTimer.java b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java index 30d5f462..d970cb05 100644 --- a/src/main/java/com/replaymod/pathing/player/ReplayTimer.java +++ b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java @@ -8,7 +8,9 @@ import net.minecraft.client.render.RenderTickCounter; * Wrapper around the current timer that prevents the timer from advancing by itself. */ public class ReplayTimer extends WrappedTimer { - //#if MC>=11400 + //#if MC>=12003 + //$$ private final RenderTickCounter state = new RenderTickCounter(0, 0, f -> f); + //#elseif MC>=11400 private final RenderTickCounter state = new RenderTickCounter(0, 0); //#else //$$ private final Timer state = new Timer(0); diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index 6f06a823..1cd11622 100644 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -92,16 +92,24 @@ public class PacketListener extends ChannelInboundHandlerAdapter { private static final MinecraftClient mc = getMinecraft(); private static final Logger logger = LogManager.getLogger(); + private static final ResourcePackSendS2CPacket RESOURCE_PACK_SEND_PACKET = + //#if MC>=12003 + //$$ new ResourcePackSendS2CPacket(null, "", "", false, null) + //#elseif MC>=11700 + //$$ new ResourcePackSendS2CPacket("", "", false, null) + //#else + new ResourcePackSendS2CPacket() + //#endif + ; + private static final int PACKET_ID_RESOURCE_PACK_SEND = getPacketId(NetworkState.PLAY, RESOURCE_PACK_SEND_PACKET); + //#if MC>=12002 + //$$ private static final int PACKET_ID_CONFIG_RESOURCE_PACK_SEND = getPacketId(NetworkState.CONFIGURATION, RESOURCE_PACK_SEND_PACKET); + //#endif //#if MC>=11700 - //$$ private static final int PACKET_ID_RESOURCE_PACK_SEND = getPacketId(NetworkState.PLAY, new ResourcePackSendS2CPacket("", "", false, null)); //$$ private static final int PACKET_ID_LOGIN_COMPRESSION = getPacketId(NetworkState.LOGIN, new LoginCompressionS2CPacket(0)); //#else - private static final int PACKET_ID_RESOURCE_PACK_SEND = getPacketId(NetworkState.PLAY, new ResourcePackSendS2CPacket()); private static final int PACKET_ID_LOGIN_COMPRESSION = getPacketId(NetworkState.LOGIN, new LoginCompressionS2CPacket()); //#endif - //#if MC>=12002 - //$$ private static final int PACKET_ID_CONFIG_RESOURCE_PACK_SEND = getPacketId(NetworkState.CONFIGURATION, new ResourcePackSendS2CPacket("", "", false, null)); - //#endif //#if MC<10904 //$$ private static final int PACKET_ID_PLAY_COMPRESSION = getPacketId(EnumConnectionState.PLAY, new S46PacketSetCompressionLevel()); //#endif @@ -388,6 +396,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter { ) { ClientConnection connection = ctx.pipeline().get(ClientConnection.class); save(resourcePackRecorder.handleResourcePack(connection, (ResourcePackSendS2CPacket) decodeMcPacket(packet))); + //#if MC>=12003 + //$$ super.channelRead(ctx, msg); + //#endif return; } @@ -516,6 +527,10 @@ public class PacketListener extends ChannelInboundHandlerAdapter { this.serverWasPaused = true; } + public ResourcePackRecorder getResourcePackRecorder() { + return resourcePackRecorder; + } + private class DecodedPacketListener extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { diff --git a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java index 01fd26c9..1d2fc815 100644 --- a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java +++ b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java @@ -1,7 +1,6 @@ package com.replaymod.recording.packet; import com.google.common.hash.Hashing; -import com.google.common.io.Files; import com.replaymod.replaystudio.replay.ReplayFile; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ConfirmScreen; @@ -11,6 +10,10 @@ import net.minecraft.client.resource.ClientBuiltinResourcePackProvider; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +//#if MC>=12003 +//$$ import java.util.UUID; +//#endif + //#if MC>=11900 //$$ import java.net.MalformedURLException; //$$ import java.net.URL; @@ -55,6 +58,8 @@ import net.minecraft.network.ClientConnection; import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -75,10 +80,10 @@ public class ResourcePackRecorder { this.replayFile = replayFile; } - public void recordResourcePack(File file, int requestId) { + public void recordResourcePack(Path file, int requestId) { try { // Read in resource pack file - byte[] bytes = Files.toByteArray(file); + byte[] bytes = Files.readAllBytes(file); // Check whether it is already known String hash = Hashing.sha1().hashBytes(bytes).toString(); boolean doWrite = false; // Whether we are the first and have to write it @@ -105,7 +110,24 @@ public class ResourcePackRecorder { } } - //#if MC>=10800 + //#if MC>=12003 + //$$ private final Map mcIdToReplayId = new HashMap<>(); + //$$ + //$$ public synchronized ResourcePackSendS2CPacket handleResourcePack(ClientConnection netManager, ResourcePackSendS2CPacket packet) { + //$$ final int requestId = nextRequestId++; + //$$ mcIdToReplayId.put(packet.id(), requestId); + //$$ return new ResourcePackSendS2CPacket(packet.id(), "replay://" + requestId, "", packet.required(), packet.prompt()); + //$$ } + //$$ + //$$ public void recordResourcePack(Path file, UUID uuid) { + //$$ Integer id = mcIdToReplayId.get(uuid); + //$$ if (id == null) { + //$$ logger.warn("Got resource pack download for unexpected uuid " + uuid + " at " + file); + //$$ return; + //$$ } + //$$ recordResourcePack(file, id); + //$$ } + //#elseif MC>=10800 public ResourcePackStatusC2SPacket makeStatusPacket(String hash, Status action) { //#if MC>=11002 return new ResourcePackStatusC2SPacket(action); @@ -128,7 +150,7 @@ public class ResourcePackRecorder { if (levelDir.isFile()) { netManager.send(makeStatusPacket(hash, Status.ACCEPTED)); addCallback(setServerResourcePack(levelDir), result -> { - recordResourcePack(levelDir, requestId); + recordResourcePack(levelDir.toPath(), requestId); netManager.send(makeStatusPacket(hash, Status.SUCCESSFULLY_LOADED)); }, throwable -> { netManager.send(makeStatusPacket(hash, Status.FAILED_DOWNLOAD)); @@ -196,7 +218,7 @@ public class ResourcePackRecorder { //#endif downloadResourcePack(final int requestId, String url, String hash) { ClientBuiltinResourcePackProvider packFinder = mc.getResourcePackDownloader(); - ((IDownloadingPackFinder) packFinder).setRequestCallback(file -> recordResourcePack(file, requestId)); + ((IDownloadingPackFinder) packFinder).setRequestCallback(file -> recordResourcePack(file.toPath(), requestId)); //#if MC>=11900 //$$ try { //$$ URL theUrl = new URL(url); @@ -286,7 +308,7 @@ public class ResourcePackRecorder { //$$ acc.setActive(false); //$$ acc.setPack(new FileResourcePack(file)); //$$ Minecraft.getMinecraft().scheduleResourcesRefresh(); - //$$ recordResourcePack(file, requestId); + //$$ recordResourcePack(file.toPath(), requestId); //$$ } //$$ } //$$ }, hashmap, 50*1024*1024, guiScreen, Minecraft.getMinecraft().getProxy()); diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_BlockOnChunkRebuilds.java b/src/main/java/com/replaymod/render/mixin/Mixin_BlockOnChunkRebuilds.java index fdbdd826..579e3268 100644 --- a/src/main/java/com/replaymod/render/mixin/Mixin_BlockOnChunkRebuilds.java +++ b/src/main/java/com/replaymod/render/mixin/Mixin_BlockOnChunkRebuilds.java @@ -9,6 +9,7 @@ import net.minecraft.util.thread.TaskExecutor; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +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; @@ -20,12 +21,28 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +//#if MC>=12003 +//$$ import net.minecraft.client.render.chunk.BlockBufferBuilderPool; +//#endif + @Mixin(ChunkBuilder.class) public abstract class Mixin_BlockOnChunkRebuilds implements ForceChunkLoadingHook.IBlockOnChunkRebuilds { + //#if MC>=12003 + //$$ @Shadow @Final private BlockBufferBuilderPool buffersPool; + //$$ @Unique + //$$ private int getAvailableBufferCount() { + //$$ return this.buffersPool.getAvailableBuilderCount(); + //$$ } + //#else @Shadow @Final private Queue threadBuffers; + @Unique + private int getAvailableBufferCount() { + return threadBuffers.size(); + } + //#endif //#if MC>=11800 - //$$ @org.spongepowered.asm.mixin.Unique + //$$ @Unique //$$ private boolean upload() { //$$ boolean anything = false; //$$ Runnable runnable; @@ -52,12 +69,12 @@ public abstract class Mixin_BlockOnChunkRebuilds implements ForceChunkLoadingHoo @Inject(method = "", at = @At("RETURN")) private void rememberTotalThreads(CallbackInfo ci) { - this.totalBufferCount = this.threadBuffers.size(); + this.totalBufferCount = getAvailableBufferCount(); } @Inject(method = "scheduleRunTasks", at = @At("RETURN")) private void notifyMainThreadIfEverythingIsDone(CallbackInfo ci) { - if (this.threadBuffers.size() == this.totalBufferCount) { + if (getAvailableBufferCount() == this.totalBufferCount) { // Looks like we're done, better notify the main thread in case the previous task didn't generate an upload this.waitingForWorkLock.lock(); try { @@ -84,7 +101,7 @@ public abstract class Mixin_BlockOnChunkRebuilds implements ForceChunkLoadingHoo private boolean waitForMainThreadWork() { boolean allDone = this.mailbox.ask(reply -> () -> { scheduleRunTasks(); - reply.send(this.threadBuffers.size() == this.totalBufferCount); + reply.send(getAvailableBufferCount() == this.totalBufferCount); }).join(); if (allDone) { diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index 7272c7a4..39ea79ec 100644 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -655,7 +655,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if(p instanceof ResourcePackSendS2CPacket) { ResourcePackSendS2CPacket packet = (ResourcePackSendS2CPacket) p; + //#if MC>=12003 + //$$ String url = packet.url(); + //#else String url = packet.getURL(); + //#endif if (url.startsWith("replay://")) { //#else //$$ String url; @@ -671,7 +675,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if (!file.exists()) { IOUtils.copy(replayFile.getResourcePack(hash).get(), new FileOutputStream(file)); } + //#if MC>=12003 + //$$ mc.getServerResourcePackProvider().addResourcePack(packet.id(), file.toPath()); + //#else setServerResourcePack(file); + //#endif } } return null; diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index c0475556..15b14a57 100644 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -42,6 +42,10 @@ import net.minecraft.network.ClientConnection; import java.io.IOException; import java.util.*; +//#if MC>=12003 +//$$ import net.minecraft.client.resource.server.ServerResourcePackManager; +//#endif + //#if MC>=12002 //$$ import io.netty.channel.ChannelDuplexHandler; //$$ import io.netty.channel.ChannelPromise; @@ -355,6 +359,10 @@ public class ReplayHandler { //#if MC>=11400 ((MinecraftAccessor) mc).setConnection(networkManager); //#endif + + //#if MC>=12003 + //$$ mc.getServerResourcePackProvider().init(networkManager, ServerResourcePackManager.AcceptanceStatus.ALLOWED); + //#endif } public ReplayFile getReplayFile() { diff --git a/versions/1.20.3/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java b/versions/1.20.3/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java new file mode 100644 index 00000000..7dc67921 --- /dev/null +++ b/versions/1.20.3/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java @@ -0,0 +1,29 @@ +package com.replaymod.recording.mixin; + +import com.replaymod.recording.ReplayModRecording; +import com.replaymod.recording.packet.PacketListener; +import net.minecraft.client.resource.server.ServerResourcePackManager; +import net.minecraft.util.Downloader; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.nio.file.Path; +import java.util.Map; +import java.util.UUID; + +@Mixin(ServerResourcePackManager.class) +public abstract class MixinDownloadingPackFinder { + @Inject(method = "onDownload", at = @At("HEAD")) + private void recordDownloadedPack(@Coerce Object packs, Downloader.DownloadResult result, CallbackInfo ci) { + PacketListener packetListener = ReplayModRecording.instance.getConnectionEventHandler().getPacketListener(); + if (packetListener == null) { + return; + } + for (Map.Entry entry : result.downloaded().entrySet()) { + packetListener.getResourcePackRecorder().recordResourcePack(entry.getValue(), entry.getKey()); + } + } +} diff --git a/versions/mapping-fabric-1.20.3-1.20.2.txt b/versions/mapping-fabric-1.20.3-1.20.2.txt index e69de29b..91036156 100644 --- a/versions/mapping-fabric-1.20.3-1.20.2.txt +++ b/versions/mapping-fabric-1.20.3-1.20.2.txt @@ -0,0 +1 @@ +net.minecraft.text.PlainTextContent net.minecraft.text.LiteralTextContent From 1bf1e0d7be382b8a48208ef90ba9c8a6d3faec8a Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 9 Dec 2023 10:19:27 +0100 Subject: [PATCH 7/8] Port to 1.20.4 --- build.gradle | 10 +++++----- jGui | 2 +- root.gradle.kts | 4 ++-- settings.gradle.kts | 4 ++-- .../recording/mixin/MixinDownloadingPackFinder.java | 0 ...0.3-1.20.2.txt => mapping-fabric-1.20.4-1.20.2.txt} | 0 6 files changed, 10 insertions(+), 10 deletions(-) rename versions/{1.20.3 => 1.20.4}/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java (100%) rename versions/{mapping-fabric-1.20.3-1.20.2.txt => mapping-fabric-1.20.4-1.20.2.txt} (100%) diff --git a/build.gradle b/build.gradle index 2bbff83a..79272749 100644 --- a/build.gradle +++ b/build.gradle @@ -265,7 +265,7 @@ dependencies { 11904: '1.19.4', 12001: '1.20.1', 12002: '1.20.2', - 12003: '1.20.3-rc1', + 12004: '1.20.4', ][mcVersion] mappings 'net.fabricmc:yarn:' + [ 11404: '1.14.4+build.16', @@ -284,9 +284,9 @@ dependencies { 11904: '1.19.4+build.1:v2', 12001: '1.20.1+build.2:v2', 12002: '1.20.2+build.4:v2', - 12003: '1.20.3-rc1+build.2:v2', + 12004: '1.20.4+build.1:v2', ][mcVersion] - modImplementation 'net.fabricmc:fabric-loader:0.14.11' + modImplementation 'net.fabricmc:fabric-loader:0.15.1' def fabricApiVersion = [ 11404: '0.4.3+build.247-1.14', 11502: '0.5.1+build.294-1.15', @@ -304,7 +304,7 @@ dependencies { 11904: '0.76.0+1.19.4', 12001: '0.83.1+1.20.1', 12002: '0.90.7+1.20.2', - 12003: '0.91.1+1.20.3', + 12004: '0.91.2+1.20.4', ][mcVersion] def fabricApiModules = [ "api-base", @@ -385,7 +385,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:5052121", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:6cd39b0", shadeExclusions // FIXME this should be pulled in by ReplayStudio, and IntelliJ sees it, but javac for some reason does not implementation 'com.github.viaversion:opennbt:0a02214' // 2.0-SNAPSHOT (ViaVersion Edition) diff --git a/jGui b/jGui index b4a618ae..83f71ae3 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit b4a618aef9f4a9260e8bb75368d75d95b6a72e0c +Subproject commit 83f71ae3e20f5d8a93f81ce7f30f7547262d3c22 diff --git a/root.gradle.kts b/root.gradle.kts index cef238dd..72cfd411 100755 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -193,7 +193,7 @@ val doRelease by tasks.registering { defaultTasks("bundleJar") preprocess { - val mc12003 = createNode("1.20.3", 12003, "yarn") + val mc12004 = createNode("1.20.4", 12004, "yarn") val mc12002 = createNode("1.20.2", 12002, "yarn") val mc12001 = createNode("1.20.1", 12001, "yarn") val mc11904 = createNode("1.19.4", 11904, "yarn") @@ -220,7 +220,7 @@ preprocess { val mc10800 = createNode("1.8", 10800, "srg") val mc10710 = createNode("1.7.10", 10710, "srg") - mc12003.link(mc12002, file("versions/mapping-fabric-1.20.3-1.20.2.txt")) + mc12004.link(mc12002, file("versions/mapping-fabric-1.20.4-1.20.2.txt")) mc12002.link(mc12001) mc12001.link(mc11904) mc11904.link(mc11903) diff --git a/settings.gradle.kts b/settings.gradle.kts index 3ab38e6b..bb1dd6e4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,7 +39,7 @@ val jGuiVersions = listOf( "1.19.4", "1.20.1", "1.20.2", - "1.20.3", + "1.20.4", ) val replayModVersions = listOf( // "1.7.10", @@ -67,7 +67,7 @@ val replayModVersions = listOf( "1.19.4", "1.20.1", "1.20.2", - "1.20.3", + "1.20.4", ) rootProject.buildFileName = "root.gradle.kts" diff --git a/versions/1.20.3/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java b/versions/1.20.4/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java similarity index 100% rename from versions/1.20.3/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java rename to versions/1.20.4/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java diff --git a/versions/mapping-fabric-1.20.3-1.20.2.txt b/versions/mapping-fabric-1.20.4-1.20.2.txt similarity index 100% rename from versions/mapping-fabric-1.20.3-1.20.2.txt rename to versions/mapping-fabric-1.20.4-1.20.2.txt From 6e5536305b5c046b2b0aac93ca2b267f6c132d99 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 9 Dec 2023 10:20:48 +0100 Subject: [PATCH 8/8] Bump 1.20.2 fabric-api to version 0.92.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 79272749..cd3f4f57 100644 --- a/build.gradle +++ b/build.gradle @@ -303,7 +303,7 @@ dependencies { 11903: '0.68.1+1.19.3', 11904: '0.76.0+1.19.4', 12001: '0.83.1+1.20.1', - 12002: '0.90.7+1.20.2', + 12002: '0.91.2+1.20.2', 12004: '0.91.2+1.20.4', ][mcVersion] def fabricApiModules = [