diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs
index acf5a1a2..83de138f 100644
--- a/TShockAPI/ConfigFile.cs
+++ b/TShockAPI/ConfigFile.cs
@@ -67,6 +67,9 @@ namespace TShockAPI
public int MaximumLoginAttempts = 3;
+ public string RconPassword = "";
+ public int RconPort = 7778;
+
public static ConfigFile Read(string path)
{
if (!File.Exists(path))
diff --git a/TShockAPI/RconHandler.cs b/TShockAPI/RconHandler.cs
new file mode 100644
index 00000000..037a54a2
--- /dev/null
+++ b/TShockAPI/RconHandler.cs
@@ -0,0 +1,253 @@
+/*
+TShock, a server mod for Terraria
+Copyright (C) 2011 The TShock Team
+
+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 .
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using Terraria;
+
+namespace TShockAPI
+{
+ class RconHandler
+ {
+ public static string Password = "";
+ private static DateTime lastRequest;
+ public static int ListenPort;
+ public static bool ContinueServer = true;
+ public static string Response = "";
+
+ public static void StartThread()
+ {
+ (new Thread(Start)).Start();
+ }
+
+ public static void Start()
+ {
+ Log.Info("Starting RconHandler.");
+ try
+ {
+ Console.WriteLine(string.Format("RconHandler is running at UDP port {0} and password is {1}",
+ ListenPort.ToString(),
+ Password));
+ Thread listen = new Thread(new ThreadStart(Listener));
+ listen.Start();
+ while (true)
+ {
+ if (listen.ThreadState != ThreadState.Running)
+ {
+ listen.Abort();
+ while (listen.ThreadState != ThreadState.Stopped)
+ continue;
+ listen.Start();
+ }
+ Thread.Sleep(3000);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error(e.ToString());
+ }
+ }
+
+ private static void Listener()
+ {
+ while (ContinueServer)
+ {
+ UdpClient listener = new UdpClient(ListenPort);
+ try
+ {
+ var listenEP = new IPEndPoint(IPAddress.Any, ListenPort);
+ lastRequest = DateTime.Now;
+ byte[] bytes = listener.Receive(ref listenEP);
+ Log.Info(string.Format("Recieved packet from {0}:{1}", listenEP.Address.ToString(), listenEP.Port.ToString()));
+ var packet = parsePacket(bytes, listenEP);
+ listener.Send(packet, packet.Length, listenEP);
+ listener.Close();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e.ToString());
+ listener.Close();
+ }
+ }
+ }
+
+ private static string sendPacket(byte[] bytes, string hostname, int port)
+ {
+ var response = Encoding.UTF8.GetString(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }) + "disconnect";
+ try
+ {
+ var EP = new IPEndPoint(IPAddress.Any, port);
+ UdpClient client = new UdpClient();
+ client.Connect(hostname, port);
+ client.Client.ReceiveTimeout = 500;
+ client.Send(bytes, bytes.Length);
+ response = Encoding.UTF8.GetString(client.Receive(ref EP));
+ }
+ catch (Exception e)
+ {
+ Log.Error(e.ToString());
+ }
+ return response;
+ }
+
+ private static byte[] parsePacket(byte[] bytes, IPEndPoint EP)
+ {
+ string response = "";
+ var packetstring = Encoding.UTF8.GetString(padPacket(bytes));
+ var redirect = false;
+ var print = true;
+ if ((DateTime.Now - lastRequest).Milliseconds >= 100)
+ {
+ if (packetstring.StartsWith("rcon") || packetstring.Substring(4).StartsWith("rcon") || packetstring.Substring(5).StartsWith("rcon"))
+ {
+ if (!string.IsNullOrEmpty(Password))
+ {
+ var args = packetstring.Split(' ');
+ if (args.Length >= 3)
+ {
+ if (args[1] == Password)
+ {
+ string command = "";
+ for (int i = 2; i < args.Length; i++)
+ command += args[i] + " ";
+ command = command.TrimEnd(' ').TrimEnd('\0');
+ response = executeCommand(command);
+ if (response == "" && Response != "")
+ {
+ response = Response;
+ Response = "";
+ }
+ }
+ else
+ {
+ response = "Invalid password.";
+ Log.ConsoleInfo("Bad rcon password from " + EP.ToString());
+ }
+ }
+ else
+ response = "";
+ }
+ else
+ {
+ response = "The server must set a password for clients to use rcon.";
+ Log.Info("No password for rcon set");
+ }
+ }
+ else
+ redirect = true;
+ }
+ if (!redirect)
+ return (constructPacket(response, print));
+ else
+ return (constructPacket("", false));
+ }
+
+ private static string executeCommand(string text)
+ {
+ if (Main.rand == null)
+ Main.rand = new Random();
+ if (WorldGen.genRand == null)
+ WorldGen.genRand = new Random();
+ if (text.StartsWith("exit"))
+ {
+ Tools.ForceKickAll("Server shutting down!");
+ return "Server shutting down.";
+ }
+ else if (text.StartsWith("playing") || text.StartsWith("/playing"))
+ {
+ int count = 0;
+ foreach (TSPlayer player in TShock.Players)
+ {
+ if (player != null && player.Active)
+ {
+ count++;
+ TSPlayer.Server.SendMessage(string.Format("{0} ({1}) [{2}]", player.Name, player.IP, player.Group.Name));
+ }
+ }
+ TSPlayer.Server.SendMessage(string.Format("{0} players connected.", count));
+ }
+ else if (text.StartsWith("status"))
+ {
+ Response += "map: " + Main.worldName + "\n";
+ Response += "num score ping name\tlastmsg address\tqport rate\n";
+ int count = 0;
+ foreach (TSPlayer player in TShock.Players)
+ {
+ if (player != null && player.Active)
+ {
+ count++;
+ Response += (string.Format("{0} 0 0 {1}({2})\t{3} {4}\t0 0", count, player.Name, player.Group.Name, Netplay.serverSock[player.Index].tcpClient.Client.RemoteEndPoint.ToString())) + "\n";
+ }
+ }
+ }
+ else if (text.StartsWith("say "))
+ {
+ Log.Info(string.Format("Server said: {0}", text.Remove(0, 4)));
+ return string.Format("Server said: {0}", text.Remove(0, 4));
+ }
+ else if (text == "autosave")
+ {
+ Main.autoSave = TShock.Config.AutoSave = !TShock.Config.AutoSave;
+ Log.ConsoleInfo("AutoSave " + (TShock.Config.AutoSave ? "Enabled" : "Disabled"));
+ return "AutoSave " + (TShock.Config.AutoSave ? "Enabled" : "Disabled");
+ }
+ else
+ if (!Commands.HandleCommand(TSPlayer.Server, text))
+ return "Invalid command.";
+ return "";
+ }
+
+ private static byte[] constructPacket(string response, bool print)
+ {
+ var oob = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
+ MemoryStream stream = new MemoryStream();
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(oob);
+ if (print)
+ writer.Write(Encoding.UTF8.GetBytes(string.Format("print\n{0}", response)));
+ else
+ writer.Write(Encoding.UTF8.GetBytes("disconnect\n"));
+ var packet = Encoding.UTF8.GetBytes(
+ (Encoding.UTF8.GetString(stream.GetBuffer())
+ .Substring(0, (int)stream.Length)));
+ return packet;
+ }
+
+ private static byte[] padPacket(byte[] packet)
+ {
+ var returnpacket = new byte[(4 + packet.Length)];
+ int h = 0;
+ if (packet[0] != 0xFF)
+ {
+ for (int i = 0; i < 4; i++)
+ returnpacket[i] = 0xFF;
+ for (int i = 4; i < returnpacket.Length; i++)
+ returnpacket[i] = packet[h++];
+ }
+ else
+ returnpacket = packet;
+ return returnpacket;
+ }
+ }
+}
diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs
index 4d6503a9..9737ac03 100644
--- a/TShockAPI/TSPlayer.cs
+++ b/TShockAPI/TSPlayer.cs
@@ -370,6 +370,7 @@ namespace TShockAPI
public override void SendMessage(string msg, byte red, byte green, byte blue)
{
Console.WriteLine(msg);
+ RconHandler.Response += msg + "\n";
}
public void SetBloodMoon(bool bloodMoon)
diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs
index ba7d5e91..e267e469 100755
--- a/TShockAPI/TShock.cs
+++ b/TShockAPI/TShock.cs
@@ -113,6 +113,7 @@ namespace TShockAPI
RegionManager.ReadAllSettings();
WarpsManager.ReadAllSettings();
ItemManager.LoadBans();
+ RconHandler.StartThread();
Log.ConsoleInfo("AutoSave " + (TShock.Config.AutoSave ? "Enabled" : "Disabled"));
Log.ConsoleInfo("Backups " + (Backups.Interval > 0 ? "Enabled" : "Disabled"));
@@ -373,6 +374,8 @@ namespace TShockAPI
if (text.StartsWith("exit"))
{
Tools.ForceKickAll("Server shutting down!");
+ WorldGen.saveWorld(false);
+ Netplay.disconnect = true;
}
else if (text.StartsWith("playing") || text.StartsWith("/playing"))
{
@@ -595,6 +598,9 @@ namespace TShockAPI
Netplay.serverPort = file.ServerPort;
Netplay.spamCheck = file.SpamChecks;
+
+ RconHandler.Password = file.RconPassword;
+ RconHandler.ListenPort = file.RconPort;
}
diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj
index b16f3565..5ecf8d47 100644
--- a/TShockAPI/TShockAPI.csproj
+++ b/TShockAPI/TShockAPI.csproj
@@ -84,6 +84,7 @@
+
@@ -149,11 +150,11 @@
-
\ No newline at end of file