Skip to content

Commit

Permalink
Add marker manipulation & nbt sanitization
Browse files Browse the repository at this point in the history
  • Loading branch information
Moulberry committed Jan 21, 2024
1 parent b694a05 commit dbcfd0b
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 16 deletions.
27 changes: 20 additions & 7 deletions src/main/java/com/moulberry/axiom/AxiomPaper.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public void onEnable() {
msg.registerOutgoingPluginChannel(this, "axiom:set_world_property");
msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties");
msg.registerOutgoingPluginChannel(this, "axiom:restrictions");
msg.registerOutgoingPluginChannel(this, "axiom:marker_data");
msg.registerOutgoingPluginChannel(this, "axiom:marker_nbt_response");

if (configuration.getBoolean("packet-handlers.hello")) {
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this));
Expand Down Expand Up @@ -127,6 +129,9 @@ public void onEnable() {
if (configuration.getBoolean("packet-handlers.delete-entity")) {
msg.registerIncomingPluginChannel(this, "axiom:delete_entity", new DeleteEntityPacketListener(this));
}
if (configuration.getBoolean("packet-handlers.marker-nbt-request")) {
msg.registerIncomingPluginChannel(this, "axiom:marker_nbt_request", new MarkerNbtRequestPacketListener(this));
}

if (configuration.getBoolean("packet-handlers.set-buffer")) {
SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this);
Expand Down Expand Up @@ -238,11 +243,12 @@ public void afterInitChannel(@NonNull Channel channel) {
playerRestrictions.keySet().retainAll(stillActiveAxiomPlayers);
}, 20, 20);

boolean sendMarkers = configuration.getBoolean("send-markers");
int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick");
int maxChunkSendsPerTick = configuration.getInt("max-chunk-sends-per-tick");

Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
WorldExtension.tick(MinecraftServer.getServer(), maxChunkRelightsPerTick, maxChunkSendsPerTick);
WorldExtension.tick(MinecraftServer.getServer(), sendMarkers, maxChunkRelightsPerTick, maxChunkSendsPerTick);
}, 1, 1);
}

