corrade-vassal

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 1  →  ?path2? @ 2
/Vassal/Vassal/VassalForm.cs
@@ -0,0 +1,1483 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
using System.Threading;
using System.Web;
using System.Windows.Forms;
using OpenMetaverse;
using Parallel = System.Threading.Tasks.Parallel;
using Timer = System.Timers.Timer;
 
namespace Vassal
{
public partial class Vassal : Form
{
public static System.Timers.Timer overviewTabTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
public static System.Timers.Timer topScriptsTabTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
public static System.Timers.Timer topCollidersTabTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
public static Dictionary<string, Vector3> ConfiguredRegions = new Dictionary<string, Vector3>();
public static VassalConfiguration vassalConfiguration = new VassalConfiguration();
public static Vassal vassalForm;
public static readonly object ClientInstanceTeleportLock = new object();
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>RFC1738 URL Escapes a string</summary>
/// <param name="data">a string to escape</param>
/// <returns>an RFC1738 escaped string</returns>
private static string wasURLEscapeDataString(string data)
{
return HttpUtility.UrlEncode(data);
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>RFC1738 URL Unescape a string</summary>
/// <param name="data">a string to unescape</param>
/// <returns>an RFC1738 unescaped string</returns>
private static string wasURLUnescapeDataString(string data)
{
return HttpUtility.UrlDecode(data);
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>URI unescapes an RFC3986 URI escaped string</summary>
/// <param name="data">a string to unescape</param>
/// <returns>the resulting string</returns>
private static string wasURIUnescapeDataString(string data)
{
// Uri.UnescapeDataString can only handle 32766 characters at a time
return string.Join("", Enumerable.Range(0, (data.Length + 32765)/32766)
.Select(o => Uri.UnescapeDataString(data.Substring(o*32766, Math.Min(32766, data.Length - (o*32766)))))
.ToArray());
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>RFC3986 URI Escapes a string</summary>
/// <param name="data">a string to escape</param>
/// <returns>an RFC3986 escaped string</returns>
private static string wasURIEscapeDataString(string data)
{
// Uri.EscapeDataString can only handle 32766 characters at a time
return string.Join("", Enumerable.Range(0, (data.Length + 32765)/32766)
.Select(o => Uri.EscapeDataString(data.Substring(o*32766, Math.Min(32766, data.Length - (o*32766)))))
.ToArray());
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Gets an array element at a given modulo index.
/// </summary>
/// <typeparam name="T">the array type</typeparam>
/// <param name="index">a positive or negative index of the element</param>
/// <param name="data">the array</param>
/// <return>an array element</return>
public static T wasGetElementAt<T>(T[] data, int index)
{
switch (index < 0)
{
case true:
return data[((index%data.Length) + data.Length)%data.Length];
default:
return data[index%data.Length];
}
}
 
#region KEY-VALUE DATA
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the value of a key from a key-value data string.
/// </summary>
/// <param name="key">the key of the value</param>
/// <param name="data">the key-value data segment</param>
/// <returns>true if the key was found in data</returns>
private static string wasKeyValueGet(string key, string data)
{
return data.Split('&')
.AsParallel()
.Select(o => o.Split('=').ToList())
.Where(o => o.Count.Equals(2))
.Select(o => new
{
k = o.First(),
v = o.Last()
})
.Where(o => o.k.Equals(key))
.Select(o => o.v)
.FirstOrDefault();
}
#endregion
 
#region CRYPTOGRAPHY
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Gets a sub-array from an array.
/// </summary>
/// <typeparam name="T">the array type</typeparam>
/// <param name="data">the array</param>
/// <param name="start">the start index</param>
/// <param name="stop">the stop index (-1 denotes the end)</param>
/// <returns>the array slice between start and stop</returns>
public static T[] wasGetSubArray<T>(T[] data, int start, int stop)
{
if (stop.Equals(-1))
stop = data.Length - 1;
T[] result = new T[stop - start + 1];
Array.Copy(data, start, result, 0, stop - start + 1);
return result;
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Delete a sub-array and return the result.
/// </summary>
/// <typeparam name="T">the array type</typeparam>
/// <param name="data">the array</param>
/// <param name="start">the start index</param>
/// <param name="stop">the stop index (-1 denotes the end)</param>
/// <returns>the array without elements between start and stop</returns>
public static T[] wasDeleteSubArray<T>(T[] data, int start, int stop)
{
if (stop.Equals(-1))
stop = data.Length - 1;
T[] result = new T[data.Length - (stop - start) - 1];
Array.Copy(data, 0, result, 0, start);
Array.Copy(data, stop + 1, result, start, data.Length - stop - 1);
return result;
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Concatenate multiple arrays.
/// </summary>
/// <typeparam name="T">the array type</typeparam>
/// <param name="arrays">multiple arrays</param>
/// <returns>a flat array with all arrays concatenated</returns>
public static T[] wasConcatenateArrays<T>(params T[][] arrays)
{
int resultLength = 0;
foreach (T[] o in arrays)
{
resultLength += o.Length;
}
T[] result = new T[resultLength];
int offset = 0;
for (int x = 0; x < arrays.Length; x++)
{
arrays[x].CopyTo(result, offset);
offset += arrays[x].Length;
}
return result;
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Permutes an array in reverse a given number of times.
/// </summary>
/// <typeparam name="T">the array type</typeparam>
/// <param name="input">the array</param>
/// <param name="times">the number of times to permute</param>
/// <returns>the array with the elements permuted</returns>
private static T[] wasReversePermuteArrayElements<T>(T[] input, int times)
{
if (times.Equals(0)) return input;
T[] slice = new T[input.Length];
Array.Copy(input, 1, slice, 0, input.Length - 1);
Array.Copy(input, 0, slice, input.Length - 1, 1);
return wasReversePermuteArrayElements(slice, --times);
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Permutes an array forward a given number of times.
/// </summary>
/// <typeparam name="T">the array type</typeparam>
/// <param name="input">the array</param>
/// <param name="times">the number of times to permute</param>
/// <returns>the array with the elements permuted</returns>
private static T[] wasForwardPermuteArrayElements<T>(T[] input, int times)
{
if (times.Equals(0)) return input;
T[] slice = new T[input.Length];
Array.Copy(input, input.Length - 1, slice, 0, 1);
Array.Copy(input, 0, slice, 1, input.Length - 1);
return wasForwardPermuteArrayElements(slice, --times);
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Encrypt or decrypt a message given a set of rotors, plugs and a reflector.
/// </summary>
/// <param name="message">the message to encyrpt or decrypt</param>
/// <param name="rotors">any combination of: 1, 2, 3, 4, 5, 6, 7, 8, b, g</param>
/// <param name="plugs">the letter representing the start character for the rotor</param>
/// <param name="reflector">any one of: B, b, C, c</param>
/// <returns>either a decrypted or encrypted string</returns>
private static string wasEnigma(string message, char[] rotors, char[] plugs, char reflector)
{
Dictionary<char, char[]> def_rotors = new Dictionary<char, char[]>
{
{
'1', new[]
{
'e', 'k', 'm', 'f', 'l',
'g', 'd', 'q', 'v', 'z',
'n', 't', 'o', 'w', 'y',
'h', 'x', 'u', 's', 'p',
'a', 'i', 'b', 'r', 'c',
'j'
}
},
{
'2', new[]
{
'a', 'j', 'd', 'k', 's',
'i', 'r', 'u', 'x', 'b',
'l', 'h', 'w', 't', 'm',
'c', 'q', 'g', 'z', 'n',
'p', 'y', 'f', 'v', 'o',
'e'
}
},
{
'3', new[]
{
'b', 'd', 'f', 'h', 'j',
'l', 'c', 'p', 'r', 't',
'x', 'v', 'z', 'n', 'y',
'e', 'i', 'w', 'g', 'a',
'k', 'm', 'u', 's', 'q',
'o'
}
},
{
'4', new[]
{
'e', 's', 'o', 'v', 'p',
'z', 'j', 'a', 'y', 'q',
'u', 'i', 'r', 'h', 'x',
'l', 'n', 'f', 't', 'g',
'k', 'd', 'c', 'm', 'w',
'b'
}
},
{
'5', new[]
{
'v', 'z', 'b', 'r', 'g',
'i', 't', 'y', 'u', 'p',
's', 'd', 'n', 'h', 'l',
'x', 'a', 'w', 'm', 'j',
'q', 'o', 'f', 'e', 'c',
'k'
}
},
{
'6', new[]
{
'j', 'p', 'g', 'v', 'o',
'u', 'm', 'f', 'y', 'q',
'b', 'e', 'n', 'h', 'z',
'r', 'd', 'k', 'a', 's',
'x', 'l', 'i', 'c', 't',
'w'
}
},
{
'7', new[]
{
'n', 'z', 'j', 'h', 'g',
'r', 'c', 'x', 'm', 'y',
's', 'w', 'b', 'o', 'u',
'f', 'a', 'i', 'v', 'l',
'p', 'e', 'k', 'q', 'd',
't'
}
},
{
'8', new[]
{
'f', 'k', 'q', 'h', 't',
'l', 'x', 'o', 'c', 'b',
'j', 's', 'p', 'd', 'z',
'r', 'a', 'm', 'e', 'w',
'n', 'i', 'u', 'y', 'g',
'v'
}
},
{
'b', new[]
{
'l', 'e', 'y', 'j', 'v',
'c', 'n', 'i', 'x', 'w',
'p', 'b', 'q', 'm', 'd',
'r', 't', 'a', 'k', 'z',
'g', 'f', 'u', 'h', 'o',
's'
}
},
{
'g', new[]
{
'f', 's', 'o', 'k', 'a',
'n', 'u', 'e', 'r', 'h',
'm', 'b', 't', 'i', 'y',
'c', 'w', 'l', 'q', 'p',
'z', 'x', 'v', 'g', 'j',
'd'
}
}
};
 
Dictionary<char, char[]> def_reflectors = new Dictionary<char, char[]>
{
{
'B', new[]
{
'a', 'y', 'b', 'r', 'c', 'u', 'd', 'h',
'e', 'q', 'f', 's', 'g', 'l', 'i', 'p',
'j', 'x', 'k', 'n', 'm', 'o', 't', 'z',
'v', 'w'
}
},
{
'b', new[]
{
'a', 'e', 'b', 'n', 'c', 'k', 'd', 'q',
'f', 'u', 'g', 'y', 'h', 'w', 'i', 'j',
'l', 'o', 'm', 'p', 'r', 'x', 's', 'z',
't', 'v'
}
},
{
'C', new[]
{
'a', 'f', 'b', 'v', 'c', 'p', 'd', 'j',
'e', 'i', 'g', 'o', 'h', 'y', 'k', 'r',
'l', 'z', 'm', 'x', 'n', 'w', 't', 'q',
's', 'u'
}
},
{
'c', new[]
{
'a', 'r', 'b', 'd', 'c', 'o', 'e', 'j',
'f', 'n', 'g', 't', 'h', 'k', 'i', 'v',
'l', 'm', 'p', 'w', 'q', 'z', 's', 'x',
'u', 'y'
}
}
};
 
// Setup rotors from plugs.
foreach (char rotor in rotors)
{
char plug = plugs[Array.IndexOf(rotors, rotor)];
int i = Array.IndexOf(def_rotors[rotor], plug);
if (i.Equals(0)) continue;
def_rotors[rotor] = wasConcatenateArrays(new[] {plug},
wasGetSubArray(wasDeleteSubArray(def_rotors[rotor], i, i), i, -1),
wasGetSubArray(wasDeleteSubArray(def_rotors[rotor], i + 1, -1), 0, i - 1));
}
 
StringBuilder result = new StringBuilder();
foreach (char c in message)
{
if (!char.IsLetter(c))
{
result.Append(c);
continue;
}
 
// Normalize to lower.
char l = char.ToLower(c);
 
Action<char[]> rotate = o =>
{
int i = o.Length - 1;
do
{
def_rotors[o[0]] = wasForwardPermuteArrayElements(def_rotors[o[0]], 1);
if (i.Equals(0))
{
rotors = wasReversePermuteArrayElements(o, 1);
continue;
}
l = wasGetElementAt(def_rotors[o[1]], Array.IndexOf(def_rotors[o[0]], l) - 1);
o = wasReversePermuteArrayElements(o, 1);
} while (--i > -1);
};
 
// Forward pass through the Enigma's rotors.
rotate.Invoke(rotors);
 
// Reflect
int x = Array.IndexOf(def_reflectors[reflector], l);
l = (x + 1)%2 == 0 ? def_reflectors[reflector][x - 1] : def_reflectors[reflector][x + 1];
 
// Reverse the order of the rotors.
Array.Reverse(rotors);
 
// Reverse pass through the Enigma's rotors.
rotate.Invoke(rotors);
 
if (char.IsUpper(c))
{
l = char.ToUpper(l);
}
result.Append(l);
}
 
return result.ToString();
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Expand the VIGENRE key to the length of the input.
/// </summary>
/// <param name="input">the input to expand to</param>
/// <param name="enc_key">the key to expand</param>
/// <returns>the expanded key</returns>
private static string wasVigenereExpandKey(string input, string enc_key)
{
string exp_key = string.Empty;
int i = 0, j = 0;
do
{
char p = input[i];
if (!char.IsLetter(p))
{
exp_key += p;
++i;
continue;
}
int m = j%enc_key.Length;
exp_key += enc_key[m];
++j;
++i;
} while (i < input.Length);
return exp_key;
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Encrypt using VIGENERE.
/// </summary>
/// <param name="input">the input to encrypt</param>
/// <param name="enc_key">the key to encrypt with</param>
/// <returns>the encrypted input</returns>
private static string wasEncryptVIGENERE(string input, string enc_key)
{
char[] a =
{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};
 
enc_key = wasVigenereExpandKey(input, enc_key);
string result = string.Empty;
int i = 0;
do
{
char p = input[i];
if (!char.IsLetter(p))
{
result += p;
++i;
continue;
}
char q =
wasReversePermuteArrayElements(a, Array.IndexOf(a, enc_key[i]))[
Array.IndexOf(a, char.ToLowerInvariant(p))];
if (char.IsUpper(p))
{
q = char.ToUpperInvariant(q);
}
result += q;
++i;
} while (i < input.Length);
return result;
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Decrypt using VIGENERE.
/// </summary>
/// <param name="input">the input to decrypt</param>
/// <param name="enc_key">the key to decrypt with</param>
/// <returns>the decrypted input</returns>
private static string wasDecryptVIGENERE(string input, string enc_key)
{
char[] a =
{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};
 
enc_key = wasVigenereExpandKey(input, enc_key);
string result = string.Empty;
int i = 0;
do
{
char p = input[i];
if (!char.IsLetter(p))
{
result += p;
++i;
continue;
}
char q =
a[
Array.IndexOf(wasReversePermuteArrayElements(a, Array.IndexOf(a, enc_key[i])),
char.ToLowerInvariant(p))];
if (char.IsUpper(p))
{
q = char.ToUpperInvariant(q);
}
result += q;
++i;
} while (i < input.Length);
return result;
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// An implementation of the ATBASH cypher for latin alphabets.
/// </summary>
/// <param name="data">the data to encrypt or decrypt</param>
/// <returns>the encrypted or decrypted data</returns>
private static string wasATBASH(string data)
{
char[] a =
{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};
 
char[] input = data.ToArray();
 
Parallel.ForEach(Enumerable.Range(0, data.Length), i =>
{
char e = input[i];
if (!char.IsLetter(e)) return;
int x = 25 - Array.BinarySearch(a, char.ToLowerInvariant(e));
if (!char.IsUpper(e))
{
input[i] = a[x];
return;
}
input[i] = char.ToUpperInvariant(a[x]);
});
 
return new string(input);
}
 
#endregion
 
/// <summary>
/// Corrade's input filter function.
/// </summary>
private static readonly Func<string, string> wasInput = o =>
{
if (string.IsNullOrEmpty(o)) return string.Empty;
 
foreach (Filter filter in vassalConfiguration.InputFilters)
{
switch (filter)
{
case Filter.RFC1738:
o = wasURLUnescapeDataString(o);
break;
case Filter.RFC3986:
o = wasURIUnescapeDataString(o);
break;
case Filter.ENIGMA:
o = wasEnigma(o, vassalConfiguration.ENIGMA.rotors.ToArray(),
vassalConfiguration.ENIGMA.plugs.ToArray(),
vassalConfiguration.ENIGMA.reflector);
break;
case Filter.VIGENERE:
o = wasDecryptVIGENERE(o, vassalConfiguration.VIGENERESecret);
break;
case Filter.ATBASH:
o = wasATBASH(o);
break;
case Filter.BASE64:
o = Encoding.UTF8.GetString(Convert.FromBase64String(o));
break;
}
}
return o;
};
 
/// <summary>
/// Corrade's output filter function.
/// </summary>
private static readonly Func<string, string> wasOutput = o =>
{
if (string.IsNullOrEmpty(o)) return string.Empty;
 
foreach (Filter filter in vassalConfiguration.OutputFilters)
{
switch (filter)
{
case Filter.RFC1738:
o = wasURLEscapeDataString(o);
break;
case Filter.RFC3986:
o = wasURIEscapeDataString(o);
break;
case Filter.ENIGMA:
o = wasEnigma(o, vassalConfiguration.ENIGMA.rotors.ToArray(),
vassalConfiguration.ENIGMA.plugs.ToArray(),
vassalConfiguration.ENIGMA.reflector);
break;
case Filter.VIGENERE:
o = wasEncryptVIGENERE(o, vassalConfiguration.VIGENERESecret);
break;
case Filter.ATBASH:
o = wasATBASH(o);
break;
case Filter.BASE64:
o = Convert.ToBase64String(Encoding.UTF8.GetBytes(o));
break;
}
}
return o;
};
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>Escapes a dictionary's keys and values for sending as POST data.</summary>
/// <param name="data">A dictionary containing keys and values to be escaped</param>
private static Dictionary<string, string> wasKeyValueEscape(Dictionary<string, string> data)
{
return data.AsParallel().ToDictionary(o => wasOutput(o.Key), p => wasOutput(p.Value));
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Converts a list of string to a comma-separated values string.
/// </summary>
/// <param name="l">a list of strings</param>
/// <returns>a commma-separated list of values</returns>
/// <remarks>compliant with RFC 4180</remarks>
public static string wasEnumerableToCSV(IEnumerable<string> l)
{
string[] csv = l.Select(o => o.Clone() as string).ToArray();
Parallel.ForEach(csv.Select((v, i) => new {i, v}), o =>
{
string cell = o.v.Replace("\"", "\"\"");
switch (new[] {'"', ' ', ',', '\r', '\n'}.Any(p => cell.Contains(p)))
{
case true:
csv[o.i] = "\"" + cell + "\"";
break;
default:
csv[o.i] = cell;
break;
}
});
return String.Join(",", csv);
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Converts a comma-separated list of values to a list of strings.
/// </summary>
/// <param name="csv">a comma-separated list of values</param>
/// <returns>a list of strings</returns>
/// <remarks>compliant with RFC 4180</remarks>
public static IEnumerable<string> wasCSVToEnumerable(string csv)
{
Stack<char> s = new Stack<char>();
StringBuilder m = new StringBuilder();
for (int i = 0; i < csv.Length; ++i)
{
switch (csv[i])
{
case ',':
if (!s.Any() || !s.Peek().Equals('"'))
{
yield return m.ToString();
m = new StringBuilder();
continue;
}
m.Append(csv[i]);
continue;
case '"':
if (i + 1 < csv.Length && csv[i].Equals(csv[i + 1]))
{
m.Append(csv[i]);
++i;
continue;
}
if (!s.Any() || !s.Peek().Equals(csv[i]))
{
s.Push(csv[i]);
continue;
}
s.Pop();
continue;
}
m.Append(csv[i]);
}
 
yield return m.ToString();
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Serialises a dictionary to key-value data.
/// </summary>
/// <param name="data">a dictionary</param>
/// <returns>a key-value data encoded string</returns>
private static string wasKeyValueEncode(Dictionary<string, string> data)
{
return String.Join("&", data.AsParallel().Select(o => String.Join("=", o.Key, o.Value)));
}
 
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// Sends a post request to an URL with set key-value pairs.
/// </summary>
/// <param name="URL">the url to send the message to</param>
/// <param name="message">key-value pairs to send</param>
/// <param name="millisecondsTimeout">the time in milliseconds for the request to timeout</param>
private static string wasPOST(string URL, Dictionary<string, string> message, uint millisecondsTimeout)
{
try
{
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(URL);
request.UserAgent = VASSAL_CONSTANTS.USER_AGENT;
request.Proxy = WebRequest.DefaultWebProxy;
request.Timeout = (int) millisecondsTimeout;
request.AllowAutoRedirect = true;
request.AllowWriteStreamBuffering = true;
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
request.Method = WebRequestMethods.Http.Post;
// set the content type based on chosen output filers
switch (vassalConfiguration.OutputFilters.Last())
{
case Filter.RFC1738:
request.ContentType = VASSAL_CONSTANTS.CONTENT_TYPE.WWW_FORM_URLENCODED;
break;
default:
request.ContentType = VASSAL_CONSTANTS.CONTENT_TYPE.TEXT_PLAIN;
break;
}
// send request
using (Stream requestStream = request.GetRequestStream())
{
using (StreamWriter dataStream = new StreamWriter(requestStream))
{
dataStream.Write(wasKeyValueEncode(message));
}
}
// read response
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (
StreamReader streamReader = new StreamReader(responseStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
catch (Exception)
{
 
}
 
return null;
}
 
/// <summary>
/// Constants used by Corrade.
/// </summary>
public struct VASSAL_CONSTANTS
{
/// <summary>
/// Copyright.
/// </summary>
public const string COPYRIGHT = @"(c) Copyright 2013 Wizardry and Steamworks";
 
public const string WIZARDRY_AND_STEAMWORKS = @"Wizardry and Steamworks";
public const string VASSAL = @"Vassal";
public const string WIZARDRY_AND_STEAMWORKS_WEBSITE = @"http://grimore.org";
 
/// <summary>
/// Vassal version.
/// </summary>
public static readonly string VASSAL_VERSION = Assembly.GetEntryAssembly().GetName().Version.ToString();
 
/// <summary>
/// Corrade user agent.
/// </summary>
public static readonly string USER_AGENT =
$"{VASSAL}/{VASSAL_VERSION} ({WIZARDRY_AND_STEAMWORKS_WEBSITE})";
 
/// <summary>
/// Vassal compile date.
/// </summary>
public static readonly string VASSAL_COMPILE_DATE = new DateTime(2000, 1, 1).Add(new TimeSpan(
TimeSpan.TicksPerDay*Assembly.GetEntryAssembly().GetName().Version.Build + // days since 1 January 2000
TimeSpan.TicksPerSecond*2*Assembly.GetEntryAssembly().GetName().Version.Revision)).ToLongDateString();
 
/// <summary>
/// Vassal configuration file.
/// </summary>
public static readonly string VASSAL_CONFIGURATION_FILE = @"Vassal.ini";
 
/// <summary>
/// Vassal regions file.
/// </summary>
public static readonly string VASSAL_REGIONS = @"Regions.csv";
 
/// <summary>
/// Conten-types that Corrade can send and receive.
/// </summary>
public struct CONTENT_TYPE
{
public const string TEXT_PLAIN = @"text/plain";
public const string WWW_FORM_URLENCODED = @"application/x-www-form-urlencoded";
}
}
 
private static readonly System.Action updateCurrentRegionName = () =>
{
try
{
string result = wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "getregiondata"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{"data", "Name"}
}), 60000);
bool success;
if (string.IsNullOrEmpty(result) ||
!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
{
vassalForm.BeginInvoke((MethodInvoker)(() =>
{
vassalForm.StatusText.Text = @"Failed to query Corrade for current region.";
}));
return;
}
switch (success)
{
case true:
vassalForm.BeginInvoke((MethodInvoker)(() =>
{
vassalForm.CurrentRegionAt.Visible = true;
vassalForm.CurrentRegionName.Visible = true;
vassalForm.CurrentRegionName.Text =
wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result))).Last();
}));
break;
default:
vassalForm.BeginInvoke((MethodInvoker)(() =>
{
vassalForm.CurrentRegionAt.Visible = false;
vassalForm.CurrentRegionName.Visible = false;
vassalForm.StatusText.Text = @"Error getting current region: " +
wasInput(wasKeyValueGet("error", result));
}));
break;
}
}
catch (Exception ex)
{
vassalForm.BeginInvoke((MethodInvoker)(() =>
{
vassalForm.StatusText.Text =
@"Error getting current region: " +
ex.Message;
}));
}
};
 
public Vassal()
{
InitializeComponent();
vassalForm = this;
}
 
private void RegionSelected(object sender, EventArgs e)
{
ListViewItem listViewItem = null;
vassalForm.Invoke((MethodInvoker) (() =>
{
listViewItem = LoadedRegions.SelectedItem as ListViewItem;
}));
 
switch (listViewItem != null)
{
case true:
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.StatusText.Text = @"Teleporting to " +
listViewItem.Text;
}));
break;
default:
return;
}
 
new Thread(() =>
{
lock (ClientInstanceTeleportLock)
{
try
{
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.RegionTeleportGroup.Enabled = false;
vassalForm.StatusProgress.Value = 0;
}));
int elapsedSeconds = 0;
System.Timers.Timer teleportTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
teleportTimer.Elapsed += (o, p) =>
{
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.StatusProgress.Value =
Math.Min(
(int)
(100d*
(TimeSpan.FromSeconds(++elapsedSeconds).TotalMilliseconds/
vassalConfiguration.TeleportTimeout)), 100);
}));
};
teleportTimer.Start();
string result = null;
ManualResetEvent receivedPOST = new ManualResetEvent(false);
new Thread(() =>
{
result = wasInput(wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "teleport"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{"region", listViewItem.Text},
{"position", ((Vector3) listViewItem.Tag).ToString()},
{"fly", "True"}
}), vassalConfiguration.TeleportTimeout));
receivedPOST.Set();
}) {IsBackground = true}.Start();
receivedPOST.WaitOne((int) vassalConfiguration.TeleportTimeout, false);
teleportTimer.Stop();
switch (!string.IsNullOrEmpty(result) && wasKeyValueGet("success", result) == "True")
{
case true:
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.StatusText.Text = @"Now at " + listViewItem.Text;
}));
break;
default:
switch (!string.IsNullOrEmpty(result))
{
case true:
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.StatusText.Text = @"Failed teleporting to " + listViewItem.Text +
@": " +
wasKeyValueGet("error", result);
}));
break;
default:
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.StatusText.Text = @"Failed teleporting to " + listViewItem.Text;
}));
break;
}
break;
}
}
catch (Exception ex)
{
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.StatusText.Text = @"Error communicating with Corrade: " + ex.Message;
}));
}
finally
{
vassalForm.Invoke((MethodInvoker) (() =>
{
vassalForm.StatusProgress.Value = 100;
vassalForm.RegionTeleportGroup.Enabled = true;
// Set the map image to the loading spinner.
Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
System.IO.Stream file =
thisAssembly.GetManifestResourceStream("Vassal.img.loading.gif");
switch (file != null)
{
case true:
vassalForm.Invoke((MethodInvoker)(() =>
{
RegionAvatarsMap.SizeMode = PictureBoxSizeMode.CenterImage;
RegionAvatarsMap.Image = Image.FromStream(file);
RegionAvatarsMap.Refresh();
}));
break;
}
// Clear the top scripts table.
TopScriptsGridView.Rows.Clear();
// Clear the top colliders table.
TopCollidersGridView.Rows.Clear();
}));
updateCurrentRegionName.BeginInvoke(updateCurrentRegionName.EndInvoke, null);
}
}
}).Start();
}
 
