diff --git a/Makefile b/Makefile index 8442ab00db..9352d34d51 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ INSTALL_PROGRAM = $(INSTALL) -m755 INSTALL_DATA = $(INSTALL) -m644 # program targets -CORE = fileformats rcg rgl rsdl rnull game utility geoip +CORE = fileformats rcg rgl rsdl rnull game utility geoip irc TOOLS = editor tsbuild ralint VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`) @@ -97,6 +97,14 @@ game_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico PROGRAMS += game game: $(game_TARGET) +irc_SRCS := $(shell find OpenRA.Irc/ -iname '*.cs') +irc_TARGET = OpenRA.Irc.dll +irc_KIND = library +irc_DEPS = $(fileformats_TARGET) $(game_TARGET) +irc_LIBS = $(COMMON_LIBS) $(irc_DEPS) +PROGRAMS += irc +irc: $(irc_TARGET) + # Renderer dlls rsdl_SRCS := $(shell find OpenRA.Renderer.SdlCommon/ -iname '*.cs') rsdl_TARGET = OpenRA.Renderer.SdlCommon.dll @@ -141,8 +149,8 @@ STD_MOD_DEPS = $(STD_MOD_LIBS) $(ralint_TARGET) mod_ra_SRCS := $(shell find OpenRA.Mods.RA/ -iname '*.cs') mod_ra_TARGET = mods/ra/OpenRA.Mods.RA.dll mod_ra_KIND = library -mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET) $(geoip_TARGET) -mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET) $(geoip_TARGET) +mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) +mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) PROGRAMS += mod_ra mod_ra: $(mod_ra_TARGET) diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index 89ee438400..2133b20e91 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -162,6 +162,21 @@ namespace OpenRA.GameRules public string CycleTabsKey = "tab"; } + public class IrcSettings + { + public string Hostname = "irc.open-ra.org"; + public int Port = 6667; + public string Nickname = null; + public string Username = "openra"; + public string Realname = null; + public string DefaultNickname = "Newbie"; + public string Channel = "global"; + public string TimestampFormat = "HH:mm:ss"; + public int ReconnectDelay = 10000; + public int ConnectionTimeout = 300000; + public bool Debug = false; + public bool ConnectAutomatically = false; + } public class Settings { @@ -174,6 +189,7 @@ namespace OpenRA.GameRules public ServerSettings Server = new ServerSettings(); public DebugSettings Debug = new DebugSettings(); public KeySettings Keys = new KeySettings(); + public IrcSettings Irc = new IrcSettings(); public Dictionary Sections; @@ -189,6 +205,7 @@ namespace OpenRA.GameRules {"Server", Server}, {"Debug", Debug}, {"Keys", Keys}, + {"Irc", Irc} }; // Override fieldloader to ignore invalid entries diff --git a/OpenRA.Irc/Channel.cs b/OpenRA.Irc/Channel.cs new file mode 100644 index 0000000000..aec534758a --- /dev/null +++ b/OpenRA.Irc/Channel.cs @@ -0,0 +1,27 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using OpenRA.FileFormats.Primitives; + +namespace OpenRA.Irc +{ + public class Channel + { + public readonly string Name; + public readonly ObservableSortedDictionary Users = new ObservableSortedDictionary(StringComparer.OrdinalIgnoreCase); + public Topic Topic = new Topic(); + + public Channel(string name) + { + Name = name; + } + } +} diff --git a/OpenRA.Irc/IrcClient.cs b/OpenRA.Irc/IrcClient.cs new file mode 100644 index 0000000000..306678c305 --- /dev/null +++ b/OpenRA.Irc/IrcClient.cs @@ -0,0 +1,402 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using OpenRA.FileFormats.Primitives; + +namespace OpenRA.Irc +{ + public class IrcClient : IDisposable + { + public static readonly IrcClient Instance = new IrcClient(); + + public static string MainChannel { get { return '#' + Game.Settings.Irc.Channel; } } + + public static void AddHistory(string line) + { + Instance.History.Add("{0} {1}".F(DateTime.Now.ToString(Game.Settings.Irc.TimestampFormat), line)); + } + + public static void AddMessage(string nickname, string message) + { + AddHistory("{0}: {1}".F(nickname, message)); + } + + public static void AddNotice(string nickname, string message) + { + AddHistory("-{0}- {1}".F(nickname, message)); + } + + public static void AddSelfNotice(string nickname, string message) + { + AddHistory("-> -{0}- {1}".F(nickname, message)); + } + + public static void AddAction(string nickname, string message) + { + AddHistory("* {0} {1}".F(nickname, message)); + } + + static void InstanceInitialize() + { + var s = Game.Settings.Irc; + + Instance.OnPublicMessage += l => + { + var action = IrcUtils.FromAction(l.Message); + if (action != null) + AddAction(l.Prefix.Nickname, action); + else + AddMessage(l.Prefix.Nickname, l.Message); + }; + Instance.OnPrivateMessage += l => + { + var ctcp = IrcUtils.FromCtcp(l.Message); + if (ctcp == null) + return; + + var split = ctcp.Split(new[] { ' ' }, 2); + var command = split[0]; + if (command.EqualsIC("VERSION")) + { + var mod = Game.CurrentMods.Values.FirstOrDefault(); + if (mod == null) + return; + Instance.CtcpRespond(l.Prefix.Nickname, command, "{0}: {1}".F(mod.Title, mod.Version)); + } + }; + Instance.OnPrivateNotice += l => + { + if (l.Target == "*") // Drop pre-register notices + return; + AddNotice(l.Prefix.Nickname, l.Message); + }; + Instance.OnRegister += l => + { + Instance.Join(MainChannel); + Game.Settings.Irc.Nickname = Instance.LocalUser.Nickname; + Game.Settings.Save(); + }; + Instance.OnConnecting += () => AddHistory("Connecting"); + Instance.OnConnect += () => AddHistory("Connected"); + Instance.OnPart += l => AddHistory("{0} left{1}".F(l.Prefix.Nickname, l.Message != null ? ": " + l.Message : "")); + Instance.OnJoin += l => AddHistory("{0} joined".F(l.Prefix.Nickname)); + Instance.OnQuit += l => AddHistory("{0} quit{1}".F(l.Prefix.Nickname, l.Message != null ? ": " + l.Message : "")); + Instance.OnKick += l => AddHistory("{0} kicked {1}{2}".F(l.Prefix.Nickname, l.KickeeNickname, l.Message != null ? ": " + l.Message : "")); + Instance.OnNicknameSet += l => + { + AddHistory("{0} set their nickname to {1}".F(l.Prefix.Nickname, l.NewNickname)); + if (l.NewNickname == Instance.LocalUser.Nickname) + { + Instance.Nickname = l.NewNickname; + Game.Settings.Irc.Nickname = l.NewNickname; + Game.Settings.Save(); + } + }; + Instance.OnTopicSet += l => AddHistory("{0} set the topic to {1}".F(l.Prefix.Nickname, l.Message)); + Instance.OnNumeric += l => + { + if (l.Numeric == NumericCommand.RPL_TOPIC) + { + var topic = Instance.GetChannel(MainChannel).Topic; + AddHistory("Topic is {0}".F(topic.Message)); + } + else if (l.Numeric == NumericCommand.RPL_TOPICWHOTIME) + { + var topic = Instance.GetChannel(MainChannel).Topic; + AddHistory("Topic set by {0} at {1}".F(topic.Author.Nickname, topic.Time.ToLocalTime())); + } + else if (l.Numeric == NumericCommand.RPL_NOTOPIC) + AddHistory("No topic is set"); + else if (l.Numeric == NumericCommand.ERR_NICKNAMEINUSE) + AddHistory("Nickname {0} is already in use".F(l.AltTarget)); + else if (l.Numeric == NumericCommand.ERR_ERRONEUSNICKNAME) + AddHistory("Nickname {0} is erroneus".F(l.AltTarget)); + }; + Instance.OnDisconnect += () => + { + if (Instance.ConnectionFailure != null) + { + AddHistory("Disconnected: {0}".F(Instance.ConnectionFailure.Message)); + if (s.ReconnectDelay >= 0) + { + AddHistory("Reconnecting in {0} seconds".F(s.ReconnectDelay / 1000)); + Instance.ConnectionState = IrcConnectionState.Reconnecting; + Game.RunAfterDelay(s.ReconnectDelay, () => + { + if (Instance.IsReconnecting) + Instance.Connect(Instance.Hostname, Instance.Port, Instance.ConnectionTimeout, Instance.Nickname, Instance.Username, Instance.Realname); + }); + } + } + else + AddHistory("Disconnected"); + }; + Instance.OnLineRead += l => + { + if (s.Debug) + AddHistory(l.RawString); + }; + Game.OnQuit += Instance.Disconnect; + } + + static IrcClient() + { + Log.AddChannel("irc", "irc.log"); + InstanceInitialize(); + } + + public readonly ObservableCollection History = new ObservableCollection(); + + IrcConnection connection; + Thread thread; + public IrcConnectionState ConnectionState { get; private set; } + public IrcClientUser LocalUser { get; private set; } + public Exception ConnectionFailure { get; private set; } + + public string Hostname { get; private set; } + public int Port { get; private set; } + public int ConnectionTimeout { get; private set; } + public string Nickname { get; private set; } + public string Username { get; private set; } + public string Realname { get; private set; } + + public bool IsConnected + { + get { return ConnectionState == IrcConnectionState.Connected; } + } + + public bool IsReconnecting + { + get { return ConnectionState == IrcConnectionState.Reconnecting; } + } + + public IrcClient() + { + ConnectionState = IrcConnectionState.Disconnected; + } + + public void Connect(string hostname, int port, int connectionTimeout, string nickname, string username, string realname) + { + ConnectionFailure = null; + if (IsConnected) + Disconnect(); + + Hostname = hostname; + Port = port; + ConnectionTimeout = connectionTimeout; + Nickname = nickname; + Username = username; + Realname = realname; + + thread = new Thread(() => + { + try + { + ConnectionState = IrcConnectionState.Connecting; + LocalUser = new IrcClientUser(this); + connection = new IrcConnection(); + OnConnecting(); + connection.Connect(hostname, port, connectionTimeout); + ConnectionState = IrcConnectionState.Connected; + OnConnect(); + SetNickname(nickname); + SetUser(username, realname); + ProcessLines(); + } + catch (Exception e) + { + Log.Write("irc", e.ToString()); + if (e is SocketException || e is IOException) + ConnectionFailure = e; + } + finally + { + Disconnect(); + } + }) { IsBackground = true }; + thread.Start(); + } + + public void WriteLine(string format, params object[] args) + { + try + { + connection.WriteLine(format, args); + } + catch (Exception e) + { + Log.Write("irc", e.ToString()); + if (e is SocketException || e is IOException) + ConnectionFailure = e; + Disconnect(); + } + } + + public void Disconnect() + { + if (!IsConnected || IsReconnecting) + { + ConnectionState = IrcConnectionState.Disconnected; + return; + } + + ConnectionState = IrcConnectionState.Disconnecting; + OnDisconnecting(); + connection.Close(); + ConnectionState = IrcConnectionState.Disconnected; + OnDisconnect(); + LocalUser = null; + connection = null; + } + + void IDisposable.Dispose() + { + Disconnect(); + } + + void ProcessLines() + { + string line; + while (IsConnected && (line = connection.ReadLine()) != null) + ProcessLine(line); + } + + void ProcessLine(string line) + { + if (string.IsNullOrEmpty(line)) + return; + + var l = new Line(this, line); + OnLineRead(l); + + int numeric; + if (int.TryParse(l.Command, out numeric)) + { + var nl = new NumericLine(l, numeric); + LocalUser.OnNumeric(nl); + OnNumeric(nl); + switch (nl.Numeric) + { + case NumericCommand.RPL_WELCOME: + OnRegister(nl); + break; + case NumericCommand.RPL_ENDOFNAMES: + OnSync(nl); + break; + } + } + else + { + switch (l.Command) + { + case "PING": + Pong(l.Message); + OnPing(l); + break; + case "PRIVMSG": + if (IrcUtils.IsChannel(l.Target)) + OnPublicMessage(l); + else + OnPrivateMessage(l); + break; + case "NOTICE": + if (IrcUtils.IsChannel(l.Target)) + OnPublicNotice(l); + else + OnPrivateNotice(l); + break; + case "JOIN": + var jl = new JoinLine(l); + LocalUser.OnJoin(jl); + OnJoin(jl); + break; + case "PART": + LocalUser.OnPart(l); + OnPart(l); + break; + case "NICK": + var nsl = new NicknameSetLine(l); + LocalUser.OnNicknameSet(nsl); + OnNicknameSet(nsl); + break; + case "QUIT": + OnQuit(l); + LocalUser.OnQuit(l); + break; + case "KICK": + var kl = new KickLine(l); + LocalUser.OnKick(kl); + OnKick(kl); + break; + case "TOPIC": + LocalUser.OnTopicSet(l); + OnTopicSet(l); + break; + } + } + } + + public event Action OnRegister = l => { }; + public event Action OnSync = l => { }; + public event Action OnLineRead = _ => { }; + public event Action OnConnect = () => { }; + public event Action OnConnecting = () => { }; + public event Action OnDisconnect = () => { }; + public event Action OnDisconnecting = () => { }; + public event Action OnPublicMessage = _ => { }; + public event Action OnPublicNotice = _ => { }; + public event Action OnPrivateMessage = _ => { }; + public event Action OnPrivateNotice = _ => { }; + public event Action OnJoin = _ => { }; + public event Action OnPart = _ => { }; + public event Action OnNicknameSet = _ => { }; + public event Action OnQuit = _ => { }; + public event Action OnPing = _ => { }; + public event Action OnNumeric = _ => { }; + public event Action OnKick = _ => { }; + public event Action OnTopicSet = _ => { }; + + public void SetNickname(string nickname) { WriteLine("NICK {0}", nickname); } + public void SetUser(string username, string realname) { WriteLine("USER {0} 0 * :{1}", username, realname); } + public void Join(string channel) { WriteLine("JOIN {0}", channel); } + public void Part(string channel) { WriteLine("PART {0}", channel); } + public void Message(string target, string message) { WriteLine("PRIVMSG {0} :{1}", target, message); } + public void Notice(string target, string message) { WriteLine("NOTICE {0} :{1}", target, message); } + public void Pong(string reply) { WriteLine("PONG :{0}", reply); } + public void CtcpRequest(string target, string command, string request) { Message(target, IrcUtils.ToCtcp("{0} {1}".F(command, request))); } + public void CtcpRespond(string target, string command, string response) { Notice(target, IrcUtils.ToCtcp("{0} {1}".F(command, response))); } + public void Act(string target, string message) { Message(target, IrcUtils.ToAction(message)); } + public void GetTopic(string channel) { WriteLine("TOPIC {0}", channel); } + public void Quit(string message) { WriteLine("QUIT :{0}", message); } + + public Channel GetChannel(string channel) + { + if (!IsConnected) + return null; + Channel c; + LocalUser.Channels.TryGetValue(channel, out c); + return c; + } + } + + public enum IrcConnectionState + { + Disconnected, + Connected, + Disconnecting, + Connecting, + Reconnecting + } +} diff --git a/OpenRA.Irc/IrcClientUser.cs b/OpenRA.Irc/IrcClientUser.cs new file mode 100644 index 0000000000..b6f054164b --- /dev/null +++ b/OpenRA.Irc/IrcClientUser.cs @@ -0,0 +1,112 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; +using OpenRA.FileFormats.Primitives; + +namespace OpenRA.Irc +{ + public class IrcClientUser : User + { + public readonly ObservableDictionary Channels = new ObservableDictionary(StringComparer.OrdinalIgnoreCase); + public readonly IrcClient Client; + + public IrcClientUser(IrcClient client) + { + Client = client; + } + + public void OnNumeric(NumericLine line) + { + switch (line.Numeric) + { + case NumericCommand.RPL_WELCOME: + new User(line.Message.Substring(line.Message.LastIndexOf(' ') + 1)).CopyTo(this); + break; + case NumericCommand.RPL_NAMREPLY: + { + var channel = line.GetChannel(); + var nicknames = line.Message.Replace("~", "").Replace("&", "").Replace("@", "").Replace("%", "").Replace("+", "").Split(' '); + + foreach (var nickname in nicknames.Where(n => !channel.Users.ContainsKey(n))) + channel.Users.Add(nickname, new User { Nickname = nickname }); + } + break; + case NumericCommand.RPL_TOPIC: + line.GetChannel().Topic.Message = line.Message; + break; + case NumericCommand.RPL_TOPICWHOTIME: + { + var topic = line.GetChannel().Topic; + topic.Author = new User(line[4]); + topic.Time = IrcUtils.DateTimeFromUnixTime(int.Parse(line[5])); + } + break; + case NumericCommand.ERR_NICKNAMEINUSE: + if (line.Target == "*") // no nickname set yet + Client.SetNickname(Client.Nickname + new Random().Next(10000, 99999)); + break; + } + } + + public void OnJoin(Line line) + { + if (line.PrefixIsSelf()) + Channels.Add(line.Target, new Channel(line.Target)); + + line.GetChannel().Users.Add(line.Prefix.Nickname, new User(line.Prefix)); + } + + public void OnPart(Line line) + { + line.GetChannel().Users.Remove(line.Prefix.Nickname); + + if (line.PrefixIsSelf()) + Channels.Remove(line.Target); + } + + public void OnNicknameSet(NicknameSetLine line) + { + if (line.PrefixIsSelf()) + Nickname = line.NewNickname; + + foreach (var channel in Channels.Values.Where(c => c.Users.ContainsKey(line.Prefix.Nickname))) + { + var user = channel.Users[line.Prefix.Nickname]; + channel.Users.Remove(line.Prefix.Nickname); + user.Nickname = line.NewNickname; + channel.Users.Add(line.NewNickname, user); + } + } + + public void OnQuit(Line line) + { + foreach (var channel in Channels) + channel.Value.Users.Remove(line.Prefix.Nickname); + } + + public void OnKick(KickLine line) + { + line.GetChannel().Users.Remove(line.KickeeNickname); + + if (line.KickeeNickname.EqualsIC(Nickname)) + Channels.Remove(line.Target); + } + + public void OnTopicSet(Line line) + { + var topic = line.GetChannel().Topic; + topic.Message = line.Message; + topic.Author = line.Prefix; + topic.Time = DateTime.UtcNow; + } + } +} diff --git a/OpenRA.Irc/IrcConnection.cs b/OpenRA.Irc/IrcConnection.cs new file mode 100644 index 0000000000..ae9679f83b --- /dev/null +++ b/OpenRA.Irc/IrcConnection.cs @@ -0,0 +1,83 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.IO; +using System.Net.Sockets; + +namespace OpenRA.Irc +{ + public class IrcConnection : IDisposable + { + TcpClient socket; + Stream stream; + StreamWriter writer; + StreamReader reader; + bool disposed; + + public void Connect(string hostname, int port, int connectionTimeout) + { + CheckDisposed(); + if (socket != null && socket.Connected) + throw new InvalidOperationException("Socket already connected"); + + socket = new TcpClient(hostname, port); + socket.ReceiveTimeout = socket.SendTimeout = connectionTimeout; + stream = socket.GetStream(); + writer = new StreamWriter(stream) { AutoFlush = true }; + reader = new StreamReader(stream); + } + + public void WriteLine(string format, params object[] args) + { + CheckDisposed(); + writer.WriteLine(format, args); + } + + public string ReadLine() + { + CheckDisposed(); + return reader.ReadLine(); + } + + public void Close() + { + CloseImpl(); + GC.SuppressFinalize(this); + } + + void CloseImpl() + { + if (disposed) return; + + disposed = true; + if (socket != null) socket.Close(); + if (stream != null) stream.Close(); + if (writer != null) writer.Close(); + if (reader != null) reader.Close(); + } + + void IDisposable.Dispose() + { + Close(); + } + + ~IrcConnection() + { + CloseImpl(); + } + + void CheckDisposed() + { + if (disposed) + throw new ObjectDisposedException(GetType().FullName); + } + } +} diff --git a/OpenRA.Irc/IrcUtils.cs b/OpenRA.Irc/IrcUtils.cs new file mode 100644 index 0000000000..d896496ac3 --- /dev/null +++ b/OpenRA.Irc/IrcUtils.cs @@ -0,0 +1,67 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; + +namespace OpenRA.Irc +{ + public static class IrcUtils + { + public static bool IsChannel(string s) + { + return !string.IsNullOrEmpty(s) && s[0] == '#'; + } + + public static bool IsNickname(string s) + { + return !string.IsNullOrEmpty(s) && (char.IsLetter(s[0]) || NicknameSpecialChars.Contains(s[0])) + && s.Substring(1).All(c => char.IsLetterOrDigit(c) || NicknameSpecialChars.Contains(c) || c == '-'); + } + + const string NicknameSpecialChars = @"[]\`_^{|}"; + + public static DateTime DateTimeFromUnixTime(int seconds) + { + return new DateTime(1970, 1, 1).AddSeconds(seconds); + } + + public static bool EqualsIC(this string a, string b) + { + return a.Equals(b, StringComparison.OrdinalIgnoreCase); + } + + public static string FromCtcp(string message) + { + if (message.Length < 2 || !message.StartsWith("\x0001") || !message.EndsWith("\x0001")) + return null; + + return message.Substring(1, message.Length - 2); + } + + public static string ToCtcp(string message) + { + return "\x0001{0}\x0001".F(message); + } + + public static string FromAction(string message) + { + if (!message.StartsWith("\x0001ACTION ") || !message.EndsWith("\x0001")) + return null; + + return message.Substring(8, message.Length - 8 - 1); + } + + public static string ToAction(string message) + { + return "\x0001ACTION {0}\x0001".F(message); + } + } +} diff --git a/OpenRA.Irc/Line.cs b/OpenRA.Irc/Line.cs new file mode 100644 index 0000000000..c21faaf3b6 --- /dev/null +++ b/OpenRA.Irc/Line.cs @@ -0,0 +1,155 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System.Linq; + +namespace OpenRA.Irc +{ + public class Line + { + public readonly IrcClient Client; + public readonly string RawString; + public readonly string[] RawStringParts; + public readonly User Prefix; + public readonly string Command; + public string Target { get; protected set; } + public string Message { get; protected set; } + + public Line(Line line) + { + Client = line.Client; + RawString = line.RawString; + RawStringParts = line.RawStringParts; + Prefix = line.Prefix; + Command = line.Command; + Target = line.Target; + Message = line.Message; + } + + public Line(IrcClient client, string line) + { + RawString = line; + RawStringParts = line.Split(' '); + Client = client; + + if (line[0] == ':') + { + line = line.Substring(1); + var prefixDelim = line.Split(new[] { ' ' }, 2); + Prefix = new User(prefixDelim[0]); + + if (prefixDelim.Length > 1) + { + var messageDelim = prefixDelim[1].Split(new[] { ':' }, 2); + + var args = messageDelim[0].Trim().Split(' '); + + Command = args[0]; + if (args.Length > 1) + Target = args[1]; + + if (messageDelim.Length > 1) + Message = messageDelim[1]; + } + } + else + { + var messageDelim = line.Split(new[] { ':' }, 2); + + var args = messageDelim[0].Trim().Split(' '); + + Command = args[0]; + if (args.Length > 1) + Target = args[1]; + + if (messageDelim.Length > 1) + Message = messageDelim[1]; + } + } + + public virtual Channel GetChannel() + { + return Client.GetChannel(Target); + } + + public string this[int index] + { + get { return RawStringParts[index]; } + } + + public bool PrefixIsSelf() + { + return Client.LocalUser != null && Prefix.Nickname.EqualsIC(Client.LocalUser.Nickname); + } + + public bool TargetIsSelf() + { + return Target != null && Target.EqualsIC(Client.LocalUser.Nickname); + } + } + + public class NicknameSetLine : Line + { + public readonly string NewNickname; + + public NicknameSetLine(Line line) + : base(line) + { + NewNickname = Message; + } + } + + public class NumericLine : Line + { + public readonly NumericCommand Numeric; + public readonly string AltTarget; + + public override Channel GetChannel() + { + if (IrcUtils.IsChannel(AltTarget)) + return Client.GetChannel(AltTarget); + return Client.GetChannel(Target); + } + + public NumericLine(Line line, int numeric) + : base(line) + { + if (!IrcUtils.IsChannel(Target)) + { + var numericParts = line.RawStringParts.Skip(1).TakeWhile(p => !p.StartsWith(":")); + AltTarget = numericParts.LastOrDefault(IrcUtils.IsChannel); + if (AltTarget == null) + AltTarget = numericParts.LastOrDefault(); + } + Numeric = (NumericCommand)numeric; + } + } + + public class JoinLine : Line // for compatibility with certain IRCds + { + public JoinLine(Line line) + : base(line) + { + if (Message != null) // don't overwrite the target if it was already set properly by the IRCd + Target = Message; + } + } + + public class KickLine : Line + { + public readonly string KickeeNickname; + + public KickLine(Line line) + : base(line) + { + KickeeNickname = this[3]; + } + } +} diff --git a/OpenRA.Irc/NumericCommand.cs b/OpenRA.Irc/NumericCommand.cs new file mode 100644 index 0000000000..ac3b33dc56 --- /dev/null +++ b/OpenRA.Irc/NumericCommand.cs @@ -0,0 +1,25 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +namespace OpenRA.Irc +{ + public enum NumericCommand + { + Undefined = 0, + RPL_WELCOME = 001, + RPL_NOTOPIC = 331, + RPL_TOPIC = 332, + RPL_TOPICWHOTIME = 333, + RPL_NAMREPLY = 353, + RPL_ENDOFNAMES = 366, + ERR_ERRONEUSNICKNAME = 432, + ERR_NICKNAMEINUSE = 433 + } +} diff --git a/OpenRA.Irc/OpenRA.Irc.csproj b/OpenRA.Irc/OpenRA.Irc.csproj new file mode 100644 index 0000000000..afdc1fca9c --- /dev/null +++ b/OpenRA.Irc/OpenRA.Irc.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {85B48234-8B31-4BE6-AF9C-665CC6866841} + Library + Properties + OpenRA.Irc + OpenRA.Irc + v3.5 + 512 + + + + + true + full + false + .. + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + {bdaeab25-991e-46a7-af1e-4f0e03358daa} + OpenRA.FileFormats + + + {0dfb103f-2962-400f-8c6d-e2c28ccba633} + OpenRA.Game + + + + + + + + + \ No newline at end of file diff --git a/OpenRA.Irc/Topic.cs b/OpenRA.Irc/Topic.cs new file mode 100644 index 0000000000..b97cb8c4e8 --- /dev/null +++ b/OpenRA.Irc/Topic.cs @@ -0,0 +1,30 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; + +namespace OpenRA.Irc +{ + public class Topic + { + public string Message; + public User Author; + public DateTime Time; + + public Topic() { } + + public Topic(string message, User author, DateTime time) + { + Message = message; + Author = author; + Time = time; + } + } +} diff --git a/OpenRA.Irc/User.cs b/OpenRA.Irc/User.cs new file mode 100644 index 0000000000..d64011fafc --- /dev/null +++ b/OpenRA.Irc/User.cs @@ -0,0 +1,73 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; + +namespace OpenRA.Irc +{ + public class User + { + public string Nickname; + public string Username; + public string Hostname; + + public User() { } + + public User(User user) + { + Nickname = user.Nickname; + Username = user.Username; + Hostname = user.Hostname; + } + + public void CopyTo(User user) + { + user.Nickname = Nickname; + user.Username = Username; + user.Hostname = Hostname; + } + + public User(string prefix) + { + if (string.IsNullOrEmpty(prefix)) + throw new ArgumentException(); + + var ex = prefix.IndexOf('!'); + var at = prefix.IndexOf('@'); + + if (ex >= 0 && at >= 0 && at < ex) + throw new ArgumentException("Bogus input string: @ before !"); + + if (ex >= 0) + { + Nickname = prefix.Substring(0, ex); + if (at >= 0) + { + Username = prefix.Substring(ex + 1, at - ex - 1); + Hostname = prefix.Substring(at + 1); + } + else + Username = prefix.Substring(ex + 1); + } + else + Nickname = prefix; + } + + public override string ToString() + { + var ret = "" + Nickname; + if (Username != null) + ret += "!" + Username; + if (Hostname != null) + ret += "@" + Hostname; + return ret; + } + } +} diff --git a/OpenRA.sln b/OpenRA.sln index 1f7cfd7087..2f304547f8 100644 --- a/OpenRA.sln +++ b/OpenRA.sln @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Mods.TS", "OpenRA.Mo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeoIP", "GeoIP\GeoIP.csproj", "{021DDD6A-A608-424C-9A9A-252D8A9989E0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Irc", "OpenRA.Irc\OpenRA.Irc.csproj", "{85B48234-8B31-4BE6-AF9C-665CC6866841}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,85 +47,6 @@ Global Release|Mixed Platforms = Release|Mixed Platforms EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.Build.0 = Release|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.Build.0 = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.Build.0 = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.ActiveCfg = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.Build.0 = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Any CPU.ActiveCfg = Release|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.Build.0 = Release|x86 - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.Build.0 = Release|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.Build.0 = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.Build.0 = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.Build.0 = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.Build.0 = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.Build.0 = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.Build.0 = Release|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -132,30 +55,53 @@ Global {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Release|Any CPU.Build.0 = Release|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.Build.0 = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.Build.0 = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.Build.0 = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.ActiveCfg = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Any CPU.Build.0 = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Any CPU.ActiveCfg = Release|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {0DFB103F-2962-400F-8C6D-E2C28CCBA633}.Release|Mixed Platforms.Build.0 = Release|x86 + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Any CPU.Build.0 = Release|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2881135D-4D62-493E-8F83-5EEE92CCC6BE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Any CPU.Build.0 = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {67CF1A10-C5F6-48FA-B1A7-FE83BE4CE2CC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Any CPU.Build.0 = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {00038B75-405B-44F5-8691-BD2546DBE224}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Any CPU.Build.0 = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1A8E50CC-EE32-4E57-8842-0C39C8EA7541}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Any CPU.Build.0 = Debug|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -164,6 +110,70 @@ Global {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Any CPU.Build.0 = Release|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F9FA4D9F-2302-470A-8A07-6E37F488C124}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.Build.0 = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Any CPU.Build.0 = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0C4AEC1A-E7D5-4114-8CCD-3EEC82872981}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Any CPU.Build.0 = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E9C01A96-C3E2-4772-825B-A740AC513D31}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Any CPU.Build.0 = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {52FD9F0B-B209-4ED7-8A32-AC8033363263}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Any CPU.Build.0 = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C0B0465C-6BE2-409C-8770-3A9BF64C4344}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Any CPU.Build.0 = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5457CBF5-4CE4-421E-A8BF-9FD6C9732E1D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Any CPU.Build.0 = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {021DDD6A-A608-424C-9A9A-252D8A9989E0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Any CPU.Build.0 = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = OpenRA.Game\OpenRA.Game.csproj