From 8d4b7df1ce13e616d5dc59cd363f193018752821 Mon Sep 17 00:00:00 2001 From: Janis Jansons Date: Mon, 7 Sep 2026 00:24:43 +0300 Subject: [PATCH] protocol: hello carries five fields; the released client reads no serverName Upstream's git HEAD appends serverName to server_installed, but the shipped 1.0.4 client (fabric-26.2) decodes exactly version/lastGen/interval/mode/ ticks and vanilla disconnects the player with "8 bytes extra" on the first hello. Verified with javap on the release jar; the other three codecs match. Drops the server-name option (a leftover key now only warns). Claude-Session: https://claude.ai/code/session_011FePLXwBsCGLTzaSkk1Z6V --- gradle.properties | 2 +- src/main/java/lv/janhouse/mapsyncer/Settings.java | 6 +++--- .../java/lv/janhouse/mapsyncer/net/Protocol.java | 8 ++++++-- .../lv/janhouse/mapsyncer/net/SyncService.java | 2 +- src/main/resources/config.yml | 4 ---- .../lv/janhouse/mapsyncer/net/ProtocolTest.java | 14 ++++++++++++++ 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/gradle.properties b/gradle.properties index c6a3e00..ef56560 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = lv.janhouse -version = 0.1.0 +version = 0.1.1 # Paper dev bundle (Mojang-mapped server + API) the plugin compiles against. # Must match the server it is deployed on: Paper 26.2 on JDK 25. The renderer diff --git a/src/main/java/lv/janhouse/mapsyncer/Settings.java b/src/main/java/lv/janhouse/mapsyncer/Settings.java index 468ab81..3315e2c 100644 --- a/src/main/java/lv/janhouse/mapsyncer/Settings.java +++ b/src/main/java/lv/janhouse/mapsyncer/Settings.java @@ -34,8 +34,6 @@ public final class Settings { public final int clientPollIntervalTicks; public final int helloDelayTicks; - public final String serverName; - public final WorldSettings defaultWorld; public final Map worlds; @@ -66,7 +64,9 @@ public final class Settings { clientPollIntervalTicks = clamp(c.getInt("sync.client-poll-interval-ticks", 6000), 1200, 72000, "sync.client-poll-interval-ticks", warn); helloDelayTicks = clamp(c.getInt("sync.hello-delay-ticks", 100), 1, 1200, "sync.hello-delay-ticks", warn); - serverName = c.getString("server-name", ""); + if (c.contains("server-name")) { + warn.accept("server-name is ignored: the released MapSyncer client cannot read that hello field."); + } ConfigurationSection ws = c.getConfigurationSection("worlds"); WorldSettings def = new WorldSettings(true, TrackedWorld.Layers.AUTO); diff --git a/src/main/java/lv/janhouse/mapsyncer/net/Protocol.java b/src/main/java/lv/janhouse/mapsyncer/net/Protocol.java index b538bd8..7637a0f 100644 --- a/src/main/java/lv/janhouse/mapsyncer/net/Protocol.java +++ b/src/main/java/lv/janhouse/mapsyncer/net/Protocol.java @@ -74,16 +74,20 @@ public final class Protocol { // -------------------------------------------------------------- encoders + /** + * Five fields, no more. Upstream's git HEAD appends a sixth (serverName), but + * the released 1.0.4 client reads exactly five and vanilla disconnects on + * "8 bytes extra" if anything follows — verified against the shipped jar. + */ public static byte[] encodeServerInstalled(String version, long lastGenerationTimestamp, int autoSyncIntervalMinutes, UpdateMode mode, - int incrementalUpdateIntervalTicks, String serverName) { + int incrementalUpdateIntervalTicks) { return new PacketWriter(64) .writeUtf(version) .writeLong(lastGenerationTimestamp) .writeInt(autoSyncIntervalMinutes) .writeByte(mode.ordinal()) .writeInt(incrementalUpdateIntervalTicks) - .writeUtf(serverName == null ? "" : serverName) .toByteArray(); } diff --git a/src/main/java/lv/janhouse/mapsyncer/net/SyncService.java b/src/main/java/lv/janhouse/mapsyncer/net/SyncService.java index ee9f242..c66ed90 100644 --- a/src/main/java/lv/janhouse/mapsyncer/net/SyncService.java +++ b/src/main/java/lv/janhouse/mapsyncer/net/SyncService.java @@ -199,7 +199,7 @@ public final class SyncService implements PluginMessageListener, Listener { int minutes = Math.max(1, ticks / 20 / 60); byte[] payload = Protocol.encodeServerInstalled( "mapsyncer-paper/" + plugin.getPluginMeta().getVersion(), - lastGen, minutes, Protocol.UpdateMode.TICK, ticks, settings.serverName); + lastGen, minutes, Protocol.UpdateMode.TICK, ticks); player.sendPluginMessage(plugin, Protocol.SERVER_INSTALLED, payload); s.helloSent = true; helloCount.incrementAndGet(); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 66ddbfb..ffb1764 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -58,10 +58,6 @@ sync: # cache, otherwise it forgets what it already has and re-syncs on every join. hello-delay-ticks: 100 -# Optional stable name sent to clients. The client mod uses it to recognise this -# server when it is reached under a different address. Leave empty to disable. -server-name: "" - # Per-world settings. Keys are Bukkit world names. Anything not listed uses # "default". `layers` controls what is rendered: # auto surface for worlds with a sky, cave layers for worlds with a ceiling diff --git a/src/test/java/lv/janhouse/mapsyncer/net/ProtocolTest.java b/src/test/java/lv/janhouse/mapsyncer/net/ProtocolTest.java index 7f8054c..600805f 100644 --- a/src/test/java/lv/janhouse/mapsyncer/net/ProtocolTest.java +++ b/src/test/java/lv/janhouse/mapsyncer/net/ProtocolTest.java @@ -138,6 +138,20 @@ class ProtocolTest { assertFalse(Protocol.split(0, 0, "null", new byte[100], 1, Protocol.SURFACE_LAYER).get(0).isSplit()); } + @Test + void helloHasExactlyFiveFields() { + // The released 1.0.4 client reads utf, long, int, byte, int and nothing + // else; a trailing byte disconnects the player. + byte[] hello = Protocol.encodeServerInstalled("v", 123L, 5, Protocol.UpdateMode.TICK, 6000); + PacketReader r = new PacketReader(hello); + assertEquals("v", r.readUtf()); + assertEquals(123L, r.readLong()); + assertEquals(5, r.readInt()); + assertEquals(1, r.readByte()); + assertEquals(6000, r.readInt()); + assertEquals(0, r.readableBytes()); + } + @Test void hashValidity() { assertTrue(Protocol.isValidHash("a1b2c3d4"));