From 0ad766d8dd033b01feb12842e43728f8845218ad Mon Sep 17 00:00:00 2001 From: roepun <138587970+roepun@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:23:43 +0200 Subject: [PATCH 1/4] Add auto-connect option to server config and settings GUI --- .../mapsync/mod/config/ServerConfig.java | 6 ++++++ .../mod/config/gui/SyncConnectionsGui.java | 19 ++++++++++++------- .../mapsync/mod/sync/GameContext.java | 11 +++++++++++ .../mapsync/mod/sync/SyncConnections.java | 2 ++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java index 5b4bb2c..9a160e2 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java @@ -12,6 +12,9 @@ public final class ServerConfig extends JsonConfig { @Expose private ArrayList syncServerAddresses = new ArrayList<>(); + @Expose + private boolean autoConnect = false; + public @NotNull List<@NotNull String> getSyncServerAddresses() { return this.syncServerAddresses.stream() .map(String::trim) @@ -27,6 +30,9 @@ public void setSyncServerAddresses( this.syncServerAddresses = new ArrayList<>(syncAddresses); } + public boolean isAutoConnect() { return this.autoConnect; } + public void setAutoConnect(final boolean autoConnect) { this.autoConnect = autoConnect; } + @Override public void resetToDefaults() { this.setSyncServerAddresses(List.of( diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java index 2a6fa53..c116466 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java @@ -8,6 +8,7 @@ import java.util.Set; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Checkbox; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.CommonComponents; @@ -40,13 +41,6 @@ protected void init() { final int offsetRight = this.width / 2 + innerWidth / 2; this.offsetTop = this.height / 3; - this.addRenderableWidget( - Button.builder(Component.literal("Close"), (button) -> this.minecraft.setScreen(this.parentScreen)) - .pos(offsetRight - 100, this.offsetTop) - .width(100) - .build() - ); - final EditBox addressField = this.addRenderableWidget(new EditBox( this.font, this.offsetLeft, @@ -58,6 +52,17 @@ protected void init() { addressField.setValue(this.addressFieldValue); addressField.setResponder((value) -> this.addressFieldValue = value); + this.addRenderableWidget( + Checkbox.builder(Component.literal("Auto-connect"), this.font) + .pos(offsetRight - 100, this.offsetTop + 18) + .selected(this.gameContext.getGameConfig().isAutoConnect()) + .onValueChange((checkbox, value) -> { + this.gameContext.getGameConfig().setAutoConnect(value); + this.gameContext.getGameConfig().save(); + }) + .build() + ); + this.addRenderableWidget( Button .builder( diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java index ad0b59e..69b3cc8 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java @@ -5,8 +5,11 @@ import gjum.minecraft.mapsync.mod.data.GameAddress; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; +import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; + import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientWorldEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; @@ -143,6 +146,14 @@ public static void initEvents() { previous.shutDown(); } MapSyncMod.handleDimensionChange(minecraft, level, gameContext); + + final ServerConfig gameConfig = gameContext.getGameConfig(); + if (gameConfig.isAutoConnect() && gameContext.getSyncConnections().hasNoConnections()) { + final @NotNull List<@NotNull String> addresses = gameConfig.getSyncServerAddresses(); + if (!addresses.isEmpty()) { + gameContext.getSyncConnections().setAll(Set.copyOf(addresses)); + } + } }); } } diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java index e28ff19..c2bc0aa 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java @@ -27,6 +27,8 @@ public final class SyncConnections implements Iterable { return this.clients.values().iterator(); } + public boolean hasNoConnections() { return this.clients.isEmpty(); } + public void setAll( final @NotNull Set<@NotNull String> syncAddresses ) { From 5b46f6f1ffa8b820eb64919535dcfbd988561489 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 14 Apr 2026 07:09:03 +0100 Subject: [PATCH 2/4] Slightly undo the reconnect logic in preparation --- .../gjum/minecraft/mapsync/mod/MapSyncMod.java | 12 ++++++++++++ .../mapsync/mod/config/ServerConfig.java | 11 +++++++++-- .../mod/config/gui/SyncConnectionsGui.java | 2 +- .../minecraft/mapsync/mod/sync/GameContext.java | 16 +++++----------- .../mapsync/mod/sync/SyncConnections.java | 2 -- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java index 4a835f2..ceb5dab 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java @@ -31,6 +31,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; @@ -132,6 +133,17 @@ public static void handleSyncPacket( } } + public static void handleGameConnection( + final @NotNull Minecraft minecraft, + final @NotNull GameContext gameContext + ) { + if (gameContext.getGameConfig().shouldAutoConnect()) { + gameContext.getSyncConnections().setAll(Set.copyOf( + gameContext.getGameConfig().getSyncServerAddresses() + )); + } + } + /// @param clientLevel This is the *new* dimension. public static void handleDimensionChange( final @NotNull Minecraft minecraft, diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java index 9a160e2..345f9be 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/ServerConfig.java @@ -30,8 +30,15 @@ public void setSyncServerAddresses( this.syncServerAddresses = new ArrayList<>(syncAddresses); } - public boolean isAutoConnect() { return this.autoConnect; } - public void setAutoConnect(final boolean autoConnect) { this.autoConnect = autoConnect; } + public boolean shouldAutoConnect() { + return this.autoConnect; + } + + public void setAutoConnect( + final boolean autoConnect + ) { + this.autoConnect = autoConnect; + } @Override public void resetToDefaults() { diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java index c116466..abbec27 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/config/gui/SyncConnectionsGui.java @@ -55,7 +55,7 @@ protected void init() { this.addRenderableWidget( Checkbox.builder(Component.literal("Auto-connect"), this.font) .pos(offsetRight - 100, this.offsetTop + 18) - .selected(this.gameContext.getGameConfig().isAutoConnect()) + .selected(this.gameContext.getGameConfig().shouldAutoConnect()) .onValueChange((checkbox, value) -> { this.gameContext.getGameConfig().setAutoConnect(value); this.gameContext.getGameConfig().save(); diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java index 69b3cc8..4be6a70 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/GameContext.java @@ -5,11 +5,8 @@ import gjum.minecraft.mapsync.mod.data.GameAddress; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; - import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientWorldEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; @@ -124,6 +121,11 @@ public static void initEvents() { } } }); + ClientPlayConnectionEvents.JOIN.register((gameConnection, packetSender, minecraft) -> { + if (instance instanceof final GameContext context) { + MapSyncMod.handleGameConnection(minecraft, context); + } + }); ClientPlayConnectionEvents.DISCONNECT.register((gameConnection, minecraft) -> { if (INSTANCE.getAndSet((Object) null) instanceof final GameContext context) { context.shutdown(); @@ -146,14 +148,6 @@ public static void initEvents() { previous.shutDown(); } MapSyncMod.handleDimensionChange(minecraft, level, gameContext); - - final ServerConfig gameConfig = gameContext.getGameConfig(); - if (gameConfig.isAutoConnect() && gameContext.getSyncConnections().hasNoConnections()) { - final @NotNull List<@NotNull String> addresses = gameConfig.getSyncServerAddresses(); - if (!addresses.isEmpty()) { - gameContext.getSyncConnections().setAll(Set.copyOf(addresses)); - } - } }); } } diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java index c2bc0aa..e28ff19 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java @@ -27,8 +27,6 @@ public final class SyncConnections implements Iterable { return this.clients.values().iterator(); } - public boolean hasNoConnections() { return this.clients.isEmpty(); } - public void setAll( final @NotNull Set<@NotNull String> syncAddresses ) { From 7b37a4e49e86aaa8d7e98eb67ff0297b56581610 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 29 Apr 2026 05:09:25 +0100 Subject: [PATCH 3/4] Add dimension-change packet --- .../minecraft/mapsync/mod/MapSyncMod.java | 20 ++++++++--- .../minecraft/mapsync/mod/net/Packet.java | 2 ++ .../mapsync/mod/net/auth/AuthProcess.java | 10 ++---- .../net/packet/ClientboundWelcomePacket.java | 7 ++-- .../ServerboundDimensionChangePacket.java | 31 ++++++++++++++++ .../packet/ServerboundHandshakePacket.java | 5 +-- .../mapsync/mod/sync/SyncConnections.java | 9 +++++ mapsync-server/src/main.ts | 36 +++++++++++++++++-- mapsync-server/src/packets.ts | 23 ++++++++++-- 9 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundDimensionChangePacket.java diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java index ceb5dab..e4b1458 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/MapSyncMod.java @@ -11,6 +11,7 @@ import gjum.minecraft.mapsync.mod.net.CloseContext; import gjum.minecraft.mapsync.mod.net.Packet; import gjum.minecraft.mapsync.mod.net.SyncClient; +import gjum.minecraft.mapsync.mod.net.packet.ServerboundDimensionChangePacket; import gjum.minecraft.mapsync.mod.sync.DimensionState; import gjum.minecraft.mapsync.mod.sync.GameContext; import gjum.minecraft.mapsync.mod.net.UnexpectedPacketException; @@ -133,6 +134,16 @@ public static void handleSyncPacket( } } + public static void handleWelcomed( + final @NotNull SyncClient client + ) { + if (client.gameContext.getDimensionState().orElse(null) instanceof final DimensionState dimensionState) { + client.send(new ServerboundDimensionChangePacket( + dimensionState.dimension.identifier() + )); + } + } + public static void handleGameConnection( final @NotNull Minecraft minecraft, final @NotNull GameContext gameContext @@ -144,14 +155,15 @@ public static void handleGameConnection( } } - /// @param clientLevel This is the *new* dimension. + /// @param level This is the *new* dimension. public static void handleDimensionChange( final @NotNull Minecraft minecraft, - final @NotNull ClientLevel clientLevel, + final @NotNull ClientLevel level, final @NotNull GameContext gameContext ) { - debugLog("handleDimensionChange"); - // TODO tell sync server to only send chunks for this dimension now + gameContext.getSyncConnections().broadcast(new ServerboundDimensionChangePacket( + level.dimension().identifier() + )); } /** diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/Packet.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/Packet.java index 547a9cc..2472f67 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/Packet.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/Packet.java @@ -9,6 +9,7 @@ import gjum.minecraft.mapsync.mod.net.packet.ClientboundWelcomePacket; import gjum.minecraft.mapsync.mod.net.packet.ServerboundCatchupRequestPacket; import gjum.minecraft.mapsync.mod.net.packet.ServerboundChunkTimestampsRequestPacket; +import gjum.minecraft.mapsync.mod.net.packet.ServerboundDimensionChangePacket; import gjum.minecraft.mapsync.mod.net.packet.ServerboundHandshakePacket; import gjum.minecraft.mapsync.mod.net.packet.ServerboundIdentityResponsePacket; import org.apache.commons.lang3.NotImplementedException; @@ -43,6 +44,7 @@ public static void encodePacket( case ChunkTilePacket $ -> ChunkTilePacket.PACKET_ID; case ServerboundHandshakePacket $ -> ServerboundHandshakePacket.PACKET_ID; case ServerboundIdentityResponsePacket $ -> ServerboundIdentityResponsePacket.PACKET_ID; + case ServerboundDimensionChangePacket $ -> ServerboundDimensionChangePacket.PACKET_ID; case ServerboundChunkTimestampsRequestPacket $ -> ServerboundChunkTimestampsRequestPacket.PACKET_ID; case ServerboundCatchupRequestPacket $ -> ServerboundCatchupRequestPacket.PACKET_ID; default -> throw new UnexpectedPacketException(packet); diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/auth/AuthProcess.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/auth/AuthProcess.java index 6c3514a..eba5efb 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/auth/AuthProcess.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/auth/AuthProcess.java @@ -1,6 +1,6 @@ package gjum.minecraft.mapsync.mod.net.auth; -import gjum.minecraft.mapsync.mod.sync.DimensionState; +import gjum.minecraft.mapsync.mod.MapSyncMod; import gjum.minecraft.mapsync.mod.net.SyncClient; import gjum.minecraft.mapsync.mod.net.UnexpectedPacketException; import gjum.minecraft.mapsync.mod.net.packet.ClientboundIdentityRequestPacket; @@ -21,17 +21,12 @@ private record AwaitingIdentityRequest() implements AuthState {} public static void sendHandshake( final @NotNull SyncClient client ) throws Exception { - final DimensionState dimensionState = client.gameContext.getDimensionState().orElse(null); - if (dimensionState == null) { - throw new IllegalStateException("no dimension state"); - } if (!client.authState.setIf(Objects::isNull, AwaitingIdentityRequest::new)) { throw new IllegalStateException("already authenticated"); } client.send(new ServerboundHandshakePacket( MagicValues.VERSION, - client.gameContext.getGameAddress(), - dimensionState.dimension.identifier().toString() + client.gameContext.getGameAddress() )); } @@ -74,5 +69,6 @@ public static void handleWelcome( if (!client.authState.setIf((state) -> state instanceof AwaitingWelcome, Welcomed::new)) { throw new UnexpectedPacketException(packet); } + MapSyncMod.handleWelcomed(client); } } diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ClientboundWelcomePacket.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ClientboundWelcomePacket.java index 25ad26a..46bc0ea 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ClientboundWelcomePacket.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ClientboundWelcomePacket.java @@ -4,11 +4,12 @@ import gjum.minecraft.mapsync.mod.net.buffers.BufferReader; import org.jetbrains.annotations.NotNull; -/// This is sent by the server to indicate a successful connection: that the client can begin sending chunk data. The -/// server will immediately follow up this packet with a [ClientboundRegionTimestampsPacket]. +/// This is sent by the server to indicate a successful connection. The client should then inform the server of its +/// current dimension via [ServerboundDimensionChangePacket], after which the client can begin sending chunk data for +/// that dimension. /// /// - Prev: [ServerboundIdentityResponsePacket] -/// - Next: [ClientboundRegionTimestampsPacket] +/// - Next: [ServerboundDimensionChangePacket] public record ClientboundWelcomePacket() implements Packet { public static final int PACKET_ID = 9; diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundDimensionChangePacket.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundDimensionChangePacket.java new file mode 100644 index 0000000..d493a5b --- /dev/null +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundDimensionChangePacket.java @@ -0,0 +1,31 @@ +package gjum.minecraft.mapsync.mod.net.packet; + +import gjum.minecraft.mapsync.mod.net.Packet; +import gjum.minecraft.mapsync.mod.net.buffers.BufferWriter; +import gjum.minecraft.mapsync.mod.utils.Assertions; +import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.NotNull; + +/// The client should send this to the server: +/// +/// 1. Whenever the player changes dimension (such as going through a portal) +/// 2. Whenever a new sync connection is made while the player is already in-game. +/// +/// - Prev: [ClientboundWelcomePacket] +/// - Next: [ClientboundRegionTimestampsPacket] +public record ServerboundDimensionChangePacket( + @NotNull Identifier dimension +) implements Packet { + public static final int PACKET_ID = 10; + + public ServerboundDimensionChangePacket { + Assertions.assertNotNull(dimension); + } + + @Override + public void write( + final @NotNull BufferWriter writer + ) throws Exception { + writer.writeString(this.dimension().toString()); + } +} diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundHandshakePacket.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundHandshakePacket.java index 49c5ada..78ca465 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundHandshakePacket.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/net/packet/ServerboundHandshakePacket.java @@ -12,15 +12,13 @@ /// - Next: [ClientboundIdentityRequestPacket] public record ServerboundHandshakePacket( @NotNull String modVersion, - @NotNull GameAddress gameAddress, - @NotNull String dimension + @NotNull GameAddress gameAddress ) implements Packet { public static final int PACKET_ID = 1; public ServerboundHandshakePacket { Assertions.assertNotNull(modVersion); Assertions.assertNotNull(gameAddress); - Assertions.assertNotNull(dimension); } @Override @@ -29,6 +27,5 @@ public void write( ) throws Exception { writer.writeString(this.modVersion()); writer.writeString(this.gameAddress().address()); - writer.writeString(this.dimension()); } } diff --git a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java index e28ff19..6fc665d 100644 --- a/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java +++ b/mapsync-mod/src/main/java/gjum/minecraft/mapsync/mod/sync/SyncConnections.java @@ -1,6 +1,7 @@ package gjum.minecraft.mapsync.mod.sync; import gjum.minecraft.mapsync.mod.MapSyncMod; +import gjum.minecraft.mapsync.mod.net.Packet; import gjum.minecraft.mapsync.mod.net.SyncClient; import java.util.Iterator; import java.util.Map; @@ -87,4 +88,12 @@ public void closeAll( return true; }); } + + public void broadcast( + final @NotNull Packet packet + ) { + for (final SyncClient syncClient : this) { + syncClient.send(packet); + } + } } diff --git a/mapsync-server/src/main.ts b/mapsync-server/src/main.ts index ecd6ed7..19b643a 100644 --- a/mapsync-server/src/main.ts +++ b/mapsync-server/src/main.ts @@ -16,6 +16,7 @@ import { ClientboundWelcomePacket, UnexpectedPacketError, ClientboundIdentityRequestPacket, + ServerboundDimensionChangePacket, } from "./packets.ts"; import { AwaitingHandshake, @@ -73,6 +74,8 @@ export class ProtocolHandler { return this.handleCatchupRequest(client, pkt); case pkt instanceof ChunkTilePacket: return this.handleChunkTilePacket(client, pkt); + case pkt instanceof ServerboundDimensionChangePacket: + return this.handleDimensionChange(client, pkt); default: throw new Error( `Unknown packet [${node_utils.inspect(pkt)}] from client ${client.id}`, @@ -94,8 +97,6 @@ export class ProtocolHandler { } // TODO: Check whether the game address is supported client.gameAddress = packet.gameAddress; - // TODO: Make this its own packet - client.dimension = packet.dimension; const serverSalt: Buffer = this.config.auth ? node_crypto.randomBytes(32) : Buffer.allocUnsafe(0); @@ -172,6 +173,16 @@ export class ProtocolHandler { // TODO check version, mc server, user access client.send(new ClientboundWelcomePacket()); + } + + private async handleDimensionChange( + client: WSClient, + pkt: ServerboundDimensionChangePacket, + ) { + if (client.dimension === pkt.dimension) { + return; + } + client.dimension = pkt.dimension; for (const region of await database.getRegionTimestamps( client.dimension!, @@ -193,6 +204,13 @@ export class ProtocolHandler { ) { const welcome = client.requireWelcomed(); + if (client.dimension !== pkt.dimension) { + client.warn( + `Client send chunk data for [${pkt.dimension}] when their dimension is [${client.dimension}]!`, + ); + return; + } + // TODO ignore if same chunk hash exists in db await database @@ -225,6 +243,13 @@ export class ProtocolHandler { ) { const welcome = client.requireWelcomed(); + if (client.dimension !== pkt.dimension) { + client.warn( + `Client requested catchup for [${pkt.dimension}] when their dimension is [${client.dimension}]!`, + ); + return; + } + for (const req of pkt.chunks) { let chunk = await database.getChunkData( pkt.dimension, @@ -262,6 +287,13 @@ export class ProtocolHandler { ) { const welcome = client.requireWelcomed(); + if (client.dimension !== pkt.dimension) { + client.warn( + `Client requested chunk timestamps for [${pkt.dimension}] when their dimension is [${client.dimension}]!`, + ); + return; + } + const chunks = await database.getChunkTimestamps( pkt.dimension, pkt.regionX, diff --git a/mapsync-server/src/packets.ts b/mapsync-server/src/packets.ts index 89a5b92..07ac0ff 100644 --- a/mapsync-server/src/packets.ts +++ b/mapsync-server/src/packets.ts @@ -15,6 +15,7 @@ import { export type ServerboundPacket = | ServerboundHandshakePacket | ServerboundIdentityResponsePacket + | ServerboundDimensionChangePacket | ServerboundChunkTimestampsRequestPacket | ServerboundCatchupRequestPacket | ChunkTilePacket; @@ -48,7 +49,6 @@ export class ServerboundHandshakePacket extends Packet { public constructor( public readonly modVersion: string, public readonly gameAddress: string, - public readonly dimension: string, ) { super(ServerboundHandshakePacket.PACKET_ID); } @@ -57,7 +57,6 @@ export class ServerboundHandshakePacket extends Packet { return new ServerboundHandshakePacket( reader.readString(), reader.readString(), - reader.readString(), ); } } @@ -107,6 +106,20 @@ export class ClientboundWelcomePacket extends Packet { public encode(writer: BufferWriter) {} } +export class ServerboundDimensionChangePacket extends Packet { + public static readonly PACKET_ID = asUnt8(10); + + public constructor(public readonly dimension: string) { + super(ServerboundDimensionChangePacket.PACKET_ID); + } + + public static decode( + reader: BufferReader, + ): ServerboundDimensionChangePacket { + return new ServerboundDimensionChangePacket(reader.readString()); + } +} + export class ClientboundRegionTimestampsPacket extends Packet { public static readonly PACKET_ID = asUnt8(7); @@ -196,7 +209,9 @@ export class ServerboundCatchupRequestPacket extends Packet { const dimension = reader.readString(); const anchorChunkX = reader.readInt16() << 5n; const anchorChunkZ = reader.readInt16() << 5n; - const chunks: CatchupChunk[] = new Array(Number(reader.readUnt10()) + 1); + const chunks: CatchupChunk[] = new Array( + Number(reader.readUnt10()) + 1, + ); for (let i = 0; i < chunks.length; i++) { chunks[i] = { chunkX: asInt32(anchorChunkX + reader.readUnt5()), @@ -253,6 +268,8 @@ export function decodePacket(reader: BufferReader): ServerboundPacket { return ServerboundHandshakePacket.decode(reader); case ServerboundIdentityResponsePacket.PACKET_ID: return ServerboundIdentityResponsePacket.decode(reader); + case ServerboundDimensionChangePacket.PACKET_ID: + return ServerboundDimensionChangePacket.decode(reader); case ServerboundChunkTimestampsRequestPacket.PACKET_ID: return ServerboundChunkTimestampsRequestPacket.decode(reader); case ServerboundCatchupRequestPacket.PACKET_ID: From eb8d49d2a5260d0aa88d93b77dea0bf119703882 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 29 Apr 2026 05:26:29 +0100 Subject: [PATCH 4/4] Add dimension-check convenience method --- mapsync-server/src/main.ts | 10 ++++++---- mapsync-server/src/server.ts | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mapsync-server/src/main.ts b/mapsync-server/src/main.ts index 19b643a..8bb7294 100644 --- a/mapsync-server/src/main.ts +++ b/mapsync-server/src/main.ts @@ -179,9 +179,11 @@ export class ProtocolHandler { client: WSClient, pkt: ServerboundDimensionChangePacket, ) { - if (client.dimension === pkt.dimension) { + if (client.isInDimension(pkt.dimension)) { return; } + // TODO: Stop any sync process of the previous dimension + client.dimension = pkt.dimension; for (const region of await database.getRegionTimestamps( @@ -204,7 +206,7 @@ export class ProtocolHandler { ) { const welcome = client.requireWelcomed(); - if (client.dimension !== pkt.dimension) { + if (!client.isInDimension(pkt.dimension)) { client.warn( `Client send chunk data for [${pkt.dimension}] when their dimension is [${client.dimension}]!`, ); @@ -243,7 +245,7 @@ export class ProtocolHandler { ) { const welcome = client.requireWelcomed(); - if (client.dimension !== pkt.dimension) { + if (!client.isInDimension(pkt.dimension)) { client.warn( `Client requested catchup for [${pkt.dimension}] when their dimension is [${client.dimension}]!`, ); @@ -287,7 +289,7 @@ export class ProtocolHandler { ) { const welcome = client.requireWelcomed(); - if (client.dimension !== pkt.dimension) { + if (!client.isInDimension(pkt.dimension)) { client.warn( `Client requested chunk timestamps for [${pkt.dimension}] when their dimension is [${client.dimension}]!`, ); diff --git a/mapsync-server/src/server.ts b/mapsync-server/src/server.ts index 4bf6975..cc40606 100644 --- a/mapsync-server/src/server.ts +++ b/mapsync-server/src/server.ts @@ -110,6 +110,10 @@ export class WSClient { throw new Error("Client is not authenticated!"); } + public isInDimension(dimension: string): boolean { + return this.dimension === dimension; + } + public kick(internalReason: string) { this.log("Kicking:", internalReason); this.ws.close(1000);