Fixed /log *, /-log *, and formatted the whitespace

git-svn-id: c32cf1ecefca26b8770d7b0740bc0c5c174087e4@757 c754d5e9-d413-0410-bcdf-9574c9dba6b7

--- SLProxy/Analyst.cs
+++ SLProxy/Analyst.cs
@@ -26,711 +26,864 @@
  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
- */
+ */
+
+using SLProxy;
+using libsecondlife;
+using Nwc.XmlRpc;
+using libsecondlife.Packets;
+using System.Reflection;
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text.RegularExpressions;
+
+public class Analyst
+{
+    private static Proxy proxy;
+    private static Hashtable commandDelegates = new Hashtable();
+    private static Hashtable loggedPackets = new Hashtable();
+    // private static string logGrep = null;
+    private static Hashtable modifiedPackets = new Hashtable();
+    private static LLUUID agentID;
+    private static LLUUID sessionID;
+    private static bool logLogin = false;
+    private static Assembly libslAssembly;
+
+    public static void Main(string[] args)
+    {
+
+        libslAssembly = Assembly.Load("libsecondlife");
+        if (libslAssembly == null) throw new Exception("Assembly load exception");
+
+        ProxyConfig proxyConfig = new ProxyConfig("Analyst V2", "Austin Jennings / Andrew Ortman", args);
+        proxy = new Proxy(proxyConfig);
+
+        // build the table of /command delegates
+        InitializeCommandDelegates();
+
+        // add delegates for login
+        proxy.SetLoginRequestDelegate(new XmlRpcRequestDelegate(LoginRequest));
+        proxy.SetLoginResponseDelegate(new XmlRpcResponseDelegate(LoginResponse));
+
+        // add a delegate for outgoing chat
+        proxy.AddDelegate(PacketType.ChatFromViewer, Direction.Incoming, new PacketDelegate(ChatFromViewerIn));
+        proxy.AddDelegate(PacketType.ChatFromViewer, Direction.Outgoing, new PacketDelegate(ChatFromViewerOut));
+
+        //  handle command line arguments
+        foreach (string arg in args)
+            if (arg == "--log-all")
+                LogAll();
+            else if (arg == "--log-login")
+                logLogin = true;
+
+        // start the proxy
+        proxy.Start();
+    }
+
+    // LoginRequest: dump a login request to the console
+    private static void LoginRequest(XmlRpcRequest request)
+    {
+        if (logLogin)
+        {
+            Console.WriteLine("==> Login Request");
+            Console.WriteLine(request);
+        }
+    }
+
+    // Loginresponse: dump a login response to the console
+    private static void LoginResponse(XmlRpcResponse response)
+    {
+        Hashtable values = (Hashtable)response.Value;
+        if (values.Contains("agent_id"))
+            agentID = new LLUUID((string)values["agent_id"]);
+        if (values.Contains("session_id"))
+            sessionID = new LLUUID((string)values["session_id"]);
+
+        if (logLogin)
+        {
+            Console.WriteLine("<== Login Response");
+            Console.WriteLine(response);
+        }
+    }
+
+    // ChatFromViewerIn: incoming ChatFromViewer delegate; shouldn't be possible, but just in case...
+    private static Packet ChatFromViewerIn(Packet packet, IPEndPoint sim)
+    {
+        if (loggedPackets.Contains(PacketType.ChatFromViewer) || modifiedPackets.Contains(PacketType.ChatFromViewer))
+            // user has asked to log or modify this packet
+            return Analyze(packet, sim, Direction.Incoming);
+        else
+            // return the packet unmodified
+            return packet;
+    }
+
+    // ChatFromViewerOut: outgoing ChatFromViewer delegate; check for Analyst commands
+    private static Packet ChatFromViewerOut(Packet packet, IPEndPoint sim)
+    {
+        // deconstruct the packet
+        ChatFromViewerPacket cpacket = (ChatFromViewerPacket)packet;
+        string message = System.Text.Encoding.UTF8.GetString(cpacket.ChatData.Message).Replace("\0", "");
+
+        if (message.Length > 1 && message[0] == '/')
+        {
+            string[] words = message.Split(' ');
+            if (commandDelegates.Contains(words[0]))
+            {
+                // this is an Analyst command; act on it and drop the chat packet
+                ((CommandDelegate)commandDelegates[words[0]])(words);
+                return null;
+            }
+        }
+
+        if (loggedPackets.Contains(PacketType.ChatFromViewer) || modifiedPackets.Contains(PacketType.ChatFromViewer))
+            // user has asked to log or modify this packet
+            return Analyze(packet, sim, Direction.Outgoing);
+        else
+            // return the packet unmodified
+            return packet;
+    }
+
+    // CommandDelegate: specifies a callback delegate for a /command
+    private delegate void CommandDelegate(string[] words);
+
+    // InitializeCommandDelegates: configure Analyst's commands
+    private static void InitializeCommandDelegates()
+    {
+        commandDelegates["/log"] = new CommandDelegate(CmdLog);
+        commandDelegates["/-log"] = new CommandDelegate(CmdNoLog);
+        // commandDelegates["/grep"] = new CommandDelegate(CmdGrep);
+        commandDelegates["/set"] = new CommandDelegate(CmdSet);
+        commandDelegates["/-set"] = new CommandDelegate(CmdNoSet);
+        commandDelegates["/inject"] = new CommandDelegate(CmdInject);
+        commandDelegates["/in"] = new CommandDelegate(CmdInject);
+    }
+
+    private static PacketType packetTypeFromName(string name)
+    {
+        Type packetTypeType = typeof(PacketType);
+        System.Reflection.FieldInfo f = packetTypeType.GetField(name);
+        if (f == null) throw new ArgumentException("Bad packet type");
+        return (PacketType)Enum.ToObject(packetTypeType, (int)f.GetValue(packetTypeType));
+    }
+
+    // CmdLog: handle a /log command
+    private static void CmdLog(string[] words)
+    {
+        if (words.Length != 2)
+            SayToUser("Usage: /log <packet name>");
+        else if (words[1] == "*")
+        {
+            LogAll();
+            SayToUser("logging all packets");
+        }
+        else
+        {
+            PacketType pType;
+            try
+            {
+                pType = packetTypeFromName(words[1]);
+            }
+            catch (ArgumentException e)
+            {
+                SayToUser("Bad packet name: " + words[1]);
+                return;
+            }
+            loggedPackets[pType] = null;
+            if (words[1] != "ChatFromViewer")
+            {
+                proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(AnalyzeIn));
+                proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(AnalyzeOut));
+            }
+            SayToUser("logging " + words[1]);
+        }
+    }
+
+    // CmdNoLog: handle a /-log command
+    private static void CmdNoLog(string[] words)
+    {
+        if (words.Length != 2)
+            SayToUser("Usage: /-log <packet name>");
+        else if (words[1] == "*")
+        {
+            NoLogAll();
+            SayToUser("stopped logging all packets");
+        }
+        else
+        {
+            PacketType pType = packetTypeFromName(words[1]);
+            loggedPackets.Remove(pType);
+
+            if (!modifiedPackets.Contains(words[1]))
+            {
+                if (words[1] != "ChatFromViewer")
+                {
+                    proxy.RemoveDelegate(pType, Direction.Incoming);
+                    proxy.RemoveDelegate(pType, Direction.Outgoing);
+                }
+            }
+
+            SayToUser("stopped logging " + words[1]);
+        }
+    }
+
+    /*	// CmdGrep: handle a /grep command
+        private static void CmdGrep(string[] words) {
+            if (words.Length == 1) {
+                logGrep = null;
+                SayToUser("stopped filtering logs");
+            } else {
+                string[] regexArray = new string[words.Length - 1];
+                Array.Copy(words, 1, regexArray, 0, words.Length - 1);
+                logGrep = String.Join(" ", regexArray);
+                SayToUser("filtering log with " + logGrep);
+            }
+        } */
+
+    // CmdSet: handle a /set command
+    private static void CmdSet(string[] words)
+    {
+        if (words.Length < 5)
+            SayToUser("Usage: /set <packet name> <block> <field> <value>");
+        else
+        {
+            PacketType pType;
+            try
+            {
+                pType = packetTypeFromName(words[1]);
+            }
+            catch (ArgumentException e)
+            {
+                SayToUser("Bad packet name: " + words[1]);
+                return;
+            }
+
+            string[] valueArray = new string[words.Length - 4];
+            Array.Copy(words, 4, valueArray, 0, words.Length - 4);
+            string valueString = String.Join(" ", valueArray);
+            object value;
+            try
+            {
+                value = MagicCast(words[1], words[2], words[3], valueString);
+            }
+            catch (Exception e)
+            {
+                SayToUser(e.Message);
+                return;
+            }
+
+            Hashtable fields;
+            if (modifiedPackets.Contains(pType))
+                fields = (Hashtable)modifiedPackets[pType];
+            else
+                fields = new Hashtable();
+
+            fields[new BlockField(words[2], words[3])] = value;
+            modifiedPackets[pType] = fields;
+
+            if (words[1] != "ChatFromViewer")
+            {
+                proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(AnalyzeIn));
+                proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(AnalyzeOut));
+            }
+
+            SayToUser("setting " + words[1] + "." + words[2] + "." + words[3] + " = " + valueString);
+        }
+    }
+
+    // CmdNoSet: handle a /-set command
+    private static void CmdNoSet(string[] words)
+    {
+        if (words.Length == 2 && words[1] == "*")
+        {
+            foreach (PacketType pType in modifiedPackets.Keys)
+                if (!loggedPackets.Contains(pType) && pType != PacketType.ChatFromViewer)
+                {
+                    proxy.RemoveDelegate(pType, Direction.Incoming);
+                    proxy.RemoveDelegate(pType, Direction.Outgoing);
+                }
+            modifiedPackets = new Hashtable();
+
+            SayToUser("stopped setting all fields");
+        }
+        else if (words.Length == 4)
+        {
+            PacketType pType;
+            try
+            {
+                pType = packetTypeFromName(words[1]);
+            }
+            catch (ArgumentException e)
+            {
+                SayToUser("Bad packet name: " + words[1]);
+                return;
+            }
+
+
+            if (modifiedPackets.Contains(pType))
+            {
+                Hashtable fields = (Hashtable)modifiedPackets[pType];
+                fields.Remove(new BlockField(words[2], words[3]));
+
+                if (fields.Count == 0)
+                {
+                    modifiedPackets.Remove(pType);
+
+                    if (!loggedPackets.Contains(pType))
+                    {
+                        if (words[1] != "ChatFromViewer")
+                        {
+                            proxy.RemoveDelegate(pType, Direction.Incoming);
+                            proxy.RemoveDelegate(pType, Direction.Outgoing);
+                        }
+                    }
+                }
+            }
+
+            SayToUser("stopped setting " + words[1] + "." + words[2] + "." + words[3]);
+        }
+        else
+            SayToUser("Usage: /-set <packet name> <block> <field>");
+    }
+
+
+    // CmdInject: handle an /inject command
+    private static void CmdInject(string[] words)
+    {
+        if (words.Length < 2)
+            SayToUser("Usage: /inject <packet file> [value]");
+        else
+        {
+            string[] valueArray = new string[words.Length - 2];
+            Array.Copy(words, 2, valueArray, 0, words.Length - 2);
+            string value = String.Join(" ", valueArray);
+
+            FileStream fs = null;
+            StreamReader sr = null;
+            Direction direction = Direction.Incoming;
+            string name = null;
+            //Hashtable blocks = new Hashtable();
+            string block = null;
+            object blockObj = null;
+            //Hashtable fields = new Hashtable();
+            Type packetClass = null;
+            Packet packet = null;
+
+            try
+            {
+                fs = File.OpenRead(words[1] + ".packet");
+                sr = new StreamReader(fs);
+
+                string line;
+                while ((line = sr.ReadLine()) != null)
+                {
+                    Match match;
+
+                    if (name == null)
+                    {
+                        match = (new Regex(@"^\s*(in|out)\s+(\w+)\s*$")).Match(line);
+                        if (!match.Success)
+                        {
+                            SayToUser("expecting direction and packet name, got: " + line);
+                            return;
+                        }
+
+                        string lineDir = match.Groups[1].Captures[0].ToString();
+                        string lineName = match.Groups[2].Captures[0].ToString();
+
+                        if (lineDir == "in")
+                            direction = Direction.Incoming;
+                        else if (lineDir == "out")
+                            direction = Direction.Outgoing;
+                        else
+                        {
+                            SayToUser("expecting 'in' or 'out', got: " + line);
+                            return;
+                        }
+
+                        name = lineName;
+                        packetClass = libslAssembly.GetType("libsecondlife.Packets." + name + "Packet");
+                        if (packetClass == null) throw new Exception("Couldn't get class " + name + "Packet");
+                        ConstructorInfo ctr = packetClass.GetConstructor(new Type[] { });
+                        if (ctr == null) throw new Exception("Couldn't get suitable constructor for " + name + "Packet");
+                        packet = (Packet)ctr.Invoke(new object[] { });
+                        Console.WriteLine("Created new " + name + "Packet");
+                    }
+                    else
+                    {
+                        match = (new Regex(@"^\s*\[(\w+)\]\s*$")).Match(line);
+                        if (match.Success)
+                        {
+                            //FIXME: support variable blocks
+
+                            block = match.Groups[1].Captures[0].ToString();
+                            FieldInfo blockField = packetClass.GetField(block);
+                            if (blockField == null) throw new Exception("Couldn't get " + name + "Packet." + block);
+                            blockObj = blockField.GetValue(packet);
+                            if (blockObj == null) throw new Exception("Got " + name + "Packet." + block + " == null");
+                            Console.WriteLine("Got block " + name + "Packet." + block);
+
+                            continue;
+                        }
+
+                        if (block == null)
+                        {
+                            SayToUser("expecting block name, got: " + line);
+                            return;
+                        }
+
+                        match = (new Regex(@"^\s*(\w+)\s*=\s*(.*)$")).Match(line);
+                        if (match.Success)
+                        {
+                            string lineField = match.Groups[1].Captures[0].ToString();
+                            string lineValue = match.Groups[2].Captures[0].ToString();
+                            object fval;
+
+                            //FIXME: use of MagicCast inefficient
+                            if (lineValue == "$Value")
+                                fval = MagicCast(name, block, lineField, value);
+                            else if (lineValue == "$UUID")
+                                fval = LLUUID.Random();
+                            else if (lineValue == "$AgentID")
+                                fval = agentID;
+                            else if (lineValue == "$SessionID")
+                                fval = sessionID;
+                            else
+                                fval = MagicCast(name, block, lineField, lineValue);
+
+                            MagicSetField(blockObj, lineField, fval);
+                            continue;
+                        }
+
+                        SayToUser("expecting block name or field, got: " + line);
+                        return;
+                    }
+                }
+
+                if (name == null)
+                {
+                    SayToUser("expecting direction and packet name, got EOF");
+                    return;
+                }
+
+                packet.Header.Flags |= Helpers.MSG_RELIABLE;
+                //if (protocolManager.Command(name).Encoded)
+                //	packet.Header.Flags |= Helpers.MSG_ZEROCODED;
+                proxy.InjectPacket(packet, direction);
+
+                SayToUser("injected " + words[1]);
+            }
+            catch (Exception e)
+            {
+                SayToUser("failed to inject " + words[1] + ": " + e.Message);
+                Console.WriteLine("failed to inject " + words[1] + ": " + e.Message + "\n" + e.StackTrace);
+            }
+            finally
+            {
+                if (fs != null)
+                    fs.Close();
+                if (sr != null)
+                    sr.Close();
+            }
+        }
+    }
+
+    // SayToUser: send a message to the user as in-world chat
+    private static void SayToUser(string message)
+    {
+        ChatFromSimulatorPacket packet = new ChatFromSimulatorPacket();
+        packet.ChatData.FromName = Helpers.StringToField("Analyst");
+        packet.ChatData.SourceID = LLUUID.Random();
+        packet.ChatData.OwnerID = agentID;
+        packet.ChatData.SourceType = (byte)2;
+        packet.ChatData.ChatType = (byte)1;
+        packet.ChatData.Audible = (byte)1;
+        packet.ChatData.Position = new LLVector3(0, 0, 0);
+        packet.ChatData.Message = Helpers.StringToField(message);
+        proxy.InjectPacket(packet, Direction.Incoming);
+    }
+
+    // BlockField: product type for a block name and field name
+    private struct BlockField
+    {
+        public string block;
+        public string field;
+
+
+        public BlockField(string block, string field)
+        {
+            this.block = block;
+            this.field = field;
+        }
+    }
+
+    private static void MagicSetField(object obj, string field, object val)
+    {
+        Type cls = obj.GetType();
+
+        FieldInfo fieldInf = cls.GetField(field);
+        if (fieldInf == null)
+        {
+            PropertyInfo prop = cls.GetProperty(field);
+            if (prop == null) throw new Exception("Couldn't find field " + cls.Name + "." + field);
+            prop.SetValue(obj, val, null);
+            //throw new Exception("FIXME: can't set properties");
+        }
+        else
+        {
+            fieldInf.SetValue(obj, val);
+        }
+
+    }
+
+    // MagicCast: given a packet/block/field name and a string, convert the string to a value of the appropriate type
+    private static object MagicCast(string name, string block, string field, string value)
+    {
+        Type packetClass = libslAssembly.GetType("libsecondlife.Packets." + name + "Packet");
+        if (packetClass == null) throw new Exception("Couldn't get class " + name + "Packet");
+        /*		try {
+                    packetMap = protocolManager.Command(name);
+                } catch {
+                    throw new Exception("unkown packet " + name);
+                } */
+
+
+        //FIXME: support variable blocks
+
+        FieldInfo blockField = packetClass.GetField(block);
+        if (blockField == null) throw new Exception("Couldn't get " + name + "Packet." + block);
+        Type blockClass = blockField.FieldType;
+        if (blockClass.IsArray) blockClass = blockClass.GetElementType();
+        Console.WriteLine("DEBUG: " + blockClass.Name);
+
+        FieldInfo fieldField = blockClass.GetField(field); PropertyInfo fieldProp = null;
+        Type fieldClass = null;
+        if (fieldField == null)
+        {
+            fieldProp = blockClass.GetProperty(field);
+            if (fieldProp == null) throw new Exception("Couldn't get " + name + "Packet." + block + "." + field);
+            fieldClass = fieldProp.PropertyType;
+        }
+        else
+        {
+            fieldClass = fieldField.FieldType;
+        }
+
+        try
+        {
+            if (fieldClass == typeof(byte))
+            {
+                return Convert.ToByte(value);
+            }
+            else if (fieldClass == typeof(ushort))
+            {
+                return Convert.ToUInt16(value);
+            }
+            else if (fieldClass == typeof(uint))
+            {
+                return Convert.ToUInt32(value);
+            }
+            else if (fieldClass == typeof(ulong))
+            {
+                return Convert.ToUInt64(value);
+            }
+            else if (fieldClass == typeof(sbyte))
+            {
+                return Convert.ToSByte(value);
+            }
+            else if (fieldClass == typeof(short))
+            {
+                return Convert.ToInt16(value);
+            }
+            else if (fieldClass == typeof(int))
+            {
+                return Convert.ToInt32(value);
+            }
+            else if (fieldClass == typeof(long))
+            {
+                return Convert.ToInt64(value);
+            }
+            else if (fieldClass == typeof(float))
+            {
+                return Convert.ToSingle(value);
+            }
+            else if (fieldClass == typeof(double))
+            {
+                return Convert.ToDouble(value);
+            }
+            else if (fieldClass == typeof(LLUUID))
+            {
+                return new LLUUID(value);
+            }
+            else if (fieldClass == typeof(bool))
+            {
+                if (value.ToLower() == "true")
+                    return true;
+                else if (value.ToLower() == "false")
+                    return false;
+                else
+                    throw new Exception();
+            }
+            else if (fieldClass == typeof(byte[]))
+            {
+                return Helpers.StringToField(value);
+            }
+            else if (fieldClass == typeof(LLVector3))
+            {
+                Match vector3Match = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
+                if (!vector3Match.Success)
+                    throw new Exception();
+                return new LLVector3
+                    (Convert.ToSingle(vector3Match.Groups[1].Captures[0].ToString())
+                    , Convert.ToSingle(vector3Match.Groups[2].Captures[0].ToString())
+                    , Convert.ToSingle(vector3Match.Groups[3].Captures[0].ToString())
+                    );
+            }
+            else if (fieldClass == typeof(LLVector3d))
+            {
+                Match vector3dMatch = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
+                if (!vector3dMatch.Success)
+                    throw new Exception();
+                return new LLVector3d
+                    (Convert.ToDouble(vector3dMatch.Groups[1].Captures[0].ToString())
+                    , Convert.ToDouble(vector3dMatch.Groups[2].Captures[0].ToString())
+                    , Convert.ToDouble(vector3dMatch.Groups[3].Captures[0].ToString())
+                    );
+            }
+            else if (fieldClass == typeof(LLVector4))
+            {
+                Match vector4Match = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
+                if (!vector4Match.Success)
+                    throw new Exception();
+                float vector4X = Convert.ToSingle(vector4Match.Groups[1].Captures[0].ToString());
+                float vector4Y = Convert.ToSingle(vector4Match.Groups[2].Captures[0].ToString());
+                float vector4Z = Convert.ToSingle(vector4Match.Groups[3].Captures[0].ToString());
+                float vector4S = Convert.ToSingle(vector4Match.Groups[4].Captures[0].ToString());
+                byte[] vector4Bytes = new byte[16];
+                Array.Copy(BitConverter.GetBytes(vector4X), 0, vector4Bytes, 0, 4);
+                Array.Copy(BitConverter.GetBytes(vector4Y), 0, vector4Bytes, 4, 4);
+                Array.Copy(BitConverter.GetBytes(vector4Z), 0, vector4Bytes, 8, 4);
+                Array.Copy(BitConverter.GetBytes(vector4S), 0, vector4Bytes, 12, 4);
+                return new LLVector4(vector4Bytes, 0);
+            }
+            else if (fieldClass == typeof(LLQuaternion))
+            {
+                Match quaternionMatch = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
+                if (!quaternionMatch.Success)
+                    throw new Exception();
+                return new LLQuaternion
+                    (Convert.ToSingle(quaternionMatch.Groups[1].Captures[0].ToString())
+                    , Convert.ToSingle(quaternionMatch.Groups[2].Captures[0].ToString())
+                    , Convert.ToSingle(quaternionMatch.Groups[3].Captures[0].ToString())
+                    );
+            }
+            else
+            {
+                throw new Exception("unsupported field type " + fieldClass);
+            }
+        }
+        catch
+        {
+            throw new Exception("unable to interpret " + value + " as " + fieldClass);
+        }
+        /*				try {
+                            switch (fieldMap.Type) {
+                                case FieldType.LLVector3:
+                                case FieldType.IPADDR:
+                                    return IPAddress.Parse(value);
+                                case FieldType.IPPORT:
+                                    return Convert.ToUInt16(value);
+                                case FieldType.Variable:
+                                    Match match = Regex.Match(value, @"^0x([0-9a-fA-F]{2})*", RegexOptions.IgnoreCase);
+                                    if (match.Success) {
+                                        byte[] buf = new byte[match.Groups[1].Captures.Count];
+                                        int i = 0;
+                                        foreach (Capture capture in match.Groups[1].Captures)
+                                            buf[i++] = Byte.Parse(capture.ToString(), NumberStyles.AllowHexSpecifier);
+                                        return buf;
+                                    } else
+                                        return value;
+                            }
+                        } catch {
+                            throw new Exception("unable to interpret " + value + " as " + fieldMap.Type);
+                        }

-using SLProxy;
-using libsecondlife;
-using Nwc.XmlRpc;
-using libsecondlife.Packets;
-using System.Reflection;
+                        throw new Exception("unsupported field type " + fieldMap.Type);
+                    }

-using System;
-using System.Collections;
-using System.Globalization;
-using System.IO;
-using System.Net;
-using System.Text.RegularExpressions;
+                    throw new Exception("unknown field " + name + "." + block + "." + field);
+                }

-public class Analyst {
-	private static Proxy proxy;
-	private static Hashtable commandDelegates = new Hashtable();
-	private static Hashtable loggedPackets = new Hashtable();
-	// private static string logGrep = null;
-	private static Hashtable modifiedPackets = new Hashtable();
-	private static LLUUID agentID;
-	private static LLUUID sessionID;
-	private static bool logLogin = false;
-	private static Assembly libslAssembly;
+                throw new Exception("unknown block " + name + "." + block); */
+    }
+
+    // AnalyzeIn: analyze an incoming packet
+    private static Packet AnalyzeIn(Packet packet, IPEndPoint endPoint)
+    {
+        return Analyze(packet, endPoint, Direction.Incoming);
+    }
+
+    // AnalyzeOut: analyze an outgoing packet
+    private static Packet AnalyzeOut(Packet packet, IPEndPoint endPoint)
+    {
+        return Analyze(packet, endPoint, Direction.Outgoing);
+    }
+
+    // Analyze: modify and/or log a pocket
+    private static Packet Analyze(Packet packet, IPEndPoint endPoint, Direction direction)
+    {
+        if (modifiedPackets.Contains(packet.Type))
+            try
+            {
+                Hashtable changes = (Hashtable)modifiedPackets[packet.Type];
+                Type packetClass = packet.GetType();
+
+                foreach (BlockField bf in changes.Keys)
+                {
+                    //FIXME: support variable blocks
+
+                    FieldInfo blockField = packetClass.GetField(bf.block);
+                    //Type blockClass = blockField.FieldType;
+                    object blockObject = blockField.GetValue(packet);
+                    MagicSetField(blockObject, bf.field, changes[blockField]);
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("failed to modify " + packet.Type + ": " + e.Message);
+                Console.WriteLine(e.StackTrace);
+            }
+
+        if (loggedPackets.Contains(packet.Type))
+            LogPacket(packet, endPoint, direction);
+
+        return packet;
+    }
+
+    // LogAll: register logging delegates for all packets
+    private static void LogAll()
+    {
+        Type packetTypeType = typeof(PacketType);
+        System.Reflection.MemberInfo[] packetTypes = packetTypeType.GetMembers();
+
+        for (int i = 0; i < packetTypes.Length; i++)
+        {
+            if (packetTypes[i].MemberType == System.Reflection.MemberTypes.Field && packetTypes[i].DeclaringType == packetTypeType)
+            {
+                string name = packetTypes[i].Name;
+                PacketType pType;
+
+                try
+                {
+                    pType = packetTypeFromName(name);
+                }
+                catch (Exception)
+                {
+                    continue;
+                }
+
+                loggedPackets[pType] = null;
+
+                if (pType != PacketType.ChatFromViewer)
+                {
+                    proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(AnalyzeIn));
+                    proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(AnalyzeOut));
+                }
+            }
+        }
+    }
+
+    // NoLogAll: unregister logging delegates for all packets
+    private static void NoLogAll()
+    {
+        Type packetTypeType = typeof(PacketType);
+        System.Reflection.MemberInfo[] packetTypes = packetTypeType.GetMembers();
+
+        for (int i = 0; i < packetTypes.Length; i++)
+        {
+            if (packetTypes[i].MemberType == System.Reflection.MemberTypes.Field && packetTypes[i].DeclaringType == packetTypeType)
+            {
+                string name = packetTypes[i].Name;
+                PacketType pType;
+
+                try
+                {
+                    pType = packetTypeFromName(name);
+                }
+                catch (Exception)
+                {
+                    continue;
+                }
+
+                loggedPackets.Remove(pType);
+
+                if (pType != PacketType.ChatFromViewer)
+                {
+                    proxy.RemoveDelegate(pType, Direction.Incoming);
+                    proxy.RemoveDelegate(pType, Direction.Outgoing);
+                }
+            }
+        }
+    }
+
+    // LogPacket: dump a packet to the console
+    private static void LogPacket(Packet packet, IPEndPoint endPoint, Direction direction)
+    {
+        /* if (logGrep != null) {
+            bool match = false;
+            foreach (Block block in packet.Blocks())
+                foreach (Field field in block.Fields) {
+                    string value;
+                    if (field.Layout.Type == FieldType.Variable)
+                        value = DataConvert.toChoppedString(field.Data);
+                    else
+                        value = field.Data.ToString();
+                    if (Regex.Match(packet.Layout.Name + "." + block.Layout.Name + "." + field.Layout.Name + " = " + value, logGrep, RegexOptions.IgnoreCase).Success) {
+                        match = true;
+                        break;
+                    }

-	public static void Main(string[] args) {
-
-		libslAssembly = Assembly.Load("libsecondlife");
-		if(libslAssembly == null) throw new Exception("Assembly load exception");
-
-		ProxyConfig proxyConfig = new ProxyConfig("Analyst V2", "Austin Jennings / Andrew Ortman", args);
-		proxy = new Proxy(proxyConfig);
-
-		// build the table of /command delegates
-		InitializeCommandDelegates();
-
-		// add delegates for login
-		proxy.SetLoginRequestDelegate(new XmlRpcRequestDelegate(LoginRequest));
-		proxy.SetLoginResponseDelegate(new XmlRpcResponseDelegate(LoginResponse));
-
-		// add a delegate for outgoing chat
-		proxy.AddDelegate(PacketType.ChatFromViewer, Direction.Incoming, new PacketDelegate(ChatFromViewerIn));
-		proxy.AddDelegate(PacketType.ChatFromViewer, Direction.Outgoing, new PacketDelegate(ChatFromViewerOut));
-
-		//  handle command line arguments
-		foreach (string arg in args)
-			if (arg == "--log-all")
-				LogAll();
-			else if (arg == "--log-login")
-				logLogin = true;
-
-		// start the proxy
-		proxy.Start();
-	}
-
-	// LoginRequest: dump a login request to the console
-	private static void LoginRequest(XmlRpcRequest request) {
-		if (logLogin) {
-			Console.WriteLine("==> Login Request");
-			Console.WriteLine(request);
-		}
-	}
-
-	// Loginresponse: dump a login response to the console
-	private static void LoginResponse(XmlRpcResponse response) {
-		Hashtable values = (Hashtable)response.Value;
-		if (values.Contains("agent_id"))
-			agentID = new LLUUID((string)values["agent_id"]);
-		if (values.Contains("session_id"))
-			sessionID = new LLUUID((string)values["session_id"]);
-
-		if (logLogin) {
-			Console.WriteLine("<== Login Response");
-			Console.WriteLine(response);
-		}
-	}
-
-	// ChatFromViewerIn: incoming ChatFromViewer delegate; shouldn't be possible, but just in case...
-	private static Packet ChatFromViewerIn(Packet packet, IPEndPoint sim) {
-		if (loggedPackets.Contains(PacketType.ChatFromViewer) || modifiedPackets.Contains(PacketType.ChatFromViewer))
-			// user has asked to log or modify this packet
-			return Analyze(packet, sim, Direction.Incoming);
-		else
-			// return the packet unmodified
-			return packet;
-	}
-
-	// ChatFromViewerOut: outgoing ChatFromViewer delegate; check for Analyst commands
-	private static Packet ChatFromViewerOut(Packet packet, IPEndPoint sim) {
-		// deconstruct the packet
-		ChatFromViewerPacket cpacket = (ChatFromViewerPacket) packet;
-		string message = System.Text.Encoding.UTF8.GetString(cpacket.ChatData.Message).Replace("\0", "");
-
-		if (message.Length > 1 && message[0] == '/') {
-			string[] words = message.Split(' ');
-			if (commandDelegates.Contains(words[0])) {
-				// this is an Analyst command; act on it and drop the chat packet
-				((CommandDelegate)commandDelegates[words[0]])(words);
-				return null;
-			}
-		}
-
-		if (loggedPackets.Contains(PacketType.ChatFromViewer) || modifiedPackets.Contains(PacketType.ChatFromViewer))
-			// user has asked to log or modify this packet
-			return Analyze(packet, sim, Direction.Outgoing);
-		else
-			// return the packet unmodified
-			return packet;
-	}
-
-	// CommandDelegate: specifies a callback delegate for a /command
-	private delegate void CommandDelegate(string[] words);
-
-	// InitializeCommandDelegates: configure Analyst's commands
-	private static void InitializeCommandDelegates() {
-		commandDelegates["/log"] = new CommandDelegate(CmdLog);
-		commandDelegates["/-log"] = new CommandDelegate(CmdNoLog);
-		// commandDelegates["/grep"] = new CommandDelegate(CmdGrep);
-		commandDelegates["/set"] = new CommandDelegate(CmdSet);
-		commandDelegates["/-set"] = new CommandDelegate(CmdNoSet);
-		commandDelegates["/inject"] = new CommandDelegate(CmdInject);
-		commandDelegates["/in"] = new CommandDelegate(CmdInject);
-	}
-
-	private static PacketType packetTypeFromName(string name) {
-		Type packetTypeType = typeof(PacketType);
-		System.Reflection.FieldInfo f = packetTypeType.GetField(name);
-		if(f == null) throw new ArgumentException("Bad packet type");
-		return (PacketType)Enum.ToObject(packetTypeType, (int)f.GetValue(packetTypeType));
-	}
-
-	// CmdLog: handle a /log command
-	private static void CmdLog(string[] words) {
-		if (words.Length != 2)
-			SayToUser("Usage: /log <packet name>");
-		else if (words[1] == "*") {
-			LogAll();
-			SayToUser("logging all packets");
-		} else {
-			PacketType pType;
-			try {
-				pType = packetTypeFromName(words[1]);
-			} catch(ArgumentException e) {
-				SayToUser("Bad packet name: "+words[1]);
-				return;
-			}
-			loggedPackets[pType] = null;
-			if (words[1] != "ChatFromViewer") {
-				proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(AnalyzeIn));
-				proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(AnalyzeOut));
-			}
-			SayToUser("logging " + words[1]);
-		}
-	}
-
-	// CmdNoLog: handle a /-log command
-	private static void CmdNoLog(string[] words) {
-		if (words.Length != 2)
-			SayToUser("Usage: /-log <packet name>");
-		else if (words[1] == "*") {
-			NoLogAll();
-			SayToUser("stopped logging all packets");
-		} else {
-			PacketType pType = packetTypeFromName(words[1]);
-			loggedPackets.Remove(pType);
-
-			if (!modifiedPackets.Contains(words[1])) {
-				if (words[1] != "ChatFromViewer") {
-					proxy.RemoveDelegate(pType, Direction.Incoming);
-					proxy.RemoveDelegate(pType, Direction.Outgoing);
-				}
-			}
-
-			SayToUser("stopped logging " + words[1]);
-		}
-	}
-
-/*	// CmdGrep: handle a /grep command
-	private static void CmdGrep(string[] words) {
-		if (words.Length == 1) {
-			logGrep = null;
-			SayToUser("stopped filtering logs");
-		} else {
-			string[] regexArray = new string[words.Length - 1];
-			Array.Copy(words, 1, regexArray, 0, words.Length - 1);
-			logGrep = String.Join(" ", regexArray);
-			SayToUser("filtering log with " + logGrep);
-		}
-	} */
-
-	// CmdSet: handle a /set command
-	private static void CmdSet(string[] words) {
-		if (words.Length < 5)
-			SayToUser("Usage: /set <packet name> <block> <field> <value>");
-		else {
-			PacketType pType;
-			try {
-				pType = packetTypeFromName(words[1]);
-			} catch(ArgumentException e) {
-				SayToUser("Bad packet name: "+words[1]);
-				return;
-			}
-
-			string[] valueArray = new string[words.Length - 4];
-			Array.Copy(words, 4, valueArray, 0, words.Length - 4);
-			string valueString = String.Join(" ", valueArray);
-			object value;
-			try {
-				value = MagicCast(words[1], words[2], words[3], valueString);
-			} catch (Exception e) {
-				SayToUser(e.Message);
-				return;
-			}
-
-			Hashtable fields;
-			if (modifiedPackets.Contains(pType))
-				fields = (Hashtable)modifiedPackets[pType];
-			else
-				fields = new Hashtable();
-
-			fields[new BlockField(words[2], words[3])] = value;
-			modifiedPackets[pType] = fields;
-
-			if (words[1] != "ChatFromViewer") {
-				proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(AnalyzeIn));
-				proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(AnalyzeOut));
-			}
-
-			SayToUser("setting " + words[1] + "." + words[2] + "." + words[3] + " = " + valueString);
-		}
-	}
-
-	// CmdNoSet: handle a /-set command
-	private static void CmdNoSet(string[] words) {
-		if (words.Length == 2 && words[1] == "*") {
-			foreach (PacketType pType in modifiedPackets.Keys)
-				if (!loggedPackets.Contains(pType) && pType != PacketType.ChatFromViewer) {
-					proxy.RemoveDelegate(pType, Direction.Incoming);
-					proxy.RemoveDelegate(pType, Direction.Outgoing);
-				}
-			modifiedPackets = new Hashtable();
-
-			SayToUser("stopped setting all fields");
-		} else if (words.Length == 4) {
-			PacketType pType;
-			try {
-				pType = packetTypeFromName(words[1]);
-			} catch(ArgumentException e) {
-				SayToUser("Bad packet name: "+words[1]);
-				return;
-			}
-
-
-			if (modifiedPackets.Contains(pType)) {
-				Hashtable fields = (Hashtable)modifiedPackets[pType];
-				fields.Remove(new BlockField(words[2], words[3]));
-
-				if (fields.Count == 0) {
-					modifiedPackets.Remove(pType);
-
-					if (!loggedPackets.Contains(pType)) {
-						if (words[1] != "ChatFromViewer") {
-							proxy.RemoveDelegate(pType, Direction.Incoming);
-							proxy.RemoveDelegate(pType, Direction.Outgoing);
-						}
-					}
-				}
-			}
-
-			SayToUser("stopped setting " + words[1] + "." + words[2] + "." + words[3]);
-		} else
-			SayToUser("Usage: /-set <packet name> <block> <field>");
-	}
-
-
-	// CmdInject: handle an /inject command
-	private static void CmdInject(string[] words) {
-		if (words.Length < 2)
-			SayToUser("Usage: /inject <packet file> [value]");
-		else {
-			string[] valueArray = new string[words.Length - 2];
-			Array.Copy(words, 2, valueArray, 0, words.Length - 2);
-			string value = String.Join(" ", valueArray);
-
-			FileStream fs = null;
-			StreamReader sr = null;
-			Direction direction = Direction.Incoming;
-			string name = null;
-			//Hashtable blocks = new Hashtable();
-			string block = null;
-			object blockObj = null;
-			//Hashtable fields = new Hashtable();
-			Type packetClass = null;
-			Packet packet = null;
-
-			try {
-				fs = File.OpenRead(words[1] + ".packet");
-				sr = new StreamReader(fs);
-
-				string line;
-				while ((line = sr.ReadLine()) != null) {
-					Match match;
-
-					if (name == null) {
-						match = (new Regex(@"^\s*(in|out)\s+(\w+)\s*$")).Match(line);
-						if (!match.Success) {
-							SayToUser("expecting direction and packet name, got: " + line);
-							return;
-						}
-
-						string lineDir = match.Groups[1].Captures[0].ToString();
-						string lineName = match.Groups[2].Captures[0].ToString();
-
-						if (lineDir == "in")
-							direction = Direction.Incoming;
-						else if (lineDir == "out")
-							direction = Direction.Outgoing;
-						else {
-							SayToUser("expecting 'in' or 'out', got: " + line);
-							return;
-						}
-
-						name = lineName;
-						packetClass = libslAssembly.GetType("libsecondlife.Packets."+name+"Packet");
-						if(packetClass == null) throw new Exception("Couldn't get class "+name+"Packet");
-						ConstructorInfo ctr = packetClass.GetConstructor(new Type[] { });
-						if(ctr == null) throw new Exception("Couldn't get suitable constructor for "+name+"Packet");
-						packet = (Packet) ctr.Invoke(new object[] { });
-						Console.WriteLine("Created new "+name+"Packet");
-					} else {
-						match = (new Regex(@"^\s*\[(\w+)\]\s*$")).Match(line);
-						if (match.Success) {
-							//FIXME: support variable blocks
-
-							block = match.Groups[1].Captures[0].ToString();
-							FieldInfo blockField = packetClass.GetField(block);
-							if(blockField == null) throw new Exception("Couldn't get "+name+"Packet."+block);
-							blockObj = blockField.GetValue(packet);
-							if(blockObj == null) throw new Exception("Got "+name+"Packet."+block+" == null");
-							Console.WriteLine("Got block "+name+"Packet."+block);
-
-							continue;
-						}
-
-						if (block == null) {
-							SayToUser("expecting block name, got: " + line);
-							return;
-						}
-
-						match = (new Regex(@"^\s*(\w+)\s*=\s*(.*)$")).Match(line);
-						if (match.Success) {
-							string lineField = match.Groups[1].Captures[0].ToString();
-							string lineValue = match.Groups[2].Captures[0].ToString();
-							object fval;
-
-							//FIXME: use of MagicCast inefficient
-							if (lineValue == "$Value")
-								fval = MagicCast(name, block, lineField, value);
-							else if (lineValue == "$UUID")
-								fval = LLUUID.Random();
-							else if (lineValue == "$AgentID")
-								fval = agentID;
-							else if (lineValue == "$SessionID")
-								fval = sessionID;
-							else
-								fval = MagicCast(name, block, lineField, lineValue);
-
-							MagicSetField(blockObj,lineField,fval);
-							continue;
-						}
-
-						SayToUser("expecting block name or field, got: " + line);
-						return;
-					}
-				}
-
-				if (name == null) {
-					SayToUser("expecting direction and packet name, got EOF");
-					return;
-				}
-
-				packet.Header.Flags |= Helpers.MSG_RELIABLE;
-				//if (protocolManager.Command(name).Encoded)
-				//	packet.Header.Flags |= Helpers.MSG_ZEROCODED;
-				proxy.InjectPacket(packet, direction);
-
-				SayToUser("injected " + words[1]);
-			} catch (Exception e) {
-				SayToUser("failed to inject " + words[1] + ": " + e.Message);
-				Console.WriteLine("failed to inject " + words[1] + ": " + e.Message + "\n" + e.StackTrace);
-			} finally {
-				if (fs != null)
-					fs.Close();
-				if (sr != null)
-					sr.Close();
-			}
-		}
-	}
-
-	// SayToUser: send a message to the user as in-world chat
-	private static void SayToUser(string message) {
-		ChatFromSimulatorPacket packet = new ChatFromSimulatorPacket();
-		packet.ChatData.FromName = Helpers.StringToField("Analyst");
-		packet.ChatData.SourceID = LLUUID.Random();
-		packet.ChatData.OwnerID = agentID;
-		packet.ChatData.SourceType = (byte)2;
-		packet.ChatData.ChatType = (byte)1;
-		packet.ChatData.Audible = (byte)1;
-		packet.ChatData.Position = new LLVector3(0, 0, 0);
-		packet.ChatData.Message = Helpers.StringToField(message);
-		proxy.InjectPacket(packet, Direction.Incoming);
-	}
-
-	// BlockField: product type for a block name and field name
-	private struct BlockField {
-		public string block;
-		public string field;
-
-
-		public BlockField(string block, string field) {
-			this.block = block;
-			this.field = field;
-		}
-	}
-
-	private static void MagicSetField(object obj, string field, object val) {
-		Type cls = obj.GetType();
-
-		FieldInfo fieldInf = cls.GetField(field);
-		if(fieldInf == null) {
-			PropertyInfo prop = cls.GetProperty(field);
-			if(prop == null) throw new Exception("Couldn't find field "+cls.Name+"."+field);
-			prop.SetValue(obj,val,null);
-			//throw new Exception("FIXME: can't set properties");
-		} else {
-			fieldInf.SetValue(obj,val);
-		}
-
-	}
-
-	// MagicCast: given a packet/block/field name and a string, convert the string to a value of the appropriate type
-	private static object MagicCast(string name, string block, string field, string value) {
-		Type packetClass = libslAssembly.GetType("libsecondlife.Packets."+name+"Packet");
-		if(packetClass == null) throw new Exception("Couldn't get class "+name+"Packet");
-/*		try {
-			packetMap = protocolManager.Command(name);
-		} catch {
-			throw new Exception("unkown packet " + name);
-		} */
-
-
-		//FIXME: support variable blocks
-
-		FieldInfo blockField = packetClass.GetField(block);
-		if(blockField == null) throw new Exception("Couldn't get "+name+"Packet."+block);
-		Type blockClass = blockField.FieldType;
-		if(blockClass.IsArray) blockClass = blockClass.GetElementType();
-		Console.WriteLine("DEBUG: "+blockClass.Name);
-
-		FieldInfo fieldField = blockClass.GetField(field); PropertyInfo fieldProp = null;
-		Type fieldClass = null;
-		if(fieldField == null) {
-			fieldProp = blockClass.GetProperty(field);
-			if(fieldProp == null) throw new Exception("Couldn't get "+name+"Packet."+block+"."+field);
-			fieldClass = fieldProp.PropertyType;
-		} else {
-			fieldClass = fieldField.FieldType;
-		}
-
-		try {
-			if(fieldClass == typeof(byte)) {
-				return Convert.ToByte(value);
-			} else if(fieldClass == typeof(ushort)) {
-				return Convert.ToUInt16(value);
-			} else if(fieldClass == typeof(uint)) {
-				return Convert.ToUInt32(value);
-			} else if(fieldClass == typeof(ulong)) {
-				return Convert.ToUInt64(value);
-			} else if(fieldClass == typeof(sbyte)) {
-				return Convert.ToSByte(value);
-			} else if(fieldClass == typeof(short)) {
-				return Convert.ToInt16(value);
-			} else if(fieldClass == typeof(int)) {
-				return Convert.ToInt32(value);
-			} else if(fieldClass == typeof(long)) {
-				return Convert.ToInt64(value);
-			} else if(fieldClass == typeof(float)) {
-				return Convert.ToSingle(value);
-			} else if(fieldClass == typeof(double)) {
-				return Convert.ToDouble(value);
-			} else if(fieldClass == typeof(LLUUID)) {
-				return new LLUUID(value);
-			} else if(fieldClass == typeof(bool)) {
-				if (value.ToLower() == "true")
-					return true;
-				else if (value.ToLower() == "false")
-					return false;
-				else
-					throw new Exception();
-			} else if(fieldClass == typeof(byte[])) {
-				return Helpers.StringToField(value);
-			} else if(fieldClass == typeof(LLVector3)) {
-				Match vector3Match = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
-				if (!vector3Match.Success)
-					throw new Exception();
-				return new LLVector3
-					(Convert.ToSingle(vector3Match.Groups[1].Captures[0].ToString())
-					,Convert.ToSingle(vector3Match.Groups[2].Captures[0].ToString())
-					,Convert.ToSingle(vector3Match.Groups[3].Captures[0].ToString())
-					);
-			} else if(fieldClass == typeof(LLVector3d)) {
-				Match vector3dMatch = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
-				if (!vector3dMatch.Success)
-					throw new Exception();
-				return new LLVector3d
-					(Convert.ToDouble(vector3dMatch.Groups[1].Captures[0].ToString())
-					,Convert.ToDouble(vector3dMatch.Groups[2].Captures[0].ToString())
-					,Convert.ToDouble(vector3dMatch.Groups[3].Captures[0].ToString())
-					);
-			} else if(fieldClass == typeof(LLVector4)) {
-				Match vector4Match = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
-				if (!vector4Match.Success)
-					throw new Exception();
-				float vector4X = Convert.ToSingle(vector4Match.Groups[1].Captures[0].ToString());
-				float vector4Y = Convert.ToSingle(vector4Match.Groups[2].Captures[0].ToString());
-				float vector4Z = Convert.ToSingle(vector4Match.Groups[3].Captures[0].ToString());
-				float vector4S = Convert.ToSingle(vector4Match.Groups[4].Captures[0].ToString());
-				byte[] vector4Bytes = new byte[16];
-				Array.Copy(BitConverter.GetBytes(vector4X), 0, vector4Bytes,  0, 4);
-				Array.Copy(BitConverter.GetBytes(vector4Y), 0, vector4Bytes,  4, 4);
-				Array.Copy(BitConverter.GetBytes(vector4Z), 0, vector4Bytes,  8, 4);
-				Array.Copy(BitConverter.GetBytes(vector4S), 0, vector4Bytes, 12, 4);
-				return new LLVector4(vector4Bytes, 0);
-			} else if(fieldClass == typeof(LLQuaternion)) {
-				Match quaternionMatch = (new Regex(@"<\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*>")).Match(value);
-				if (!quaternionMatch.Success)
-					throw new Exception();
-				return new LLQuaternion
-					(Convert.ToSingle(quaternionMatch.Groups[1].Captures[0].ToString())
-					,Convert.ToSingle(quaternionMatch.Groups[2].Captures[0].ToString())
-					,Convert.ToSingle(quaternionMatch.Groups[3].Captures[0].ToString())
-					);
-			} else {
-				throw new Exception("unsupported field type " + fieldClass);
-			}
-		} catch {
-			throw new Exception("unable to interpret " + value + " as " + fieldClass);
-		}
-/*				try {
-					switch (fieldMap.Type) {
-						case FieldType.LLVector3:
-						case FieldType.IPADDR:
-							return IPAddress.Parse(value);
-						case FieldType.IPPORT:
-							return Convert.ToUInt16(value);
-						case FieldType.Variable:
-							Match match = Regex.Match(value, @"^0x([0-9a-fA-F]{2})*", RegexOptions.IgnoreCase);
-							if (match.Success) {
-								byte[] buf = new byte[match.Groups[1].Captures.Count];
-								int i = 0;
-								foreach (Capture capture in match.Groups[1].Captures)
-									buf[i++] = Byte.Parse(capture.ToString(), NumberStyles.AllowHexSpecifier);
-								return buf;
-							} else
-								return value;
-					}
-				} catch {
-					throw new Exception("unable to interpret " + value + " as " + fieldMap.Type);
-				}
-
-				throw new Exception("unsupported field type " + fieldMap.Type);
-			}
-
-			throw new Exception("unknown field " + name + "." + block + "." + field);
-		}
-
-		throw new Exception("unknown block " + name + "." + block); */
-	}
-
-	// AnalyzeIn: analyze an incoming packet
-	private static Packet AnalyzeIn(Packet packet, IPEndPoint endPoint) {
-		return Analyze(packet, endPoint, Direction.Incoming);
-	}
-
-	// AnalyzeOut: analyze an outgoing packet
-	private static Packet AnalyzeOut(Packet packet, IPEndPoint endPoint) {
-		return Analyze(packet, endPoint, Direction.Outgoing);
-	}
-
-	// Analyze: modify and/or log a pocket
-	private static Packet Analyze(Packet packet, IPEndPoint endPoint, Direction direction) {
-		if (modifiedPackets.Contains(packet.Type))
-			try {
-				Hashtable changes = (Hashtable)modifiedPackets[packet.Type];
-				Type packetClass = packet.GetType();
-
-				foreach (BlockField bf in changes.Keys) {
-					//FIXME: support variable blocks
-
-					FieldInfo blockField = packetClass.GetField(bf.block);
-					//Type blockClass = blockField.FieldType;
-					object blockObject = blockField.GetValue(packet);
-					MagicSetField(blockObject,bf.field,changes[blockField]);
-				}
-			} catch (Exception e) {
-				Console.WriteLine("failed to modify " + packet.Type + ": " + e.Message);
-				Console.WriteLine(e.StackTrace);
-			}
-
-		if (loggedPackets.Contains(packet.Type))
-			LogPacket(packet, endPoint, direction);
-
-		return packet;
-	}
-
-	// LogAll: register logging delegates for all packets
-	private static void LogAll() {
-		Type packetTypeType = typeof(PacketType);
-		System.Reflection.MemberInfo[] packetTypes =  packetTypeType.GetMembers();
-
-		for(int i = 0; i < packetTypes.Length; i++) {
-			if(packetTypes[i].MemberType == System.Reflection.MemberTypes.Field && packetTypes[i].DeclaringType == packetTypeType) {
-				string name = packetTypes[i].Name;
-				PacketType pType = packetTypeFromName(name);
-				loggedPackets[pType] = null;
-				if (name != "ChatFromViewer") {
-					proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(AnalyzeIn));
-					proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(AnalyzeOut));
-				}
-			}
-		}
-	}
-
-	// NoLogAll: unregister logging delegates for all packets
-	private static void NoLogAll() {
-		Type packetTypeType = typeof(PacketType);
-		System.Reflection.MemberInfo[] packetTypes =  packetTypeType.GetMembers();
-
-		for(int i = 0; i < packetTypes.Length; i++) {
-			if(packetTypes[i].MemberType == System.Reflection.MemberTypes.Field  && packetTypes[i].DeclaringType == packetTypeType) {
-				string name = packetTypes[i].Name;
-				PacketType pType = packetTypeFromName(name);
-				loggedPackets.Remove(pType);
-				if (name != "ChatFromViewer") {
-					proxy.RemoveDelegate(pType, Direction.Incoming);
-					proxy.RemoveDelegate(pType, Direction.Outgoing);
-				}
-			}
-		}
-	}
-
-	// LogPacket: dump a packet to the console
-	private static void LogPacket(Packet packet, IPEndPoint endPoint, Direction direction) {
-		/* if (logGrep != null) {
-			bool match = false;
-			foreach (Block block in packet.Blocks())
-				foreach (Field field in block.Fields) {
-					string value;
-					if (field.Layout.Type == FieldType.Variable)
-						value = DataConvert.toChoppedString(field.Data);
-					else
-						value = field.Data.ToString();
-					if (Regex.Match(packet.Layout.Name + "." + block.Layout.Name + "." + field.Layout.Name + " = " + value, logGrep, RegexOptions.IgnoreCase).Success) {
-						match = true;
-						break;
-					}
-
-					// try matching variable fields in 0x notation
-					if (field.Layout.Type == FieldType.Variable) {
-						StringWriter sw = new StringWriter();
-						sw.Write("0x");
-						foreach (byte b in (byte[])field.Data)
-							sw.Write("{0:x2}", b);
-						if (Regex.Match(packet.Layout.Name + "." + block.Layout.Name + "." + field.Layout.Name + " = " + sw, logGrep, RegexOptions.IgnoreCase).Success) {
-							match = true;
-							break;
-						}
-					}
-				}
-			if (!match)
-				return;
-		} */
-
-		Console.WriteLine("{0} {1,21} {2,5} {3}{4}{5}"
-				 ,direction == Direction.Incoming ? "<--" : "-->"
-				 ,endPoint
-				 ,packet.Header.Sequence
-				 ,InterpretOptions(packet.Header.Flags)
-				 ,Environment.NewLine
-				 ,packet
-				 );
-	}
-
-	// InterpretOptions: produce a string representing a packet's header options
-	private static string InterpretOptions(byte options) {
-		return "["
-		     + ((options & Helpers.MSG_APPENDED_ACKS) != 0 ? "Ack" : "   ")
-		     + " "
-		     + ((options & Helpers.MSG_RESENT)        != 0 ? "Res" : "   ")
-		     + " "
-		     + ((options & Helpers.MSG_RELIABLE)      != 0 ? "Rel" : "   ")
-		     + " "
-		     + ((options & Helpers.MSG_ZEROCODED)     != 0 ? "Zer" : "   ")
-		     + "]"
-		     ;
-	}
-}
+                    // try matching variable fields in 0x notation
+                    if (field.Layout.Type == FieldType.Variable) {
+                        StringWriter sw = new StringWriter();
+                        sw.Write("0x");
+                        foreach (byte b in (byte[])field.Data)
+                            sw.Write("{0:x2}", b);
+                        if (Regex.Match(packet.Layout.Name + "." + block.Layout.Name + "." + field.Layout.Name + " = " + sw, logGrep, RegexOptions.IgnoreCase).Success) {
+                            match = true;
+                            break;
+                        }
+                    }
+                }
+            if (!match)
+                return;
+        } */
+
+        Console.WriteLine("{0} {1,21} {2,5} {3}{4}{5}"
+                 , direction == Direction.Incoming ? "<--" : "-->"
+                 , endPoint
+                 , packet.Header.Sequence
+                 , InterpretOptions(packet.Header.Flags)
+                 , Environment.NewLine
+                 , packet
+                 );
+    }
+
+    // InterpretOptions: produce a string representing a packet's header options
+    private static string InterpretOptions(byte options)
+    {
+        return "["
+             + ((options & Helpers.MSG_APPENDED_ACKS) != 0 ? "Ack" : "   ")
+             + " "
+             + ((options & Helpers.MSG_RESENT) != 0 ? "Res" : "   ")
+             + " "
+             + ((options & Helpers.MSG_RELIABLE) != 0 ? "Rel" : "   ")
+             + " "
+             + ((options & Helpers.MSG_ZEROCODED) != 0 ? "Zer" : "   ")
+             + "]"
+             ;
+    }
+}