private void SettingsRequested(object sender, EventArgs e)
{
SettingsForm settingsForm = new SettingsForm {TopMost = true};
settingsForm.Show();
}
 
private void VassalShown(object sender, EventArgs e)
{
// Get the configuration file settings if it exists.
if (File.Exists(VASSAL_CONSTANTS.VASSAL_CONFIGURATION_FILE))
{
VassalConfiguration.Load(VASSAL_CONSTANTS.VASSAL_CONFIGURATION_FILE, ref vassalConfiguration);
}
 
// Get all the regions if they exist.
if (File.Exists(VASSAL_CONSTANTS.VASSAL_REGIONS))
{
Vector3 localPosition;
ConfiguredRegions =
File.ReadAllLines(VASSAL_CONSTANTS.VASSAL_REGIONS)
.Select(o => new List<string>(wasCSVToEnumerable(o)))
.Where(o => o.Count == 2)
.ToDictionary(o => o.First(),
p =>
Vector3.TryParse(p.Last(), out localPosition)
? localPosition
: Vector3.Zero).OrderBy(o => o.Key).ToDictionary(o => o.Key, o => o.Value);
LoadedRegions.Items.Clear();
LoadedRegions.Items.AddRange(
ConfiguredRegions.Select(o => (object)new ListViewItem {Text = o.Key, Tag = o.Value})
.ToArray());
}
 
// Update the current region in case the configuration is initialized.
if (!vassalConfiguration.Equals(default(VassalConfiguration)))
{
updateCurrentRegionName.BeginInvoke(updateCurrentRegionName.EndInvoke, null);
}
 
// Set the map image to the loading spinner.
Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
System.IO.Stream file =
thisAssembly.GetManifestResourceStream("Vassal.img.loading.gif");
switch (file != null)
{
case true:
vassalForm.Invoke((MethodInvoker)(() =>
{
RegionAvatarsMap.SizeMode = PictureBoxSizeMode.CenterImage;
RegionAvatarsMap.Image = Image.FromStream(file);
RegionAvatarsMap.Refresh();
}));
break;
}
 
// Start the overview timer.
overviewTabTimer.Elapsed += (o, p) =>
{
if (!Monitor.TryEnter(ClientInstanceTeleportLock))
return;
 
try
{
// Do not do anything in case the tab is not selected.
vassalForm.Invoke((MethodInvoker) (() =>
{
if (!Tabs.SelectedTab.Equals(OverviewTab))
throw new Exception();
}));
 
// Get the statistics.
string result = wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "getregiondata"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{
"data", wasEnumerableToCSV(new[]
{
"Agents",
"LastLag",
"Dilation",
"FPS",
"PhysicsFPS",
"ActiveScripts",
"ScriptTime",
"Objects"
})
}
}), vassalConfiguration.DataTimeout);
 
