Skip to content

Instantly share code, notes, and snippets.

@anna-is-cute
Last active January 1, 2016 13:59
Show Gist options
  • Save anna-is-cute/8154689 to your computer and use it in GitHub Desktop.
Save anna-is-cute/8154689 to your computer and use it in GitHub Desktop.
MCSignOnDoor in D
/*
* MCSignOnDoor rewrite in D by jkcclemens <jkc.clemens@royaldev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
module org.royaldev.mcsod.MCSOD;
import std.stdio: writeln, writefln;
import std.c.stdlib: exit;
import std.conv: to;
import std.socket: Socket, SocketException, SocketType, AddressFamily, InternetAddress;
import std.getopt: getopt, config;
string host = "localhost";
short port = 25565;
string motd = "Server is down.";
int onlinePlayers = 0;
int maxPlayers = 20;
MCSOD mcsod = null;
extern(C) private void shutdown(int value) {
if (mcsod !is null) mcsod.shutdown();
exit(1);
}
private void main(string[] args) {
version (Posix) { // posix support only; windows should learn to be a legit OS
import core.sys.posix.signal: sigset, SIGINT;
sigset(SIGINT, &shutdown);
}
bool help = false;
getopt(
args,
config.caseSensitive,
config.bundling,
config.passThrough,
"h|help", &help,
"H|host", &host,
"p|port", &port,
"m|motd", &motd,
"o|online-players", &onlinePlayers,
"P|max-players", &maxPlayers
);
if (help) {
writeln("Usage: ", args[0], " [-H host] [-p port] [-m motd] [-o onlinePlayers] [-P maxPlayers]");
writeln("-H | --host\n\tSets the hostname to bind to.\n\tDefaults to \"localhost\"");
writeln("-p | --port\n\tSets the port to bind to.\n\tDefaults to 25565");
writeln("-m | --motd\n\tSets the Message of the Day to display.\n\tDefaults to \"Server is down.\"");
writeln("-o | --online-players\n\tSets the amount of online players to display.\n\tDefaults to 0.");
writeln("-P | --max-players\n\tSets the maximum amount of players to display.\n\tDefaults to 20.");
exit(0);
}
mcsod = new MCSOD();
}
public class MCSOD {
Socket s = null;
Socket c = null;
public this() {
try {
s = new Socket(AddressFamily.INET, SocketType.STREAM);
s.bind(new InternetAddress(host, port));
} catch (SocketException s) {
writeln("Couldn't connect!");
exit(1);
}
s.listen(100);
while (true) {
c = s.accept();
writeln("Connection from ", c.remoteAddress().toAddrString(), ":", c.remoteAddress().toPortString(), ".");
while (c.isAlive()) {
int length = fromVarint(c);
if (length < 1) {
c.close();
continue;
}
int packetID = fromVarint(c);
if (packetID != 0) {
writefln("Invalid packet ID: %s", packetID);
c.close();
continue;
}
int protocolVersion = fromVarint(c);
if (protocolVersion != 4) {
writefln("Invalid protocol version: %s", protocolVersion);
c.close();
continue;
}
int stringLength = fromVarint(c);
if (stringLength < 1) c.close();
if (c.receive(new byte[stringLength]) != stringLength) { // read out host string
c.close();
continue;
}
if (c.receive(new byte[2]) != 2) { // read two bytes for port
c.close();
continue;
}
int nextState = fromVarint(c);
if (nextState != 1) {
writefln("Invalid next state: %s", nextState);
c.close();
continue;
}
if (fromVarint(c) != 1) {
c.close();
continue;
}
packetID = fromVarint(c);
if (packetID != 0) {
writefln("Invalid packet ID: %s", packetID);
c.close();
continue;
}
char[] json =
"{" ~
"\"version\": {" ~
"\"name\": \"Offline\"," ~
"\"protocol\": 4" ~
"}," ~
"\"players\": {" ~
"\"max\": " ~ to!string(maxPlayers) ~ "," ~
"\"online\": " ~ to!string(onlinePlayers) ~
"}," ~
"\"description\": \"" ~ motd ~ "\"" ~
"}".dup;
auto jsonLength = toVarint(json.length);
auto toSend = toVarint(0) ~ jsonLength ~ cast(ubyte[]) json;
toSend = toVarint(toSend.length) ~ toSend;
c.send(toSend);
writeln("Sent response.");
c.close();
}
if (c.isAlive()) c.close();
}
}
private void shutdown() {
if (c !is null) c.close();
if (s !is null) s.close();
}
private int fromVarint(Socket s) {
if (!s.isAlive()) throw new Exception("Socket is closed");
int d = 0;
int i = 0;
while (true) {
auto bs = new byte[1];
s.receive(bs);
auto b = bs[0];
d |= (b & 0x7F) << 7 * i;
i++;
if (!(b & 0x80)) return d;
}
}
private ubyte[] toVarint(long input) {
ubyte[]ret;
int x;
if (input < 0) ret.length = 10; // shortcut negative numbers, this is always the case
else {
long tmp = input;
for (x = 1; tmp >= 128; x++) {
// arithmetic shift is fine, because we've already checked for
// negative numbers
tmp >>= 7;
}
ret.length = x;
}
for (x = 0; x < ret.length; x++) {
// set the top bit
ret[x] = cast(ubyte) (1 << 7);
ret[x] |= (cast(ubyte) input) & 0b1111111;
input >>= 7;
}
// unset the top bit of the last data element
ret[$-1] &= 0b1111111;
return ret;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment