2015-08-20 01:06:44 -04:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2017-05-26 18:49:27 -04:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2018-04-08 02:44:42 -04:00
|
|
|
|
using SharedLibraryCore.Helpers;
|
|
|
|
|
using SharedLibraryCore.Objects;
|
|
|
|
|
using SharedLibraryCore.Dtos;
|
|
|
|
|
using SharedLibraryCore.Configuration;
|
2018-04-11 18:24:21 -04:00
|
|
|
|
using SharedLibraryCore.Interfaces;
|
2018-04-08 02:44:42 -04:00
|
|
|
|
|
|
|
|
|
namespace SharedLibraryCore
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
|
|
|
|
public abstract class Server
|
|
|
|
|
{
|
2017-08-08 22:44:52 -04:00
|
|
|
|
public enum Game
|
|
|
|
|
{
|
|
|
|
|
UKN,
|
|
|
|
|
IW3,
|
|
|
|
|
IW4,
|
|
|
|
|
IW5,
|
|
|
|
|
T4,
|
|
|
|
|
T5,
|
2018-03-24 17:35:54 -04:00
|
|
|
|
T5M,
|
2018-04-08 17:50:58 -04:00
|
|
|
|
T6M,
|
2017-08-08 22:44:52 -04:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-11 18:24:21 -04:00
|
|
|
|
public Server(IManager mgr, ServerConfiguration config)
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2017-06-19 13:58:01 -04:00
|
|
|
|
Password = config.Password;
|
2018-03-14 01:36:25 -04:00
|
|
|
|
IP = config.IPAddress;
|
2017-06-19 13:58:01 -04:00
|
|
|
|
Port = config.Port;
|
2017-05-26 18:49:27 -04:00
|
|
|
|
Manager = mgr;
|
2018-10-06 12:47:14 -04:00
|
|
|
|
Logger = Manager.GetLogger(this.GetHashCode());
|
2018-10-07 22:34:30 -04:00
|
|
|
|
Logger.WriteInfo(this.ToString());
|
2018-03-14 01:36:25 -04:00
|
|
|
|
ServerConfig = config;
|
2018-04-02 01:25:06 -04:00
|
|
|
|
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2017-05-26 18:49:27 -04:00
|
|
|
|
Players = new List<Player>(new Player[18]);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
Reports = new List<Report>();
|
2018-03-14 14:22:04 -04:00
|
|
|
|
PlayerHistory = new Queue<PlayerHistory>();
|
2018-03-09 03:01:12 -05:00
|
|
|
|
ChatHistory = new List<ChatInfo>();
|
2017-06-19 13:58:01 -04:00
|
|
|
|
NextMessage = 0;
|
2018-03-30 00:13:40 -04:00
|
|
|
|
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
|
|
|
|
|
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
|
2017-06-12 13:50:00 -04:00
|
|
|
|
InitializeTokens();
|
|
|
|
|
InitializeAutoMessages();
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Returns current server IP set by `net_ip` -- *STRING*
|
2017-06-12 13:50:00 -04:00
|
|
|
|
public String GetIP()
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
|
|
|
|
return IP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Returns current server port set by `net_port` -- *INT*
|
2017-06-12 13:50:00 -04:00
|
|
|
|
public int GetPort()
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
|
|
|
|
return Port;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Returns list of all current players
|
2017-06-07 20:59:59 -04:00
|
|
|
|
public List<Player> GetPlayersAsList()
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2017-05-26 18:49:27 -04:00
|
|
|
|
return Players.FindAll(x => x != null);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Add a player to the server's player list
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="P">Player pulled from memory reading</param>
|
|
|
|
|
/// <returns>True if player added sucessfully, false otherwise</returns>
|
2017-05-26 18:49:27 -04:00
|
|
|
|
abstract public Task<bool> AddPlayer(Player P);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Remove player by client number
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="cNum">Client ID of player to be removed</param>
|
|
|
|
|
/// <returns>true if removal succeded, false otherwise</returns>
|
2017-05-26 18:49:27 -04:00
|
|
|
|
abstract public Task RemovePlayer(int cNum);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get a player by name
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pName">Player name to search for</param>
|
|
|
|
|
/// <returns>Matching player if found</returns>
|
2017-11-16 18:09:19 -05:00
|
|
|
|
public List<Player> GetClientByName(String pName)
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2017-06-16 17:35:51 -04:00
|
|
|
|
string[] QuoteSplit = pName.Split('"');
|
2017-11-16 18:09:19 -05:00
|
|
|
|
bool literal = false;
|
2017-06-16 17:35:51 -04:00
|
|
|
|
if (QuoteSplit.Length > 1)
|
2017-11-16 18:09:19 -05:00
|
|
|
|
{
|
2017-06-16 17:35:51 -04:00
|
|
|
|
pName = QuoteSplit[1];
|
2017-11-16 18:09:19 -05:00
|
|
|
|
literal = true;
|
|
|
|
|
}
|
|
|
|
|
if (literal)
|
|
|
|
|
return Players.Where(p => p != null && p.Name.ToLower().Equals(pName.ToLower())).ToList();
|
|
|
|
|
|
|
|
|
|
return Players.Where(p => p != null && p.Name.ToLower().Contains(pName.ToLower())).ToList();
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-26 20:19:42 -04:00
|
|
|
|
virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts) => (Task<bool>)Task.CompletedTask;
|
2017-10-15 21:40:27 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Process any server event
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="E">Event</param>
|
|
|
|
|
/// <returns>True on sucess</returns>
|
2018-09-23 20:45:54 -04:00
|
|
|
|
abstract protected Task<bool> ProcessEvent(GameEvent E);
|
2018-04-13 02:32:30 -04:00
|
|
|
|
abstract public Task ExecuteEvent(GameEvent E);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send a message to all players
|
|
|
|
|
/// </summary>
|
2018-09-29 15:52:22 -04:00
|
|
|
|
/// <param name="message">Message to be sent to all players</param>
|
|
|
|
|
public GameEvent Broadcast(string message, Player sender = null)
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2018-09-29 15:52:22 -04:00
|
|
|
|
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{message}");
|
2018-10-25 09:14:39 -04:00
|
|
|
|
|
|
|
|
|
//Logger.WriteVerbose(message.StripColors());
|
|
|
|
|
|
2018-08-30 21:53:00 -04:00
|
|
|
|
var e = new GameEvent()
|
2018-06-16 22:11:25 -04:00
|
|
|
|
{
|
|
|
|
|
Type = GameEvent.EventType.Broadcast,
|
2018-08-31 23:35:51 -04:00
|
|
|
|
Data = formattedMessage,
|
|
|
|
|
Owner = this,
|
2018-09-29 15:52:22 -04:00
|
|
|
|
Origin = sender,
|
2018-08-30 21:53:00 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Manager.GetEventHandler().AddEvent(e);
|
2018-09-29 15:52:22 -04:00
|
|
|
|
return e;
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
2017-05-26 18:49:27 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send a message to a particular players
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Message">Message to send</param>
|
|
|
|
|
/// <param name="Target">Player to send message to</param>
|
2018-09-29 15:52:22 -04:00
|
|
|
|
protected async Task Tell(String Message, Player Target)
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2018-02-08 02:23:45 -05:00
|
|
|
|
#if !DEBUG
|
2018-09-04 13:40:29 -04:00
|
|
|
|
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Tell, Target.ClientNumber, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{Message}");
|
2017-11-25 20:29:58 -05:00
|
|
|
|
if (Target.ClientNumber > -1 && Message.Length > 0 && Target.Level != Player.Permission.Console)
|
2018-04-13 02:32:30 -04:00
|
|
|
|
await this.ExecuteCommandAsync(formattedMessage);
|
2018-02-08 02:23:45 -05:00
|
|
|
|
#else
|
|
|
|
|
Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}");
|
2018-04-26 02:13:04 -04:00
|
|
|
|
await Task.CompletedTask;
|
2018-02-08 02:23:45 -05:00
|
|
|
|
#endif
|
2015-08-22 02:04:30 -04:00
|
|
|
|
|
|
|
|
|
if (Target.Level == Player.Permission.Console)
|
|
|
|
|
{
|
|
|
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
2018-05-10 01:34:29 -04:00
|
|
|
|
Console.WriteLine(Message.StripColors());
|
2015-08-22 02:04:30 -04:00
|
|
|
|
Console.ForegroundColor = ConsoleColor.Gray;
|
|
|
|
|
}
|
2018-05-08 00:58:46 -04:00
|
|
|
|
|
2018-09-04 13:40:29 -04:00
|
|
|
|
// prevent this from queueing up too many command responses
|
2018-02-23 02:06:13 -05:00
|
|
|
|
if (CommandResult.Count > 15)
|
|
|
|
|
CommandResult.RemoveAt(0);
|
2018-04-08 02:44:42 -04:00
|
|
|
|
|
2018-09-04 13:40:29 -04:00
|
|
|
|
// it was a remote command so we need to add it to the command result queue
|
2018-05-10 01:34:29 -04:00
|
|
|
|
if (Target.ClientNumber < 0)
|
2017-11-25 20:29:58 -05:00
|
|
|
|
{
|
2018-05-10 01:34:29 -04:00
|
|
|
|
CommandResult.Add(new CommandResponseInfo()
|
|
|
|
|
{
|
|
|
|
|
Response = Message.StripColors(),
|
|
|
|
|
ClientId = Target.ClientId
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send a message to all admins on the server
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="message">Message to send out</param>
|
2018-09-29 15:52:22 -04:00
|
|
|
|
public void ToAdmins(String message)
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2018-09-02 17:59:27 -04:00
|
|
|
|
foreach (var client in GetPlayersAsList().Where(c => c.Level > Player.Permission.Flagged))
|
2015-08-20 15:23:13 -04:00
|
|
|
|
{
|
2018-09-29 15:52:22 -04:00
|
|
|
|
client.Tell(message);
|
2015-08-20 15:23:13 -04:00
|
|
|
|
}
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Kick a player from the server
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Reason">Reason for kicking</param>
|
|
|
|
|
/// <param name="Target">Player to kick</param>
|
2018-10-02 13:39:08 -04:00
|
|
|
|
abstract protected Task Kick(String Reason, Player Target, Player Origin);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Temporarily ban a player ( default 1 hour ) from the server
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Reason">Reason for banning the player</param>
|
|
|
|
|
/// <param name="Target">The player to ban</param>
|
2018-10-02 13:39:08 -04:00
|
|
|
|
abstract protected Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Perm ban a player from the server
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Reason">The reason for the ban</param>
|
|
|
|
|
/// <param name="Target">The person to ban</param>
|
|
|
|
|
/// <param name="Origin">The person who banned the target</param>
|
2018-10-02 13:39:08 -04:00
|
|
|
|
abstract protected Task Ban(String Reason, Player Target, Player Origin);
|
2015-08-20 15:23:13 -04:00
|
|
|
|
|
2018-10-03 22:20:49 -04:00
|
|
|
|
abstract protected Task Warn(String Reason, Player Target, Player Origin);
|
2016-01-16 17:58:24 -05:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Unban a player by npID / GUID
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="npID">npID of the player</param>
|
|
|
|
|
/// <param name="Target">I don't remember what this is for</param>
|
|
|
|
|
/// <returns></returns>
|
2018-02-17 01:13:38 -05:00
|
|
|
|
abstract public Task Unban(string reason, Player Target, Player Origin);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
2017-05-26 18:49:27 -04:00
|
|
|
|
/// Change the current searver map
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// </summary>
|
2017-05-26 18:49:27 -04:00
|
|
|
|
/// <param name="mapName">Non-localized map name</param>
|
|
|
|
|
public async Task LoadMap(string mapName)
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2017-05-26 18:49:27 -04:00
|
|
|
|
await this.ExecuteCommandAsync($"map {mapName}");
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-26 18:49:27 -04:00
|
|
|
|
public async Task LoadMap(Map newMap)
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2017-05-26 18:49:27 -04:00
|
|
|
|
await this.ExecuteCommandAsync($"map {newMap.Name}");
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initalize the macro variables
|
|
|
|
|
/// </summary>
|
2017-06-12 13:50:00 -04:00
|
|
|
|
abstract public void InitializeTokens();
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Read the map configuration
|
|
|
|
|
/// </summary>
|
2017-06-12 13:50:00 -04:00
|
|
|
|
protected void InitializeMaps()
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2017-06-19 13:58:01 -04:00
|
|
|
|
Maps = new List<Map>();
|
2018-03-26 00:51:25 -04:00
|
|
|
|
var gameMaps = Manager.GetApplicationSettings().Configuration().Maps.FirstOrDefault(m => m.Game == GameName);
|
|
|
|
|
if (gameMaps != null)
|
|
|
|
|
Maps.AddRange(gameMaps.Maps);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-20 15:23:13 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initialize the messages to be broadcasted
|
|
|
|
|
/// </summary>
|
2017-06-12 13:50:00 -04:00
|
|
|
|
protected void InitializeAutoMessages()
|
2015-08-20 01:06:44 -04:00
|
|
|
|
{
|
2017-06-19 13:58:01 -04:00
|
|
|
|
BroadcastMessages = new List<String>();
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2018-05-10 01:34:29 -04:00
|
|
|
|
if (ServerConfig.AutoMessages != null)
|
2018-03-18 22:25:11 -04:00
|
|
|
|
BroadcastMessages.AddRange(ServerConfig.AutoMessages);
|
|
|
|
|
BroadcastMessages.AddRange(Manager.GetApplicationSettings().Configuration().AutoMessages);
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 23:45:21 -04:00
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
2018-10-07 22:34:30 -04:00
|
|
|
|
return $"{IP}-{Port}";
|
2017-06-06 23:45:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-25 20:29:58 -05:00
|
|
|
|
protected async Task<bool> ScriptLoaded()
|
2017-11-02 12:49:45 -04:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return (await this.GetDvarAsync<string>("sv_customcallbacks")).Value == "1";
|
|
|
|
|
}
|
2017-11-25 20:29:58 -05:00
|
|
|
|
|
|
|
|
|
catch (Exceptions.DvarException)
|
2017-11-02 12:49:45 -04:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-19 13:58:01 -04:00
|
|
|
|
// Objects
|
2018-04-13 02:32:30 -04:00
|
|
|
|
public IManager Manager { get; protected set; }
|
|
|
|
|
public ILogger Logger { get; private set; }
|
2018-03-14 01:36:25 -04:00
|
|
|
|
public ServerConfiguration ServerConfig { get; private set; }
|
2017-06-19 13:58:01 -04:00
|
|
|
|
public List<Map> Maps { get; protected set; }
|
2017-10-15 21:40:27 -04:00
|
|
|
|
public List<Report> Reports { get; set; }
|
2018-03-09 03:01:12 -05:00
|
|
|
|
public List<ChatInfo> ChatHistory { get; protected set; }
|
2018-03-18 22:25:11 -04:00
|
|
|
|
public Queue<PlayerHistory> PlayerHistory { get; private set; }
|
2017-08-08 22:44:52 -04:00
|
|
|
|
public Game GameName { get; protected set; }
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2017-06-19 13:58:01 -04:00
|
|
|
|
// Info
|
|
|
|
|
public string Hostname { get; protected set; }
|
|
|
|
|
public string Website { get; protected set; }
|
2018-04-23 01:43:48 -04:00
|
|
|
|
public string Gametype { get; set; }
|
|
|
|
|
public Map CurrentMap { get; set; }
|
2017-10-15 21:40:27 -04:00
|
|
|
|
public int ClientNum
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return Players.Where(p => p != null).Count();
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-26 18:49:27 -04:00
|
|
|
|
public int MaxClients { get; protected set; }
|
|
|
|
|
public List<Player> Players { get; protected set; }
|
|
|
|
|
public string Password { get; private set; }
|
2017-08-09 00:35:23 -04:00
|
|
|
|
public bool Throttled { get; protected set; }
|
2017-11-02 12:49:45 -04:00
|
|
|
|
public bool CustomCallback { get; protected set; }
|
2018-03-06 02:22:19 -05:00
|
|
|
|
public string WorkingDirectory { get; protected set; }
|
2018-04-02 01:25:06 -04:00
|
|
|
|
public RCon.Connection RemoteConnection { get; protected set; }
|
2018-04-11 18:24:21 -04:00
|
|
|
|
public IRConParser RconParser { get; protected set; }
|
2018-04-13 02:32:30 -04:00
|
|
|
|
public IEventParser EventParser { get; set; }
|
2018-09-06 14:25:58 -04:00
|
|
|
|
public string LogPath { get; protected set; }
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2017-06-19 13:58:01 -04:00
|
|
|
|
// Internal
|
|
|
|
|
protected string IP;
|
|
|
|
|
protected int Port;
|
|
|
|
|
protected string FSGame;
|
|
|
|
|
protected int NextMessage;
|
|
|
|
|
protected int ConnectionErrors;
|
|
|
|
|
protected List<string> BroadcastMessages;
|
|
|
|
|
protected TimeSpan LastMessage;
|
|
|
|
|
protected DateTime LastPoll;
|
2018-05-10 01:34:29 -04:00
|
|
|
|
protected ManualResetEventSlim OnRemoteCommandResponse;
|
2015-08-20 01:06:44 -04:00
|
|
|
|
|
2018-03-30 00:13:40 -04:00
|
|
|
|
// only here for performance
|
2018-08-03 22:11:58 -04:00
|
|
|
|
private readonly bool CustomSayEnabled;
|
|
|
|
|
private readonly string CustomSayName;
|
2018-03-30 00:13:40 -04:00
|
|
|
|
|
2017-05-26 18:49:27 -04:00
|
|
|
|
//Remote
|
2018-02-23 02:06:13 -05:00
|
|
|
|
public IList<CommandResponseInfo> CommandResult = new List<CommandResponseInfo>();
|
2015-08-20 01:06:44 -04:00
|
|
|
|
}
|
|
|
|
|
}
|