bool success;
if (string.IsNullOrEmpty(result) ||
!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
throw new Exception();
 
List<string> data = wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result))).ToList();
if (data.Count.Equals(0))
throw new Exception();
 
vassalForm.Invoke((MethodInvoker) (() =>
{
Agents.Text = data[data.IndexOf("Agents") + 1];
LastLag.Text = data[data.IndexOf("LastLag") + 1];
Dilation.Text = data[data.IndexOf("Dilation") + 1];
FPS.Text = data[data.IndexOf("FPS") + 1];
PhysicsFPS.Text = data[data.IndexOf("PhysicsFPS") + 1];
ActiveScripts.Text = data[data.IndexOf("ActiveScripts") + 1];
ScriptTime.Text = data[data.IndexOf("ScriptTime") + 1];
Objects.Text = data[data.IndexOf("Objects") + 1];
}));
// Get the map image.
result = wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "getgridregiondata"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{ "data", "MapImageID"}
}), vassalConfiguration.DataTimeout);
if (string.IsNullOrEmpty(result) ||
!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
throw new Exception();
 
data = wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result))).ToList();
if (!data.Count.Equals(2))
throw new Exception();
result = wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "download"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{"item", data.Last() },
{"type", "Texture" },
{"format", "Jpeg" },
}), vassalConfiguration.DataTimeout);
if (string.IsNullOrEmpty(result) ||
!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
throw new Exception();
byte[] mapImageBytes = Convert.FromBase64String(wasInput(wasKeyValueGet("data", result)));
Image mapImage;
using (MemoryStream memoryStream = new MemoryStream(mapImageBytes, 0, mapImageBytes.Length))
{
mapImage = Image.FromStream(memoryStream);
}
 
// Get the avatar positions.
result = wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "getavatarpositions"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{ "entity", "region"}
}), vassalConfiguration.DataTimeout);
 
