sendChunkDataPackets$nullifyRest(ChunkMap tacs) {
71 | return Int2ObjectMaps.emptyMap();
72 | }
73 | }
--------------------------------------------------------------------------------
/src/main/java/com/abdelaziz/pluto/mixin/network/flushconsolidation/ChunkMapMixin.java:
--------------------------------------------------------------------------------
1 | package com.abdelaziz.pluto.mixin.network.flushconsolidation;
2 |
3 | import com.abdelaziz.pluto.common.network.util.AutoFlushUtil;
4 | import com.abdelaziz.pluto.common.player.PlutoServerPlayer;
5 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
6 | import net.minecraft.core.SectionPos;
7 | import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
8 | import net.minecraft.server.level.ChunkMap;
9 | import net.minecraft.server.level.PlayerMap;
10 | import net.minecraft.server.level.ServerLevel;
11 | import net.minecraft.server.level.ServerPlayer;
12 | import net.minecraft.world.level.ChunkPos;
13 | import org.apache.commons.lang3.mutable.MutableObject;
14 | import org.spongepowered.asm.mixin.Final;
15 | import org.spongepowered.asm.mixin.Mixin;
16 | import org.spongepowered.asm.mixin.Overwrite;
17 | import org.spongepowered.asm.mixin.Shadow;
18 | import org.spongepowered.asm.mixin.injection.At;
19 | import org.spongepowered.asm.mixin.injection.Inject;
20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
21 |
22 | /**
23 | * Mixes into various methods in {@code ChunkMap} to utilize flush consolidation for sending chunks all at once to the
24 | * client, along with loading chunks in a spiral order. Helpful for heavy server activity or flying very quickly.
25 | *
26 | * Note for anyone attempting to modify this class in the future: for some reason, mojang includes both the chunk loading & chunk unloading
27 | * packets in the same method. This is why chunks must always be sent to the player when they leave an area.
28 | */
29 | @Mixin(ChunkMap.class)
30 | public abstract class ChunkMapMixin {
31 | @Shadow
32 | @Final
33 | ServerLevel level;
34 |
35 | @Shadow
36 | int viewDistance;
37 |
38 | @Shadow
39 | @Final
40 | private Int2ObjectMap entityMap;
41 |
42 | @Shadow
43 | @Final
44 | private PlayerMap playerMap;
45 |
46 | @Shadow
47 | @Final
48 | private ChunkMap.DistanceManager distanceManager;
49 |
50 | @Shadow
51 | public static boolean isChunkInRange(int x1, int y1, int x2, int y2, int maxDistance) {
52 | // PAIL: isWithinEuclideanDistance(x1, y1, x2, y2, maxDistance)
53 | throw new AssertionError("pedantic");
54 | }
55 |
56 | /**
57 | * This is run on login. This method is overwritten to avoid sending duplicate chunks (which mc does by default)
58 | *
59 | * @reason optimize sending chunks
60 | * @author solonovamax
61 | */
62 | @Overwrite
63 | public void updatePlayerStatus(ServerPlayer player, boolean added) {
64 | boolean skipPlayer = this.skipPlayer(player);
65 | boolean isWatchingWorld = !this.playerMap.ignoredOrUnknown(player);
66 |
67 | int chunkPosX = SectionPos.blockToSectionCoord(player.getBlockX());
68 | int chunkPosZ = SectionPos.blockToSectionCoord(player.getBlockZ());
69 |
70 | AutoFlushUtil.setAutoFlush(player, false);
71 |
72 | try {
73 | if (added) {
74 | this.playerMap.addPlayer(ChunkPos.asLong(chunkPosX, chunkPosZ), player, skipPlayer);
75 | this.updatePlayerPos(player);
76 |
77 | if (!skipPlayer) {
78 | this.distanceManager.addPlayer(SectionPos.of(player), player);
79 | }
80 |
81 | // Send spiral watch packets if added
82 | sendSpiralChunkWatchPackets(player);
83 | } else {
84 | SectionPos chunkSectionPos = player.getLastSectionPos();
85 | this.playerMap.removePlayer(chunkSectionPos.chunk().toLong(), player);
86 |
87 | if (isWatchingWorld) {
88 | this.distanceManager.removePlayer(chunkSectionPos, player);
89 | }
90 |
91 | // Loop through & unload chunks if removed
92 | unloadChunks(player, chunkPosX, chunkPosZ, viewDistance);
93 | }
94 | } finally {
95 | AutoFlushUtil.setAutoFlush(player, true);
96 | }
97 | }
98 |
99 | /**
100 | * @author Andrew Steinborn
101 | * @reason Add support for flush consolidation & optimize sending chunks
102 | */
103 | @Overwrite
104 | public void move(ServerPlayer player) {
105 | // TODO: optimize this further by only considering entities that the player is close to.
106 | // use the FastChunkEntityAccess magic to do this.
107 | for (ChunkMap.TrackedEntity entityTracker : this.entityMap.values()) {
108 | if (entityTracker.entity == player) {
109 | entityTracker.updatePlayers(this.level.players());
110 | } else {
111 | entityTracker.updatePlayer(player);
112 | }
113 | }
114 |
115 | SectionPos oldPos = player.getLastSectionPos();
116 | SectionPos newPos = SectionPos.of(player);
117 | boolean isWatchingWorld = this.playerMap.ignored(player);
118 | boolean noChunkGen = this.skipPlayer(player);
119 | boolean movedSections = !oldPos.equals(newPos);
120 |
121 | if (movedSections || isWatchingWorld != noChunkGen) {
122 | this.updatePlayerPos(player);
123 |
124 | if (!isWatchingWorld) {
125 | this.distanceManager.removePlayer(oldPos, player);
126 | }
127 |
128 | if (!noChunkGen) {
129 | this.distanceManager.addPlayer(newPos, player);
130 | }
131 |
132 | if (!isWatchingWorld && noChunkGen) {
133 | this.playerMap.ignorePlayer(player);
134 | }
135 |
136 | if (isWatchingWorld && !noChunkGen) {
137 | this.playerMap.unIgnorePlayer(player);
138 | }
139 |
140 | long oldChunkPos = ChunkPos.asLong(oldPos.getX(), oldPos.getZ());
141 | long newChunkPos = ChunkPos.asLong(newPos.getX(), newPos.getZ());
142 | this.playerMap.updatePlayer(oldChunkPos, newChunkPos, player);
143 | }
144 |
145 | // The player *always* needs to be send chunks, as for some reason both chunk loading & unloading packets are handled
146 | // by the same method (why mojang)
147 | if (player.level == this.level)
148 | this.sendChunkWatchPackets(oldPos, player);
149 | }
150 |
151 | @Inject(method = "tick()V", at = @At("HEAD"))
152 | public void disableAutoFlushForEntityTracking(CallbackInfo info) {
153 | for (ServerPlayer player : level.players()) {
154 | AutoFlushUtil.setAutoFlush(player, false);
155 | }
156 | }
157 |
158 | @Inject(method = "tick()V", at = @At("RETURN"))
159 | public void enableAutoFlushForEntityTracking(CallbackInfo info) {
160 | for (ServerPlayer player : level.players()) {
161 | AutoFlushUtil.setAutoFlush(player, true);
162 | }
163 | }
164 |
165 | /**
166 | * @param player The player
167 | * @param pos The position of the chunk to send
168 | * @param mutableObject A new mutable object
169 | * @param oldWithinViewDistance If the chunk was previously within the player's view distance
170 | * @param newWithinViewDistance If the chunk is now within the player's view distance
171 | */
172 | @Shadow
173 | protected abstract void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject mutableObject,
174 | boolean oldWithinViewDistance, boolean newWithinViewDistance);
175 |
176 | @Shadow
177 | protected abstract boolean skipPlayer(ServerPlayer player);
178 |
179 | @Shadow
180 | protected abstract SectionPos updatePlayerPos(ServerPlayer serverPlayerEntity);
181 |
182 | private void sendChunkWatchPackets(SectionPos oldPos, ServerPlayer player) {
183 | AutoFlushUtil.setAutoFlush(player, false);
184 | try {
185 | int oldChunkX = oldPos.x();
186 | int oldChunkZ = oldPos.z();
187 |
188 | int newChunkX = SectionPos.blockToSectionCoord(player.getBlockX());
189 | int newChunkZ = SectionPos.blockToSectionCoord(player.getBlockZ());
190 |
191 | int playerViewDistance = getPlayerViewDistance(player); // +1 for buffer
192 |
193 | if (shouldReloadAllChunks(player)) { // Player updated view distance, unload chunks & resend (only unload chunks not visible)
194 | //noinspection InstanceofIncompatibleInterface
195 | if (player instanceof PlutoServerPlayer plutoPlayer)
196 | plutoPlayer.setNeedsChunksReloaded(false);
197 |
198 | for (int curX = newChunkX - viewDistance - 1; curX <= newChunkX + viewDistance + 1; ++curX) {
199 | for (int curZ = newChunkZ - viewDistance - 1; curZ <= newChunkZ + viewDistance + 1; ++curZ) {
200 | ChunkPos chunkPos = new ChunkPos(curX, curZ);
201 | boolean inNew = isChunkInRange(curX, curZ, newChunkX, newChunkZ, playerViewDistance);
202 |
203 | this.updateChunkTracking(player, chunkPos, new MutableObject<>(), true, inNew);
204 | }
205 | }
206 |
207 | // Send new chunks
208 | sendSpiralChunkWatchPackets(player);
209 | } else if (Math.abs(oldChunkX - newChunkX) > playerViewDistance * 2 ||
210 | Math.abs(oldChunkZ - newChunkZ) > playerViewDistance * 2) {
211 | // If the player is not near the old chunks, send all new chunks & unload old chunks
212 |
213 | // Unload previous chunks
214 | // Chunk unload packets are very light, so we can just do it like this
215 | unloadChunks(player, oldChunkX, oldChunkZ, viewDistance);
216 |
217 | // Send new chunks
218 | sendSpiralChunkWatchPackets(player);
219 | } else {
220 | int minSendChunkX = Math.min(newChunkX, oldChunkX) - playerViewDistance - 1;
221 | int minSendChunkZ = Math.min(newChunkZ, oldChunkZ) - playerViewDistance - 1;
222 | int maxSendChunkX = Math.max(newChunkX, oldChunkX) + playerViewDistance + 1;
223 | int maxSendChunkZ = Math.max(newChunkZ, oldChunkZ) + playerViewDistance + 1;
224 |
225 | // We're sending *all* chunks in the range of where the player was, to where the player currently is.
226 | // This is because the #updateChunkTracking method will also unload chunks.
227 | // For chunks outside of the view distance, it does nothing.
228 | for (int curX = minSendChunkX; curX <= maxSendChunkX; ++curX) {
229 | for (int curZ = minSendChunkZ; curZ <= maxSendChunkZ; ++curZ) {
230 | ChunkPos chunkPos = new ChunkPos(curX, curZ);
231 | boolean inOld = isChunkInRange(curX, curZ, oldChunkX, oldChunkZ, playerViewDistance);
232 | boolean inNew = isChunkInRange(curX, curZ, newChunkX, newChunkZ, playerViewDistance);
233 | this.updateChunkTracking(player, chunkPos, new MutableObject<>(), inOld, inNew);
234 | }
235 | }
236 | }
237 | } finally {
238 | AutoFlushUtil.setAutoFlush(player, true);
239 | }
240 | }
241 |
242 | /**
243 | * Sends watch packets to the client in a spiral for a player, which has *no* chunks loaded in the area.
244 | */
245 | private void sendSpiralChunkWatchPackets(ServerPlayer player) {
246 | int chunkPosX = SectionPos.blockToSectionCoord(player.getBlockX());
247 | int chunkPosZ = SectionPos.blockToSectionCoord(player.getBlockZ());
248 |
249 |
250 | // + 1 because mc adds 1 when it sends chunks
251 | int playerViewDistance = getPlayerViewDistance(player) + 1;
252 |
253 | int x = 0, z = 0, dx = 0, dz = -1;
254 | int t = playerViewDistance * 2;
255 | int maxI = t * t * 2;
256 | for (int i = 0; i < maxI; i++) {
257 | if ((-playerViewDistance <= x) && (x <= playerViewDistance) && (-playerViewDistance <= z) && (z <= playerViewDistance)) {
258 | boolean inNew = isChunkInRange(chunkPosX, chunkPosZ, chunkPosX + x, chunkPosZ + z, playerViewDistance);
259 |
260 | this.updateChunkTracking(player,
261 | new ChunkPos(chunkPosX + x, chunkPosZ + z),
262 | new MutableObject<>(), false, inNew
263 | );
264 | }
265 | if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) {
266 | t = dx;
267 | dx = -dz;
268 | dz = t;
269 | }
270 | x += dx;
271 | z += dz;
272 | }
273 | }
274 |
275 | private void unloadChunks(ServerPlayer player, int chunkPosX, int chunkPosZ, int distance) {
276 | for (int curX = chunkPosX - distance - 1; curX <= chunkPosX + distance + 1; ++curX) {
277 | for (int curZ = chunkPosZ - distance - 1; curZ <= chunkPosZ + distance + 1; ++curZ) {
278 | ChunkPos chunkPos = new ChunkPos(curX, curZ);
279 |
280 | this.updateChunkTracking(player, chunkPos, new MutableObject<>(), true, false);
281 | }
282 | }
283 | }
284 |
285 | private int getPlayerViewDistance(ServerPlayer playerEntity) {
286 | //noinspection InstanceofIncompatibleInterface
287 | return playerEntity instanceof PlutoServerPlayer plutoPlayerEntity
288 | ? plutoPlayerEntity.getPlayerViewDistance() != -1
289 | // if -1, the view distance hasn't been set
290 | // We *actually* need to send view distance + 1, because mc doesn't render chunks adjacent to unloaded ones
291 | ? Math.min(this.viewDistance,
292 | plutoPlayerEntity.getPlayerViewDistance() +
293 | 1)
294 | : this.viewDistance : this.viewDistance;
295 | }
296 |
297 | private boolean shouldReloadAllChunks(ServerPlayer playerEntity) {
298 | //noinspection InstanceofIncompatibleInterface
299 | return playerEntity instanceof PlutoServerPlayer plutoPlayerEntity && plutoPlayerEntity.getNeedsChunksReloaded();
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/main/java/com/abdelaziz/pluto/mixin/network/flushconsolidation/ConnectionMixin.java:
--------------------------------------------------------------------------------
1 | package com.abdelaziz.pluto.mixin.network.flushconsolidation;
2 |
3 | import com.abdelaziz.pluto.common.network.ConfigurableAutoFlush;
4 | import io.netty.channel.Channel;
5 | import net.minecraft.network.Connection;
6 | import org.objectweb.asm.Opcodes;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.Shadow;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.Inject;
11 | import org.spongepowered.asm.mixin.injection.Redirect;
12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
13 |
14 | import java.util.concurrent.atomic.AtomicBoolean;
15 |
16 | /**
17 | * Optimizes ClientConnection by adding the ability to skip auto-flushing and using void promises where possible.
18 | */
19 | @Mixin(Connection.class)
20 | public abstract class ConnectionMixin implements ConfigurableAutoFlush {
21 | @Shadow
22 | private Channel channel;
23 |
24 | private AtomicBoolean autoFlush;
25 |
26 | @Inject(method = "", at = @At("RETURN"))
27 | private void initAddedFields(CallbackInfo ci) {
28 | this.autoFlush = new AtomicBoolean(true);
29 | }
30 |
31 | @Redirect(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/network/Connection;channel:Lio/netty/channel/Channel;", opcode = Opcodes.GETFIELD))
32 | public Channel disableForcedFlushEveryTick(Connection clientConnection) {
33 | return null;
34 | }
35 |
36 | @Override
37 | public void setShouldAutoFlush(boolean shouldAutoFlush) {
38 | boolean prev = this.autoFlush.getAndSet(shouldAutoFlush);
39 | if (!prev && shouldAutoFlush) {
40 | this.channel.flush();
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/java/com/abdelaziz/pluto/mixin/network/flushconsolidation/ServerEntityMixin.java:
--------------------------------------------------------------------------------
1 | package com.abdelaziz.pluto.mixin.network.flushconsolidation;
2 |
3 | import net.minecraft.server.level.ServerEntity;
4 | import net.minecraft.server.level.ServerPlayer;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 |
10 | import static com.abdelaziz.pluto.common.network.util.AutoFlushUtil.setAutoFlush;
11 |
12 | @Mixin(ServerEntity.class)
13 | public class ServerEntityMixin {
14 |
15 | @Inject(at = @At("HEAD"), method = "addPairing")
16 | public void addPairing$disableAutoFlush(ServerPlayer player, CallbackInfo ci) {
17 | setAutoFlush(player, false);
18 | }
19 |
20 | @Inject(at = @At("RETURN"), method = "addPairing")
21 | public void addPairing$reenableAutoFlush(ServerPlayer player, CallbackInfo ci) {
22 | setAutoFlush(player, true);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/abdelaziz/pluto/mixin/network/microopt/FriendlyByteBufMixin.java:
--------------------------------------------------------------------------------
1 | package com.abdelaziz.pluto.mixin.network.microopt;
2 |
3 | import com.abdelaziz.pluto.common.network.util.VarIntUtil;
4 | import io.netty.buffer.ByteBuf;
5 | import io.netty.buffer.ByteBufUtil;
6 | import io.netty.handler.codec.EncoderException;
7 | import net.minecraft.network.FriendlyByteBuf;
8 | import org.spongepowered.asm.mixin.Final;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.Overwrite;
11 | import org.spongepowered.asm.mixin.Shadow;
12 |
13 | import java.nio.charset.Charset;
14 | import java.nio.charset.StandardCharsets;
15 |
16 | /**
17 | * Multiple micro-optimizations for packet writing.
18 | */
19 | @Mixin(FriendlyByteBuf.class)
20 | public abstract class FriendlyByteBufMixin extends ByteBuf {
21 |
22 | @Shadow
23 | @Final
24 | private ByteBuf source;
25 |
26 | @Shadow
27 | public abstract int writeCharSequence(CharSequence charSequence, Charset charset);
28 |
29 | /**
30 | * @author Andrew
31 | * @reason Use optimized VarInt byte size lookup table
32 | */
33 | @Overwrite
34 | public static int getVarIntSize(int value) {
35 | return VarIntUtil.getVarIntLength(value);
36 | }
37 |
38 | /**
39 | * @author Andrew
40 | * @reason Use {@link ByteBuf#writeCharSequence(CharSequence, Charset)} instead for improved performance along with
41 | * computing the byte size ahead of time with {@link ByteBufUtil#utf8Bytes(CharSequence)}
42 | */
43 | @Overwrite
44 | public FriendlyByteBuf writeUtf(String string, int i) {
45 | int utf8Bytes = ByteBufUtil.utf8Bytes(string);
46 | if (utf8Bytes > i) {
47 | throw new EncoderException("String too big (was " + utf8Bytes + " bytes encoded, max " + i + ")");
48 | } else {
49 | this.writeVarInt(utf8Bytes);
50 | this.writeCharSequence(string, StandardCharsets.UTF_8);
51 | return new FriendlyByteBuf(source);
52 | }
53 | }
54 |
55 | /**
56 | * @author Andrew
57 | * @reason optimized VarInt writing
58 | */
59 | @Overwrite
60 | public FriendlyByteBuf writeVarInt(int value) {
61 | // Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
62 | // that the proxy will write, to improve inlining.
63 | if ((value & (0xFFFFFFFF << 7)) == 0) {
64 | source.writeByte(value);
65 | } else if ((value & (0xFFFFFFFF << 14)) == 0) {
66 | int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
67 | source.writeShort(w);
68 | } else {
69 | writeVarIntFull(source, value);
70 | }
71 | return new FriendlyByteBuf(source);
72 | }
73 |
74 | private static void writeVarIntFull(ByteBuf buf, int value) {
75 | // See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
76 | if ((value & (0xFFFFFFFF << 7)) == 0) {
77 | buf.writeByte(value);
78 | } else if ((value & (0xFFFFFFFF << 14)) == 0) {
79 | int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
80 | buf.writeShort(w);
81 | } else if ((value & (0xFFFFFFFF << 21)) == 0) {
82 | int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
83 | buf.writeMedium(w);
84 | } else if ((value & (0xFFFFFFFF << 28)) == 0) {
85 | int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
86 | | ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21);
87 | buf.writeInt(w);
88 | } else {
89 | int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
90 | | ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
91 | buf.writeInt(w);
92 | buf.writeByte(value >>> 28);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/com/abdelaziz/pluto/mixin/network/microopt/ServerEntityMixin.java:
--------------------------------------------------------------------------------
1 | package com.abdelaziz.pluto.mixin.network.microopt;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import net.minecraft.server.level.ServerEntity;
5 | import net.minecraft.world.entity.Entity;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.injection.At;
8 | import org.spongepowered.asm.mixin.injection.Redirect;
9 |
10 | import java.util.List;
11 |
12 | @Mixin(ServerEntity.class)
13 | public class ServerEntityMixin {
14 |
15 | @Redirect(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Collections;emptyList()Ljava/util/List;"))
16 | public List construct$initialPassengersListIsGuavaImmutableList() {
17 | // This is a tiny micro-optimization, but in most cases, the passengers list for an entity is typically empty.
18 | // Furthermore, it is using Guava's ImmutableList type, but the constructor uses the JDK (pre-Java 9) empty
19 | // collections. By using Guava's collection type here, this check can often be simplified to a simple reference
20 | // equality check, which is very cheap.
21 | return ImmutableList.of();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/abdelaziz/pluto/mixin/network/pipeline/LegacyQueryHandlerMixin.java:
--------------------------------------------------------------------------------
1 | package com.abdelaziz.pluto.mixin.network.pipeline;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import net.minecraft.server.network.LegacyQueryHandler;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.injection.At;
8 | import org.spongepowered.asm.mixin.injection.Inject;
9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10 |
11 | /**
12 | * Mixes into {@link LegacyQueryHandler} to fix a security issue.
13 | */
14 | @Mixin(LegacyQueryHandler.class)
15 | public abstract class LegacyQueryHandlerMixin {
16 | @Inject(method = "channelRead", at = @At(value = "HEAD"), cancellable = true)
17 | public void channelRead(ChannelHandlerContext ctx, Object msg, CallbackInfo ci) throws Exception {
18 | if (!ctx.channel().isActive()) {
19 | ((ByteBuf) msg).clear();
20 | ci.cancel();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/abdelaziz/pluto/mixin/network/pipeline/Varint21FrameDecoderMixin.java:
--------------------------------------------------------------------------------
1 | package com.abdelaziz.pluto.mixin.network.pipeline;
2 |
3 | import com.abdelaziz.pluto.common.network.VarintByteDecoder;
4 | import io.netty.buffer.ByteBuf;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import net.minecraft.network.Varint21FrameDecoder;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.Overwrite;
9 |
10 | import java.util.List;
11 |
12 | import static com.abdelaziz.pluto.common.network.util.exception.WellKnownExceptions.BAD_LENGTH_CACHED;
13 | import static com.abdelaziz.pluto.common.network.util.exception.WellKnownExceptions.VARINT_BIG_CACHED;
14 |
15 | /**
16 | * Overrides the SplitterHandler to use optimized packet splitting from Velocity 1.1.0. In addition this applies a
17 | * security fix to stop "nullping" attacks.
18 | */
19 | @Mixin(Varint21FrameDecoder.class)
20 | public class Varint21FrameDecoderMixin {
21 | private final VarintByteDecoder reader = new VarintByteDecoder();
22 |
23 | /**
24 | * @author Andrew Steinborn
25 | * @reason Use optimized Velocity varint decoder that reduces bounds checking
26 | */
27 | @Overwrite
28 | public void decode(ChannelHandlerContext ctx, ByteBuf in, List