118 lines
3.9 KiB
D
118 lines
3.9 KiB
D
|
module server_protocol;
|
||
|
|
||
|
import std.socket;
|
||
|
import std.algorithm;
|
||
|
import std.conv;
|
||
|
import std.stdio;
|
||
|
|
||
|
import streams;
|
||
|
|
||
|
const int SEGMENT_BITS = 0x7F;
|
||
|
const int CONTINUE_BIT = 0x80;
|
||
|
|
||
|
struct ServerStatus {
|
||
|
bool online;
|
||
|
int playersOnline;
|
||
|
int maxPlayers;
|
||
|
string[] playerNames;
|
||
|
}
|
||
|
|
||
|
ServerStatus fetchStatus(string ipAndPort) {
|
||
|
ptrdiff_t portIdx = countUntil(ipAndPort, ":");
|
||
|
if (portIdx == -1) throw new Exception("Invalid IP address. Port is required.");
|
||
|
string ip = ipAndPort[0..portIdx];
|
||
|
ushort port = ipAndPort[portIdx + 1 .. $].to!ushort;
|
||
|
Address address = new InternetAddress(ip, port);
|
||
|
writeln(address);
|
||
|
|
||
|
Socket socket = new TcpSocket(address);
|
||
|
auto sIn = SocketInputStream(socket);
|
||
|
auto sOut = SocketOutputStream(socket);
|
||
|
auto bufferedOut = bufferedOutputStreamFor(sOut);
|
||
|
|
||
|
auto arrayOut = byteArrayOutputStream();
|
||
|
|
||
|
writeVarInt(&arrayOut, 0x00);
|
||
|
writeVarInt(&arrayOut, 763);
|
||
|
writeString(&arrayOut, ip);
|
||
|
auto dOut = dataOutputStreamFor(&arrayOut, Endianness.BigEndian);
|
||
|
dOut.writeToStream(port);
|
||
|
writeVarInt(&arrayOut, 1);
|
||
|
|
||
|
ubyte[] handshakePacket = arrayOut.toArray();
|
||
|
writeln(handshakePacket);
|
||
|
writeVarInt(&bufferedOut, cast(int) handshakePacket.length);
|
||
|
bufferedOut.writeToStream(handshakePacket);
|
||
|
bufferedOut.flushStream();
|
||
|
writeln("Sent handshake packet.");
|
||
|
|
||
|
auto arrayOut2 = byteArrayOutputStream();
|
||
|
writeVarInt(arrayOut2, 0x00);
|
||
|
ubyte[] statusRequestPacket = arrayOut2.toArray();
|
||
|
writeVarInt(bufferedOut, cast(int) statusRequestPacket.length);
|
||
|
bufferedOut.writeToStream(statusRequestPacket);
|
||
|
bufferedOut.flushStream();
|
||
|
writeln("Sent status request packet.");
|
||
|
|
||
|
int responsePacketSize = readVarInt(sIn);
|
||
|
writefln!"Got response of %d bytes"(responsePacketSize);
|
||
|
ubyte[] packetIdAndData = new ubyte[responsePacketSize];
|
||
|
StreamResult result = sIn.readFromStream(packetIdAndData);
|
||
|
if (result.hasError || result.count != responsePacketSize) throw new Exception("Failed to read response packet.");
|
||
|
auto packetIn = arrayInputStreamFor(packetIdAndData);
|
||
|
int packetId = readVarInt(packetIn);
|
||
|
if (packetId != 0x00) throw new Exception("Received invalid packetId when receiving status response.");
|
||
|
string jsonStr = readString(packetIn);
|
||
|
writeln(jsonStr);
|
||
|
|
||
|
|
||
|
ServerStatus status;
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
int readVarInt(S)(S s) if (isByteInputStream!S) {
|
||
|
int value = 0;
|
||
|
int position = 0;
|
||
|
ubyte[1] buf;
|
||
|
while (true) {
|
||
|
writeln("Attempting to read from stream...");
|
||
|
|
||
|
StreamResult result = s.readFromStream(buf);
|
||
|
writeln(result);
|
||
|
if (result.hasError) throw new Exception(cast(string) result.error.message);
|
||
|
ubyte currentByte = buf[0];
|
||
|
value |= (currentByte & SEGMENT_BITS) << position;
|
||
|
if ((currentByte & CONTINUE_BIT) == 0) break;
|
||
|
position += 7;
|
||
|
if (position >= 32) throw new Exception("VarInt is too big.");
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
void writeVarInt(S)(S s, int value) if (isByteOutputStream!S) {
|
||
|
while (true) {
|
||
|
if ((value & ~SEGMENT_BITS) == 0) {
|
||
|
StreamResult r = s.writeToStream([cast(ubyte) value]);
|
||
|
if (r.hasError || r.count != 1) throw new Exception("Failed to write byte to stream");
|
||
|
return;
|
||
|
}
|
||
|
StreamResult r = s.writeToStream([(value & SEGMENT_BITS) | CONTINUE_BIT]);
|
||
|
if (r.hasError || r.count != 1) throw new Exception("Failed to write byte to stream");
|
||
|
value >>>= 7;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
string readString(S)(S s) if (isByteInputStream!S) {
|
||
|
int length = readVarInt(s);
|
||
|
ubyte[] data = new ubyte[length];
|
||
|
StreamResult result = s.readFromStream(data);
|
||
|
if (result.hasError || result.count != length) throw new Exception("Couldn't read string.");
|
||
|
return cast(string) data.idup;
|
||
|
}
|
||
|
|
||
|
void writeString(S)(S s, string str) if (isByteOutputStream!S) {
|
||
|
ubyte[] bytes = cast(ubyte[]) str;
|
||
|
writeVarInt(s, cast(int) bytes.length);
|
||
|
s.writeToStream(bytes);
|
||
|
}
|