-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for plugin info field, show warning when mod is loaded cl…
…ient-side
- Loading branch information
1 parent
8260fb3
commit 2465883
Showing
5 changed files
with
204 additions
and
122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
src/server/java/io/thiemann/kurt/query/query/AbstractQueryServer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
161
src/server/java/io/thiemann/kurt/query/query/QueryServer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |