Last active
January 1, 2016 13:59
-
-
Save anna-is-cute/8154689 to your computer and use it in GitHub Desktop.
MCSignOnDoor in D
This file contains hidden or 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
/* | |
* MCSignOnDoor rewrite in D by jkcclemens <[email protected]> | |
* | |
* 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