Give sliders a default accent colour

Give sliders a default accent colour

--- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -42,7 +42,7 @@
         public double? SnakedStart { get; private set; }
         public double? SnakedEnd { get; private set; }

-        private Color4 accentColour;
+        private Color4 accentColour = Color4.White;
         /// <summary>
         /// Used to colour the path.
         /// </summary>


Add failing test for incorrect composer selection

--- osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs
+++ osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs
@@ -7,9 +7,13 @@
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets;
 using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
 using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
 using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.UI;
 using osu.Game.Screens.Edit.Compose.Components;
 using osu.Game.Tests.Beatmaps;
 using osuTK;
@@ -66,5 +70,36 @@

             AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider);
         }
+
+        [Test]
+        public void TestOverlappingObjectsWithSameStartTime()
+        {
+            AddStep("add overlapping circles", () =>
+            {
+                EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2));
+                EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2 + new Vector2(-10, -20)));
+                EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2 + new Vector2(10, -20)));
+            });
+
+            AddStep("click at centre of playfield", () =>
+            {
+                var hitObjectContainer = Editor.ChildrenOfType<HitObjectContainer>().Single();
+                var centre = hitObjectContainer.ToScreenSpace(OsuPlayfield.BASE_SIZE / 2);
+                InputManager.MoveMouseTo(centre);
+                InputManager.Click(MouseButton.Left);
+            });
+
+            AddAssert("frontmost object selected", () =>
+            {
+                var hasCombo = Editor.ChildrenOfType<HitCircleSelectionBlueprint>().Single(b => b.IsSelected).Item as IHasComboInformation;
+                return hasCombo?.IndexInCurrentCombo == 0;
+            });
+        }
+
+        private HitCircle createHitCircle(double startTime, Vector2 position) => new HitCircle
+        {
+            StartTime = startTime,
+            Position = position,
+        };
     }
 }


