├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── src └── claj │ ├── Redirector.java │ ├── Blacklist.java │ ├── Room.java │ ├── Main.java │ ├── Serializer.java │ ├── Control.java │ └── Distributor.java ├── README.md └── gradlew /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Copy-Link-and-Join/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/* 2 | .settings/* 3 | .vscode/* 4 | 5 | bin/* 6 | lib/* 7 | build/* 8 | 9 | *.classpath 10 | *.project 11 | *.bat 12 | *.xml 13 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/claj/Redirector.java: -------------------------------------------------------------------------------- 1 | package claj; 2 | 3 | import arc.net.Connection; 4 | import arc.net.DcReason; 5 | import arc.net.NetListener; 6 | 7 | /** 8 | * Contains a host and a client, redirects packets from one to the other. 9 | * 10 | * @author xzxADIxzx 11 | */ 12 | public class Redirector implements NetListener { 13 | 14 | public Connection host, client; 15 | 16 | public Redirector(Connection host) { 17 | this.host = host; 18 | } 19 | 20 | @Override 21 | public void disconnected(Connection connection, DcReason reason) { 22 | host.close(DcReason.closed); 23 | if (client != null) client.close(DcReason.closed); 24 | } 25 | 26 | @Override 27 | public void received(Connection connection, Object object) { 28 | var receiver = connection == host ? client : host; 29 | if (receiver != null) receiver.sendTCP(object); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Copy Link and Join 2 | Provides the ability to transfer packets between Mindustry clients for network play. 3 | 4 | ## How to use 5 | Everything you need to connect is in my [Scheme Size](https://github.com/xzxADIxzx/Scheme-Size) mod. 6 | Just host the game and create a room in a special dialog next to the open server button, then copy the link and send it to your friend who can connect using it. 7 | 8 | ## How to host server 9 | Download the repository and compile on any version of Java using `./gradlew jar` command. Start the server with the command `java -jar Copy-Link-and-Join.jar port` and replace port with the one you need. 10 | 11 | ## Local definitions 12 | host - the player who hosted the game and created a room on the server. 13 | server - a remote machine that transmits packets from a client to a host. 14 | client - a player connecting via a link to a host through a server. 15 | link - a string containing the room key, ip and port of the server. 16 | room - an object that exists only on the server, serving as a container for connections to the host and client. -------------------------------------------------------------------------------- /src/claj/Blacklist.java: -------------------------------------------------------------------------------- 1 | package claj; 2 | 3 | import arc.struct.Seq; 4 | import arc.util.Http; 5 | import arc.util.Log; 6 | import arc.util.serialization.Jval; 7 | 8 | public class Blacklist { 9 | 10 | public static final String actionsURL = "https://api.github.com/meta"; 11 | public static final Seq ips = new Seq<>(); 12 | 13 | public static void refresh() { 14 | Http.get(actionsURL, result -> { 15 | var json = Jval.read(result.getResultAsString()); 16 | json.get("actions").asArray().each(element -> { 17 | String ip = element.asString(); 18 | if (ip.charAt(4) != ':') ips.add(ip); // skip IPv6 19 | }); 20 | 21 | Log.info("Added @ GitHub Actions IPs to blacklist.", ips.size); 22 | }, error -> Log.err("Failed to fetch GitHub Actions IPs", error)); 23 | } 24 | 25 | public static void add(String ip) { 26 | ips.add(ip); 27 | } 28 | 29 | public static boolean contains(String ip) { 30 | return ips.contains(ip); 31 | } 32 | 33 | public static void remove(String ip) { 34 | ips.remove(ip); 35 | } 36 | 37 | public static void clear() { 38 | ips.clear(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/claj/Room.java: -------------------------------------------------------------------------------- 1 | package claj; 2 | 3 | import arc.net.Connection; 4 | import arc.net.DcReason; 5 | import arc.struct.Seq; 6 | import arc.util.Log; 7 | 8 | /** 9 | * Represents a room containing a host and redirectors. 10 | * 11 | * @author xzxADIxzx 12 | */ 13 | public class Room { 14 | 15 | public String link; 16 | 17 | public Connection host; 18 | public Seq redirectors = new Seq<>(); 19 | 20 | public Room(String link, Connection host) { 21 | this.link = link; 22 | this.host = host; 23 | 24 | sendMessage("new"); // there must be at least one empty redirector in the room 25 | sendMessage("Hello, it's me, [#0096FF]xzxADIxzx#7729[], the creator of CLaJ."); // some contact info 26 | sendMessage("I just want to say that if you have any problems, you can always message me on Discord."); 27 | 28 | Log.info("Room @ created!", link); 29 | } 30 | 31 | public void close() { 32 | // rooms only closes if the host left, so there's no point in disconnecting it again 33 | redirectors.each(r -> r.disconnected(null, DcReason.closed)); 34 | 35 | Log.info("Room @ closed.", link); 36 | } 37 | 38 | public void sendMessage(String message) { 39 | host.sendTCP(message); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/claj/Main.java: -------------------------------------------------------------------------------- 1 | package claj; 2 | 3 | import arc.net.ArcNet; 4 | import arc.net.Connection; 5 | import arc.util.Log; 6 | 7 | import java.time.LocalDateTime; 8 | import java.time.format.DateTimeFormatter; 9 | 10 | public class Main { 11 | 12 | public static final String[] tags = { "&lc&fb[D]&fr", "&lb&fb[I]&fr", "&ly&fb[W]&fr", "&lr&fb[E]", "" }; 13 | public static final DateTimeFormatter dateTime = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); 14 | 15 | public static void main(String[] args) { 16 | ArcNet.errorHandler = Log::err; 17 | Log.logger = (level, text) -> { // this is how fashionable i am 18 | String result = Log.format("&lk&fb[" + dateTime.format(LocalDateTime.now()) + "]&fr " + tags[level.ordinal()] + " " + text + "&fr"); 19 | System.out.println(result); 20 | }; 21 | 22 | var distributor = new Distributor(); 23 | new Control(distributor); 24 | 25 | try { 26 | if (args.length == 0) throw new RuntimeException("Need a port as an argument!"); 27 | distributor.run(Integer.parseInt(args[0])); 28 | } catch (Throwable error) { 29 | Log.err("Could not to load redirect system", error); 30 | } 31 | } 32 | 33 | public static String getIP(Connection connection) { 34 | return connection.getRemoteAddressTCP().getAddress().getHostAddress(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/claj/Serializer.java: -------------------------------------------------------------------------------- 1 | package claj; 2 | 3 | import arc.net.FrameworkMessage; 4 | import arc.net.FrameworkMessage.*; 5 | import arc.util.io.*; 6 | import arc.net.NetSerializer; 7 | 8 | import java.nio.ByteBuffer; 9 | 10 | public class Serializer implements NetSerializer { 11 | 12 | public static final byte frameworkID = -2, linkID = -3; 13 | public ByteBuffer last = ByteBuffer.allocate(8192); 14 | 15 | @Override 16 | public void write(ByteBuffer buffer, Object object) { 17 | if (object instanceof ByteBuffer raw) { 18 | buffer.put(raw); 19 | } else if (object instanceof FrameworkMessage message) { 20 | buffer.put(frameworkID); 21 | writeFramework(buffer, message); 22 | } else if (object instanceof String link) { 23 | buffer.put(linkID); 24 | Writes.get(new ByteBufferOutput(buffer)).str(link); 25 | } 26 | } 27 | 28 | @Override 29 | public Object read(ByteBuffer buffer) { 30 | int lastPosition = buffer.position(); 31 | byte id = buffer.get(); 32 | 33 | if (id == frameworkID) return readFramework(buffer); 34 | if (id == linkID) return Reads.get(new ByteBufferInput(buffer)).str(); 35 | 36 | last.clear(); 37 | last.put(buffer.position(lastPosition)); 38 | last.limit(buffer.limit() - lastPosition); 39 | 40 | return last.position(0); 41 | } 42 | 43 | public void writeFramework(ByteBuffer buffer, FrameworkMessage message) { 44 | if (message instanceof Ping ping) buffer.put((byte) 0).putInt(ping.id).put(ping.isReply ? (byte) 1 : 0); 45 | else if (message instanceof DiscoverHost) buffer.put((byte) 1); 46 | else if (message instanceof KeepAlive) buffer.put((byte) 2); 47 | else if (message instanceof RegisterUDP p) buffer.put((byte) 3).putInt(p.connectionID); 48 | else if (message instanceof RegisterTCP p) buffer.put((byte) 4).putInt(p.connectionID); 49 | } 50 | 51 | public FrameworkMessage readFramework(ByteBuffer buffer) { 52 | byte id = buffer.get(); 53 | 54 | if (id == 0) 55 | return new Ping() {{ 56 | id = buffer.getInt(); 57 | isReply = buffer.get() == 1; 58 | }}; 59 | else if (id == 1) return FrameworkMessage.discoverHost; 60 | else if (id == 2) return FrameworkMessage.keepAlive; 61 | else if (id == 3) 62 | return new RegisterUDP() {{ 63 | connectionID = buffer.getInt(); 64 | }}; 65 | else if (id == 4) 66 | return new RegisterTCP() {{ 67 | connectionID = buffer.getInt(); 68 | }}; 69 | 70 | throw new RuntimeException("Unknown framework message!"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/claj/Control.java: -------------------------------------------------------------------------------- 1 | package claj; 2 | 3 | import arc.util.CommandHandler; 4 | import arc.util.CommandHandler.CommandResponse; 5 | import arc.util.CommandHandler.ResponseType; 6 | import arc.util.Log; 7 | import arc.util.Strings; 8 | import arc.util.Threads; 9 | 10 | import java.util.Scanner; 11 | 12 | public class Control { 13 | 14 | public final CommandHandler handler = new CommandHandler(""); 15 | public final Distributor distributor; 16 | 17 | public Control(Distributor distributor) { 18 | this.distributor = distributor; 19 | this.registerCommands(); 20 | 21 | Threads.daemon("Application Control", () -> { 22 | try (Scanner scanner = new Scanner(System.in)) { 23 | while (scanner.hasNext()) handleCommand(scanner.nextLine()); 24 | } 25 | }); 26 | } 27 | 28 | private void handleCommand(String command) { 29 | CommandResponse response = handler.handleMessage(command); 30 | 31 | if (response.type == ResponseType.unknownCommand) { 32 | String closest = handler.getCommandList().map(cmd -> cmd.text).min(cmd -> Strings.levenshtein(cmd, command)); 33 | Log.err("Command not found. Did you mean @?", closest); 34 | } else if (response.type != ResponseType.noCommand && response.type != ResponseType.valid) 35 | Log.err("Too @ command arguments.", response.type == ResponseType.fewArguments ? "few" : "many"); 36 | } 37 | 38 | private void registerCommands() { 39 | handler.register("help", "Display the command list.", args -> { 40 | Log.info("Commands:"); 41 | handler.getCommandList().each(command -> Log.info(" &b&lb@@&fr - @", 42 | command.text, command.paramText.isEmpty() ? "" : " &lc&fi" + command.paramText, command.description)); 43 | }); 44 | 45 | handler.register("list", "Displays all current rooms.", args -> { 46 | Log.info("Rooms:"); 47 | distributor.rooms.forEach(entry -> { 48 | Log.info(" &b&lbRoom @&fr", entry.value.link); 49 | entry.value.redirectors.each(r -> { 50 | Log.info(" [H] &b&lbConnection @&fr - @", r.host.getID(), Main.getIP(r.host)); 51 | if (r.client == null) return; 52 | Log.info(" [C] &b&lbConnection @&fr - @", r.client.getID(), Main.getIP(r.client)); 53 | }); 54 | }); 55 | }); 56 | 57 | handler.register("limit", "[amount]", "Sets spam packet limit.", args -> { 58 | if (args.length == 0) 59 | Log.info("Current limit - @ packets per 3 seconds.", distributor.spamLimit); 60 | else { 61 | distributor.spamLimit = Strings.parseInt(args[0], 300); 62 | Log.info("Packet spam limit set to @ packets per 3 seconds.", distributor.spamLimit); 63 | } 64 | }); 65 | 66 | handler.register("ban", "", "Adds the IP to blacklist.", args -> { 67 | Blacklist.add(args[0]); 68 | Log.info("IP @ has been blacklisted.", args[0]); 69 | }); 70 | 71 | handler.register("unban", "", "Removes the IP from blacklist.", args -> { 72 | Blacklist.remove(args[0]); 73 | Log.info("IP @ has been removed from blacklist.", args[0]); 74 | }); 75 | 76 | handler.register("refresh", "Unbans all IPs and refresh GitHub Actions IPs.", args -> { 77 | Blacklist.clear(); 78 | Blacklist.refresh(); 79 | }); 80 | 81 | handler.register("exit", "Stop hosting distributor and exit the application.", args -> { 82 | distributor.rooms.forEach(entry -> entry.value.sendMessage("[scarlet]\u26A0[] The server is shutting down.\nTry to reconnect in a minute.")); 83 | 84 | Log.info("Shutting down the application."); 85 | distributor.stop(); 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/claj/Distributor.java: -------------------------------------------------------------------------------- 1 | package claj; 2 | 3 | import arc.math.Mathf; 4 | import arc.net.Connection; 5 | import arc.net.DcReason; 6 | import arc.net.FrameworkMessage; 7 | import arc.net.NetListener; 8 | import arc.net.Server; 9 | import arc.struct.IntMap; 10 | import arc.struct.IntMap.Entry; 11 | import arc.util.Log; 12 | import arc.util.Ratekeeper; 13 | 14 | import static claj.Main.*; 15 | 16 | import java.io.IOException; 17 | 18 | /** 19 | * It is an entry point for clients, distributes their packets to redirectors. 20 | * 21 | * @author xzxADIxzx 22 | */ 23 | public class Distributor extends Server { 24 | 25 | /** List of all characters that are allowed in a link. */ 26 | public static final char[] symbols = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwYyXxZz".toCharArray(); 27 | 28 | /** Limit for packet count sent within 3 sec that will lead to a disconnect. Note: only for clients. */ 29 | public int spamLimit = 500; 30 | 31 | /** Map containing the connection id and its redirector. */ 32 | public IntMap rooms = new IntMap<>(); 33 | 34 | /** Map containing the connection id and its redirector. */ 35 | public IntMap redirectors = new IntMap<>(); 36 | 37 | public Distributor() { 38 | super(32768, 8192, new Serializer()); 39 | addListener(new Listener()); 40 | } 41 | 42 | public void run(int port) throws IOException { 43 | Blacklist.refresh(); // refresh github's ips 44 | Log.info("Distributor hosted on port @.", port); 45 | 46 | bind(port, port); 47 | run(); 48 | } 49 | 50 | // region room management 51 | 52 | public String generateLink() { 53 | StringBuilder builder = new StringBuilder("CLaJ"); 54 | for (int i = 0; i < 42; i++) 55 | builder.append(symbols[Mathf.random(symbols.length - 1)]); 56 | 57 | return builder.toString(); 58 | } 59 | 60 | public Room find(String link) { 61 | for (Entry entry : rooms) 62 | if (entry.value.link.equals(link)) return entry.value; 63 | 64 | return null; 65 | } 66 | 67 | public Room find(Redirector redirector) { 68 | for (Entry entry : rooms) 69 | if (entry.value.redirectors.contains(redirector)) return entry.value; 70 | 71 | return null; 72 | } 73 | 74 | // endregion 75 | 76 | public class Listener implements NetListener { 77 | 78 | @Override 79 | public void connected(Connection connection) { 80 | if (Blacklist.contains(connection.getRemoteAddressTCP().getAddress().getHostAddress())) { 81 | connection.close(DcReason.closed); 82 | return; 83 | } 84 | 85 | Log.info("Connection @ received!", connection.getID()); 86 | connection.setArbitraryData(new Ratekeeper()); 87 | } 88 | 89 | @Override 90 | public void disconnected(Connection connection, DcReason reason) { 91 | Log.info("Connection @ lost: @.", connection.getID(), reason); 92 | 93 | var room = rooms.get(connection.getID()); 94 | if (room != null) { 95 | room.close(); // disconnects all related redirectors 96 | rooms.remove(connection.getID()); 97 | return; 98 | } 99 | 100 | var redirector = redirectors.get(connection.getID()); 101 | if (redirector == null) return; 102 | 103 | redirectors.remove(redirector.host.getID()); 104 | if (redirector.client != null) redirectors.remove(redirector.client.getID()); 105 | 106 | // called after deletion to prevent double close 107 | redirector.disconnected(connection, reason); 108 | 109 | room = find(redirector); 110 | if (room != null) room.redirectors.remove(redirector); 111 | } 112 | 113 | @Override 114 | public void received(Connection connection, Object object) { 115 | var rate = (Ratekeeper) connection.getArbitraryData(); 116 | if (!rate.allow(3000L, spamLimit)) { 117 | rate.occurences = -spamLimit; // reset to prevent message spam 118 | 119 | var redirector = redirectors.get(connection.getID()); 120 | if (redirector != null && connection == redirector.host) { 121 | Log.warn("Connection @ spammed with packets but not disconnected due to being a host.", connection.getID()); 122 | return; // host can spam packets when killing core and etc. 123 | } 124 | 125 | Log.warn("Connection @ disconnected due to packet spam.", connection.getID()); 126 | if (redirector != null) { 127 | var room = find(redirector); 128 | if (room != null) { 129 | room.sendMessage("[scarlet]\u26A0[] Connection closed due to packet spam."); 130 | room.redirectors.remove(redirector); 131 | } 132 | } 133 | 134 | connection.close(DcReason.closed); 135 | return; 136 | } 137 | 138 | if (object instanceof FrameworkMessage) return; 139 | if (object instanceof String link) { 140 | if (link.equals("new")) { 141 | link = generateLink(); 142 | 143 | connection.sendTCP(link); 144 | rooms.put(connection.getID(), new Room(link, connection)); 145 | 146 | Log.info("Connection @ created a room @.", connection.getID(), link); 147 | } else if (link.startsWith("host")) { 148 | var room = find(link.substring(4)); 149 | if (room == null || !getIP(room.host).equals(getIP(connection))) { 150 | connection.close(DcReason.error); // kick the connection if it tries to host a redirector without permission 151 | return; 152 | } 153 | 154 | var redirector = new Redirector(connection); 155 | room.redirectors.add(redirector); 156 | redirectors.put(connection.getID(), redirector); 157 | 158 | Log.info("Connection @ hosted a redirector in room @.", connection.getID(), room.link); 159 | } else if (link.startsWith("join")) { 160 | var room = find(link.substring(4)); 161 | if (room == null) { 162 | connection.close(DcReason.error); 163 | return; 164 | } 165 | 166 | var redirector = room.redirectors.find(r -> r.client == null); 167 | if (redirector == null) { 168 | connection.close(DcReason.error); // no empty redirectors 169 | return; 170 | } 171 | 172 | redirector.client = connection; 173 | redirectors.put(connection.getID(), redirector); 174 | room.sendMessage("new"); // ask to create a new redirector for the future 175 | 176 | Log.info("Connection @ joined to room @.", connection.getID(), room.link); 177 | } 178 | 179 | return; 180 | } 181 | 182 | var redirector = redirectors.get(connection.getID()); 183 | if (redirector != null) redirector.received(connection, object); 184 | } 185 | } 186 | } 187 | --------------------------------------------------------------------------------