more commands, config loading

This commit is contained in:
Sen 2025-06-09 19:38:41 +02:00
parent 29e9ab8cb8
commit e7aab16773
Signed by: sen
GPG key ID: 3AC50A6F47D1B722
14 changed files with 324 additions and 30 deletions

View file

@ -29,5 +29,5 @@ application {
tasks.shadowJar {
destinationDirectory = rootProject.file("dev")
archiveFileName = "vloginproxy.jar"
archiveFileName = "vproxy.jar"
}

View file

@ -28,6 +28,8 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.IDN;
import java.net.InetAddress;
import java.util.Collection;
@ -36,6 +38,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.Callable;
@ -76,9 +79,11 @@ import proxy.packet.S38PacketPlayerListItem;
import proxy.packet.S38PacketPlayerListItem.Action;
import proxy.packet.S40PacketDisconnect;
import proxy.packet.ServerInfo;
import proxy.util.Config;
import proxy.util.Formatter;
import proxy.util.User;
import proxy.util.Log;
import proxy.util.Option;
public class Proxy {
public static final String NAME = "VProxy";
@ -99,6 +104,7 @@ public class Proxy {
private volatile boolean isAlive;
private volatile boolean running = true;
private final Map<String, Option> options = Maps.newTreeMap();
private final List<ChannelFuture> endpoints = Collections.<ChannelFuture>synchronizedList(Lists.newArrayList());
private final List<Connection> networkManagers = Collections.<Connection>synchronizedList(Lists.newArrayList());
private final Queue<FutureTask<?>> futureTaskQueue = Queues.<FutureTask<?>>newArrayDeque();
@ -106,24 +112,43 @@ public class Proxy {
private final Map<String, ProxyHandler> players = Maps.newTreeMap();
private final Map<String, Command> commands = Maps.newTreeMap();
private final Thread serverThread;
private final ServerInfo status = new ServerInfo(NAME + " 1.8.9", 47);
@Config
private int compression = -1;
@Config
private boolean epoll = true;
@Config
private boolean register = true;
@Config
private boolean checkCase = true;
@Config
private boolean kickOnConnect = true;
@Config
private String forwardHost = "127.0.0.1";
@Config
private int forwardPort = 25563;
@Config
private String proxyHost = "";
@Config
private int proxyPort = 25564;
@Config
private int minPassLength = 8;
@Config
private int maxAttempts = 5;
@Config
private int maxPlayers = 64;
@Config
private int pingInterval = 5;
@Config
private String defaultSkinTexture = "ewogICJ0aW1lc3RhbXAiIDogMTc0OTEyOTgyODQ2MCwKICAicHJvZmlsZUlkIiA6ICJlYjcxMWNlYmNjMWM0ZGQwYTc5MmE3ZGM2Y2Q3NzcyZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJTZW5qaXVuIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzk5ZGMxYmU3ODQ1N2ViODk3Zjk3MTYxYzZjYTEyOTFlMjkxN2IzYzI4ZWIzMTc4OGY3MGEzYWY4ZGIwMGEyN2IiCiAgICB9CiAgfQp9";
@Config
private String defaultSkinSignature = "uX4TdWeMU7nQZp4ZZY5d4GUOg3e+KzgndM3UhcQHZuxpfJqg5l9vZ6KmHpYpUf5hZqEzTLxWefhxLpurm3nd2kzL51GVHMQuquiISqXXGBY33bfCJtl0iVijKhAuvVWrwvBZo5tI/zCz3OQBEdW+DkNAfX7ZXntrl+0JWqhxg2+suzL4pnW7ye45/p4rVhiZ4pU/n0jTF8q6Jv6Rx4L8AoemsQ4MWmC/J5rT24gUD6DuuAc06KjUJy9g1gHNwVJ5uUjzaJ/li6as22Io09B0MdyVe5ryO9WbRKriUComNDWp/YdQrpp/hZThSNTAeJphAS5+YslRod/ZJPknrgx1yLauYaYDoHaVfQx9ZZGvJFOO+tIPQGBIefO1W3QgBqz+hdgFwsevtcqsewcdT/1khAAA2nESahOX5OuVZZhphmbfcUXK4FPn2H4jqcquP5VcsEpq3+IgE/+Nt8qFi4uANB6of/bPDWP/oDzISvtFVj/Odh+kZ/Y+OEYxn9ALYiVz5QF5gww2mtwiOh5/P9kJNc88ZrLgtnf/PggLDr6rET6L2t9DN2biwbm8NlMEB0oMa3wF/E166HRgXsxvTGvYxod1L4c70uaiyU/FlA0fL+K03bxIKYRuH7yht2E2MnzxeuPDIU/26DUG56Ek00eYdHVk9NuyUEX/96uMCArjpZ0=";
@Config
private String[] motds = new String[0];
@Config
private String[] info = new String[0];
private ServerInfo status;
private long lastPingSync;
private static String getIcon(File file) {
@ -160,6 +185,14 @@ public class Proxy {
public Proxy() {
this.serverThread = Thread.currentThread();
this.isAlive = true;
for(Field field : Proxy.class.getDeclaredFields()) {
if(field.isAnnotationPresent(Config.class)) {
field.setAccessible(true);
if(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()))
throw new IllegalArgumentException(field.getName() + ": is static or final");
this.options.put(field.getName(), new Option(field, this));
}
}
this.registerCommands();
}
@ -189,28 +222,40 @@ public class Proxy {
return elem.getAsJsonObject();
}
private void loadConfig() {
public void loadConfig() {
JsonObject obj = loadFile(new File("vproxy.json"));
// ...
obj = loadFile(new File("users.json"));
for(Entry<String, JsonElement> entry : obj.entrySet()) {
if(!this.options.containsKey(entry.getKey()))
continue;
if(!this.options.get(entry.getKey()).set(entry.getValue()))
Log.warn("Could not set option %s", entry.getKey());
}
this.status.setCapacity(this.maxPlayers);
this.status.getMotds().clear();
this.status.getIcons().clear();
this.status.getList().clear();
Collections.addAll(this.status.getMotds(), this.motds);
Collections.addAll(this.status.getList(), this.info);
File[] icons = new File("icons").listFiles(file -> file.isFile() && file.getName().endsWith(".png"));
if(icons != null) {
for(File file : icons) {
String icon = getIcon(file);
if(file != null)
this.status.getIcons().add(icon);
}
}
}
private void loadUsers() {
JsonObject obj = loadFile(new File("users.json"));
if(obj.has("users") && obj.get("users").isJsonArray()) {
for(User user : User.fromJson(obj.get("users").getAsJsonArray())) {
this.users.put(user.getUsername().toLowerCase(Locale.US), user);
}
}
// ...
this.status = new ServerInfo(NAME + " 1.8.9", 47);
this.status.setCapacity(this.maxPlayers);
Collections.addAll(this.status.getMotds(), Formatter.DARK_RED + "Miau\n" + Formatter.YELLOW + "Test??", Formatter.AQUA + "Server\n" + Formatter.GREEN + "Test!!");
String icon = getIcon(new File("icon1.png"));
if(icon != null)
this.status.getIcons().add(icon);
icon = getIcon(new File("icon2.png"));
if(icon != null)
this.status.getIcons().add(icon);
Collections.addAll(this.status.getList(), Formatter.DARK_GREEN + "TESTTTT", "Test 2!", Formatter.BLUE + "Test numbah 3!!!!!");
}
private static void saveFile(JsonObject obj, File file) {
@ -229,7 +274,11 @@ public class Proxy {
public void saveConfig() {
JsonObject obj = new JsonObject();
// ...
for(Entry<String, Option> entry : this.options.entrySet()) {
obj.add(entry.getKey(), entry.getValue().get());
}
saveFile(obj, new File("vproxy.json"));
}
@ -242,6 +291,7 @@ public class Proxy {
public void run() {
this.loadConfig();
this.loadUsers();
Log.info("Hosting proxy on %s:%d", this.proxyHost.isEmpty() ? "0.0.0.0" : this.proxyHost, this.proxyPort);
try {
this.addLanEndpoint(this.proxyHost.isEmpty() ? null : InetAddress.getByName(IDN.toASCII(this.proxyHost)), this.proxyPort);
@ -561,19 +611,27 @@ public class Proxy {
private void register(Command cmd) {
if(this.commands.containsKey(cmd.getName()))
throw new IllegalArgumentException("Command '" + cmd.getName() + "' ist already registered");
throw new IllegalArgumentException("Command '" + cmd.getName() + "' is already registered");
this.commands.put(cmd.getName(), cmd);
}
private void registerCommands() {
this.register(new CommandHelp());
this.register(new CommandExit());
this.register(new CommandReload());
this.register(new CommandAdmin());
this.register(new CommandRevoke());
this.register(new CommandRegister());
this.register(new CommandDelete());
this.register(new CommandList());
this.register(new CommandPing());
this.register(new CommandKick());
this.register(new CommandSave());
this.register(new CommandSeed());
for(String cmd : new String[] {"?", "about", "ban", "ban-ip", "banlist", "icanhasbukkit", "me", "pardon", "pardon-ip", "pl", "plugins", "restart", "rl", "say", "scoreboard", "stop", "tell", "tellraw", "timings", "title", "toggledownfall", "trigger", "ver", "version", "whitelist", "clone", "debug", "fill", "testfor", "testforblock", "testforblocks", "setidletimeout", "stats", "save-off", "save-on", "save-all"}) {
this.register(new CommandDummy(cmd));
}
}
public Map<String, Command> getCommands() {
@ -617,8 +675,11 @@ public class Proxy {
Log.error("Command '%s' not found", args[0]);
return false;
}
if(player != null && (cmd.getPermission() == null || (cmd.getPermission().isEmpty() && !player.isAdmin()) || !player.hasPermission(cmd.getPermission()))) {
player.sendMessage(Formatter.DARK_RED + "You are not allowed to execute this command");
if(!cmd.canUse(player)) {
if(player == null)
Log.error("The console can not use this command");
else
player.sendMessage(Formatter.DARK_RED + "You are not allowed to execute this command");
return true;
}
String[] argv = new String[args.length - 1];

View file

@ -21,6 +21,9 @@ public abstract class Command {
public String getPermission() {
return "vproxy.cmd." + this.getName();
}
public boolean canUse(ProxyHandler player) {
return player == null || (this.getPermission() != null && (this.getPermission().isEmpty() ? player.isAdmin() : player.hasPermission(this.getPermission())));
}
protected static void sendMessage(ProxyHandler player, String msg) {
if(player != null)

View file

@ -0,0 +1,27 @@
package proxy.command;
import proxy.Proxy;
import proxy.handler.ProxyHandler;
public class CommandDummy extends Command {
private final String name;
public CommandDummy(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public String getHelp() {
return null;
}
public boolean canUse(ProxyHandler player) {
return false;
}
public void run(Proxy proxy, ProxyHandler player, String[] args) {
}
}

View file

@ -20,7 +20,7 @@ public class CommandExit extends Command {
}
public void run(Proxy proxy, ProxyHandler player, String[] args) {
String msg = args.length == 0 ? "Proxy stopped" : Formatter.joinSpace(args);
String msg = args.length == 0 ? "Proxy stopped" : Formatter.joinSpace(0, args);
for(ProxyHandler plr : Lists.newArrayList(proxy.getPlayers())) {
plr.disconnect(msg);
}

View file

@ -2,6 +2,8 @@ package proxy.command;
import java.util.Locale;
import com.google.common.collect.Collections2;
import proxy.Proxy;
import proxy.handler.ProxyHandler;
import proxy.util.Formatter;
@ -28,17 +30,20 @@ public class CommandHelp extends Command {
public void run(Proxy proxy, ProxyHandler player, String[] args) {
if(args.length == 0) {
for(Command cmd : proxy.getCommands().values()) {
sendMessage(player, formatCommand(cmd, player != null));
if(cmd.canUse(player))
sendMessage(player, formatCommand(cmd, player != null));
}
return;
}
Command cmd = proxy.getCommands().get(args[0].toLowerCase(Locale.US));
if(cmd == null)
throw new RunException("Command '%s' not found", args[0]);
if(!cmd.canUse(player))
throw new RunException("Command '%s' can not be used", args[0]);
sendMessage(player, formatCommand(cmd, player != null));
}
public Iterable<String> complete(Proxy proxy, ProxyHandler player, String[] args) {
return args.length == 1 ? proxy.getCommands().keySet() : null;
return args.length == 1 ? Collections2.filter(proxy.getCommands().keySet(), name -> proxy.getCommands().get(name).canUse(player)) : null;
}
}

View file

@ -0,0 +1,38 @@
package proxy.command;
import com.google.common.collect.Collections2;
import proxy.Proxy;
import proxy.handler.ProxyHandler;
import proxy.util.Formatter;
public class CommandKick extends Command {
public String getName() {
return "kick";
}
public String getHelp() {
return "Kicks a player off the server";
}
public String getArgs() {
return "<player> [reason ...]";
}
public void run(Proxy proxy, ProxyHandler player, String[] args) {
if(args.length < 1)
throw new RunException("Please provide a player name");
ProxyHandler plr = proxy.getPlayer(args[0]);
if(plr == null)
throw new RunException("'%s' is not online", args[0]);
if(plr == player)
throw new RunException("You cannot kick yourself");
if(player != null && plr.isAdmin())
throw new RunException("Admins can only be kicked by the console");
plr.disconnect(args.length <= 1 ? "Kicked from server" : Formatter.joinSpace(1, args));
}
public Iterable<String> complete(Proxy proxy, ProxyHandler player, String[] args) {
return args.length == 1 ? (player == null ? proxy.getPlayerNames() : Collections2.filter(proxy.getPlayerNames(), name -> !proxy.getPlayer(name).isAdmin())) : null;
}
}

View file

@ -0,0 +1,19 @@
package proxy.command;
import proxy.Proxy;
import proxy.handler.ProxyHandler;
public class CommandReload extends Command {
public String getName() {
return "reload";
}
public String getHelp() {
return "Reloads the proxy config";
}
public void run(Proxy proxy, ProxyHandler player, String[] args) {
proxy.loadConfig();
sendInfo(player, "Configuration reloaded");
}
}

View file

@ -0,0 +1,23 @@
package proxy.command;
import proxy.Proxy;
import proxy.handler.ProxyHandler;
import proxy.packet.C01PacketChatMessage;
public class CommandSave extends Command {
public String getName() {
return "save";
}
public String getHelp() {
return "Saves all worlds";
}
public boolean canUse(ProxyHandler player) {
return player != null && super.canUse(player);
}
public void run(Proxy proxy, ProxyHandler player, String[] args) {
player.sendToServer(new C01PacketChatMessage("/save-all"));
}
}

View file

@ -0,0 +1,23 @@
package proxy.command;
import proxy.Proxy;
import proxy.handler.ProxyHandler;
import proxy.packet.C01PacketChatMessage;
public class CommandSeed extends Command {
public String getName() {
return "seed";
}
public String getHelp() {
return "Displays the world seed";
}
public boolean canUse(ProxyHandler player) {
return player != null && super.canUse(player);
}
public void run(Proxy proxy, ProxyHandler player, String[] args) {
player.sendToServer(new C01PacketChatMessage("/seed"));
}
}

View file

@ -389,7 +389,7 @@ public class ProxyHandler extends User implements Handler {
if(cmd == null)
return null;
List<String> list = Lists.<String>newArrayList();
if(cmd.getPermission() == null || (cmd.getPermission().isEmpty() && !this.isAdmin()) || !this.hasPermission(cmd.getPermission()))
if(!cmd.canUse(this))
return list;
String[] argv = new String[args.length - 1];
System.arraycopy(args, 1, argv, 0, argv.length);
@ -456,7 +456,7 @@ public class ProxyHandler extends User implements Handler {
}
for(Command cmd : this.proxy.getCommands().values()) {
String command = "/" + cmd.getName();
if(cmd.getPermission() != null && (!cmd.getPermission().isEmpty() || this.isAdmin()) && command.regionMatches(true, 0, this.completion, 0, this.completion.length()))
if(cmd.canUse(this) && command.regionMatches(true, 0, this.completion, 0, this.completion.length()))
list.add(command);
}
Collections.sort(list);

View file

@ -0,0 +1,12 @@
package proxy.util;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(FIELD)
public @interface Config {
}

View file

@ -78,16 +78,16 @@ public enum Formatter {
return sb.toString().trim();
}
public static String joinSpace(String ... objs) {
return joinSpace((Object[])objs);
public static String joinSpace(int index, String ... objs) {
return joinSpace(index, (Object[])objs);
}
public static String joinSpace(Object ... objs) {
public static String joinSpace(int index, Object ... objs) {
StringBuilder sb = new StringBuilder();
for(Object obj : objs) {
for(int z = index; z < objs.length; z++) {
if(sb.length() != 0)
sb.append(' ');
sb.append(obj);
sb.append(objs[z]);
}
return sb.toString();
}

View file

@ -0,0 +1,83 @@
package proxy.util;
import java.lang.reflect.Field;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
public class Option {
private final Field field;
private final Object object;
public Option(Field field, Object object) {
this.field = field;
if(this.field.getType() != int.class && this.field.getType() != boolean.class && this.field.getType() != String.class && this.field.getType() != String[].class)
throw new IllegalArgumentException(field.getName() + ": is not int, boolean, String or String[]");
this.object = object;
}
public JsonElement get() {
Object obj;
try {
obj = this.field.get(this.object);
}
catch(IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
if(this.field.getType() == int.class)
return new JsonPrimitive((Integer)obj);
if(this.field.getType() == boolean.class)
return new JsonPrimitive((Boolean)obj);
if(this.field.getType() == String.class)
return new JsonPrimitive((String)obj);
JsonArray arr = new JsonArray();
for(String str : (String[])obj) {
arr.add(new JsonPrimitive(str));
}
return arr;
}
public boolean set(JsonElement data) {
Object value;
if(this.field.getType() == int.class) {
if(!data.isJsonPrimitive())
return false;
value = data.getAsNumber().intValue();
}
else if(this.field.getType() == boolean.class) {
if(!data.isJsonPrimitive())
return false;
value = data.getAsBoolean();
}
else if(this.field.getType() == String.class) {
if(!data.isJsonPrimitive())
return false;
value = data.getAsString();
}
else {
if(data.isJsonPrimitive()) {
value = data.getAsString();
}
else {
if(!data.isJsonArray())
return false;
JsonArray arr = data.getAsJsonArray();
String[] strs = new String[arr.size()];
for(int z = 0; z < strs.length; z++) {
if(!arr.get(z).isJsonPrimitive())
return false;
strs[z] = arr.get(z).getAsString();
}
value = strs;
}
}
try {
this.field.set(this.object, value);
}
catch(IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return true;
}
}