package openmods.network.event; import com.google.common.base.Preconditions; import cpw.mods.fml.common.network.ByteBufUtils; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.common.network.internal.FMLProxyPacket; import cpw.mods.fml.relauncher.Side; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageCodec; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import net.minecraft.network.INetHandler; import openmods.OpenMods; import openmods.utils.io.PacketChunker; @Sharable public class NetworkEventCodec extends MessageToMessageCodec<FMLProxyPacket, NetworkEvent> { private final PacketChunker chunker = new PacketChunker(); private final NetworkEventRegistry registry; public NetworkEventCodec(NetworkEventRegistry registry) { this.registry = registry; } @Override protected void encode(ChannelHandlerContext ctx, NetworkEvent msg, List<Object> out) throws IOException { int id = registry.getIdForClass(msg.getClass()); INetworkEventType type = registry.getTypeForId(id); byte[] payload = toRawBytes(msg, type.isCompressed()); Channel channel = ctx.channel(); Side side = channel.attr(NetworkRegistry.CHANNEL_SOURCE).get(); EventDirection validator = type.getDirection(); Preconditions.checkState(validator != null && validator.validateSend(side), "Invalid direction: sending packet %s on side %s", msg.getClass(), side); if (type.isChunked()) { final int maxChunkSize = side == Side.SERVER? PacketChunker.PACKET_SIZE_S3F : PacketChunker.PACKET_SIZE_C17; byte[][] chunked = chunker.splitIntoChunks(payload, maxChunkSize); for (byte[] chunk : chunked) { FMLProxyPacket partialPacket = createPacket(id, chunk); partialPacket.setDispatcher(msg.dispatcher); out.add(partialPacket); } } else { FMLProxyPacket partialPacket = createPacket(id, payload); partialPacket.setDispatcher(msg.dispatcher); out.add(partialPacket); } } @Override protected void decode(ChannelHandlerContext ctx, FMLProxyPacket msg, List<Object> out) throws Exception { ByteBuf payload = msg.payload(); int typeId = ByteBufUtils.readVarInt(payload, 5); INetworkEventType type = registry.getTypeForId(typeId); Channel channel = ctx.channel(); Side side = channel.attr(NetworkRegistry.CHANNEL_SOURCE).get(); EventDirection validator = type.getDirection(); Preconditions.checkState(validator != null && validator.validateReceive(side), "Invalid direction: receiving packet %s on side %s", msg.getClass(), side); InputStream input = new ByteBufInputStream(payload); if (type.isChunked()) { byte[] fullPayload = chunker.consumeChunk(input, input.available()); if (fullPayload == null) return; input = new ByteArrayInputStream(fullPayload); } if (type.isCompressed()) input = new GZIPInputStream(input); DataInput data = new DataInputStream(input); NetworkEvent event = type.createPacket(); event.readFromStream(data); event.dispatcher = msg.getDispatcher(); INetHandler handler = msg.handler(); if (handler != null) event.sender = OpenMods.proxy.getPlayerFromHandler(handler); int bufferJunkSize = input.available(); if (bufferJunkSize > 0) { // compressed stream neads extra read to realize it's finished Preconditions.checkState(input.read() == -1, "%s junk bytes left in buffer, event", bufferJunkSize, event); } input.close(); out.add(event); } private static FMLProxyPacket createPacket(int id, byte[] payload) { ByteBuf buf = Unpooled.buffer(payload.length + 5); ByteBufUtils.writeVarInt(buf, id, 5); buf.writeBytes(payload); FMLProxyPacket partialPacket = new FMLProxyPacket(buf.copy(), NetworkEventDispatcher.CHANNEL_NAME); return partialPacket; } private static byte[] toRawBytes(NetworkEvent event, boolean compress) throws IOException { ByteArrayOutputStream payload = new ByteArrayOutputStream(); OutputStream stream = compress? new GZIPOutputStream(payload) : payload; DataOutputStream output = new DataOutputStream(stream); event.writeToStream(output); stream.close(); return payload.toByteArray(); } }