Skip to content

Commit

Permalink
Fix VoxelMap support: special case for zero-length packet prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
turikhay committed Sep 7, 2022
1 parent c4d750e commit a054bf6
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,48 @@
import java.util.Objects;

public /* sealed */ abstract class WorldId implements IdMessagePacket<WorldId> {
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
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class CompanionSpigot extends JavaPlugin implements Listener {
System.getProperty(CompanionSpigot.class.getPackage().getName() + ".useTextualId", "false")
);

List<Handler<?>> handlers = Arrays.asList(
List<Handler<?, ?>> handlers = Arrays.asList(
new XaerosHandler(this),
new WorldIdHandler(this)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Id extends IdMessagePacket<?>> implements Listener {
public abstract class Handler<Id extends IdMessagePacket<?>, A> implements Listener {
private final String channelName;
protected final CompanionSpigot plugin;

Expand All @@ -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<A> context) {
scheduleLevelIdPacket(
() -> {
IdRef<Id> idRef;
Expand All @@ -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",
Expand All @@ -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<Id> processRef(IdRef<Id> idRef, Context<A> context) {
return idRef;
}

public abstract void scheduleLevelIdPacket(Runnable r, Context<A> context);
public abstract Id getId(World world);

public enum EventSource {
Expand All @@ -70,7 +77,7 @@ public enum EventSource {
PLUGIN_MESSAGE
}

private static class IdRef<Id extends IdMessagePacket<?>> {
static class IdRef<Id extends IdMessagePacket<?>> {
private final Id id;
private final byte[] data;

Expand All @@ -79,6 +86,10 @@ private IdRef(Id id, byte[] data) {
this.data = data;
}

public Id getId() {
return id;
}

@Override
public String toString() {
return "DefaultId{" +
Expand All @@ -87,8 +98,32 @@ public String toString() {
'}';
}

private static <Id extends IdMessagePacket<?>> IdRef<Id> of(Id id) {
static <Id extends IdMessagePacket<?>> IdRef<Id> of(Id id) {
return new IdRef<>(id, IdMessagePacket.bytesPacket(id));
}
}

static class Context<A> {
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<A> getAux() {
return Optional.ofNullable(aux);
}

private static <A> Context<A> of(EventSource source) {
return new Context<>(source, null);
}
}
}
Loading

0 comments on commit a054bf6

Please sign in to comment.