first commit
This commit is contained in:
38
pom.xml
Normal file
38
pom.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.andrewkydev</groupId>
|
||||||
|
<artifactId>ServerReloader</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<!-- Build an executable JAR -->
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addClasspath>true</addClasspath>
|
||||||
|
<classpathPrefix>lib/</classpathPrefix>
|
||||||
|
<mainClass>org.andrewkydev.Main</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
58
src/main/java/org/andrewkydev/Main.java
Normal file
58
src/main/java/org/andrewkydev/Main.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package org.andrewkydev;
|
||||||
|
|
||||||
|
import org.andrewkydev.rcon.Rcon;
|
||||||
|
import org.andrewkydev.rcon.ex.AuthenticationException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class watches a directory for changes and sends a reload command to the server when a file in the directory is modified.
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
/**
|
||||||
|
* The main method of the class.
|
||||||
|
*
|
||||||
|
* @param args the command line arguments
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// The host and port of the Minecraft server
|
||||||
|
String host = "127.0.0.1";
|
||||||
|
int port = 25575;
|
||||||
|
// The password of the RCON connection
|
||||||
|
String password = "1YjdkNTJiN";
|
||||||
|
// The command to send to the server
|
||||||
|
String command = "reload true";
|
||||||
|
// The directory to watch for changes
|
||||||
|
String pathToWatch = "/home/andrew/projects/1/plugins/";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a new watch service
|
||||||
|
WatchService watchService = FileSystems.getDefault().newWatchService();
|
||||||
|
|
||||||
|
// Register the directory with the watch service
|
||||||
|
Path path = Paths.get(pathToWatch);
|
||||||
|
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
|
||||||
|
|
||||||
|
// Wait for changes
|
||||||
|
WatchKey key;
|
||||||
|
while ((key = watchService.take()) != null) {
|
||||||
|
// Process the events
|
||||||
|
for (WatchEvent<?> event : key.pollEvents()) {
|
||||||
|
// Print the event details
|
||||||
|
System.out.println("Event kind: " + event.kind() + ". File affected: " + event.context() + ".");
|
||||||
|
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||||
|
// Create a new Rcon instance and send the command
|
||||||
|
Rcon rcon = new Rcon(host, port, password.getBytes());
|
||||||
|
String response = rcon.command(command);
|
||||||
|
System.out.println(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reset the key
|
||||||
|
key.reset();
|
||||||
|
}
|
||||||
|
} catch (IOException | InterruptedException | AuthenticationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/main/java/org/andrewkydev/rcon/Rcon.java
Normal file
124
src/main/java/org/andrewkydev/rcon/Rcon.java
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package org.andrewkydev.rcon;
|
||||||
|
|
||||||
|
|
||||||
|
import org.andrewkydev.rcon.ex.AuthenticationException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class Rcon {
|
||||||
|
|
||||||
|
private final Object sync = new Object();
|
||||||
|
private final Random rand = new Random();
|
||||||
|
|
||||||
|
private int requestId;
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
private Charset charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create, connect and authenticate a new Rcon object
|
||||||
|
*
|
||||||
|
* @param host Rcon server address
|
||||||
|
* @param port Rcon server port
|
||||||
|
* @param password Rcon server password
|
||||||
|
* @throws IOException
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
public Rcon(String host, int port, byte[] password) throws IOException, AuthenticationException {
|
||||||
|
// Default charset is utf8
|
||||||
|
this.charset = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
// Connect to host
|
||||||
|
this.connect(host, port, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to a rcon server
|
||||||
|
*
|
||||||
|
* @param host Rcon server address
|
||||||
|
* @param port Rcon server port
|
||||||
|
* @param password Rcon server password
|
||||||
|
* @throws IOException
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
public void connect(String host, int port, byte[] password) throws IOException, AuthenticationException {
|
||||||
|
if (host == null || host.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Host can't be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port < 1 || port > 65535) {
|
||||||
|
throw new IllegalArgumentException("Port is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the rcon server
|
||||||
|
synchronized (sync) {
|
||||||
|
// New random request id
|
||||||
|
this.requestId = rand.nextInt();
|
||||||
|
|
||||||
|
// We can't reuse a socket, so we need a new one
|
||||||
|
this.socket = new Socket(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the auth packet
|
||||||
|
RconPacket res = this.send(RconPacket.SERVERDATA_AUTH, password);
|
||||||
|
|
||||||
|
// Auth failed
|
||||||
|
if (res.getRequestId() == -1) {
|
||||||
|
throw new AuthenticationException("Password rejected by server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the current server
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void disconnect() throws IOException {
|
||||||
|
synchronized (sync) {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a command to the server
|
||||||
|
*
|
||||||
|
* @param payload The command to send
|
||||||
|
* @return The payload of the response
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public String command(String payload) throws IOException {
|
||||||
|
if (payload == null || payload.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Payload can't be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
RconPacket response = this.send(RconPacket.SERVERDATA_EXECCOMMAND, payload.getBytes());
|
||||||
|
|
||||||
|
return new String(response.getPayload(), this.getCharset());
|
||||||
|
}
|
||||||
|
|
||||||
|
private RconPacket send(int type, byte[] payload) throws IOException {
|
||||||
|
synchronized (sync) {
|
||||||
|
return RconPacket.send(this, type, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRequestId() {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Charset getCharset() {
|
||||||
|
return charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCharset(Charset charset) {
|
||||||
|
this.charset = charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
144
src/main/java/org/andrewkydev/rcon/RconPacket.java
Normal file
144
src/main/java/org/andrewkydev/rcon/RconPacket.java
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package org.andrewkydev.rcon;
|
||||||
|
|
||||||
|
|
||||||
|
import org.andrewkydev.rcon.ex.MalformedPacketException;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.BufferUnderflowException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class RconPacket {
|
||||||
|
|
||||||
|
public static final int SERVERDATA_EXECCOMMAND = 2;
|
||||||
|
public static final int SERVERDATA_AUTH = 3;
|
||||||
|
|
||||||
|
private int requestId;
|
||||||
|
private int type;
|
||||||
|
private byte[] payload;
|
||||||
|
|
||||||
|
private RconPacket(int requestId, int type, byte[] payload) {
|
||||||
|
this.requestId = requestId;
|
||||||
|
this.type = type;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRequestId() {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a Rcon packet and fetch the response
|
||||||
|
*
|
||||||
|
* @param rcon Rcon instance
|
||||||
|
* @param type The packet type
|
||||||
|
* @param payload The payload (password, command, etc.)
|
||||||
|
* @return A RconPacket object containing the response
|
||||||
|
* @throws IOException
|
||||||
|
* @throws MalformedPacketException
|
||||||
|
*/
|
||||||
|
protected static RconPacket send(Rcon rcon, int type, byte[] payload) throws IOException {
|
||||||
|
try {
|
||||||
|
RconPacket.write(rcon.getSocket().getOutputStream(), rcon.getRequestId(), type, payload);
|
||||||
|
} catch (SocketException se) {
|
||||||
|
// Close the socket if something happens
|
||||||
|
rcon.getSocket().close();
|
||||||
|
|
||||||
|
// Rethrow the exception
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RconPacket.read(rcon.getSocket().getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a rcon packet on an outputstream
|
||||||
|
*
|
||||||
|
* @param out The OutputStream to write on
|
||||||
|
* @param requestId The request id
|
||||||
|
* @param type The packet type
|
||||||
|
* @param payload The payload
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private static void write(OutputStream out, int requestId, int type, byte[] payload) throws IOException {
|
||||||
|
int bodyLength = RconPacket.getBodyLength(payload.length);
|
||||||
|
int packetLength = RconPacket.getPacketLength(bodyLength);
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(packetLength);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
buffer.putInt(bodyLength);
|
||||||
|
buffer.putInt(requestId);
|
||||||
|
buffer.putInt(type);
|
||||||
|
buffer.put(payload);
|
||||||
|
|
||||||
|
// Null bytes terminators
|
||||||
|
buffer.put((byte) 0);
|
||||||
|
buffer.put((byte) 0);
|
||||||
|
|
||||||
|
// Woosh!
|
||||||
|
out.write(buffer.array());
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an incoming rcon packet
|
||||||
|
*
|
||||||
|
* @param in The InputStream to read on
|
||||||
|
* @return The read RconPacket
|
||||||
|
* @throws IOException
|
||||||
|
* @throws MalformedPacketException
|
||||||
|
*/
|
||||||
|
private static RconPacket read(InputStream in) throws IOException {
|
||||||
|
// Header is 3 4-bytes ints
|
||||||
|
byte[] header = new byte[4 * 3];
|
||||||
|
|
||||||
|
// Read the 3 ints
|
||||||
|
in.read(header);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use a bytebuffer in little endian to read the first 3 ints
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(header);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
int length = buffer.getInt();
|
||||||
|
int requestId = buffer.getInt();
|
||||||
|
int type = buffer.getInt();
|
||||||
|
|
||||||
|
// Payload size can be computed now that we have its length
|
||||||
|
byte[] payload = new byte[length - 4 - 4 - 2];
|
||||||
|
|
||||||
|
DataInputStream dis = new DataInputStream(in);
|
||||||
|
|
||||||
|
// Read the full payload
|
||||||
|
dis.readFully(payload);
|
||||||
|
|
||||||
|
// Read the null bytes
|
||||||
|
dis.read(new byte[2]);
|
||||||
|
|
||||||
|
return new RconPacket(requestId, type, payload);
|
||||||
|
} catch (BufferUnderflowException | EOFException e) {
|
||||||
|
throw new MalformedPacketException("Cannot read the whole packet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getPacketLength(int bodyLength) {
|
||||||
|
// 4 bytes for length + x bytes for body length
|
||||||
|
return 4 + bodyLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getBodyLength(int payloadLength) {
|
||||||
|
// 4 bytes for requestId, 4 bytes for type, x bytes for payload, 2 bytes for two null bytes
|
||||||
|
return 4 + 4 + payloadLength + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.andrewkydev.rcon.ex;
|
||||||
|
|
||||||
|
public class AuthenticationException extends Exception {
|
||||||
|
|
||||||
|
public AuthenticationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.andrewkydev.rcon.ex;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class MalformedPacketException extends IOException {
|
||||||
|
|
||||||
|
public MalformedPacketException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user