├── .gitignore ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── LICENSE ├── bukkit ├── api │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── nz │ │ └── pactifylauncher │ │ └── plugin │ │ └── bukkit │ │ └── api │ │ ├── PactifyAPI.java │ │ └── player │ │ ├── PactifyPlayer.java │ │ └── PlayersService.java └── plugin │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── nz │ │ └── pactifylauncher │ │ └── plugin │ │ └── bukkit │ │ ├── PLSPMessenger.java │ │ ├── PactifyPlugin.java │ │ ├── command │ │ ├── CheckCommand.java │ │ ├── ListCommand.java │ │ └── StatsCommand.java │ │ ├── conf │ │ ├── Conf.java │ │ └── YamlConfProvider.java │ │ ├── player │ │ ├── PPactifyPlayer.java │ │ └── PPlayersService.java │ │ └── util │ │ ├── BukkitUtil.java │ │ ├── PacketOutBuffer.java │ │ └── SchedulerUtil.java │ └── resources │ └── plugin.yml ├── bungee └── plugin │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── nz │ │ └── pactifylauncher │ │ └── plugin │ │ └── bungee │ │ ├── HandshakeFix.java │ │ └── PactifyPlugin.java │ └── resources │ └── bungee.yml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | ### Maven template 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 12 | .mvn/wrapper/maven-wrapper.jar 13 | 14 | ### JetBrains template 15 | *.iml 16 | .idea/* 17 | !.idea/codeStyles 18 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /bukkit/api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.github.pactifylauncherexamples 8 | pactify-plugin 9 | 1.0-SNAPSHOT 10 | ../../pom.xml 11 | 12 | pactify-plugin-bukkit-api 13 | 14 | 15 | ../ 16 | 17 | 18 | 19 | 20 | org.spigotmc 21 | spigot-api 22 | 1.8.8-R0.1-SNAPSHOT 23 | provided 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /bukkit/api/src/main/java/nz/pactifylauncher/plugin/bukkit/api/PactifyAPI.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.api; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import nz.pactifylauncher.plugin.bukkit.api.player.PlayersService; 5 | 6 | /** 7 | * Pactify Launcher API for Bukkit. 8 | */ 9 | @UtilityClass 10 | public class PactifyAPI { 11 | private static Impl implementation; 12 | 13 | /** 14 | * @deprecated You certainly don't want to use this method. 15 | */ 16 | @Deprecated 17 | public static void setImplementation(Impl implementation) { 18 | PactifyAPI.implementation = implementation; 19 | } 20 | 21 | /** 22 | * Returns the {@linkplain PlayersService players service}; containing players-related API methods. 23 | * 24 | * @return Returns the {@linkplain PlayersService players service} 25 | */ 26 | public static PlayersService players() { 27 | return implementation.getPlayersService(); 28 | } 29 | 30 | public interface Impl { 31 | PlayersService getPlayersService(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bukkit/api/src/main/java/nz/pactifylauncher/plugin/bukkit/api/player/PactifyPlayer.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.api.player; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | /** 6 | * Class which represents a {@link Player} with the additional Pactify Plugin methods! 7 | */ 8 | public interface PactifyPlayer { 9 | /** 10 | * Returns the {@linkplain Player Bukkit Player} (cannot be null). 11 | * 12 | * @return the {@linkplain Player Bukkit Player} 13 | */ 14 | Player getPlayer(); 15 | 16 | /** 17 | * Check whether this player is using the Pactify Launcher. 18 | * 19 | * @return {@code true} if this player uses the Pactify Launcher; {@code false} otherwise 20 | */ 21 | boolean hasLauncher(); 22 | 23 | /** 24 | * Returns the used Pactify Launcher protocol version. 25 | * 26 | * @return the protocol version; or zero if this player does not use the Pactify Launcher 27 | */ 28 | int getLauncherProtocolVersion(); 29 | } 30 | -------------------------------------------------------------------------------- /bukkit/api/src/main/java/nz/pactifylauncher/plugin/bukkit/api/player/PlayersService.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.api.player; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.player.AsyncPlayerPreLoginEvent; 5 | import org.bukkit.event.player.PlayerLoginEvent; 6 | 7 | import java.util.UUID; 8 | 9 | /** 10 | * Note: All of these players-related methods only work from the {@link PlayerLoginEvent} (after the LOWEST priority). 11 | * So don't try to call them during the {@link AsyncPlayerPreLoginEvent}! 12 | */ 13 | public interface PlayersService { 14 | /** 15 | * Returns the {@link PactifyPlayer} related to a player. 16 | * 17 | * @param playerUid UUID of the player 18 | * @return the {@link PactifyPlayer} related to the player online with this UUID; or {@code null} if no player is 19 | * online with this UUID 20 | */ 21 | PactifyPlayer getPlayer(UUID playerUid); 22 | 23 | /** 24 | * Returns the {@link PactifyPlayer} related to a player. 25 | * 26 | * @param player player 27 | * @return the {@link PactifyPlayer} related to this player; or {@code null} if this player is not online 28 | */ 29 | PactifyPlayer getPlayer(Player player); 30 | 31 | /** 32 | * Check whether a player is using the Pactify launcher. 33 | * 34 | * @param playerUid UUID of the player to check 35 | * @return {@code true} if a player is online with this UUID and uses the Pactify Launcher; {@code false} otherwise 36 | */ 37 | boolean hasLauncher(UUID playerUid); 38 | 39 | /** 40 | * Check whether a player is using the Pactify launcher. 41 | * 42 | * @param player player to check 43 | * @return {@code true} if the player is online and uses the Pactify Launcher; {@code false} otherwise 44 | */ 45 | boolean hasLauncher(Player player); 46 | } 47 | -------------------------------------------------------------------------------- /bukkit/plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.github.pactifylauncherexamples 8 | pactify-plugin 9 | 1.0-SNAPSHOT 10 | ../../pom.xml 11 | 12 | pactify-plugin-bukkit 13 | 14 | 15 | ../../ 16 | 17 | 18 | 19 | 20 | ${project.groupId} 21 | pactify-plugin-bukkit-api 22 | ${project.version} 23 | compile 24 | 25 | 26 | pactify.client.api 27 | plsp-protocol 28 | 20200201 29 | compile 30 | 31 | 32 | org.spigotmc 33 | spigot-api 34 | 1.8.8-R0.1-SNAPSHOT 35 | provided 36 | 37 | 38 | 39 | 40 | PactifyPlugin-Bukkit-${project.version} 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-shade-plugin 46 | 47 | 48 | package 49 | 50 | shade 51 | 52 | 53 | 54 | 55 | *:* 56 | 57 | META-INF/*.SF 58 | META-INF/*.DSA 59 | META-INF/*.RSA 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | true 72 | src/main/resources 73 | 74 | plugin.yml 75 | 76 | 77 | 78 | src/main/resources 79 | 80 | ** 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/PLSPMessenger.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import nz.pactifylauncher.plugin.bukkit.util.PacketOutBuffer; 5 | import org.bukkit.entity.Player; 6 | import pactify.client.api.mcprotocol.util.NotchianPacketUtil; 7 | import pactify.client.api.plsp.PLSPPacket; 8 | import pactify.client.api.plsp.PLSPPacketHandler; 9 | import pactify.client.api.plsp.PLSPProtocol; 10 | 11 | import java.util.logging.Level; 12 | 13 | @RequiredArgsConstructor 14 | public class PLSPMessenger { 15 | private static final String PLSP_CHANNEL = "PLSP"; 16 | 17 | private final PactifyPlugin plugin; 18 | 19 | public void enable() { 20 | plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, PLSP_CHANNEL); 21 | } 22 | 23 | public void disable() { 24 | plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, PLSP_CHANNEL); 25 | } 26 | 27 | public void sendPLSPMessage(Player player, PLSPPacket message) { 28 | try { 29 | PacketOutBuffer buf = new PacketOutBuffer(); 30 | PLSPProtocol.PacketData packetData = PLSPProtocol.getClientPacketByClass(message.getClass()); 31 | NotchianPacketUtil.writeString(buf, packetData.getId(), Short.MAX_VALUE); 32 | message.write(buf); 33 | player.sendPluginMessage(plugin, PLSP_CHANNEL, buf.toBytes()); 34 | } catch (Exception e) { 35 | plugin.getLogger().log(Level.WARNING, "Exception sending PLSP message to " 36 | + (player != null ? player.getName() : "null") + ":", e); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/PactifyPlugin.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit; 2 | 3 | import lombok.Getter; 4 | import nz.pactifylauncher.plugin.bukkit.api.PactifyAPI; 5 | import nz.pactifylauncher.plugin.bukkit.command.CheckCommand; 6 | import nz.pactifylauncher.plugin.bukkit.command.ListCommand; 7 | import nz.pactifylauncher.plugin.bukkit.command.StatsCommand; 8 | import nz.pactifylauncher.plugin.bukkit.conf.Conf; 9 | import nz.pactifylauncher.plugin.bukkit.conf.YamlConfProvider; 10 | import nz.pactifylauncher.plugin.bukkit.player.PPlayersService; 11 | import nz.pactifylauncher.plugin.bukkit.util.BukkitUtil; 12 | import org.bukkit.plugin.java.JavaPlugin; 13 | 14 | public class PactifyPlugin extends JavaPlugin implements PactifyAPI.Impl { 15 | private @Getter Conf conf; 16 | private final @Getter PLSPMessenger plspMessenger = new PLSPMessenger(this); 17 | private final @Getter PPlayersService playersService = new PPlayersService(this); 18 | private @Getter int serverVersion; 19 | 20 | @Override 21 | public void onLoad() { 22 | // Initialize unsafe utilities (those with reflection) 23 | try { 24 | Class.forName(BukkitUtil.class.getName(), true, getClass().getClassLoader()); 25 | } catch (ClassNotFoundException e) { 26 | throw new RuntimeException(e); 27 | } 28 | 29 | // Read configuration 30 | serverVersion = BukkitUtil.findServerVersion(); 31 | conf = YamlConfProvider.load(this); 32 | // TODO: Dump configuration values? 33 | 34 | PactifyAPI.setImplementation(this); 35 | } 36 | 37 | @Override 38 | public void onEnable() { 39 | plspMessenger.enable(); 40 | playersService.enable(); 41 | registerCommands(); 42 | getLogger().info("Enabled!"); 43 | } 44 | 45 | @Override 46 | public void onDisable() { 47 | playersService.disable(); 48 | plspMessenger.disable(); 49 | getLogger().info("Disabled!"); 50 | } 51 | 52 | private void registerCommands() { 53 | getCommand("pactifycheck").setExecutor(new CheckCommand(this)); 54 | getCommand("pactifylist").setExecutor(new ListCommand(this)); 55 | getCommand("pactifystats").setExecutor(new StatsCommand(this)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/command/CheckCommand.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.command; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import nz.pactifylauncher.plugin.bukkit.PactifyPlugin; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.UUID; 12 | 13 | @RequiredArgsConstructor 14 | public class CheckCommand implements CommandExecutor { 15 | private final PactifyPlugin plugin; 16 | 17 | @Override 18 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 19 | if (args.length == 0) { 20 | sender.sendMessage(ChatColor.RED + "Usage: /pacheck "); 21 | return true; 22 | } 23 | 24 | Player player; 25 | try { 26 | player = plugin.getServer().getPlayer(UUID.fromString(args[0])); 27 | } catch (IllegalArgumentException ignored) { 28 | player = plugin.getServer().getPlayer(args[0]); 29 | } 30 | if (player == null) { 31 | sender.sendMessage(ChatColor.RED + "Player " + args[0] + " is not online"); 32 | return true; 33 | } 34 | 35 | sender.sendMessage(ChatColor.YELLOW + "Player " + player.getName() + " is " 36 | + (plugin.getPlayersService().hasLauncher(player) ? ChatColor.GREEN + "using" : ChatColor.RED + "not using") 37 | + ChatColor.YELLOW + " the Pactify Launcher"); 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/command/ListCommand.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.command; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import nz.pactifylauncher.plugin.bukkit.PactifyPlugin; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @RequiredArgsConstructor 15 | public class ListCommand implements CommandExecutor { 16 | private final PactifyPlugin plugin; 17 | 18 | @Override 19 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 20 | List pactifyList = new ArrayList<>(); 21 | List vanillaList = new ArrayList<>(); 22 | for (Player player : plugin.getServer().getOnlinePlayers()) { 23 | if (plugin.getPlayersService().hasLauncher(player)) { 24 | pactifyList.add(player.getName()); 25 | } else { 26 | vanillaList.add(player.getName()); 27 | } 28 | } 29 | pactifyList.sort(String::compareToIgnoreCase); 30 | vanillaList.sort(String::compareToIgnoreCase); 31 | 32 | sender.sendMessage(ChatColor.YELLOW + "Players using the Pactify Launcher: " 33 | + (pactifyList.isEmpty() ? ChatColor.GRAY + "(none)" : ChatColor.GREEN + String.join(", ", pactifyList))); 34 | sender.sendMessage(ChatColor.YELLOW + "Players not using the Pactify Launcher: " 35 | + (vanillaList.isEmpty() ? ChatColor.GRAY + "(none)" : ChatColor.RED + String.join(", ", vanillaList))); 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/command/StatsCommand.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.command; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import nz.pactifylauncher.plugin.bukkit.PactifyPlugin; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.entity.Player; 10 | 11 | @RequiredArgsConstructor 12 | public class StatsCommand implements CommandExecutor { 13 | private final PactifyPlugin plugin; 14 | 15 | @Override 16 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 17 | int onlineCnt = 0; 18 | int pactifyCnt = 0; 19 | for (Player player : plugin.getServer().getOnlinePlayers()) { 20 | ++onlineCnt; 21 | if (plugin.getPlayersService().hasLauncher(player)) { 22 | ++pactifyCnt; 23 | } 24 | } 25 | 26 | sender.sendMessage(ChatColor.YELLOW + "Players using the Pactify Launcher: " 27 | + ChatColor.GREEN + pactifyCnt + ChatColor.YELLOW + "/" + onlineCnt); 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/conf/Conf.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.conf; 2 | 3 | import lombok.Data; 4 | import lombok.NonNull; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.Singular; 7 | 8 | import java.util.List; 9 | 10 | @lombok.Builder(builderClassName = "Builder") 11 | @Data 12 | public class Conf { 13 | private JoinActions loginWithPactify; 14 | private JoinActions loginWithoutPactify; 15 | 16 | @lombok.Builder(builderClassName = "Builder") 17 | @Data 18 | public static class JoinActions { 19 | private @Singular("message") List messages; 20 | private @Singular("command") List commands; 21 | } 22 | 23 | @RequiredArgsConstructor 24 | @Data 25 | public static class Action { 26 | private final @NonNull String value; 27 | private final int delay; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/conf/YamlConfProvider.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.conf; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import lombok.AccessLevel; 5 | import lombok.RequiredArgsConstructor; 6 | import nz.pactifylauncher.plugin.bukkit.PactifyPlugin; 7 | import org.bukkit.ChatColor; 8 | import org.yaml.snakeyaml.DumperOptions; 9 | import org.yaml.snakeyaml.Yaml; 10 | import org.yaml.snakeyaml.constructor.SafeConstructor; 11 | import org.yaml.snakeyaml.nodes.Node; 12 | import org.yaml.snakeyaml.representer.BaseRepresenter; 13 | import org.yaml.snakeyaml.representer.Representer; 14 | 15 | import java.io.*; 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.Modifier; 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.*; 20 | import java.util.function.Function; 21 | import java.util.logging.Level; 22 | import java.util.stream.Collectors; 23 | 24 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE) 25 | public class YamlConfProvider { 26 | private static final ThreadLocal YAML = ThreadLocal.withInitial(() -> { 27 | DumperOptions options = new DumperOptions(); 28 | options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); 29 | options.setPrettyFlow(false); 30 | options.setIndent(2); 31 | return new Yaml(new SafeConstructor(), new NoAnchorRepresenter(), options); 32 | }); 33 | 34 | private static final String HEADER = "# This is the main configuration file for the Pactify Plugin on Bukkit.\n" 35 | + "# For a reference for any variable inside this file, check out the Pactify Plugin\n" 36 | + "# README: https://github.com/PactifyLauncherExamples/PactifyPlugin\n\n"; 37 | 38 | public static Conf load(PactifyPlugin plugin) { 39 | return new YamlConfProvider(plugin, new File(plugin.getDataFolder(), "config.yml")).loadConfig(); 40 | } 41 | 42 | private final PactifyPlugin plugin; 43 | private final File file; 44 | private final Map root = new LinkedHashMap<>(); 45 | 46 | private Conf loadConfig() { 47 | readValues(); 48 | Conf conf = Conf.builder() 49 | .loginWithPactify(getJoinActions("join.with-pactify", 50 | Collections.singletonList(ImmutableMap.of("delay", 0, "value", "&eYou are connected with the &cPactify Launcher&e!")), 51 | Collections.singletonList(ImmutableMap.of("delay", 20, "value", "/minecraft:effect {{name}} speed 15")))) 52 | .loginWithoutPactify(getJoinActions("join.without-pactify", 53 | Collections.emptyList(), 54 | Collections.emptyList())) 55 | .build(); 56 | writeValues(); 57 | return conf; 58 | } 59 | 60 | private Conf.JoinActions getJoinActions(String path, List defMessages, List defCommands) { 61 | return Conf.JoinActions.builder() 62 | .messages(getActionList(path + ".messages", defMessages, s -> ChatColor.translateAlternateColorCodes('&', s))) 63 | .commands(getActionList(path + ".commands", defCommands, s -> s.startsWith("/") ? s.substring(1) : s)) 64 | .build(); 65 | } 66 | 67 | @SuppressWarnings({"unchecked", "rawtypes"}) 68 | private List getActionList(String path, List def, Function valueTransformer) { 69 | Object value = get(path); 70 | if (!(value instanceof List)) { 71 | value = def; 72 | } 73 | List ret = new ArrayList<>(); 74 | value = ((List) value).stream().map(e -> { 75 | String v; 76 | if ((v = asString(e, null)) != null) { 77 | if (!v.isEmpty()) { 78 | ret.add(new Conf.Action(valueTransformer.apply(v), 0)); 79 | return ImmutableMap.of("delay", 0, "value", v); 80 | } 81 | } else if (e instanceof Map) { 82 | v = asString(((Map) e).get("value"), ""); 83 | if (!v.isEmpty()) { 84 | int delay = asInt(((Map) e).get("delay"), 0); 85 | ret.add(new Conf.Action(valueTransformer.apply(v), delay)); 86 | return ImmutableMap.of("delay", delay, "value", v); 87 | } 88 | } 89 | return null; 90 | }).filter(Objects::nonNull).collect(Collectors.toList()); 91 | set(path, value); 92 | return ret; 93 | } 94 | 95 | private void readValues() { 96 | Map config = null; 97 | if (file.exists()) { 98 | try (Reader rd = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { 99 | //noinspection unchecked 100 | config = (Map) YAML.get().load(rd); 101 | } catch (IOException | ClassCastException e) { 102 | plugin.getLogger().log(Level.SEVERE, 103 | "Could not load " + file + ", please correct your syntax errors", e); 104 | throw new RuntimeException(e); 105 | } 106 | } 107 | root.clear(); 108 | if (config != null && !config.isEmpty()) { 109 | root.putAll(config); 110 | } 111 | } 112 | 113 | private void writeValues() { 114 | String dump = HEADER + YAML.get().dump(root); 115 | 116 | //noinspection ResultOfMethodCallIgnored 117 | file.getParentFile().mkdirs(); 118 | try (Writer wr = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { 119 | wr.write(dump); 120 | } catch (IOException e) { 121 | plugin.getLogger().log(Level.WARNING, "Could not save " + file, e); 122 | } 123 | } 124 | 125 | private void set(String path, Object value) { 126 | Map section = root; 127 | int i; 128 | while ((i = path.indexOf('.')) != -1) { 129 | Object v = section.get(path.substring(0, i)); 130 | if (!(v instanceof Map)) { 131 | section.put(path.substring(0, i), v = new LinkedHashMap()); 132 | } 133 | //noinspection unchecked 134 | section = (Map) v; 135 | path = path.substring(i + 1); 136 | } 137 | section.put(path, value); 138 | } 139 | 140 | private Object get(String path) { 141 | Map section = root; 142 | int i; 143 | while ((i = path.indexOf('.')) != -1) { 144 | Object v = section.get(path.substring(0, i)); 145 | if (!(v instanceof Map)) { 146 | return null; 147 | } 148 | //noinspection unchecked 149 | section = (Map) v; 150 | path = path.substring(i + 1); 151 | } 152 | return section.get(path); 153 | } 154 | 155 | private static int asInt(Object value, int def) { 156 | if (value instanceof Number) { 157 | return ((Number) value).intValue(); 158 | } 159 | return def; 160 | } 161 | 162 | private static String asString(Object value, String def) { 163 | if (value instanceof String) { 164 | return (String) value; 165 | } 166 | if (value instanceof Number) { 167 | return value.toString(); 168 | } 169 | if (value instanceof Boolean) { 170 | return value.toString(); 171 | } 172 | return def; 173 | } 174 | 175 | public static class NoAnchorRepresenter extends Representer { 176 | public NoAnchorRepresenter() { 177 | try { 178 | Field field = BaseRepresenter.class.getDeclaredField("representedObjects"); 179 | field.setAccessible(true); 180 | 181 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 182 | modifiersField.setAccessible(true); 183 | modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 184 | 185 | field.set(this, new HashMap() { 186 | @Override 187 | public Node put(Object k, Node v) { 188 | return null; 189 | } 190 | }); 191 | } catch (NoSuchFieldException | IllegalAccessException e) { 192 | e.printStackTrace(); 193 | } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/player/PPactifyPlayer.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.player; 2 | 3 | import lombok.Data; 4 | import lombok.RequiredArgsConstructor; 5 | import nz.pactifylauncher.plugin.bukkit.api.player.PactifyPlayer; 6 | import nz.pactifylauncher.plugin.bukkit.conf.Conf; 7 | import nz.pactifylauncher.plugin.bukkit.util.BukkitUtil; 8 | import nz.pactifylauncher.plugin.bukkit.util.SchedulerUtil; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.metadata.MetadataValue; 11 | import pactify.client.api.plsp.packet.client.PLSPPacketConfFlag; 12 | import pactify.client.api.plsp.packet.client.PLSPPacketConfFlags; 13 | import pactify.client.api.plsp.packet.client.PLSPPacketReset; 14 | 15 | import java.util.Arrays; 16 | import java.util.HashSet; 17 | import java.util.List; 18 | import java.util.Set; 19 | import java.util.function.Consumer; 20 | import java.util.regex.Matcher; 21 | import java.util.regex.Pattern; 22 | 23 | @RequiredArgsConstructor 24 | @Data 25 | public class PPactifyPlayer implements PactifyPlayer { 26 | private static final Pattern PACTIFY_HOSTNAME_PATTERN = Pattern.compile("[\u0000\u0002]PAC([0-9A-F]{5})[\u0000\u0002]"); 27 | 28 | private final PPlayersService service; 29 | private final Player player; 30 | private final Set scheduledTasks = new HashSet<>(); 31 | private boolean joined; 32 | private int launcherProtocolVersion; 33 | 34 | public void init() { 35 | List hostnameMeta = player.getMetadata("PactifyPlugin:hostname"); 36 | if (!hostnameMeta.isEmpty()) { 37 | String hostname = hostnameMeta.get(0).asString(); 38 | Matcher m = PACTIFY_HOSTNAME_PATTERN.matcher(hostname); 39 | if (m.find()) { 40 | launcherProtocolVersion = Math.max(1, Integer.parseInt(m.group(1), 16)); 41 | } 42 | } else { 43 | service.getPlugin().getLogger().warning("Unable to verify the launcher of " + player.getName() 44 | + ": it probably logged when the plugin was disabled!"); 45 | } 46 | 47 | BukkitUtil.addChannel(player, "PLSP"); // Register the PLSP channel for the Player.sendPluginMessage calls 48 | } 49 | 50 | public void join() { 51 | joined = true; 52 | 53 | // This client can come from another server if BungeeCord is used, so we reset it to ensure a clean state! 54 | service.getPlugin().getPlspMessenger().sendPLSPMessage(player, new PLSPPacketReset()); 55 | 56 | // Send client capabilities 57 | // TODO: Add config and API 58 | int sv = service.getPlugin().getServerVersion(); 59 | boolean attackCooldown; 60 | boolean playerPush; 61 | boolean largeHitbox; 62 | boolean swordBlocking; 63 | boolean hitAndBlock; 64 | if (sv >= 1_009_000) { // >= 1.9.0 65 | attackCooldown = true; 66 | playerPush = true; 67 | largeHitbox = false; 68 | swordBlocking = false; 69 | hitAndBlock = false; 70 | } else { // < 1.9.0 71 | attackCooldown = false; 72 | playerPush = false; 73 | largeHitbox = true; 74 | swordBlocking = true; 75 | hitAndBlock = (sv < 1_008_000); // < 1.8.0 76 | } 77 | service.getPlugin().getPlspMessenger().sendPLSPMessage(player, new PLSPPacketConfFlags(Arrays.asList( 78 | new PLSPPacketConfFlag("attack_cooldown", attackCooldown), 79 | new PLSPPacketConfFlag("player_push", playerPush), 80 | new PLSPPacketConfFlag("large_hitbox", largeHitbox), 81 | new PLSPPacketConfFlag("sword_blocking", swordBlocking), 82 | new PLSPPacketConfFlag("hit_and_block", hitAndBlock) 83 | ))); 84 | } 85 | 86 | public void free(boolean onQuit) { 87 | SchedulerUtil.cancelTasks(service.getPlugin(), scheduledTasks); 88 | if (!onQuit) { 89 | service.getPlugin().getPlspMessenger().sendPLSPMessage(player, new PLSPPacketReset()); 90 | } 91 | } 92 | 93 | public void doJoinActions(Conf.JoinActions actions) { 94 | if (actions != null) { 95 | doJoinActions(actions.getMessages(), player::sendMessage); 96 | doJoinActions(actions.getCommands(), action -> { 97 | service.getPlugin().getServer().dispatchCommand( 98 | service.getPlugin().getServer().getConsoleSender(), 99 | action.replace("{{name}}", player.getName()) 100 | .replace("{{uuid}}", player.getUniqueId().toString())); 101 | }); 102 | } 103 | } 104 | 105 | private void doJoinActions(List actions, Consumer handler) { 106 | if (actions != null) { 107 | for (Conf.Action action : actions) { 108 | if (action.getDelay() <= 0) { 109 | handler.accept(action.getValue()); 110 | } else { 111 | SchedulerUtil.scheduleSyncDelayedTask(service.getPlugin(), scheduledTasks, 112 | () -> handler.accept(action.getValue()), action.getDelay()); 113 | } 114 | } 115 | } 116 | } 117 | 118 | @Override 119 | public boolean hasLauncher() { 120 | return launcherProtocolVersion > 0; 121 | } 122 | 123 | @Override 124 | public int getLauncherProtocolVersion() { 125 | return launcherProtocolVersion; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/player/PPlayersService.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.player; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import nz.pactifylauncher.plugin.bukkit.PactifyPlugin; 6 | import nz.pactifylauncher.plugin.bukkit.api.player.PactifyPlayer; 7 | import nz.pactifylauncher.plugin.bukkit.api.player.PlayersService; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.EventPriority; 11 | import org.bukkit.event.HandlerList; 12 | import org.bukkit.event.Listener; 13 | import org.bukkit.event.player.PlayerJoinEvent; 14 | import org.bukkit.event.player.PlayerLoginEvent; 15 | import org.bukkit.event.player.PlayerQuitEvent; 16 | import org.bukkit.event.server.PluginDisableEvent; 17 | import org.bukkit.metadata.FixedMetadataValue; 18 | 19 | import java.util.HashMap; 20 | import java.util.Iterator; 21 | import java.util.Map; 22 | import java.util.UUID; 23 | import java.util.logging.Level; 24 | 25 | @RequiredArgsConstructor 26 | public class PPlayersService implements PlayersService, Listener { 27 | private final @Getter PactifyPlugin plugin; 28 | private final Map players = new HashMap<>(); 29 | 30 | public void enable() { 31 | // Register events 32 | plugin.getServer().getPluginManager().registerEvents(this, plugin); 33 | 34 | // (Re-)Initialize already connected players 35 | for (Player player : plugin.getServer().getOnlinePlayers()) { 36 | PPactifyPlayer pactifyPlayer; 37 | players.put(player.getUniqueId(), pactifyPlayer = new PPactifyPlayer(this, player)); 38 | try { 39 | pactifyPlayer.init(); 40 | pactifyPlayer.join(); 41 | } catch (Throwable t) { 42 | plugin.getLogger().log(Level.WARNING, "An error occurred initializing " + player.getName() + ":", t); 43 | } 44 | } 45 | } 46 | 47 | public void disable() { 48 | // Unregister events 49 | HandlerList.unregisterAll(this); 50 | } 51 | 52 | @Override 53 | public PPactifyPlayer getPlayer(UUID playerUid) { 54 | return players.get(playerUid); 55 | } 56 | 57 | @Override 58 | public PPactifyPlayer getPlayer(Player player) { 59 | return player == null ? null : getPlayer(player.getUniqueId()); 60 | } 61 | 62 | @Override 63 | public boolean hasLauncher(UUID playerUid) { 64 | PactifyPlayer pactifyPlayer = getPlayer(playerUid); 65 | return pactifyPlayer != null && pactifyPlayer.hasLauncher(); 66 | } 67 | 68 | @Override 69 | public boolean hasLauncher(Player player) { 70 | PactifyPlayer pactifyPlayer = getPlayer(player); 71 | return pactifyPlayer != null && pactifyPlayer.hasLauncher(); 72 | } 73 | 74 | @EventHandler(priority = EventPriority.LOWEST) 75 | public void onPlayerLogin(PlayerLoginEvent event) { 76 | // Store the hostname in a metadata to keep it between reloads 77 | event.getPlayer().setMetadata("PactifyPlugin:hostname", new FixedMetadataValue(plugin, event.getHostname())); 78 | 79 | // Then initialize the PactifyPlayer instance 80 | PPactifyPlayer pactifyPlayer; 81 | players.put(event.getPlayer().getUniqueId(), pactifyPlayer = new PPactifyPlayer(this, event.getPlayer())); 82 | pactifyPlayer.init(); 83 | } 84 | 85 | @EventHandler(priority = EventPriority.MONITOR) 86 | public void onPlayerLoginMonitor(PlayerLoginEvent event) { 87 | if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { 88 | // PlayerQuitEvent is not triggered if PlayerLoginEvent is disallowed 89 | playerQuit(event.getPlayer()); 90 | } 91 | } 92 | 93 | @EventHandler(priority = EventPriority.LOWEST) 94 | public void onPlayerJoin(PlayerJoinEvent event) { 95 | PPactifyPlayer pactifyPlayer = getPlayer(event.getPlayer()); 96 | pactifyPlayer.join(); 97 | pactifyPlayer.doJoinActions(pactifyPlayer.hasLauncher() 98 | ? plugin.getConf().getLoginWithPactify() 99 | : plugin.getConf().getLoginWithoutPactify()); 100 | } 101 | 102 | @EventHandler(priority = EventPriority.HIGHEST) 103 | public void onPlayerQuit(PlayerQuitEvent event) { 104 | playerQuit(event.getPlayer()); 105 | } 106 | 107 | private void playerQuit(Player player) { 108 | PPactifyPlayer pactifyPlayer = players.remove(player.getUniqueId()); 109 | if (pactifyPlayer != null) { 110 | pactifyPlayer.free(true); 111 | } 112 | } 113 | 114 | @EventHandler(priority = EventPriority.MONITOR) 115 | public void onPluginDisable(PluginDisableEvent event) { 116 | if (event.getPlugin() != plugin) { 117 | return; 118 | } 119 | 120 | // Free registered players 121 | // NOTE: This is not in the disable() method because we can't send Plugin Messages with an already disabled 122 | // plugin! 123 | for (Iterator it = players.values().iterator(); it.hasNext(); ) { 124 | PPactifyPlayer pactifyPlayer = it.next(); 125 | it.remove(); 126 | try { 127 | pactifyPlayer.free(false); 128 | } catch (Throwable t) { 129 | plugin.getLogger().log(Level.WARNING, "An error occurred freeing " 130 | + pactifyPlayer.getPlayer().getName() + ":", t); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/util/BukkitUtil.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | @UtilityClass 13 | public class BukkitUtil { 14 | private static final Method PLAYER_ADDCHANNEL_METHOD; 15 | private static final Pattern SERVER_VERSION = Pattern.compile( 16 | "\\(MC: (?[0-9]{1,3})\\.(?[0-9]{1,3})(?:\\.(?[0-9]{1,3}))?\\)"); 17 | 18 | static { 19 | String ocbPackage = Bukkit.getServer().getClass().getName().replaceAll("\\.[^.]+$", ""); 20 | 21 | try { 22 | Class playerClass = Class.forName(ocbPackage + ".entity.CraftPlayer"); 23 | Method addChannelMethod = playerClass.getDeclaredMethod("addChannel", String.class); 24 | addChannelMethod.setAccessible(true); 25 | PLAYER_ADDCHANNEL_METHOD = addChannelMethod; 26 | } catch (ClassNotFoundException | NoSuchMethodException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | public static int findServerVersion() { 32 | Matcher m = SERVER_VERSION.matcher(Bukkit.getVersion()); 33 | if (m.find()) { 34 | int major = Integer.parseInt(m.group("major")); 35 | int minor = Integer.parseInt(m.group("minor")); 36 | int patch = m.group("patch") != null ? Integer.parseInt(m.group("patch")) : 0; 37 | return major * 1000000 + minor * 1000 + patch; 38 | } 39 | throw new RuntimeException("Unable to detect server version! Bukkit.getVersion()=" + Bukkit.getVersion()); 40 | } 41 | 42 | public static void addChannel(Player player, String channel) { 43 | try { 44 | PLAYER_ADDCHANNEL_METHOD.invoke(player, channel); 45 | } catch (IllegalAccessException | InvocationTargetException e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/util/PacketOutBuffer.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.util; 2 | 3 | import com.google.common.io.ByteArrayDataOutput; 4 | import com.google.common.io.ByteStreams; 5 | import pactify.client.api.mcprotocol.AbstractNotchianPacketBuffer; 6 | 7 | public class PacketOutBuffer extends AbstractNotchianPacketBuffer { 8 | private final ByteArrayDataOutput handle = ByteStreams.newDataOutput(); 9 | 10 | @Override 11 | public PacketOutBuffer writeBytes(byte[] src) { 12 | handle.write(src); 13 | return this; 14 | } 15 | 16 | @Override 17 | public PacketOutBuffer writeBytes(byte[] src, int offset, int len) { 18 | handle.write(src, offset, len); 19 | return this; 20 | } 21 | 22 | @Override 23 | public PacketOutBuffer writeBoolean(boolean value) { 24 | handle.writeBoolean(value); 25 | return this; 26 | } 27 | 28 | @Override 29 | public PacketOutBuffer writeByte(int value) { 30 | handle.writeByte(value); 31 | return this; 32 | } 33 | 34 | @Override 35 | public PacketOutBuffer writeShort(int value) { 36 | handle.writeShort(value); 37 | return this; 38 | } 39 | 40 | @Override 41 | public PacketOutBuffer writeInt(int value) { 42 | handle.writeInt(value); 43 | return this; 44 | } 45 | 46 | @Override 47 | public PacketOutBuffer writeLong(long value) { 48 | handle.writeLong(value); 49 | return this; 50 | } 51 | 52 | @Override 53 | public PacketOutBuffer writeFloat(float value) { 54 | handle.writeFloat(value); 55 | return this; 56 | } 57 | 58 | @Override 59 | public PacketOutBuffer writeDouble(double value) { 60 | handle.writeDouble(value); 61 | return this; 62 | } 63 | 64 | @Override 65 | public PacketOutBuffer writeVarInt(int value) { 66 | while ((value & -128) != 0) { 67 | handle.writeByte(value & 127 | 128); 68 | value >>>= 7; 69 | } 70 | handle.writeByte(value); 71 | return this; 72 | } 73 | 74 | @Override 75 | public PacketOutBuffer writeVarLong(long value) { 76 | while ((value & -128L) != 0L) { 77 | this.writeByte((int) (value & 127L) | 128); 78 | value >>>= 7; 79 | } 80 | this.writeByte((int) value); 81 | return this; 82 | } 83 | 84 | @Override 85 | public PacketOutBuffer writeByteArray(byte[] bytes) { 86 | this.writeVarInt(bytes.length); 87 | this.writeBytes(bytes); 88 | return this; 89 | } 90 | 91 | @Override 92 | public PacketOutBuffer writeVarIntArray(int[] ints) { 93 | this.writeVarInt(ints.length); 94 | for (int value : ints) { 95 | this.writeVarInt(value); 96 | } 97 | return this; 98 | } 99 | 100 | @Override 101 | public PacketOutBuffer writeLongArray(long[] longs) { 102 | this.writeVarInt(longs.length); 103 | for (long value : longs) { 104 | this.writeLong(value); 105 | } 106 | return this; 107 | } 108 | 109 | public byte[] toBytes() { 110 | return handle.toByteArray(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/java/nz/pactifylauncher/plugin/bukkit/util/SchedulerUtil.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bukkit.util; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.experimental.UtilityClass; 5 | import org.bukkit.plugin.java.JavaPlugin; 6 | 7 | import java.util.Collection; 8 | import java.util.Iterator; 9 | 10 | @UtilityClass 11 | public class SchedulerUtil { 12 | public static void scheduleSyncDelayedTask(JavaPlugin plugin, Collection tasksCollection, Runnable task, 13 | long delay) { 14 | new Task(tasksCollection, task).scheduleSyncDelayedTask(plugin, delay); 15 | } 16 | 17 | public static void cancelTasks(JavaPlugin plugin, Collection tasksCollection) { 18 | for (Iterator it = tasksCollection.iterator(); it.hasNext(); ) { 19 | plugin.getServer().getScheduler().cancelTask(it.next()); 20 | it.remove(); 21 | } 22 | } 23 | 24 | @RequiredArgsConstructor 25 | private static class Task implements Runnable { 26 | private final Collection tasksCollection; 27 | private final Runnable handle; 28 | private int taskId; 29 | 30 | @Override 31 | public void run() { 32 | tasksCollection.remove(taskId); 33 | handle.run(); 34 | } 35 | 36 | public void scheduleSyncDelayedTask(JavaPlugin plugin, long delay) { 37 | taskId = plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, this, delay); 38 | if (taskId != -1) { 39 | tasksCollection.add(taskId); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bukkit/plugin/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: PactifyPlugin 2 | main: nz.pactifylauncher.plugin.bukkit.PactifyPlugin 3 | version: ${describe} 4 | authors: [PactifyLauncherExamples] 5 | 6 | commands: 7 | pactifycheck: 8 | aliases: [pacheck] 9 | description: Check whether a given player is using the Pactify Launcher 10 | usage: /pacheck 11 | permission: pactifyplugin.check 12 | pactifylist: 13 | aliases: [palist] 14 | description: Separately lists the players who use the Pactify Launcher and those who don't 15 | usage: /palist 16 | permission: pactifyplugin.list 17 | pactifystats: 18 | aliases: [pastats] 19 | description: View the Pactify Launcher usage statistics. 20 | usage: /pastats 21 | permission: pactifyplugin.stats 22 | 23 | permissions: 24 | pactifyplugin.check: 25 | default: op 26 | description: Allows use of /pacheck 27 | pactifyplugin.list: 28 | default: op 29 | description: Allows use of /palist 30 | pactifyplugin.stats: 31 | default: op 32 | description: Allows use of /pastats 33 | -------------------------------------------------------------------------------- /bungee/plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.github.pactifylauncherexamples 8 | pactify-plugin 9 | 1.0-SNAPSHOT 10 | ../../pom.xml 11 | 12 | pactify-plugin-bungee 13 | 14 | 15 | ../../ 16 | 17 | 18 | 19 | 20 | net.md-5 21 | bungeecord-api 22 | 1.15-SNAPSHOT 23 | provided 24 | 25 | 26 | net.md-5 27 | bungeecord-proxy 28 | 1.15-SNAPSHOT 29 | provided 30 | 31 | 32 | 33 | 34 | PactifyPlugin-Bungee-${project.version} 35 | 36 | 37 | 38 | true 39 | src/main/resources 40 | 41 | bungee.yml 42 | 43 | 44 | 45 | src/main/resources 46 | 47 | ** 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /bungee/plugin/src/main/java/nz/pactifylauncher/plugin/bungee/HandshakeFix.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bungee; 2 | 3 | import net.md_5.bungee.api.event.PlayerHandshakeEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.connection.InitialHandler; 6 | import net.md_5.bungee.event.EventHandler; 7 | 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | /** 12 | * When IP Forwarding is enabled, BungeeCord don't send the "extra data in handshake" to the spigot server... 13 | * But it is required to detect the Pactify Launcher from Spigot. So we fix that! 14 | */ 15 | public class HandshakeFix implements Listener { 16 | private static final Pattern PACTIFY_HOSTNAME_PATTERN = Pattern.compile("\u0000(PAC[0-9A-F]{5})\u0000"); 17 | 18 | @EventHandler 19 | public void onPlayerHandshake(PlayerHandshakeEvent event) { 20 | InitialHandler con = (InitialHandler) event.getConnection(); 21 | Matcher m = PACTIFY_HOSTNAME_PATTERN.matcher(con.getExtraDataInHandshake()); 22 | if (m.find()) { 23 | // Send the Pactify Launcher handshake using \u0002 instead of \u0000 (so that will not break IP forwarding) 24 | event.getHandshake().setHost(event.getHandshake().getHost() + "\u0002" + m.group(1) + "\u0002"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bungee/plugin/src/main/java/nz/pactifylauncher/plugin/bungee/PactifyPlugin.java: -------------------------------------------------------------------------------- 1 | package nz.pactifylauncher.plugin.bungee; 2 | 3 | import net.md_5.bungee.api.plugin.Plugin; 4 | 5 | public class PactifyPlugin extends Plugin { 6 | @Override 7 | public void onEnable() { 8 | getProxy().getPluginManager().registerListener(this, new HandshakeFix()); 9 | getLogger().info("Enabled!"); 10 | } 11 | 12 | @Override 13 | public void onDisable() { 14 | getLogger().info("Disabled!"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bungee/plugin/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: PactifyPlugin 2 | main: nz.pactifylauncher.plugin.bungee.PactifyPlugin 3 | version: ${describe} 4 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.pactifylauncherexamples 7 | pactify-plugin 8 | 1.0-SNAPSHOT 9 | pom 10 | ${project.artifactId} 11 | https://github.com/PactifyLauncherExamples/PactifyPlugin 12 | 13 | 14 | bukkit/api 15 | bukkit/plugin 16 | bungee/plugin 17 | 18 | 19 | 20 | UTF-8 21 | 1.8 22 | 1.8 23 | X 24 | ./ 25 | 26 | 27 | 28 | 29 | spigot-repo 30 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 31 | 32 | 33 | bungeecord-repo 34 | https://oss.sonatype.org/content/repositories/snapshots/ 35 | 36 | 37 | pactifylauncherexamples-repo 38 | https://raw.githubusercontent.com/PactifyLauncherExamples/maven-repository/master/ 39 | 40 | 41 | 42 | 43 | 44 | org.projectlombok 45 | lombok 46 | 1.18.10 47 | provided 48 | 49 | 50 | 51 | 52 | PactifyPlugin-${project.version} 53 | 54 | 55 | 56 | 57 | maven-clean-plugin 58 | 3.1.0 59 | 60 | 61 | maven-resources-plugin 62 | 3.0.2 63 | 64 | 65 | maven-compiler-plugin 66 | 3.8.0 67 | 68 | 69 | maven-surefire-plugin 70 | 2.22.1 71 | 72 | 73 | maven-jar-plugin 74 | 3.0.2 75 | 76 | 77 | maven-install-plugin 78 | 2.5.2 79 | 80 | 81 | maven-deploy-plugin 82 | 2.8.2 83 | 84 | 85 | maven-site-plugin 86 | 3.7.1 87 | 88 | 89 | maven-project-info-reports-plugin 90 | 3.0.0 91 | 92 | 93 | maven-shade-plugin 94 | 3.1.1 95 | 96 | 97 | 98 | 99 | 100 | 101 | net.md-5 102 | scriptus 103 | 0.3.1 104 | 105 | ${project.version}-%s-${build.number} 106 | ${scriptus.scmDirectory} 107 | 108 | 109 | 110 | initialize 111 | 112 | describe 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-jar-plugin 121 | 122 | 123 | 124 | ${project.version} 125 | ${describe} 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | --------------------------------------------------------------------------------