source code checkin

This commit is contained in:
Brett Hewitson 2021-01-07 16:23:23 +10:00
parent 5f8fb2c825
commit 7e57b72e35
675 changed files with 168433 additions and 0 deletions

View file

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Globals">
<SccProjectName>%24/Development/ServerManagers/Main/ArkData</SccProjectName>
<SccProvider>{4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}</SccProvider>
<SccAuxPath>https://dev.azure.com/bretthewitson</SccAuxPath>
<SccLocalPath>.</SccLocalPath>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,112 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace ArkData
{
/// <summary>
/// The container for the data.
/// </summary>
public partial class DataContainer
{
/// <summary>
/// Instantiates the DataContainer and parses all the user-data files
/// </summary>
/// <returns>The async task context containing the resulting container.</returns>
public static async Task<DataContainer> CreateAsync(string playerFileFolder, string tribeFileFolder)
{
var playerFiles = new string[0];
var tribeFiles = new string[0];
if (Directory.Exists(playerFileFolder))
{
playerFiles = Directory.GetFiles(playerFileFolder).Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(DataFileDetails.PlayerFilePrefix)
&& Path.GetFileNameWithoutExtension(f).EndsWith(DataFileDetails.PlayerFileSuffix)
&& Path.GetExtension(f).Equals(DataFileDetails.PlayerFileExtension)).ToArray();
}
if (Directory.Exists(tribeFileFolder))
{
tribeFiles = Directory.GetFiles(tribeFileFolder).Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(DataFileDetails.TribeFilePrefix)
&& Path.GetFileNameWithoutExtension(f).EndsWith(DataFileDetails.TribeFileSuffix)
&& Path.GetExtension(f).Equals(DataFileDetails.TribeFileExtension)).ToArray();
}
var container = new DataContainer();
foreach (var file in playerFiles)
container.Players.Add(await Parser.ParsePlayerAsync(file));
foreach (var file in tribeFiles)
container.Tribes.Add(await Parser.ParseTribeAsync(file));
container.LinkPlayerTribe();
return container;
}
/// <summary>
/// Loads the profile data for all users from the steam service
/// </summary>
/// <returns>The async task context.</returns>
public async Task<DateTime> LoadSteamAsync(string apiKey, int steamUpdateInterval = 0)
{
const int MAX_STEAM_IDS = 100;
// need to make multiple calls of 100 steam id's.
var lastSteamUpdateUtc = DateTime.UtcNow;
var startIndex = 0;
var playerSteamIds = Players.Where(p => p.LastPlatformUpdateUtc.AddMinutes(steamUpdateInterval) < DateTime.UtcNow).Select(p => p.PlayerId).ToArray();
while (true)
{
// check if the start index has exceeded the Players list count.
if (startIndex >= playerSteamIds.Length) break;
// get the number of steam ids to read.
int steamIdsCount = System.Math.Min(MAX_STEAM_IDS, playerSteamIds.Length - startIndex);
// get a comma delimited list of the steam ids to process
var builder = string.Join(",", playerSteamIds, startIndex, steamIdsCount);
using (var client = new HttpClient())
{
client.BaseAddress = new System.Uri("https://api.steampowered.com/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(string.Format("ISteamUser/GetPlayerSummaries/v0002/?key={0}&steamids={1}", apiKey, builder));
if (response.IsSuccessStatusCode)
using (var reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
LinkSteamProfiles(await reader.ReadToEndAsync(), lastSteamUpdateUtc);
}
else
throw new System.Net.WebException("The Steam API request was unsuccessful. Are you using a valid key?");
}
startIndex += steamIdsCount;
}
SteamLoaded = true;
return lastSteamUpdateUtc;
}
/// <summary>
/// Fetches the player server status. Can only be done after fetching Steam player data.
/// </summary>
/// <param name="ipString">The IP of the server.</param>
/// <param name="port">The port of the server.</param>
/// <returns>The async task context.</returns>
public Task LoadOnlinePlayersAsync(string ipString, int port)
{
if (SteamLoaded)
return Task.Run(() =>
{
LinkOnlinePlayers(ipString, port);
});
else
throw new System.Exception("The Steam user data should be loaded before the server status can be checked.");
}
}
}

View file

