using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
namespace Prospect.Server.Steam
{
public class AppTicket
{
private AppTicket()
{
}
///
/// Full appticket, with a GC token and session header.
///
public byte[] AuthTicket { get; set; }
public ulong GcToken { get; set; }
public DateTimeOffset TokenGenerated { get; set; }
private IPAddress SessionExternalIp { get; set; }
///
/// Time the client has been connected to Steam in ms.
///
public uint ClientConnectionTime { get; set; }
///
/// How many servers the client has connected to.
///
public uint ClientConnectionCount { get; set; }
public uint Version { get; set; }
public ulong SteamId { get; set; }
public uint AppId { get; set; }
public IPAddress OwnershipTicketExternalIp { get; set; }
public IPAddress OwnershipTicketInternalIp { get; set; }
public uint OwnershipFlags { get; set; }
public DateTimeOffset OwnershipTicketGenerated { get; set; }
public DateTimeOffset OwnershipTicketExpires { get; set; }
public List Licenses { get; set; }
public List Dlc { get; set; }
public byte[] Signature { get; set; }
public bool IsExpired { get; set; }
public bool HasValidSignature { get; set; }
public bool IsValid { get; set; }
public static AppTicket Parse(string ticketHex)
{
var result = new AppTicket();
var ticketBytes = Convert.FromHexString(ticketHex);
using (var stream = new MemoryStream(ticketBytes))
using (var ticketReader = new BinaryReader(stream))
{
// AuthTicket
var initialLength = ticketReader.ReadUInt32();
if (initialLength == 20)
{
result.AuthTicket = ticketBytes.AsSpan(0, 52).ToArray();
result.GcToken = ticketReader.ReadUInt64();
ticketReader.BaseStream.Position += 8;
result.TokenGenerated = DateTimeOffset.FromUnixTimeSeconds(ticketReader.ReadUInt32());
if (ticketReader.ReadUInt32() != 24)
{
return null;
}
ticketReader.BaseStream.Position += 8;
result.SessionExternalIp = new IPAddress(BitConverter.GetBytes(ticketReader.ReadUInt32()));
ticketReader.BaseStream.Position += 4;
result.ClientConnectionTime = ticketReader.ReadUInt32();
result.ClientConnectionCount = ticketReader.ReadUInt32();
if (ticketReader.ReadUInt32() + ticketReader.BaseStream.Position != ticketReader.BaseStream.Length)
{
return null;
}
}
else
{
ticketReader.BaseStream.Position -= 4;
}
// Ownership ticket
var ownershipTicketOffset = ticketReader.BaseStream.Position;
var ownershipTicketLength = ticketReader.ReadUInt32();
if (ownershipTicketOffset + ownershipTicketLength != ticketReader.BaseStream.Length &&
ownershipTicketOffset + ownershipTicketLength + 128 != ticketReader.BaseStream.Length)
{
return null;
}
result.Version = ticketReader.ReadUInt32();
result.SteamId = ticketReader.ReadUInt64();
result.AppId = ticketReader.ReadUInt32();
result.OwnershipTicketExternalIp = new IPAddress(BitConverter.GetBytes(ticketReader.ReadUInt32()));
result.OwnershipTicketInternalIp = new IPAddress(BitConverter.GetBytes(ticketReader.ReadUInt32()));
result.OwnershipFlags = ticketReader.ReadUInt32();
result.OwnershipTicketGenerated = DateTimeOffset.FromUnixTimeSeconds(ticketReader.ReadUInt32());
result.OwnershipTicketExpires = DateTimeOffset.FromUnixTimeSeconds(ticketReader.ReadUInt32());
result.Licenses = new List();
var licenseCount = ticketReader.ReadUInt16();
for (var i = 0; i < licenseCount; i++)
{
result.Licenses.Add(ticketReader.ReadUInt32());
}
result.Dlc = new List();
var dlcCount = ticketReader.ReadUInt16();
for (var i = 0; i < dlcCount; i++)
{
var dlc = new AppDlc();
dlc.AppId = ticketReader.ReadUInt32();
dlc.Licenses = new List();
var dlcLicenseCount = ticketReader.ReadUInt16();
for (var j = 0; j < dlcLicenseCount; j++)
{
dlc.Licenses.Add(ticketReader.ReadUInt32());
}
result.Dlc.Add(dlc);
}
ticketReader.ReadUInt16();
if (ticketReader.BaseStream.Position + 128 == ticketReader.BaseStream.Length)
{
result.Signature = ticketBytes.AsSpan((int)ticketReader.BaseStream.Position, 128).ToArray();
}
var date = DateTimeOffset.UtcNow;
result.IsExpired = result.OwnershipTicketExpires < date;
result.HasValidSignature = result.Signature != null && SteamCrypto.VerifySignature(ticketBytes.AsSpan((int)ownershipTicketOffset, (int)ownershipTicketLength).ToArray(), result.Signature);
result.IsValid = !result.IsExpired && (result.Signature == null || result.HasValidSignature);
}
return result;
}
}
}