VERSION: 0.9

CHANGELOG:
-webfront now displays player info and link to repz account
-webfront shows ips for authed admin ( determined by ip )
-webfront now show chat and allows authed players to send ingame messages
-webfront now has public ban list http://127.0.0.1/?pubbans
-webfront now shows player history
-fixed time span issue in webfront
-fixed most recent ban always missing
-fixed crash when RCON stops responding and removing a player
-version on footer
This commit is contained in:
RaidMax 2015-04-24 14:37:56 -05:00
parent f42ee69580
commit c9889f0792
13 changed files with 176 additions and 71 deletions

View File

@ -189,7 +189,7 @@ namespace IW4MAdmin
public override void Execute(Event E)
{
if (E.Owner.Unban(E.Data.Trim(), E.Target))
E.Origin.Tell("Successfully unbanned " + E.Data.Trim());
E.Origin.Tell("Successfully unbanned " + E.Target.getName());
else
E.Origin.Tell("Unable to find a ban for that GUID");
}

View File

@ -582,7 +582,12 @@ namespace IW4MAdmin
public List<Aliases> findPlayers(String name)
{
String Query = String.Format("SELECT * FROM ALIASES WHERE NAMES LIKE '%{0}%' LIMIT 15", name);
String[] EyePee = name.Split('.');
String Penor = "THISISNOTANIP";
if (EyePee.Length > 1)
Penor = (EyePee[0] + '.' + EyePee[1] + '.');
String Query = String.Format("SELECT * FROM ALIASES WHERE NAMES LIKE '%{0}%' OR IPS LIKE '%{1}%' LIMIT 15", name, Penor);
DataTable Result = GetDataTable(Query);
List<Aliases> players = new List<Aliases>();

View File

@ -125,22 +125,22 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="webfront\bans.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="webfront\footer.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="webfront\header.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="webfront\main.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="webfront\player.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="webfront\stats.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="config\maps.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@ -10,7 +10,7 @@ namespace IW4MAdmin
static String IP;
static int Port;
static String RCON;
static public double Version = 0.8;
static public double Version = 0.9;
static public double latestVersion;
static public List<Server> Servers;//

View File

@ -39,6 +39,7 @@ namespace IW4MAdmin
Skills = new Moserware.TrueSkill();
statusPlayers = new Dictionary<string, Player>();
chatHistory = new List<Chat>();
playerHistory = new Queue<int>();
lastWebChat = DateTime.Now;
nextMessage = 0;
initCommands();
@ -299,7 +300,7 @@ namespace IW4MAdmin
#endif
Log.Write("Client " + NewPlayer.getName() + " connecting...", Log.Level.Debug); // they're clean
if (chatHistory.Count > Math.Ceiling((double)clientnum / 2))
while (chatHistory.Count > Math.Ceiling((double)clientnum / 2))
chatHistory.RemoveAt(0);
chatHistory.Add(new Chat(NewPlayer, "<i>CONNECTED</i>", DateTime.Now));
@ -527,6 +528,12 @@ namespace IW4MAdmin
{
isRunning = true;
#if DEBUG
// Random rnd = new Random();
// while (playerHistory.Count < 144)
// playerHistory.Enqueue(rnd.Next(0, 18));
#endif
//Handles new rcon requests in a fashionable manner
Thread RCONQueue = new Thread(new ThreadStart(RCON.ManageRCONQueue));
RCONQueue.Start();
@ -553,6 +560,8 @@ namespace IW4MAdmin
String[] lines = new String[8];
String[] oldLines = new String[8];
DateTime start = DateTime.Now;
DateTime playerCountStart = DateTime.Now;
DateTime lastCount = DateTime.Now;
Utilities.Wait(1);
#if DEBUG == false
@ -566,6 +575,16 @@ namespace IW4MAdmin
#endif
{
lastMessage = DateTime.Now - start;
lastCount = DateTime.Now;
if ((lastCount - playerCountStart).TotalMinutes > 4)
{
while (playerHistory.Count > 144 )
playerHistory.Dequeue();
playerHistory.Enqueue(clientnum);
playerCountStart = DateTime.Now;
}
if(lastMessage.TotalSeconds > messageTime && messages.Count > 0)
{
initMacros(); // somethings dynamically change so we have to re-init the dictionary
@ -922,7 +941,7 @@ namespace IW4MAdmin
return false;
}
if (chatHistory.Count > Math.Ceiling(((double)clientnum - 1) / 2))
while (chatHistory.Count > Math.Ceiling(((double)clientnum - 1) / 2))
chatHistory.RemoveAt(0);
chatHistory.Add(new Chat(E.Origin, "<i>DISCONNECTED</i>", DateTime.Now));
@ -1006,7 +1025,7 @@ namespace IW4MAdmin
E.Data = Utilities.stripColors(Utilities.cleanChars(E.Data));
if (E.Data.Length > 50)
E.Data = E.Data.Substring(0, 50) + "...";
if (chatHistory.Count > Math.Ceiling((double)clientnum/2))
while (chatHistory.Count > Math.Ceiling((double)clientnum/2))
chatHistory.RemoveAt(0);
chatHistory.Add(new Chat(E.Origin, E.Data, DateTime.Now));
@ -1145,18 +1164,10 @@ namespace IW4MAdmin
if (B.getID() == Target.getID())
{
clientDB.removeBan(Target.getID(), Target.getIP());
int position = Bans.IndexOf(B);
if (position > -1 && position < Bans.Count - 1)
{
Log.Write("Removing ban at index #" + position, Log.Level.Debug);
Bans.RemoveAt(position);
Bans[position] = null;
}
else
Log.Write(position + " is an invalid ban index!", Log.Level.Debug);
for (int i = 0; i < IW4MAdmin.Program.Servers.Count; i++)
IW4MAdmin.Program.Servers[i].Bans = IW4MAdmin.Program.Servers[i].clientDB.getBans();
Player P = clientDB.getPlayer(Target.getID(), -1);
P.setLevel(Player.Permission.User);
@ -1231,7 +1242,7 @@ namespace IW4MAdmin
if ((requestTime - lastWebChat).TotalSeconds > 1)
{
Broadcast("^1[WEBCHAT] ^5" + P.getName() + "^7 - " + Message);
if (chatHistory.Count > Math.Ceiling((double)clientnum / 2))
while (chatHistory.Count > Math.Ceiling((double)clientnum / 2))
chatHistory.RemoveAt(0);
if (Message.Length > 50)
@ -1395,6 +1406,8 @@ namespace IW4MAdmin
public int totalKills = 0;
public List<Report> Reports;
public List<Chat> chatHistory;
public Queue<int> playerHistory;
//Info
private String IP;

View File

@ -265,7 +265,7 @@ namespace IW4MAdmin
{
String responseLine = S.Trim();
if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line!
if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 92) // its a client line!
{
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

View File

@ -13,18 +13,20 @@ namespace IW4MAdmin_Web
{
class Client
{
public Client ( WebFront.Page req, int cur, IDictionary<String, String> inc, String D)
public Client ( WebFront.Page req, int cur, IDictionary<String, String> inc, String D, IW4MAdmin.Player P)
{
requestedPage = req;
requestedPageNumber = cur;
requestOrigin = inc;
requestData = D;
playerRequesting = P;
}
public WebFront.Page requestedPage { get; private set; }
public int requestedPageNumber { get; private set; }
public IDictionary<String, String> requestOrigin { get; private set; }
public String requestData { get; private set; }
public IW4MAdmin.Player playerRequesting { get; private set; }
}
@ -117,7 +119,7 @@ namespace IW4MAdmin_Web
}
}
players.AppendFormat("<td><a href='/{0}/{1}/?player'>{2}</a></td>", i, P.getDBID(), IW4MAdmin.Utilities.nameHTMLFormatted(P));
players.AppendFormat("<td><a href='/{0}/{1}/userip/?player'>{2}</a></td>", i, P.getDBID(), IW4MAdmin.Utilities.nameHTMLFormatted(P));
if (count % 2 != 0)
{
@ -133,9 +135,10 @@ namespace IW4MAdmin_Web
<th class=server_title><span>{0}</span></th>
<th class=server_map><span>{1}</span></th>
<th class=server_players><span>{2}</span></th>
<th class=server_gametype><span>{3}</span></th>
<th><a href=/{4}/0/?stats>Stats</a> | </th>
<th class=server_gametype><span>{3}</span></th>
<th><a href=/{4}/0/?stats>Stats</a></th>
<th><a href=/{4}/0/?bans>Bans</a></th>
<th><a class='history' href='/{4}/0/?playerhistory'>History</a></th>
</tr>
</table>
<table cellpadding='0' cellspacing='0' class='players'>
@ -203,7 +206,7 @@ namespace IW4MAdmin_Web
Prefix = "class=row-grey";
else
Prefix = "class=row-white";
String Link = "/" + server + "/" + P.getDBID() + "/?player";
String Link = "/" + server + "/" + P.getDBID() + "/userip/?player";
buffer.AppendFormat("<tr {4}><td><a href='{5}'>{0}</a></th><td style='border-left: 3px solid #bbb; text-align:left;'>{1}</td><td style='border-left: 3px solid #bbb;text-align:left;'>{2}</td><td style='width: 175px; text-align:right;'>{3}</td></tr></div>", P.getName(), P.getLastO(), IW4MAdmin.Utilities.nameHTMLFormatted(B), Bans[i].getWhen(), Prefix, Link);
cycleFix++;
}
@ -251,7 +254,7 @@ namespace IW4MAdmin_Web
else
Prefix = "class=row-white";
String Link = "/" + server + "/" + P.getDBID() + "/?player";
String Link = "/" + server + "/" + P.getDBID() + "/userip/?player";
buffer.AppendFormat("<tr {5}><td><a href='{6}'>{0}</a></td><td style='border-left: 3px solid #bbb; text-align:left;'>{1}</td><td style='border-left: 3px solid #bbb;text-align:left;'>{2}</td><td style='border-left: 3px solid #bbb;text-align:left;'>{3}</td><td style='width: 175px; text-align:right;'>{4}</td></tr></div>", P.getName(), P.stats.Kills, P.stats.Deaths, P.stats.KDR, P.stats.Skill, Prefix, Link);
cycleFix++;
}
@ -325,7 +328,7 @@ namespace IW4MAdmin_Web
StringBuilder IPs = new StringBuilder();
if (logged && Data == null)
if (logged)
{
foreach (IW4MAdmin.Player a in aliases)
{
@ -340,10 +343,14 @@ namespace IW4MAdmin_Web
else
IPs.Append("XXX.XXX.XXX.XXX");
Int64 forumID = Int64.Parse(Player.getID().Substring(0,16), NumberStyles.AllowHexSpecifier);
forumID = forumID - 76561197960265728;
Int64 forumID = 0;
if (Player.getID().Length == 16)
{
forumID = Int64.Parse(Player.getID().Substring(0, 16), NumberStyles.AllowHexSpecifier);
forumID = forumID - 76561197960265728;
}
buffer.AppendFormat("<td><a href='{9}'>{0}</a></td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td><td>{5}</td><td>{6} ago</td><td><a href='https://repziw4.de/memberlist.php?mode=viewprofile&u={7}'>{8}</a></td>", Player.getName(), str, IPs, Rating, IW4MAdmin.Utilities.nameHTMLFormatted(Player.getLevel()), Player.getConnections(), Player.getLastConnection(), forumID, Player.getName(), "/0/" + Player.getDBID() + "/?player");
buffer.AppendFormat("<td><a href='{9}'>{0}</a></td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td><td>{5}</td><td>{6} ago</td><td><a href='https://repziw4.de/memberlist.php?mode=viewprofile&u={7}'>{8}</a></td>", Player.getName(), str, IPs, Rating, IW4MAdmin.Utilities.nameHTMLFormatted(Player.getLevel()), Player.getConnections(), Player.getLastConnection(), forumID, Player.getName(), "/0/" + Player.getDBID() + "/userip/?player");
buffer.Append("</tr>");
}
@ -362,11 +369,6 @@ namespace IW4MAdmin_Web
bool logged = IW4MAdmin.Program.Servers[server].clientDB.getAdmins().Exists(player => player.getIP() == C.requestOrigin["Host"].Split(':')[0]);
if (logged)
Console.WriteLine(C.requestOrigin["Host"] + " is authed");
else
Console.WriteLine(C.requestOrigin["Host"] + " is not authed");
switch (C.requestedPage)
{
case WebFront.Page.main:
@ -379,7 +381,7 @@ namespace IW4MAdmin_Web
output = output.Replace("{{STATS}}", parseMacros("STATS", C.requestedPage, server, C.requestedPageNumber, logged, C.requestData));
break;
case WebFront.Page.player:
output = output.Replace("{{PLAYER}}", parseMacros("PLAYER", C.requestedPage, server, C.requestedPageNumber, logged, C.requestData));
output = output.Replace("{{PLAYER}}", parseMacros("PLAYER", C.requestedPage, server, C.requestedPageNumber, (C.playerRequesting.getLevel() > IW4MAdmin.Player.Permission.Flagged), C.requestData));
break;
}
@ -409,7 +411,7 @@ namespace IW4MAdmin_Web
{
public void OnRequest(HttpRequestHead request, IDataProducer requestBody, IHttpResponseDelegate response)
{
String type = "text/html";
if (request.Uri.StartsWith("/"))
{
//Console.WriteLine("[WEBFRONT] Processing Request for " + request.Uri);
@ -441,7 +443,7 @@ namespace IW4MAdmin_Web
IW4MAdmin.file Bans = new IW4MAdmin.file("webfront\\bans.html");
var bans = Bans.getLines();
Bans.Close();
Client toSend = new Client(WebFront.Page.bans, page, request.Headers, null);
Client toSend = new Client(WebFront.Page.bans, page, request.Headers, null, null);
body = Macro.findMacros((header + bans + footer), toSend, server);
}
@ -450,21 +452,58 @@ namespace IW4MAdmin_Web
IW4MAdmin.file Stats = new IW4MAdmin.file("webfront\\stats.html");
var stats = Stats.getLines();
Stats.Close();
Client toSend = new Client(WebFront.Page.stats, page, request.Headers, null);
Client toSend = new Client(WebFront.Page.stats, page, request.Headers, null, null);
body = Macro.findMacros(header + stats + footer, toSend, server);
}
else if (request.QueryString == "playerhistory")
{
//type = "text/plain";
StringBuilder test = new StringBuilder();
test.Append("<script type='text/javascript' src='//www.google.com/jsapi'></script><div id='chart_div'></div>");
test.Append("<script> var players = [");
int count = 1;
DateTime prev = DateTime.Now;
foreach (int i in IW4MAdmin.Program.Servers[server].playerHistory.ToArray())
{
test.AppendFormat("[[{0},{1},{2}], {3}]", prev.Hour, prev.Minute, prev.Second, i);
prev = prev.AddMinutes(-5);
if (count < IW4MAdmin.Program.Servers[server].playerHistory.Count)
test.Append(',');
count++;
}
test.Append("];\n");
test.Append("</script>");
test.Append("<script>function drawBasic(){var a=new google.visualization.DataTable;a.addColumn('timeofday','Time'),a.addColumn('number','Players'),a.addRows(players);var e={ hAxis:{title:'Time', gridlines: {count:10}}, vAxis:{title:'Players'}, vAxis: {viewWindow: {max:18}, gridlines: {count:7}}},i=new google.visualization.LineChart(document.getElementById('chart_div'));i.draw(a,e)}google.load('visualization','1',{ callback: drawBasic, packages:['corechart','line']});</script>");
body = test.ToString();
}
else if (request.QueryString == "player")
{
IW4MAdmin.file Player = new IW4MAdmin.file("webfront\\player.html");
var player = Player.getLines();
Player.Close();
string Data;
if (req.Length > 2)
Data = req[2];
else
Data = null;
Client toSend = new Client(WebFront.Page.player, page, request.Headers, Data);
String Data = null, IP = null, ID = null;
if (req.Length > 3)
{
ID = req[1];
IP = req[2];
Data = req[3];
}
else if (req.Length >2)
{
ID = req[1];
IP = req[2];
}
IW4MAdmin.Player P = IW4MAdmin.Program.Servers[server].clientDB.getPlayer(IP);
if (P == null)
P = new IW4MAdmin.Player("Guest", "Guest", 0, 0);
if (P.getLevel() > IW4MAdmin.Player.Permission.Flagged)
Console.WriteLine(P.getName() + " is authenticated");
Client toSend = new Client(WebFront.Page.player, page, request.Headers, Data, P);
body = Macro.findMacros(header + player + footer, toSend, server);
}
@ -506,12 +545,25 @@ namespace IW4MAdmin_Web
}
}
else if (request.QueryString == "pubbans")
{
type = "text/plain";
StringBuilder banTXT = new StringBuilder();
banTXT.AppendFormat("===========================================\nIW4M ADMIN PUBLIC BAN LIST\nGENERATED {0}\nIP---GUID---REASON---TIME\n===========================================\n", DateTime.Now.ToString());
foreach (IW4MAdmin.Ban B in IW4MAdmin.Program.Servers[0].Bans)
{
if (B.getIP() != null && B.getIP() != String.Empty && B.getReason() != null && B.getReason() != String.Empty)
banTXT.AppendFormat("{0}---{1}---{2}---{3}\n", B.getIP(), B.getID(), B.getReason().Trim(), Math.Round((B.getTime()-DateTime.MinValue).TotalSeconds, 0));
}
body = banTXT.ToString();
}
else
{
IW4MAdmin.file Main = new IW4MAdmin.file("webfront\\main.html");
var main = Main.getLines();
Main.Close();
Client toSend = new Client(WebFront.Page.main, page, request.Headers, null);
Client toSend = new Client(WebFront.Page.main, page, request.Headers, null, null);
body = Macro.findMacros(header + main + footer, toSend, server);
}
@ -523,7 +575,7 @@ namespace IW4MAdmin_Web
Status = "200 OK",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/html" },
{ "Content-Type", type },
{ "Content-Length", body.Length.ToString() },
}
};
@ -539,7 +591,7 @@ namespace IW4MAdmin_Web
Status = "404 Not Found",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/text" },
{ "Content-Type", type },
{ "Content-Length", responseBody.Length.ToString() }
}
};