Adjust background error log from static dependency injection error

--- Rock.Core/Rock.StaticDependencyInjection/CompositionRoot.cs
+++ Rock.Core/Rock.StaticDependencyInjection/CompositionRoot.cs
@@ -45,7 +45,7 @@

         protected override void OnError(string message, Exception exception, ImportInfo import)
         {
-            BackgroundErrorLogger.Log(exception, message, "Rock.Core", import.ToString());
+            BackgroundErrorLogger.Log(exception, "Static Dependency Injection - " + message, "Rock.Core", "ImportInfo:\r\n" + import);

             base.OnError(message, exception, import);
         }


Don't display Completed blank rental agreements in list Fixed problem deleting blank rental agreements (with rates and conditions)

--- Server/src/HetsApi/Controllers/RentalAgreementController.cs
+++ Server/src/HetsApi/Controllers/RentalAgreementController.cs
@@ -262,7 +262,7 @@
                 string targetUrl = pdfHost + pdfUrl;

                 // generate pdf document name [unique portion only]
-                string ownerName = rentalAgreement.Equipment.Owner.OrganizationName.Trim().ToLower();
+                string ownerName = rentalAgreement.Equipment?.Owner?.OrganizationName?.Trim().ToLower();
                 ownerName = CleanName(ownerName);
                 ownerName = ownerName.Replace(" ", "");
                 ownerName = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(ownerName);