@ -0,0 +1,101 @@
using SSQLib;
using System.Net;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Runtime.Remoting;
using System;
/// <summary>
/// The container for the data.
/// </summary>
namespace ArkData
{
public partial class DataContainer
{
/// <summary>
/// A list of all players registered on the server.
/// </summary>
public List<PlayerData> Players { get; set; }
/// <summary>
/// A list of all tribes registered on the server.
/// </summary>
public List<TribeData> Tribes { get; set; }
/// <summary>
/// Indicates whether the steam user data has been loaded.
/// </summary>
private bool SteamLoaded { get; set; }
/// <summary>
/// Constructs the DataContainer.
/// </summary>
public DataContainer()
{
Players = new List<PlayerData>();
Tribes = new List<TribeData>();
SteamLoaded = false;
}
/// <summary>
/// Links the online players, to the player profiles.
/// </summary>
/// <param name="ipString">The server ip address.</param>
/// <param name="port">The Steam query port.</param>
private void LinkOnlinePlayers(string ipString, int port)
{
try
{
var online = Enumerable.OfType<PlayerInfo>(new SSQL().Players(new IPEndPoint(IPAddress.Parse(ipString), port))).ToList();
for (var i = 0; i < Players.Count; i++)
{
var online_player = online.SingleOrDefault(p => p.Name == Players[i].PlayerName);
if (online_player != null)
Players[i].Online = true;
else
Players[i].Online = false;
}
}
catch (SSQLServerException)
{
throw new ServerException("The connection to the server failed. Please check the configured IP address and port.");
}
}
/// <summary>
/// Links the players to their tribes and the tribes to the players.
/// </summary>
private void LinkPlayerTribe()
{
for (var i = 0; i < Players.Count; i++)
{
var player = Players[i];
player.OwnedTribes = Tribes.Where(t => t.OwnerId == player.CharacterId).ToList();
player.Tribe = Tribes.SingleOrDefault(t => t.Id == player.TribeId);
}
for (var i = 0; i < Tribes.Count; i++)
{
var tribe = Tribes[i];
tribe.Owner = Players.SingleOrDefault(p => p.CharacterId == tribe.OwnerId);
tribe.Players = Players.Where(p => p.TribeId == tribe.Id).ToList();
}
}
/// <summary>
/// Deserializes JSON from Steam API and links Steam profile to player profile.
/// </summary>
/// <param name="jsonString">The JSON data string.</param>
private void LinkSteamProfiles(string jsonString, DateTime lastSteamUpdateUtc)
{
var profiles = JsonConvert.DeserializeObject<Models.SteamResponse<Models.SteamProfile>>(jsonString).response.players;
for (var i = 0; i < profiles.Count; i++)
{
var player = Players.Single(p => p.PlayerId == profiles[i].steamid);
player.PlayerName = profiles[i].personaname;
player.LastPlatformUpdateUtc = lastSteamUpdateUtc;
}
}
}
}

View file

@ -0,0 +1,108 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
namespace ArkData
{
/// <summary>
/// The container for the data.
/// </summary>
public partial class DataContainer
{
/// <summary>
/// Instantiates the DataContainer and parses all the user data files
/// </summary>
public static DataContainer Create(string playerFileFolder, string tribeFileFolder)
{
var playerFiles = new string[0];
var tribeFiles = new string[0];
if (Directory.Exists(playerFileFolder))
{
playerFiles = Directory.GetFiles(playerFileFolder).Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(DataFileDetails.PlayerFilePrefix)
&& Path.GetFileNameWithoutExtension(f).EndsWith(DataFileDetails.PlayerFileSuffix)
&& Path.GetExtension(f).Equals(DataFileDetails.PlayerFileExtension)).ToArray();
}
if (Directory.Exists(tribeFileFolder))
{
tribeFiles = Directory.GetFiles(tribeFileFolder).Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(DataFileDetails.TribeFilePrefix)
&& Path.GetFileNameWithoutExtension(f).EndsWith(DataFileDetails.TribeFileSuffix)
&& Path.GetExtension(f).Equals(DataFileDetails.TribeFileExtension)).ToArray();
}
var container = new DataContainer();
foreach (var file in playerFiles)
container.Players.Add(Parser.ParsePlayer(file));
foreach (var file in tribeFiles)
container.Tribes.Add(Parser.ParseTribe(file));
container.LinkPlayerTribe();
return container;
}
/// <summary>
/// Loads the profile data for all users from the steam service
/// </summary>
/// <param name="apiKey">The Steam API key</param>
public DateTime LoadSteam(string apiKey, int steamUpdateInterval = 0)
{
const int MAX_STEAM_IDS = 100;
// need to make multiple calls of 100 steam id's.
var lastSteamUpdateUtc = DateTime.UtcNow;
var startIndex = 0;
var playerSteamIds = Players.Where(p => p.LastPlatformUpdateUtc.AddMinutes(steamUpdateInterval) < DateTime.UtcNow).Select(p => p.PlayerId).ToArray();
while (true)
{
// check if the start index has exceeded the Players list count.
if (startIndex >= playerSteamIds.Length) break;
// get the number of steam ids to read.
int steamIdsCount = System.Math.Min(MAX_STEAM_IDS, playerSteamIds.Length - startIndex);
// get a comma delimited list of the steam ids to process
var builder = string.Join(",", playerSteamIds, startIndex, steamIdsCount);
using (var client = new HttpClient())
{
client.BaseAddress = new System.Uri("https://api.steampowered.com/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync(string.Format("ISteamUser/GetPlayerSummaries/v0002/?key={0}&steamids={1}", apiKey, builder)).Result;
if (response.IsSuccessStatusCode)
using (var reader = new StreamReader(response.Content.ReadAsStreamAsync().Result))
{
LinkSteamProfiles(reader.ReadToEnd(), lastSteamUpdateUtc);
}
else
throw new System.Net.WebException("The Steam API request was unsuccessful. Are you using a valid key?");
}
startIndex += steamIdsCount;
}
SteamLoaded = true;
return lastSteamUpdateUtc;
}
/// <summary>
/// Fetches the player server status. Can only be done after fetching Steam player data.
/// </summary>
/// <param name="ipString">The IP of the server.</param>
/// <param name="port">The port of the server.</param>
public void LoadOnlinePlayers(string ipString, int port)
{
if (SteamLoaded)
{
LinkOnlinePlayers(ipString, port);
}
else
throw new System.Exception("The Steam user data should be loaded before the server status can be checked.");
}
}
}