Expand Down Expand Up @@ -292,17 +298,22 @@ public boolean canModifyWorld(Player player, World world) {

@EventHandler
public void onFailMove(PlayerFailMoveEvent event) {
if (event.getPlayer().hasPermission("axiom.*")) {
if (event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
event.setAllowed(true); // Support for arcball camera
} else if (event.getPlayer().isFlying()) {
event.setAllowed(true); // Support for noclip
}
if (!this.activeAxiomPlayers.contains(event.getPlayer().getUniqueId())) {
return;
}
if (event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
event.setAllowed(true); // Support for arcball camera
} else if (event.getPlayer().isFlying()) {
event.setAllowed(true); // Support for noclip
}
}

@EventHandler
public void onChangedWorld(PlayerChangedWorldEvent event) {
if (!this.activeAxiomPlayers.contains(event.getPlayer().getUniqueId())) {
return;
}

World world = event.getPlayer().getWorld();

ServerWorldPropertiesRegistry properties = getOrCreateWorldProperties(world);
Expand All @@ -312,6 +323,8 @@ public void onChangedWorld(PlayerChangedWorldEvent event) {
} else {
properties.registerFor(this, event.getPlayer());
}

WorldExtension.onPlayerJoin(world, event.getPlayer());
}

@EventHandler
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/com/moulberry/axiom/NbtSanitization.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.moulberry.axiom;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;

import java.util.Set;

public class NbtSanitization {

private static final Set<String> ALLOWED_KEYS = Set.of(
"id", // entity id
// generic
"Pos",
"Rotation",
"Invulnerable",
"CustomName",
"CustomNameVisible",
"Silent",
"NoGravity",
"Glowing",
"Tags",
"Passengers",
// marker
"data",
// display entity
"transformation",
"interpolation_duration",
"start_interpolation",
"teleport_duration",
"billboard",
"view_range",
"shadow_radius",
"shadow_strength",
"width",
"height",
"glow_color_override",
"brightness",
"line_width",
"text_opacity",
"background",
"shadow",
"see_through",
"default_background",
"alignment",
"text",
"block_state",
"item",
"item_display"
);

public static void sanitizeEntity(CompoundTag entityRoot) {
entityRoot.getAllKeys().retainAll(ALLOWED_KEYS);

if (entityRoot.contains("Passengers", Tag.TAG_LIST)) {
ListTag listTag = entityRoot.getList("Passengers", Tag.TAG_COMPOUND);
for (Tag tag : listTag) {
sanitizeEntity((CompoundTag) tag);
}
}
}

}
84 changes: 77 additions & 7 deletions src/main/java/com/moulberry/axiom/WorldExtension.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package com.moulberry.axiom;

import com.moulberry.axiom.marker.MarkerData;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.longs.*;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Marker;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.LevelChunk;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
import org.bukkit.entity.Player;

import java.util.*;

Expand All @@ -23,22 +32,24 @@ public static WorldExtension get(ServerLevel serverLevel) {
return extension;
}

public static void tick(MinecraftServer server, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
public static void onPlayerJoin(World world, Player player) {
ServerLevel level = ((CraftWorld)world).getHandle();
get(level).onPlayerJoin(player);
}

public static void tick(MinecraftServer server, boolean sendMarkers, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
extensions.keySet().retainAll(server.levelKeys());

for (ServerLevel level : server.getAllLevels()) {
WorldExtension extension = extensions.get(level.dimension());
if (extension != null) {
extension.level = level;
extension.tick(maxChunkRelightsPerTick, maxChunkSendsPerTick);
}
get(level).tick(sendMarkers, maxChunkRelightsPerTick, maxChunkSendsPerTick);
}
}

private ServerLevel level;

private final LongSet pendingChunksToSend = new LongOpenHashSet();
private final LongSet pendingChunksToLight = new LongOpenHashSet();
private final Map<UUID, MarkerData> previousMarkerData = new HashMap<>();

public void sendChunk(int cx, int cz) {
this.pendingChunksToSend.add(ChunkPos.asLong(cx, cz));
Expand All @@ -48,7 +59,66 @@ public void lightChunk(int cx, int cz) {
this.pendingChunksToLight.add(ChunkPos.asLong(cx, cz));
}

public void tick(int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
public void onPlayerJoin(Player player) {
if (!this.previousMarkerData.isEmpty()) {
List<MarkerData> markerData = new ArrayList<>(this.previousMarkerData.values());

FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeCollection(markerData, MarkerData::write);
buf.writeCollection(Set.of(), FriendlyByteBuf::writeUUID);
byte[] bytes = new byte[buf.writerIndex()];
buf.getBytes(0, bytes);

player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:marker_data", bytes);
}
}

public void tick(boolean sendMarkers, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
if (sendMarkers) {
this.tickMarkers();
}
this.tickChunkRelight(maxChunkRelightsPerTick, maxChunkSendsPerTick);
}

private void tickMarkers() {
List<MarkerData> changedData = new ArrayList<>();

Set<UUID> allMarkers = new HashSet<>();

for (Entity entity : this.level.getEntities().getAll()) {
if (entity instanceof Marker marker) {
MarkerData currentData = MarkerData.createFrom(marker);

MarkerData previousData = this.previousMarkerData.get(marker.getUUID());
if (!Objects.equals(currentData, previousData)) {
this.previousMarkerData.put(marker.getUUID(), currentData);
changedData.add(currentData);
}

allMarkers.add(marker.getUUID());
}
}

Set<UUID> oldUuids = new HashSet<>(this.previousMarkerData.keySet());
oldUuids.removeAll(allMarkers);
this.previousMarkerData.keySet().removeAll(oldUuids);

if (!changedData.isEmpty() || !oldUuids.isEmpty()) {
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeCollection(changedData, MarkerData::write);
buf.writeCollection(oldUuids, FriendlyByteBuf::writeUUID);
byte[] bytes = new byte[buf.writerIndex()];
buf.getBytes(0, bytes);

for (ServerPlayer player : this.level.players()) {
if (AxiomPaper.PLUGIN.activeAxiomPlayers.contains(player.getUUID())) {
player.getBukkitEntity().sendPluginMessage(AxiomPaper.PLUGIN, "axiom:marker_data", bytes);
}
}
}
}

private void tickChunkRelight(int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
ChunkMap chunkMap = this.level.getChunkSource().chunkMap;

boolean sendAll = maxChunkSendsPerTick <= 0;
Expand Down
105 changes: 105 additions & 0 deletions src/main/java/com/moulberry/axiom/marker/MarkerData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.moulberry.axiom.marker;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Marker;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.UUID;

public record MarkerData(UUID uuid, Vec3 position, @Nullable String name, @Nullable Vec3 minRegion, @Nullable Vec3 maxRegion) {
public static MarkerData read(FriendlyByteBuf friendlyByteBuf) {
UUID uuid = friendlyByteBuf.readUUID();
Vec3 position = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
String name = friendlyByteBuf.readNullable(FriendlyByteBuf::readUtf);

Vec3 minRegion = null;
Vec3 maxRegion = null;
if (friendlyByteBuf.readBoolean()) {
minRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
maxRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
}

return new MarkerData(uuid, position, name, minRegion, maxRegion);
}

public static void write(FriendlyByteBuf friendlyByteBuf, MarkerData markerData) {
friendlyByteBuf.writeUUID(markerData.uuid);
friendlyByteBuf.writeDouble(markerData.position.x);
friendlyByteBuf.writeDouble(markerData.position.y);
friendlyByteBuf.writeDouble(markerData.position.z);
friendlyByteBuf.writeNullable(markerData.name, FriendlyByteBuf::writeUtf);

if (markerData.minRegion != null && markerData.maxRegion != null) {
friendlyByteBuf.writeBoolean(true);
friendlyByteBuf.writeDouble(markerData.minRegion.x);
friendlyByteBuf.writeDouble(markerData.minRegion.y);
friendlyByteBuf.writeDouble(markerData.minRegion.z);
friendlyByteBuf.writeDouble(markerData.maxRegion.x);
friendlyByteBuf.writeDouble(markerData.maxRegion.y);
friendlyByteBuf.writeDouble(markerData.maxRegion.z);
} else {
friendlyByteBuf.writeBoolean(false);
}
}

private static final Field dataField;
static {
ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar();
String fieldName = reflectionRemapper.remapFieldName(Marker.class, "data");

try {
dataField = Marker.class.getDeclaredField(fieldName);
dataField.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

public static CompoundTag getData(Marker marker) {
try {
return (CompoundTag) dataField.get(marker);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

public static MarkerData createFrom(Marker marker) {
Vec3 position = marker.position();
CompoundTag data = getData(marker);

String name = data.getString("name").trim();
if (name.isEmpty()) name = null;

Vec3 minRegion = null;
Vec3 maxRegion = null;
if (data.contains("min", Tag.TAG_LIST) && data.contains("max", Tag.TAG_LIST)) {
ListTag min = data.getList("min", Tag.TAG_DOUBLE);
ListTag max = data.getList("max", Tag.TAG_DOUBLE);

if (min.size() == 3 && max.size() == 3) {
double minX = min.getDouble(0);
double minY = min.getDouble(1);
double minZ = min.getDouble(2);
double maxX = max.getDouble(0);
double maxY = max.getDouble(1);
double maxZ = max.getDouble(2);
minRegion = new Vec3(minX, minY, minZ);
maxRegion = new Vec3(maxX, maxY, maxZ);
}

}

return new MarkerData(marker.getUUID(), position, name, minRegion, maxRegion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.moulberry.axiom.AxiomConstants;
import com.moulberry.axiom.AxiomPaper;
import com.moulberry.axiom.View;
import com.moulberry.axiom.WorldExtension;
import com.moulberry.axiom.event.AxiomHandshakeEvent;
import com.moulberry.axiom.persistence.ItemStackDataType;
import com.moulberry.axiom.persistence.UUIDDataType;
Expand Down Expand Up @@ -167,6 +168,8 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla
} else {
properties.registerFor(plugin, player);
}

WorldExtension.onPlayerJoin(world, player);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.moulberry.axiom.packet;

import com.moulberry.axiom.AxiomPaper;
import com.moulberry.axiom.NbtSanitization;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
Expand Down Expand Up @@ -104,6 +105,8 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla
if (blacklistedEntities.contains(type)) continue;

if (entry.merge != null && !entry.merge.isEmpty()) {
NbtSanitization.sanitizeEntity(entry.merge);

CompoundTag compoundTag = entity.saveWithoutId(new CompoundTag());
compoundTag = merge(compoundTag, entry.merge);
entity.load(compoundTag);
Expand Down
Loading

0 comments on commit dbcfd0b

Please sign in to comment.