From fd8c851e7540309243c55a75a6f959f7943e759a Mon Sep 17 00:00:00 2001 From: layue13 Date: Fri, 25 Sep 2026 18:39:48 +0800 Subject: [PATCH] test: cover vanilla to Forge transfer handshake sequence --- .../core/session/SessionTransferTest.java | 134 ++++++++++++++++-- 1 file changed, 122 insertions(+), 12 deletions(-) diff --git a/proxy-core/src/test/java/dev/strataproxy/core/session/SessionTransferTest.java b/proxy-core/src/test/java/dev/strataproxy/core/session/SessionTransferTest.java index 2c0e55f..60fb956 100644 --- a/proxy-core/src/test/java/dev/strataproxy/core/session/SessionTransferTest.java +++ b/proxy-core/src/test/java/dev/strataproxy/core/session/SessionTransferTest.java @@ -855,6 +855,91 @@ final class SessionTransferTest { } } + @Test + void vanillaToForgeTransferRelaysTheFmlHandshakeInOrder() throws Exception { + var catalog = new InMemoryBackendCatalog(); + try (ServerSocket oldServer = server(); ServerSocket newServer = server()) { + var oldClosed = new CompletableFuture(); + var newNegotiated = new CompletableFuture(); + backendThread(oldServer, () -> { + try (Socket socket = oldServer.accept()) { + socket.setSoTimeout(5000); + var input = new DataInputStream(socket.getInputStream()); + acceptLogin(input, new DataOutputStream(socket.getOutputStream()), 0); + assertEquals(-1, input.read()); + oldClosed.complete(null); + } catch (Throwable failure) { oldClosed.completeExceptionally(failure); } + }); + backendThread(newServer, () -> { + try (Socket socket = newServer.accept()) { + socket.setSoTimeout(5000); + var input = new DataInputStream(socket.getInputStream()); + var output = new DataOutputStream(socket.getOutputStream()); + acceptHandshakeAndLogin(input, output); + sendLoginSuccess(output); + writeFrame(output, serverForgeRegistration()); + writeFrame(output, serverForgeHello(7)); + assertArrayEquals(clientForgeRegistration(), readFrame(input)); + assertArrayEquals(clientForgeMessage(new byte[]{1, 2}), readFrame(input)); + assertArrayEquals(clientForgeMessage(new byte[]{2, 0}), readFrame(input)); + writeFrame(output, serverForgeMessage(new byte[]{2, 0})); + assertArrayEquals(clientForgeAck(2), readFrame(input)); + writeFrame(output, serverForgeMessage(new byte[]{3, 0, 0, 0})); + writeFrame(output, serverForgeAck(2)); + assertArrayEquals(clientForgeAck(3), readFrame(input)); + assertArrayEquals(clientForgeAck(4), readFrame(input)); + writeFrame(output, serverForgeAck(3)); + assertArrayEquals(clientForgeAck(5), readFrame(input)); + sendJoinGame(output, 0, 200); + writeFrame(output, new byte[]{0x08}); + newNegotiated.complete(null); + while (input.read() != -1) { } + } catch (Throwable failure) { newNegotiated.completeExceptionally(failure); } + }); + register(catalog, "old", oldServer); + register(catalog, "new", newServer); + var listener = listener(catalog); + try { + var bound = (InetSocketAddress) listener.start().toCompletableFuture() + .get(5, TimeUnit.SECONDS).localAddress(); + try (Socket client = new Socket(InetAddress.getLoopbackAddress(), bound.getPort())) { + client.setSoTimeout(5000); + var input = new DataInputStream(client.getInputStream()); + var output = new DataOutputStream(client.getOutputStream()); + sendLogin(output); + assertEquals(2, packetId(readFrame(input))); + assertEquals(1, packetId(readFrame(input))); + assertEquals(8, packetId(readFrame(input))); + var player = awaitPlayer(listener); + var transfer = listener.transfer(player.identity(), "new").toCompletableFuture(); + byte[] reset = readFrame(input); + assertEquals(0x3f, packetId(reset)); + assertEquals((byte) 0xfe, reset[reset.length - 1]); + assertArrayEquals(serverForgeRegistration(), readFrame(input)); + assertArrayEquals(serverForgeHello(7), readFrame(input)); + writeFrame(output, clientForgeRegistration()); + writeFrame(output, clientForgeMessage(new byte[]{1, 2})); + writeFrame(output, clientForgeMessage(new byte[]{2, 0})); + assertArrayEquals(serverForgeMessage(new byte[]{2, 0}), readFrame(input)); + writeFrame(output, clientForgeAck(2)); + assertArrayEquals(serverForgeMessage(new byte[]{3, 0, 0, 0}), readFrame(input)); + assertArrayEquals(serverForgeAck(2), readFrame(input)); + writeFrame(output, clientForgeAck(3)); + writeFrame(output, clientForgeAck(4)); + assertArrayEquals(serverForgeAck(3), readFrame(input)); + writeFrame(output, clientForgeAck(5)); + assertEquals(-1, respawnDimension(readFrame(input))); + assertEquals(7, respawnDimension(readFrame(input))); + assertEquals(8, packetId(readFrame(input))); + var result = transfer.get(5, TimeUnit.SECONDS); + assertEquals(TransferStatus.NETWORK_READY, result.status(), result.detail().orElse("")); + newNegotiated.get(5, TimeUnit.SECONDS); + oldClosed.get(5, TimeUnit.SECONDS); + } + } finally { listener.close().toCompletableFuture().get(5, TimeUnit.SECONDS); } + } + } + @Test void forgeTransferResetsHandshakeAndUsesServerHelloDimensionOverride() throws Exception { var catalog = new InMemoryBackendCatalog(); @@ -1350,17 +1435,49 @@ final class SessionTransferTest { return payload.toByteArray(); } - private static byte[] serverForgeAck() throws Exception { + private static byte[] clientForgeRegistration() throws Exception { + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + var output = new DataOutputStream(payload); + byte[] channels = "FML|HS\0FML".getBytes(StandardCharsets.UTF_8); + writeVarInt(output, 0x17); + writeString(output, "REGISTER"); + output.writeShort(channels.length); + output.write(channels); + return payload.toByteArray(); + } + + private static byte[] serverForgeMessage(byte[] handshake) throws Exception { ByteArrayOutputStream payload = new ByteArrayOutputStream(); var output = new DataOutputStream(payload); writeVarInt(output, 0x3f); writeString(output, "FML|HS"); - output.writeShort(2); - output.writeByte(0xff); - output.writeByte(3); + output.writeShort(handshake.length); + output.write(handshake); return payload.toByteArray(); } + private static byte[] clientForgeMessage(byte[] handshake) throws Exception { + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + var output = new DataOutputStream(payload); + writeVarInt(output, 0x17); + writeString(output, "FML|HS"); + output.writeShort(handshake.length); + output.write(handshake); + return payload.toByteArray(); + } + + private static byte[] serverForgeAck(int phase) throws Exception { + return serverForgeMessage(new byte[]{(byte) 0xff, (byte) phase}); + } + + private static byte[] clientForgeAck(int phase) throws Exception { + return clientForgeMessage(new byte[]{(byte) 0xff, (byte) phase}); + } + + private static byte[] serverForgeAck() throws Exception { + return serverForgeAck(3); + } + private static byte[] serverForgeRegistryData() throws Exception { ByteArrayOutputStream payload = new ByteArrayOutputStream(); var output = new DataOutputStream(payload); @@ -1374,14 +1491,7 @@ final class SessionTransferTest { } private static byte[] clientForgeAck() throws Exception { - ByteArrayOutputStream payload = new ByteArrayOutputStream(); - var output = new DataOutputStream(payload); - writeVarInt(output, 0x17); - writeString(output, "FML|HS"); - output.writeShort(2); - output.writeByte(0xff); - output.writeByte(5); - return payload.toByteArray(); + return clientForgeAck(5); } private static void sendLogin(DataOutputStream output) throws Exception {