├── .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 extends Event> forwardedEvent;
37 |
38 | public ForwardingListener(Snap snap, Class extends Event> forwardedEvent) {
39 | this.snap = snap;
40 | this.forwardedEvent = forwardedEvent;
41 | }
42 |
43 | public Class extends Event> 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 | }
--------------------------------------------------------------------------------