Skip to content

Commit

Permalink
add support for plugin info field, show warning when mod is loaded cl…
Browse files Browse the repository at this point in the history
…ient-side
  • Loading branch information
KurtThiemann committed Sep 17, 2023
1 parent 8260fb3 commit 2465883
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 122 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {

apply plugin: 'foxloader.dev'

version '1.1.0'
version '1.2.0'

foxloader {
// forceReload = true
Expand Down
4 changes: 4 additions & 0 deletions src/client/java/io/thiemann/kurt/query/QueryModClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
import com.fox2code.foxloader.loader.ClientMod;

public class QueryModClient extends QueryMod implements ClientMod {
@Override
public void onInit() {
this.getLogger().warning("ReIndevQuery is only needed on the server side.");
}
}
1 change: 1 addition & 0 deletions src/server/java/io/thiemann/kurt/query/QueryModServer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.thiemann.kurt.query;

import com.fox2code.foxloader.loader.ModLoader;
import io.thiemann.kurt.query.query.QueryServer;
import com.fox2code.foxloader.loader.ServerMod;
import com.fox2code.foxloader.network.NetworkPlayer;
Expand Down
158 changes: 158 additions & 0 deletions src/server/java/io/thiemann/kurt/query/query/AbstractQueryServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package io.thiemann.kurt.query.query;

import com.fox2code.foxloader.network.NetworkPlayer;
import io.thiemann.kurt.query.query.packet.*;
import net.minecraft.server.MinecraftServer;

import java.io.IOException;
import java.net.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public abstract class AbstractQueryServer {
private final DatagramSocket socket;
private final Thread thread;
private final Map<String, Integer> challenges = new ConcurrentHashMap<>();
private long lastChallengeClear = 0;

public AbstractQueryServer(int port, InetAddress laddr) throws SocketException {
this.socket = new DatagramSocket(port, laddr);
this.thread = new Thread(this::run);
this.thread.start();

}

private void run() {
while (!this.socket.isClosed()) {
if (System.currentTimeMillis() - this.lastChallengeClear > 30000) {
this.challenges.clear();
this.lastChallengeClear = System.currentTimeMillis();
}

try {
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
this.socket.receive(packet);
this.handlePacket(packet);
} catch (IOException ignored) {
}
}
}

/**
* Handle incoming packet
* <a href="https://wiki.vg/Query#Client_to_Server_Packet_Format">Packet format</a>
*
* @param msg incoming packet
*/
private void handlePacket(DatagramPacket msg) {
ServerBoundPacket packet;
try {
packet = ServerBoundPacket.fromBuffer(msg.getData(), msg.getLength());
} catch (QueryProtocolException e) {
return;
}

ClientBoundPacket response;
if (packet instanceof ServerBoundHandshakePacket) {
response = this.handshake((ServerBoundHandshakePacket) packet, msg.getSocketAddress());
} else if (packet instanceof ServerBoundStatPacket) {
if (((ServerBoundStatPacket) packet).isFull()) {
response = this.fullStat((ServerBoundStatPacket) packet, msg.getSocketAddress());
} else {
response = this.basicStat((ServerBoundStatPacket) packet, msg.getSocketAddress());
}
} else {
return;
}

if (response == null) {
return;
}

byte[] payload = response.serialize();
DatagramPacket responsePacket = new DatagramPacket(payload, payload.length, msg.getSocketAddress());
try {
this.socket.send(responsePacket);
} catch (IOException ignored) {
}
}

private ClientBoundHandshakePacket handshake(ServerBoundHandshakePacket packet, SocketAddress address) {
int randomToken = ((int) (Math.random() * 1000000)) & 0x0F0F0F0F;
this.challenges.put(address.toString(), randomToken);

return new ClientBoundHandshakePacket(packet.getSessionId(), randomToken);
}

private ClientBoundBasicStatPacket basicStat(ServerBoundStatPacket packet, SocketAddress address) {
if (!this.challenges.containsKey(address.toString())) {
return null;
}

if (packet.getChallenge() != this.challenges.get(address.toString())) {
return null;
}

return new ClientBoundBasicStatPacket(
packet.getSessionId(),
this.getMotd(),
"SMP",
"world",
this.getPlayersOnline(),
this.getMaxPlayers(),
this.getMinecraftServerPort(),
this.getMinecraftServerIp()
);
}

private ClientBoundFullStatPacket fullStat(ServerBoundStatPacket packet, SocketAddress address) {
if (!this.challenges.containsKey(address.toString())) {
return null;
}

if (packet.getChallenge() != this.challenges.get(address.toString())) {
System.out.println("Wrong challenge: " + packet.getChallenge() + " != " + this.challenges.get(address.toString()) + " (" + address.toString() + ")");
return null;
}

return new ClientBoundFullStatPacket(
packet.getSessionId(),
this.getMotd(),
"SMP",
this.getGameId(),
this.getGameVersion(),
this.getPluginInfo(),
"world",
this.getPlayersOnline(),
this.getMaxPlayers(),
this.getMinecraftServerPort(),
this.getMinecraftServerIp(),
this.getPlayerList()
);
}

public void close() {
this.socket.close();
this.thread.interrupt();
}

protected abstract int getMinecraftServerPort();

protected abstract String getMinecraftServerIp();

protected abstract String getMotd();

protected abstract int getPlayersOnline();

protected abstract int getMaxPlayers();

protected abstract String[] getPlayerList();

protected abstract String getPluginInfo();

protected abstract String getGameId();

protected abstract String getGameVersion();
}
161 changes: 40 additions & 121 deletions src/server/java/io/thiemann/kurt/query/query/QueryServer.java
Original file line number Diff line number Diff line change
@@ -1,153 +1,72 @@
package io.thiemann.kurt.query.query;

import com.fox2code.foxloader.launcher.BuildConfig;
import com.fox2code.foxloader.loader.ModContainer;
import com.fox2code.foxloader.loader.ModLoader;
import com.fox2code.foxloader.network.NetworkPlayer;
import io.thiemann.kurt.query.query.packet.*;
import net.minecraft.server.MinecraftServer;
import java.io.IOException;
import java.net.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Collection;


public class QueryServer {
public class QueryServer extends AbstractQueryServer {
private final MinecraftServer server;
private final DatagramSocket socket;
private final Thread thread;
private final Map<String, Integer> challenges = new ConcurrentHashMap<>();
private long lastChallengeClear = 0;

public QueryServer(MinecraftServer server, int port, InetAddress laddr) throws SocketException {
super(port, laddr);
this.server = server;
this.socket = new DatagramSocket(port, laddr);
this.thread = new Thread(this::run);
this.thread.start();

}

private void run() {
while (!this.socket.isClosed()) {
if (System.currentTimeMillis() - this.lastChallengeClear > 30000) {
this.challenges.clear();
this.lastChallengeClear = System.currentTimeMillis();
}

try {
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
this.socket.receive(packet);
this.handlePacket(packet);
} catch (IOException ignored) {
}
}
@Override
protected int getMinecraftServerPort() {
return this.server.propertyManagerObj.getIntProperty("server-port", 25565);
}

/**
* Handle incoming packet
* <a href="https://wiki.vg/Query#Client_to_Server_Packet_Format">Packet format</a>
*
* @param msg incoming packet
*/
private void handlePacket(DatagramPacket msg) {
ServerBoundPacket packet;
try {
packet = ServerBoundPacket.fromBuffer(msg.getData(), msg.getLength());
} catch (QueryProtocolException e) {
return;
}

ClientBoundPacket response;
if (packet instanceof ServerBoundHandshakePacket) {
response = this.handshake((ServerBoundHandshakePacket) packet, msg.getSocketAddress());
} else if (packet instanceof ServerBoundStatPacket) {
if (((ServerBoundStatPacket) packet).isFull()) {
response = this.fullStat((ServerBoundStatPacket) packet, msg.getSocketAddress());
} else {
response = this.basicStat((ServerBoundStatPacket) packet, msg.getSocketAddress());
}
} else {
return;
}

if (response == null) {
return;
}

byte[] payload = response.serialize();
DatagramPacket responsePacket = new DatagramPacket(payload, payload.length, msg.getSocketAddress());
try {
this.socket.send(responsePacket);
} catch (IOException ignored) {
@Override
protected String getMinecraftServerIp() {
String address = this.server.propertyManagerObj.getStringProperty("server-ip", "");
if (address.length() == 0) {
address = "127.0.0.1";
}
return address;
}

private ClientBoundHandshakePacket handshake(ServerBoundHandshakePacket packet, SocketAddress address) {
int randomToken = ((int) (Math.random() * 1000000)) & 0x0F0F0F0F;
this.challenges.put(address.toString(), randomToken);

return new ClientBoundHandshakePacket(packet.getSessionId(), randomToken);
@Override
protected String getMotd() {
return this.server.getMotd();
}

private ClientBoundBasicStatPacket basicStat(ServerBoundStatPacket packet, SocketAddress address) {
if (!this.challenges.containsKey(address.toString())) {
return null;
}

if (packet.getChallenge() != this.challenges.get(address.toString())) {
return null;
}

return new ClientBoundBasicStatPacket(
packet.getSessionId(),
this.server.getMotd(),
"SMP",
"world",
this.server.configManager.playersOnline(),
this.server.configManager.getMaxPlayers(),
this.getMinecraftServerPort(),
this.getMinecraftServerIp()
);
@Override
protected int getPlayersOnline() {
return this.server.configManager.playersOnline();
}

private ClientBoundFullStatPacket fullStat(ServerBoundStatPacket packet, SocketAddress address) {
if (!this.challenges.containsKey(address.toString())) {
return null;
}

if (packet.getChallenge() != this.challenges.get(address.toString())) {
System.out.println("Wrong challenge: " + packet.getChallenge() + " != " + this.challenges.get(address.toString()) + " (" + address.toString() + ")");
return null;
}
@Override
protected int getMaxPlayers() {
return this.server.configManager.getMaxPlayers();
}

return new ClientBoundFullStatPacket(
packet.getSessionId(),
this.server.getMotd(),
"SMP",
"MINECRAFT",
"Beta 1.7",
"FoxLoader",
"world",
this.server.configManager.playersOnline(),
this.server.configManager.getMaxPlayers(),
this.getMinecraftServerPort(),
this.getMinecraftServerIp(),
this.server.configManager.playerEntities.stream().map(NetworkPlayer::getPlayerName).toArray(String[]::new)
);
@Override
protected String[] getPlayerList() {
return this.server.configManager.playerEntities.stream().map(NetworkPlayer::getPlayerName).toArray(String[]::new);
}

private int getMinecraftServerPort() {
return this.server.propertyManagerObj.getIntProperty("server-port", 25565);
@Override
protected String getPluginInfo() {
Collection<ModContainer> mods = ModLoader.getModContainers();
String[] modNames = mods.stream().map(mod -> mod.name + " " + mod.version).toArray(String[]::new);

return "FoxLoader " + BuildConfig.FOXLOADER_VERSION + ": " + String.join("; ", modNames);
}

private String getMinecraftServerIp() {
String address = this.server.propertyManagerObj.getStringProperty("server-ip", "");
if (address.length() == 0) {
address = "127.0.0.1";
}
return address;
@Override
protected String getGameId() {
return "MINECRAFT";
}

public void close() {
this.socket.close();
this.thread.interrupt();
@Override
protected String getGameVersion() {
return "Beta 1.7";
}
}

0 comments on commit 2465883

Please sign in to comment.