View file

@ -0,0 +1,13 @@
namespace ArkData
{
public static class DataFileDetails
{
public static string PlayerFilePrefix { get; set; } = string.Empty;
public static string PlayerFileSuffix { get; set; } = string.Empty;
public static string PlayerFileExtension { get; set; } = ".arkprofile";
public static string TribeFilePrefix { get; set; } = string.Empty;
public static string TribeFileSuffix { get; set; } = string.Empty;
public static string TribeFileExtension { get; set; } = ".arktribe";
}
}

58
src/ArkData/Extensions.cs Normal file
View file

@ -0,0 +1,58 @@
using System.Collections.Generic;
namespace ArkData
{
internal static class Extensions
{
private static readonly int[] Empty = new int[0];
public static int LocateFirst(this byte[] self, byte[] candidate, int offset = 0)
{
if (IsEmptyLocate(self, candidate, offset))
return -1;
for (int position = offset; position < self.Length; position++)
if (IsMatch(self, position, candidate))
return position;
return -1;
}
public static int[] Locate(this byte[] self, byte[] candidate)
{
if (IsEmptyLocate(self, candidate, 0))
return Empty;
List<int> list = new List<int>();
for (int position = 0; position < self.Length; ++position)
if (IsMatch(self, position, candidate))
list.Add(position);
if (list.Count != 0)
return list.ToArray();
return Empty;
}
private static bool IsMatch(byte[] array, int position, byte[] candidate)
{
if (candidate.Length > array.Length - position)
return false;
for (int index = 0; index < candidate.Length; ++index)
if ((array[position + index] != candidate[index]))
return false;
return true;
}
private static bool IsEmptyLocate(byte[] array, byte[] candidate, int offset)
{
if (array != null && candidate != null &&
(array.Length != 0 && candidate.Length != 0) &&
(candidate.Length <= array.Length && offset != -1))
return offset > array.Length;
return true;
}
}
}

59
src/ArkData/Helpers.cs Normal file
View file

@ -0,0 +1,59 @@
using System;
using System.Text;
namespace ArkData
{
internal class Helpers
{
public static int GetInt(byte[] data, string name)
{
byte[] bytes1 = Encoding.Default.GetBytes(name);
byte[] bytes2 = Encoding.Default.GetBytes("IntProperty");
int offset = data.LocateFirst(bytes1, 0);
int num = data.LocateFirst(bytes2, offset);
if (num > -1)
return BitConverter.ToInt32(data, num + bytes2.Length + 9);
return -1;
}
public static ushort GetUInt16(byte[] data, string name)
{
byte[] bytes1 = Encoding.Default.GetBytes(name);
byte[] bytes2 = Encoding.Default.GetBytes("UInt16Property");
int offset = data.LocateFirst(bytes1, 0);
int num = data.LocateFirst(bytes2, offset);
if (num >= 0)
return BitConverter.ToUInt16(data, num + bytes2.Length + 9);
return 0;
}
public static string GetString(byte[] data, string name)
{
byte[] bytes1 = Encoding.Default.GetBytes(name);
byte[] bytes2 = Encoding.Default.GetBytes("StrProperty");
int offset = data.LocateFirst(bytes1, 0);
int num = data.LocateFirst(bytes2, offset);
if (num < 0)
return string.Empty;
byte[] numArray = new byte[1];
Array.Copy(data, num + bytes2.Length + 1, numArray, 0, 1);
int length = (int)numArray[0] - (data[num + bytes2.Length + 12] == byte.MaxValue ? 6 : 5);
byte[] bytes3 = new byte[length];
Array.Copy(data, num + bytes2.Length + 13, bytes3, 0, length);
if (data[num + bytes2.Length + 12] == byte.MaxValue)
return Encoding.Unicode.GetString(bytes3);
return Encoding.Default.GetString(bytes3);
}
}
}