@@ -339,6 +339,8 @@

         private static string CleanName(string name)
         {
+            if (name == null) return "";
+
             name = name.Replace("'", "");
             name = name.Replace("<", "");
             name = name.Replace(">", "");
@@ -861,15 +863,19 @@
         /// </summary>
         [HttpGet]
         [Route("blankAgreements")]
-        [SwaggerOperation("BlankRentalAgreementGet")]
+        [SwaggerOperation("BlankRentalAgreementsGet")]
         [SwaggerResponse(200, type: typeof(List<HetRentalAgreement>))]
         [RequiresPermission(HetPermission.Login)]
-        public virtual IActionResult BlankRentalAgreementGet()
+        public virtual IActionResult BlankRentalAgreementsGet()
         {
             // get the current district
             int? districtId = UserAccountHelper.GetUsersDistrictId(_context, _httpContext);

-            // get all "blank" agreements
+            // get active status id
+            int? statusId = StatusHelper.GetStatusId(HetRentalAgreement.StatusActive, "rentalAgreementStatus", _context);
+            if (statusId == null) return new ObjectResult(new HetsResponse("HETS-23", ErrorViewModel.GetDescription("HETS-23", _configuration)));
+
+            // get all active "blank" agreements
             List<HetRentalAgreement> agreements = _context.HetRentalAgreement.AsNoTracking()
                 .Include(x => x.RentalAgreementStatusType)
                 .Include(x => x.District)
@@ -877,7 +883,8 @@
                 .Include(x => x.Equipment)
                 .Where(x => x.District.DistrictId == districtId &&
                             x.RentalRequestId == null &&
-                            x.RentalRequestRotationListId == null)
+                            x.RentalRequestRotationListId == null &&
+                            x.RentalAgreementStatusTypeId == (int)statusId)
                 .ToList();

             return new ObjectResult(new HetsResponse(agreements));
@@ -946,6 +953,8 @@

             // get agreement and validate
             HetRentalAgreement agreement = _context.HetRentalAgreement
+                .Include(a => a.HetRentalAgreementRate)
+                .Include(a => a.HetRentalAgreementCondition)
                 .First(a => a.RentalAgreementId == id);

             if (agreement.RentalAgreementStatusTypeId != statusId)
@@ -961,6 +970,18 @@
             if (agreement.RentalRequestId != null)
             {
                 return new ObjectResult(new HetsResponse("HETS-27", ErrorViewModel.GetDescription("HETS-27", _configuration)));
+            }
+
+            // delete rate
+            foreach (HetRentalAgreementRate item in agreement.HetRentalAgreementRate)
+            {
+                _context.HetRentalAgreementRate.Remove(item);
+            }
+
+            // delete conditions
+            foreach (HetRentalAgreementCondition item in agreement.HetRentalAgreementCondition)
+            {
+                _context.HetRentalAgreementCondition.Remove(item);
             }

             // delete the agreement


[trunk] Publish additional directories first, as subsequent steps may copy into those directories

--- packages/Publisher/dev/Scripts/Product/DelegateProcessing.cs
+++ packages/Publisher/dev/Scripts/Product/DelegateProcessing.cs
@@ -75,6 +75,30 @@
             // gather up the publishing metadata for those nodes
             var metaData = Publisher.ProductModuleUtilities.GetPublishingMetaData(moduleToBuild.OwningNode.Target, nodesToPublish);

+            // publish static directories first, as future steps may copy files into them
+            // for additional directories already on disk...
+            var additionalDirs = metaData.FilterByType<Publisher.AdditionalDirectoriesAttribute>();
+            // TODO: convert to var
+            foreach (Publisher.ProductModuleUtilities.MetaData meta in additionalDirs)
+            {
+                var dirData = meta.Data as Publisher.PublishDirectory;
+                if (null == dirData)
+                {
+                    throw new Opus.Core.Exception("Meta data '{0}' in '{1}' was of unexpected type '{2}'. Expected '{3}'",
+                        meta.Name, meta.Node.UniqueModuleName, meta.Data.GetType().ToString(), typeof(Publisher.PublishDirectory).ToString());
+                }
+
+                Opus.Core.Log.MessageAll("Additional dir '{0}' : '{1}' -> '{2}'", meta.Node.UniqueModuleName, dirData.Directory, publishDirPath);
+                copyAdditionalDir(
+                    moduleToBuild,
+                    primaryModule,
+                    dirsToCreate,
+                    meta,
+                    dirData,
+                    publishDirPath,
+                    context);
+            }
+
             // for built files to copy...
             var copyFiles = metaData.FilterByType<Publisher.CopyFileLocationsAttribute>();
             // TODO: convert to var
@@ -109,29 +133,6 @@
                 }
             }

