Record raw packets instead of re-encoding decoded packets

Until now, recording worked by injecting right before the vanilla packet
handler. At that point it was receiving packet classes and had to re-encode them
to store them in the replay file. However, not all decoded packets can
necessarily be re-encoded without errors, requiring us to have a bunch of
workarounds.

This commit changes the way recording works by injecting right before the
decoder. It therefore receives the raw bytebufs and no longer has to deal with
the re-encode issue.
This commit is contained in:
Jonas Herzig
2022-06-15 13:04:33 +02:00
parent df11ba2e36
commit 13ac8dcf0a
5 changed files with 195 additions and 279 deletions

View File

@@ -3,7 +3,6 @@ package com.replaymod.recording.handler;
import com.replaymod.core.ReplayMod; import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.ModCompat; import com.replaymod.core.utils.ModCompat;
import com.replaymod.core.utils.Utils; import com.replaymod.core.utils.Utils;
import com.replaymod.core.versions.MCVer;
import com.replaymod.editor.gui.MarkerProcessor; import com.replaymod.editor.gui.MarkerProcessor;
import com.replaymod.recording.ServerInfoExt; import com.replaymod.recording.ServerInfoExt;
import com.replaymod.recording.Setting; import com.replaymod.recording.Setting;
@@ -43,7 +42,6 @@ import static com.replaymod.core.versions.MCVer.getMinecraft;
*/ */
public class ConnectionEventHandler { public class ConnectionEventHandler {
private static final String packetHandlerKey = "packet_handler";
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss"; private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
private static final MinecraftClient mc = getMinecraft(); private static final MinecraftClient mc = getMinecraft();
@@ -141,7 +139,13 @@ public class ConnectionEventHandler {
metaData.setMcVersion(ReplayMod.instance.getMinecraftVersion()); metaData.setMcVersion(ReplayMod.instance.getMinecraftVersion());
packetListener = new PacketListener(core, outputPath, replayFile, metaData); packetListener = new PacketListener(core, outputPath, replayFile, metaData);
Channel channel = ((NetworkManagerAccessor) networkManager).getChannel(); Channel channel = ((NetworkManagerAccessor) networkManager).getChannel();
channel.pipeline().addBefore(packetHandlerKey, "replay_recorder", packetListener); if (channel.pipeline().get(PacketListener.DECODER_KEY) != null) {
// Regular channel, we'll inject our recorder directly before the decoder
channel.pipeline().addBefore(PacketListener.DECODER_KEY, PacketListener.RAW_RECORDER_KEY, packetListener);
} else {
// Integrated server passes packets directly, there's no splitting, decompression or decoding
channel.pipeline().addFirst(PacketListener.RAW_RECORDER_KEY, packetListener);
}
recordingEventHandler = new RecordingEventHandler(packetListener); recordingEventHandler = new RecordingEventHandler(packetListener);
recordingEventHandler.register(); recordingEventHandler.register();

View File

@@ -321,99 +321,6 @@ public class RecordingEventHandler extends EventRegistrations {
} }
} }
//#if FABRIC>=1
// FIXME fabric
//#else
//$$ @SubscribeEvent
//$$ public void onPickupItem(ItemPickupEvent event) {
//$$ try {
//#if MC>=11100
//#if MC>=11200
//#if MC>=11400
//$$ ItemStack stack = event.getStack();
//$$ packetListener.save(new SCollectItemPacket(
//$$ event.getOriginalEntity().getEntityId(),
//$$ event.getPlayer().getEntityId(),
//$$ event.getStack().getCount()
//$$ ));
//#else
//$$ packetListener.save(new SPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId(),
//$$ event.pickedUp.getItem().getMaxStackSize()));
//#endif
//#else
//$$ packetListener.save(new SPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId(),
//$$ event.pickedUp.getEntityItem().getMaxStackSize()));
//#endif
//#else
//$$ packetListener.save(new SPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId()));
//#endif
//$$ } catch(Exception e) {
//$$ e.printStackTrace();
//$$ }
//$$ }
//#endif
//#if MC>=11400
// FIXME fabric
//#else
//$$ @SubscribeEvent
//$$ public void onSleep(PlayerSleepInBedEvent event) {
//$$ try {
//#if MC>=10904
//$$ if (event.getEntityPlayer() != mc.player) {
//$$ return;
//$$ }
//$$
//$$ packetListener.save(new SPacketUseBed(event.getEntityPlayer(), event.getPos()));
//#else
//$$ if (event.entityPlayer != mc.thePlayer) {
//$$ return;
//$$ }
//$$
//$$ packetListener.save(new S0APacketUseBed(event.entityPlayer,
//#if MC>=10800
//$$ event.pos
//#else
//$$ event.x, event.y, event.z
//#endif
//$$ ));
//#endif
//$$
//$$ wasSleeping = true;
//$$
//$$ } catch(Exception e) {
//$$ e.printStackTrace();
//$$ }
//$$ }
//#endif
/* FIXME event not (yet?) on 1.13
@SubscribeEvent
public void enterMinecart(MinecartInteractEvent event) {
try {
//#if MC>=10904
if(event.getEntity() != mc.player) {
return;
}
packetListener.save(new SPacketEntityAttach(event.getPlayer(), event.getMinecart()));
lastRiding = event.getMinecart().getEntityId();
//#else
//$$ if(event.entity != mc.thePlayer) {
//$$ return;
//$$ }
//$$
//$$ packetListener.save(new S1BPacketEntityAttach(0, event.player, event.minecart));
//$$
//$$ lastRiding = event.minecart.getEntityId();
//#endif
} catch(Exception e) {
e.printStackTrace();
}
}
*/
//#if MC>=10800 //#if MC>=10800
public void onBlockBreakAnim(int breakerId, BlockPos pos, int progress) { public void onBlockBreakAnim(int breakerId, BlockPos pos, int progress) {
//#else //#else

View File

@@ -0,0 +1,39 @@
package com.replaymod.recording.mixin;
import com.replaymod.recording.packet.PacketListener;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import net.minecraft.network.ClientConnection;
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.Map;
@Mixin(ClientConnection.class)
public abstract class MixinClientConnection {
@Shadow
private Channel channel;
@Inject(method = "setCompressionThreshold", at = @At("RETURN"))
private void ensureReplayModRecorderIsAfterDecompress(CallbackInfo ci) {
ChannelHandler recorder = null;
for (Map.Entry<String, ChannelHandler> entry : channel.pipeline()) {
String key = entry.getKey();
if (PacketListener.RAW_RECORDER_KEY.equals(key)) {
recorder = entry.getValue();
}
if (PacketListener.DECOMPRESS_KEY.equals(key)) {
if (recorder != null) {
// If we've already found the recorder, then that means decompress is after recorder. That's no good
// because it means the recorder is getting compressed packets, we need to move the recorder.
channel.pipeline().remove(recorder);
channel.pipeline().addBefore(PacketListener.DECODER_KEY, PacketListener.RAW_RECORDER_KEY, recorder);
return;
}
}
}
}
}

View File

@@ -2,7 +2,6 @@ package com.replaymod.recording.packet;
import com.github.steveice10.netty.buffer.PooledByteBufAllocator; import com.github.steveice10.netty.buffer.PooledByteBufAllocator;
import com.github.steveice10.packetlib.tcp.io.ByteBufNetOutput; import com.github.steveice10.packetlib.tcp.io.ByteBufNetOutput;
import com.google.common.collect.Lists;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.replaymod.core.ReplayMod; import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.Restrictions; import com.replaymod.core.utils.Restrictions;
@@ -16,23 +15,24 @@ import com.replaymod.recording.handler.ConnectionEventHandler;
import com.replaymod.replaystudio.PacketData; import com.replaymod.replaystudio.PacketData;
import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.data.Marker;
import com.replaymod.replaystudio.io.ReplayOutputStream; import com.replaymod.replaystudio.io.ReplayOutputStream;
import com.replaymod.replaystudio.lib.viaversion.api.protocol.packet.State;
import com.replaymod.replaystudio.protocol.Packet;
import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayFile;
import com.replaymod.replaystudio.replay.ReplayMetaData; import com.replaymod.replaystudio.replay.ReplayMetaData;
import de.johni0702.minecraft.gui.container.VanillaGuiScreen; import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.AttributeKey;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.network.packet.s2c.play.DisconnectS2CPacket; import net.minecraft.network.packet.s2c.play.DisconnectS2CPacket;
import net.minecraft.network.packet.s2c.play.ItemPickupAnimationS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkState;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.LiteralText; import net.minecraft.text.LiteralText;
import net.minecraft.util.crash.CrashReport; import net.minecraft.util.crash.CrashReport;
@@ -41,24 +41,6 @@ import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
//#if MC>=11500
//#else
//$$ import com.replaymod.recording.mixin.SPacketSpawnMobAccessor;
//$$ import com.replaymod.recording.mixin.SPacketSpawnPlayerAccessor;
//$$ import net.minecraft.network.packet.s2c.play.MobSpawnS2CPacket;
//#endif
//#if MC>=11400
import net.minecraft.network.packet.s2c.login.LoginSuccessS2CPacket;
//#else
//$$ import net.minecraftforge.fml.common.network.internal.FMLProxyPacket;
//#endif
//#if MC>=10904
//#else
//$$ import java.util.List;
//#endif
//#if MC>=10800 //#if MC>=10800
//#if MC<10904 //#if MC<10904
//$$ import net.minecraft.network.play.server.S46PacketSetCompressionLevel; //$$ import net.minecraft.network.play.server.S46PacketSetCompressionLevel;
@@ -68,7 +50,6 @@ import net.minecraft.network.packet.s2c.play.ResourcePackSendS2CPacket;
import net.minecraft.network.NetworkSide; import net.minecraft.network.NetworkSide;
//#endif //#endif
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
@@ -86,12 +67,31 @@ import java.util.concurrent.atomic.AtomicInteger;
import static com.replaymod.core.versions.MCVer.*; import static com.replaymod.core.versions.MCVer.*;
import static com.replaymod.replaystudio.util.Utils.writeInt; import static com.replaymod.replaystudio.util.Utils.writeInt;
import static java.util.Objects.requireNonNull;
@ChannelHandler.Sharable // so we can re-order it
public class PacketListener extends ChannelInboundHandlerAdapter { public class PacketListener extends ChannelInboundHandlerAdapter {
public static final String RAW_RECORDER_KEY = "replay_recorder_raw";
public static final String DECODED_RECORDER_KEY = "replay_recorder_decoded";
public static final String DECOMPRESS_KEY = "decompress";
public static final String DECODER_KEY = "decoder";
private static final MinecraftClient mc = getMinecraft(); private static final MinecraftClient mc = getMinecraft();
private static final Logger logger = LogManager.getLogger(); private static final Logger logger = LogManager.getLogger();
//#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<10904
//$$ private static final int PACKET_ID_PLAY_COMPRESSION = getPacketId(EnumConnectionState.PLAY, new S46PacketSetCompressionLevel());
//#endif
private final ReplayMod core; private final ReplayMod core;
private final Path outputPath; private final Path outputPath;
private final ReplayFile replayFile; private final ReplayFile replayFile;
@@ -109,13 +109,6 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
private long lastSentPacket; private long lastSentPacket;
private long timePassedWhilePaused; private long timePassedWhilePaused;
private volatile boolean serverWasPaused; private volatile boolean serverWasPaused;
//#if MC>=11400
private NetworkState connectionState = NetworkState.LOGIN;
private boolean loginPhase = true;
//#else
//$$ private EnumConnectionState connectionState = EnumConnectionState.PLAY;
//$$ private boolean loginPhase = false;
//#endif
/** /**
* Used to keep track of the last metadata save job submitted to the save service and * Used to keep track of the last metadata save job submitted to the save service and
@@ -163,6 +156,17 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
}); });
} }
public void save(net.minecraft.network.Packet packet) {
Packet encoded;
try {
encoded = encodeMcPacket(getConnectionState(), packet);
} catch (Exception e) {
logger.error("Encoding packet:", e);
return;
}
save(encoded);
}
public void save(Packet packet) { public void save(Packet packet) {
// If we're not on the main thread (i.e. we're on the netty thread), then we need to schedule the saving // If we're not on the main thread (i.e. we're on the netty thread), then we need to schedule the saving
// to happen on the main thread so we can guarantee correct ordering of inbound and inject packets. // to happen on the main thread so we can guarantee correct ordering of inbound and inject packets.
@@ -173,24 +177,12 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
return; return;
} }
try { try {
if(packet instanceof PlayerSpawnS2CPacket) { //#if MC>=11800
//#if MC>=10800 if (packet.getRegistry().getState() == State.LOGIN && packet.getId() == PACKET_ID_LOGIN_COMPRESSION) {
UUID uuid = ((PlayerSpawnS2CPacket) packet).getPlayerUuid();
//#else
//$$ UUID uuid = ((S0CPacketSpawnPlayer) packet).func_148948_e().getId();
//#endif
Set<String> uuids = new HashSet<>(Arrays.asList(metaData.getPlayers()));
uuids.add(uuid.toString());
metaData.setPlayers(uuids.toArray(new String[uuids.size()]));
saveMetaData();
}
//#if MC>=10800
if (packet instanceof LoginCompressionS2CPacket) {
return; // Replay data is never compressed on the packet level return; // Replay data is never compressed on the packet level
} }
//#if MC<10904 //#if MC<10904
//$$ if (packet instanceof S46PacketSetCompressionLevel) { //$$ if (packet.getRegistry().getState() == State.PLAY && packet.getId() == PACKET_ID_PLAY_COMPRESSION) {
//$$ return; // Replay data is never compressed on the packet level //$$ return; // Replay data is never compressed on the packet level
//$$ } //$$ }
//#endif //#endif
@@ -203,7 +195,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
} }
int timestamp = (int) (now - startTime - timePassedWhilePaused); int timestamp = (int) (now - startTime - timePassedWhilePaused);
lastSentPacket = timestamp; lastSentPacket = timestamp;
PacketData packetData = getPacketData(timestamp, packet); PacketData packetData = new PacketData(timestamp, packet);
saveService.submit(() -> { saveService.submit(() -> {
try { try {
if (ReplayMod.isMinimalMode()) { if (ReplayMod.isMinimalMode()) {
@@ -230,18 +222,27 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}); });
//#if MC>=11400
if (packet instanceof LoginSuccessS2CPacket) {
connectionState = NetworkState.PLAY;
loginPhase = false;
}
//#endif
} catch(Exception e) { } catch(Exception e) {
logger.error("Writing packet:", e); logger.error("Writing packet:", e);
} }
} }
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
if (ctx.pipeline().get(DECODED_RECORDER_KEY) == null) {
if (ctx.pipeline().get(PacketListener.DECODER_KEY) != null) {
// Regular channel, we'll inject our decoded recorder directly after the decoder
ctx.pipeline().addAfter(DECODER_KEY, DECODED_RECORDER_KEY, new DecodedPacketListener());
} else {
// Integrated server passes packets directly, there's no splitting, decompression or decoding
// The decoded packet handler can just go directly behind this hand
ctx.pipeline().addAfter(RAW_RECORDER_KEY, DECODED_RECORDER_KEY, new DecodedPacketListener());
}
}
}
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) { public void channelInactive(ChannelHandlerContext ctx) {
metaData.setDuration((int) lastSentPacket); metaData.setDuration((int) lastSentPacket);
@@ -328,139 +329,44 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
} }
this.context = ctx; this.context = ctx;
if (msg instanceof Packet) { NetworkState connectionState = getConnectionState();
try {
Packet packet = (Packet) msg;
//#if MC>=10904 Packet packet = null;
if(packet instanceof ItemPickupAnimationS2CPacket) { if (msg instanceof ByteBuf) {
if(mc.player != null || // for regular connections, we're expecting to observe `ByteBuf`s here
((ItemPickupAnimationS2CPacket) packet).getEntityId() == mc.player.getEntityId()) { ByteBuf buf = (ByteBuf) msg;
//#else if (buf.readableBytes() > 0) {
//$$ if(packet instanceof S0DPacketCollectItem) { packet = decodePacket(connectionState, buf);
//$$ if(mc.thePlayer != null || ((S0DPacketCollectItem) packet).getEntityID() == mc.thePlayer.getEntityId()) {
//#endif
super.channelRead(ctx, msg);
return;
} }
} else if (msg instanceof net.minecraft.network.Packet) {
// for integrated server connections MC is passing the packet objects directly, so we need to encode them
// ourselves to be able to store them
packet = encodeMcPacket(connectionState, (net.minecraft.network.Packet) msg);
} }
//#if MC>=10800 if (packet != null) {
if (packet instanceof ResourcePackSendS2CPacket) { if (connectionState == NetworkState.PLAY && packet.getId() == PACKET_ID_RESOURCE_PACK_SEND) {
ClientConnection connection = ctx.pipeline().get(ClientConnection.class); ClientConnection connection = ctx.pipeline().get(ClientConnection.class);
save(resourcePackRecorder.handleResourcePack(connection, (ResourcePackSendS2CPacket) packet)); save(resourcePackRecorder.handleResourcePack(connection, (ResourcePackSendS2CPacket) decodeMcPacket(packet)));
return; return;
} }
//#else
//$$ if (packet instanceof S3FPacketCustomPayload) {
//$$ S3FPacketCustomPayload p = (S3FPacketCustomPayload) packet;
//$$ if ("MC|RPack".equals(p.func_149169_c())) {
//$$ save(resourcePackRecorder.handleResourcePack(p));
//$$ return;
//$$ }
//$$ }
//#endif
//#if MC<11400
//$$ if (packet instanceof FMLProxyPacket) {
//$$ // This packet requires special handling
//#if MC>=10800
//$$ ((FMLProxyPacket) packet).toS3FPackets().forEach(this::save);
//#else
//$$ save(((FMLProxyPacket) packet).toS3FPacket());
//#endif
//$$ super.channelRead(ctx, msg);
//$$ return;
//$$ }
//#endif
//#if MC>=10800
if (packet instanceof CustomPayloadS2CPacket) {
// Forge may read from this ByteBuf and/or release it during handling
// We want to save the full thing however, so we create a copy and save that one instead of the
// original one
// Note: This isn't an issue with vanilla MC because our saving code runs on the main thread
// shortly before the vanilla handling code does. Forge however does some stuff on the netty
// threads which leads to this race condition
packet = new CustomPayloadS2CPacket(
((CustomPayloadS2CPacket) packet).getChannel(),
new PacketByteBuf(((CustomPayloadS2CPacket) packet).getData().slice().retain())
);
}
//#endif
save(packet); save(packet);
if (packet instanceof CustomPayloadS2CPacket) {
CustomPayloadS2CPacket p = (CustomPayloadS2CPacket) packet;
if (Restrictions.PLUGIN_CHANNEL.equals(p.getChannel())) {
packet = new DisconnectS2CPacket(new LiteralText("Please update to view this replay."));
save(packet);
}
}
} catch(Exception e) {
logger.error("Handling packet for recording:", e);
}
} }
super.channelRead(ctx, msg); super.channelRead(ctx, msg);
} }
//#if MC>=10904 private NetworkState getConnectionState() {
private <T> void DataManager_set(DataTracker dataManager, DataTracker.Entry<T> entry) { ChannelHandlerContext ctx = context;
dataManager.startTracking(entry.getData(), entry.get()); if (ctx == null) {
return NetworkState.LOGIN;
}
AttributeKey<NetworkState> key = ClientConnection.ATTR_KEY_PROTOCOL;
return ctx.channel().attr(key).get();
} }
//#endif
@SuppressWarnings("unchecked")
private PacketData getPacketData(int timestamp, Packet packet) throws Exception {
//#if MC<11500
//$$ if (packet instanceof MobSpawnS2CPacket) {
//$$ MobSpawnS2CPacket p = (MobSpawnS2CPacket) packet;
//$$ SPacketSpawnMobAccessor pa = (SPacketSpawnMobAccessor) p;
//$$ if (pa.getDataManager() == null) {
//$$ pa.setDataManager(new DataTracker(null));
//$$ if (p.getTrackedValues() != null) {
//$$ Set<Integer> seen = new HashSet<>();
//#if MC>=10904
//$$ for (DataTracker.Entry<?> entry : Lists.reverse(p.getTrackedValues())) {
//$$ if (!seen.add(entry.getData().getId())) continue;
//$$ DataManager_set(pa.getDataManager(), entry);
//$$ }
//#else
//$$ for(DataWatcher.WatchableObject wo : Lists.reverse((List<DataWatcher.WatchableObject>) p.func_149027_c())) {
//$$ if (!seen.add(wo.getDataValueId())) continue;
//$$ pa.getDataManager().addObject(wo.getDataValueId(), wo.getObject());
//$$ }
//#endif
//$$ }
//$$ }
//$$ }
//$$
//$$ if (packet instanceof PlayerSpawnS2CPacket) {
//$$ PlayerSpawnS2CPacket p = (PlayerSpawnS2CPacket) packet;
//$$ SPacketSpawnPlayerAccessor pa = (SPacketSpawnPlayerAccessor) p;
//$$ if (pa.getDataManager() == null) {
//$$ pa.setDataManager(new DataTracker(null));
//$$ if (p.getTrackedValues() != null) {
//$$ Set<Integer> seen = new HashSet<>();
//#if MC>=10904
//$$ for (DataTracker.Entry<?> entry : Lists.reverse(p.getTrackedValues())) {
//$$ if (!seen.add(entry.getData().getId())) continue;
//$$ DataManager_set(pa.getDataManager(), entry);
//$$ }
//#else
//$$ for(DataWatcher.WatchableObject wo : Lists.reverse((List<DataWatcher.WatchableObject>) p.func_148944_c())) {
//$$ if (!seen.add(wo.getDataValueId())) continue;
//$$ pa.getDataManager().addObject(wo.getDataValueId(), wo.getObject());
//$$ }
//#endif
//$$ }
//$$ }
//$$ }
//#endif
private static Packet encodeMcPacket(NetworkState connectionState, net.minecraft.network.Packet packet) throws Exception {
//#if MC>=10800 //#if MC>=10800
Integer packetId = connectionState.getPacketId(NetworkSide.CLIENTBOUND, packet); Integer packetId = connectionState.getPacketId(NetworkSide.CLIENTBOUND, packet);
//#else //#else
@@ -472,23 +378,55 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
ByteBuf byteBuf = Unpooled.buffer(); ByteBuf byteBuf = Unpooled.buffer();
try { try {
packet.write(new PacketByteBuf(byteBuf)); packet.write(new PacketByteBuf(byteBuf));
return new PacketData(timestamp, new com.replaymod.replaystudio.protocol.Packet( return new Packet(
MCVer.getPacketTypeRegistry(loginPhase), MCVer.getPacketTypeRegistry(connectionState == NetworkState.LOGIN),
packetId, packetId,
com.github.steveice10.netty.buffer.Unpooled.wrappedBuffer( com.github.steveice10.netty.buffer.Unpooled.wrappedBuffer(
byteBuf.array(), byteBuf.array(),
byteBuf.arrayOffset(), byteBuf.arrayOffset(),
byteBuf.readableBytes() byteBuf.readableBytes()
) )
)); );
} finally { } finally {
byteBuf.release(); byteBuf.release();
//#if MC>=10800
if (packet instanceof CustomPayloadS2CPacket) {
((CustomPayloadS2CPacket) packet).getData().release();
} }
}
private static net.minecraft.network.Packet decodeMcPacket(Packet packet) throws IOException, IllegalAccessException, InstantiationException {
NetworkState connectionState = packet.getRegistry().getState() == State.LOGIN ? NetworkState.LOGIN : NetworkState.PLAY;
int packetId = packet.getId();
PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.wrappedBuffer(packet.getBuf().nioBuffer()));
//#if MC>=11700
//$$ return connectionState.getPacketHandler(NetworkSide.CLIENTBOUND, packetId, packetBuf);
//#else
//#if MC>=10800
net.minecraft.network.Packet p = connectionState.getPacketHandler(NetworkSide.CLIENTBOUND, packetId);
//#else
//$$ net.minecraft.network.Packet p = net.minecraft.network.Packet.generatePacket(connectionState.func_150755_b(), packetId);
//#endif //#endif
p.read(packetBuf);
return p;
//#endif
}
private static Packet decodePacket(NetworkState connectionState, ByteBuf buf) {
PacketByteBuf packetBuf = new PacketByteBuf(buf.slice());
int packetId = packetBuf.readVarInt();
byte[] bytes = new byte[packetBuf.readableBytes()];
packetBuf.readBytes(bytes);
return new Packet(
MCVer.getPacketTypeRegistry(connectionState == NetworkState.LOGIN),
packetId,
com.github.steveice10.netty.buffer.Unpooled.wrappedBuffer(bytes)
);
}
private static int getPacketId(NetworkState networkState, net.minecraft.network.Packet packet) {
try {
return requireNonNull(networkState.getPacketId(NetworkSide.CLIENTBOUND, packet));
} catch (Exception e) {
throw new RuntimeException("Failed to determine packet id for " + packet.getClass(), e);
} }
} }
@@ -530,4 +468,31 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
public void setServerWasPaused() { public void setServerWasPaused() {
this.serverWasPaused = true; this.serverWasPaused = true;
} }
private class DecodedPacketListener extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof CustomPayloadS2CPacket) {
CustomPayloadS2CPacket packet = (CustomPayloadS2CPacket) msg;
if (Restrictions.PLUGIN_CHANNEL.equals(packet.getChannel())) {
save(new DisconnectS2CPacket(new LiteralText("Please update to view this replay.")));
}
}
if (msg instanceof PlayerSpawnS2CPacket) {
//#if MC>=10800
UUID uuid = ((PlayerSpawnS2CPacket) msg).getPlayerUuid();
//#else
//$$ UUID uuid = ((S0CPacketSpawnPlayer) msg).func_148948_e().getId();
//#endif
Set<String> uuids = new HashSet<>(Arrays.asList(metaData.getPlayers()));
uuids.add(uuid.toString());
metaData.setPlayers(uuids.toArray(new String[uuids.size()]));
saveMetaData();
}
super.channelRead(ctx, msg);
}
}
} }

View File

@@ -8,6 +8,7 @@
"EntityLivingBaseAccessor", "EntityLivingBaseAccessor",
"IntegratedServerAccessor", "IntegratedServerAccessor",
"NetworkManagerAccessor", "NetworkManagerAccessor",
"MixinClientConnection",
//#if MC<11500 //#if MC<11500
//$$ "SPacketSpawnMobAccessor", //$$ "SPacketSpawnMobAccessor",
//$$ "SPacketSpawnPlayerAccessor", //$$ "SPacketSpawnPlayerAccessor",