diff --git a/docs/api/networking.md b/docs/api/networking.md new file mode 100644 index 0000000..dddc800 --- /dev/null +++ b/docs/api/networking.md @@ -0,0 +1,99 @@ +# Networking API + +The CoreProtect Networking API allows clients to receive data using packets. + +| Networking Details | | +|-------------------------|--------| +| **Networking Version:** | 1 | +| **Plugin Version:** | v21.3+ | + +--- + +## Packets + +The server will not respond unless the player has the correct permission, which is `coreprotect.networking`. + +--- + +## Server to Client + +### Data Packet +Sends data from the database. + +* Channel: `coreprotect:data` + +| Type: `Int` | 1 | 2 | 3 | 4 | +|-------------|------------------------|------------------------|--------------------|--------------------| +| | Time: `long` | Time: `long` | Time: `long` | Time: `long` | +| | Phrase selector: `UTF` | Phrase selector: `UTF` | Result User: `UTF` | Result User: `UTF` | +| | Result User: `UTF` | Result User: `UTF` | Message: `UTF` | Target: `UTF` | +| | Target: `UTF` | Amount: `Int` | Sign: `Boolean` | | +| | Amount: `Int` | X: `Int` | X: `Int` | | +| | X: `Int` | Y: `Int` | Y: `Int` | | +| | Y: `Int` | Z: `Int` | Z: `Int` | | +| | Z: `Int` | World name: `UTF` | World name: `UTF` | | +| | World name: `UTF` | | | | +| | Rolledback: `Boolean` | | | | +| | isContainer: `Boolean` | | | | +| | Added: `Boolean` | | | | + +Example (Fabric): +``` +ByteArrayInputStream in = new ByteArrayInputStream(buf.getWrittenBytes()); +DataInputStream dis = new DataInputStream(in); +int type = dis.readInt(); +long time = dis.readLong(); +String selector = dis.readUTF(); +String resultUser = dis.readUTF(); +String target = dis.readUTF(); +int amount = dis.readInt(); +int x = dis.readInt(); +int y = dis.readInt(); +int z = dis.readInt(); +String worldName = dis.readUTF(); +boolean rolledback = dis.readBoolean(); +boolean isContainer = dis.readBoolean(); +boolean added = dis.readBoolean(); +``` + +### Handshake Packet +Sends handshake if player is registered. + +* Channel: `coreprotect:handshake` +* Registered: `Boolean` + +--- + +## Client to Server + +### Handshake Packet +Sends handshake to register + +* Channel: `coreprotect:handshake` +* Mod Version: `UTF` +* Mod Id: `UTF` +* CoreProtect Protocol: `Int` + +Example (Fabric): +``` +PacketByteBuf packetByteBuf = new PacketByteBuf(Unpooled.buffer()); +ByteArrayOutputStream msgBytes = new ByteArrayOutputStream(); +DataOutputStream msgOut = new DataOutputStream(msgBytes); +msgOut.writeUTF(modVersion); +msgOut.writeUTF(modId); +msgOut.writeInt(coreprotectProtocol); +packetByteBuf.writeBytes(msgBytes.toByteArray()); +``` + +--- + +## Debugging + +### /co network-debug +Allows you to debug the networking API if you are registered and have correct permissions. +To utilize the command, `network-debug: true` must be set in the CoreProtect `config.yml`. + +**Example** +`/co network-debug ` + +___ \ No newline at end of file diff --git a/docs/permissions.md b/docs/permissions.md index a7567e8..a70f719 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -38,6 +38,9 @@ The following permissions can be used to restrict functionality within the plugi   * **coreprotect.consumer** *(default: op)* Allows access to the CoreProtect consumer command. +   +* **coreprotect.networking** *(default: op)* + Allows access to the CoreProtect networking API. --- diff --git a/lang/en.yml b/lang/en.yml index 2bfb983..d2fcd5d 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -125,6 +125,8 @@ MISSING_PARAMETERS: "Please use \"{0}\"." MISSING_ROLLBACK_RADIUS: "You did not specify a {rollback|restore} radius." MISSING_ROLLBACK_USER: "You did not specify a {rollback|restore} user." MYSQL_UNAVAILABLE: "Unable to connect to MySQL server." +NETWORK_CONNECTION: "Connection by {0} {successful|failed}. Using {1} {2}." +NETWORK_TEST: "Network test data has been successful sent." NO_DATA: "No data found at {0}." NO_DATA_LOCATION: "No {data|transactions|interactions|messages} found at this location." NO_PERMISSION: "You do not have permission to do that." diff --git a/src/main/java/net/coreprotect/CoreProtect.java b/src/main/java/net/coreprotect/CoreProtect.java index 3005fa5..21ac542 100755 --- a/src/main/java/net/coreprotect/CoreProtect.java +++ b/src/main/java/net/coreprotect/CoreProtect.java @@ -14,7 +14,6 @@ import net.coreprotect.config.Config; import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Consumer; import net.coreprotect.consumer.process.Process; -import net.coreprotect.database.Database; import net.coreprotect.language.Language; import net.coreprotect.language.Phrase; import net.coreprotect.listener.ListenerHandler; @@ -200,7 +199,7 @@ public final class CoreProtect extends JavaPlugin { Thread.sleep(100); } - Database.closeConnection(); + ConfigHandler.performDisable(); Chat.console(Phrase.build(Phrase.DISABLE_SUCCESS, "CoreProtect v" + plugin.getDescription().getVersion())); } catch (Exception e) { diff --git a/src/main/java/net/coreprotect/command/CommandHandler.java b/src/main/java/net/coreprotect/command/CommandHandler.java index dfde176..596a4be 100755 --- a/src/main/java/net/coreprotect/command/CommandHandler.java +++ b/src/main/java/net/coreprotect/command/CommandHandler.java @@ -1196,6 +1196,9 @@ public class CommandHandler implements CommandExecutor { else if (user.hasPermission("coreprotect.consumer") && corecommand.equals("consumer")) { permission = true; } + else if (user.hasPermission("coreprotect.networking") && corecommand.equals("network-debug")) { + permission = true; + } } if (corecommand.equals("rollback") || corecommand.equals("restore") || corecommand.equals("rb") || corecommand.equals("rs") || corecommand.equals("ro") || corecommand.equals("re")) { @@ -1237,6 +1240,9 @@ public class CommandHandler implements CommandExecutor { else if (corecommand.equals("consumer")) { ConsumerCommand.runCommand(user, permission, argumentArray); } + else if (corecommand.equals("network-debug")) { + NetworkDebugCommand.runCommand(user, permission, argumentArray); + } else { Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.COMMAND_NOT_FOUND, Color.WHITE, "/co " + corecommand)); } diff --git a/src/main/java/net/coreprotect/command/LookupCommand.java b/src/main/java/net/coreprotect/command/LookupCommand.java index d079e35..cda9b6a 100755 --- a/src/main/java/net/coreprotect/command/LookupCommand.java +++ b/src/main/java/net/coreprotect/command/LookupCommand.java @@ -34,6 +34,8 @@ import net.coreprotect.database.lookup.SignMessageLookup; import net.coreprotect.database.statement.UserStatement; import net.coreprotect.language.Phrase; import net.coreprotect.language.Selector; +import net.coreprotect.listener.channel.PluginChannelHandshakeListener; +import net.coreprotect.listener.channel.PluginChannelListener; import net.coreprotect.utility.Chat; import net.coreprotect.utility.ChatMessage; import net.coreprotect.utility.Color; @@ -832,6 +834,13 @@ public class LookupCommand { String message = data[2]; String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true); Chat.sendComponent(player2, timeago + " " + Color.WHITE + "- " + Color.DARK_AQUA + dplayer + ": " + Color.WHITE, message); + if (PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(player2)) { + int wid = Integer.parseInt(data[3]); + int x = Integer.parseInt(data[4]); + int y = Integer.parseInt(data[5]); + int z = Integer.parseInt(data[6]); + PluginChannelListener.getInstance().sendMessageData(player2, Integer.parseInt(time), dplayer, message, false, x, y, z, wid); + } } } else if (finalArgAction.contains(8)) { // login/logouts @@ -856,6 +865,7 @@ public class LookupCommand { String tag = (action != 0 ? Color.GREEN + "+" : Color.RED + "-"); Chat.sendComponent(player2, timeago + " " + tag + " " + Color.DARK_AQUA + Phrase.build(Phrase.LOOKUP_LOGIN, Color.DARK_AQUA + dplayer + Color.WHITE, (action != 0 ? Selector.FIRST : Selector.SECOND))); Chat.sendComponent(player2, Color.WHITE + leftPadding + Color.GREY + "^ " + Util.getCoordinates(command.getName(), wid, x, y, z, true, true) + ""); + PluginChannelListener.getInstance().sendInfoData(player2, Integer.parseInt(time), Phrase.LOOKUP_LOGIN, (action != 0 ? Selector.FIRST : Selector.SECOND), dplayer, -1, x, y, z, wid); } } else if (finalArgAction.contains(9)) { // username-changes @@ -865,6 +875,7 @@ public class LookupCommand { String username = data[2]; String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true); Chat.sendComponent(player2, timeago + " " + Color.WHITE + "- " + Phrase.build(Phrase.LOOKUP_USERNAME, Color.DARK_AQUA + user + Color.WHITE, Color.DARK_AQUA + username + Color.WHITE)); + PluginChannelListener.getInstance().sendUsernameData(player2, Integer.parseInt(time), user, username); } } else if (finalArgAction.contains(10)) { // sign messages @@ -888,6 +899,7 @@ public class LookupCommand { Chat.sendComponent(player2, timeago + " " + Color.WHITE + "- " + Color.DARK_AQUA + dplayer + ": " + Color.WHITE, message); Chat.sendComponent(player2, Color.WHITE + leftPadding + Color.GREY + "^ " + Util.getCoordinates(command.getName(), wid, x, y, z, true, true) + ""); + PluginChannelListener.getInstance().sendMessageData(player2, Integer.parseInt(time), dplayer, message, true, x, y, z, wid); } } else if (finalArgAction.contains(4) && finalArgAction.contains(11)) { // inventory transactions @@ -898,6 +910,10 @@ public class LookupCommand { int ddata = Integer.parseInt(data[6]); int daction = Integer.parseInt(data[7]); int amount = Integer.parseInt(data[10]); + int wid = Integer.parseInt(data[9]); + int x = Integer.parseInt(data[2]); + int y = Integer.parseInt(data[3]); + int z = Integer.parseInt(data[4]); String rbd = ((Integer.parseInt(data[8]) == 2 || Integer.parseInt(data[8]) == 3) ? Color.STRIKETHROUGH : ""); String timeago = Util.getTimeSince(Integer.parseInt(time), unixtimestamp, true); Material blockType = Util.itemFilter(Util.getType(Integer.parseInt(dtype)), (Integer.parseInt(data[13]) == 0)); @@ -931,6 +947,7 @@ public class LookupCommand { } Chat.sendComponent(player2, timeago + " " + tag + " " + Phrase.build(Phrase.LOOKUP_CONTAINER, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, "x" + amount, Color.DARK_AQUA + rbd + dname + Color.WHITE, selector)); + PluginChannelListener.getInstance().sendData(player2, Integer.parseInt(time), Phrase.LOOKUP_CONTAINER, selector, dplayer, dname, amount, x, y, z, wid, rbd, true, tag.contains("+")); } } else { @@ -1024,6 +1041,7 @@ public class LookupCommand { } Chat.sendComponent(player2, timeago + " " + tag + " " + Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, "x" + amount, Color.DARK_AQUA + rbd + dname + Color.WHITE, selector)); + PluginChannelListener.getInstance().sendData(player2, Integer.parseInt(time), phrase, selector, dplayer, dname, (tag.contains("+") ? 1 : -1), x, y, z, wid, rbd, action.contains("container"), tag.contains("+")); } else { if (daction == 2 || daction == 3) { @@ -1039,6 +1057,7 @@ public class LookupCommand { } Chat.sendComponent(player2, timeago + " " + tag + " " + Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, Color.DARK_AQUA + rbd + dname + Color.WHITE, selector)); + PluginChannelListener.getInstance().sendData(player2, Integer.parseInt(time), phrase, selector, dplayer, dname, (tag.contains("+") ? 1 : -1), x, y, z, wid, rbd, false, tag.contains("+")); } action = (finalArgAction.size() == 0 ? " (" + action + ")" : ""); diff --git a/src/main/java/net/coreprotect/command/NetworkDebugCommand.java b/src/main/java/net/coreprotect/command/NetworkDebugCommand.java new file mode 100644 index 0000000..1322eff --- /dev/null +++ b/src/main/java/net/coreprotect/command/NetworkDebugCommand.java @@ -0,0 +1,25 @@ +package net.coreprotect.command; + +import org.bukkit.command.CommandSender; + +import net.coreprotect.config.Config; +import net.coreprotect.language.Phrase; +import net.coreprotect.listener.channel.PluginChannelListener; +import net.coreprotect.utility.Chat; +import net.coreprotect.utility.Color; + +public class NetworkDebugCommand { + protected static void runCommand(CommandSender player, boolean permission, String[] args) { + if (!permission || !Config.getGlobal().NETWORK_DEBUG) { + Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION)); + return; + } + + try { + PluginChannelListener.getInstance().sendTest(player, args.length == 2 ? args[1] : ""); + } + catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/net/coreprotect/config/Config.java b/src/main/java/net/coreprotect/config/Config.java index 6e9a30b..e58bd0e 100644 --- a/src/main/java/net/coreprotect/config/Config.java +++ b/src/main/java/net/coreprotect/config/Config.java @@ -48,6 +48,7 @@ public class Config extends Language { public boolean LOG_CANCELLED_CHAT; public boolean HOPPER_FILTER_META; public boolean EXCLUDE_TNT; + public boolean NETWORK_DEBUG; public boolean MYSQL; public boolean CHECK_UPDATES; public boolean API_ENABLED; @@ -189,6 +190,7 @@ public class Config extends Language { this.LOG_CANCELLED_CHAT = this.getBoolean("log-cancelled-chat", true); this.HOPPER_FILTER_META = this.getBoolean("hopper-filter-meta", false); this.EXCLUDE_TNT = this.getBoolean("exclude-tnt", false); + this.NETWORK_DEBUG = this.getBoolean("network-debug", false); this.DONATION_KEY = this.getString("donation-key"); this.MYSQL = this.getBoolean("use-mysql"); this.PREFIX = this.getString("table-prefix"); diff --git a/src/main/java/net/coreprotect/config/ConfigHandler.java b/src/main/java/net/coreprotect/config/ConfigHandler.java index a718295..85c8180 100644 --- a/src/main/java/net/coreprotect/config/ConfigHandler.java +++ b/src/main/java/net/coreprotect/config/ConfigHandler.java @@ -28,6 +28,7 @@ import net.coreprotect.consumer.Queue; import net.coreprotect.database.Database; import net.coreprotect.database.statement.UserStatement; import net.coreprotect.language.Phrase; +import net.coreprotect.listener.ListenerHandler; import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; import net.coreprotect.patch.Patch; @@ -411,7 +412,7 @@ public class ConfigHandler extends Queue { ConfigHandler.loadConfig(); // Load (or create) the configuration file. ConfigHandler.loadDatabase(); // Initialize MySQL and create tables if necessary. - + ListenerHandler.registerNetworking(); // Register channels for networking API } catch (Exception e) { e.printStackTrace(); @@ -455,4 +456,14 @@ public class ConfigHandler extends Queue { return false; } + public static void performDisable() { + try { + Database.closeConnection(); + ListenerHandler.unregisterNetworking(); // Unregister channels for networking API + } + catch (Exception e) { + e.printStackTrace(); + } + } + } diff --git a/src/main/java/net/coreprotect/database/Lookup.java b/src/main/java/net/coreprotect/database/Lookup.java index de483f0..2883595 100755 --- a/src/main/java/net/coreprotect/database/Lookup.java +++ b/src/main/java/net/coreprotect/database/Lookup.java @@ -22,6 +22,7 @@ import net.coreprotect.consumer.Consumer; import net.coreprotect.consumer.Queue; import net.coreprotect.database.logger.ItemLogger; import net.coreprotect.database.statement.UserStatement; +import net.coreprotect.listener.channel.PluginChannelHandshakeListener; import net.coreprotect.thread.CacheHandler; import net.coreprotect.utility.Util; @@ -147,6 +148,13 @@ public class Lookup extends Queue { String resultMessage = results.getString("message"); Object[] dataArray = new Object[] { resultId, resultTime, resultUserId, resultMessage }; + if (PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(user)) { + int resultWorldId = results.getInt("wid"); + int resultX = results.getInt("x"); + int resultY = results.getInt("y"); + int resultZ = results.getInt("z"); + dataArray = new Object[] { resultId, resultTime, resultUserId, resultMessage, resultWorldId, resultX, resultY, resultZ }; + } list.add(dataArray); } else if (actionList.contains(8)) { @@ -619,6 +627,9 @@ public class Lookup extends Queue { else if (actionList.contains(6) || actionList.contains(7)) { queryTable = "chat"; rows = "rowid as id,time,user,message"; + if (PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(user)) { + rows += ",wid,x,y,z"; + } if (actionList.contains(7)) { queryTable = "command"; diff --git a/src/main/java/net/coreprotect/database/lookup/BlockLookup.java b/src/main/java/net/coreprotect/database/lookup/BlockLookup.java index 7aa9e11..26147aa 100644 --- a/src/main/java/net/coreprotect/database/lookup/BlockLookup.java +++ b/src/main/java/net/coreprotect/database/lookup/BlockLookup.java @@ -12,6 +12,7 @@ import net.coreprotect.config.ConfigHandler; import net.coreprotect.database.statement.UserStatement; import net.coreprotect.language.Phrase; import net.coreprotect.language.Selector; +import net.coreprotect.listener.channel.PluginChannelListener; import net.coreprotect.utility.Color; import net.coreprotect.utility.Util; @@ -130,6 +131,7 @@ public class BlockLookup { } resultTextBuilder.append(timeAgo + " " + tag + " ").append(Phrase.build(phrase, Color.DARK_AQUA + rbFormat + resultUser + Color.WHITE + rbFormat, Color.DARK_AQUA + rbFormat + target + Color.WHITE, selector)).append("\n"); + PluginChannelListener.getInstance().sendData(commandSender, resultTime, phrase, selector, resultUser, target, -1, x, y, z, worldId, rbFormat, false, tag.contains("+")); } resultText = resultTextBuilder.toString(); diff --git a/src/main/java/net/coreprotect/database/lookup/ChestTransactionLookup.java b/src/main/java/net/coreprotect/database/lookup/ChestTransactionLookup.java index 2cca09f..90e8fe3 100644 --- a/src/main/java/net/coreprotect/database/lookup/ChestTransactionLookup.java +++ b/src/main/java/net/coreprotect/database/lookup/ChestTransactionLookup.java @@ -12,6 +12,7 @@ import net.coreprotect.config.ConfigHandler; import net.coreprotect.database.statement.UserStatement; import net.coreprotect.language.Phrase; import net.coreprotect.language.Selector; +import net.coreprotect.listener.channel.PluginChannelListener; import net.coreprotect.utility.Color; import net.coreprotect.utility.Util; @@ -117,6 +118,7 @@ public class ChestTransactionLookup { } resultBuilder.append(timeAgo + " " + tag + " ").append(Phrase.build(Phrase.LOOKUP_CONTAINER, Color.DARK_AQUA + rbFormat + resultUser + Color.WHITE + rbFormat, "x" + resultAmount, Color.DARK_AQUA + rbFormat + target + Color.WHITE, selector)).append("\n"); + PluginChannelListener.getInstance().sendData(commandSender, resultTime, Phrase.LOOKUP_CONTAINER, selector, resultUser, target, resultAmount, x, y, z, worldId, rbFormat, true, tag.contains("+")); } result = resultBuilder.toString(); results.close(); diff --git a/src/main/java/net/coreprotect/database/lookup/InteractionLookup.java b/src/main/java/net/coreprotect/database/lookup/InteractionLookup.java index 6ec9683..bcf2ba4 100644 --- a/src/main/java/net/coreprotect/database/lookup/InteractionLookup.java +++ b/src/main/java/net/coreprotect/database/lookup/InteractionLookup.java @@ -12,6 +12,7 @@ import net.coreprotect.config.ConfigHandler; import net.coreprotect.database.statement.UserStatement; import net.coreprotect.language.Phrase; import net.coreprotect.language.Selector; +import net.coreprotect.listener.channel.PluginChannelListener; import net.coreprotect.utility.Color; import net.coreprotect.utility.Util; @@ -108,6 +109,7 @@ public class InteractionLookup { } resultBuilder.append(timeAgo + " " + Color.WHITE + "- ").append(Phrase.build(Phrase.LOOKUP_INTERACTION, Color.DARK_AQUA + rbFormat + resultUser + Color.WHITE + rbFormat, Color.DARK_AQUA + rbFormat + target + Color.WHITE, Selector.FIRST)).append("\n"); + PluginChannelListener.getInstance().sendData(commandSender, resultTime, Phrase.LOOKUP_INTERACTION, Selector.FIRST, resultUser, target, -1, x, y, z, worldId, rbFormat, false, false); } result = resultBuilder.toString(); results.close(); diff --git a/src/main/java/net/coreprotect/database/lookup/SignMessageLookup.java b/src/main/java/net/coreprotect/database/lookup/SignMessageLookup.java index 50a3f38..b400cb9 100644 --- a/src/main/java/net/coreprotect/database/lookup/SignMessageLookup.java +++ b/src/main/java/net/coreprotect/database/lookup/SignMessageLookup.java @@ -12,6 +12,7 @@ import net.coreprotect.config.ConfigHandler; import net.coreprotect.database.statement.UserStatement; import net.coreprotect.language.Phrase; import net.coreprotect.language.Selector; +import net.coreprotect.listener.channel.PluginChannelListener; import net.coreprotect.utility.Color; import net.coreprotect.utility.Util; @@ -109,6 +110,7 @@ public class SignMessageLookup { } found = true; result.add(timeAgo + Color.WHITE + " - " + Color.DARK_AQUA + resultUser + ": " + Color.WHITE + "\n" + message.toString() + Color.WHITE); + PluginChannelListener.getInstance().sendMessageData(commandSender, resultTime, resultUser, message.toString(), true, x, y, z, worldId); } results.close(); diff --git a/src/main/java/net/coreprotect/language/Language.java b/src/main/java/net/coreprotect/language/Language.java index 9e22262..12b0cb0 100644 --- a/src/main/java/net/coreprotect/language/Language.java +++ b/src/main/java/net/coreprotect/language/Language.java @@ -154,6 +154,8 @@ public class Language { phrases.put(Phrase.MISSING_ROLLBACK_RADIUS, "You did not specify a {rollback|restore} radius."); phrases.put(Phrase.MISSING_ROLLBACK_USER, "You did not specify a {rollback|restore} user."); phrases.put(Phrase.MYSQL_UNAVAILABLE, "Unable to connect to MySQL server."); + phrases.put(Phrase.NETWORK_CONNECTION, "Connection by {0} {successful|failed}. Using {1} {2}."); + phrases.put(Phrase.NETWORK_TEST, "Network test data has been successful sent."); phrases.put(Phrase.NO_DATA, "No data found at {0}."); phrases.put(Phrase.NO_DATA_LOCATION, "No {data|transactions|interactions|messages} found at this location."); phrases.put(Phrase.NO_PERMISSION, "You do not have permission to do that."); diff --git a/src/main/java/net/coreprotect/language/Phrase.java b/src/main/java/net/coreprotect/language/Phrase.java index 5ce2b60..c9ea324 100644 --- a/src/main/java/net/coreprotect/language/Phrase.java +++ b/src/main/java/net/coreprotect/language/Phrase.java @@ -3,6 +3,8 @@ package net.coreprotect.language; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import net.coreprotect.utility.ChatMessage; import net.coreprotect.utility.Color; @@ -135,6 +137,8 @@ public enum Phrase { MISSING_ROLLBACK_RADIUS, MISSING_ROLLBACK_USER, MYSQL_UNAVAILABLE, + NETWORK_CONNECTION, + NETWORK_TEST, NO_DATA, NO_DATA_LOCATION, NO_PERMISSION, @@ -287,4 +291,16 @@ public enum Phrase { return output; } + public static String getPhraseSelector(Phrase phrase, String selector) { + String translatedPhrase = phrase.getTranslatedPhrase(); + Pattern phrasePattern = Pattern.compile("(\\{[a-zA-Z| ]+})"); + Matcher patternMatch = phrasePattern.matcher(translatedPhrase); + String match = ""; + if (patternMatch.find()) { + match = patternMatch.group(1); + match = Selector.processSelection(match, selector, ""); + } + + return match; + } } diff --git a/src/main/java/net/coreprotect/listener/ListenerHandler.java b/src/main/java/net/coreprotect/listener/ListenerHandler.java index e0768df..b18753f 100644 --- a/src/main/java/net/coreprotect/listener/ListenerHandler.java +++ b/src/main/java/net/coreprotect/listener/ListenerHandler.java @@ -15,6 +15,8 @@ import net.coreprotect.listener.block.BlockIgniteListener; import net.coreprotect.listener.block.BlockPistonListener; import net.coreprotect.listener.block.BlockPlaceListener; import net.coreprotect.listener.block.BlockSpreadListener; +import net.coreprotect.listener.channel.PluginChannelHandshakeListener; +import net.coreprotect.listener.channel.PluginChannelListener; import net.coreprotect.listener.entity.CreatureSpawnListener; import net.coreprotect.listener.entity.EntityBlockFormListener; import net.coreprotect.listener.entity.EntityChangeBlockListener; @@ -122,6 +124,20 @@ public final class ListenerHandler { pluginManager.registerEvents(new PlayerChatListener(), plugin); } + // Plugin channel events + pluginManager.registerEvents(new PluginChannelListener(), plugin); + } + + public static void registerNetworking() { + CoreProtect.getInstance().getServer().getMessenger().registerIncomingPluginChannel(CoreProtect.getInstance(), PluginChannelHandshakeListener.pluginChannel, new PluginChannelHandshakeListener()); + CoreProtect.getInstance().getServer().getMessenger().registerOutgoingPluginChannel(CoreProtect.getInstance(), PluginChannelHandshakeListener.pluginChannel); + CoreProtect.getInstance().getServer().getMessenger().registerOutgoingPluginChannel(CoreProtect.getInstance(), PluginChannelListener.pluginChannel); + } + + public static void unregisterNetworking() { + CoreProtect.getInstance().getServer().getMessenger().unregisterIncomingPluginChannel(CoreProtect.getInstance(), PluginChannelHandshakeListener.pluginChannel); + CoreProtect.getInstance().getServer().getMessenger().unregisterOutgoingPluginChannel(CoreProtect.getInstance(), PluginChannelHandshakeListener.pluginChannel); + CoreProtect.getInstance().getServer().getMessenger().unregisterOutgoingPluginChannel(CoreProtect.getInstance(), PluginChannelListener.pluginChannel); } } diff --git a/src/main/java/net/coreprotect/listener/channel/PluginChannelHandshakeListener.java b/src/main/java/net/coreprotect/listener/channel/PluginChannelHandshakeListener.java new file mode 100644 index 0000000..d345283 --- /dev/null +++ b/src/main/java/net/coreprotect/listener/channel/PluginChannelHandshakeListener.java @@ -0,0 +1,109 @@ +package net.coreprotect.listener.channel; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.messaging.PluginMessageListener; + +import net.coreprotect.CoreProtect; +import net.coreprotect.config.Config; +import net.coreprotect.language.Phrase; +import net.coreprotect.language.Selector; +import net.coreprotect.utility.Chat; + +public class PluginChannelHandshakeListener implements PluginMessageListener, Listener { + + public static final String pluginChannel = "coreprotect:handshake"; + private final int networkingProtocolVersion = 1; + private final Set pluginChannelPlayers; + private static PluginChannelHandshakeListener instance; + + public PluginChannelHandshakeListener() { + instance = this; + pluginChannelPlayers = new HashSet<>(); + } + + public static PluginChannelHandshakeListener getInstance() { + return instance; + } + + public Set getPluginChannelPlayers() { + return pluginChannelPlayers; + } + + public boolean isPluginChannelPlayer(CommandSender commandSender) { + if (!(commandSender instanceof Player)) { + return false; + } + + return getPluginChannelPlayers().contains(((Player) commandSender).getUniqueId()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + getPluginChannelPlayers().remove(event.getPlayer().getUniqueId()); + } + + @Override + public void onPluginMessageReceived(String s, Player player, byte[] bytes) { + handleHandshake(s, player, bytes); + } + + private void handleHandshake(String channel, Player player, byte[] bytes) { + if (!player.hasPermission("coreprotect.networking")) { + return; + } + + if (!channel.equals(pluginChannel)) { + return; + } + + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + DataInputStream dis = new DataInputStream(in); + + try { + String modVersion = dis.readUTF(); + String modId = dis.readUTF(); + int protocolVersion = dis.readInt(); + if (Config.getGlobal().NETWORK_DEBUG) { + Chat.console(new String(bytes)); + Chat.console(modVersion); + Chat.console(modId); + Chat.console(String.valueOf(protocolVersion)); + } + + if (protocolVersion != networkingProtocolVersion) { + Chat.console(Phrase.build(Phrase.NETWORK_CONNECTION, player.getName(), modId, modVersion, Selector.SECOND)); + return; + } + + getPluginChannelPlayers().add(player.getUniqueId()); + Chat.console(Phrase.build(Phrase.NETWORK_CONNECTION, player.getName(), modId, modVersion, Selector.FIRST)); + + player.sendPluginMessage(CoreProtect.getInstance(), pluginChannel, sendRegistered()); + } + catch (Exception exception) { + Chat.console(exception.toString()); + exception.printStackTrace(); + } + } + + private byte[] sendRegistered() throws IOException { + ByteArrayOutputStream msgBytes = new ByteArrayOutputStream(); + DataOutputStream msgOut = new DataOutputStream(msgBytes); + msgOut.writeBoolean(true); + + return msgBytes.toByteArray(); + } +} diff --git a/src/main/java/net/coreprotect/listener/channel/PluginChannelListener.java b/src/main/java/net/coreprotect/listener/channel/PluginChannelListener.java new file mode 100644 index 0000000..bf75194 --- /dev/null +++ b/src/main/java/net/coreprotect/listener/channel/PluginChannelListener.java @@ -0,0 +1,218 @@ +package net.coreprotect.listener.channel; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Random; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; + +import net.coreprotect.CoreProtect; +import net.coreprotect.config.Config; +import net.coreprotect.language.Phrase; +import net.coreprotect.language.Selector; +import net.coreprotect.utility.Chat; +import net.coreprotect.utility.Color; +import net.coreprotect.utility.Util; + +public class PluginChannelListener implements Listener { + + public static final String pluginChannel = "coreprotect:data"; + private static PluginChannelListener instance; + + public PluginChannelListener() { + instance = this; + } + + public static PluginChannelListener getInstance() { + return instance; + } + + public void sendData(CommandSender commandSender, long timeAgo, Phrase phrase, String selector, String resultUser, String target, int amount, int x, int y, int z, int worldId, String rbFormat, boolean isContainer, boolean added) throws IOException { + if (!PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(commandSender)) { + return; + } + + String phraseSelector = Phrase.getPhraseSelector(phrase, selector); + String worldName = Util.getWorldName(worldId); + + ByteArrayOutputStream msgBytes = new ByteArrayOutputStream(); + DataOutputStream msgOut = new DataOutputStream(msgBytes); + msgOut.writeInt(1); + msgOut.writeLong(timeAgo * 1000); + msgOut.writeUTF(phraseSelector); + msgOut.writeUTF(resultUser); + msgOut.writeUTF(target); + msgOut.writeInt(amount); + msgOut.writeInt(x); + msgOut.writeInt(y); + msgOut.writeInt(z); + msgOut.writeUTF(worldName); + msgOut.writeBoolean(!rbFormat.isEmpty()); + msgOut.writeBoolean(isContainer); + msgOut.writeBoolean(added); + if (Config.getGlobal().NETWORK_DEBUG) { + Chat.console(String.valueOf(timeAgo * 1000)); + Chat.console(phraseSelector); + Chat.console(resultUser); + Chat.console(target); + Chat.console(String.valueOf(amount)); + Chat.console(String.valueOf(x)); + Chat.console(String.valueOf(y)); + Chat.console(String.valueOf(z)); + Chat.console(worldName); + Chat.console(String.valueOf(!rbFormat.isEmpty())); + Chat.console(String.valueOf(isContainer)); + Chat.console(String.valueOf(added)); + Chat.console(""); + } + + send(commandSender, msgBytes.toByteArray()); + } + + public void sendInfoData(CommandSender commandSender, long timeAgo, Phrase phrase, String selector, String resultUser, int amount, int x, int y, int z, int worldId) throws IOException { + if (!PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(commandSender)) { + return; + } + + String phraseSelector = Phrase.getPhraseSelector(phrase, selector); + String worldName = Util.getWorldName(worldId); + + ByteArrayOutputStream msgBytes = new ByteArrayOutputStream(); + DataOutputStream msgOut = new DataOutputStream(msgBytes); + + msgOut.writeInt(2); + msgOut.writeLong(timeAgo * 1000); + msgOut.writeUTF(phraseSelector); + msgOut.writeUTF(resultUser); + msgOut.writeInt(amount); + msgOut.writeInt(x); + msgOut.writeInt(y); + msgOut.writeInt(z); + msgOut.writeUTF(worldName); + if (Config.getGlobal().NETWORK_DEBUG) { + Chat.console(String.valueOf(timeAgo * 1000)); + Chat.console(phraseSelector); + Chat.console(resultUser); + Chat.console(String.valueOf(amount)); + Chat.console(String.valueOf(x)); + Chat.console(String.valueOf(y)); + Chat.console(String.valueOf(z)); + Chat.console(worldName); + Chat.console(""); + } + + send(commandSender, msgBytes.toByteArray()); + } + + public void sendMessageData(CommandSender commandSender, long timeAgo, String resultUser, String message, boolean sign, int x, int y, int z, int worldId) throws IOException { + if (!PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(commandSender)) { + return; + } + + String worldName = Util.getWorldName(worldId); + + ByteArrayOutputStream msgBytes = new ByteArrayOutputStream(); + DataOutputStream msgOut = new DataOutputStream(msgBytes); + + msgOut.writeInt(3); + msgOut.writeLong(timeAgo * 1000); + msgOut.writeUTF(resultUser); + msgOut.writeUTF(message); + msgOut.writeBoolean(sign); + msgOut.writeInt(x); + msgOut.writeInt(y); + msgOut.writeInt(z); + msgOut.writeUTF(worldName); + if (Config.getGlobal().NETWORK_DEBUG) { + Chat.console(String.valueOf(timeAgo * 1000)); + Chat.console(resultUser); + Chat.console(message); + Chat.console(String.valueOf(sign)); + Chat.console(String.valueOf(x)); + Chat.console(String.valueOf(y)); + Chat.console(String.valueOf(z)); + Chat.console(worldName); + Chat.console(""); + } + + send(commandSender, msgBytes.toByteArray()); + } + + public void sendUsernameData(CommandSender commandSender, long timeAgo, String resultUser, String target) throws IOException { + if (!PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(commandSender)) { + return; + } + + ByteArrayOutputStream msgBytes = new ByteArrayOutputStream(); + DataOutputStream msgOut = new DataOutputStream(msgBytes); + + msgOut.writeInt(4); + msgOut.writeLong(timeAgo * 1000); + msgOut.writeUTF(resultUser); + msgOut.writeUTF(target); + + if (Config.getGlobal().NETWORK_DEBUG) { + Chat.console(String.valueOf(timeAgo * 1000)); + Chat.console(resultUser); + Chat.console(target); + Chat.console(""); + } + + send(commandSender, msgBytes.toByteArray()); + } + + public void sendTest(CommandSender commandSender, String type) throws IOException { + if (!PluginChannelHandshakeListener.getInstance().isPluginChannelPlayer(commandSender)) { + return; + } + + int worldId = 1; + Random rand = new Random(); + int timeAgo = rand.nextInt(20); + String selector = Selector.FIRST; + String resultUser = "Anne"; + int amount = 5; + int x = rand.nextInt(10); + int y = rand.nextInt(10); + int z = rand.nextInt(10); + String rbFormat = "test"; + String message = "This is a test"; + boolean sign = true; + + switch (type) { + case "2": + sendInfoData(commandSender, timeAgo, Phrase.LOOKUP_LOGIN, selector, resultUser, amount, x, y, z, worldId); + break; + case "3": + sendMessageData(commandSender, timeAgo, resultUser, message, sign, x, y, z, worldId); + break; + case "4": + sendUsernameData(commandSender, timeAgo, resultUser, "Arne"); + break; + default: + sendData(commandSender, timeAgo, Phrase.LOOKUP_CONTAINER, selector, resultUser, "clay_ball", amount, x, y, z, worldId, rbFormat, false, true); + break; + } + + commandSender.sendMessage(Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NETWORK_TEST)); + } + + private void send(CommandSender commandSender, byte[] msgBytes) { + if (!(commandSender instanceof Player)) { + return; + } + + PluginChannelListener.getInstance().sendCoreProtectData((Player) commandSender, msgBytes); + } + + private void sendCoreProtectData(Player player, byte[] data) { + if (!player.hasPermission("coreprotect.networking")) { + return; + } + + player.sendPluginMessage(CoreProtect.getInstance(), pluginChannel, data); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5ef1269..c5c5a62 100755 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -50,6 +50,8 @@ permissions: coreprotect.teleport: true coreprotect.reload: true coreprotect.status: true + coreprotect.consumer: true + coreprotect.networking: true coreprotect.co: description: Has permission to access the CoreProtect /co command default: true @@ -137,4 +139,7 @@ permissions: default: op coreprotect.consumer: description: Has permission to use the consumer command + default: op + coreprotect.networking: + description: Has permission to use the networking API default: op \ No newline at end of file