-            // for additional files already on disk...
-            var additionalDirs = metaData.FilterByType<Publisher.AdditionalDirectoriesAttribute>();
-            // TODO: convert to var
-            foreach (Publisher.ProductModuleUtilities.MetaData meta in additionalDirs)
-            {
-                var dirData = meta.Data as Publisher.PublishDirectory;
-                if (null == dirData)
-                {
-                    throw new Opus.Core.Exception("Meta data '{0}' in '{1}' was of unexpected type '{2}'. Expected '{3}'",
-                        meta.Name, meta.Node.UniqueModuleName, meta.Data.GetType().ToString(), typeof(Publisher.PublishDirectory).ToString());
-                }
-
-                Opus.Core.Log.MessageAll("Additional dir '{0}' : '{1}' -> '{2}'", meta.Node.UniqueModuleName, dirData.Directory, publishDirPath);
-                copyAdditionalDir(
-                    moduleToBuild,
-                    primaryModule,
-                    dirsToCreate,
-                    meta,
-                    dirData,
-                    publishDirPath,
-                    context);
-            }
-
             var options = moduleToBuild.Options as Publisher.IPublishOptions;
             if (options.OSXApplicationBundle)
             {


Add logging to the master server connection button

--- Client/Network/NetworkServerList.cs
+++ Client/Network/NetworkServerList.cs
@@ -93,18 +93,25 @@
         /// </summary>
         public static void IntroduceToServer(long currentEntryId)
         {
-            var token = RandomString(10);
-            var ownEndpoint = new IPEndPoint(NetUtility.GetMyAddress(out var _), NetworkMain.Config.Port);
+            try
+            {
+                var token = RandomString(10);
+                var ownEndpoint = new IPEndPoint(NetUtility.GetMyAddress(out var _), NetworkMain.Config.Port);

-            var msgData = NetworkMain.CliMsgFactory.CreateNewMessageData<MsIntroductionMsgData>();
-            msgData.Id = currentEntryId;
-            msgData.Token = token;
-            msgData.InternalEndpoint = Common.StringFromEndpoint(ownEndpoint);
+                var msgData = NetworkMain.CliMsgFactory.CreateNewMessageData<MsIntroductionMsgData>();
+                msgData.Id = currentEntryId;
+                msgData.Token = token;
+                msgData.InternalEndpoint = Common.StringFromEndpoint(ownEndpoint);

-            var introduceMsg = NetworkMain.MstSrvMsgFactory.CreateNew<MainMstSrvMsg>(msgData);
+                var introduceMsg = NetworkMain.MstSrvMsgFactory.CreateNew<MainMstSrvMsg>(msgData);

-            LunaLog.Log($"[LMP]: Sending NAT introduction to server. Token: {token}");
-            NetworkSender.QueueOutgoingMessage(introduceMsg);
+                LunaLog.Log($"[LMP]: Sending NAT introduction to server. Token: {token}");
+                NetworkSender.QueueOutgoingMessage(introduceMsg);
+            }
+            catch (Exception e)
+            {
+                LunaLog.LogError($"[LMP]: Error connecting to server: {e}");
+            }
         }

         /// <summary>


Refs #1387 - implementatie van EersteLetterNamenOphalenCategorie

--- Solution/Chiro.Gap.Services/GelieerdePersonenService.svc.cs
+++ Solution/Chiro.Gap.Services/GelieerdePersonenService.svc.cs
@@ -37,6 +37,7 @@
         private readonly IRepository<GelieerdePersoon> _gelieerdePersonenRepo;
         private readonly IRepository<GroepsWerkJaar> _groepsWerkJarenRepo;
         private readonly IRepository<Groep> _groepenRepo;
+        private readonly IRepository<Categorie> _categorieenRepo;

         // Managers voor niet-triviale businesslogica

@@ -66,6 +67,7 @@
             _gelieerdePersonenRepo = repositoryProvider.RepositoryGet<GelieerdePersoon>();
             _groepsWerkJarenRepo = repositoryProvider.RepositoryGet<GroepsWerkJaar>();
             _groepenRepo = repositoryProvider.RepositoryGet<Groep>();
+            _categorieenRepo = repositoryProvider.RepositoryGet<Categorie>();

             _autorisatieMgr = autorisatieMgr;
             _communicatieVormenMgr = communicatieVormenMgr;
@@ -205,11 +207,23 @@
         /// <summary>
         /// Haalt een lijst op van de eerste letters van de achternamen van gelieerde personen van een categorie
         /// </summary>
-        /// <param name="categorie">Categorie waaruit we de letters willen halen</param>
+        /// <param name="categorieID">ID van de categorie waaruit we de letters willen halen</param>
         /// <returns>Lijst met de eerste letter van de namen</returns>
-        public IList<string> EersteLetterNamenOphalenCategorie(int categorie)
-        {
-            throw new NotImplementedException(NIEUWEBACKEND.Info);
+        public IList<string> EersteLetterNamenOphalenCategorie(int categorieID)
+        {
+            var categorie = _categorieenRepo.ByID(categorieID);
+
+            if (!_autorisatieMgr.IsGav(categorie))
+            {
+                throw FaultExceptionHelper.GeenGav();
+            }
+
+            // ik concatenate hieronder naam en voornaam, om personen op te vangen
+            // zonder familienaam. Blijkbaar zijn er zo. (Als dat maar goed komt...)
+
+            return (from gp in categorie.GelieerdePersoon
+                    orderby gp.Persoon.Naam + gp.Persoon.VoorNaam
+                    select (gp.Persoon.Naam + gp.Persoon.VoorNaam).Substring(0, 1).ToUpper()).Distinct().ToList();
         }

         /// <summary>


Changed references to obsolete methods

Changed references to obsolete methods

--- Solutions/PowerShell.Commands/Commands/Web/SetMasterPage.cs
+++ Solutions/PowerShell.Commands/Commands/Web/SetMasterPage.cs
@@ -16,11 +16,10 @@
         protected override void ExecuteCmdlet()
         {
             if(!string.IsNullOrEmpty(MasterPageUrl))
-
                 this.SelectedWeb.SetMasterPageByUrl(MasterPageUrl);

             if (!string.IsNullOrEmpty(CustomMasterPageUrl))
-                this.SelectedWeb.SetCustomMasterPageForSiteByUrl(CustomMasterPageUrl);
+                this.SelectedWeb.SetCustomMasterPageByUrl(CustomMasterPageUrl);

         }
     }


Fix collapsed controls

Fix collapsed controls

- Issue MatterHackers/MCCentral#2521
Control glitches potentially related to layout engine revisions

--- ControlElements/MHTextEditWidget.cs
+++ ControlElements/MHTextEditWidget.cs
@@ -50,7 +50,7 @@

 			this.ActualTextEditWidget = new TextEditWidget(text, x, y, pointSize, pixelWidth, pixelHeight, multiLine, tabIndex: tabIndex, typeFace: typeFace)
 			{
-				HAnchor = HAnchor.Stretch,
+				HAnchor = HAnchor.Fit,
 				VAnchor = VAnchor.Bottom
 			};
 			this.ActualTextEditWidget.MinimumSize = new Vector2(Math.Max(ActualTextEditWidget.MinimumSize.X, pixelWidth), Math.Max(ActualTextEditWidget.MinimumSize.Y, pixelHeight));


Add progress to ExtractZip.

Add progress to ExtractZip.

--- Crunchyroll-Downloader/Installer.cs
+++ Crunchyroll-Downloader/Installer.cs
@@ -52,8 +52,11 @@
 				data.Progress.GoNext();

 				zip.ExtractZip(InstallFolder + @"\ffmpeg.zip", InstallFolder, "");
+				data.Progress.CurrentTask.Progress = 0.33;
 				zip.ExtractZip(InstallFolder + @"\ffplay.zip", InstallFolder, "");
+				data.Progress.CurrentTask.Progress = 0.66;
 				zip.ExtractZip(InstallFolder + @"\ffprobe.zip", InstallFolder, "");
+				data.Progress.CurrentTask.Progress = 1.00;

 				File.Delete(@"C:\ProgramData\Crunchy-DL\ffmpeg.zip");
 				File.Delete(@"C:\ProgramData\Crunchy-DL\ffplay.zip");


Slightly clearer exception messages

Slightly clearer exception messages

--- Shielded/Shield.cs
+++ Shielded/Shield.cs
@@ -78,9 +78,9 @@
             AssertInTransaction();
             var ctx = _context;
             if (write && ctx.CommitCheckDone && !item.HasChanges)
-                throw new InvalidOperationException("Writes not allowed here.");
+                throw new InvalidOperationException("New writes not allowed here.");
             if (_blockEnlist != null && _blockEnlist != item)
-                throw new InvalidOperationException("Accessing shielded fields in this context is forbidden.");
+                throw new InvalidOperationException("Accessing other shielded fields in this context is forbidden.");

             var items = ctx.Items;
             if (hasLocals)
@@ -95,7 +95,7 @@
             if (!items.Enlisted.Contains(item))
             {
                 if (ctx.CommitCheckDone)
-                    throw new InvalidOperationException("Cannot access new fields.");
+                    throw new InvalidOperationException("Cannot access new fields here.");
                 // must not add into Enlisted before we run commutes, otherwise commutes' calls
                 // to Enlist would return false, and the commutes, although running first, would
                 // not actually check the lock! this also means that CheckCommutes must tolerate


Updated docs for zombie, vulnerable & thrusting

--- Skylight/Player.cs
+++ Skylight/Player.cs
@@ -412,10 +412,9 @@
         /// <param name="isFriend">if set to <c>true</c> [is friend].</param>
         /// <param name="level">The level.</param>
         /// <param name="hasClub">If the player has builder's club or not.</param>
-        /// <param name="isInvulnerable"></param>
-        /// <param name="isThrusting"></param>
-        /// <param name="isZombie"></param>
-        /// <param name="isZombie"></param>
+        /// <param name="isInvulnerable">The player can die.</param>
+        /// <param name="isThrusting">Player is using boost potion</param>
+        /// <param name="isZombie">Player is a zombie</param>
         public Player(Room room, int id, string name, int smiley, double xPos, double yPos, bool isGod, bool isMod,
             bool hasChat, int coins, bool purple, bool isFriend, int level, bool hasClub, bool isInvulnerable, bool isThrusting, bool isZombie)
         {


don't update menus twice when using cut

don't update menus twice when using cut

--- Sounds/MainForm.cs
+++ Sounds/MainForm.cs
@@ -1228,7 +1228,7 @@
                 i.Selected = true;
         }

-        public void CopySelected()
+        public void CopySelected(bool updateMenus = true)
         {
             // TODO: we parse this later using existing code because easier
             // but we could instead just pass TagLib.File to them directly
@@ -1236,7 +1236,11 @@
             var items = listView1.SelectedItems.Cast<ListViewItem>()
                 .Select(x => ((TagLib.File)x.Tag).Name);
             Clipboard.SetData(ClipboardType, items.ToList());
-            UpdateMenus(); // to update paste
+            // to update paste; cut will call delete which does this for us
+            if (updateMenus)
+            {
+                UpdateMenus();
+            }
         }

         private void copyToolStripMenuItem_Click(object sender, EventArgs e)
@@ -1246,7 +1250,7 @@

         private void cutToolStripMenuItem_Click(object sender, EventArgs e)
         {
-            CopySelected();
+            CopySelected(false);
             DeleteSelected();
         }


Changed the defaults to my preferred settings.

--- Source/Settings.cs
+++ Source/Settings.cs
@@ -42,17 +42,17 @@
         {
             // Set defaults
             Speed = 1.0f;
-            Step = 0.01f;
+            Step = 0.025f;
             PrecisionControlsModifier = 0.1f;
             ZeroControlsKey = "`";
             SetControlsKey = "y";
             Enabled = true;

-            MinTime = 0.05f;
-            Exponent = 2.0f;
+            MinTime = 0.2f;
+            Exponent = 1.5f;

-            PositionDeadZone = 0.1f;
-            PositionExponent = 2.0f;
+            PositionDeadZone = 0.025f;
+            PositionExponent = 1.5f;
         }

         internal void Load(ConfigNode config)


Change access ID cast to ConvertField to accomodate multiple database types.

git-tfs-id: [https://tfs.codeplex.com/tfs/TFS07]$/gsf/Main;C87521

--- Source/Libraries/GSF.TimeSeries/Transport/DataSubscriber.cs
+++ Source/Libraries/GSF.TimeSeries/Transport/DataSubscriber.cs
@@ -2791,7 +2791,7 @@
                                 if (!row["IsConcentrator"].ToNonNullString("0").ParseBoolean())
                                 {
                                     if (accessIDFieldExists)
-                                        accessID = (int)row.Field<long>("AccessID");
+                                        accessID = row.ConvertField<int>("AccessID");

                                     // Get longitude and latitude values if they are defined
                                     longitude = 0M;
@@ -2881,7 +2881,7 @@

                                     // Remove any devices in the database that are associated with the parent device and do not exist in the meta-data
                                     if (uniqueIDs.BinarySearch(uniqueID) < 0)
-                                        command.ExecuteNonQuery(deleteDeviceSql, m_metadataSynchronizationTimeout, uniqueID);
+                                        command.ExecuteNonQuery(deleteDeviceSql, m_metadataSynchronizationTimeout, uniqueID);
                                 }
                                 UpdateSyncProgress();
                             }


Transfer rows into objects

Transfer rows into objects

--- StoredProcedure.cs
+++ StoredProcedure.cs
@@ -4,6 +4,7 @@
 using System.Data;
 using System.Data.SqlClient;
 using System.Linq;
+using System.Reflection;
 using System.Text;

 namespace Iag.Unity.DataAccess
@@ -380,6 +381,16 @@
             }
         }

