From fb5e15a2f1df18118888996cdb399c99fb287624 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Sat, 16 Aug 2025 20:59:28 +0500 Subject: [PATCH 1/2] Add a server keys feature --- .../api/internal/ClientPluginChannelApi.java | 13 ++++++++--- .../distanthorizons/core/config/Config.java | 22 +++++++++++++++++++ .../structure/ClientOnlySaveStructure.java | 19 +++++++++++----- .../core/level/IKeyedClientLevelManager.java | 2 +- .../core/level/IServerKeyedClientLevel.java | 3 +++ .../multiplayer/server/ServerPlayerState.java | 6 ++++- .../messages/base/LevelInitMessage.java | 14 ++++++++++-- 7 files changed, 66 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java index 05926b4f8..fc5e9ae26 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientPluginChannelApi.java @@ -75,7 +75,12 @@ public class ClientPluginChannelApi private void onLevelInitMessage(LevelInitMessage msg) { - if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_REGEX)) + if (!msg.serverKey.matches(LevelInitMessage.SERVER_KEY_REGEX)) + { + throw new IllegalArgumentException("Server sent invalid server key."); + } + + if (!msg.levelKey.matches(LevelInitMessage.LEVEL_KEY_REGEX)) { throw new IllegalArgumentException("Server sent invalid level key."); } @@ -105,10 +110,12 @@ public class ClientPluginChannelApi this.levelUnloadHandler.accept(clientLevel); } - if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey)) + if (existingKeyedClientLevel == null + || !existingKeyedClientLevel.getServerKey().equals(msg.serverKey) + || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey)) { LOGGER.info("Loading level with key: [" + msg.levelKey + "]."); - IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey); + IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey); this.levelLoadHandler.accept(keyedLevel); } }); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 0ec97caea..5b2982409 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -1599,6 +1599,28 @@ public class Config + "") .build(); + public static ConfigEntry serverId = new ConfigEntry.Builder() + .set(new Random().nextInt()) + .setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) + .comment("" + + "DO NOT CHANGE UNLESS YOU KNOW WHAT YOU'RE DOING.\n" + + "Autogenerated ID used to prevent multiple independent servers from accidentally\n" + + "writing over each other's LODs when the same serverKey is set on both.\n" + + "") + .build(); + + public static ConfigEntry serverKey = new ConfigEntry.Builder() + .setChatCommandName("levelKeys.serverKey") + .setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) + .set("") + .comment("" + + "Custom server key used which can be used to always reuse the same LOD data folder,\n" + + "for cases when the server doesn't have a static IP for some reason.\n" + + "If this value is empty, the client itself decides which folder name to use.\n" + + "Requires rejoining the server to apply after changing.\n" + + "") + .build(); + public static ConfigEntry levelKeyPrefix = new ConfigEntry.Builder() .setChatCommandName("levelKeys.prefix") .set("") diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java index 3efb4c820..b1993de84 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java @@ -81,13 +81,20 @@ public class ClientOnlySaveStructure implements ISaveStructure { IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper; LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "]."); - // This world was identified by the server directly, so we can know for sure which folder to use. - saveFolder = getSaveFolderByLevelId(keyedClientLevel.getServerLevelKey()); + + String serverKey = keyedClientLevel.getServerKey(); + if (serverKey.isEmpty()) + { + serverKey = getServerFolderName(); + } + + // This world was identified by the server directly, so we can know for sure which folder to use. + saveFolder = getSaveFolderByLevelId(serverKey, keyedClientLevel.getServerLevelKey()); } else { // get the default folder - saveFolder = getSaveFolderByLevelId(levelWrapper.getDhIdentifier()); + saveFolder = getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDhIdentifier()); } // Allow API users to override the save folder @@ -116,7 +123,7 @@ public class ClientOnlySaveStructure implements ISaveStructure return this.getSaveFolder(levelWrapper); } - return getSaveFolderByLevelId(levelWrapper.getDimensionType().getName()); + return getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDimensionType().getName()); } @@ -173,11 +180,11 @@ public class ClientOnlySaveStructure implements ISaveStructure } - private static File getSaveFolderByLevelId(String dimensionName) + private static File getSaveFolderByLevelId(String folderName, String dimensionName) { String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar + SERVER_DATA_FOLDER_NAME + File.separatorChar - + getServerFolderName() + File.separatorChar + + folderName + File.separatorChar + dimensionName.replaceAll(":", "@@"); return new File(path); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IKeyedClientLevelManager.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IKeyedClientLevelManager.java index 404099c60..ca66ebcb6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IKeyedClientLevelManager.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IKeyedClientLevelManager.java @@ -30,7 +30,7 @@ public interface IKeyedClientLevelManager extends IBindable { IServerKeyedClientLevel getServerKeyedLevel(); /** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */ - IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String levelKey); + IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey); void clearKeyedLevel(); boolean isEnabled(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IServerKeyedClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IServerKeyedClientLevel.java index ae0c9de08..3aaf5c4c4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IServerKeyedClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IServerKeyedClientLevel.java @@ -24,6 +24,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp /** Enhances a {@link IClientLevelWrapper} with server provided level information. */ public interface IServerKeyedClientLevel extends IClientLevelWrapper { + /** Returns the folder name the server wants the player to use. */ + String getServerKey(); + /** Returns the level key, which is used to select the correct folder on the client. */ String getServerLevelKey(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java index 857f4cc75..7069774b9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java @@ -27,6 +27,10 @@ public class ServerPlayerState implements Closeable private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage); + private final String serverKeyWithoutId = Config.Server.serverKey.get(); + private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim()) + .replaceAll("[^" + LevelInitMessage.PART_ALLOWED_CHARS_REGEX + " ]", "") + .replaceAll(" ", "_"); private String lastLevelKey = ""; @@ -89,7 +93,7 @@ public class ServerPlayerState implements Closeable if (!levelKey.equals(this.lastLevelKey)) { this.lastLevelKey = levelKey; - this.networkSession.sendMessage(new LevelInitMessage(levelKey)); + this.networkSession.sendMessage(new LevelInitMessage(this.serverKey, levelKey)); } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/LevelInitMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/LevelInitMessage.java index fa3c108fe..119edf165 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/LevelInitMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/LevelInitMessage.java @@ -10,12 +10,18 @@ public class LevelInitMessage extends AbstractNetworkMessage public static final String PART_ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_"; + // A plain string of characters + // 1-150 characters in total + public static final String SERVER_KEY_REGEX = String.format("^(?=.{1,%s}$)[%s]+$", + MAX_LENGTH, PART_ALLOWED_CHARS_REGEX); + // prefix@namespace:path // 1-150 characters in total, all parts except namespace can be omitted - public static final String VALIDATION_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$", + public static final String LEVEL_KEY_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$", MAX_LENGTH, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX); + public String serverKey; public String levelKey; public long serverTime; @@ -26,8 +32,9 @@ public class LevelInitMessage extends AbstractNetworkMessage //==============// public LevelInitMessage() { } - public LevelInitMessage(String levelKey) + public LevelInitMessage(String serverKey, String levelKey) { + this.serverKey = serverKey; this.levelKey = levelKey; this.serverTime = System.currentTimeMillis(); } @@ -41,6 +48,7 @@ public class LevelInitMessage extends AbstractNetworkMessage @Override public void encode(ByteBuf out) { + this.writeString(this.serverKey, out); this.writeString(this.levelKey, out); out.writeLong(this.serverTime); } @@ -48,6 +56,7 @@ public class LevelInitMessage extends AbstractNetworkMessage @Override public void decode(ByteBuf in) { + this.serverKey = this.readString(in); this.levelKey = this.readString(in); this.serverTime = in.readLong(); } @@ -62,6 +71,7 @@ public class LevelInitMessage extends AbstractNetworkMessage public MoreObjects.ToStringHelper toStringHelper() { return super.toStringHelper() + .add("serverKey", this.serverKey) .add("levelKey", this.levelKey) .add("serverTime", this.serverTime); } From 034ec7d656aa2ac9a1d5c00798b506046d11b179 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Sat, 16 Aug 2025 21:01:45 +0500 Subject: [PATCH 2/2] Bump protocol version --- .../main/java/com/seibel/distanthorizons/coreapi/ModInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java index 77a692d62..dc314b0a3 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java @@ -31,7 +31,7 @@ public final class ModInfo public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial"; /** Incremented every time any packets are added, changed or removed, with a few exceptions. */ - public static final int PROTOCOL_VERSION = 11; + public static final int PROTOCOL_VERSION = 12; public static final String WRAPPER_PACKET_PATH = "message"; /** The internal mod name */