├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java ├── de │ └── themoep │ │ └── snap │ │ ├── PluginConfig.java │ │ ├── Snap.java │ │ ├── SnapBungeeAdapter.java │ │ ├── SnapListener.java │ │ ├── SnapUtils.java │ │ └── forwarding │ │ ├── SnapCommandSender.java │ │ ├── SnapPlayer.java │ │ ├── SnapProxyServer.java │ │ ├── SnapServer.java │ │ ├── SnapServerInfo.java │ │ ├── SnapTitle.java │ │ └── listener │ │ ├── ChatListener.java │ │ ├── ClientConnectListener.java │ │ ├── ConnectionInitListener.java │ │ ├── ForwardingListener.java │ │ ├── LoginListener.java │ │ ├── PlayerDisconnectListener.java │ │ ├── PlayerHandshakeListener.java │ │ ├── PluginMessageListener.java │ │ ├── PostLoginListener.java │ │ ├── PreLoginListener.java │ │ ├── ProxyPingListener.java │ │ ├── ProxyQueryListener.java │ │ ├── ProxyReloadListener.java │ │ ├── ServerConnectListener.java │ │ ├── ServerConnectedListener.java │ │ ├── ServerDisconnectListener.java │ │ ├── ServerKickListener.java │ │ ├── ServerSwitchListener.java │ │ ├── SettingsChangedListener.java │ │ └── TabCompleteResponseListener.java └── net │ └── md_5 │ └── bungee │ └── api │ └── plugin │ └── PluginClassloader.java └── resources ├── snap.conf └── velocity-plugin.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | out 4 | gen 5 | target/ 6 | dependency-reduced-pom.xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snap! 2 | 3 | This is the Seriously Necessary Adapter Plugin to enable plugins written against the 4 | BungeeCord or Waterfall API to load and (kinda) run on [Velocity](https://velocitypowered.com/). 👀 5 | 6 | ## How? 7 | 8 | Simply add the Bungee plugins into the plugins folder inside the Snap plugin folder. 9 | 10 | Snap will use it's own instance of BungeeCord's plugin manager to load the plugins 11 | from there and translate BungeeCord methods, classes as well as event calls to the 12 | respective Velocity ones and vice versa. 13 | 14 | ## Why? 15 | 16 | Originally I wanted to document the Velocity equivalents to Bungee events, methods 17 | and classes. This evolved into the idea of writing a converter for source code which 18 | led me to decide to try to make a plugin which can directly load Bungee plugins. 19 | 20 | Seeing as the proxies don't have too much logic that seems to have worked although 21 | it is definitely a lot more inefficient than just running native Velocity plugins 22 | due lots of classes being in need of getting translated on the fly. 23 | 24 | Technically this could be made in a way that is a lot more efficient by directly 25 | modifying the Velocity or BungeeCord source code to extend the respective other 26 | classes but in practice that massively increases the work required to get this 27 | plugin running, and that's all I wanted to do for now. 28 | 29 | ## What works? 30 | 31 | Most of it (hopefully). I mean that's the goal... make sure to report any issues! 32 | 33 | ## What doesn't work? 34 | 35 | Some functionality isn't easily recreated (e.g. group handling is not a 36 | thing in Velocity, use [a permissions plugin](https://luckperms.net)) and of course 37 | anything related to hacking into Bungee-internals or packets wont work. 38 | Just write a Velocity plugin at that point... 39 | 40 | Those functions not supported will throw an UnsupportedOperationException. Please report 41 | those including the plugin causing them on the issue tracker! 42 | 43 | If you are sure that the plugin will work fine otherwise then you can have it return 44 | default values by setting `throw-unsupported-exception` to `false` in the `snap.conf`! 45 | 46 | ### Not supported: 47 | 48 | - Using Bungee's **inbuilt permissions system** to set and get groups/permissions. 49 | Don't! Please use LuckPerms on Velocity. (hasPermission checks work though) 50 | - **Reconnect server functionality.** That's an inbuilt function in Bungee but better 51 | suited for a plugin. The related methods will return `null` or set nothing. Instead 52 | of erroring. 53 | - **Scoreboards.** Velocity doesn't have API for them and I'm not going to create a 54 | packet based one. Maybe there will be a way to integrate in some plugin or Velocity 55 | adds support in the future. 56 | - Some **ProxyConfig** settings don't exist on Velocity or aren't exposed in the API so 57 | they return some sensible defaults which should reflect the proxy's state. 58 | - Registering commands after a plugin was enabled. I currently have no good way to hook 59 | into this besides straight up modifying the PluginManager class which I would like to 60 | avoid. 61 | - Velocity plugins and Bungee plugins are not available to each other via the respective 62 | PluginManager APIs and as dependencies. Their classes should be accessible though. 63 | - Some connection handling and related events might not work 100% exactly like on 64 | Bungee. They are as close as possible though but if you already have to fiddle with 65 | that then its best to create a standalone Velocity plugin tbh. 66 | - Some events don't work 100% or not at all. 67 | Not working: `TabCompleteEvent`, `ProxyDefineCommandEvent`, `ProxyExceptionEvent`. 68 | Only partially: `ServerDisconnectEvent` (only triggers on kicks), 69 | `ClientConnectEvent` (uses Velocity's `LoginEvent` with `PostOrder.FIRST`) 70 | `ConnectionInitEvent` (uses Velocity's `LoginEvent` with `PostOrder.EARLY`) 71 | - **Unsafe** doesn't work. 72 | 73 | ## Sounds awesome! How can I get it? 74 | 75 | You can download the jar via [GitHub releases](https://github.com/Phoenix616/Snap/releases) 76 | or get builds from the latest commits from the [Minebench.de Jenkins](https://ci.minebench.de/job/Snap/). 77 | 78 | ## How can I support the project? 79 | 80 | For the start trying out the plugin and reporting what other plugins work and don't work 81 | would already help a ton figuring out what work is still needed. 82 | 83 | Of course I would also appreciate [monetary help](https://tip.phoenix616.dev) if the plugin 84 | has helped you transition to Velocity either by directly using it or referencing its code to 85 | adapt Bungee plugins to get running on Velocity natively. (Did you know that GitHub is still 86 | doubling donations to [my GitHub Sponsors page](https://ghsponsor.phoenix616.dev)? 😉) 87 | 88 | ## Is it open source? 89 | 90 | Yes, the base code of Snap is open source! Unless noted otherwise in the source it's licensed 91 | under LGPLv3 in order to be compatible with the shipped Waterfall/BungeeCord. 92 | 93 | ``` 94 | Snap 95 | Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 96 | 97 | This program is free software: you can redistribute it and/or 98 | modify it under the terms of the GNU Lesser General Public 99 | License as published by the Free Software Foundation, either 100 | version 3 of the License, or (at your option) any later version. 101 | 102 | This program is distributed in the hope that it will be useful, 103 | but WITHOUT ANY WARRANTY; without even the implied warranty of 104 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 105 | GNU Lesser General Public License for more details. 106 | 107 | You should have received a copy of the GNU Lesser General Public 108 | License along with this program. If not, see . 109 | ``` 110 | 111 | Please note that BungeeCord is not Free Software and licensed under their own, BSD 3-Clause based 112 | [license](https://github.com/SpigotMC/BungeeCord/blob/master/LICENSE) (which forbids usage of BungeeCord "for commercial software hosting services without 113 | written permission from the author") and that Waterfall uses 114 | an [MIT License](https://github.com/PaperMC/Waterfall/blob/master/LICENSE.txt) for its patches. 115 | 116 | Therefore pre-built binaries of Snap would have to be distributed under Bungee's modified BSD 3-Clause license or a compatible one. 117 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 4.0.0 7 | 8 | de.themoep 9 | snap 10 | 1.2-SNAPSHOT 11 | 12 | 13 | UTF-8 14 | 17 15 | 17 16 | ${buildNumber} 17 | ${project.version} ${buildDescription} 18 | 19 | 20 | 21 | 22 | papermc-repo 23 | https://repo.papermc.io/repository/maven-public/ 24 | 25 | 26 | 27 | 28 | 29 | com.velocitypowered 30 | velocity-api 31 | 3.3.0-SNAPSHOT 32 | provided 33 | 34 | 35 | io.github.waterfallmc 36 | waterfall-api 37 | 1.21-R0.1-SNAPSHOT 38 | compile 39 | 40 | 41 | 42 | org.apache.maven 43 | maven-resolver-provider 44 | 3.9.1 45 | compile 46 | 47 | 48 | org.apache.maven.resolver 49 | maven-resolver-connector-basic 50 | 1.9.7 51 | compile 52 | 53 | 54 | org.apache.maven.resolver 55 | maven-resolver-transport-http 56 | 1.9.7 57 | compile 58 | 59 | 60 | 61 | net.kyori 62 | adventure-platform-bungeecord 63 | 4.3.0 64 | compile 65 | 66 | 67 | org.slf4j 68 | jul-to-slf4j 69 | 2.0.5 70 | compile 71 | 72 | 73 | 74 | 75 | net.sf.jopt-simple 76 | jopt-simple 77 | 5.0.4 78 | compile 79 | 80 | 81 | com.mysql 82 | mysql-connector-j 83 | 8.2.0 84 | runtime 85 | 86 | 87 | 88 | 89 | 90 | static_build_number 91 | 92 | 93 | !env.BUILD_NUMBER 94 | 95 | 96 | 97 | 0 98 | (compiled at ${maven.build.timestamp}) 99 | 100 | 101 | 102 | dynamic_build_number 103 | 104 | 105 | env.BUILD_NUMBER 106 | 107 | 108 | 109 | ${env.BUILD_NUMBER} 110 | (build ${env.BUILD_NUMBER}) 111 | 112 | 113 | 114 | 115 | 116 | 117 | ${project.name} 118 | 119 | 120 | true 121 | ${project.basedir}/src/main/resources 122 | 123 | 124 | ${project.basedir}/../ 125 | 126 | LICENSE 127 | 128 | 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-compiler-plugin 134 | 3.11.0 135 | 136 | none 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-shade-plugin 142 | 3.5.1 143 | 144 | 145 | package 146 | 147 | shade 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/PluginConfig.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import org.spongepowered.configurate.ConfigurationNode; 22 | import org.spongepowered.configurate.hocon.HoconConfigurationLoader; 23 | import org.spongepowered.configurate.serialize.SerializationException; 24 | 25 | import java.io.BufferedReader; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.InputStreamReader; 29 | import java.nio.file.Files; 30 | import java.nio.file.Path; 31 | import java.util.regex.Pattern; 32 | 33 | public class PluginConfig { 34 | 35 | private static final Pattern PATH_PATTERN = Pattern.compile("\\."); 36 | 37 | private final Snap plugin; 38 | private final Path configFile; 39 | private final String defaultFile; 40 | private final HoconConfigurationLoader configLoader; 41 | private ConfigurationNode config; 42 | private ConfigurationNode defaultConfig; 43 | 44 | public PluginConfig(Snap plugin, Path configFile) { 45 | this(plugin, configFile, configFile.getFileName().toString()); 46 | } 47 | 48 | public PluginConfig(Snap plugin, Path configFile, String defaultFile) { 49 | this.plugin = plugin; 50 | this.configFile = configFile; 51 | this.defaultFile = defaultFile; 52 | configLoader = HoconConfigurationLoader.builder() 53 | .path(configFile) 54 | .build(); 55 | } 56 | 57 | public boolean load() { 58 | try { 59 | config = configLoader.load(); 60 | if (defaultFile != null && plugin.getClass().getClassLoader().getResource(defaultFile) != null) { 61 | defaultConfig = HoconConfigurationLoader.builder() 62 | .path(configFile) 63 | .source(() -> new BufferedReader(new InputStreamReader(plugin.getClass().getClassLoader().getResourceAsStream(defaultFile)))) 64 | .build() 65 | .load(); 66 | if (config.empty()) { 67 | config = defaultConfig.copy(); 68 | } 69 | } 70 | plugin.getLogger().info("Loaded " + configFile.getFileName()); 71 | return true; 72 | } catch (IOException e) { 73 | plugin.getLogger().error("Unable to load configuration file " + configFile.getFileName(), e); 74 | return false; 75 | } 76 | } 77 | 78 | public boolean createDefaultConfig() throws IOException { 79 | try (InputStream in = plugin.getClass().getClassLoader().getResourceAsStream(defaultFile)) { 80 | if (in == null) { 81 | plugin.getLogger().warn("No default config '" + defaultFile + "' found in " + plugin.getClass().getSimpleName() + "!"); 82 | return false; 83 | } 84 | if (!Files.exists(configFile)) { 85 | Path parent = configFile.getParent(); 86 | if (!Files.exists(parent)) { 87 | Files.createDirectories(parent); 88 | } 89 | try { 90 | Files.copy(in, configFile); 91 | return true; 92 | } catch (IOException ex) { 93 | plugin.getLogger().error("Could not save '" + defaultFile + "' to " + configFile, ex); 94 | } 95 | } 96 | } catch (IOException ex) { 97 | plugin.getLogger().error("Could not load default config from " + defaultFile, ex); 98 | } 99 | return false; 100 | } 101 | 102 | public void save() { 103 | try { 104 | configLoader.save(config); 105 | } catch (IOException e) { 106 | e.printStackTrace(); 107 | } 108 | } 109 | 110 | public Object set(String path, Object value) { 111 | ConfigurationNode node = config.node(splitPath(path)); 112 | Object prev = node.raw(); 113 | try { 114 | node.set(value); 115 | } catch (SerializationException e) { 116 | plugin.getLogger().error("Could not set node at " + path, e); 117 | } 118 | return prev; 119 | } 120 | 121 | public ConfigurationNode remove(String path) { 122 | ConfigurationNode node = config.node(splitPath(path)); 123 | try { 124 | return node.virtual() ? node : node.set(null); 125 | } catch (SerializationException e) { 126 | plugin.getLogger().error("Could not remove node at " + path, e); 127 | return null; 128 | } 129 | } 130 | 131 | public ConfigurationNode getRawConfig() { 132 | return config; 133 | } 134 | 135 | public ConfigurationNode getRawConfig(String path) { 136 | return getRawConfig().node(splitPath(path)); 137 | } 138 | 139 | public boolean has(String path) { 140 | return !getRawConfig(path).virtual(); 141 | } 142 | 143 | public boolean isSection(String path) { 144 | return getRawConfig(path).isMap(); 145 | } 146 | 147 | public int getInt(String path) { 148 | return getInt(path, 0); 149 | } 150 | 151 | public int getInt(String path, int def) { 152 | return getRawConfig(path).getInt(def); 153 | } 154 | 155 | public double getDouble(String path) { 156 | return getDouble(path, 0); 157 | } 158 | 159 | public double getDouble(String path, double def) { 160 | return getRawConfig(path).getDouble(def); 161 | } 162 | 163 | public String getString(String path) { 164 | return getRawConfig(path).getString(); 165 | } 166 | 167 | public String getString(String path, String def) { 168 | return getRawConfig(path).getString(def); 169 | } 170 | 171 | public boolean getBoolean(String path) { 172 | return getBoolean(path, false); 173 | } 174 | 175 | public boolean getBoolean(String path, boolean def) { 176 | return getRawConfig(path).getBoolean(def); 177 | } 178 | 179 | private static Object[] splitPath(String key) { 180 | return PATH_PATTERN.split(key); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/Snap.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import com.google.common.collect.HashBasedTable; 6 | import com.google.common.collect.Table; 7 | import com.google.inject.Inject; 8 | import com.velocitypowered.api.event.Subscribe; 9 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 10 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 11 | import com.velocitypowered.api.proxy.InboundConnection; 12 | import com.velocitypowered.api.proxy.Player; 13 | import com.velocitypowered.api.proxy.ProxyServer; 14 | import com.velocitypowered.api.proxy.server.RegisteredServer; 15 | import de.themoep.snap.forwarding.SnapPlayer; 16 | import de.themoep.snap.forwarding.SnapServerInfo; 17 | import net.kyori.adventure.key.InvalidKeyException; 18 | import net.kyori.adventure.key.Key; 19 | import org.slf4j.Logger; 20 | import org.slf4j.bridge.SLF4JBridgeHandler; 21 | 22 | import java.io.IOException; 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.nio.file.Path; 25 | import java.util.Map; 26 | import java.util.Set; 27 | import java.util.UUID; 28 | import java.util.concurrent.CompletableFuture; 29 | import java.util.concurrent.ConcurrentHashMap; 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.logging.LogManager; 32 | 33 | public class Snap { 34 | private final ProxyServer proxy; 35 | private final Logger logger; 36 | private final Path dataFolder; 37 | private SnapBungeeAdapter bungeeAdapter; 38 | 39 | private PluginConfig config; 40 | 41 | private boolean throwUnsupportedException = true; 42 | private boolean registerAllForwardingListeners = false; 43 | 44 | private final Map players = new ConcurrentHashMap<>(); 45 | private final Map playerNames = new ConcurrentHashMap<>(); 46 | private final Map servers = new ConcurrentHashMap<>(); 47 | private final Set transferred = ConcurrentHashMap.newKeySet(); 48 | 49 | private final Table> cookieRequests = HashBasedTable.create(); 50 | 51 | private final Cache gameprofileUuidCache = CacheBuilder.newBuilder().expireAfterWrite(15, TimeUnit.SECONDS).build(); 52 | 53 | static { 54 | LogManager.getLogManager().reset(); 55 | SLF4JBridgeHandler.install(); 56 | } 57 | 58 | @Inject 59 | public Snap(ProxyServer proxy, Logger logger, @DataDirectory Path dataFolder) { 60 | this.proxy = proxy; 61 | this.logger = logger; 62 | this.dataFolder = dataFolder; 63 | } 64 | 65 | @Subscribe 66 | public void onProxyInitialization(ProxyInitializeEvent event) throws NoSuchMethodException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, IOException { 67 | if (loadConfig()) { 68 | if (!config.has("stats-id") || config.getString("stats-id").isEmpty()) { 69 | config.set("stats-id", UUID.randomUUID().toString()); 70 | config.save(); 71 | } 72 | bungeeAdapter = new SnapBungeeAdapter(this); 73 | bungeeAdapter.loadPlugins(); 74 | getProxy().getEventManager().register(this, new SnapListener(this)); 75 | } else { 76 | getLogger().error("Unable to load config! Plugin will not enable."); 77 | } 78 | } 79 | 80 | private boolean loadConfig() { 81 | config = new PluginConfig(this, dataFolder.resolve("snap.conf")); 82 | try { 83 | config.createDefaultConfig(); 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | } 87 | if (config.load()) { 88 | throwUnsupportedException = config.getBoolean("throw-unsupported-exception", throwUnsupportedException); 89 | registerAllForwardingListeners = config.getBoolean("register-all-listeners", registerAllForwardingListeners); 90 | return true; 91 | } 92 | return false; 93 | } 94 | 95 | public PluginConfig getConfig() { 96 | return config; 97 | } 98 | 99 | public boolean shouldRegisterAllForwardingListeners() { 100 | return registerAllForwardingListeners; 101 | } 102 | 103 | public ProxyServer getProxy() { 104 | return proxy; 105 | } 106 | 107 | public Logger getLogger() { 108 | return logger; 109 | } 110 | 111 | public Path getDataFolder() { 112 | return dataFolder; 113 | } 114 | 115 | public SnapBungeeAdapter getBungeeAdapter() { 116 | return bungeeAdapter; 117 | } 118 | 119 | public Map getPlayers() { 120 | return players; 121 | } 122 | 123 | public Map getPlayerNames() { 124 | return playerNames; 125 | } 126 | 127 | public SnapPlayer getPlayer(Player player) { 128 | SnapPlayer p = players.computeIfAbsent(player.getUniqueId(), u -> new SnapPlayer(this, player)); 129 | playerNames.putIfAbsent(p.getName(), p); 130 | return p; 131 | } 132 | 133 | public SnapServerInfo getServerInfo(RegisteredServer server) { 134 | if (server == null) { 135 | return null; 136 | } 137 | return servers.computeIfAbsent(server.getServerInfo().getName(), u -> new SnapServerInfo(this, server)); 138 | } 139 | 140 | public Map getServers() { 141 | return servers; 142 | } 143 | 144 | public Object unsupported(String... message) { 145 | if (throwUnsupportedException) { 146 | throw new UnsupportedOperationException(message.length > 0 ? String.join("\n", message): "Not implemented (yet)!"); 147 | } 148 | return null; 149 | } 150 | 151 | public void cacheUuidForGameprofile(String username, UUID uuid) { 152 | gameprofileUuidCache.put(username, uuid); 153 | } 154 | 155 | public UUID pullCachedUuidForUsername(String username) { 156 | UUID playerId = gameprofileUuidCache.getIfPresent(username); 157 | if (playerId != null) { 158 | gameprofileUuidCache.invalidate(username); 159 | } 160 | return playerId; 161 | } 162 | 163 | void invalidate(Player player) { 164 | players.remove(player.getUniqueId()); 165 | playerNames.remove(player.getUsername()); 166 | cookieRequests.row(player.getUniqueId()).clear(); 167 | transferred.remove(player.getUniqueId()); 168 | } 169 | 170 | public CompletableFuture retrieveCookie(InboundConnection connection, String key) { 171 | try { 172 | if (connection instanceof Player player) { 173 | return retrieveCookie(player, Key.key(key)); 174 | } 175 | unsupported("Retrieving cookies from a non-Player connection is not supported in Velocity's API! (Was " + connection.getClass().getName() + ")"); 176 | } catch (InvalidKeyException e) { 177 | unsupported("Tried to retrieve cookie at key '" + key + "' but the provided key was invalid!"); 178 | } 179 | return CompletableFuture.completedFuture(null); 180 | } 181 | 182 | private CompletableFuture retrieveCookie(Player player, Key key) { 183 | CompletableFuture future = new CompletableFuture<>(); 184 | cookieRequests.put(player.getUniqueId(), key, future); 185 | player.requestCookie(key); 186 | return future; 187 | } 188 | 189 | boolean completeCookieRequest(Player player, Key key, byte[] data) { 190 | CompletableFuture future = cookieRequests.get(player.getUniqueId(), key); 191 | if (future != null) { 192 | cookieRequests.remove(player.getUniqueId(), key); 193 | future.complete(data); 194 | return true; 195 | } 196 | return false; 197 | } 198 | 199 | public void markTransferred(Player player) { 200 | transferred.add(player.getUniqueId()); 201 | } 202 | 203 | public boolean isTransferred(UUID playerId) { 204 | return transferred.contains(playerId); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/SnapBungeeAdapter.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.google.common.collect.Lists; 22 | import com.velocitypowered.api.command.CommandManager; 23 | import com.velocitypowered.api.command.CommandSource; 24 | import com.velocitypowered.api.command.SimpleCommand; 25 | import com.velocitypowered.api.proxy.Player; 26 | import de.themoep.snap.forwarding.SnapCommandSender; 27 | import de.themoep.snap.forwarding.SnapProxyServer; 28 | import de.themoep.snap.forwarding.listener.ChatListener; 29 | import de.themoep.snap.forwarding.listener.ClientConnectListener; 30 | import de.themoep.snap.forwarding.listener.ConnectionInitListener; 31 | import de.themoep.snap.forwarding.listener.ForwardingListener; 32 | import de.themoep.snap.forwarding.listener.LoginListener; 33 | import de.themoep.snap.forwarding.listener.PlayerDisconnectListener; 34 | import de.themoep.snap.forwarding.listener.PlayerHandshakeListener; 35 | import de.themoep.snap.forwarding.listener.PluginMessageListener; 36 | import de.themoep.snap.forwarding.listener.PostLoginListener; 37 | import de.themoep.snap.forwarding.listener.PreLoginListener; 38 | import de.themoep.snap.forwarding.listener.ProxyPingListener; 39 | import de.themoep.snap.forwarding.listener.ProxyQueryListener; 40 | import de.themoep.snap.forwarding.listener.ProxyReloadListener; 41 | import de.themoep.snap.forwarding.listener.ServerConnectListener; 42 | import de.themoep.snap.forwarding.listener.ServerConnectedListener; 43 | import de.themoep.snap.forwarding.listener.ServerDisconnectListener; 44 | import de.themoep.snap.forwarding.listener.ServerKickListener; 45 | import de.themoep.snap.forwarding.listener.ServerSwitchListener; 46 | import de.themoep.snap.forwarding.listener.SettingsChangedListener; 47 | import de.themoep.snap.forwarding.listener.TabCompleteResponseListener; 48 | import net.md_5.bungee.api.CommandSender; 49 | import net.md_5.bungee.api.plugin.Command; 50 | import net.md_5.bungee.api.plugin.Plugin; 51 | import net.md_5.bungee.api.plugin.PluginManager; 52 | import net.md_5.bungee.event.EventBus; 53 | import org.yaml.snakeyaml.Yaml; 54 | import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor; 55 | import org.yaml.snakeyaml.introspector.PropertyUtils; 56 | 57 | import java.io.File; 58 | import java.io.IOException; 59 | import java.lang.reflect.Field; 60 | import java.lang.reflect.InvocationTargetException; 61 | import java.lang.reflect.Method; 62 | import java.util.ArrayList; 63 | import java.util.List; 64 | import java.util.Map; 65 | import java.util.logging.Handler; 66 | 67 | public class SnapBungeeAdapter { 68 | private final SnapProxyServer snapProxy; 69 | private final PluginManager pluginManager; 70 | private final File pluginsFolder; 71 | private final Snap snap; 72 | private final List forwardingListeners = new ArrayList<>(); 73 | 74 | private final Map, Map>> registeredBungeeListeners; 75 | 76 | SnapBungeeAdapter(Snap snap) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException, IOException, NoSuchMethodException, InvocationTargetException { 77 | this.snap = snap; 78 | 79 | pluginsFolder = new File(snap.getDataFolder().toFile(), "plugins"); 80 | if (!pluginsFolder.exists()) { 81 | pluginsFolder.mkdirs(); 82 | } 83 | 84 | snapProxy = new SnapProxyServer(snap); 85 | net.md_5.bungee.api.ProxyServer.setInstance(snapProxy); 86 | getClass().getClassLoader().loadClass(Yaml.class.getName()); 87 | pluginManager = new PluginManager(snapProxy); 88 | 89 | // Replace Yaml instance with one with proper class loader 90 | Field fYaml = pluginManager.getClass().getDeclaredField("yaml"); 91 | fYaml.setAccessible(true); 92 | org.yaml.snakeyaml.constructor.Constructor constructor = new CustomClassLoaderConstructor(snap.getClass().getClassLoader()); 93 | PropertyUtils properties = constructor.getPropertyUtils(); 94 | properties.setSkipMissingProperties(true); 95 | constructor.setPropertyUtils(properties); 96 | Yaml yaml = new Yaml(constructor); 97 | fYaml.set(pluginManager, yaml); 98 | 99 | // Get event bus from PluginManager 100 | Field fEventBus = pluginManager.getClass().getDeclaredField("eventBus"); 101 | fEventBus.setAccessible(true); 102 | EventBus eventBus = (EventBus) fEventBus.get(pluginManager); 103 | 104 | // Get listener map from EventBus 105 | Field fListeners = eventBus.getClass().getDeclaredField("byListenerAndPriority"); 106 | fListeners.setAccessible(true); 107 | registeredBungeeListeners = (Map, Map>>) fListeners.get(eventBus); 108 | 109 | setupEvents(); 110 | } 111 | 112 | private void setupEvents() { 113 | // Register forwarding events 114 | addListener(new ChatListener(snap)); 115 | addListener(new ClientConnectListener(snap)); 116 | addListener(new ConnectionInitListener(snap)); 117 | addListener(new LoginListener(snap)); 118 | addListener(new PlayerDisconnectListener(snap)); 119 | addListener(new PlayerHandshakeListener(snap)); 120 | addListener(new PluginMessageListener(snap)); 121 | addListener(new PostLoginListener(snap)); 122 | addListener(new PreLoginListener(snap)); 123 | // TODO addListener(new ProxyDefineCommandListener(snap)); hard to convert :S 124 | addListener(new ProxyPingListener(snap)); 125 | addListener(new ProxyQueryListener(snap)); 126 | addListener(new ProxyReloadListener(snap)); 127 | addListener(new ServerConnectedListener(snap)); 128 | addListener(new ServerConnectListener(snap)); 129 | addListener(new ServerDisconnectListener(snap)); 130 | addListener(new ServerKickListener(snap)); 131 | addListener(new ServerSwitchListener(snap)); 132 | addListener(new SettingsChangedListener(snap)); 133 | // TODO addListener(tnew TabCompleteListener(snap)); no real Velocity equivalent 134 | addListener(new TabCompleteResponseListener(snap)); 135 | } 136 | 137 | private void addListener(ForwardingListener listener) { 138 | forwardingListeners.add(listener); 139 | } 140 | 141 | void loadPlugins() { 142 | pluginManager.detectPlugins(pluginsFolder); 143 | pluginManager.loadPlugins(); 144 | pluginManager.enablePlugins(); 145 | 146 | registerForwardingListeners(); 147 | 148 | CommandManager cm = snap.getProxy().getCommandManager(); 149 | for (Map.Entry e : pluginManager.getCommands()) { 150 | Command command = e.getValue(); 151 | cm.register( 152 | cm.metaBuilder(command.getName()).aliases(command.getAliases()).build(), 153 | new SimpleCommand() { 154 | 155 | @Override 156 | public void execute(Invocation invocation) { 157 | command.execute(convert(invocation.source()), invocation.arguments()); 158 | } 159 | 160 | @Override 161 | public boolean hasPermission(Invocation invocation) { 162 | return command.hasPermission(convert(invocation.source())); 163 | } 164 | 165 | private CommandSender convert(CommandSource source) { 166 | SnapCommandSender sender; 167 | if (source instanceof Player) { 168 | sender = snap.getPlayer((Player) source); 169 | } else { 170 | sender = new SnapCommandSender(snap, source); 171 | } 172 | return sender; 173 | } 174 | } 175 | ); 176 | } 177 | 178 | snap.getLogger().info("Loaded " + pluginManager.getPlugins().size() + " plugins!"); 179 | } 180 | 181 | /** 182 | * Register the forwarding listeners that are required for the events of the plugins to work 183 | */ 184 | private void registerForwardingListeners() { 185 | for (ForwardingListener forwardingListener : forwardingListeners) { 186 | if (snap.shouldRegisterAllForwardingListeners() 187 | || registeredBungeeListeners.containsKey(forwardingListener.getForwardedEvent())) { 188 | snap.getLogger().info("Registering forwarding listener for " + forwardingListener.getForwardedEvent().getSimpleName()); 189 | // TODO: actually use the priority 190 | snap.getProxy().getEventManager().register(snap, forwardingListener); 191 | } 192 | } 193 | if (!snap.shouldRegisterAllForwardingListeners()) { 194 | snap.getLogger().info("If you experience some plugin-event-listeners to not work properly then set register-all-listeners = true in the snap.conf!"); 195 | } 196 | } 197 | 198 | public SnapProxyServer getProxy() { 199 | return snapProxy; 200 | } 201 | 202 | public PluginManager getPluginManager() { 203 | return pluginManager; 204 | } 205 | 206 | public File getPluginsFolder() { 207 | return pluginsFolder; 208 | } 209 | 210 | // Code below is under the following license of BungeeCord: 211 | /* 212 | * Copyright (c) 2012, md_5. All rights reserved. 213 | * 214 | * Redistribution and use in source and binary forms, with or without 215 | * modification, are permitted provided that the following conditions are met: 216 | * 217 | * Redistributions of source code must retain the above copyright notice, this 218 | * list of conditions and the following disclaimer. 219 | * 220 | * Redistributions in binary form must reproduce the above copyright notice, 221 | * this list of conditions and the following disclaimer in the documentation 222 | * and/or other materials provided with the distribution. 223 | * 224 | * The name of the author may not be used to endorse or promote products derived 225 | * from this software without specific prior written permission. 226 | * 227 | * You may not use the software for commercial software hosting services without 228 | * written permission from the author. 229 | * 230 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 231 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 232 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 233 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 234 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 235 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 236 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 237 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 238 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 239 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 240 | * POSSIBILITY OF SUCH DAMAGE. 241 | */ 242 | 243 | public void disablePlugins() { 244 | // From https://github.com/SpigotMC/BungeeCord/blob/c987ee199d3ec93ce13469deefb1e2787d97147c/proxy/src/main/java/net/md_5/bungee/BungeeCord.java#L465-L480 245 | for (Plugin plugin : Lists.reverse(new ArrayList<>(getPluginManager().getPlugins()))) { 246 | try { 247 | plugin.onDisable(); 248 | for (Handler handler : plugin.getLogger().getHandlers()) { 249 | handler.close(); 250 | } 251 | } catch (Throwable t) { 252 | snap.getLogger().error("Exception disabling plugin " + plugin.getDescription().getName(), t); 253 | } 254 | getProxy().getScheduler().cancel(plugin); 255 | plugin.getExecutorService().shutdownNow(); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/SnapListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.PostOrder; 22 | import com.velocitypowered.api.event.Subscribe; 23 | import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent; 24 | import com.velocitypowered.api.event.connection.DisconnectEvent; 25 | import com.velocitypowered.api.event.connection.LoginEvent; 26 | import com.velocitypowered.api.event.player.CookieReceiveEvent; 27 | import com.velocitypowered.api.event.player.GameProfileRequestEvent; 28 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 29 | import com.velocitypowered.api.network.HandshakeIntent; 30 | import com.velocitypowered.api.proxy.Player; 31 | import com.velocitypowered.api.util.GameProfile; 32 | 33 | import java.util.UUID; 34 | 35 | /** 36 | * Listeners to manage internal states 37 | */ 38 | public class SnapListener { 39 | private final Snap snap; 40 | 41 | public SnapListener(Snap snap) { 42 | this.snap = snap; 43 | } 44 | 45 | @Subscribe 46 | public void onHandshake(ConnectionHandshakeEvent event) { 47 | if (event.getIntent() == HandshakeIntent.TRANSFER && event.getConnection() instanceof Player player) { 48 | snap.markTransferred(player); 49 | } 50 | } 51 | 52 | @Subscribe(order = PostOrder.FIRST) 53 | public void onPlayerConnect(LoginEvent event) { 54 | if (event.getResult().isAllowed()) { 55 | snap.getPlayer(event.getPlayer()); 56 | } 57 | } 58 | 59 | @Subscribe(order = PostOrder.LAST) 60 | public void onPlayerConnectLast(LoginEvent event) { 61 | if (!event.getResult().isAllowed()) { 62 | snap.getPlayers().remove(event.getPlayer().getUniqueId()); 63 | snap.getPlayerNames().remove(event.getPlayer().getUsername()); 64 | } 65 | } 66 | 67 | @Subscribe(order = PostOrder.LAST) 68 | public void onPlayerQuit(DisconnectEvent event) { 69 | snap.invalidate(event.getPlayer()); 70 | } 71 | 72 | @Subscribe 73 | public void onShutdown(ProxyShutdownEvent event) { 74 | snap.getBungeeAdapter().disablePlugins(); 75 | } 76 | 77 | @Subscribe 78 | public void onGameprofileRequest(GameProfileRequestEvent event) { 79 | UUID playerId = snap.pullCachedUuidForUsername(event.getUsername()); 80 | if (playerId != null && !event.isOnlineMode() && !event.getGameProfile().getId().equals(playerId)) { 81 | event.setGameProfile(new GameProfile(playerId, event.getGameProfile().getName(), event.getGameProfile().getProperties())); 82 | } 83 | } 84 | 85 | @Subscribe 86 | public void onCookieReceive(CookieReceiveEvent event) { 87 | if (snap.completeCookieRequest(event.getPlayer(), event.getResult().getKey(), event.getResult().getData())) { 88 | event.setResult(CookieReceiveEvent.ForwardResult.handled()); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/SnapUtils.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 22 | import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; 23 | import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; 24 | import com.velocitypowered.api.proxy.server.ServerPing.Players; 25 | import com.velocitypowered.api.proxy.server.ServerPing.SamplePlayer; 26 | import com.velocitypowered.api.proxy.server.ServerPing.Version; 27 | import com.velocitypowered.api.util.ModInfo; 28 | import net.kyori.adventure.text.Component; 29 | import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; 30 | import net.md_5.bungee.api.Favicon; 31 | import net.md_5.bungee.api.ServerPing; 32 | import net.md_5.bungee.api.chat.BaseComponent; 33 | import net.md_5.bungee.api.chat.ComponentBuilder; 34 | import net.md_5.bungee.api.plugin.Plugin; 35 | import net.md_5.bungee.api.scheduler.ScheduledTask; 36 | 37 | import java.util.Arrays; 38 | import java.util.stream.Collectors; 39 | 40 | public class SnapUtils { 41 | 42 | public static ChannelIdentifier createChannelIdentifier(String channel) { 43 | if (channel.contains(":")) { 44 | String[] split = channel.split(":", 2); 45 | return MinecraftChannelIdentifier.create(split[0], split[1]); 46 | } 47 | return new LegacyChannelIdentifier(channel); 48 | } 49 | 50 | public static T convertEnum(S source, T def) { 51 | try { 52 | return (T) Enum.valueOf(def.getClass(), source.name()); 53 | } catch (IllegalArgumentException e) { 54 | return def; 55 | } 56 | } 57 | 58 | public static BaseComponent[] convertComponent(Component component) { 59 | return component == null ? new ComponentBuilder().create() : BungeeComponentSerializer.get().serialize(component); 60 | } 61 | 62 | public static Component convertComponent(BaseComponent... components) { 63 | return components == null ? Component.empty() : BungeeComponentSerializer.get().deserialize(components); 64 | } 65 | 66 | public static ServerPing convertPing(com.velocitypowered.api.proxy.server.ServerPing vPing) { 67 | BaseComponent motd = new net.md_5.bungee.api.chat.TextComponent(); 68 | motd.setExtra(Arrays.asList(convertComponent(vPing.getDescriptionComponent()))); 69 | ServerPing bPing = new ServerPing( 70 | new ServerPing.Protocol(vPing.getVersion().getName(), vPing.getVersion().getProtocol()), 71 | vPing.getPlayers().map(p -> new ServerPing.Players( 72 | p.getMax(), 73 | p.getOnline(), 74 | p.getSample().stream() 75 | .map(s -> new ServerPing.PlayerInfo(s.getName(), s.getId())) 76 | .toArray(ServerPing.PlayerInfo[]::new) 77 | )).orElse(null), 78 | motd, 79 | vPing.getFavicon().map(f -> Favicon.create(f.getBase64Url())).orElse(null) 80 | ); 81 | if (vPing.getModinfo().isPresent()) { 82 | bPing.getModinfo().setType(vPing.getModinfo().get().getType()); 83 | bPing.getModinfo().setModList(vPing.getModinfo().get().getMods().stream() 84 | .map(m -> new ServerPing.ModItem(m.getId(), m.getVersion())) 85 | .collect(Collectors.toList())); 86 | } 87 | 88 | return bPing; 89 | } 90 | 91 | public static com.velocitypowered.api.proxy.server.ServerPing convertPing(ServerPing ping) { 92 | return new com.velocitypowered.api.proxy.server.ServerPing( 93 | new Version(ping.getVersion().getProtocol(), ping.getVersion().getName()), 94 | ping.getPlayers() != null ? new Players( 95 | ping.getPlayers().getOnline(), 96 | ping.getPlayers().getMax(), 97 | Arrays.stream(ping.getPlayers().getSample()) 98 | .map(p -> new com.velocitypowered.api.proxy.server.ServerPing.SamplePlayer(p.getName(), p.getUniqueId())) 99 | .collect(Collectors.toList()) 100 | ) : null, 101 | convertComponent(ping.getDescriptionComponent()), 102 | ping.getFaviconObject() != null ? new com.velocitypowered.api.util.Favicon(ping.getFaviconObject().getEncoded()) : null, 103 | new ModInfo(ping.getModinfo().getType(), ping.getModinfo().getModList().stream() 104 | .map(m -> new ModInfo.Mod(m.getModid(), m.getVersion())) 105 | .collect(Collectors.toList())) 106 | ); 107 | } 108 | 109 | public static ScheduledTask convertTask(Plugin plugin, Runnable runnable, com.velocitypowered.api.scheduler.ScheduledTask vTask) { 110 | return new ScheduledTask() { 111 | @Override 112 | public int getId() { 113 | return vTask.hashCode(); 114 | } 115 | 116 | @Override 117 | public Plugin getOwner() { 118 | return plugin; 119 | } 120 | 121 | @Override 122 | public Runnable getTask() { 123 | return runnable; 124 | } 125 | 126 | @Override 127 | public void cancel() { 128 | vTask.cancel(); 129 | } 130 | }; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/SnapCommandSender.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.command.CommandSource; 22 | import com.velocitypowered.api.proxy.ConsoleCommandSource; 23 | import de.themoep.snap.Snap; 24 | import de.themoep.snap.SnapUtils; 25 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 26 | import net.md_5.bungee.api.chat.BaseComponent; 27 | import net.md_5.bungee.api.event.PermissionCheckEvent; 28 | 29 | import java.util.Collection; 30 | import java.util.Collections; 31 | 32 | public class SnapCommandSender implements net.md_5.bungee.api.CommandSender { 33 | protected final Snap snap; 34 | private final CommandSource source; 35 | 36 | public SnapCommandSender(Snap snap, CommandSource source) { 37 | this.snap = snap; 38 | this.source = source; 39 | } 40 | 41 | @Override 42 | public String getName() { 43 | return source instanceof ConsoleCommandSource ? "Console" : "Unknown"; 44 | } 45 | 46 | @Override 47 | public void sendMessage(String message) { 48 | source.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); 49 | } 50 | 51 | @Override 52 | public void sendMessages(String... messages) { 53 | for (String message : messages) { 54 | sendMessage(message); 55 | } 56 | } 57 | 58 | @Override 59 | public void sendMessage(BaseComponent... message) { 60 | source.sendMessage(SnapUtils.convertComponent(message)); 61 | } 62 | 63 | @Override 64 | public void sendMessage(BaseComponent message) { 65 | source.sendMessage(SnapUtils.convertComponent(message)); 66 | } 67 | 68 | @Override 69 | public Collection getGroups() { 70 | // TODO: Hook into permissions plugins? 71 | snap.unsupported("Tried to get groups for " + getName() + " which is not supported!"); 72 | return Collections.emptySet(); 73 | } 74 | 75 | @Override 76 | public void addGroups(String... groups) { 77 | // TODO: Hook into permissions plugins? 78 | snap.unsupported("Tried to set groups " + String.join(", ", groups) + " for " + getName() + " which is not supported!"); 79 | } 80 | 81 | @Override 82 | public void removeGroups(String... groups) { 83 | // TODO: Hook into permissions plugins? 84 | snap.unsupported("Tried to remove groups " + String.join(", ", groups) + " for " + getName() + " which is not supported!"); 85 | } 86 | 87 | @Override 88 | public boolean hasPermission(String permission) { 89 | return snap.getBungeeAdapter().getPluginManager() 90 | .callEvent(new PermissionCheckEvent(this, permission, source.hasPermission(permission))) 91 | .hasPermission(); 92 | } 93 | 94 | @Override 95 | public void setPermission(String permission, boolean value) { 96 | // TODO: Hook into permissions plugins? 97 | snap.unsupported("Tried to set permission " + permission + " to " + value + " for " + getName() + " which is not supported!"); 98 | } 99 | 100 | @Override 101 | public Collection getPermissions() { 102 | // TODO: Hook into permissions plugins? 103 | snap.unsupported("Tried to get permissions for " + getName() + " which is not supported!"); 104 | return Collections.emptySet(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/SnapPlayer.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.proxy.Player; 22 | import com.velocitypowered.api.proxy.server.RegisteredServer; 23 | import com.velocitypowered.api.util.ModInfo; 24 | import de.themoep.snap.Snap; 25 | import de.themoep.snap.SnapUtils; 26 | import net.kyori.adventure.audience.MessageType; 27 | import net.kyori.adventure.identity.Identity; 28 | import net.kyori.adventure.key.InvalidKeyException; 29 | import net.kyori.adventure.key.Key; 30 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 31 | import net.md_5.bungee.api.Callback; 32 | import net.md_5.bungee.api.ChatMessageType; 33 | import net.md_5.bungee.api.ServerConnectRequest; 34 | import net.md_5.bungee.api.SkinConfiguration; 35 | import net.md_5.bungee.api.Title; 36 | import net.md_5.bungee.api.chat.BaseComponent; 37 | import net.md_5.bungee.api.config.ListenerInfo; 38 | import net.md_5.bungee.api.config.ServerInfo; 39 | import net.md_5.bungee.api.connection.PendingConnection; 40 | import net.md_5.bungee.api.connection.ProxiedPlayer; 41 | import net.md_5.bungee.api.connection.Server; 42 | import net.md_5.bungee.api.event.ServerConnectEvent; 43 | import net.md_5.bungee.api.score.Scoreboard; 44 | 45 | import java.net.InetSocketAddress; 46 | import java.net.SocketAddress; 47 | import java.util.Collections; 48 | import java.util.Locale; 49 | import java.util.Map; 50 | import java.util.Optional; 51 | import java.util.UUID; 52 | import java.util.concurrent.CompletableFuture; 53 | import java.util.stream.Collectors; 54 | 55 | public class SnapPlayer extends SnapCommandSender implements ProxiedPlayer { 56 | private final Player player; 57 | private final PendingConnection connection; 58 | private String displayName; 59 | 60 | public SnapPlayer(Snap snap, Player player) { 61 | super(snap, player); 62 | this.player = player; 63 | connection = new PendingConnection() { 64 | @Override 65 | public String getName() { 66 | return SnapPlayer.this.player.getUsername(); 67 | } 68 | 69 | @Override 70 | public int getVersion() { 71 | return player.getProtocolVersion().getProtocol(); 72 | } 73 | 74 | @Override 75 | public InetSocketAddress getVirtualHost() { 76 | return player.getVirtualHost().orElse(null); 77 | } 78 | 79 | @Override 80 | public ListenerInfo getListener() { 81 | return snap.getBungeeAdapter().getProxy().getListener(); 82 | } 83 | 84 | @Override 85 | public String getUUID() { 86 | return getUniqueId().toString(); 87 | } 88 | 89 | @Override 90 | public UUID getUniqueId() { 91 | return player.getUniqueId(); 92 | } 93 | 94 | @Override 95 | public void setUniqueId(UUID uuid) { 96 | throw new IllegalStateException("Can only set uuid while state is username"); 97 | } 98 | 99 | @Override 100 | public boolean isOnlineMode() { 101 | return player.isOnlineMode(); 102 | } 103 | 104 | @Override 105 | public void setOnlineMode(boolean onlineMode) { 106 | throw new IllegalStateException("Can only set online mode while state is username"); 107 | } 108 | 109 | @Override 110 | public boolean isLegacy() { 111 | return player.getProtocolVersion().isLegacy(); 112 | } 113 | 114 | @Override 115 | public boolean isTransferred() { 116 | return snap.isTransferred(player.getUniqueId()); 117 | } 118 | 119 | @Override 120 | public CompletableFuture retrieveCookie(String key) { 121 | return snap.retrieveCookie(player, key); 122 | } 123 | 124 | @Override 125 | public InetSocketAddress getAddress() { 126 | return player.getRemoteAddress(); 127 | } 128 | 129 | @Override 130 | public SocketAddress getSocketAddress() { 131 | return getAddress(); 132 | } 133 | 134 | @Override 135 | public void disconnect(String reason) { 136 | SnapPlayer.this.disconnect(reason); 137 | } 138 | 139 | @Override 140 | public void disconnect(BaseComponent... reason) { 141 | player.disconnect(SnapUtils.convertComponent(reason)); 142 | } 143 | 144 | @Override 145 | public void disconnect(BaseComponent reason) { 146 | player.disconnect(SnapUtils.convertComponent(reason)); 147 | } 148 | 149 | @Override 150 | public boolean isConnected() { 151 | return player.isActive(); 152 | } 153 | 154 | @Override 155 | public Unsafe unsafe() { 156 | return (Unsafe) snap.unsupported("Unsafe is not supported by Snap!"); 157 | } 158 | }; 159 | displayName = player.getUsername(); 160 | } 161 | 162 | public Player getPlayer() { 163 | return player; 164 | } 165 | 166 | @Override 167 | public String getDisplayName() { 168 | return displayName; 169 | } 170 | 171 | @Override 172 | public void setDisplayName(String name) { 173 | this.displayName = name; 174 | } 175 | 176 | @Override 177 | public void sendMessage(ChatMessageType position, BaseComponent... message) { 178 | if (position == ChatMessageType.ACTION_BAR) { 179 | player.sendActionBar(SnapUtils.convertComponent(message)); 180 | } else { 181 | player.sendMessage(SnapUtils.convertComponent(message), SnapUtils.convertEnum(position, MessageType.SYSTEM)); 182 | } 183 | } 184 | 185 | @Override 186 | public void sendMessage(ChatMessageType position, BaseComponent message) { 187 | sendMessage(position, new BaseComponent[]{message}); 188 | } 189 | 190 | @Override 191 | public void sendMessage(UUID uuid, BaseComponent... message) { 192 | player.sendMessage(Identity.identity(uuid), SnapUtils.convertComponent(message)); 193 | } 194 | 195 | @Override 196 | public void sendMessage(UUID uuid, BaseComponent message) { 197 | player.sendMessage(Identity.identity(uuid), SnapUtils.convertComponent(message)); 198 | } 199 | 200 | @Override 201 | public void connect(ServerInfo target) { 202 | snap.getProxy().getServer(target.getName()).ifPresent(s -> player.createConnectionRequest(s).fireAndForget()); 203 | } 204 | 205 | @Override 206 | public void connect(ServerInfo target, ServerConnectEvent.Reason reason) { 207 | // TODO: How to reason? 208 | connect(target); 209 | } 210 | 211 | @Override 212 | public void connect(ServerInfo target, Callback callback) { 213 | Optional server = snap.getProxy().getServer(target.getName()); 214 | if (server.isPresent()) { 215 | player.createConnectionRequest(server.get()).connectWithIndication().thenAccept(r -> callback.done(r, null)); 216 | } else { 217 | callback.done(false, null); 218 | } 219 | } 220 | 221 | @Override 222 | public void connect(ServerInfo serverInfo, Callback callback, boolean retry) { 223 | connect(serverInfo, (b, e) -> { 224 | if (!b) { 225 | connect(serverInfo, callback, b); 226 | } else { 227 | callback.done(b, e); 228 | } 229 | }); 230 | } 231 | 232 | @Override 233 | public void connect(ServerInfo serverInfo, Callback callback, boolean retry, int timeout) { 234 | // TODO: Support timeouts? 235 | connect(serverInfo, callback, retry); 236 | } 237 | 238 | @Override 239 | public void connect(ServerInfo target, Callback callback, ServerConnectEvent.Reason reason) { 240 | // TODO: How to reason? 241 | connect(target, callback); 242 | } 243 | 244 | @Override 245 | public void connect(ServerInfo serverInfo, Callback callback, boolean retry, ServerConnectEvent.Reason reason, int timeout) { 246 | // TODO: Reason and timeout 247 | connect(serverInfo, callback, retry); 248 | } 249 | 250 | @Override 251 | public void connect(ServerConnectRequest request) { 252 | Optional server = snap.getProxy().getServer(request.getTarget().getName()); 253 | if (server.isPresent()) { 254 | player.createConnectionRequest(server.get()).connect().thenAccept(r -> { 255 | ServerConnectRequest.Result status; 256 | switch (r.getStatus()) { 257 | case SUCCESS: 258 | status = ServerConnectRequest.Result.SUCCESS; 259 | break; 260 | case ALREADY_CONNECTED: 261 | status = ServerConnectRequest.Result.ALREADY_CONNECTED; 262 | break; 263 | case CONNECTION_IN_PROGRESS: 264 | status = ServerConnectRequest.Result.ALREADY_CONNECTING; 265 | break; 266 | case CONNECTION_CANCELLED: 267 | status = ServerConnectRequest.Result.EVENT_CANCEL; 268 | break; 269 | case SERVER_DISCONNECTED: 270 | default: 271 | status = ServerConnectRequest.Result.FAIL; 272 | break; 273 | } 274 | Throwable error = r.getReasonComponent().isPresent() 275 | ? new Exception(LegacyComponentSerializer.legacySection().serialize(r.getReasonComponent().get())) 276 | : null; 277 | request.getCallback().done(status, error); 278 | }); 279 | } else { 280 | request.getCallback().done(ServerConnectRequest.Result.FAIL, new Exception("Server not found")); 281 | } 282 | } 283 | 284 | @Override 285 | public Server getServer() { 286 | return player.getCurrentServer().map(s -> new SnapServer(snap, s)).orElse(null); 287 | } 288 | 289 | @Override 290 | public int getPing() { 291 | return player.getPing() > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) player.getPing(); 292 | } 293 | 294 | @Override 295 | public void sendData(String channel, byte[] data) { 296 | player.sendPluginMessage(SnapUtils.createChannelIdentifier(channel), data); 297 | } 298 | 299 | @Override 300 | public PendingConnection getPendingConnection() { 301 | return connection; 302 | } 303 | 304 | @Override 305 | public void chat(String message) { 306 | player.spoofChatInput(message); 307 | } 308 | 309 | @Override 310 | public ServerInfo getReconnectServer() { 311 | // TODO: Nah 312 | return (ServerInfo) snap.unsupported(); 313 | } 314 | 315 | @Override 316 | public void setReconnectServer(ServerInfo server) { 317 | // TODO: Nah 318 | snap.unsupported("Setting the reconnect server of a player is not supported in Snap!"); 319 | } 320 | 321 | @Override 322 | public String getUUID() { 323 | return getPendingConnection().getUUID(); 324 | } 325 | 326 | @Override 327 | public UUID getUniqueId() { 328 | return getPendingConnection().getUniqueId(); 329 | } 330 | 331 | @Override 332 | public Locale getLocale() { 333 | return player.getPlayerSettings().getLocale(); 334 | } 335 | 336 | @Override 337 | public byte getViewDistance() { 338 | return player.getPlayerSettings().getViewDistance(); 339 | } 340 | 341 | @Override 342 | public ChatMode getChatMode() { 343 | return SnapUtils.convertEnum(player.getPlayerSettings().getChatMode(), ChatMode.SHOWN); 344 | } 345 | 346 | @Override 347 | public boolean hasChatColors() { 348 | return player.getPlayerSettings().hasChatColors(); 349 | } 350 | 351 | @Override 352 | public SkinConfiguration getSkinParts() { 353 | return new SkinConfiguration() { 354 | @Override 355 | public boolean hasCape() { 356 | return player.getPlayerSettings().getSkinParts().hasCape(); 357 | } 358 | 359 | @Override 360 | public boolean hasJacket() { 361 | return player.getPlayerSettings().getSkinParts().hasJacket(); 362 | } 363 | 364 | @Override 365 | public boolean hasLeftSleeve() { 366 | return player.getPlayerSettings().getSkinParts().hasLeftSleeve(); 367 | } 368 | 369 | @Override 370 | public boolean hasRightSleeve() { 371 | return player.getPlayerSettings().getSkinParts().hasRightSleeve(); 372 | } 373 | 374 | @Override 375 | public boolean hasLeftPants() { 376 | return player.getPlayerSettings().getSkinParts().hasLeftPants(); 377 | } 378 | 379 | @Override 380 | public boolean hasRightPants() { 381 | return player.getPlayerSettings().getSkinParts().hasRightPants(); 382 | } 383 | 384 | @Override 385 | public boolean hasHat() { 386 | return player.getPlayerSettings().getSkinParts().hasHat(); 387 | } 388 | }; 389 | } 390 | 391 | @Override 392 | public MainHand getMainHand() { 393 | return SnapUtils.convertEnum(player.getPlayerSettings().getMainHand(), MainHand.RIGHT); 394 | } 395 | 396 | @Override 397 | public void setTabHeader(BaseComponent header, BaseComponent footer) { 398 | player.getTabList().setHeaderAndFooter(SnapUtils.convertComponent(header), SnapUtils.convertComponent(footer)); 399 | } 400 | 401 | @Override 402 | public void setTabHeader(BaseComponent[] header, BaseComponent[] footer) { 403 | player.getTabList().setHeaderAndFooter(SnapUtils.convertComponent(header), SnapUtils.convertComponent(footer)); 404 | } 405 | 406 | @Override 407 | public void resetTabHeader() { 408 | player.getTabList().clearHeaderAndFooter(); 409 | } 410 | 411 | @Override 412 | public void sendTitle(Title title) { 413 | title.send(this); 414 | } 415 | 416 | @Override 417 | public boolean isForgeUser() { 418 | if (player.getModInfo().isPresent()) { 419 | return player.getModInfo().get().getType().equalsIgnoreCase("FML"); 420 | } 421 | return false; 422 | } 423 | 424 | @Override 425 | public Map getModList() { 426 | if (isForgeUser()) { 427 | return player.getModInfo().get().getMods().stream() 428 | .collect(Collectors.toMap(ModInfo.Mod::getId, ModInfo.Mod::getVersion)); 429 | } 430 | return Collections.emptyMap(); 431 | } 432 | 433 | @Override 434 | public Scoreboard getScoreboard() { 435 | // TODO: Support that? How? Velocity doesn't do this. 436 | return (Scoreboard) snap.unsupported("Scoreboards are not supported by Snap"); 437 | } 438 | 439 | @Override 440 | public CompletableFuture retrieveCookie(String s) { 441 | return getPendingConnection().retrieveCookie(s); 442 | } 443 | 444 | @Override 445 | public void storeCookie(String key, byte[] bytes) { 446 | try { 447 | player.storeCookie(Key.key(key), bytes); 448 | } catch (InvalidKeyException e) { 449 | snap.unsupported("Tried to store cookie at key '" + key + "' but the provided key was invalid!"); 450 | } 451 | } 452 | 453 | @Override 454 | public void transfer(String host, int port) { 455 | player.transferToHost(new InetSocketAddress(host, port)); 456 | } 457 | 458 | @Override 459 | public String getName() { 460 | return player.getUsername(); 461 | } 462 | 463 | @Override 464 | public InetSocketAddress getAddress() { 465 | return getPendingConnection().getAddress(); 466 | } 467 | 468 | @Override 469 | public SocketAddress getSocketAddress() { 470 | return getPendingConnection().getSocketAddress(); 471 | } 472 | 473 | @Override 474 | public void disconnect(String reason) { 475 | player.disconnect(LegacyComponentSerializer.legacySection().deserialize(reason)); 476 | } 477 | 478 | @Override 479 | public void disconnect(BaseComponent... reason) { 480 | getPendingConnection().disconnect(reason); 481 | } 482 | 483 | @Override 484 | public void disconnect(BaseComponent reason) { 485 | getPendingConnection().disconnect(reason); 486 | } 487 | 488 | @Override 489 | public boolean isConnected() { 490 | return player.isActive(); 491 | } 492 | 493 | @Override 494 | public Unsafe unsafe() { 495 | return (Unsafe) snap.unsupported("Unsafe is not supported by Snap!"); 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/SnapProxyServer.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.proxy.Player; 22 | import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 23 | import com.velocitypowered.api.proxy.server.RegisteredServer; 24 | import de.themoep.snap.Snap; 25 | import de.themoep.snap.SnapUtils; 26 | import net.kyori.adventure.text.Component; 27 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 28 | import net.md_5.bungee.api.CommandSender; 29 | import net.md_5.bungee.api.Favicon; 30 | import net.md_5.bungee.api.ProxyConfig; 31 | import net.md_5.bungee.api.ProxyServer; 32 | import net.md_5.bungee.api.ReconnectHandler; 33 | import net.md_5.bungee.api.Title; 34 | import net.md_5.bungee.api.chat.BaseComponent; 35 | import net.md_5.bungee.api.config.ConfigurationAdapter; 36 | import net.md_5.bungee.api.config.ListenerInfo; 37 | import net.md_5.bungee.api.config.ServerInfo; 38 | import net.md_5.bungee.api.connection.ProxiedPlayer; 39 | import net.md_5.bungee.api.plugin.Plugin; 40 | import net.md_5.bungee.api.plugin.PluginManager; 41 | import net.md_5.bungee.api.scheduler.ScheduledTask; 42 | import net.md_5.bungee.api.scheduler.TaskScheduler; 43 | 44 | import java.io.File; 45 | import java.lang.reflect.Field; 46 | import java.net.InetSocketAddress; 47 | import java.net.SocketAddress; 48 | import java.util.ArrayList; 49 | import java.util.Collection; 50 | import java.util.Collections; 51 | import java.util.HashMap; 52 | import java.util.HashSet; 53 | import java.util.Locale; 54 | import java.util.Map; 55 | import java.util.Optional; 56 | import java.util.Set; 57 | import java.util.UUID; 58 | import java.util.concurrent.TimeUnit; 59 | import java.util.logging.Logger; 60 | import java.util.stream.Collectors; 61 | 62 | public class SnapProxyServer extends ProxyServer { 63 | private final Snap snap; 64 | 65 | private String statsId; 66 | private Field fIdentifierMap; 67 | private Set channels = new HashSet<>(); 68 | private final ListenerInfo listener; 69 | private Collection listeners; 70 | private Logger logger = Logger.getLogger("Snap"); 71 | private TaskScheduler scheduler; 72 | 73 | public SnapProxyServer(Snap snap) { 74 | this.snap = snap; 75 | com.velocitypowered.api.proxy.config.ProxyConfig config = snap.getProxy().getConfiguration(); 76 | 77 | statsId = snap.getConfig().getString("stats-id"); 78 | if (statsId == null) { 79 | statsId = UUID.randomUUID().toString(); 80 | } 81 | 82 | listener = new ListenerInfo( 83 | snap.getProxy().getBoundAddress(), 84 | LegacyComponentSerializer.legacySection().serialize(config.getMotd()), 85 | config.getShowMaxPlayers(), 86 | 60, // Default? 87 | config.getAttemptConnectionOrder(), 88 | true, 89 | config.getForcedHosts().entrySet().stream() 90 | .filter(e -> !e.getValue().isEmpty()) 91 | .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0))), 92 | "GLOBAL_PING", 93 | true, 94 | false, // TODO: Read ping passthrough from Velocity config? 95 | config.getQueryPort(), 96 | config.isQueryEnabled(), 97 | false 98 | ); 99 | listeners = Collections.singleton(listener); 100 | 101 | scheduler = new TaskScheduler() { 102 | 103 | private Map scheduledTasks = new HashMap<>(); 104 | 105 | @Override 106 | public void cancel(int i) { 107 | Optional.ofNullable(scheduledTasks.remove(i)).ifPresent(this::cancel); 108 | } 109 | 110 | @Override 111 | public void cancel(ScheduledTask scheduledTask) { 112 | scheduledTask.cancel(); 113 | } 114 | 115 | @Override 116 | public int cancel(Plugin plugin) { 117 | int i = 0; 118 | for (ScheduledTask task : new ArrayList<>(scheduledTasks.values())) { 119 | if (task.getOwner() == plugin) { 120 | cancel(task); 121 | i++; 122 | } 123 | } 124 | return i; 125 | } 126 | 127 | @Override 128 | public ScheduledTask runAsync(Plugin plugin, Runnable runnable) { 129 | ScheduledTask bTask = SnapUtils.convertTask(plugin, runnable, snap.getProxy().getScheduler().buildTask(snap, runnable).schedule()); 130 | scheduledTasks.put(bTask.hashCode(), bTask); 131 | return bTask; 132 | } 133 | 134 | @Override 135 | public ScheduledTask schedule(Plugin plugin, Runnable runnable, long delay, TimeUnit timeUnit) { 136 | ScheduledTask bTask = SnapUtils.convertTask(plugin, runnable, snap.getProxy().getScheduler().buildTask(snap, runnable).delay(delay, timeUnit).schedule()); 137 | scheduledTasks.put(bTask.hashCode(), bTask); 138 | return bTask; 139 | } 140 | 141 | @Override 142 | public ScheduledTask schedule(Plugin plugin, Runnable runnable, long delay, long period, TimeUnit timeUnit) { 143 | ScheduledTask bTask = SnapUtils.convertTask(plugin, runnable, snap.getProxy().getScheduler().buildTask(snap, runnable).delay(delay, timeUnit).repeat(period, timeUnit).schedule()); 144 | scheduledTasks.put(bTask.hashCode(), bTask); 145 | return bTask; 146 | } 147 | 148 | @Override 149 | public Unsafe unsafe() { 150 | return (Unsafe) snap.unsupported("Unsafe is not supported by Snap!"); 151 | } 152 | }; 153 | 154 | try { 155 | fIdentifierMap = snap.getProxy().getChannelRegistrar().getClass().getDeclaredField("identifierMap"); 156 | fIdentifierMap.setAccessible(true); 157 | fIdentifierMap.get(snap.getProxy().getChannelRegistrar()); 158 | } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) { 159 | e.printStackTrace(); 160 | fIdentifierMap = null; 161 | } 162 | } 163 | 164 | public ListenerInfo getListener() { 165 | return listener; 166 | } 167 | 168 | @Override 169 | public String getName() { 170 | return snap.getProxy().getVersion().getName(); 171 | } 172 | 173 | @Override 174 | public String getVersion() { 175 | return snap.getProxy().getVersion().getVersion(); 176 | } 177 | 178 | @Override 179 | public String getTranslation(String name, Object... args) { 180 | snap.unsupported("Tried to get translation " + name + " but translations aren't supported (yet)!"); 181 | return null; 182 | } 183 | 184 | @Override 185 | public Logger getLogger() { 186 | return logger; 187 | } 188 | 189 | @Override 190 | public Collection getPlayers() { 191 | return snap.getPlayers().values().stream().map(p -> (ProxiedPlayer) p).collect(Collectors.toList()); 192 | } 193 | 194 | @Override 195 | public ProxiedPlayer getPlayer(String name) { 196 | return snap.getPlayerNames().get(name); 197 | } 198 | 199 | @Override 200 | public ProxiedPlayer getPlayer(UUID uuid) { 201 | return snap.getPlayers().get(uuid); 202 | } 203 | 204 | @Override 205 | public Map getServers() { 206 | return snap.getProxy().getAllServers().stream().map(snap::getServerInfo).collect(Collectors.toMap(SnapServerInfo::getName, s -> s)); 207 | } 208 | 209 | @Override 210 | public Map getServersCopy() { 211 | return Collections.unmodifiableMap(getServers()); 212 | } 213 | 214 | @Override 215 | public ServerInfo getServerInfo(String name) { 216 | // Bungee returns null if the server name is null instead of throwing an error... 217 | if (name == null) return null; 218 | return snap.getProxy().getServer(name).map(snap::getServerInfo).orElse(null); 219 | } 220 | 221 | @Override 222 | public PluginManager getPluginManager() { 223 | return snap.getBungeeAdapter().getPluginManager(); 224 | } 225 | 226 | @Override 227 | public ConfigurationAdapter getConfigurationAdapter() { 228 | // TODO: Implement 229 | return (ConfigurationAdapter) snap.unsupported(); 230 | } 231 | 232 | @Override 233 | public void setConfigurationAdapter(ConfigurationAdapter adapter) { 234 | // TODO: Implement 235 | snap.unsupported(); 236 | } 237 | 238 | @Override 239 | public ReconnectHandler getReconnectHandler() { 240 | // TODO: Implement 241 | return (ReconnectHandler) snap.unsupported(); 242 | } 243 | 244 | @Override 245 | public void setReconnectHandler(ReconnectHandler handler) { 246 | // TODO: Implement 247 | snap.unsupported(); 248 | } 249 | 250 | @Override 251 | public void stop() { 252 | snap.getProxy().shutdown(); 253 | } 254 | 255 | @Override 256 | public void stop(String reason) { 257 | snap.getProxy().shutdown(LegacyComponentSerializer.legacySection().deserialize(reason)); 258 | } 259 | 260 | @Override 261 | public void registerChannel(String channel) { 262 | snap.getProxy().getChannelRegistrar().register(SnapUtils.createChannelIdentifier(channel)); 263 | channels.add(channel); 264 | } 265 | 266 | @Override 267 | public void unregisterChannel(String channel) { 268 | snap.getProxy().getChannelRegistrar().unregister(SnapUtils.createChannelIdentifier(channel)); 269 | channels.remove(channel); 270 | } 271 | 272 | @Override 273 | public Collection getChannels() { 274 | // TODO: Non-reflection way to access registered channels 275 | if (fIdentifierMap != null) { 276 | try { 277 | Map identifierMap = (Map) fIdentifierMap.get(snap.getProxy().getChannelRegistrar()); 278 | return identifierMap.keySet(); 279 | } catch (IllegalAccessException | ClassCastException e) { 280 | e.printStackTrace(); 281 | } 282 | } 283 | return Collections.unmodifiableSet(channels); 284 | } 285 | 286 | @Override 287 | public String getGameVersion() { 288 | return snap.getProxy().getVersion().getVersion(); 289 | } 290 | 291 | @Override 292 | public int getProtocolVersion() { 293 | // TODO: Get supported protocol versions 294 | snap.unsupported(); 295 | return 0; 296 | } 297 | 298 | @Override 299 | public ServerInfo constructServerInfo(String name, InetSocketAddress address, String motd, boolean restricted) { 300 | // TODO: Server info support 301 | return (ServerInfo) snap.unsupported(); 302 | } 303 | 304 | @Override 305 | public ServerInfo constructServerInfo(String name, SocketAddress address, String motd, boolean restricted) { 306 | // TODO: Server info support 307 | return (ServerInfo) snap.unsupported(); 308 | } 309 | 310 | @Override 311 | public CommandSender getConsole() { 312 | return new SnapCommandSender(snap, snap.getProxy().getConsoleCommandSource()); 313 | } 314 | 315 | @Override 316 | public File getPluginsFolder() { 317 | return snap.getBungeeAdapter().getPluginsFolder(); 318 | } 319 | 320 | @Override 321 | public TaskScheduler getScheduler() { 322 | return scheduler; 323 | } 324 | 325 | @Override 326 | public int getOnlineCount() { 327 | return snap.getProxy().getPlayerCount(); 328 | } 329 | 330 | @Override 331 | public void broadcast(String message) { 332 | broadcast(LegacyComponentSerializer.legacySection().deserialize(message)); 333 | } 334 | 335 | @Override 336 | public void broadcast(BaseComponent... message) { 337 | broadcast(SnapUtils.convertComponent(message)); 338 | } 339 | 340 | @Override 341 | public void broadcast(BaseComponent message) { 342 | broadcast(SnapUtils.convertComponent(message)); 343 | } 344 | 345 | private void broadcast(Component component) { 346 | for (Player player : snap.getProxy().getAllPlayers()) { 347 | player.sendMessage(component); 348 | } 349 | } 350 | 351 | @Override 352 | public Collection getDisabledCommands() { 353 | return Collections.emptySet(); 354 | } 355 | 356 | @Override 357 | public ProxyConfig getConfig() { 358 | com.velocitypowered.api.proxy.config.ProxyConfig config = snap.getProxy().getConfiguration(); 359 | return new ProxyConfig() { 360 | @Override 361 | public int getTimeout() { 362 | return config.getReadTimeout(); 363 | } 364 | 365 | @Override 366 | public String getUuid() { 367 | return statsId; 368 | } 369 | 370 | @Override 371 | public Collection getListeners() { 372 | return listeners; 373 | } 374 | 375 | @Override 376 | public Map getServers() { 377 | return SnapProxyServer.this.getServers(); 378 | } 379 | 380 | @Override 381 | public Map getServersCopy() { 382 | return SnapProxyServer.this.getServersCopy(); 383 | } 384 | 385 | @Override 386 | public ServerInfo getServerInfo(String name) { 387 | return getServers().get(name); 388 | } 389 | 390 | @Override 391 | public ServerInfo addServer(ServerInfo server) { 392 | Optional previous = snap.getProxy().getServer(server.getName()); 393 | 394 | if (previous.isPresent()) { 395 | if (previous.get().getServerInfo().getName().equals(server.getName()) 396 | && previous.get().getServerInfo().getAddress().equals(server.getAddress())) { 397 | // Don't register the same server twice 398 | return server; 399 | } 400 | snap.getProxy().unregisterServer(previous.get().getServerInfo()); 401 | snap.getServers().remove(previous.get().getServerInfo().getName()); 402 | } 403 | 404 | ServerInfo previousInfo = snap.getServerInfo(previous.orElse(null)); 405 | 406 | RegisteredServer rs = snap.getProxy().registerServer( 407 | new com.velocitypowered.api.proxy.server.ServerInfo(server.getName(), server.getAddress())); 408 | snap.getServerInfo(rs); 409 | return previousInfo; 410 | } 411 | 412 | @Override 413 | public boolean addServers(Collection servers) { 414 | boolean changed = false; 415 | for (ServerInfo server : servers) { 416 | if (server != addServer(server)) changed = true; 417 | } 418 | return changed; 419 | } 420 | 421 | @Override 422 | public ServerInfo removeServerNamed(String name) { 423 | return removeServer(getServerInfo(name)); 424 | } 425 | 426 | @Override 427 | public ServerInfo removeServer(ServerInfo server) { 428 | if (server instanceof SnapServerInfo) { 429 | snap.getProxy().unregisterServer(((SnapServerInfo) server).getServer().getServerInfo()); 430 | getServers().remove(server.getName()); 431 | return server; 432 | } 433 | return null; 434 | } 435 | 436 | @Override 437 | public boolean removeServersNamed(Collection names) { 438 | boolean changed = false; 439 | for (String name : names) { 440 | if (null != removeServerNamed(name)) changed = true; 441 | } 442 | return changed; 443 | } 444 | 445 | @Override 446 | public boolean removeServers(Collection servers) { 447 | boolean changed = false; 448 | for (ServerInfo server : servers) { 449 | if (null != removeServer(server)) changed = true; 450 | } 451 | return changed; 452 | } 453 | 454 | @Override 455 | public boolean isOnlineMode() { 456 | return config.isOnlineMode(); 457 | } 458 | 459 | @Override 460 | public boolean isLogCommands() { 461 | snap.unsupported(); 462 | return true; // TODO: Why can't one read that? 463 | } 464 | 465 | @Override 466 | public int getRemotePingCache() { 467 | snap.unsupported(); 468 | return 0; 469 | } 470 | 471 | @Override 472 | public int getPlayerLimit() { 473 | return config.getShowMaxPlayers(); 474 | } 475 | 476 | @Override 477 | public Collection getDisabledCommands() { 478 | return SnapProxyServer.this.getDisabledCommands(); 479 | } 480 | 481 | @Override 482 | public int getServerConnectTimeout() { 483 | return config.getConnectTimeout(); 484 | } 485 | 486 | @Override 487 | public int getRemotePingTimeout() { 488 | return config.getReadTimeout(); 489 | } 490 | 491 | @Override 492 | public int getThrottle() { 493 | return config.getLoginRatelimit(); 494 | } 495 | 496 | @Override 497 | public boolean isIpForward() { 498 | snap.unsupported(); 499 | return true; 500 | } 501 | 502 | @Override 503 | public String getFavicon() { 504 | return config.getFavicon().map(f -> f.getBase64Url()).orElse(""); 505 | } 506 | 507 | @Override 508 | public Favicon getFaviconObject() { 509 | return config.getFavicon().map(f -> Favicon.create(f.getBase64Url())).orElse(null); 510 | } 511 | 512 | @Override 513 | public boolean isLogInitialHandlerConnections() { 514 | // TODO: Somehow read "show-ping-requests" from Velocity config... why is this not exposed?!? 515 | snap.unsupported(); 516 | return false; 517 | } 518 | 519 | @Override 520 | public String getGameVersion() { 521 | // TODO: Allow configuring this? 522 | return snap.getProxy().getVersion().getVersion(); 523 | } 524 | 525 | @Override 526 | public boolean isUseNettyDnsResolver() { 527 | snap.unsupported(); 528 | return false; 529 | } 530 | 531 | @Override 532 | public int getTabThrottle() { 533 | snap.unsupported(); 534 | return 0; 535 | } 536 | 537 | @Override 538 | public boolean isDisableModernTabLimiter() { 539 | snap.unsupported(); 540 | return false; 541 | } 542 | 543 | @Override 544 | public boolean isDisableEntityMetadataRewrite() { 545 | snap.unsupported(); 546 | return true; 547 | } 548 | 549 | @Override 550 | public boolean isDisableTabListRewrite() { 551 | snap.unsupported(); 552 | return true; 553 | } 554 | 555 | @Override 556 | public int getPluginChannelLimit() { 557 | snap.unsupported(); 558 | return Integer.MAX_VALUE; 559 | } 560 | 561 | @Override 562 | public int getPluginChannelNameLimit() { 563 | snap.unsupported(); 564 | return Integer.MAX_VALUE; 565 | } 566 | }; 567 | } 568 | 569 | @Override 570 | public Collection matchPlayer(String match) { 571 | SnapPlayer p = snap.getPlayerNames().get(match); 572 | if (p != null) { 573 | return Collections.singleton(p); 574 | } 575 | Set matched = new HashSet<>(); 576 | for (SnapPlayer value : snap.getPlayers().values()) { 577 | if (value.getName().toLowerCase(Locale.ROOT).startsWith(match.toLowerCase(Locale.ROOT))) { 578 | matched.add(value); 579 | } 580 | } 581 | return matched; 582 | } 583 | 584 | @Override 585 | public Title createTitle() { 586 | return new SnapTitle(); 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/SnapServer.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.proxy.Player; 22 | import com.velocitypowered.api.proxy.ServerConnection; 23 | import com.velocitypowered.api.proxy.server.RegisteredServer; 24 | import de.themoep.snap.Snap; 25 | import de.themoep.snap.SnapUtils; 26 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 27 | import net.md_5.bungee.api.chat.BaseComponent; 28 | import net.md_5.bungee.api.config.ServerInfo; 29 | import net.md_5.bungee.api.connection.Server; 30 | 31 | import java.net.InetSocketAddress; 32 | import java.net.SocketAddress; 33 | 34 | public class SnapServer implements Server { 35 | private final Snap snap; 36 | private final Player player; 37 | private final RegisteredServer server; 38 | private boolean connected = true; 39 | 40 | public SnapServer(Snap snap, ServerConnection serverConnection) { 41 | this(snap, serverConnection.getPlayer(), serverConnection.getServer()); 42 | } 43 | 44 | public SnapServer(Snap snap, Player player, RegisteredServer server) { 45 | this.snap = snap; 46 | this.player = player; 47 | this.server = server; 48 | } 49 | 50 | @Override 51 | public ServerInfo getInfo() { 52 | return snap.getServerInfo(server); 53 | } 54 | 55 | @Override 56 | public void sendData(String channel, byte[] data) { 57 | server.sendPluginMessage(SnapUtils.createChannelIdentifier(channel), data); 58 | } 59 | 60 | @Override 61 | public InetSocketAddress getAddress() { 62 | return server.getServerInfo().getAddress(); 63 | } 64 | 65 | @Override 66 | public SocketAddress getSocketAddress() { 67 | return server.getServerInfo().getAddress(); 68 | } 69 | 70 | @Override 71 | public void disconnect(String reason) { 72 | // TODO: This tries to mirror what Bungee does in that case but might not be exact? 73 | if (server.getServerInfo().getName().equals(snap.getProxy().getConfiguration().getAttemptConnectionOrder().get(0))) { 74 | if (snap.getProxy().getConfiguration().getAttemptConnectionOrder().size() == 1) { 75 | player.disconnect(LegacyComponentSerializer.legacySection().deserialize(reason)); 76 | } else { 77 | snap.getProxy().getServer(snap.getProxy().getConfiguration().getAttemptConnectionOrder().get(1)) 78 | .ifPresent(s -> player.createConnectionRequest(s).fireAndForget()); 79 | } 80 | } else { 81 | snap.getProxy().getServer(snap.getProxy().getConfiguration().getAttemptConnectionOrder().get(0)) 82 | .ifPresent(s -> player.createConnectionRequest(s).fireAndForget()); 83 | } 84 | connected = false; 85 | } 86 | 87 | @Override 88 | public void disconnect(BaseComponent... reason) { 89 | // TODO: This tries to mirror what Bungee does in that case but might not be exact? 90 | if (server.getServerInfo().getName().equals(snap.getProxy().getConfiguration().getAttemptConnectionOrder().get(0))) { 91 | if (snap.getProxy().getConfiguration().getAttemptConnectionOrder().size() == 1) { 92 | player.disconnect(SnapUtils.convertComponent(reason)); 93 | } else { 94 | snap.getProxy().getServer(snap.getProxy().getConfiguration().getAttemptConnectionOrder().get(1)) 95 | .ifPresent(s -> player.createConnectionRequest(s).fireAndForget()); 96 | } 97 | } else { 98 | snap.getProxy().getServer(snap.getProxy().getConfiguration().getAttemptConnectionOrder().get(0)) 99 | .ifPresent(s -> player.createConnectionRequest(s).fireAndForget()); 100 | } 101 | connected = false; 102 | } 103 | 104 | @Override 105 | public void disconnect(BaseComponent reason) { 106 | disconnect(new BaseComponent[]{reason}); 107 | } 108 | 109 | @Override 110 | public boolean isConnected() { 111 | return connected && player.isActive(); 112 | } 113 | 114 | @Override 115 | public Unsafe unsafe() { 116 | return (Unsafe) snap.unsupported("Unsafe is not supported by Snap!"); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/SnapServerInfo.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.proxy.Player; 22 | import com.velocitypowered.api.proxy.server.RegisteredServer; 23 | import de.themoep.snap.Snap; 24 | import de.themoep.snap.SnapUtils; 25 | import net.md_5.bungee.api.Callback; 26 | import net.md_5.bungee.api.CommandSender; 27 | import net.md_5.bungee.api.ServerPing; 28 | import net.md_5.bungee.api.connection.ProxiedPlayer; 29 | 30 | import java.net.InetSocketAddress; 31 | import java.net.SocketAddress; 32 | import java.util.Collection; 33 | import java.util.LinkedHashSet; 34 | import java.util.Set; 35 | 36 | public class SnapServerInfo implements net.md_5.bungee.api.config.ServerInfo { 37 | private final Snap snap; 38 | 39 | private final RegisteredServer server; 40 | 41 | public SnapServerInfo(Snap snap, RegisteredServer server) { 42 | this.snap = snap; 43 | this.server = server; 44 | } 45 | 46 | public RegisteredServer getServer() { 47 | return server; 48 | } 49 | 50 | @Override 51 | public String getName() { 52 | return server.getServerInfo().getName(); 53 | } 54 | 55 | @Override 56 | public InetSocketAddress getAddress() { 57 | return server.getServerInfo().getAddress(); 58 | } 59 | 60 | @Override 61 | public SocketAddress getSocketAddress() { 62 | return getAddress(); 63 | } 64 | 65 | @Override 66 | public Collection getPlayers() { 67 | Set players = new LinkedHashSet<>(); 68 | for (Player player : server.getPlayersConnected()) { 69 | players.add(snap.getPlayer(player)); 70 | } 71 | return players; 72 | } 73 | 74 | @Override 75 | public String getMotd() { 76 | return (String) snap.unsupported("Servers don't have an MOTD in Velocity!"); 77 | } 78 | 79 | @Override 80 | public boolean isRestricted() { 81 | snap.unsupported(); 82 | return false; 83 | } 84 | 85 | @Override 86 | public String getPermission() { 87 | snap.unsupported(); 88 | return null; 89 | } 90 | 91 | @Override 92 | public boolean canAccess(CommandSender sender) { 93 | snap.unsupported(); 94 | return true; 95 | } 96 | 97 | @Override 98 | public void sendData(String channel, byte[] data) { 99 | server.sendPluginMessage(SnapUtils.createChannelIdentifier(channel), data); 100 | } 101 | 102 | @Override 103 | public boolean sendData(String channel, byte[] data, boolean queue) { 104 | return server.sendPluginMessage(SnapUtils.createChannelIdentifier(channel), data); 105 | } 106 | 107 | @Override 108 | public void ping(Callback callback) { 109 | server.ping().whenComplete((p, e) -> callback.done(SnapUtils.convertPing(p), e)); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/SnapTitle.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2020 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import de.themoep.snap.SnapUtils; 22 | import net.md_5.bungee.api.Title; 23 | import net.md_5.bungee.api.chat.BaseComponent; 24 | import net.md_5.bungee.api.connection.ProxiedPlayer; 25 | 26 | import java.time.Duration; 27 | 28 | public class SnapTitle implements Title { 29 | private boolean clear = false; 30 | private BaseComponent[] title = null; 31 | private BaseComponent[] subTitle = null; 32 | private int fadeIn = 20; 33 | private int stay = 60; 34 | private int fadeOut = 20; 35 | 36 | @Override 37 | public Title title(BaseComponent text) { 38 | return title(new BaseComponent[]{text}); 39 | } 40 | 41 | @Override 42 | public Title title(BaseComponent... text) { 43 | this.title = text; 44 | return this; 45 | } 46 | 47 | @Override 48 | public Title subTitle(BaseComponent text) { 49 | return subTitle(new BaseComponent[]{text}); 50 | } 51 | 52 | @Override 53 | public Title subTitle(BaseComponent... text) { 54 | this.subTitle = text; 55 | return this; 56 | } 57 | 58 | @Override 59 | public Title fadeIn(int ticks) { 60 | this.fadeIn = ticks; 61 | return this; 62 | } 63 | 64 | @Override 65 | public Title stay(int ticks) { 66 | this.stay = ticks; 67 | return this; 68 | } 69 | 70 | @Override 71 | public Title fadeOut(int ticks) { 72 | this.fadeOut = ticks; 73 | return this; 74 | } 75 | 76 | @Override 77 | public Title clear() { 78 | clear = true; 79 | return this; 80 | } 81 | 82 | @Override 83 | public Title reset() { 84 | clear(); 85 | title = null; 86 | subTitle = null; 87 | fadeIn = 20; 88 | stay = 60; 89 | fadeOut = 20; 90 | return this; 91 | } 92 | 93 | @Override 94 | public Title send(ProxiedPlayer player) { 95 | if (player instanceof SnapPlayer) { 96 | if (clear) { 97 | ((SnapPlayer) player).getPlayer().clearTitle(); 98 | } else { 99 | ((SnapPlayer) player).getPlayer().showTitle(net.kyori.adventure.title.Title.title( 100 | SnapUtils.convertComponent(title), 101 | SnapUtils.convertComponent(subTitle), 102 | net.kyori.adventure.title.Title.Times.of( 103 | Duration.ofMillis(fadeIn * 50), 104 | Duration.ofMillis(stay * 50), 105 | Duration.ofMillis(fadeOut * 50) 106 | ) 107 | )); 108 | } 109 | } 110 | return this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ChatListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.player.PlayerChatEvent; 23 | import de.themoep.snap.Snap; 24 | import de.themoep.snap.forwarding.SnapPlayer; 25 | import net.md_5.bungee.api.event.ChatEvent; 26 | 27 | public class ChatListener extends ForwardingListener { 28 | 29 | public ChatListener(Snap snap) { 30 | super(snap, ChatEvent.class); 31 | } 32 | 33 | @Subscribe 34 | public void on(PlayerChatEvent event) { 35 | SnapPlayer player = snap.getPlayer(event.getPlayer()); 36 | 37 | ChatEvent e = new ChatEvent( 38 | player, player.getServer(), 39 | event.getResult().getMessage().orElse(event.getMessage()) 40 | ); 41 | e.setCancelled(!event.getResult().isAllowed()); 42 | snap.getBungeeAdapter().getPluginManager().callEvent(e); 43 | if (e.isCancelled()) { 44 | event.setResult(PlayerChatEvent.ChatResult.denied()); 45 | } else { 46 | event.setResult(PlayerChatEvent.ChatResult.message(e.getMessage())); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ClientConnectListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.PostOrder; 22 | import com.velocitypowered.api.event.Subscribe; 23 | import com.velocitypowered.api.event.connection.PreLoginEvent; 24 | import de.themoep.snap.Snap; 25 | import net.kyori.adventure.text.Component; 26 | import net.md_5.bungee.api.event.ClientConnectEvent; 27 | 28 | public class ClientConnectListener extends ForwardingListener { 29 | 30 | // TODO: Find better implementation as this has no real Velocity equivalent 31 | public ClientConnectListener(Snap snap) { 32 | super(snap, ClientConnectEvent.class); 33 | } 34 | 35 | @Subscribe(order = PostOrder.FIRST) 36 | public void on(PreLoginEvent event) { 37 | ClientConnectEvent e = new ClientConnectEvent( 38 | event.getConnection().getRemoteAddress(), 39 | snap.getBungeeAdapter().getProxy().getListener() 40 | ); 41 | e.setCancelled(!event.getResult().isAllowed()); 42 | snap.getBungeeAdapter().getPluginManager().callEvent(e); 43 | if (e.isCancelled()) { 44 | boolean originallyAllowed = event.getResult().isAllowed(); 45 | if (originallyAllowed) { 46 | event.setResult(PreLoginEvent.PreLoginComponentResult.denied(Component.text("ClientConnectEvent Cancelled"))); 47 | } 48 | } else { 49 | event.setResult(PreLoginEvent.PreLoginComponentResult.allowed()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ConnectionInitListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Continuation; 22 | import com.velocitypowered.api.event.PostOrder; 23 | import com.velocitypowered.api.event.Subscribe; 24 | import com.velocitypowered.api.event.connection.PreLoginEvent; 25 | import de.themoep.snap.Snap; 26 | import io.github.waterfallmc.waterfall.event.ConnectionInitEvent; 27 | import net.kyori.adventure.text.Component; 28 | 29 | public class ConnectionInitListener extends ForwardingListener { 30 | 31 | // TODO: Find better implementation as this has no real Velocity equivalent 32 | public ConnectionInitListener(Snap snap) { 33 | super(snap, ConnectionInitEvent.class); 34 | } 35 | 36 | @Subscribe(order = PostOrder.EARLY) 37 | public void on(PreLoginEvent event, Continuation continuation) { 38 | if (!event.getResult().isAllowed()) { 39 | return; 40 | } 41 | 42 | snap.getBungeeAdapter().getPluginManager().callEvent(new ConnectionInitEvent( 43 | event.getConnection().getRemoteAddress(), 44 | snap.getBungeeAdapter().getProxy().getListener(), 45 | (e, t) -> { 46 | if (e.isCancelled()) { 47 | event.setResult(PreLoginEvent.PreLoginComponentResult.denied(Component.text("ConnectionInitEvent Cancelled"))); 48 | } 49 | if (t != null) { 50 | continuation.resumeWithException(t); 51 | } else { 52 | continuation.resume(); 53 | } 54 | } 55 | )); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ForwardingListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.proxy.InboundConnection; 22 | import com.velocitypowered.api.proxy.Player; 23 | import de.themoep.snap.Snap; 24 | import net.md_5.bungee.api.chat.BaseComponent; 25 | import net.md_5.bungee.api.config.ListenerInfo; 26 | import net.md_5.bungee.api.connection.PendingConnection; 27 | import net.md_5.bungee.api.plugin.Event; 28 | 29 | import java.net.InetSocketAddress; 30 | import java.net.SocketAddress; 31 | import java.util.UUID; 32 | import java.util.concurrent.CompletableFuture; 33 | 34 | public abstract class ForwardingListener { 35 | protected final Snap snap; 36 | private final Class forwardedEvent; 37 | 38 | public ForwardingListener(Snap snap, Class forwardedEvent) { 39 | this.snap = snap; 40 | this.forwardedEvent = forwardedEvent; 41 | } 42 | 43 | public Class getForwardedEvent() { 44 | return forwardedEvent; 45 | } 46 | 47 | protected PendingConnection convertConnection(InboundConnection connection) { 48 | return new PendingConnection() { 49 | @Override 50 | public String getName() { 51 | return null; 52 | } 53 | 54 | @Override 55 | public int getVersion() { 56 | return connection.getProtocolVersion().getProtocol(); 57 | } 58 | 59 | @Override 60 | public InetSocketAddress getVirtualHost() { 61 | return connection.getVirtualHost().orElse(null); 62 | } 63 | 64 | @Override 65 | public ListenerInfo getListener() { 66 | return snap.getBungeeAdapter().getProxy().getListener(); 67 | } 68 | 69 | @Override 70 | public String getUUID() { 71 | return null; 72 | } 73 | 74 | @Override 75 | public UUID getUniqueId() { 76 | return null; 77 | } 78 | 79 | @Override 80 | public void setUniqueId(UUID uuid) { 81 | snap.unsupported("Setting UUID of a connection on PlayerHandshakeEvent is not supported in Velocity's API!"); 82 | } 83 | 84 | @Override 85 | public boolean isOnlineMode() { 86 | return false; 87 | } 88 | 89 | @Override 90 | public void setOnlineMode(boolean onlineMode) { 91 | snap.unsupported("Setting online of a connection on PlayerHandshakeEvent is not supported in Velocity's API!"); 92 | } 93 | 94 | @Override 95 | public boolean isLegacy() { 96 | return connection.getProtocolVersion().isLegacy(); 97 | } 98 | 99 | @Override 100 | public boolean isTransferred() { 101 | if (connection instanceof Player player) { 102 | return snap.isTransferred(player.getUniqueId()); 103 | } 104 | snap.unsupported("Tried to check an InboundConnection which is not a Player (" + connection.getClass().getName() + ") for whether it was transferred!"); 105 | return false; 106 | } 107 | 108 | @Override 109 | public CompletableFuture retrieveCookie(String key) { 110 | return snap.retrieveCookie(connection, key); 111 | } 112 | 113 | @Override 114 | public InetSocketAddress getAddress() { 115 | return connection.getRemoteAddress(); 116 | } 117 | 118 | @Override 119 | public SocketAddress getSocketAddress() { 120 | return getAddress(); 121 | } 122 | 123 | @Override 124 | public void disconnect(String reason) { 125 | 126 | } 127 | 128 | @Override 129 | public void disconnect(BaseComponent... reason) { 130 | 131 | } 132 | 133 | @Override 134 | public void disconnect(BaseComponent reason) { 135 | disconnect(new BaseComponent[]{reason}); 136 | } 137 | 138 | @Override 139 | public boolean isConnected() { 140 | return connection.isActive(); 141 | } 142 | 143 | @Override 144 | public Unsafe unsafe() { 145 | return (Unsafe) snap.unsupported("Unsafe is not supported in Snap!"); 146 | } 147 | }; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/LoginListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Continuation; 22 | import com.velocitypowered.api.event.ResultedEvent; 23 | import com.velocitypowered.api.event.Subscribe; 24 | import de.themoep.snap.Snap; 25 | import de.themoep.snap.SnapUtils; 26 | import net.md_5.bungee.api.event.LoginEvent; 27 | 28 | public class LoginListener extends ForwardingListener { 29 | 30 | public LoginListener(Snap snap) { 31 | super(snap, LoginEvent.class); 32 | } 33 | 34 | @Subscribe 35 | public void on(com.velocitypowered.api.event.connection.LoginEvent event, Continuation continuation) { 36 | LoginEvent e = new LoginEvent(snap.getPlayer(event.getPlayer()).getPendingConnection(), (le, t) -> { 37 | if (le.isCancelled() && event.getResult().isAllowed()) { 38 | event.setResult(ResultedEvent.ComponentResult.denied(SnapUtils.convertComponent(le.getCancelReasonComponents()))); 39 | } else if (!le.isCancelled() && !event.getResult().isAllowed()) { 40 | event.setResult(ResultedEvent.ComponentResult.allowed()); 41 | } 42 | if (t != null) { 43 | continuation.resumeWithException(t); 44 | } else { 45 | continuation.resume(); 46 | } 47 | }); 48 | if (!event.getResult().isAllowed()) { 49 | e.setCancelled(true); 50 | event.getResult().getReasonComponent().ifPresent(c -> e.setCancelReason(SnapUtils.convertComponent(c))); 51 | } 52 | snap.getBungeeAdapter().getPluginManager().callEvent(e); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/PlayerDisconnectListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.connection.DisconnectEvent; 23 | import de.themoep.snap.Snap; 24 | import net.md_5.bungee.api.event.PlayerDisconnectEvent; 25 | 26 | public class PlayerDisconnectListener extends ForwardingListener { 27 | 28 | public PlayerDisconnectListener(Snap snap) { 29 | super(snap, PlayerDisconnectEvent.class); 30 | } 31 | 32 | @Subscribe 33 | public void on(DisconnectEvent event) { 34 | snap.getBungeeAdapter().getPluginManager().callEvent(new PlayerDisconnectEvent(snap.getPlayer(event.getPlayer()))); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/PlayerHandshakeListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent; 23 | import de.themoep.snap.Snap; 24 | import net.md_5.bungee.api.event.PlayerHandshakeEvent; 25 | import net.md_5.bungee.protocol.packet.Handshake; 26 | 27 | import java.net.InetSocketAddress; 28 | 29 | public class PlayerHandshakeListener extends ForwardingListener { 30 | 31 | public PlayerHandshakeListener(Snap snap) { 32 | super(snap, PlayerHandshakeEvent.class); 33 | } 34 | 35 | @Subscribe 36 | public void on(ConnectionHandshakeEvent event) { 37 | snap.getBungeeAdapter().getPluginManager().callEvent(new PlayerHandshakeEvent( 38 | convertConnection(event.getConnection()), 39 | new Handshake( 40 | event.getConnection().getProtocolVersion().getProtocol(), 41 | event.getConnection().getVirtualHost().map(InetSocketAddress::getHostString).orElse(null), 42 | event.getConnection().getVirtualHost().map(InetSocketAddress::getPort).orElse(0), 43 | event.getConnection().getProtocolVersion().getProtocol() 44 | ) { 45 | public void setProtocolVersion(int protocolVersion) { 46 | snap.unsupported(); 47 | } 48 | 49 | public void setHost(String host) { 50 | snap.unsupported(); 51 | } 52 | 53 | public void setPort(int port) { 54 | snap.unsupported(); 55 | } 56 | 57 | public void setRequestedProtocol(int requestedProtocol) { 58 | snap.unsupported(); 59 | } 60 | } 61 | )); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/PluginMessageListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult; 23 | import com.velocitypowered.api.proxy.Player; 24 | import com.velocitypowered.api.proxy.ServerConnection; 25 | import com.velocitypowered.api.proxy.server.RegisteredServer; 26 | import de.themoep.snap.Snap; 27 | import de.themoep.snap.forwarding.SnapServer; 28 | import net.md_5.bungee.api.connection.Connection; 29 | import net.md_5.bungee.api.event.PluginMessageEvent; 30 | 31 | public class PluginMessageListener extends ForwardingListener { 32 | 33 | public PluginMessageListener(Snap snap) { 34 | super(snap, PluginMessageEvent.class); 35 | } 36 | 37 | @Subscribe 38 | public void on(com.velocitypowered.api.event.connection.PluginMessageEvent event) { 39 | Connection sender = convert(event.getSource(), event.getTarget()); 40 | Connection receiver = convert(event.getTarget(), event.getSource()); 41 | 42 | PluginMessageEvent e = new PluginMessageEvent(sender, receiver, event.getIdentifier().getId(), event.getData()); 43 | e.setCancelled(!event.getResult().isAllowed()); 44 | snap.getBungeeAdapter().getPluginManager().callEvent(e); 45 | event.setResult(e.isCancelled() ? ForwardResult.handled() : ForwardResult.forward()); 46 | } 47 | 48 | private Connection convert(Object o, Object other) { 49 | if (o instanceof Player) { 50 | return snap.getPlayer((Player) o); 51 | } else if (o instanceof ServerConnection) { 52 | return new SnapServer(snap, (ServerConnection) o); 53 | } else if (o instanceof RegisteredServer) { 54 | if (other instanceof Player && ((Player) other).getCurrentServer().isPresent() && ((Player) other).getCurrentServer().get().getServer() == o) { 55 | return new SnapServer(snap, ((Player) other).getCurrentServer().get()); 56 | } else if (!((RegisteredServer) o).getPlayersConnected().isEmpty()) { 57 | return new SnapServer(snap, ((RegisteredServer) o).getPlayersConnected().iterator().next().getCurrentServer().get()); 58 | } 59 | } 60 | return null; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/PostLoginListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Continuation; 22 | import com.velocitypowered.api.event.PostOrder; 23 | import com.velocitypowered.api.event.Subscribe; 24 | import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; 25 | import de.themoep.snap.Snap; 26 | import de.themoep.snap.forwarding.SnapServerInfo; 27 | import net.md_5.bungee.api.event.PostLoginEvent; 28 | 29 | public class PostLoginListener extends ForwardingListener { 30 | 31 | public PostLoginListener(Snap snap) { 32 | super(snap, PostLoginEvent.class); 33 | } 34 | 35 | @Subscribe(order = PostOrder.FIRST) 36 | public void on(PlayerChooseInitialServerEvent event, Continuation continuation) { 37 | snap.getBungeeAdapter().getPluginManager().callEvent(new PostLoginEvent( 38 | snap.getPlayer(event.getPlayer()), 39 | snap.getServerInfo(event.getInitialServer().orElse(null)), 40 | (e, t) -> { 41 | if (e.getTarget() != null) { 42 | event.setInitialServer(((SnapServerInfo) e.getTarget()).getServer()); 43 | } else { 44 | event.setInitialServer(null); 45 | } 46 | if (t != null) { 47 | continuation.resumeWithException(t); 48 | } else { 49 | continuation.resume(); 50 | } 51 | } 52 | )); 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/PreLoginListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Continuation; 22 | import com.velocitypowered.api.event.Subscribe; 23 | import de.themoep.snap.Snap; 24 | import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; 25 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 26 | import net.md_5.bungee.api.chat.BaseComponent; 27 | import net.md_5.bungee.api.config.ListenerInfo; 28 | import net.md_5.bungee.api.connection.PendingConnection; 29 | import net.md_5.bungee.api.event.PreLoginEvent; 30 | 31 | import java.net.InetSocketAddress; 32 | import java.net.SocketAddress; 33 | import java.util.UUID; 34 | import java.util.concurrent.CompletableFuture; 35 | 36 | public class PreLoginListener extends ForwardingListener { 37 | 38 | public PreLoginListener(Snap snap) { 39 | super(snap, PreLoginEvent.class); 40 | } 41 | 42 | @Subscribe 43 | public void on(com.velocitypowered.api.event.connection.PreLoginEvent event, Continuation continuation) { 44 | if (!event.getResult().isAllowed()) { 45 | return; 46 | } 47 | 48 | snap.getBungeeAdapter().getPluginManager().callEvent(new PreLoginEvent(new PendingConnection() { 49 | @Override 50 | public String getName() { 51 | return event.getUsername(); 52 | } 53 | 54 | @Override 55 | public int getVersion() { 56 | return event.getConnection().getProtocolVersion().getProtocol(); 57 | } 58 | 59 | @Override 60 | public InetSocketAddress getVirtualHost() { 61 | return event.getConnection().getVirtualHost().orElse(null); 62 | } 63 | 64 | @Override 65 | public ListenerInfo getListener() { 66 | return snap.getBungeeAdapter().getProxy().getListener(); 67 | } 68 | 69 | @Override 70 | public String getUUID() { 71 | return null; 72 | } 73 | 74 | @Override 75 | public UUID getUniqueId() { 76 | return null; 77 | } 78 | 79 | @Override 80 | public void setUniqueId(UUID uuid) { 81 | if (event.getResult() == com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.forceOnlineMode()) { 82 | throw new IllegalStateException("Can only set uuid when online mode is false"); 83 | } 84 | snap.cacheUuidForGameprofile(event.getUsername(), uuid); 85 | } 86 | 87 | @Override 88 | public boolean isOnlineMode() { 89 | if (event.getResult() == com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.forceOnlineMode()) { 90 | return true; 91 | } else if (event.getResult() == com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.forceOfflineMode()) { 92 | return false; 93 | } 94 | throw new UnsupportedOperationException("Getting the online mode of a connection on PreLoginEvent is not supported in Snap!"); 95 | } 96 | 97 | @Override 98 | public void setOnlineMode(boolean onlineMode) { 99 | if (onlineMode) { 100 | event.setResult(com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.forceOnlineMode()); 101 | } else { 102 | event.setResult(com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.forceOfflineMode()); 103 | } 104 | } 105 | 106 | @Override 107 | public boolean isLegacy() { 108 | return event.getConnection().getProtocolVersion().isLegacy(); 109 | } 110 | 111 | @Override 112 | public boolean isTransferred() { 113 | return snap.isTransferred(event.getUniqueId()); 114 | } 115 | 116 | @Override 117 | public CompletableFuture retrieveCookie(String key) { 118 | return snap.retrieveCookie(event.getConnection(), key); 119 | } 120 | 121 | @Override 122 | public InetSocketAddress getAddress() { 123 | return event.getConnection().getRemoteAddress(); 124 | } 125 | 126 | @Override 127 | public SocketAddress getSocketAddress() { 128 | return getAddress(); 129 | } 130 | 131 | @Override 132 | public void disconnect(String reason) { 133 | event.setResult(com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.denied(LegacyComponentSerializer.legacySection().deserialize(reason))); 134 | } 135 | 136 | @Override 137 | public void disconnect(BaseComponent... reason) { 138 | event.setResult(com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.denied(BungeeComponentSerializer.get().deserialize(reason))); 139 | } 140 | 141 | @Override 142 | public void disconnect(BaseComponent reason) { 143 | disconnect(new BaseComponent[]{reason}); 144 | } 145 | 146 | @Override 147 | public boolean isConnected() { 148 | return event.getConnection().isActive(); 149 | } 150 | 151 | @Override 152 | public Unsafe unsafe() { 153 | return (Unsafe) snap.unsupported("Unsafe is not supported in Snap!"); 154 | } 155 | }, (e, t) -> { 156 | if (e.isCancelled()) { 157 | event.setResult(com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult.denied( 158 | BungeeComponentSerializer.get().deserialize(e .getCancelReasonComponents()))); 159 | } 160 | if (t != null) { 161 | continuation.resumeWithException(t); 162 | } else { 163 | continuation.resume(); 164 | } 165 | })); 166 | 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ProxyPingListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Continuation; 22 | import com.velocitypowered.api.event.Subscribe; 23 | import de.themoep.snap.Snap; 24 | import de.themoep.snap.SnapUtils; 25 | import net.md_5.bungee.api.event.ProxyPingEvent; 26 | 27 | public class ProxyPingListener extends ForwardingListener { 28 | 29 | public ProxyPingListener(Snap snap) { 30 | super(snap, ProxyPingEvent.class); 31 | } 32 | 33 | @Subscribe 34 | public void on(com.velocitypowered.api.event.proxy.ProxyPingEvent event, Continuation continuation) { 35 | snap.getBungeeAdapter().getPluginManager().callEvent(new ProxyPingEvent( 36 | convertConnection(event.getConnection()), 37 | SnapUtils.convertPing(event.getPing()), 38 | (e, t) -> { 39 | event.setPing(SnapUtils.convertPing(e.getResponse())); 40 | if (t != null) { 41 | continuation.resumeWithException(t); 42 | } else { 43 | continuation.resume(); 44 | } 45 | })); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ProxyQueryListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import de.themoep.snap.Snap; 23 | import io.github.waterfallmc.waterfall.QueryResult; 24 | import io.github.waterfallmc.waterfall.event.ProxyQueryEvent; 25 | 26 | import java.util.ArrayList; 27 | 28 | public class ProxyQueryListener extends ForwardingListener { 29 | 30 | public ProxyQueryListener(Snap snap) { 31 | super(snap, ProxyQueryEvent.class); 32 | } 33 | 34 | @Subscribe 35 | public void on(com.velocitypowered.api.event.query.ProxyQueryEvent event) { 36 | QueryResult r = snap.getBungeeAdapter().getPluginManager().callEvent(new ProxyQueryEvent( 37 | snap.getBungeeAdapter().getProxy().getListener(), 38 | new QueryResult( 39 | event.getResponse().getHostname(), 40 | "SMP", 41 | event.getResponse().getMap(), 42 | event.getResponse().getCurrentPlayers(), 43 | event.getResponse().getMaxPlayers(), 44 | event.getResponse().getProxyPort(), 45 | event.getResponse().getProxyHost(), 46 | "MINECRAFT", 47 | new ArrayList<>(event.getResponse().getPlayers()), 48 | event.getResponse().getGameVersion() 49 | ) 50 | )).getResult(); 51 | 52 | event.setResponse(event.getResponse().toBuilder() 53 | .hostname(r.getMotd()) 54 | //.gameType(r.getGameType()) // TODO: Not supported 55 | .map(r.getWorldName()) 56 | .currentPlayers(r.getOnlinePlayers()) 57 | .maxPlayers(r.getMaxPlayers()) 58 | .proxyPort(r.getPort()) 59 | .proxyHost(r.getAddress()) 60 | .players(r.getPlayers()) 61 | .gameVersion(r.getVersion()) 62 | .build()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ProxyReloadListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import de.themoep.snap.Snap; 23 | import net.md_5.bungee.api.event.ProxyReloadEvent; 24 | 25 | public class ProxyReloadListener extends ForwardingListener { 26 | 27 | public ProxyReloadListener(Snap snap) { 28 | super(snap, ProxyReloadEvent.class); 29 | } 30 | 31 | @Subscribe 32 | public void on(com.velocitypowered.api.event.proxy.ProxyReloadEvent event) { 33 | snap.getBungeeAdapter().getPluginManager().callEvent(new ProxyReloadEvent(snap.getBungeeAdapter().getProxy().getConsole())); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ServerConnectListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.player.ServerPreConnectEvent; 23 | import de.themoep.snap.Snap; 24 | import de.themoep.snap.forwarding.SnapServerInfo; 25 | import net.md_5.bungee.api.config.ServerInfo; 26 | import net.md_5.bungee.api.event.ServerConnectEvent; 27 | 28 | import java.util.Objects; 29 | 30 | public class ServerConnectListener extends ForwardingListener { 31 | 32 | public ServerConnectListener(Snap snap) { 33 | super(snap, ServerConnectEvent.class); 34 | } 35 | 36 | @Subscribe 37 | public void on(ServerPreConnectEvent event) { 38 | ServerInfo targetServer = snap.getServerInfo(event.getResult().getServer() 39 | .orElse(snap.getProxy().getConfiguration().getAttemptConnectionOrder().stream() 40 | .map(serverName -> snap.getProxy().getServer(serverName).orElse(null)) 41 | .filter(Objects::nonNull) 42 | .findFirst() 43 | .orElse(null))); 44 | 45 | if (targetServer == null) { 46 | event.setResult(ServerPreConnectEvent.ServerResult.denied()); 47 | snap.getLogger().warn("No target server found for " + event.getPlayer().getUsername() + "! Denying connection. Please make sure you have valid servers in your 'try' config list!"); 48 | return; 49 | } 50 | 51 | ServerConnectEvent e = new ServerConnectEvent( 52 | snap.getPlayer(event.getPlayer()), 53 | targetServer, 54 | ServerConnectEvent.Reason.UNKNOWN, 55 | null 56 | ); 57 | e.setCancelled(!event.getResult().isAllowed()); 58 | snap.getBungeeAdapter().getPluginManager().callEvent(e); 59 | if (e.isCancelled()) { 60 | event.setResult(ServerPreConnectEvent.ServerResult.denied()); 61 | } else { 62 | event.setResult(ServerPreConnectEvent.ServerResult.allowed(((SnapServerInfo) e.getTarget()).getServer())); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ServerConnectedListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import de.themoep.snap.Snap; 23 | import de.themoep.snap.forwarding.SnapServer; 24 | import net.md_5.bungee.api.event.ServerConnectedEvent; 25 | 26 | public class ServerConnectedListener extends ForwardingListener { 27 | 28 | public ServerConnectedListener(Snap snap) { 29 | super(snap, ServerConnectedEvent.class); 30 | } 31 | 32 | @Subscribe 33 | public void on(com.velocitypowered.api.event.player.ServerConnectedEvent event) { 34 | snap.getBungeeAdapter().getPluginManager().callEvent(new ServerConnectedEvent( 35 | snap.getPlayer(event.getPlayer()), 36 | new SnapServer(snap, event.getPlayer(), event.getServer())) 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ServerDisconnectListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.PostOrder; 22 | import com.velocitypowered.api.event.Subscribe; 23 | import com.velocitypowered.api.event.player.KickedFromServerEvent; 24 | import de.themoep.snap.Snap; 25 | import net.md_5.bungee.api.event.ServerDisconnectEvent; 26 | 27 | public class ServerDisconnectListener extends ForwardingListener { 28 | 29 | // TODO: Find better implementation as this has no real Velocity equivalent 30 | public ServerDisconnectListener(Snap snap) { 31 | super(snap, ServerDisconnectEvent.class); 32 | } 33 | 34 | @Subscribe(order = PostOrder.LAST) 35 | public void on(KickedFromServerEvent event) { 36 | snap.getBungeeAdapter().getPluginManager().callEvent(new ServerDisconnectEvent( 37 | snap.getPlayer(event.getPlayer()), 38 | snap.getServerInfo(event.getServer()) 39 | )); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ServerKickListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.player.KickedFromServerEvent; 23 | import de.themoep.snap.Snap; 24 | import de.themoep.snap.SnapUtils; 25 | import de.themoep.snap.forwarding.SnapServerInfo; 26 | import net.md_5.bungee.api.event.ServerKickEvent; 27 | 28 | public class ServerKickListener extends ForwardingListener { 29 | 30 | public ServerKickListener(Snap snap) { 31 | super(snap, ServerKickEvent.class); 32 | } 33 | 34 | @Subscribe 35 | public void on(KickedFromServerEvent event) { 36 | ServerKickEvent e = new ServerKickEvent( 37 | snap.getPlayer(event.getPlayer()), 38 | snap.getServerInfo(event.getServer()), 39 | SnapUtils.convertComponent(event.getServerKickReason().orElse(null)), 40 | event.getResult() instanceof KickedFromServerEvent.RedirectPlayer ? snap.getServerInfo(((KickedFromServerEvent.RedirectPlayer) event.getResult()).getServer()) : null, 41 | event.kickedDuringServerConnect() ? ServerKickEvent.State.CONNECTING : ServerKickEvent.State.CONNECTED, 42 | ServerKickEvent.Cause.UNKNOWN 43 | ); 44 | e.setCancelled(!event.getResult().isAllowed()); 45 | snap.getBungeeAdapter().getPluginManager().callEvent(e); 46 | if (e.isCancelled()) { 47 | if (e.getCancelServer() != null) { 48 | event.setResult(KickedFromServerEvent.RedirectPlayer.create(((SnapServerInfo) e.getCancelServer()).getServer(), SnapUtils.convertComponent(e.getKickReasonComponent()))); 49 | } else { 50 | event.setResult(KickedFromServerEvent.Notify.create(SnapUtils.convertComponent(e.getKickReasonComponent()))); 51 | } 52 | } else { 53 | event.setResult(KickedFromServerEvent.DisconnectPlayer.create(SnapUtils.convertComponent(e.getKickReasonComponent()))); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/ServerSwitchListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.player.ServerPostConnectEvent; 23 | import de.themoep.snap.Snap; 24 | import net.md_5.bungee.api.event.ServerSwitchEvent; 25 | 26 | public class ServerSwitchListener extends ForwardingListener { 27 | 28 | public ServerSwitchListener(Snap snap) { 29 | super(snap, ServerSwitchEvent.class); 30 | } 31 | 32 | @Subscribe 33 | public void on(ServerPostConnectEvent event) { 34 | snap.getBungeeAdapter().getPluginManager().callEvent(new ServerSwitchEvent(snap.getPlayer(event.getPlayer()), snap.getServerInfo(event.getPreviousServer()))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/SettingsChangedListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; 23 | import de.themoep.snap.Snap; 24 | import net.md_5.bungee.api.event.SettingsChangedEvent; 25 | 26 | public class SettingsChangedListener extends ForwardingListener { 27 | 28 | public SettingsChangedListener(Snap snap) { 29 | super(snap, SettingsChangedEvent.class); 30 | } 31 | 32 | @Subscribe 33 | public void on(PlayerSettingsChangedEvent event) { 34 | snap.getBungeeAdapter().getPluginManager().callEvent(new SettingsChangedEvent(snap.getPlayer(event.getPlayer()))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/themoep/snap/forwarding/listener/TabCompleteResponseListener.java: -------------------------------------------------------------------------------- 1 | package de.themoep.snap.forwarding.listener; 2 | 3 | /* 4 | * Snap 5 | * Copyright (c) 2021 Max Lee aka Phoenix616 (max@themoep.de) 6 | * 7 | * This program is free software: you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation, either 10 | * version 3 of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this program. If not, see . 19 | */ 20 | 21 | import com.velocitypowered.api.event.Subscribe; 22 | import com.velocitypowered.api.event.player.TabCompleteEvent; 23 | import de.themoep.snap.Snap; 24 | import de.themoep.snap.forwarding.SnapServer; 25 | import net.md_5.bungee.api.event.TabCompleteResponseEvent; 26 | 27 | public class TabCompleteResponseListener extends ForwardingListener { 28 | 29 | public TabCompleteResponseListener(Snap snap) { 30 | super(snap, TabCompleteResponseEvent.class); 31 | } 32 | 33 | @Subscribe 34 | public void on(TabCompleteEvent event) { 35 | TabCompleteResponseEvent e = snap.getBungeeAdapter().getPluginManager().callEvent(new TabCompleteResponseEvent( 36 | event.getPlayer().getCurrentServer().map(s -> new SnapServer(snap, s)).orElse(null), 37 | snap.getPlayer(event.getPlayer()), 38 | event.getSuggestions() 39 | )); 40 | if (e.isCancelled()) { 41 | event.getSuggestions().clear(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, md_5. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * The name of the author may not be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 17 | * You may not use the software for commercial software hosting services without 18 | * written permission from the author. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package net.md_5.bungee.api.plugin; 34 | 35 | import com.google.common.base.Preconditions; 36 | import com.google.common.io.ByteStreams; 37 | import java.io.File; 38 | import java.io.IOException; 39 | import java.io.InputStream; 40 | import java.net.URL; 41 | import java.net.URLClassLoader; 42 | import java.security.CodeSigner; 43 | import java.security.CodeSource; 44 | import java.util.Iterator; 45 | import java.util.Set; 46 | import java.util.concurrent.CopyOnWriteArraySet; 47 | import java.util.jar.JarEntry; 48 | import java.util.jar.JarFile; 49 | import java.util.jar.Manifest; 50 | import net.md_5.bungee.api.ProxyServer; 51 | 52 | final class PluginClassloader extends URLClassLoader { 53 | 54 | private static final Set allLoaders = new CopyOnWriteArraySet<>(); 55 | // 56 | private final ProxyServer proxy; 57 | private final PluginDescription desc; 58 | private final JarFile jar; 59 | private final Manifest manifest; 60 | private final URL url; 61 | private final ClassLoader libraryLoader; 62 | // 63 | private Plugin plugin; 64 | 65 | public PluginClassloader(ProxyServer proxy, PluginDescription desc, File file, ClassLoader libraryLoader) throws IOException { 66 | super(new URL[]{file.toURI().toURL()}, proxy.getClass().getClassLoader()); // Snap - Add parent class loader 67 | this.proxy = proxy; 68 | this.desc = desc; 69 | this.jar = new JarFile(file); 70 | this.manifest = this.jar.getManifest(); 71 | this.url = file.toURI().toURL(); 72 | this.libraryLoader = libraryLoader; 73 | allLoaders.add(this); 74 | } 75 | 76 | @Override 77 | protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 78 | return this.loadClass0(name, resolve, true, true); 79 | } 80 | 81 | private Class loadClass0(String name, boolean resolve, boolean checkOther, boolean checkLibraries) throws ClassNotFoundException { 82 | try { 83 | return super.loadClass(name, resolve); 84 | } catch (ClassNotFoundException var10) { 85 | if (checkLibraries && this.libraryLoader != null) { 86 | try { 87 | return this.libraryLoader.loadClass(name); 88 | } catch (ClassNotFoundException var9) { 89 | ; 90 | } 91 | } 92 | 93 | if (checkOther) { 94 | Iterator var5 = allLoaders.iterator(); 95 | 96 | while(true) { 97 | PluginClassloader loader; 98 | do { 99 | if (!var5.hasNext()) { 100 | throw new ClassNotFoundException(name); 101 | } 102 | 103 | loader = (PluginClassloader)var5.next(); 104 | } while(loader == this); 105 | 106 | try { 107 | return loader.loadClass0(name, resolve, false, this.proxy.getPluginManager().isTransitiveDepend(this.desc, loader.desc)); 108 | } catch (ClassNotFoundException var8) { 109 | ; 110 | } 111 | } 112 | } else { 113 | throw new ClassNotFoundException(name); 114 | } 115 | } 116 | } 117 | 118 | protected Class findClass(String name) throws ClassNotFoundException { 119 | String path = name.replace('.', '/').concat(".class"); 120 | JarEntry entry = this.jar.getJarEntry(path); 121 | if (entry != null) { 122 | byte[] classBytes; 123 | try { 124 | InputStream is = this.jar.getInputStream(entry); 125 | 126 | try { 127 | classBytes = ByteStreams.toByteArray(is); 128 | } catch (Throwable var9) { 129 | if (is != null) { 130 | try { 131 | is.close(); 132 | } catch (Throwable var8) { 133 | var9.addSuppressed(var8); 134 | } 135 | } 136 | 137 | throw var9; 138 | } 139 | 140 | if (is != null) { 141 | is.close(); 142 | } 143 | } catch (IOException var10) { 144 | throw new ClassNotFoundException(name, var10); 145 | } 146 | 147 | int dot = name.lastIndexOf(46); 148 | if (dot != -1) { 149 | String pkgName = name.substring(0, dot); 150 | if (this.getPackage(pkgName) == null) { 151 | try { 152 | if (this.manifest != null) { 153 | this.definePackage(pkgName, this.manifest, this.url); 154 | } else { 155 | this.definePackage(pkgName, (String)null, (String)null, (String)null, (String)null, (String)null, (String)null, (URL)null); 156 | } 157 | } catch (IllegalArgumentException var11) { 158 | if (this.getPackage(pkgName) == null) { 159 | throw new IllegalStateException("Cannot find package " + pkgName); 160 | } 161 | } 162 | } 163 | } 164 | 165 | CodeSigner[] signers = entry.getCodeSigners(); 166 | CodeSource source = new CodeSource(this.url, signers); 167 | return this.defineClass(name, classBytes, 0, classBytes.length, source); 168 | } else { 169 | return super.findClass(name); 170 | } 171 | } 172 | 173 | public void close() throws IOException { 174 | try { 175 | super.close(); 176 | } finally { 177 | this.jar.close(); 178 | } 179 | 180 | } 181 | 182 | void init(Plugin plugin) { 183 | Preconditions.checkArgument(plugin != null, "plugin"); 184 | Preconditions.checkArgument(plugin.getClass().getClassLoader() == this, "Plugin has incorrect ClassLoader"); 185 | if (this.plugin != null) { 186 | throw new IllegalArgumentException("Plugin already initialized!"); 187 | } else { 188 | this.plugin = plugin; 189 | plugin.init(this.proxy, this.desc); 190 | } 191 | } 192 | 193 | public String toString() { 194 | return "PluginClassloader(desc=" + this.desc + ")"; 195 | } 196 | 197 | static { 198 | ClassLoader.registerAsParallelCapable(); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/main/resources/snap.conf: -------------------------------------------------------------------------------- 1 | # Whether to throw exceptions when accessing methods that aren't supported 2 | # Setting this to false makes them return sensible values but they might not be correct 3 | throw-unsupported-exception = true 4 | 5 | # Whether to register all forwarding listeners even though no plugin needs it 6 | # This is required if a plugin tries to dynamically register events after its onEnable method was called 7 | register-all-listeners = false 8 | 9 | # The stats/metrics ID of this proxy, ideally the same as in the velocity.toml 10 | stats-id = "" -------------------------------------------------------------------------------- /src/main/resources/velocity-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "snap", 3 | "name": "Snap", 4 | "version": "${minecraft.plugin.version}", 5 | "main": "de.themoep.snap.Snap", 6 | "authors": ["Phoenix616"] 7 | } --------------------------------------------------------------------------------