1
0
Fork 0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-04-17 19:12:14 +02:00

Add full support for Java ping pong packets

This commit is contained in:
SamB440 2025-04-04 16:18:23 +01:00
parent ae130cf5a5
commit c09314160d
No known key found for this signature in database
GPG key ID: 65A5733A97C05078
7 changed files with 63 additions and 52 deletions

View file

@ -648,10 +648,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private ScheduledFuture<?> mountVehicleScheduledFuture = null;
/**
* A cache of IDs from ClientboundKeepAlivePackets that have been sent to the Bedrock client, but haven't been returned to the server.
* A cache of IDs from ClientboundKeepAlivePackets or ClientboundPingPacket that have been sent to the Bedrock client, but haven't been returned to the server.
* Only used if {@link GeyserConfiguration#isForwardPlayerPing()} is enabled.
*/
private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>();
private final Queue<Runnable> latencyPingCache = new ConcurrentLinkedQueue<>();
/**
* Stores the book that is currently being read. Used in {@link org.geysermc.geyser.translator.protocol.java.inventory.JavaOpenBookTranslator}

View file

@ -32,19 +32,23 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.RequiredArgsConstructor;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.packet.ModalFormRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket;
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.cumulus.component.util.ComponentType;
import org.geysermc.cumulus.form.CustomForm;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.SimpleForm;
import org.geysermc.cumulus.form.impl.FormDefinitions;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
@ -91,7 +95,23 @@ public class FormCache {
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(MAGIC_FORM_IMAGE_HACK_TIMESTAMP);
session.scheduleInEventLoop(
() -> session.sendUpstreamPacket(latencyPacket),
() -> {
session.getLatencyPingCache().add(() -> session.scheduleInEventLoop(() -> {
// Hack to fix the url image loading bug
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL);
if (attribute != null) {
attributesPacket.setAttributes(Collections.singletonList(attribute));
} else {
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
}
session.sendUpstreamPacket(attributesPacket);
}, 500, TimeUnit.MILLISECONDS));
session.sendUpstreamPacket(latencyPacket);
},
500, TimeUnit.MILLISECONDS
);
}

View file

@ -70,6 +70,11 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(MAGIC_VIRTUAL_INVENTORY_HACK);
session.getLatencyPingCache().add(() -> {
if (session.getPendingOrCurrentBedrockInventoryId() != -1) {
InventoryUtils.openPendingInventory(session);
}
});
session.sendUpstreamPacket(latencyPacket);
GeyserImpl.getInstance().getLogger().debug(session, "Unable to open a virtual inventory, sent another latency packet!");
}, 100, TimeUnit.MILLISECONDS);

View file

@ -25,18 +25,10 @@
package org.geysermc.geyser.translator.protocol.bedrock;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundKeepAlivePacket;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* Used to send the forwarded keep alive packet back to the server
@ -46,47 +38,18 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
@Override
public void translate(GeyserSession session, NetworkStackLatencyPacket packet) {
// negative timestamps are used as hack to fix the url image loading bug
if (packet.getTimestamp() >= 0) {
if (session.getGeyser().getConfig().isForwardPlayerPing()) {
// use our cached value because
// a) bedrock can be inaccurate with the value returned
// b) playstation replies with a different magnitude than other platforms
// c) 1.20.10 and later reply with a different magnitude
Long keepAliveId = session.getKeepAliveCache().poll();
if (keepAliveId == null) {
session.getGeyser().getLogger().debug("Received a latency packet that we don't have a KeepAlive for: " + packet);
return;
}
ServerboundKeepAlivePacket keepAlivePacket = new ServerboundKeepAlivePacket(keepAliveId);
session.sendDownstreamPacket(keepAlivePacket);
}
// We should receive these packets in the same order they were sent
final Runnable latencyPing = session.getLatencyPingCache().poll();
if (latencyPing == null) {
session.getGeyser().getLogger().debug("Received a latency packet that we don't have a ping for: " + packet);
return;
}
if (session.getPendingOrCurrentBedrockInventoryId() != -1) {
InventoryUtils.openPendingInventory(session);
} else {
session.scheduleInEventLoop(() -> {
// Hack to fix the url image loading bug
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL);
if (attribute != null) {
attributesPacket.setAttributes(Collections.singletonList(attribute));
} else {
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
}
session.sendUpstreamPacket(attributesPacket);
}, 500, TimeUnit.MILLISECONDS);
}
latencyPing.run();
}
@Override
public boolean shouldExecuteInEventLoop() {
return false;
return true;
}
}

View file

@ -30,6 +30,7 @@ import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundKeepAlivePacket;
/**
* Used to forward the keep alive packet to the client in order to get back a reliable ping.
@ -42,10 +43,11 @@ public class JavaKeepAliveTranslator extends PacketTranslator<ClientboundKeepAli
if (!session.getGeyser().getConfig().isForwardPlayerPing()) {
return;
}
// We use this once the client replies (see BedrockNetworkStackLatencyTranslator)
session.getKeepAliveCache().add(packet.getPingId());
long timestamp = packet.getPingId();
// We use this once the client replies
final long javaId = packet.getPingId();
long timestamp = javaId;
// We take the abs because we rely on the client responding with a negative value ONLY when we send
// a negative timestamp in the form-image-hack performed in FormCache.
@ -62,9 +64,17 @@ public class JavaKeepAliveTranslator extends PacketTranslator<ClientboundKeepAli
timestamp /= 10;
}
session.getLatencyPingCache().add(() -> {
// use our cached value because
// a) bedrock can be inaccurate with the value returned
// b) playstation replies with a different magnitude than other platforms
// c) 1.20.10 and later reply with a different magnitude
session.sendDownstreamPacket(new ServerboundKeepAlivePacket(javaId));
});
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(timestamp);
session.sendUpstreamPacketImmediately(latencyPacket);
session.sendUpstreamPacket(latencyPacket);
}
}

View file

@ -25,18 +25,26 @@
package org.geysermc.geyser.translator.protocol.java;
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundPingPacket;
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundPongPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
// Why does this packet exist? Whatever, we better implement it
// This packet is the same as keep alive, except it runs on the client's main thread.
@Translator(packet = ClientboundPingPacket.class)
public class JavaPingTranslator extends PacketTranslator<ClientboundPingPacket> {
@Override
public void translate(GeyserSession session, ClientboundPingPacket packet) {
session.sendDownstreamPacket(new ServerboundPongPacket(packet.getId()));
// We use this once the client replies
final int id = packet.getId();
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(id);
session.getLatencyPingCache().add(() -> session.sendDownstreamPacket(new ServerboundPongPacket(id)));
session.sendUpstreamPacket(latencyPacket);
}
}

View file

@ -149,6 +149,11 @@ public class InventoryUtils {
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(MAGIC_VIRTUAL_INVENTORY_HACK);
session.getLatencyPingCache().add(() -> {
if (session.getPendingOrCurrentBedrockInventoryId() != -1) {
InventoryUtils.openPendingInventory(session);
}
});
session.sendUpstreamPacket(latencyPacket);
GeyserImpl.getInstance().getLogger().debug(session, "Queuing virtual inventory (%s)", debugInventory(inventory));