+        public T ExecuteScalar<T>(T defaultValue)
+        {
+            DateTime start = DateTime.Now;
+            object val = ExecuteScalar();
+            ExecuteTime = DateTime.Now.Subtract(start);
+
+            return (T)Convert.ChangeType(val, typeof(T));
+        }
+
+        [Obsolete("This method is obsolete.  Use T ExecuteScalar<T>(T defaultValue) instead.")]
         public int ExecuteScalar(int defaultValue = 0)
         {
             DateTime start = DateTime.Now;
@@ -394,6 +405,7 @@
         }

+        [Obsolete("This method is obsolete.  Use T ExecuteScalar<T>(T defaultValue) instead.")]
         public byte[] ExecuteScalar(byte[] defaultValue)
         {
             DateTime start = DateTime.Now;
@@ -407,6 +419,7 @@
         }

+        [Obsolete("This method is obsolete.  Use T ExecuteScalar<T>(T defaultValue) instead.")]
         public string ExecuteScalar(string defaultValue = null)
         {
             DateTime start = DateTime.Now;
@@ -419,6 +432,7 @@
                 return defaultValue;
         }

+        [Obsolete("This method is obsolete.  Use T ExecuteScalar<T>(T defaultValue) instead.")]
         public bool ExecuteScalar(bool defaultValue = false)
         {
             DateTime start = DateTime.Now;
@@ -430,6 +444,8 @@
             else
                 return defaultValue;
         }