View File

@ -1,5 +1,5 @@
60
This server uses ^5IW4M Admin v0.8 ^7get it at ^5raidmax.org
This server uses ^5IW4M Admin v0.9 ^7get it at ^5raidmax.org
^5IW4M Admin ^7sees ^5YOU!
This server has harvested the information of ^5{{TOTALPLAYERS}} ^7players!
Cheaters are ^1unwelcome ^7 on this server

View File

@ -3,6 +3,8 @@ CHANGELOG:
-webfront now displays player info and link to repz account
-webfront shows ips for authed admin ( determined by ip )
-webfront now show chat and allows authed players to send ingame messages
-webfront now has public ban list http://127.0.0.1/?pubbans
-webfront now shows player history
-fixed time span issue in webfront
-fixed most recent ban always missing
-fixed crash when RCON stops responding and removing a player

View File

@ -1,3 +1,29 @@
<div id="footer">IW4M Admin v{{VERSION}} &mdash; <a href="http://raidmax.org/IW4MAdmin">RaidMax.org</a></div>
<script>
$('a').each(function () {
this.href = this.href.replace('userip', userip);
});
</script>
<script>
$(function () {
$("#history_dialog").dialog({
autoOpen: false,
modal: true,
width: 1000,
height: 350,
buttons: {
"Dismiss": function () {
$(this).dialog("close");
}
}
});
$("a.history").on("click", function (e) {
e.preventDefault();
$("#history_dialog").html("");
$("#history_dialog").dialog("option", "title", "Player History").dialog("open");
$("#history_dialog").load(this.href);
});
});
</script>
</body>
</html>

