Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
eth-p committed Aug 17, 2022
0 parents commit d76117f
Show file tree
Hide file tree
Showing 27 changed files with 1,084 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
.gradle
.idea
build
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
all: | clean jar

jar:
@./gradlew :jar

clean:
@./gradlew clean
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# clientcontext
A BungeeCord plugin that adds [LuckPerms](https://luckperms.net/) contexts for the client version and client type.

## Contexts

### client-version
This context specifies the version of Minecraft client that the player is using.
Two values will be assigned: an exact version (e.g. `1.2.1`), and a rough version (e.g. `1.2.x`).

### client-type
This context specifies the type of Minecraft client being used.
Currently, only two types are supported: `java`, and `bedrock` (through [Geyser](https://geysermc.org/) for Spigot)
25 changes: 25 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id 'java-library'
}

apply from: 'common.gradle'

// Parent project should not compile anything.
build.enabled = false
sourceSets {
main {
java { srcDirs = [] }
}
test {
java { srcDirs = [] }
}
}

// Include all subprojects.
jar {
from {
subprojects.collect {
it.sourceSets.main.output
}
}
}
13 changes: 13 additions & 0 deletions bukkit/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id 'java-library'
}

apply from: '../common.gradle'
apply from: '../common-java.gradle'

dependencies {
implementation project(':common')

// Bukkit
implementation 'org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT'
}
133 changes: 133 additions & 0 deletions bukkit/src/main/java/dev/ethp/clientcontext/BukkitPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package dev.ethp.clientcontext;

import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextConsumer;
import net.luckperms.api.context.ContextManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

public class BukkitPlugin extends JavaPlugin implements PluginMessageListener, Listener, ContextCalculator<Player> {

@Override
public void onEnable() {
LuckPerms api = LuckPermsProvider.get();
ContextManager manager = api.getContextManager();

manager.registerCalculator(this);
getServer().getPluginManager().registerEvents(this, this);
getServer().getMessenger().registerIncomingPluginChannel(this, Constants.CHANNEL, this);
getServer().getMessenger().registerOutgoingPluginChannel(this, Constants.CHANNEL);

// Sync all client info.
for (var player : getServer().getOnlinePlayers()) {
this.requestContexts(player);
}
}

public void onDisable() {
LuckPerms api = LuckPermsProvider.get();
ContextManager manager = api.getContextManager();

manager.unregisterCalculator(this);
getServer().getMessenger().unregisterIncomingPluginChannel(this);
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
}

@EventHandler
public void onConnect(PlayerJoinEvent event) {
requestContexts(event.getPlayer());
}

/**
* Called by LuckPerms to get the player's context info.
* This will read the context set from the player's metadata.
*
* @param target The player.
* @param consumer The context consumer.
*/
@SuppressWarnings({"ConstantConditions", "unchecked"})
@Override
public void calculate(@NotNull Player target, @NotNull ContextConsumer consumer) {
for (var metadata : target.getMetadata(Constants.METADATA_KEY)) {
if (metadata.getOwningPlugin() != this) continue;
for (var context : (Set<ContextData>) metadata.value()) {
consumer.accept(context);
}
}
}

/**
* Sends a plugin message to the BungeeCord plugin, asking for it to send over the player's client information.
*
* @param player The player.
*/
public void requestContexts(@NotNull Player player) {
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(buffer);
out.writeUTF("fetch");

player.sendPluginMessage(this, Constants.CHANNEL, buffer.toByteArray());
} catch (Exception ex) {
getLogger().log(
Level.WARNING,
"Unable to request contexts for player " + player.getName() + "(" + player.getUniqueId() + ")",
ex
);
}
}

@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message) {
if (!channel.equals(Constants.CHANNEL)) return;
Set<ContextData> contexts = new HashSet<>();

// Read the contexts from the BungeeCord plugin.
try {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(message));
String command = in.readUTF();
if (!command.equalsIgnoreCase("sync")) {
return;
}

for (int i = 0, max = in.readShort(); i < max; i++) {
String key = in.readUTF();
String value = in.readUTF();

if (!key.startsWith("client-")) {
// If it doesn't start with "client-", don't set the context for security reasons.
// All contexts from the plugin should start with that.
getLogger().warning("Something tried to send a context that isn't managed by ClientContext.");
getLogger().warning("The user in question is " + player.getName() + " (" + player.getUniqueId() + ")");
continue;
}

contexts.add(new ContextData(key, value));
}
} catch (Exception ex) {
getLogger().log(Level.WARNING, "Unable to read contexts from ClientContext BungeeCord plugin.", ex);
return;
}