if (string.IsNullOrEmpty(result) ||
!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
throw new Exception();
 
// Every thrid element represents a Vecto3 of the avatar position.
data = wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result))).Skip(2).Where((x, i) => i % 3 == 0).ToList();
 
// Draw the avatars onto the map.
Graphics mapGraphics = Graphics.FromImage(mapImage);
Vector3 position;
foreach (string vector in data)
{
switch (Vector3.TryParse(vector, out position))
{
case true:
mapGraphics.FillEllipse(Brushes.Chartreuse, new Rectangle((int)position.X, (int)position.Y, 4, 4));
break;
}
}
mapGraphics.DrawImage(mapImage, new Point(0, 0));
 
vassalForm.Invoke((MethodInvoker) (() =>
{
RegionAvatarsMap.SizeMode = PictureBoxSizeMode.StretchImage;
RegionAvatarsMap.Image = mapImage;
RegionAvatarsMap.Refresh();
}));
}
catch (Exception)
{
 
}
finally
{
Monitor.Exit(ClientInstanceTeleportLock);
}
};
overviewTabTimer.Start();
 
// Start the top scores timer.
topScriptsTabTimer.Elapsed += (o, p) =>
{
if (!Monitor.TryEnter(ClientInstanceTeleportLock))
return;
 
try
{
// Do not do anything in case the tab is not selected.
vassalForm.Invoke((MethodInvoker) (() =>
{
if (!Tabs.SelectedTab.Equals(TopScriptsTab))
throw new Exception();
}));
 
// Get the statistics.
string result = wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "getregiontop"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{"type", "scripts"}
}), vassalConfiguration.DataTimeout);
 
