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); }