+
+        [Obsolete("This method is obsolete.  Use T ExecuteScalar<T>(T defaultValue) instead.")]
         public DateTime? ExecuteScalar(DateTime? defaultValue = null)
         {
             DateTime start = DateTime.Now;
@@ -441,6 +457,8 @@
             else
                 return defaultValue;
         }
+
+        [Obsolete("This method is obsolete.  Use T ExecuteScalar<T>(T defaultValue) instead.")]
         public long ExecuteScalar(long defaultValue = 0)
         {
             DateTime start = DateTime.Now;
@@ -486,7 +504,7 @@
                     conn = DataLibrary.GetConnection();
                 using (UnitySqlCommand cmd = new UnitySqlCommand(conn, GetProcedureTextSql, procedureName))
                 {
-                    return cmd.ExecuteScalar(String.Empty);
+                    return cmd.ExecuteScalar<string>(String.Empty);
                 }
             }
             finally
@@ -495,6 +513,51 @@
                     conn.Dispose();
             }
         }
+
+        public IEnumerable<T> GetObjects<T>(bool strict = false) where T : class, new()
+        {
+            return GetRows().ToList().Select(row =>
+            {
+                T obj = Activator.CreateInstance<T>();
+                Fill<T>(obj, row, strict);
+                return obj;
+            });
+        }
+
+
+        // http://stackoverflow.com/questions/374651/how-to-check-if-an-object-is-nullable
+        private bool IsNullable(Type type)
+        {
+            return (!type.IsValueType || Nullable.GetUnderlyingType(type) != null);
+        }
+
+        private void Fill<T>(T obj, DataRow row, bool strict) where T : class
+        {
+            Type type = obj.GetType();
+
+            var sc = strict ? StringComparer.CurrentCulture : StringComparer.CurrentCultureIgnoreCase;
+
+            type.GetProperties()
+                .Where(prop => row.Table.Columns.Cast<DataColumn>().Any(col => sc.Equals(col.ColumnName, prop.Name))) // Only props that match
+                .ToList()
+                .ForEach(prop =>
+                {
+                    object value = row[prop.Name];
+                    if (value == DBNull.Value)
+                        value = null;
+
+                    object newValue = null;
+
+                    if (IsNullable(prop.PropertyType))
+                        newValue = (Nullable.GetUnderlyingType(prop.PropertyType) != null ? Convert.ChangeType(value, Nullable.GetUnderlyingType(prop.PropertyType)) : value);
+                    else
+                        newValue = Convert.ChangeType(value, prop.PropertyType);
+
+                    // This is what sets the class properties of the class
+                    prop.SetValue(obj, newValue, null);
+                });
+        }
+

         #region IDisposable Members


mode関係の読み取り専用プロパティをPublicに

mode関係の読み取り専用プロパティをPublicに

--- Source/ReactiveProperty.NETStandard/ReactivePropertySlim.cs
+++ Source/ReactiveProperty.NETStandard/ReactivePropertySlim.cs
@@ -111,8 +111,8 @@
             }
         }