bool success;
if (string.IsNullOrEmpty(result) ||
!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
throw new Exception();
 
HashSet<List<string>> data =
new HashSet<List<string>>(wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result)))
.Select((x, i) => new {Index = i, Value = x})
.GroupBy(x => x.Index/5)
.Select(x => x.Select(v => v.Value).ToList()));
if (data.Count.Equals(0))
throw new Exception();
 
vassalForm.Invoke((MethodInvoker) (() =>
{
switch (!TopScriptsGridView.Rows.Count.Equals(0))
{
case true:
foreach (DataGridViewRow topScriptsRow in (IEnumerable)TopScriptsGridView.Rows)
{
List<string> updateRowData =
data.AsParallel()
.FirstOrDefault(q => q[2].Equals(topScriptsRow.Cells["TopScriptsUUID"].Value.ToString()));
switch (updateRowData != null && updateRowData.Count.Equals(5))
{
case true:
topScriptsRow.Cells["TopScriptsScore"].Value = updateRowData[0];
topScriptsRow.Cells["TopScriptsTaskName"].Value = updateRowData[1];
topScriptsRow.Cells["TopScriptsUUID"].Value = updateRowData[2];
topScriptsRow.Cells["TopScriptsOwner"].Value = updateRowData[3];
topScriptsRow.Cells["TopScriptsPosition"].Value = updateRowData[4];
break;
}
}
break;
default:
foreach (List<string> updateRowData in data)
{
TopScriptsGridView.Rows.Add(updateRowData[0], updateRowData[1], updateRowData[2],
updateRowData[3], updateRowData[4]);
}
break;
}
}));
}
catch (Exception)
{
 
}
finally
{
Monitor.Exit(ClientInstanceTeleportLock);
}
};
topScriptsTabTimer.Start();
 
