From a054bf6846cbbafe501691dab6353cd0da5463c2 Mon Sep 17 00:00:00 2001 From: turikhay Date: Wed, 7 Sep 2022 22:38:43 +0300 Subject: [PATCH] Fix VoxelMap support: special case for zero-length packet prefix --- .../mc/mapmodcompanion/worldid/WorldId.java | 111 +++++++++++++++--- .../worldid/WorldIdRequest.java | 49 ++++++++ .../spigot/CompanionSpigot.java | 2 +- .../mc/mapmodcompanion/spigot/Handler.java | 51 ++++++-- .../spigot/WorldIdHandler.java | 63 +++++++++- .../mapmodcompanion/spigot/XaerosHandler.java | 6 +- 6 files changed, 247 insertions(+), 35 deletions(-) create mode 100644 common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldIdRequest.java diff --git a/common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldId.java b/common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldId.java index 54af78f..f806eb6 100644 --- a/common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldId.java +++ b/common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldId.java @@ -8,22 +8,48 @@ import java.util.Objects; public /* sealed */ abstract class WorldId implements IdMessagePacket { - private static final int MAGIC_MARKER = 42; + public static final int MAGIC_MARKER = 42; + private static final int DEFAULT_PREFIX_LENGTH = 1; + + protected final boolean isNumeric; + @Nullable + protected final Integer prefixLength; + + public WorldId(boolean isNumeric, @Nullable Integer prefixLength) { + this.isNumeric = isNumeric; + this.prefixLength = prefixLength; + } + + public int getPrefixLength() { + return prefixLength == null ? DEFAULT_PREFIX_LENGTH : prefixLength; + } + + public WorldId withPrefixLength(int prefixLength) { + return new Delegating(this, prefixLength); + } @Override public WorldId combineWith(WorldId packet) { + Integer newPrefixLength = selectPrefixLength(this, packet); if (this instanceof Numeric || packet instanceof Numeric) { // combining with numeric id always "pollutes" the result return new WorldId.Numeric( - Objects.hash(getNumericId(), packet.getNumericId()) + Objects.hash(getNumericId(), packet.getNumericId()), + newPrefixLength ); } - return new WorldId.Textual(packet.getStringId() + '_' + getStringId()); + return new WorldId.Textual( + packet.getStringId() + '_' + getStringId(), + newPrefixLength + ); } @Override public void constructPacket(DataOutputStream out) throws IOException { - out.writeByte(0); // packetId + int prefixBytesCount = prefixLength == null ? DEFAULT_PREFIX_LENGTH : prefixLength; + for (int i = 0; i < prefixBytesCount; i++) { + out.writeByte(0); // packetId, or a prefix + } out.writeByte(MAGIC_MARKER); // 42 (literally) byte[] data = getStringId().getBytes(StandardCharsets.UTF_8); out.write(data.length); // length @@ -34,10 +60,13 @@ public void constructPacket(DataOutputStream out) throws IOException { public static WorldId tryRead(byte[] data) { DataInputStream in = new DataInputStream(new ByteArrayInputStream(data)); try { - if (in.readByte() != 0) { - return null; - } - if (in.readByte() != MAGIC_MARKER) { + int prefixSize = -1; + int c; + do { + prefixSize++; + c = in.readByte(); + } while(c == 0); + if (c != MAGIC_MARKER) { return null; } int length = in.readByte(); @@ -47,22 +76,22 @@ public static WorldId tryRead(byte[] data) { return null; } String id = new String(buf, StandardCharsets.UTF_8); - Numeric possiblyNumeric = Numeric.tryNumeric(id); + Numeric possiblyNumeric = Numeric.tryNumeric(id, prefixSize); if (possiblyNumeric != null) { return possiblyNumeric; } - return new Textual(id); + return new Textual(id, prefixSize); } catch (IOException ignored) { } return null; } public static WorldId textual(String id) { - return new Textual(id); + return new Textual(id, null); } public static WorldId numeric(int id) { - return new Numeric(id); + return new Numeric(id, null); } protected abstract int getNumericId(); @@ -71,7 +100,8 @@ public static WorldId numeric(int id) { static class Numeric extends WorldId { private final int id; - public Numeric(int id) { + private Numeric(int id, @Nullable Integer prefixLength) { + super(true, prefixLength); this.id = id; } @@ -87,27 +117,29 @@ protected String getStringId() { @Override public String toString() { - return "WorldId.Numeric{" + + return "Numeric{" + "id=" + id + + (prefixLength == null ? "" : ", prefix=" + prefixLength) + '}'; } @Nullable - public static Numeric tryNumeric(String value) { + private static Numeric tryNumeric(String value, int prefixSize) { int numeric; try { numeric = Integer.parseInt(value); } catch (NumberFormatException ignored) { return null; } - return new Numeric(numeric); + return new Numeric(numeric, prefixSize); } } static class Textual extends WorldId { private final String id; - public Textual(String id) { + public Textual(String id, @Nullable Integer prefixLength) { + super(false, prefixLength); this.id = id; } @@ -125,7 +157,52 @@ protected String getStringId() { public String toString() { return "WorldId.Textual{" + "id='" + id + '\'' + + (prefixLength == null ? "" : ", prefix=" + prefixLength) + + '}'; + } + } + + private static class Delegating extends WorldId { + private final WorldId parent; + + public Delegating(WorldId parent, @Nullable Integer prefixLength) { + super(parent.isNumeric, prefixLength); + this.parent = parent; + } + + @Override + protected int getNumericId() { + return parent.getNumericId(); + } + + @Override + protected String getStringId() { + return parent.getStringId(); + } + + @Override + public String toString() { + return "Delegating{" + + "parent=" + parent + + ", isNumeric=" + isNumeric + + ", prefixLength=" + prefixLength + '}'; } } + + @Nullable + private static Integer selectPrefixLength(WorldId id0, WorldId id1) { + Integer length0 = id0.prefixLength; + Integer length1 = id1.prefixLength; + if (Objects.equals(length0, length1)) { + return length0; + } + if (length0 != null && length1 != null) { + throw new IllegalArgumentException("trying to combine IDs with different prefix sizes"); + } + if (length0 != null) { + return length0; + } + return length1; + } } diff --git a/common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldIdRequest.java b/common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldIdRequest.java new file mode 100644 index 0000000..3a0017f --- /dev/null +++ b/common/src/main/java/com/turikhay/mc/mapmodcompanion/worldid/WorldIdRequest.java @@ -0,0 +1,49 @@ +package com.turikhay.mc.mapmodcompanion.worldid; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; + +import static com.turikhay.mc.mapmodcompanion.worldid.WorldId.MAGIC_MARKER; + +public class WorldIdRequest { + + private final int prefixLength; + + public WorldIdRequest(int prefixLength) { + this.prefixLength = prefixLength; + } + + public int getPrefixLength() { + return prefixLength; + } + + public static WorldIdRequest parse(byte[] message) throws IOException { + int prefixLength = 0; + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) { + if (in.readByte() != 0) { + throw new IOException("unexpected first byte"); + } + int c; + do { + prefixLength++; + c = in.readByte(); + } while(c == 0); + if (c != MAGIC_MARKER) { + throw new IOException("first byte prefix is not a magic number"); + } + } + switch (prefixLength) { + case 1: + // Normal request + break; + case 3: + // VoxelMap fix + prefixLength = 0; + break; + default: + throw new IOException("unexpected prefix length"); + } + return new WorldIdRequest(prefixLength); + } +} diff --git a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/CompanionSpigot.java b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/CompanionSpigot.java index 3622a93..590ca20 100644 --- a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/CompanionSpigot.java +++ b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/CompanionSpigot.java @@ -19,7 +19,7 @@ public class CompanionSpigot extends JavaPlugin implements Listener { System.getProperty(CompanionSpigot.class.getPackage().getName() + ".useTextualId", "false") ); - List> handlers = Arrays.asList( + List> handlers = Arrays.asList( new XaerosHandler(this), new WorldIdHandler(this) ); diff --git a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/Handler.java b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/Handler.java index 3ad2c76..4ba0bb1 100644 --- a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/Handler.java +++ b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/Handler.java @@ -9,10 +9,12 @@ import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; +import javax.annotation.Nullable; import java.util.Arrays; import java.util.Locale; +import java.util.Optional; -public abstract class Handler> implements Listener { +public abstract class Handler, A> implements Listener { private final String channelName; protected final CompanionSpigot plugin; @@ -31,15 +33,15 @@ public void init() { @EventHandler(priority = EventPriority.MONITOR) public void onPlayerJoined(PlayerJoinEvent event) { - sendLevelId(event.getPlayer(), EventSource.JOIN); + sendLevelId(event.getPlayer(), Context.of(EventSource.JOIN)); } @EventHandler(priority = EventPriority.MONITOR) public void onWorldChanged(PlayerChangedWorldEvent event) { - sendLevelId(event.getPlayer(), EventSource.WORLD_CHANGE); + sendLevelId(event.getPlayer(), Context.of(EventSource.WORLD_CHANGE)); } - public void sendLevelId(Player player, EventSource source) { + public void sendLevelId(Player player, Context context) { scheduleLevelIdPacket( () -> { IdRef idRef; @@ -48,6 +50,7 @@ public void sendLevelId(Player player, EventSource source) { } else { idRef = defaultId; } + idRef = processRef(idRef, context); if (CompanionSpigot.ENABLE_LOGGING) { plugin.getLogger().info(String.format(Locale.ROOT, "Sending world id to %s (channel: %s): %s. Data: %s", @@ -57,11 +60,15 @@ public void sendLevelId(Player player, EventSource source) { } player.sendPluginMessage(plugin, channelName, idRef.data); }, - source + context ); } - public abstract void scheduleLevelIdPacket(Runnable r, EventSource source); + protected IdRef processRef(IdRef idRef, Context context) { + return idRef; + } + + public abstract void scheduleLevelIdPacket(Runnable r, Context context); public abstract Id getId(World world); public enum EventSource { @@ -70,7 +77,7 @@ public enum EventSource { PLUGIN_MESSAGE } - private static class IdRef> { + static class IdRef> { private final Id id; private final byte[] data; @@ -79,6 +86,10 @@ private IdRef(Id id, byte[] data) { this.data = data; } + public Id getId() { + return id; + } + @Override public String toString() { return "DefaultId{" + @@ -87,8 +98,32 @@ public String toString() { '}'; } - private static > IdRef of(Id id) { + static > IdRef of(Id id) { return new IdRef<>(id, IdMessagePacket.bytesPacket(id)); } } + + static class Context { + private final EventSource source; + + @Nullable + private final A aux; + + Context(EventSource source, @Nullable A aux) { + this.source = source; + this.aux = aux; + } + + public EventSource getSource() { + return source; + } + + public Optional getAux() { + return Optional.ofNullable(aux); + } + + private static Context of(EventSource source) { + return new Context<>(source, null); + } + } } diff --git a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/WorldIdHandler.java b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/WorldIdHandler.java index ce2e944..ab3e211 100644 --- a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/WorldIdHandler.java +++ b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/WorldIdHandler.java @@ -1,20 +1,22 @@ package com.turikhay.mc.mapmodcompanion.spigot; import com.turikhay.mc.mapmodcompanion.worldid.WorldId; +import com.turikhay.mc.mapmodcompanion.worldid.WorldIdRequest; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; +import java.io.IOException; import java.util.Arrays; import java.util.Locale; +import java.util.Optional; import java.util.UUID; import static com.turikhay.mc.mapmodcompanion.worldid.WorldIdCompanion.WORLD_ID_CHANNEL_NAME; -import static com.turikhay.mc.mapmodcompanion.worldid.WorldIdCompanion.WORLD_ID_PACKET_DELAY; -public class WorldIdHandler extends Handler implements Listener, PluginMessageListener { +public class WorldIdHandler extends Handler implements Listener, PluginMessageListener { public WorldIdHandler(CompanionSpigot plugin) { super(WORLD_ID_CHANNEL_NAME, plugin); @@ -27,8 +29,8 @@ public void init() { } @Override - public void scheduleLevelIdPacket(Runnable r, EventSource source) { - if (source != EventSource.PLUGIN_MESSAGE) { + public void scheduleLevelIdPacket(Runnable r, Context context) { + if (context.getSource() != EventSource.PLUGIN_MESSAGE) { // This handler should only send worldId on a request return; } @@ -45,18 +47,67 @@ public WorldId getId(World world) { } } + @Override + protected Handler.IdRef processRef(Handler.IdRef idRef, Context context) { + Optional aux = context.getAux(); + if (!aux.isPresent()) { + return idRef; // no context, return as-is + } + PlayerWorldIdRequest pr = aux.get(); + int prefixLength = pr.getRequest().getPrefixLength(); + if (idRef.getId().getPrefixLength() == prefixLength) { + return idRef; // ok + } + if (CompanionSpigot.ENABLE_LOGGING) { + plugin.getLogger().info(String.format(Locale.ROOT, + "Modifying response packet for %s (different prefix length)", + pr.getPlayer().getName() + )); + } + return IdRef.of(idRef.getId().withPrefixLength(prefixLength)); + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { if (!channel.equals(WORLD_ID_CHANNEL_NAME)) { return; } + WorldIdRequest request; + try { + request = WorldIdRequest.parse(message); + } catch (IOException e) { + plugin.getLogger().info(String.format(Locale.ROOT, + "Received possibly corrupted world id request from %s: %s", + player.getName(), + Arrays.toString(message) + )); + plugin.getLogger().info("Error message: " + e); + return; + } if (CompanionSpigot.ENABLE_LOGGING) { plugin.getLogger().info(String.format(Locale.ROOT, "Responding to %s's request (channel %s): %s", player.getName(), channel, Arrays.toString(message) )); } - // JourneyMap Server also sends this packet unconditionally - sendLevelId(player, EventSource.PLUGIN_MESSAGE); + sendLevelId(player, new Context<>(EventSource.PLUGIN_MESSAGE, new PlayerWorldIdRequest(player, request))); + } + + static class PlayerWorldIdRequest { + private final Player player; + private final WorldIdRequest request; + + public PlayerWorldIdRequest(Player player, WorldIdRequest request) { + this.player = player; + this.request = request; + } + + public Player getPlayer() { + return player; + } + + public WorldIdRequest getRequest() { + return request; + } } } diff --git a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/XaerosHandler.java b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/XaerosHandler.java index f4b69f2..11489a9 100644 --- a/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/XaerosHandler.java +++ b/spigot/src/main/java/com/turikhay/mc/mapmodcompanion/spigot/XaerosHandler.java @@ -7,14 +7,14 @@ import static com.turikhay.mc.mapmodcompanion.xaeros.XaerosCompanion.XAEROS_CHANNEL_NAME; import static com.turikhay.mc.mapmodcompanion.xaeros.XaerosCompanion.XAEROS_PACKET_REPEAT_TIMES; -public class XaerosHandler extends Handler implements Listener { +public class XaerosHandler extends Handler implements Listener { public XaerosHandler(CompanionSpigot plugin) { super(XAEROS_CHANNEL_NAME, plugin); } @Override - public void scheduleLevelIdPacket(Runnable r, EventSource source) { - if (source == EventSource.JOIN) { + public void scheduleLevelIdPacket(Runnable r, Context context) { + if (context.getSource() == EventSource.JOIN) { // Sometimes Xaero's World Map is not initialized at the time they receive our packet, // leading to world not being recognized. We fix this issue by sending our packet more than once. for (int i = 0; i < XAEROS_PACKET_REPEAT_TIMES; i++) {