Fix race conditions in movement packet filtering (fixes #760)
We cannot filter this packet purely on the netty thread because our filter depends on the current camera position (if it's too far, we do want to teleport it closer) and accessing the camera is only safe from the main thread. Previously the second part had not been considered which could lead to race conditions where the camera may not yet exist on the netty thread at time of handling. These appear to have gotten the wholly inappropriate "just put a null check around it" treatment, but that means that the filter sometimes doesn't work (hence the bug report). This commit changes handling such that all access to `allowMovement` is always done on the main thread and then moves the filtering there as well. And therefore filtering should now work properly.
This commit is contained in:
@@ -267,6 +267,8 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to allow (process) the next player movement packet.
|
* Whether to allow (process) the next player movement packet.
|
||||||
|
*
|
||||||
|
* Must only be accessed from the main thread.
|
||||||
*/
|
*/
|
||||||
protected boolean allowMovement;
|
protected boolean allowMovement;
|
||||||
|
|
||||||
@@ -639,7 +641,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
|||||||
if(p instanceof GameJoinS2CPacket) {
|
if(p instanceof GameJoinS2CPacket) {
|
||||||
GameJoinS2CPacket packet = (GameJoinS2CPacket) p;
|
GameJoinS2CPacket packet = (GameJoinS2CPacket) p;
|
||||||
int entId = packet.getEntityId();
|
int entId = packet.getEntityId();
|
||||||
allowMovement = true;
|
schedulePacketHandler(() -> allowMovement = true);
|
||||||
actualID = entId;
|
actualID = entId;
|
||||||
entId = -1789435; // Camera entity id should be negative which is an invalid id and can't be used by servers
|
entId = -1789435; // Camera entity id should be negative which is an invalid id and can't be used by servers
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
@@ -773,7 +775,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
|||||||
//#endif
|
//#endif
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
allowMovement = true;
|
schedulePacketHandler(() -> allowMovement = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(p instanceof PlayerPositionLookS2CPacket) {
|
if(p instanceof PlayerPositionLookS2CPacket) {
|
||||||
@@ -789,8 +791,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
|||||||
|
|
||||||
if(replayHandler.shouldSuppressCameraMovements()) return null;
|
if(replayHandler.shouldSuppressCameraMovements()) return null;
|
||||||
|
|
||||||
CameraEntity cent = replayHandler.getCameraEntity();
|
|
||||||
|
|
||||||
//#if MC>=10800
|
//#if MC>=10800
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
for (PlayerPositionLookS2CPacket.Flag relative : ppl.getFlags()) {
|
for (PlayerPositionLookS2CPacket.Flag relative : ppl.getFlags()) {
|
||||||
@@ -812,29 +812,28 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
|||||||
}
|
}
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
if(cent != null) {
|
schedulePacketHandler(new Runnable() {
|
||||||
if(!allowMovement && !((Math.abs(cent.getX() - ppl.getX()) > TP_DISTANCE_LIMIT) ||
|
|
||||||
(Math.abs(cent.getZ() - ppl.getZ()) > TP_DISTANCE_LIMIT))) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
allowMovement = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// FIXME: world shouldn't ever be null at this point, now that we use the packet queue
|
||||||
|
// probably fine to remove on the next non-patch version (don't want to break stuff now)
|
||||||
if (mc.world == null || !mc.isOnThread()) {
|
if (mc.world == null || !mc.isOnThread()) {
|
||||||
ReplayMod.instance.runLater(this);
|
ReplayMod.instance.runLater(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraEntity cent = replayHandler.getCameraEntity();
|
CameraEntity cent = replayHandler.getCameraEntity();
|
||||||
|
if (!allowMovement && !((Math.abs(cent.getX() - ppl.getX()) > TP_DISTANCE_LIMIT) ||
|
||||||
|
(Math.abs(cent.getZ() - ppl.getZ()) > TP_DISTANCE_LIMIT))) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
allowMovement = false;
|
||||||
|
}
|
||||||
cent.setCameraPosition(ppl.getX(), ppl.getY(), ppl.getZ());
|
cent.setCameraPosition(ppl.getX(), ppl.getY(), ppl.getZ());
|
||||||
cent.setCameraRotation(ppl.getYaw(), ppl.getPitch(), cent.roll);
|
cent.setCameraRotation(ppl.getYaw(), ppl.getPitch(), cent.roll);
|
||||||
}
|
}
|
||||||
}.run();
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1273,6 +1272,22 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
|||||||
ReplayMod.instance.runTasks();
|
ReplayMod.instance.runTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the given runnable on the main thread as if it was a packet handler.
|
||||||
|
* Note that the packet handler queue has different behavior than the standard ReplayMod queue.
|
||||||
|
*/
|
||||||
|
private void schedulePacketHandler(Runnable runnable) {
|
||||||
|
if (mc.isOnThread()) {
|
||||||
|
runnable.run();
|
||||||
|
} else {
|
||||||
|
//#if MC>=11400
|
||||||
|
mc.execute(runnable);
|
||||||
|
//#else
|
||||||
|
//$$ mc.addScheduledTask(runnable);
|
||||||
|
//#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void processPacketSync(Packet p) {
|
protected void processPacketSync(Packet p) {
|
||||||
//#if MC>=10904
|
//#if MC>=10904
|
||||||
if (p instanceof UnloadChunkS2CPacket) {
|
if (p instanceof UnloadChunkS2CPacket) {
|
||||||
|
|||||||
Reference in New Issue
Block a user