View file

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ArkData.Models
{
internal class SteamBan
{
public string SteamId { get; set; }
public bool CommunityBanned { get; set; }
public bool VACBanned { get; set; }
public int NumberOfVACBans { get; set; }
public int DaysSinceLastBan { get; set; }
public int NumberOfGameBans { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ArkData.Models
{
internal class SteamPlayerResponse<T>
{
public List<T> players { get; set; }
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ArkData.Models
{
internal class SteamProfile
{
public string steamid { get; set; }
public string personaname { get; set; }
public string profileurl { get; set; }
public string avatar { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ArkData.Models
{
internal class SteamResponse<T>
{
public SteamPlayerResponse<T> response { get; set; }
}
}

24
src/ArkData/PlayerData.cs Normal file
View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace ArkData
{
public class PlayerData
{
public string PlayerId { get; set; }
public string PlayerName { get; set; }
public long CharacterId { get; set; }
public string CharacterName { get; set; }
public bool Online { get; set; }
public string File { get; set; }
public string Filename { get; set; }
public DateTime FileCreated { get; set; }
public DateTime FileUpdated { get; set; }
public int? TribeId { get; set; }
public short Level { get; set; }
public virtual TribeData Tribe { get; set; }
public virtual List<TribeData> OwnedTribes { get; set; }
public DateTime LastPlatformUpdateUtc { get; set; }
}
}

View file

@ -0,0 +1,66 @@
using System;
using System.Text;
using System.IO;
using System.Threading.Tasks;
namespace ArkData
{
internal partial class Parser
{
private static ulong GetId(byte[] data)
{
byte[] bytes1 = Encoding.Default.GetBytes("PlayerDataID");
byte[] bytes2 = Encoding.Default.GetBytes("UInt64Property");
int offset = Extensions.LocateFirst(data, bytes1, 0);
int num = Extensions.LocateFirst(data, bytes2, offset);
return BitConverter.ToUInt64(data, num + bytes2.Length + 9);
}
private static string GetPlatformId(byte[] data)
{
byte[] bytes1 = Encoding.Default.GetBytes("UniqueNetIdRepl");
int num = Extensions.LocateFirst(data, bytes1, 0);
byte[] bytes2 = new byte[9];
Array.Copy(data, num + bytes1.Length, bytes2, 0, 9);
var length = BitConverter.ToUInt32(bytes2, 5) - 1;
byte[] bytes3 = new byte[length];
Array.Copy(data, num + bytes1.Length + bytes2.Length, bytes3, 0, length);
return Encoding.Default.GetString(bytes3);
}
public static PlayerData ParsePlayer(string fileName)
{
FileInfo fileInfo = new FileInfo(fileName);
if (!fileInfo.Exists)
return null;
byte[] data = File.ReadAllBytes(fileName);
var tribeId = Helpers.GetInt(data, "TribeId");
return new PlayerData()
{
//PlayerId = GetPlatformId(data),
PlayerId = Path.GetFileNameWithoutExtension(fileInfo.Name),
PlayerName = Helpers.GetString(data, "PlayerName"),
CharacterId = Convert.ToInt64(GetId(data)),
CharacterName = Helpers.GetString(data, "PlayerCharacterName"),
TribeId = tribeId > -1 ? tribeId : Helpers.GetInt(data, "TribeID"),
Level = (short)(1 + Convert.ToInt32(Helpers.GetUInt16(data, "CharacterStatusComponent_ExtraCharacterLevel"))),
File = fileName,
Filename = fileInfo.Name,
FileCreated = fileInfo.CreationTime,
FileUpdated = fileInfo.LastWriteTime
};
}
public static Task<PlayerData> ParsePlayerAsync(string fileName)
{
return Task.Run(() => ParsePlayer(fileName));
}
}
}

View file

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ArkData SDK")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("AuthiQ")]
[assembly: AssemblyProduct("ArkData SDK")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d2ee1483-021f-4900-bbe8-88338d1386f4")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.3.0")]
[assembly: AssemblyFileVersion("1.0.3.0")]

View file

@ -0,0 +1,69 @@
/*
* This file is part of SSQLib.
*
* SSQLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SSQLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SSQLib. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace SSQLib
{
internal class Packet
{
internal int RequestId = 0;
internal string Data = "";
internal Packet() { }
//Output the packet data as a byte array
internal byte[] outputAsBytes()
{
byte[] data_byte = null;
if (Data.Length > 0)
{
//Create a new packet based on the length of the request
data_byte = new byte[Data.Length + 5];
//Fill the first 4 bytes with 0xff
data_byte[0] = 0xff;
data_byte[1] = 0xff;
data_byte[2] = 0xff;
data_byte[3] = 0xff;
//Copy the data to the new request
Array.Copy(ASCIIEncoding.UTF8.GetBytes(Data), 0, data_byte, 4, Data.Length);
}
//Empty request to get challenge
else
{
data_byte = new byte[5];
//Fill the first 4 bytes with 0xff
data_byte[0] = 0xff;
data_byte[1] = 0xff;
data_byte[2] = 0xff;
data_byte[3] = 0xff;
data_byte[4] = 0x57;
}
return data_byte;
}
}
}

View file

@ -0,0 +1,170 @@
/*
* This file is part of SSQLib.
*
* SSQLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SSQLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SSQLib. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SSQLib
{
/// <summary>
/// Stores information about a player in the server
/// </summary>
public class PlayerInfo
{
private string name = "";
private int index = -9999;
private int kills = -9999;
private int deaths = -9999;
private int score = -9999;
private int ping = -9999;
private int rate = -9999;
private float time = 0.0f;
/// <summary>
/// Creates a new PlayerInfo object with default values
/// </summary>
public PlayerInfo() { }
/// <summary>
/// The name of the player
/// </summary>
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
}
}
/// <summary>
/// The amount of kills the player has (default: -9999)
/// </summary>
public int Kills
{
get {
return this.kills;
}
set {
this.kills = value;
}
}
/// <summary>
/// The amount of deaths the player has (default: -9999)
/// </summary>
public int Deaths
{
get
{
return this.deaths;
}
set
{
this.deaths = value;
}
}
/// <summary>
/// The score of the player (default: -9999)
/// </summary>
public int Score
{
get
{
return this.score;
}
set
{
this.score = value;
}
}
/// <summary>
/// The ping of the player (default: -9999)
/// </summary>
public int Ping
{
get
{
return this.ping;
}
set
{
this.ping = value;
}
}
/// <summary>
/// The rate(?) of the player (default: -9999)
/// </summary>
public int Rate
{
get
{
return this.rate;
}
set
{
this.rate = value;
}
}
/// <summary>
/// The index of the player in the server
/// </summary>
public int Index
{
get
{
return this.index;
}
set
{
this.index = value;
}
}
/// <summary>
/// The time the player has been in the server
/// </summary>
public float Time
{
get
{
return this.time;
}
set
{
this.time = value;
}
}
}
}

307
src/ArkData/SSQLib/SSQL.cs Normal file
View file

@ -0,0 +1,307 @@
/*
* This file is part of SSQLib.
*
* SSQLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SSQLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SSQLib. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections;
namespace SSQLib
{
/// <summary>
/// Used to retreive information from a Source server
/// </summary>
public class SSQL
{
/// <summary>
/// Generates an SSQL object with default values
/// </summary>
public SSQL()
{
}
/// <summary>
/// Pings the specified Source server to retreive information about it such as the server name, max players, current number of players, etc.
/// </summary>
/// <param name="ip_end">The IPEndPoint object containing the IP address and port of the server</param>
/// <returns>Information about the server or throws an SSQLServerException if it could not be retreived</returns>
public ServerInfo Server(IPEndPoint ip_end)
{
//Create a new empty server info object
ServerInfo info = new ServerInfo();
//Create an empty buffer
byte[] buf = null;
//Create a new packet and request
Packet requestPacket = new Packet();
requestPacket.Data = "TSource Engine Query";
try
{
//Attempt to get the server info
buf = SocketUtils.getInfo(ip_end, requestPacket);
}
catch(SSQLServerException e)
{
throw e;
}
//Start past the first four bytes which are all 0xff
int i = 4;
//Make sure the first character is an I
if (buf[i++] != 'I') return null;
//Make sure the returned version is above 0x07
if (buf[i++] < 0x07) return null;
StringBuilder srvName = new StringBuilder();
//Retrieve the server name
while (buf[i] != 0x00)
{
srvName.Append((char)buf[i]);
i++;
}
//Move to the next byte
i++;
//Set the name of the server
info.Name = srvName.ToString();
StringBuilder mapName = new StringBuilder();
//Retrieve the map name
while (buf[i] != 0x00)
{
mapName.Append((char)buf[i]);
i++;
}
//Move to the next byte
i++;
info.Map = mapName.ToString();
StringBuilder gameName = new StringBuilder();
//Get the short name for the game
while (buf[i] != 0x00)
{
gameName.Append((char)buf[i]);
i++;
}
//Move to the next byte
i++;
StringBuilder gameFriendly = new StringBuilder();
//Get the friendly game description
while (buf[i] != 0x00)
{
gameFriendly.Append((char)buf[i]);
i++;
}
//Move to the next byte
i++;
info.Game = gameFriendly.ToString() + " (" + gameName.ToString() + ")";
short appID = (short)System.BitConverter.ToInt16(buf, i);
//Skip the next 2 bytes
i += 2;
//Store the app id
info.AppID = appID.ToString();
//Get the number of players
info.PlayerCount = buf[i++].ToString();
//Get the number of max players
info.MaxPlayers = buf[i++].ToString();
//Get the number of bots
info.BotCount = buf[i++].ToString();
//Get the dedicated server type
if ((char)buf[i] == 'l')
info.Dedicated = ServerInfo.DedicatedType.LISTEN;
else if ((char)buf[i] == 'd')
info.Dedicated = ServerInfo.DedicatedType.DEDICATED;
else if ((char)buf[i] == 'p')
info.Dedicated = ServerInfo.DedicatedType.SOURCETV;
//Move to the next byte
i++;
//Get the OS type
if ((char)buf[i] == 'l')
info.OS = ServerInfo.OSType.LINUX;
else if ((char)buf[i] == 'w')
info.OS = ServerInfo.OSType.WINDOWS;
//Move to the next byte
i++;
//Check for password protection
if (buf[i++] == 0x01) info.Password = true;
//Check for VAC
if (buf[i++] == 0x01) info.VAC = true;
StringBuilder versionInfo = new StringBuilder();
//Get the game version
while (buf[i] != 0x00)
{
versionInfo.Append((char)buf[i]);
i++;
}
//Move to the next byte
i++;
//Set the version
info.Version = versionInfo.ToString();
return info;
}
/// <summary>
/// Retreives information about the players on a Source server
/// </summary>
/// <param name="ip_end">The IPEndPoint object storing the IP address and port of the server</param>
/// <returns>An ArrayList of PlayerInfo or throws an SSQLServerException if the server could not be reached</returns>
public ArrayList Players(IPEndPoint ip_end)
{
//Create a new array list to store the player array
ArrayList players = new ArrayList();
//Create a new buffer to receive packets
byte[] buf = null;
//Create a challenge packet
byte[] challenge = new byte[9];
challenge[0] = (byte)0xff;
challenge[1] = (byte)0xff;
challenge[2] = (byte)0xff;
challenge[3] = (byte)0xff;
challenge[4] = (byte)0x55;
challenge[5] = (byte)0x00;
challenge[6] = (byte)0x00;
challenge[7] = (byte)0x00;
challenge[8] = (byte)0x00;
try
{
//Attempt to get the challenge response
buf = SocketUtils.getInfo(ip_end, challenge);
}
catch (SSQLServerException e)
{
throw e;
}
int i = 4;
//Make sure the response starts with A
if (buf[i++] != 'A') return null;
//Create the new request with the challenge number
byte[] requestPlayer = new byte[9];
requestPlayer[0] = (byte)0xff;
requestPlayer[1] = (byte)0xff;
requestPlayer[2] = (byte)0xff;
requestPlayer[3] = (byte)0xff;
requestPlayer[4] = (byte)0x55;
requestPlayer[5] = buf[i++];
requestPlayer[6] = buf[i++];
requestPlayer[7] = buf[i++];
requestPlayer[8] = buf[i++];
try
{
//Attempt to get the players response
buf = SocketUtils.getInfo(ip_end, requestPlayer);
}
catch (SSQLServerException)
{
return null;
}
//Start past 0xffffffff
i = 4;
//Make sure the response starts with D
if (buf[i++] != 'D') return null;
//Get the amount of players
byte numPlayers = buf[i++];
//Loop through each player and extract their stats
for (int ii = 0; ii < numPlayers; ii++)
{
//Create a new player
PlayerInfo newPlayer = new PlayerInfo();
//Set the index of the player (Does not work in L4D2, always returns 0)
newPlayer.Index = buf[i++];
//Create a new player name
List<byte> playerName = new List<byte>();
//Loop through and store the player's name
while (buf[i] != 0x00)
{
playerName.Add(buf[i++]);
}
//Move past the end of the string
i++;
// Decode the player name
newPlayer.Name = Encoding.UTF8.GetString(playerName.ToArray());
//Get the kills and store them in the player info
newPlayer.Kills = (int)(buf[i] & 255) | ((buf[i + 1] & 255) << 8) | ((buf[i + 2] & 255) << 16) | ((buf[i + 3] & 255) << 24);
//Move to the next item
i += 5;
//Get the time connected as a float and store it in the player info
newPlayer.Time = (float)((int)(buf[i] & 255) | ((buf[i + 1] & 255) << 8));
//Move past the float
i += 3;
//Add the player to the list
players.Add(newPlayer);
}
//Return the list of players
return players;
}
}
}

View file

@ -0,0 +1,32 @@
/*
* This file is part of SSQLib.
*
* SSQLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SSQLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SSQLib. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SSQLib
{
class SSQLServerException : System.Exception
{
public SSQLServerException(string message) : base(message)
{
}
}
}

View file

@ -0,0 +1,317 @@
/*
* This file is part of SSQLib.
*
* SSQLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SSQLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SSQLib. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SSQLib
{
/// <summary>
/// Stores information about the Source server
/// </summary>
public class ServerInfo
{
private string name = "";
private string ip = "";
private string port = "";
private string game = "";
private string gameVersion = "";
private string appID = "";
private string map = "";
private string playerCount = "";
private string botCount = "";
private string maxPlayers = "";
private bool passworded = false;
private bool vac = false;
private ServerInfo.DedicatedType dedicated = ServerInfo.DedicatedType.NONE;
private ServerInfo.OSType os = ServerInfo.OSType.NONE;
/// <summary>
/// Creates a new object with default values
/// </summary>
public ServerInfo() { }
/// <summary>
/// The name of the server
/// </summary>
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
}
}
/// <summary>
/// The IP address of the server
/// </summary>
public string IP
{
get
{
return this.ip;
}
set
{
this.ip = value;
}
}
/// <summary>
/// The port the server uses
/// </summary>
public string Port
{
get
{
return this.port;
}
set
{
this.port = value;
}
}
/// <summary>
/// The game being played on the server (i.e. Team Fortress (tf))
/// </summary>
public string Game
{
get
{
return this.game;
}
set
{
this.game = value;
}
}
/// <summary>
/// The game version running on the server
/// </summary>
public string Version
{
get
{
return this.gameVersion;
}
set
{
this.gameVersion = value;
}
}
/// <summary>
/// The map currently being played on the server
/// </summary>
public string Map
{
get
{
return this.map;
}
set
{
this.map = value;
}
}
/// <summary>
/// The current player count on the server
/// </summary>
public string PlayerCount
{
get
{
return this.playerCount;
}
set
{
this.playerCount = value;
}
}
/// <summary>
/// The current bot count on the server
/// </summary>
public string BotCount
{
get
{
return this.botCount;
}
set
{
this.botCount = value;
}
}
/// <summary>
/// The max amount of players allowed on the server
/// </summary>
public string MaxPlayers
{
get
{
return this.maxPlayers;
}
set
{
this.maxPlayers = value;
}
}
/// <summary>
/// Stores whether the server is passworded or not
/// </summary>
public bool Password
{
get
{
return this.passworded;
}
set
{
this.passworded = value;
}
}
/// <summary>
/// Stores whether the server is VAC protected or not
/// </summary>
public bool VAC
{
get
{
return this.vac;
}
set
{
this.vac = value;
}
}
/// <summary>
/// Stores the app ID of the game used by the server
/// </summary>
public string AppID
{
get
{
return this.appID;
}
set
{
this.appID = value;
}
}
/// <summary>
/// Stores the type of server running (Listen, Dedicated, SourceTV)
/// </summary>
public ServerInfo.DedicatedType Dedicated
{
get
{
return this.dedicated;
}
set
{
this.dedicated = value;
}
}
/// <summary>
/// Stores the operating system of the server (Windows, Linux)
/// </summary>
public ServerInfo.OSType OS
{
get
{
return this.os;
}
set
{
this.os = value;
}
}
/// <summary>
/// Used to describe the type of server running
/// </summary>
public enum DedicatedType
{
/// <summary>
/// Default value
/// </summary>
NONE,
/// <summary>
/// Listen server (locally hosted)
/// </summary>
LISTEN,
/// <summary>
/// Dedicated server
/// </summary>
DEDICATED,
/// <summary>
/// SourceTV server
/// </summary>
SOURCETV
};
/// <summary>
/// Used to describe the operating system running on the server
/// </summary>
public enum OSType
{
/// <summary>
/// Default value
/// </summary>
NONE,
/// <summary>
/// Windows server
/// </summary>
WINDOWS,
/// <summary>
/// Linux server
/// </summary>
LINUX
};
}
}

View file

@ -0,0 +1,111 @@
/*
* This file is part of SSQLib.
*
* SSQLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SSQLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SSQLib. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace SSQLib
{
internal class SocketUtils
{
private SocketUtils() { }
internal static byte[] getInfo(IPEndPoint ipe, Packet packet)
{
//Create the socket
Socket srvSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//Save the max packet size
int packetSize = 12288;
//Send/Receive timeouts
srvSocket.SendTimeout = 3000;
srvSocket.ReceiveTimeout = 3000;
try
{
//Send the request to the server
srvSocket.SendTo(packet.outputAsBytes(), ipe);
}
catch (SocketException se)
{
throw new SSQLServerException("Could not send packet to server {" + se.Message + "}");
}
//Create a new receive buffer
byte[] rcvPacketInfo = new byte[packetSize];
EndPoint Remote = (EndPoint)ipe;
try
{
//Receive the data from the server
srvSocket.ReceiveFrom(rcvPacketInfo, ref Remote);
}
catch (SocketException se)
{
throw new SSQLServerException("Could not receive packet from server {" + se.Message + "}");
}
//Send the information back
return rcvPacketInfo;
}
internal static byte[] getInfo(IPEndPoint ipe, byte[] request)
{
//Create the socket
Socket srvSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//Save the max packet size
int packetSize = 12288;
//Send/Receive timeouts
srvSocket.SendTimeout = 3000;
srvSocket.ReceiveTimeout = 3000;
try
{
//Send the request to the server
srvSocket.SendTo(request, ipe);
}
catch (SocketException se)
{
throw new SSQLServerException("Could not send packet to server {" + se.Message + "}");
}
//Create a new receive buffer
byte[] rcvPacketInfo = new byte[packetSize];
EndPoint Remote = (EndPoint)ipe;
try
{
//Receive the data from the server
srvSocket.ReceiveFrom(rcvPacketInfo, ref Remote);
}
catch (SocketException se)
{
throw new SSQLServerException("Could not receive packet from server {" + se.Message + "}");
}
//Send the information back
return rcvPacketInfo;
}
}
}

23
src/ArkData/TribeData.cs Normal file
View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace ArkData
{
public class TribeData
{
public int Id { get; set; }
public string Name { get; set; }
public string File { get; set; }
public string Filename { get; set; }
public DateTime FileCreated { get; set; }
public DateTime FileUpdated { get; set; }
public int? OwnerId { get; set; }
public virtual ICollection<PlayerData> Players { get; set; }
public virtual PlayerData Owner { get; set; }
public TribeData()
{
this.Players = new HashSet<PlayerData>();
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace ArkData
{
internal partial class Parser
{
private static uint GetOwnerId(byte[] data)
{
byte[] bytes1 = Encoding.Default.GetBytes("OwnerPlayerDataID");
byte[] bytes2 = Encoding.Default.GetBytes("UInt32Property");
int offset = data.LocateFirst(bytes1, 0);
int num = data.LocateFirst(bytes2, offset);
return BitConverter.ToUInt32(data, num + bytes2.Length + 9);
}
public static TribeData ParseTribe(string fileName)
{
FileInfo fileInfo = new FileInfo(fileName);
if (!fileInfo.Exists)
return null;
byte[] data = File.ReadAllBytes(fileName);
var tribeId = Helpers.GetInt(data, "TribeId");
return new TribeData()
{
Id = tribeId > -1 ? tribeId : Helpers.GetInt(data, "TribeID"),
Name = Helpers.GetString(data, "TribeName"),
OwnerId = (int?)GetOwnerId(data),
File = fileName,
Filename = fileInfo.Name,
FileCreated = fileInfo.CreationTime,
FileUpdated = fileInfo.LastWriteTime
};
}
public static Task<TribeData> ParseTribeAsync(string fileName)
{
return Task.Run<TribeData>(() =>
{
return ParseTribe(fileName);
});
}
}
}