// Start the top colliders timer.
topCollidersTabTimer.Elapsed += (o, p) =>
{
if (!Monitor.TryEnter(ClientInstanceTeleportLock))
return;
 
try
{
// Do not do anything in case the tab is not selected.
vassalForm.Invoke((MethodInvoker)(() =>
{
if (!Tabs.SelectedTab.Equals(TopCollidersTab))
throw new Exception();
}));
 
// Get the statistics.
string result = wasPOST(vassalConfiguration.HTTPServerURL,
wasKeyValueEscape(new Dictionary<string, string>
{
{"command", "getregiontop"},
{"group", vassalConfiguration.Group},
{"password", vassalConfiguration.Password},
{"type", "colliders"}
}), vassalConfiguration.DataTimeout);
 
bool success;
if (string.IsNullOrEmpty(result) ||
!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
throw new Exception();
 
HashSet<List<string>> data =
new HashSet<List<string>>(wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result)))
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / 5)
.Select(x => x.Select(v => v.Value).ToList()));
if (data.Count.Equals(0))
throw new Exception();
 
vassalForm.Invoke((MethodInvoker)(() =>
{
switch (!TopCollidersGridView.Rows.Count.Equals(0))
{
case true:
foreach (DataGridViewRow topCollidersRow in (IEnumerable)TopCollidersGridView.Rows)
{
List<string> updateRowData =
data.AsParallel()
.FirstOrDefault(q => q[2].Equals(topCollidersRow.Cells["TopCollidersUUID"].Value.ToString()));
switch (updateRowData != null && updateRowData.Count.Equals(5))
{
case true:
topCollidersRow.Cells["TopCollidersScore"].Value = updateRowData[0];
topCollidersRow.Cells["TopCollidersTaskName"].Value = updateRowData[1];
topCollidersRow.Cells["TopCollidersUUID"].Value = updateRowData[2];
topCollidersRow.Cells["TopCollidersOwner"].Value = updateRowData[3];
topCollidersRow.Cells["TopCollidersPosition"].Value = updateRowData[4];
break;
}
}
break;
default:
foreach (List<string> updateRowData in data)
{
TopCollidersGridView.Rows.Add(updateRowData[0], updateRowData[1], updateRowData[2],
updateRowData[3], updateRowData[4]);
}
break;
}
 
}));
}
catch (Exception)
{
 
}
finally
{
Monitor.Exit(ClientInstanceTeleportLock);
}
};
topCollidersTabTimer.Start();
}
 
private void RequestedEditRegions(object sender, EventArgs e)
{
// Clear any selection.
LoadedRegions.SelectedIndex = -1;
RegionEditForm regionEditForm = new RegionEditForm { TopMost = true };
regionEditForm.Show();
}
 
private void RequestSelecting(object sender, TabControlCancelEventArgs e)
{
e.Cancel = !e.TabPage.Enabled;
}
}
}