View File

@ -5,10 +5,13 @@
<meta charset="utf-8" />
<title>{{TITLE}}</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min.js"></script>
<link href="https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet">
<script type='text/javascript' src='//www.google.com/jsapi'></script>
<script type="text/javascript">
var userip;
</script>
<script type="text/javascript" src="http://l2.io/ip.js?var=userip"></script>
<script type="text/javascript" src="//l2.io/ip.js?var=userip"></script>
<style>
* {
font-family: 'Robot', sans-serif;
@ -129,8 +132,6 @@
border-radius: 4px;
color: #fff;
font-size: 14pt;
//width: 250px;
// height: 40px;
background-color: rgb(121, 194, 97);
}
@ -219,6 +220,7 @@
font-size: 20pt;
margin-bottom: 20px;
min-width: 530px;
max-width: 545px;
}
.server_info {
@ -375,6 +377,18 @@
{
text-align: left;
}
th
{
font-size: 14pt;
}
th a
{
font-size: 12pt;
padding-left: 10px;
}
</style>
</head>
<body>
@ -401,7 +415,7 @@
function searchPlayerName() {
var nameValue = document.getElementById("search_playerName").value;
if (nameValue.length > 0)
window.location.href = ("/0/0/" + nameValue + "/?player");
window.location.href = ("/0/0/" + userip + "/" + nameValue + "/?player");
}
</script>
<div id="player_search">

View File

@ -1,13 +1,6 @@
<script>
function show_data()
{
$('#chatList').load('/?chat');
}
setInterval('show_data()', 5000);
</script>
<div id="container">
<div class="h0" style="margin-top: 0">IW4M Admin</div><div id="header_img"></div>
<div id="container">
<div class="h0" style="margin-top: 0">IW4M Admin</div><div id="logo_shit"></div>
<div id="history_dialog"></div>
<h1 style="margin-top: 0;">Currently Monitoring</h1>
<hr />
{{SERVERS}}

View File

@ -11,10 +11,10 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.Build.0 = Debug|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.Build.0 = Release|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE