make it actually connect servers now :D

crazy right??!?!?!???!
This commit is contained in:
ayunami2000 2022-07-06 16:38:13 -04:00
parent 75423d4ea5
commit 9c9d3c28eb
10 changed files with 359 additions and 44 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
web web
target/* target/*
!target/ayungee-1.0-SNAPSHOT-jar-with-dependencies.jar !target/ayungee-1.0-SNAPSHOT-jar-with-dependencies.jar
test

View File

@ -2,6 +2,10 @@
lightweight bungeecord alternative for eaglercraft servers running protocolsupport lightweight bungeecord alternative for eaglercraft servers running protocolsupport
Thanks to LAX1DUDE for very small snippets of EaglerBungee used in this project (specifically for the server icon) Thanks to LAX1DUDE and md-5 for very small snippets of EaglerBungee used in this project (specifically for the server icon, skins, & entity remapping)
**TODO: skins & capes** **TODO: ability for plugins to change a player's server & built-in auth system**
**if you have questions about the license, please, reach out to me. i just put the license i put on most of my projects on this, but if you have a problem with it, let me know.

View File

@ -16,10 +16,17 @@ public class Client {
public String username; public String username;
public int server = 0;
public byte[] handshake = null;
public int clientEntityId;
public int serverEntityId;
public void setSocket(Socket sock) throws IOException { public void setSocket(Socket sock) throws IOException {
socket = sock; socket = sock;
socketOut = sock.getOutputStream(); socketOut = sock.getOutputStream();
socketIn = socket.getInputStream(); socketIn = sock.getInputStream();
} }
public Client(String uname) { public Client(String uname) {

View File

@ -0,0 +1,81 @@
package me.ayunami2000.ayungee;
// https://github.com/LAX1DUDE/eaglercraft/blob/a8d5c856de28ba2a263abc055d7b26d50dc2bf7e/eaglercraftbungee/src/main/java/net/md_5/bungee/EntityMap.java
public class EntityMap {
public static final int[][] entityIds;
public static void rewrite(final byte[] packet, final int oldId, final int newId) {
final int packetId = packet[0] & 0xFF;
if (packetId == 29) {
for (int pos = 2; pos < (short) ((packet[1] << 8) + packet[2] & 0xff); pos += 4) {
final int readId = readInt(packet, pos);
if (readId == oldId) {
setInt(packet, pos, newId);
} else if (readId == newId) {
setInt(packet, pos, oldId);
}
}
} else {
final int[] idArray = EntityMap.entityIds[packetId];
if (idArray != null) {
for (final int pos2 : idArray) {
final int readId2 = readInt(packet, pos2);
if (readId2 == oldId) {
setInt(packet, pos2, newId);
} else if (readId2 == newId) {
setInt(packet, pos2, oldId);
}
}
}
}
if (packetId == 23) {
final int type = packet[5] & 0xFF;
if (type == 60 || type == 90) {
final int index20 = readInt(packet, 20);
if (packet.length > 24 && index20 == oldId) {
setInt(packet, 20, newId);
}
}
}
}
public static void setInt(final byte[] buf, final int pos, final int i) {
buf[pos] = (byte) (i >> 24);
buf[pos + 1] = (byte) (i >> 16);
buf[pos + 2] = (byte) (i >> 8);
buf[pos + 3] = (byte) i;
}
public static int readInt(final byte[] buf, final int pos) {
return (buf[pos] & 0xFF) << 24 | (buf[pos + 1] & 0xFF) << 16 | (buf[pos + 2] & 0xFF) << 8 | (buf[pos + 3] & 0xFF);
}
static {
(entityIds = new int[256][])[5] = new int[] { 1 };
EntityMap.entityIds[7] = new int[] { 1, 5 };
EntityMap.entityIds[17] = new int[] { 1 };
EntityMap.entityIds[18] = new int[] { 1 };
EntityMap.entityIds[19] = new int[] { 1 };
EntityMap.entityIds[20] = new int[] { 1 };
EntityMap.entityIds[22] = new int[] { 1, 5 };
EntityMap.entityIds[23] = new int[] { 1 };
EntityMap.entityIds[24] = new int[] { 1 };
EntityMap.entityIds[25] = new int[] { 1 };
EntityMap.entityIds[26] = new int[] { 1 };
EntityMap.entityIds[28] = new int[] { 1 };
EntityMap.entityIds[30] = new int[] { 1 };
EntityMap.entityIds[31] = new int[] { 1 };
EntityMap.entityIds[32] = new int[] { 1 };
EntityMap.entityIds[33] = new int[] { 1 };
EntityMap.entityIds[34] = new int[] { 1 };
EntityMap.entityIds[35] = new int[] { 1 };
EntityMap.entityIds[38] = new int[] { 1 };
EntityMap.entityIds[39] = new int[] { 1, 5 };
EntityMap.entityIds[40] = new int[] { 1 };
EntityMap.entityIds[41] = new int[] { 1 };
EntityMap.entityIds[42] = new int[] { 1 };
EntityMap.entityIds[55] = new int[] { 1 };
EntityMap.entityIds[71] = new int[] { 1 };
}
}

View File

@ -7,9 +7,7 @@ import org.yaml.snakeyaml.Yaml;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.io.*; import java.io.*;
import java.net.HttpURLConnection; import java.net.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
@ -19,8 +17,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Main { public class Main {
public static String hostname = "localhost"; public static List<ServerItem> servers = new ArrayList<>();
public static int port = 25569;
public static int webPort = 25565; public static int webPort = 25565;
public static String motdJson = ""; public static String motdJson = "";
@ -28,8 +25,6 @@ public class Main {
public static boolean forwarded = false; public static boolean forwarded = false;
public static boolean eaglerPackets = false;
public static WebSocketServer webSocketServer = null; public static WebSocketServer webSocketServer = null;
public static Map<WebSocket, Client> clients = new HashMap<>(); public static Map<WebSocket, Client> clients = new HashMap<>();
@ -107,12 +102,19 @@ public class Main {
}).start(); }).start();
} }
servers.add(new ServerItem("localhost", 25569));
List<String> stringServers = (List<String>) config.getOrDefault("servers", new ArrayList<>());
if (!stringServers.isEmpty()) {
servers.clear();
for (String serverEntry : stringServers) {
servers.add(new ServerItem(serverEntry));
}
}
hostname = (String) config.getOrDefault("hostname", "localhost");
port = (int) config.getOrDefault("port", 25569);
webPort = (int) config.getOrDefault("web_port", 25565); webPort = (int) config.getOrDefault("web_port", 25565);
forwarded = (boolean) config.getOrDefault("forwarded", false); forwarded = (boolean) config.getOrDefault("forwarded", false);
eaglerPackets = (boolean) config.getOrDefault("eag_packets", false);
List<String> defaultMotd = new ArrayList<>(); List<String> defaultMotd = new ArrayList<>();
@ -168,7 +170,7 @@ public class Main {
switch (pieces[0]) { switch (pieces[0]) {
case "help": case "help":
case "?": case "?":
System.out.println("help ; unban <ip> ; banip <ip> ; ban <username> ; stop"); System.out.println("help ; unban <ip> ; banip <ip> ; ban <username> ; send <username> <serverid> ; stop");
break; break;
case "unban": case "unban":
case "pardon": case "pardon":
@ -229,6 +231,24 @@ public class Main {
System.out.println("IP " + pieces[1] + " is already banned!"); System.out.println("IP " + pieces[1] + " is already banned!");
} }
break; break;
case "send":
case "server":
if (pieces.length == 1 || pieces.length == 2) {
System.out.println("Usage: " + pieces[0] + " <username> <serverindex>");
break;
}
Client targetUser = clients.values().stream().filter(client -> client.username.equals(pieces[1])).findFirst().orElse(clients.values().stream().filter(client -> client.username.equalsIgnoreCase(pieces[1])).findFirst().orElse(null));
if (targetUser == null) {
System.out.println("Unable to find any user with that username!");
break;
}
try {
int destServer = Integer.parseInt(pieces[2]);
targetUser.server = Math.max(0, Math.min(servers.size() - 1, destServer));
} catch (NumberFormatException e) {
System.out.println("That is not a valid number!");
}
break;
case "stop": case "stop":
case "end": case "end":
case "exit": case "exit":

View File

@ -0,0 +1,17 @@
package me.ayunami2000.ayungee;
public class ServerItem {
public String host;
public int port = 25565;
public ServerItem(String h, int p) {
host = h;
port = p;
}
public ServerItem(String hp) {
String[] pieces = hp.split(":");
if (pieces.length > 1) port = Integer.parseInt(pieces[1]);
host = pieces[0];
}
}

View File

@ -0,0 +1,115 @@
package me.ayunami2000.ayungee;
import org.java_websocket.WebSocket;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
public class Skins {
private static final HashMap<String,byte[]> skinCollection = new HashMap();
private static final HashMap<String,byte[]> capeCollection = new HashMap();
private static final HashMap<String,Long> lastSkinLayerUpdate = new HashMap();
private static final int[] SKIN_DATA_SIZE = new int[] { 64*32*4, 64*64*4, -9, -9, 1, 64*64*4, -9 }; // 128 pixel skins crash clients
private static final int[] CAPE_DATA_SIZE = new int[] { 32*32*4, -9, 1 };
public static boolean setSkin(String user, WebSocket conn, byte[] initMsg) {
if(initMsg.length >= 3) {
try {
ByteBuffer bb = ByteBuffer.wrap(initMsg);
bb.get();
int tagLen = bb.getShort();
if (!(tagLen >= 0 && tagLen < initMsg.length - 1)) return false;
StringBuilder tagBuilder = new StringBuilder();
for (int i = 0; i < tagLen; i++) tagBuilder.append(bb.getChar());
//int dataLen = bb.getShort();
int dataLen = bb.remaining() - 2;
String tag = tagBuilder.toString();
int offset = 3 + tagLen * 2 + 2;
byte[] msg = new byte[dataLen];
System.arraycopy(initMsg, offset, msg, 0, dataLen);
if("EAG|MySkin".equals(tag)) {
if(!skinCollection.containsKey(user)) {
int t = (int)msg[0] & 0xFF;
if(t >= 0 && t < SKIN_DATA_SIZE.length && msg.length == (SKIN_DATA_SIZE[t] + 1)) {
skinCollection.put(user, msg);
}
}
}else if("EAG|MyCape".equals(tag)) {
if(!capeCollection.containsKey(user)) {
int t = (int)msg[0] & 0xFF;
if(t >= 0 && t < CAPE_DATA_SIZE.length && msg.length == (CAPE_DATA_SIZE[t] + 2)) {
capeCollection.put(user, msg);
}
}
}else if("EAG|FetchSkin".equals(tag)) {
if(msg.length > 2) {
String fetch = new String(msg, 2, msg.length - 2, StandardCharsets.UTF_8);
byte[] data;
if((data = skinCollection.get(fetch)) != null) {
byte[] conc = new byte[data.length + 2];
conc[0] = msg[0]; conc[1] = msg[1]; //synchronization cookie
System.arraycopy(data, 0, conc, 2, data.length);
if((data = capeCollection.get(fetch)) != null) {
byte[] conc2 = new byte[conc.length + data.length];
System.arraycopy(conc, 0, conc2, 0, conc.length);
System.arraycopy(data, 0, conc2, conc.length, data.length);
conc = conc2;
}
byte[] packetPrefix = new byte[] { (byte) 250, 0, 12, 0, 69, 0, 65, 0, 71, 0, 124, 0, 85, 0, 115, 0, 101, 0, 114, 0, 83, 0, 107, 0, 105, 0, 110, (byte) ((conc.length >>> 8) & 0xFF), (byte) (conc.length & 0xFF) };
byte[] fullPacket = new byte[packetPrefix.length + conc.length];
System.arraycopy(packetPrefix, 0, fullPacket, 0, packetPrefix.length);
System.arraycopy(conc, 0, fullPacket, packetPrefix.length, conc.length);
conn.send(fullPacket);
}
}
}else if("EAG|SkinLayers".equals(tag)) {
long millis = System.currentTimeMillis();
Long lsu = lastSkinLayerUpdate.get(user);
if(lsu != null && millis - lsu.longValue() < 700l) { // DoS protection
return false;
}
lastSkinLayerUpdate.put(user, millis);
byte[] data;
if((data = capeCollection.get(user)) != null) {
data[1] = msg[0];
}else {
data = new byte[] { (byte)2, msg[0], (byte)0 };
capeCollection.put(user, data);
}
ByteArrayOutputStream bao = new ByteArrayOutputStream();
DataOutputStream dd = new DataOutputStream(bao);
dd.write(msg[0]);
dd.writeUTF(user);
byte[] bpacket = bao.toByteArray();
byte[] packetPrefix = new byte[] { (byte) 250, 0, 14, 0, 69, 0, 65, 0, 71, 0, 124, 0, 83, 0, 107, 0, 105, 0, 110, 0, 76, 0, 97, 0, 121, 0, 101, 0, 114, 0, 115, (byte) ((bpacket.length >>> 8) & 0xFF), (byte) (bpacket.length & 0xFF) };
int off = bpacket.length == 0 ? 2 : 0;
byte[] fullPacket = new byte[(packetPrefix.length - off) + bpacket.length];
System.arraycopy(packetPrefix, 0, fullPacket, 0, packetPrefix.length - off);
if (bpacket.length != 0) System.arraycopy(bpacket, 0, fullPacket, packetPrefix.length, bpacket.length);
for (WebSocket pl : Main.clients.keySet()) {
if (pl.equals(conn)) continue;
if (pl.isOpen()) pl.send(fullPacket);
}
} else {
return false;
}
}catch(Throwable t) {
// hacker
}
} else {
return false;
}
return true;
}
public static void removeSkin(String username) {
skinCollection.remove(username);
capeCollection.remove(username);
lastSkinLayerUpdate.remove(username);
}
}

View File

@ -63,6 +63,7 @@ public class WebSocketProxy extends WebSocketServer {
Client selfClient = Main.clients.remove(conn); Client selfClient = Main.clients.remove(conn);
if (selfClient != null) { if (selfClient != null) {
System.out.println("Player " + selfClient.username + " (" + Main.getIp(conn) + ") left!"); System.out.println("Player " + selfClient.username + " (" + Main.getIp(conn) + ") left!");
Skins.removeSkin(selfClient.username);
if (selfClient.socket.isClosed()) { if (selfClient.socket.isClosed()) {
try { try {
selfClient.socket.close(); selfClient.socket.close();
@ -88,7 +89,7 @@ public class WebSocketProxy extends WebSocketServer {
} }
@Override @Override
public void onMessage(WebSocket conn, ByteBuffer message) { public void onMessage(WebSocket conn, ByteBuffer message) { // todo: make use of the fact that it's already a bytebuffer dumbass
if (Main.bans.contains(Main.getIp(conn))) { if (Main.bans.contains(Main.getIp(conn))) {
conn.close(); conn.close();
return; return;
@ -97,60 +98,132 @@ public class WebSocketProxy extends WebSocketServer {
byte[] msg = message.array(); byte[] msg = message.array();
if (!Main.clients.containsKey(conn)) { if (!Main.clients.containsKey(conn)) {
if (msg.length > 3 && msg[1] == (byte) 69) { if (msg.length > 3 && msg[1] == (byte) 69) {
if (msg[3] < 3 || msg[3] > 16) { // todo: it uses shorts dumbass, get with the system
short unameLen = (short) ((msg[2] << 8) + msg[3] & 0xff);
if (unameLen < 3 || unameLen > 16) {
conn.close(); conn.close();
return; return;
} }
byte[] uname = new byte[msg[3]];
if (msg.length < 5 + msg[3] * 2) { if (msg.length < 5 + msg[3] * 2) {
conn.close(); conn.close();
return; return;
} }
byte[] uname = new byte[unameLen];
for (int i = 0; i < uname.length; i++) uname[i] = msg[5 + i * 2]; for (int i = 0; i < uname.length; i++) uname[i] = msg[5 + i * 2];
String username = new String(uname); String username = new String(uname);
Main.clients.put(conn, new Client(username)); Client selfClient = new Client(username);
Main.clients.put(conn, selfClient);
new Thread(() -> { new Thread(() -> {
try { try {
Socket selfSocket = new Socket(Main.hostname, Main.port); boolean firstTime = true;
Client selfClient = Main.clients.get(conn); while (conn.isOpen()) {
selfClient.setSocket(selfSocket); int currServer = selfClient.server;
while (selfClient.msgCache.size() > 0) selfClient.socketOut.write(selfClient.msgCache.remove(0)); ServerItem chosenServer = Main.servers.get(currServer);
while (conn.isOpen() && !selfSocket.isInputShutdown()) { Socket selfSocket = new Socket(chosenServer.host, chosenServer.port);
byte[] data = new byte[maxBuffSize]; selfClient.setSocket(selfSocket);
int read = selfClient.socketIn.read(data, 0, maxBuffSize); if (!firstTime) sendToServer(selfClient.handshake, selfClient);
if (read == maxBuffSize) { while (selfClient.msgCache.size() > 0) sendToServer(selfClient.msgCache.remove(0), selfClient);
while (conn.isOpen() && !selfSocket.isInputShutdown() && selfClient.server == currServer) {
byte[] dataa = new byte[maxBuffSize];
int read = selfClient.socketIn.read(dataa, 0, maxBuffSize);
byte[] data;
if (read == maxBuffSize) {
data = dataa;
} else if (read > 0) {
data = new byte[read];
System.arraycopy(dataa, 0, data, 0, read);
} else {
continue;
}
if (firstTime && data[0] == 1) selfClient.clientEntityId = selfClient.serverEntityId = EntityMap.readInt(data, 1);
if (!firstTime && data[0] == 1) {
selfClient.serverEntityId = EntityMap.readInt(data, 1);
// assume server is giving valid data; we don't have to validate it because it isn't a potentially malicious client
byte[] worldByte = new byte[data[6] * 2 + 2];
System.arraycopy(data, 5, worldByte, 0, worldByte.length);
byte gamemode = data[worldByte.length + 5];
byte dimension = data[worldByte.length + 6];
byte difficulty = data[worldByte.length + 7];
Arrays.fill(data, (byte) 0);
data[0] = 9;
EntityMap.setInt(data, 1, dimension);
data[5] = difficulty;
data[6] = gamemode;
data[7] = (byte)(256 & 0xff);
data[8] = (byte)((256 >> 8) & 0xff);
System.arraycopy(worldByte, 0, data, 9, worldByte.length);
read = 9 + worldByte.length;
byte[] trimData = new byte[read];
System.arraycopy(data, 0, trimData, 0, read);
data = trimData;
if (conn.isOpen()) conn.send(new byte[] { 9, 0, 0, 0, 1, 0, 0, 1, 0, 0, 7, 0, 100, 0, 101, 0, 102, 0, 97, 0, 117, 0, 108, 0, 116 });
if (conn.isOpen()) conn.send(new byte[] { 9, 0, 0, 0, -1, 0, 0, 1, 0, 0, 7, 0, 100, 0, 101, 0, 102, 0, 97, 0, 117, 0, 108, 0, 116 });
}
EntityMap.rewrite(data, selfClient.serverEntityId, selfClient.clientEntityId);
if (conn.isOpen()) conn.send(data); if (conn.isOpen()) conn.send(data);
} else if (read > 0) {
byte[] trueData = new byte[read];
System.arraycopy(data, 0, trueData, 0, read);
if (conn.isOpen()) conn.send(trueData);
} }
if (conn.isOpen() && selfClient.server == currServer) conn.close();
if (!selfSocket.isClosed()) selfSocket.close();
selfClient.socketOut = null;
firstTime = false;
} }
if (conn.isOpen()) conn.close();
if (!selfSocket.isClosed()) selfSocket.close();
} catch (IOException ex) { } catch (IOException ex) {
conn.close(); conn.close();
} }
}).start(); }).start();
msg[1] = (byte) 61; msg[1] = (byte) 61;
selfClient.handshake = msg;
System.out.println("Player " + username + " (" + Main.getIp(conn) + ") joined!"); System.out.println("Player " + username + " (" + Main.getIp(conn) + ") joined!");
} else { } else {
conn.close(); conn.close();
return; return;
} }
} }
byte[] packet = message.array();
if (!Main.eaglerPackets && packet.length >= 11 && packet[0] == -6 && packet[2] >= 4 && packet[4] == 69 && packet[6] == 65 && packet[8] == 71 && packet[10] == 124) return; // EAG|
Client currClient = Main.clients.get(conn); Client currClient = Main.clients.get(conn);
if (currClient.socketOut == null) { if (msg.length >= 3 && msg[0] == 3) {
currClient.msgCache.add(packet); int msgLen = (short) ((msg[1] << 8) + msg[2] & 0xff);
} else if (!currClient.socket.isOutputShutdown()) { if (msgLen != 0) {
if (msg.length >= 3 + msgLen * 2) {
byte[] chatBytes = new byte[msgLen];
for (int i = 0; i < chatBytes.length; i++) chatBytes[i] = msg[4 + i * 2];
String chatMsg = new String(chatBytes);
if (chatMsg.toLowerCase().startsWith("/server")) {
String msgArgs = chatMsg.substring(7 + (chatMsg.contains(" ") ? 1 : 0));
if (msgArgs.isEmpty()) {
//usage msg
conn.send(new byte[] { 3, 0, 25, 0, (byte) 167, 0, 57, 0, 85, 0, 115, 0, 97, 0, 103, 0, 101, 0, 58, 0, 32, 0, 47, 0, 115, 0, 101, 0, 114, 0, 118, 0, 101, 0, 114, 0, 32, 0, 60, 0, 110, 0, 117, 0, 109, 0, 98, 0, 101, 0, 114, 0, 62 });
} else {
try {
int destServer = Integer.parseInt(msgArgs);
currClient.server = Math.max(0, Math.min(Main.servers.size() - 1, destServer));
} catch (NumberFormatException e) {
//not a number
conn.send(new byte[] { 3, 0, 29, 0, (byte) 167, 0, 57, 0, 84, 0, 104, 0, 97, 0, 116, 0, 32, 0, 105, 0, 115, 0, 32, 0, 110, 0, 111, 0, 116, 0, 32, 0, 97, 0, 32, 0, 118, 0, 97, 0, 108, 0, 105, 0, 100, 0, 32, 0, 110, 0, 117, 0, 109, 0, 98, 0, 101, 0, 114, 0, 33 });
}
}
return; // don't send to underlying server
}
}
}
}
if (msg.length > 0 && msg[0] == (byte) 250) {
if (Skins.setSkin(currClient.username, conn, msg)) return;
}
if (currClient.socketOut == null || currClient.socket.isOutputShutdown()) {
currClient.msgCache.add(msg);
} else {
try { try {
currClient.socketOut.write(packet); sendToServer(msg, currClient);
} catch (IOException ignored) {} } catch (IOException ignored) {}
} }
} }
public void sendToServer(byte[] orig, Client client) throws IOException {
byte[] data = orig.clone();
EntityMap.rewrite(data, client.clientEntityId, client.serverEntityId);
client.socketOut.write(data);
}
@Override @Override
public void onError(WebSocket conn, Exception ex) { public void onError(WebSocket conn, Exception ex) {
// //

View File

@ -1,13 +1,10 @@
# mc server ip # mc servers in network. first one is default/hub
hostname: localhost servers:
# mc server port - localhost:25569
port: 25569
# ayungee port # ayungee port
web_port: 25565 web_port: 25565
# if this is behind a reverse proxy, such as caddy or nginx. uses X-Real-IP header. # if this is behind a reverse proxy, such as caddy or nginx. uses X-Real-IP header.
forwarded: false forwarded: false
# forward eagler-specific packets?
eag_packets: false
# origin blacklist URL (leave empty to disable syncing) # origin blacklist URL (leave empty to disable syncing)
origin_blacklist: "https://g.eags.us/eaglercraft/origin_blacklist.txt" origin_blacklist: "https://g.eags.us/eaglercraft/origin_blacklist.txt"
# whitelisted origins -- if specified, only allows the listed origins to connect # whitelisted origins -- if specified, only allows the listed origins to connect