-        bool IsDistinctUntilChanged => (mode & ReactivePropertyMode.DistinctUntilChanged) == ReactivePropertyMode.DistinctUntilChanged;
-        bool IsRaiseLatestValueOnSubscribe => (mode & ReactivePropertyMode.RaiseLatestValueOnSubscribe) == ReactivePropertyMode.RaiseLatestValueOnSubscribe;
+        public bool IsDistinctUntilChanged => (mode & ReactivePropertyMode.DistinctUntilChanged) == ReactivePropertyMode.DistinctUntilChanged;
+        public bool IsRaiseLatestValueOnSubscribe => (mode & ReactivePropertyMode.RaiseLatestValueOnSubscribe) == ReactivePropertyMode.RaiseLatestValueOnSubscribe;

         public ReactivePropertySlim(T initialValue = default(T), ReactivePropertyMode mode = ReactivePropertyMode.DistinctUntilChanged | ReactivePropertyMode.RaiseLatestValueOnSubscribe, IEqualityComparer<T> equalityComparer = null)
         {


Fixed MicroContainer for better handling of null object injection.

--- Sources/Silphid.Showzup/Sources/Injection/MicroContainer.cs
+++ Sources/Silphid.Showzup/Sources/Injection/MicroContainer.cs
@@ -144,6 +144,9 @@

         public void Inject(object obj, Dictionary<Type, object> extraBindings)
         {
+            if (obj == null)
+                throw new ArgumentNullException(nameof(obj));
+
             if (obj is GameObject)
                 InjectGameObject((GameObject) obj, extraBindings);
             else
@@ -152,6 +155,9 @@

         private void InjectObject(object obj, Dictionary<Type, object> extraBindings)
         {
+            if (obj == null)
+                throw new ArgumentNullException(nameof(obj));
+
             obj.GetType()
                 .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                 .ForEach(field => InjectField(obj, field, extraBindings));
@@ -203,9 +209,19 @@
         private void InjectGameObject(GameObject go) =>
             InjectGameObject(go, null);

+        private bool IsValidComponent(MonoBehaviour behaviour)
+        {
+            if (behaviour != null)
+                return true;
+
+            _logger.LogWarning(nameof(MicroContainer), "Skipping null MonoBehaviour.");
+            return false;
+        }
+
         private void InjectGameObject(GameObject go, Dictionary<Type, object> extraBindings)
         {
             go.GetComponents<MonoBehaviour>()
+                .Where(IsValidComponent)
                 .ForEach(x => InjectObject(x, extraBindings));

             go.Descendants()
@@ -215,6 +231,7 @@
         private void InjectMethods(GameObject go)
         {
             go.GetComponents<MonoBehaviour>()
+                .Where(IsValidComponent)
                 .ForEach(component => component.GetType()
                     .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                     .Where(method => method.HasAttribute<InjectAttribute>())


Improve descriptions of some activity spans.

--- StudentsCalendar.UI/Services/DefaultActivitySpanRenderer.cs
+++ StudentsCalendar.UI/Services/DefaultActivitySpanRenderer.cs
@@ -48,12 +48,22 @@

 		private string Describe(EveryXMonthsActivitySpan span)
 		{
-			return string.Format("co {0} {1}", span.Count, PluralForm(span.Count, "miesiąc", "miesięcy", "miesiące"));
+			var str = string.Format("co {0} {1}", span.Count, PluralForm(span.Count, "miesiąc", "miesięcy", "miesiące"));
+			if (span.StartDate.HasValue)
+			{
+				str += " począwszy od " + span.StartDate.Value.ToString("D", CultureInfo.CurrentCulture);
+			}
+			return str;
 		}

 		private string Describe(EveryXWeeksActivitySpan span)
 		{
-			return string.Format("co {0} {1}", span.Count, PluralForm(span.Count, "tydzień", "tygodni", "tygodnie"));
+			var str = string.Format("co {0} {1} począwszy od {2}", span.Count, PluralForm(span.Count, "tydzień", "tygodni", "tygodnie"));
+			if (span.StartDate.HasValue)
+			{
+				str += " począwszy od " + span.StartDate.Value.ToString("D", CultureInfo.CurrentCulture);
+			}
+			return str;
 		}

 		private string Describe(FullActivitySpan span)


Update SimpleBinaryBondSerializer.cs

Update SimpleBinaryBondSerializer.cs

--- TIKSN.Core/Serialization/Bond/SimpleBinaryBondSerializer.cs
+++ TIKSN.Core/Serialization/Bond/SimpleBinaryBondSerializer.cs
@@ -5,7 +5,7 @@
 {
     public class SimpleBinaryBondSerializer : SerializerBase<byte[]>
     {
-        protected override byte[] SerializeInternal(object obj)
+        protected override byte[] SerializeInternal<T>(T obj)
         {
             var output = new OutputBuffer();
             var writer = new SimpleBinaryWriter<OutputBuffer>(output);


Remove unused private method.

Remove unused private method.

--- Take2/NuLog/Loggers/TagNormalizers/StandardTagNormalizer.cs
+++ Take2/NuLog/Loggers/TagNormalizers/StandardTagNormalizer.cs
@@ -65,18 +65,5 @@
                 throw new InvalidOperationException(string.Format("tag \"{0}\" contains invalid characters.", tag));
             }
         }
-
-        /// <summary>
-        /// Checks the tags to make sure they look legit. Will throw an InvalidOperationException if
-        /// a tag is found that isn't compliant.
-        /// </summary>
-        /// <param name="tags">The tags to check.</param>
-        private void ValidateTags(string[] tags)
-        {
-            foreach (var tag in tags)
-            {
-                ValidateTag(tag);
-            }
-        }
     }
 }


Added second attempt at Silhouette. Does not work properly yet.

--- TessellationAndVoxelizationGeometryLibrary/2D/Silhouette.cs
+++ TessellationAndVoxelizationGeometryLibrary/2D/Silhouette.cs
@@ -17,7 +17,7 @@
         /// <param name="ts"></param>
         /// <param name="normal"></param>
         /// <returns></returns>
-        public static List<List<Point>> Run(TessellatedSolid ts, double[] normal)
+        public static List<List<Point>> Run2(TessellatedSolid ts, double[] normal)
         {
             //Get the negative faces
             var negativeFaces = new List<PolygonalFace>();
@@ -89,5 +89,150 @@

             return polygonList;
         }
+
+        /// <summary>
+        /// Gets the silhouette of a solid along a given normal.
+        /// </summary>
+        /// <param name="ts"></param>
+        /// <param name="normal"></param>
+        /// <returns></returns>
+        public static List<List<Point>> Run(TessellatedSolid ts, double[] normal)
+        {
+            //Get the negative faces into a dictionary
+            var negativeFaceDict = new Dictionary<int, PolygonalFace>();
+            foreach (var face in ts.Faces)
+            {
+                var dot = normal.dotProduct(face.Normal);
+                if (Math.Sign(dot) < 0)
+                {
+                    negativeFaceDict.Add(face.IndexInList, face);
+                }
+            }
+
+            var unusedNegativeFaces = new Dictionary<int, PolygonalFace>(negativeFaceDict);
+            var seperateSurfaces = new List<HashSet<PolygonalFace>>();
+
+            while (unusedNegativeFaces.Any())
+            {
+                var surface = new HashSet<PolygonalFace>();
+                var stack = new Stack<PolygonalFace>(new[] { unusedNegativeFaces.ElementAt(0).Value });
+                while (stack.Any())
+                {
+                    var face = stack.Pop();
+                    if (surface.Contains(face)) continue;
+                    surface.Add(face);
+                    unusedNegativeFaces.Remove(face.IndexInList);
+                    //Only push adjacent faces that are also negative
+                    foreach (var adjacentFace in face.AdjacentFaces)
+                    {
+                        if (adjacentFace == null) continue; //This is an error. Handle it in the error function.
+                        if (!negativeFaceDict.ContainsKey(adjacentFace.IndexInList)) continue; //Ignore if not negative
+                        stack.Push(adjacentFace);
+                    }
+                }
+                seperateSurfaces.Add(surface);
+            }
+
+            var solution = new List<List<Point>>();
+            //Get the purface positive and negative loops
+            var positiveLoops = new List<List<Vertex>>();
+            var negativeLoops = new List<List<Vertex>>();
+            foreach (var surface in seperateSurfaces)
+            {
+                //Get the surface inner and outer edges
+                var outerEdges = new HashSet<Edge>();
+                var innerEdges = new HashSet<Edge>();
+                foreach (var face in surface)
+                {
+                    if (face.Edges.Count != 3) throw new Exception();
+                    foreach (var edge in face.Edges)
+                    {
+                        //if (innerEdges.Contains(edge)) continue;
+                        if (!outerEdges.Contains(edge)) outerEdges.Add(edge);
+                        else if (outerEdges.Contains(edge))
+                        {
+                            innerEdges.Add(edge);
+                            outerEdges.Remove(edge);
+                        }
+                        else throw new Exception();
+                    }
+
+                }
+
+
+                //The inner edges may form 0 to many negative (CW) loops
+                while (outerEdges.Any())
+                {
+                    var isReversed = false;
+                    var startEdge = outerEdges.First();
+                    outerEdges.Remove(startEdge);
+                    var startVertex = startEdge.From;
+                    var vertex = startEdge.To;
+                    var loop = new List<Vertex> { vertex };
+                    var dot = 0.0;
+                    var previousEdge = startEdge;
+                    do
+                    {
+                        foreach (var edge2 in vertex.Edges.Where(edge2 => outerEdges.Contains(edge2)))
+                        {
+                            if (edge2.From == vertex)
+                            {
+                                outerEdges.Remove(edge2);
+                                vertex = edge2.To;
+                                loop.Add(vertex);
+                                //If the edge2.From vertex is the one that is shared, the previous edge comes first.
+                                dot += previousEdge.Vector.crossProduct(edge2.Vector).dotProduct(normal);
+                                break;
+                            }
+                            else if (edge2.To == vertex)
+                            {
+                                outerEdges.Remove(edge2);
+                                vertex = edge2.From;
+                                loop.Add(vertex);
+                                //If the edge2.To vertex is the one that is shared, edge2 comes first.
+                                dot += edge2.Vector.crossProduct(previousEdge.Vector).dotProduct(normal);
+                                previousEdge = edge2;
+                                break;
+                            }
+                            if (edge2 == vertex.Edges.Last() && !isReversed)
+                            {
+                                //Swap the vertices were interested in and
+                                //Reverse the loop
+                                var tempVertex = startVertex;
+                                startVertex = vertex;
+                                vertex = tempVertex;
+                                loop.Reverse();
+                                loop.Add(vertex);
+                                previousEdge = startEdge;
+                                isReversed = true;
+                            }
+                            else if (edge2 == vertex.Edges.Last() && isReversed)
+                            {
+                                //Artifically close the loop.
+                                vertex = startVertex;
+                            }
+                        }
+                    } while (vertex != startVertex && outerEdges.Any());
+                    if(Math.Sign(dot) > 0) positiveLoops.Add(loop);
+                    else if (dot.IsNegligible())
+                    {
+                        continue; //Ignore this loop for now.
+                        throw new Exception("Failed to assign CCW positive ordering. Should not occur, unless poolygon is invalid.");
+                    }
+                    else negativeLoops.Add(loop);
+                }
+
+
+            }
+            //Project positive loops into paths and make them CCW positive.
+            var allPaths = positiveLoops.Select(positiveLoop => PolygonOperations.CCWPositive(MiscFunctions.Get2DProjectionPoints(positiveLoop, normal))).ToList();
+
+            //Project negative loops into paths and make them CW negative.
+            allPaths.AddRange(negativeLoops.Select(negativeLoop => PolygonOperations.CWNegative(MiscFunctions.Get2DProjectionPoints(negativeLoop, normal))).ToList());
+
+            //Union all the paths
+            solution = PolygonOperations.Union(allPaths);
+            return solution;
+        }
     }
 }