// Update the player's contexts.
player.setMetadata(Constants.METADATA_KEY, new FixedMetadataValue(this, contexts));
LuckPermsProvider.get().getContextManager().signalContextUpdate(player);
}

}
7 changes: 7 additions & 0 deletions bukkit/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: ClientContext
author: eth-p
version: ${version}
main: dev.ethp.clientcontext.BukkitPlugin
api-version: 1.16
depend:
- LuckPerms
16 changes: 16 additions & 0 deletions bungee/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id 'java-library'
}

apply from: '../common.gradle'
apply from: '../common-java.gradle'

dependencies {
implementation project(':common')

implementation 'org.geysermc:geyser-api:2.0.0-SNAPSHOT'
implementation 'org.geysermc.floodgate:api:2.0-SNAPSHOT'

// Bungeecord
implementation 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT'
}
82 changes: 82 additions & 0 deletions bungee/src/main/java/dev/ethp/clientcontext/BungeePlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package dev.ethp.clientcontext;

import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextManager;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;

import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

public class BungeePlugin extends Plugin {
private Set<ContextCalculator<ProxiedPlayer>> calculators;
private ContextManager manager;

@Override
public void onEnable() {
this.calculators = new HashSet<>();
this.manager = LuckPermsProvider.get().getContextManager();

// Register contexts.
registerClientTypeCalculator();
registerClientVersionCalculator();

// Register listener to forward contexts to Bukkit servers.
ProxyServer server = getProxy();
server.registerChannel(Constants.CHANNEL);
server.getPluginManager().registerListener(this, new ContextSync(this, this.calculators));
}

@Override
public void onDisable() {
// Unregister contexts.
for (var calculator : this.calculators) {
this.manager.unregisterCalculator(calculator);
}

// Unregister listeners.
ProxyServer server = getProxy();
server.unregisterChannel(Constants.CHANNEL);
server.getPluginManager().unregisterListeners(this);
}

private void registerCalculator(ContextCalculator<ProxiedPlayer> calculator) {
this.manager.registerCalculator(calculator);
this.calculators.add(calculator);
}

protected void registerClientTypeCalculator() {
try {
registerCalculator(new ClientTypeContextViaFloodgate());
getLogger().info("Using Floodgate for client type detection.");
return;
} catch (Throwable t) {
}

try {
registerCalculator(new ClientTypeContextViaGeyser());
getLogger().info("Using Geyser for client type detection.");
return;
} catch (Throwable t) {
}

registerCalculator(new ClientTypeContextFallback());
getLogger().info("Using fallback for client type detection.");
getLogger().warning("All clients will appear as Java clients.");
}

protected void registerClientVersionCalculator() {
try {
VersionResolver resolver = new VersionResolver();
registerCalculator(new ClientVersionContext(resolver));
} catch (ReflectiveOperationException t) {
getLogger().log(Level.WARNING, "Unable to generate client version map.", t);
} catch (Throwable t) {
getLogger().log(Level.WARNING, "Unable to register client version context.", t);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.ethp.clientcontext;

import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextConsumer;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.jetbrains.annotations.NotNull;

/**
* A fallback LuckPerms {@link ContextCalculator} that always specifies "java" as the client type.
* This is used when Geyser fails to load.
*/
public class ClientTypeContextFallback implements ContextCalculator<ProxiedPlayer> {

@Override
public void calculate(ProxiedPlayer target, ContextConsumer contextConsumer) {
contextConsumer.accept("client-type", "java");
}

@Override
public @NotNull ContextSet estimatePotentialContexts() {
ImmutableContextSet.Builder builder = ImmutableContextSet.builder();

builder.add("client-type", "java");

return builder.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.ethp.clientcontext;

import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextConsumer;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.geysermc.floodgate.api.FloodgateApi;
import org.jetbrains.annotations.NotNull;

/**
* A LuckPerms {@link ContextCalculator} that checks if the player is connected to the server as a Java client, or
* as a Bedrock client via GeyserMC. This requires Floodgate to be loaded.
*/
public class ClientTypeContextViaFloodgate implements ContextCalculator<ProxiedPlayer> {

@Override
public void calculate(ProxiedPlayer target, ContextConsumer contextConsumer) {
FloodgateApi api = FloodgateApi.getInstance();
contextConsumer.accept("client-type", api.isFloodgatePlayer(target.getUniqueId()) ? "bedrock" : "java");
}

@Override
public @NotNull ContextSet estimatePotentialContexts() {
ImmutableContextSet.Builder builder = ImmutableContextSet.builder();

builder.add("client-type", "java");
builder.add("client-type", "bedrock");

return builder.build();
}

}
Loading

0 comments on commit d76117f

Please sign in to comment.