Initial source drop

This commit is contained in:
JustArchi 2015-10-25 06:16:50 +01:00
parent b36ab7220a
commit aa1c1962de
70 changed files with 59852 additions and 0 deletions

22
ArchiSteamFarm.sln Normal file
View file

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{35AF7887-08B9-40E8-A5EA-797D8B60B30C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>

View file

@ -0,0 +1,114 @@
using SteamKit2;
using SteamKit2.Internal;
namespace ArchiSteamFarm {
internal sealed class ArchiHandler : ClientMsgHandler {
internal sealed class PurchaseResponseCallback : CallbackMsg {
internal enum EPurchaseResult {
Unknown,
OK,
AlreadyOwned = 9,
InvalidKey = 14,
DuplicatedKey = 15,
OnCooldown = 53
}
internal EResult Result { get; private set; }
internal EPurchaseResult PurchaseResult { get; private set; }
internal int ErrorCode { get; private set; }
internal byte[] ReceiptInfo { get; private set; }
internal PurchaseResponseCallback(CMsgClientPurchaseResponse body) {
Result = (EResult) body.eresult;
ErrorCode = body.purchase_result_details;
ReceiptInfo = body.purchase_receipt_info;
if (Result == EResult.OK) {
PurchaseResult = EPurchaseResult.OK;
} else {
PurchaseResult = (EPurchaseResult) ErrorCode;
}
}
}
internal sealed class NotificationCallback : CallbackMsg {
internal enum ENotificationType {
Unknown = 0,
Trading = 1,
}
internal ENotificationType NotificationType { get; private set; }
internal NotificationCallback(CMsgClientUserNotifications.Notification body) {
uint notificationType = body.user_notification_type;
switch (notificationType) {
case 1:
NotificationType = (ENotificationType) notificationType;
break;
default:
NotificationType = ENotificationType.Unknown;
break;
}
}
}
internal void AcceptClanInvite(ulong clanID) {
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
request.Body.GroupID = clanID;
request.Body.AcceptInvite = true;
Client.Send(request);
}
internal void DeclineClanInvite(ulong clanID) {
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
request.Body.GroupID = clanID;
request.Body.AcceptInvite = false;
Client.Send(request);
}
internal void PlayGames(params ulong[] gameIDs) {
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
foreach (ulong gameID in gameIDs) {
if (gameID != 0) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
game_id = new GameID(gameID),
});
}
}
Client.Send(request);
}
// Will provide result in ClientPurchaseResponse, regardless if success or not
internal void RedeemKey(string key) {
var request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey);
request.Body.key = key;
Client.Send(request);
}
public sealed override void HandleMsg(IPacketMsg packetMsg) {
if (packetMsg != null) {
switch (packetMsg.MsgType) {
case EMsg.ClientPurchaseResponse:
HandlePurchaseResponse(packetMsg);
break;
case EMsg.ClientUserNotifications:
HandleUserNotifications(packetMsg);
break;
}
}
}
private void HandlePurchaseResponse(IPacketMsg packetMsg) {
var response = new ClientMsgProtobuf<CMsgClientPurchaseResponse>(packetMsg);
Client.PostCallback(new PurchaseResponseCallback(response.Body));
}
private void HandleUserNotifications(IPacketMsg packetMsg) {
var response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
foreach (var notification in response.Body.notifications) {
Client.PostCallback(new NotificationCallback(notification));
}
}
}
}

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{35AF7887-08B9-40E8-A5EA-797D8B60B30C}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ArchiSteamFarm</RootNamespace>
<AssemblyName>ArchiSteamFarm</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="HtmlAgilityPack, Version=1.4.9.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="protobuf-net, Version=2.0.0.668, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
<HintPath>..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SteamKit2, Version=1.6.5.29095, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SteamKit2.1.6.5\lib\net40\SteamKit2.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ArchiHandler.cs" />
<Compile Include="ArchiWebHandler.cs" />
<Compile Include="Bot.cs" />
<Compile Include="CardsFarmer.cs" />
<Compile Include="CMsgClientClanInviteAction.cs" />
<Compile Include="Logging.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SteamItem.cs" />
<Compile Include="SteamTradeOffer.cs" />
<Compile Include="Trading.cs" />
<Compile Include="Utilities.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,251 @@
using HtmlAgilityPack;
using SteamKit2;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal class ArchiWebHandler {
private const int Timeout = 1000 * 15; // In miliseconds
private readonly Bot Bot;
private readonly string ApiKey;
private ulong SteamID;
private string VanityURL;
private readonly Dictionary<string, string> SteamCookieDictionary = new Dictionary<string, string>();
// This is required because home_process request must be done on final URL
private string GetHomeProcess() {
if (!string.IsNullOrEmpty(VanityURL)) {
return "http://steamcommunity.com/id/" + VanityURL + "/home_process";
} else if (SteamID != 0) {
return "http://steamcommunity.com/profiles/" + SteamID + "/home_process";
}
return null;
}
internal ArchiWebHandler(Bot bot, string apiKey) {
Bot = bot;
ApiKey = apiKey;
}
internal void Init(SteamClient steamClient, string webAPIUserNonce, string vanityURL) {
if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(vanityURL)) {
return;
}
SteamID = steamClient.SteamID;
VanityURL = vanityURL;
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString(CultureInfo.InvariantCulture)));
// Generate an AES session key
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
// RSA encrypt it with the public key for the universe we're on
byte[] cryptedSessionKey = null;
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
cryptedSessionKey = rsa.Encrypt(sessionKey);
}
// Copy our login key
byte[] loginKey = new byte[20];
Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
// AES encrypt the loginkey with our session key
byte[] cryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
// Send the magic
KeyValue authResult;
Logging.LogGenericInfo(Bot.BotName, "Logging in to ISteamUserAuth...");
using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
iSteamUserAuth.Timeout = Timeout;
try {
authResult = iSteamUserAuth.AuthenticateUser(
steamid: SteamID,
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
method: WebRequestMethods.Http.Post,
secure: true
);
} catch (Exception e) {
Logging.LogGenericException(Bot.BotName, e);
steamClient.Disconnect(); // We may get 403 if we use the same webAPIUserNonce twice
return;
}
}
if (authResult == null) {
steamClient.Disconnect(); // Try again
return;
}
Logging.LogGenericInfo(Bot.BotName, "Success!");
string steamLogin = authResult["token"].AsString();
string steamLoginSecure = authResult["tokensecure"].AsString();
SteamCookieDictionary.Clear();
SteamCookieDictionary.Add("sessionid", sessionID);
SteamCookieDictionary.Add("steamLogin", steamLogin);
SteamCookieDictionary.Add("steamLoginSecure", steamLoginSecure);
SteamCookieDictionary.Add("birthtime", "-473356799"); // ( ͡° ͜ʖ ͡°)
Bot.Trading.CheckTrades();
}
internal List<SteamTradeOffer> GetTradeOffers() {
KeyValue response;
using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
// Timeout
iEconService.Timeout = Timeout;
try {
response = iEconService.GetTradeOffers(
key: ApiKey,
get_received_offers: 1,
active_only: 1,
secure: true
);
} catch (Exception e) {
Logging.LogGenericException(Bot.BotName, e);
return null;
}
}
if (response == null) {
return null;
}
List<SteamTradeOffer> result = new List<SteamTradeOffer>();
foreach (KeyValue trade in response["trade_offers_received"].Children) {
SteamTradeOffer tradeOffer = new SteamTradeOffer {
tradeofferid = trade["tradeofferid"].AsString(),
accountid_other = trade["accountid_other"].AsInteger(),
message = trade["message"].AsString(),
expiration_time = trade["expiration_time"].AsInteger(),
trade_offer_state = (SteamTradeOffer.ETradeOfferState) trade["trade_offer_state"].AsInteger(),
items_to_give = new List<SteamItem>(),
items_to_receive = new List<SteamItem>(),
is_our_offer = trade["is_our_offer"].AsBoolean(),
time_created = trade["time_created"].AsInteger(),
time_updated = trade["time_updated"].AsInteger(),
from_real_time_trade = trade["from_real_time_trade"].AsBoolean()
};
foreach (KeyValue item in trade["items_to_give"].Children) {
tradeOffer.items_to_give.Add(new SteamItem {
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
currencyid = item["currencyid"].AsString(),
classid = item["classid"].AsString(),
instanceid = item["instanceid"].AsString(),
amount = item["amount"].AsString(),
missing = item["missing"].AsBoolean()
});
}
foreach (KeyValue item in trade["items_to_receive"].Children) {
tradeOffer.items_to_receive.Add(new SteamItem {
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
currencyid = item["currencyid"].AsString(),
classid = item["classid"].AsString(),
instanceid = item["instanceid"].AsString(),
amount = item["amount"].AsString(),
missing = item["missing"].AsBoolean()
});
}
result.Add(tradeOffer);
}
return result;
}
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
if (tradeID == 0) {
return false;
}
string sessionID;
if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
return false;
}
string referer = "https://steamcommunity.com/tradeoffer/" + tradeID + "/";
string request = referer + "accept";
Dictionary<string, string> postData = new Dictionary<string, string>() {
{"sessionid", sessionID},
{"serverid", "1"},
{"tradeofferid", tradeID.ToString()}
};
return await Utilities.UrlPostRequest(request, postData, SteamCookieDictionary, referer).ConfigureAwait(false);
}
internal bool DeclineTradeOffer(ulong tradeID) {
if (tradeID == 0) {
return false;
}
KeyValue response;
using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
// Timeout
iEconService.Timeout = Timeout;
try {
response = iEconService.DeclineTradeOffer(
key: ApiKey,
tradeofferid: tradeID.ToString(),
method: WebRequestMethods.Http.Post,
secure: true
);
} catch (Exception e) {
Logging.LogGenericException(Bot.BotName, e);
return false;
}
}
return response != null; // Steam API doesn't respond with any error code, assume any response is a success
}
internal async Task LeaveClan(ulong clanID) {
if (clanID == 0) {
return;
}
string sessionID;
if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
return;
}
string request = GetHomeProcess();
Dictionary<string, string> postData = new Dictionary<string, string>() {
{"sessionID", sessionID},
{"action", "leaveGroup"},
{"groupId", clanID.ToString()}
};
await Utilities.UrlPostRequest(request, postData, SteamCookieDictionary).ConfigureAwait(false);
}
internal async Task<HtmlDocument> GetBadgePage(int page) {
HtmlDocument result = null;
if (SteamID != 0 && page != 0) {
result = await Utilities.UrlToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/badges?p=" + page, SteamCookieDictionary).ConfigureAwait(false);
}
return result;
}
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
HtmlDocument result = null;
if (SteamID != 0 && appID != 0) {
result = await Utilities.UrlToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/gamecards/" + appID, SteamCookieDictionary).ConfigureAwait(false);
}
return result;
}
}
}

336
ArchiSteamFarm/Bot.cs Normal file
View file

@ -0,0 +1,336 @@
using SteamKit2;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace ArchiSteamFarm {
internal class Bot {
private const byte CallbackSleep = 100; // In miliseconds
private readonly Dictionary<string, string> Config = new Dictionary<string, string>();
internal readonly string BotName;
private readonly string ConfigFile;
private readonly string SentryFile;
private readonly CardsFarmer CardsFarmer;
internal ulong BotID { get; private set; }
private string AuthCode, TwoFactorAuth;
internal ArchiHandler ArchiHandler { get; private set; }
internal ArchiWebHandler ArchiWebHandler { get; private set; }
internal CallbackManager CallbackManager { get; private set; }
internal SteamClient SteamClient { get; private set; }
internal SteamFriends SteamFriends { get; private set; }
internal SteamUser SteamUser { get; private set; }
internal Trading Trading { get; private set; }
// Config variables
private bool Enabled { get { return bool.Parse(Config["Enabled"]); } }
private string SteamLogin { get { return Config["SteamLogin"]; } }
private string SteamPassword { get { return Config["SteamPassword"]; } }
private string SteamNickname { get { return Config["SteamNickname"]; } }
private string SteamApiKey { get { return Config["SteamApiKey"]; } }
internal ulong SteamMasterID { get { return ulong.Parse(Config["SteamMasterID"]); } }
internal ulong SteamMasterClanID { get { return ulong.Parse(Config["SteamMasterClanID"]); } }
internal Bot (string botName) {
BotName = botName;
CardsFarmer = new CardsFarmer(this);
ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");
ReadConfig();
if (!Enabled) {
return;
}
Start();
}
private void ReadConfig() {
using (XmlReader reader = XmlReader.Create(ConfigFile)) {
while (reader.Read()) {
if (reader.NodeType != XmlNodeType.Element) {
continue;
}
string key = reader.Name;
if (string.IsNullOrEmpty(key)) {
continue;
}
string value = reader.GetAttribute("value");
if (string.IsNullOrEmpty(value)) {
continue;
}
Config.Add(key, value);
}
}
}
internal void Start() {
if (SteamClient != null) {
return;
}
SteamClient = new SteamClient();
ArchiHandler = new ArchiHandler();
SteamClient.AddHandler(ArchiHandler);
CallbackManager = new CallbackManager(SteamClient);
CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);
SteamFriends = SteamClient.GetHandler<SteamFriends>();
CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
SteamUser = SteamClient.GetHandler<SteamUser>();
CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
CallbackManager.Subscribe<ArchiHandler.NotificationCallback>(OnNotification);
CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);
ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
Trading = new Trading(this);
SteamClient.Connect();
Task.Run(() => HandleCallbacks());
}
internal void Stop() {
if (SteamClient == null) {
return;
}
SteamClient.Disconnect();
SteamClient = null;
CallbackManager = null;
}
internal void PlayGame(params ulong[] gameIDs) {
ArchiHandler.PlayGames(gameIDs);
}
private void HandleCallbacks() {
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
while (CallbackManager != null) {
CallbackManager.RunWaitCallbacks(timeSpan);
}
}
private void OnConnected(SteamClient.ConnectedCallback callback) {
if (callback == null) {
return;
}
if (callback.Result != EResult.OK) {
Logging.LogGenericError(BotName, "Unable to connect to Steam: " + callback.Result);
return;
}
Logging.LogGenericInfo(BotName, "Connected to Steam!");
byte[] sentryHash = null;
if (File.Exists(SentryFile)) {
byte[] sentryFileContent = File.ReadAllBytes(SentryFile);
sentryHash = CryptoHelper.SHAHash(sentryFileContent);
}
SteamUser.LogOn(new SteamUser.LogOnDetails {
Username = SteamLogin,
Password = SteamPassword,
AuthCode = AuthCode,
TwoFactorCode = TwoFactorAuth,
SentryFileHash = sentryHash
});
}
private void OnDisconnected(SteamClient.DisconnectedCallback callback) {
if (callback == null) {
return;
}
Logging.LogGenericWarning(BotName, "Disconnected from Steam, reconnecting...");
Thread.Sleep(TimeSpan.FromMilliseconds(CallbackSleep));
SteamClient.Connect();
}
private void OnFriendsList(SteamFriends.FriendsListCallback callback) {
if (callback == null) {
return;
}
foreach (var friend in callback.FriendList) {
if (friend.Relationship != EFriendRelationship.RequestRecipient) {
continue;
}
SteamID steamID = friend.SteamID;
switch (steamID.AccountType) {
case EAccountType.Clan:
//ArchiHandler.AcceptClanInvite(steamID);
break;
default:
if (steamID == SteamMasterID) {
SteamFriends.AddFriend(steamID);
} else {
SteamFriends.RemoveFriend(steamID);
}
break;
}
}
}
private void OnFriendMsg(SteamFriends.FriendMsgCallback callback) {
if (callback == null) {
return;
}
if (callback.EntryType != EChatEntryType.ChatMsg) {
return;
}
ulong steamID = callback.Sender;
if (steamID != SteamMasterID) {
return;
}
string message = callback.Message;
if (string.IsNullOrEmpty(message)) {
return;
}
if (message.Length == 17 && message[5] == '-' && message[11] == '-') {
ArchiHandler.RedeemKey(message);
}
}
private void OnAccountInfo(SteamUser.AccountInfoCallback callback) {
if (callback == null) {
return;
}
SteamFriends.SetPersonaState(EPersonaState.Online);
}
private void OnLoggedOff(SteamUser.LoggedOffCallback callback) {
if (callback == null) {
return;
}
Logging.LogGenericInfo(BotName, "Logged off of Steam: " + callback.Result);
}
private async void OnLoggedOn(SteamUser.LoggedOnCallback callback) {
if (callback == null) {
return;
}
if (callback.ClientSteamID != 0) {
BotID = callback.ClientSteamID;
}
EResult result = callback.Result;
switch (result) {
case EResult.AccountLogonDenied:
AuthCode = Program.GetSteamGuardCode(SteamLogin, false);
break;
case EResult.AccountLoginDeniedNeedTwoFactor:
TwoFactorAuth = Program.GetSteamGuardCode(SteamLogin, true);
break;
case EResult.OK:
Logging.LogGenericInfo(BotName, "Successfully logged on!");
SteamFriends.SetPersonaName(SteamNickname);
ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, callback.VanityURL);
ulong clanID = SteamMasterClanID;
if (clanID != 0) {
SteamFriends.JoinChat(clanID);
}
await CardsFarmer.StartFarming().ConfigureAwait(false);
break;
default:
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult + ", retrying...");
Stop();
Thread.Sleep(5000);
Start();
break;
}
}
private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) {
if (callback == null) {
return;
}
Logging.LogGenericInfo(BotName, "Updating sentryfile...");
int fileSize;
byte[] sentryHash;
using (FileStream fileStream = File.Open(SentryFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) {
fileStream.Seek(callback.Offset, SeekOrigin.Begin);
fileStream.Write(callback.Data, 0, callback.BytesToWrite);
fileSize = (int) fileStream.Length;
fileStream.Seek(0, SeekOrigin.Begin);
using (SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider()) {
sentryHash = sha.ComputeHash(fileStream);
}
}
// Inform the steam servers that we're accepting this sentry file
SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails {
JobID = callback.JobID,
FileName = callback.FileName,
BytesWritten = callback.BytesToWrite,
FileSize = fileSize,
Offset = callback.Offset,
Result = EResult.OK,
LastError = 0,
OneTimePassword = callback.OneTimePassword,
SentryFileHash = sentryHash,
});
}
private void OnNotification(ArchiHandler.NotificationCallback callback) {
if (callback == null) {
return;
}
switch (callback.NotificationType) {
case ArchiHandler.NotificationCallback.ENotificationType.Trading:
Trading.CheckTrades();
break;
}
}
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
if (callback == null) {
return;
}
var purchaseResult = callback.PurchaseResult;
SteamFriends.SendChatMessage(SteamMasterID, EChatEntryType.ChatMsg, "Status: " + purchaseResult);
if (purchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
await CardsFarmer.StartFarming().ConfigureAwait(false);
}
}
}
}

View file

@ -0,0 +1,47 @@
using SteamKit2;
using SteamKit2.Internal;
using System.IO;
namespace ArchiSteamFarm {
/// <summary>
/// Message used to Accept or Decline a group(clan) invite.
/// </summary>
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage, ISteamSerializable {
EMsg ISteamSerializableMessage.GetEMsg() {
return EMsg.ClientAcknowledgeClanInvite;
}
public CMsgClientClanInviteAction() {
}
/// <summary>
/// Group invited to.
/// </summary>
internal ulong GroupID = 0;
/// <summary>
/// To accept or decline the invite.
/// </summary>
internal bool AcceptInvite = true;
void ISteamSerializable.Serialize(Stream stream) {
try {
BinaryWriter binaryWriter = new BinaryWriter(stream);
binaryWriter.Write(GroupID);
binaryWriter.Write(AcceptInvite);
} catch {
throw new IOException();
}
}
void ISteamSerializable.Deserialize(Stream stream) {
try {
BinaryReader binaryReader = new BinaryReader(stream);
GroupID = binaryReader.ReadUInt64();
AcceptInvite = binaryReader.ReadBoolean();
} catch {
throw new IOException();
}
}
}
}

View file

@ -0,0 +1,109 @@
using HtmlAgilityPack;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal class CardsFarmer {
private readonly Bot Bot;
private bool NowFarming;
private readonly AutoResetEvent AutoResetEvent = new AutoResetEvent(false);
internal CardsFarmer(Bot bot) {
Bot = bot;
}
internal async Task StartFarming() {
// Find the number of badge pages
HtmlDocument badgesDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
if (badgesDocument == null) {
return;
}
var maxPages = 1;
HtmlNodeCollection badgesPagesNodeCollection = badgesDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
if (badgesPagesNodeCollection != null) {
maxPages = (byte) (badgesPagesNodeCollection.Count / 2 + 1); // Don't do this at home
}
// Find APPIDs we need to farm
List<uint> appIDs = new List<uint>();
for (var page = 1; page <= maxPages; page++) {
if (page > 1) { // Because we fetched page number 1 already
badgesDocument = await Bot.ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
if (badgesDocument == null) {
break;
}
}
HtmlNodeCollection badgesPageNodes = badgesDocument.DocumentNode.SelectNodes("//a[@class='btn_green_white_innerfade btn_small_thin']");
if (badgesPageNodes == null) {
break;
}
foreach (HtmlNode badgesPageNode in badgesPageNodes) {
string steamLink = badgesPageNode.GetAttributeValue("href", null);
if (steamLink == null) {
page = maxPages; // Break from outer loop
break;
}
uint appID = (uint) Utilities.OnlyNumbers(steamLink);
if (appID == 0) {
page = maxPages; // Break from outer loop
break;
}
appIDs.Add(appID);
}
}
// Start farming
while (appIDs.Count > 0) {
uint appID = appIDs[0];
if (await Farm(appID).ConfigureAwait(false)) {
appIDs.Remove(appID);
} else {
break;
}
}
}
private async Task<bool?> ShouldFarm(ulong appID) {
bool? result = null;
HtmlDocument gamePageDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
if (gamePageDocument != null) {
HtmlNode gamePageNode = gamePageDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (gamePageNode != null) {
result = !gamePageNode.InnerText.Contains("No card drops");
}
}
return result;
}
private async Task<bool> Farm(ulong appID) {
if (NowFarming) {
AutoResetEvent.Set();
Thread.Sleep(1000);
AutoResetEvent.Reset();
}
bool success = true;
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
while (keepFarming == null || keepFarming.Value) {
if (!NowFarming) {
NowFarming = true;
Bot.PlayGame(appID);
}
if (AutoResetEvent.WaitOne(1000 * 60 * 5)) {
success = false;
break;
}
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
}
Bot.PlayGame(0);
NowFarming = false;
return success;
}
}
}

38
ArchiSteamFarm/Logging.cs Normal file
View file

@ -0,0 +1,38 @@
using System;
using System.Runtime.CompilerServices;
namespace ArchiSteamFarm {
internal static class Logging {
private static void Log(string message) {
Console.WriteLine(DateTime.Now + " " + message);
}
internal static void LogGenericError(string botName, string message, [CallerMemberName] string previousMethodName = "") {
Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message);
}
internal static void LogGenericException(string botName, Exception exception, [CallerMemberName] string previousMethodName = "") {
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message);
}
internal static void LogGenericWarning(string botName, string message, [CallerMemberName] string previousMethodName = "") {
Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message);
}
internal static void LogGenericInfo(string botName, string message, [CallerMemberName] string previousMethodName = "") {
Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message);
}
internal static void LogGenericDebug(string botName, string message, [CallerMemberName] string previousMethodName = "") {
Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message);
}
internal static void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = "") {
LogGenericDebug("DEBUG", message, previousMethodName);
}
internal static void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = "") {
LogGenericError(nullObjectName + " is null!", previousMethodName);
}
}
}

58
ArchiSteamFarm/Program.cs Normal file
View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal static class Program {
internal const string ConfigDirectoryPath = "config";
private static readonly HashSet<Bot> Bots = new HashSet<Bot>();
internal static readonly object ConsoleLock = new object();
internal static void Exit(int exitCode = 0) {
ShutdownAllBots();
Environment.Exit(exitCode);
}
internal static string GetSteamGuardCode(string botLogin, bool twoFactorAuthentication) {
lock (ConsoleLock) {
if (twoFactorAuthentication) {
Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: ");
} else {
Console.Write("<" + botLogin + "> Please enter the auth code sent to your email : ");
}
return Console.ReadLine();
}
}
private static void ShutdownAllBots() {
lock (Bots) {
foreach (Bot bot in Bots) {
bot.Stop();
}
Bots.Clear();
}
}
private static void Main(string[] args) {
for (var i = 0; i < 4 && !Directory.Exists(ConfigDirectoryPath); i++) {
Directory.SetCurrentDirectory("..");
}
if (!Directory.Exists(ConfigDirectoryPath)) {
Logging.LogGenericError("Main", "Config directory doesn't exist!");
Console.ReadLine();
Exit(1);
}
lock (Bots) {
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectoryPath, "*.xml")) {
string botName = Path.GetFileNameWithoutExtension(configFile);
Bots.Add(new Bot(botName));
}
}
Thread.Sleep(Timeout.Infinite);
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
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("ArchiSteamFarm")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ArchiSteamFarm")]
[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(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("35af7887-08b9-40e8-a5ea-797d8b60b30c")]
// 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.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,13 @@
namespace ArchiSteamFarm {
internal sealed class SteamItem {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService
internal string appid { get; set; }
internal string contextid { get; set; }
internal string assetid { get; set; }
internal string currencyid { get; set; }
internal string classid { get; set; }
internal string instanceid { get; set; }
internal string amount { get; set; }
internal bool missing { get; set; }
}
}

View file

@ -0,0 +1,41 @@
using SteamKit2;
using System.Collections.Generic;
namespace ArchiSteamFarm {
internal sealed class SteamTradeOffer {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService
internal enum ETradeOfferState {
Unknown,
Invalid,
Active,
Accepted,
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled
}
internal string tradeofferid { get; set; }
internal int accountid_other { get; set; }
internal string message { get; set; }
internal int expiration_time { get; set; }
internal ETradeOfferState trade_offer_state { get; set; }
internal List<SteamItem> items_to_give { get; set; }
internal List<SteamItem> items_to_receive { get; set; }
internal bool is_our_offer { get; set; }
internal int time_created { get; set; }
internal int time_updated { get; set; }
internal bool from_real_time_trade { get; set; }
// Extra
internal ulong OtherSteamID64 {
get {
return new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
}
private set { }
}
}
}

74
ArchiSteamFarm/Trading.cs Normal file
View file

@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal sealed class Trading {
private Bot Bot;
private volatile byte ParsingTasks = 0;
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
internal Trading(Bot bot) {
Bot = bot;
}
internal void CheckTrades() {
if (ParsingTasks < 2) {
ParsingTasks++;
Task.Run(() => ParseActiveTrades());
}
}
private async Task ParseActiveTrades() {
await semaphore.WaitAsync().ConfigureAwait(false);
List<SteamTradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
if (tradeOffers != null) {
List<Task> tasks = new List<Task>();
foreach (SteamTradeOffer tradeOffer in tradeOffers) {
if (tradeOffer.trade_offer_state == SteamTradeOffer.ETradeOfferState.Active) {
Task task = Task.Run(async () => {
await ParseTrade(tradeOffer).ConfigureAwait(false);
});
tasks.Add(task);
}
}
await Task.WhenAll(tasks).ConfigureAwait(false);
}
ParsingTasks--;
semaphore.Release();
}
private async Task ParseTrade(SteamTradeOffer tradeOffer) {
if (tradeOffer == null) {
return;
}
ulong tradeID;
if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) {
return;
}
ulong steamID = tradeOffer.OtherSteamID64;
bool success = false;
bool tradeAccepted = false;
if (tradeOffer.items_to_give.Count == 0 || steamID == Bot.SteamMasterID) {
tradeAccepted = true;
success = await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
} else {
success = Bot.ArchiWebHandler.DeclineTradeOffer(tradeID);
}
if (!success) {
Logging.LogGenericWarning(Bot.BotName, "Response to trade " + tradeID + " failed!");
}
if (tradeAccepted && success) {
// Do whatever we want with success
}
}
}
}

117
ArchiSteamFarm/Utilities.cs Normal file
View file

@ -0,0 +1,117 @@
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class Utilities {
internal static ulong OnlyNumbers(string inputString) {
if (string.IsNullOrEmpty(inputString)) {
return 0;
}
string resultString;
try {
Regex regexObj = new Regex(@"[^\d]");
resultString = regexObj.Replace(inputString, "");
} catch (ArgumentException e) {
Logging.LogGenericException("Utilities", e);
return 0;
}
ulong result = ulong.Parse(resultString, CultureInfo.InvariantCulture);
return result;
}
internal static async Task<HttpResponseMessage> UrlToHttpResponse(string websiteAddress, Dictionary<string, string> cookieVariables) {
HttpResponseMessage result = null;
if (!string.IsNullOrEmpty(websiteAddress)) {
try {
using (HttpClientHandler clientHandler = new HttpClientHandler { UseCookies = false }) {
using (HttpClient client = new HttpClient(clientHandler)) {
client.Timeout = TimeSpan.FromSeconds(10);
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, websiteAddress);
if (cookieVariables != null) {
StringBuilder cookie = new StringBuilder();
foreach (KeyValuePair<string, string> cookieVariable in cookieVariables) {
cookie.Append(cookieVariable.Key + "=" + cookieVariable.Value + ";");
}
requestMessage.Headers.Add("Cookie", cookie.ToString());
}
HttpResponseMessage responseMessage = await client.SendAsync(requestMessage).ConfigureAwait(false);
if (responseMessage != null) {
responseMessage.EnsureSuccessStatusCode();
result = responseMessage;
}
}
}
} catch {
}
}
return result;
}
internal static async Task<HttpResponseMessage> UrlToHttpResponse(string websiteAddress) {
return await UrlToHttpResponse(websiteAddress, null).ConfigureAwait(false);
}
internal static async Task<HtmlDocument> UrlToHtmlDocument(string websiteAddress, Dictionary<string, string> cookieVariables) {
HtmlDocument result = null;
if (!string.IsNullOrEmpty(websiteAddress)) {
try {
HttpResponseMessage responseMessage = await UrlToHttpResponse(websiteAddress, cookieVariables).ConfigureAwait(false);
if (responseMessage != null) {
string source = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!string.IsNullOrEmpty(source)) {
source = WebUtility.HtmlDecode(source);
result = new HtmlDocument();
result.LoadHtml(source);
}
}
} catch {
}
}
return result;
}
internal static async Task<HtmlDocument> UrlToHtmlDocument(string websiteAddress) {
return await UrlToHtmlDocument(websiteAddress, null).ConfigureAwait(false);
}
internal static async Task<bool> UrlPostRequest(string request, Dictionary<string, string> postData, Dictionary<string, string> cookieVariables, string referer = null) {
bool result = false;
if (!string.IsNullOrEmpty(request)) {
try {
using (HttpClientHandler clientHandler = new HttpClientHandler { UseCookies = false }) {
using (HttpClient client = new HttpClient(clientHandler)) {
client.Timeout = TimeSpan.FromSeconds(15);
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, request);
requestMessage.Content = new FormUrlEncodedContent(postData);
if (cookieVariables != null && cookieVariables.Count > 0) {
StringBuilder cookie = new StringBuilder();
foreach (KeyValuePair<string, string> cookieVariable in cookieVariables) {
cookie.Append(cookieVariable.Key + "=" + cookieVariable.Value + ";");
}
requestMessage.Headers.Add("Cookie", cookie.ToString());
}
if (referer != null) {
requestMessage.Headers.Referrer = new Uri(referer);
}
HttpResponseMessage responseMessage = await client.SendAsync(requestMessage).ConfigureAwait(false);
if (responseMessage != null) {
result = responseMessage.IsSuccessStatusCode;
}
}
}
} catch {
}
}
return result;
}
}
}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- Every bot should have it's own unique .xml configuration file, this is example on which you can base on -->
<Enabled value="true"/> <!-- Master switch to turn account on and off -->
<!-- Steam -->
<SteamLogin value="Foo"/> <!-- This is your steam login, the one you use for logging in to steam -->
<SteamPassword value="Bar"/> <!-- This is your steam password, the one you use for logging in to steam -->
<SteamNickname value="ArchiSteamFarmer"/> <!-- This is your steam nickname, the one you want to use for bot. Can be anything up to 32 characters -->
<SteamApiKey value="FFFFFFFF"/> <!-- Get one at https://steamcommunity.com/dev/apikey -->
<SteamMasterID value="76561198006963719"/> <!-- This is steamID of the master, aka the "root" user being able to execute any command -->
<SteamMasterClanID value="0"/> <!-- If you want from the bot to join particular chat of given clan, set it here, otherwise leave at 0 -->
</configuration>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="HtmlAgilityPack" version="1.4.9" targetFramework="net45" />
<package id="protobuf-net" version="2.0.0.668" targetFramework="net45" />
<package id="SteamKit2" version="1.6.5" targetFramework="net45" />
</packages>

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,17 @@
----------------------------------------------------
---------- Html Agility Pack Nuget Readme ----------
----------------------------------------------------
----Silverlight 4 and Windows Phone 7.1+ projects-----
To use XPATH features: System.Xml.Xpath.dll from the Silverlight 4 SDK must be referenced.
This is normally found at
%ProgramFiles(x86)%\Microsoft SDKs\Microsoft SDKs\Silverlight\v4.0\Libraries\Client
or
%ProgramFiles%\Microsoft SDKs\Microsoft SDKs\Silverlight\v4.0\Libraries\Client
----Silverlight 5 projects-----
To use XPATH features: System.Xml.Xpath.dll from the Silverlight 5 SDK must be referenced.
This is normally found at
%ProgramFiles(x86)%\Microsoft SDKs\Microsoft SDKs\Silverlight\v5.0\Libraries\Client
or
%ProgramFiles%\Microsoft SDKs\Microsoft SDKs\Silverlight\v5.0\Libraries\Client

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

339
packages/SteamKit2.1.6.5/readme.txt vendored Normal file
View file

@ -0,0 +1,339 @@
------------------------------------------------------------------------------
v 1.6.5 Oct 17, 2015
------------------------------------------------------------------------------
* Added inventory service unified protobufs.
* Added the ability to specify the client's prefered Cell ID in `LogOnDetails.CellID`. (pr #148)
* `KeyValue` objects can now be serialized (both text and binary) to streams with `SaveToStream`.
* Fixed an issue with `CDNClient` session initialization involving sessionid values.
* Added setter for `KeyValue`'s indexer operator.
* Added `ELeaderboardDisplayType` and various leaderboard retrieval functions to `SteamUserStats`. (pr #153)
* Implemented machine id support for logon for when the Steam servers inevitably require it. (pr #152)
* Fixed case where logging on with a different account could lead to an anonymous logon instead. (bug #160)
* `SteamFriends.SetPersonaName` now supports `JobID`s and has a new associated callback: `PersonaChangeCallback`
* Updated game-related GC messages and protobufs.
------------------------------------------------------------------------------
v 1.6.4 Aug 03, 2015
------------------------------------------------------------------------------
* Added smarter server selection logic.
* Added ability to load initial server list from Steam Directory Web API. See `SteamDirectory.Initialize`.
* Added ability to persist internal server list. See Sample 7 for details.
* Added `SteamFriends.InviteUserToChat`.
* Added support in `SteamUser` for passwordless login with a login key.
* Added `NumChatMembers`, `ChatRoomName` and `ChatMembers` to `ChatEnterCallback`.
* Added new API for callback subscriptions, `CallbackManager.Subscribe`.
* Added `SteamApps.RequestFreeLicense` to request Free On-Demand licences.
* Exposed `ClientOSType` and `ClientLanguage` when logging in as a specific or as an anonymous user.
* Fixed `KeyValue` binary deserialization returning a dummy parent node containing the actually deserialized `KeyValue`. You must change to the new `Try`-prefixed methods to adopt the fixed behavior.
* Updated Steam enums and protobufs.
* Updated game-related GC messages and protobufs.
DEPRECATIONS
* `ICallbackMsg.IsType<>` and `ICallbackMsg.Handle<>` are deprecated and will be removed soon in a future version of SteamKit. Please use `CallbackManager.Subscribe` instead.
* `Callback<T>` is deprecated and will be removed in a future version of SteamKit. Please use `CallbackManager.Subscribe` instead.
* `KeyValue.ReadAsBinary` and `KeyValue.LoadAsBinary` are deprecated and will be removed in a future version of SteamKit. Use the `Try`-prefixed methods as outlined above.
------------------------------------------------------------------------------
v 1.6.3 Jun 20, 2015
------------------------------------------------------------------------------
* Added support for parsing older representations of Steam3 Steam IDs such as those from Counter-Strike: Global Offensive, i.e. `[M:1:123(456)]`.
* Steam IDs parsed from Steam3 string representations will now have the correct instance ID set.
* KeyValues can now be serialized to binary, however all values will be serialized as the string type.
* Improved reliability of TCP connections to the CM and UFS servers.
* Added `UserInitiated` property to `SteamClient.DisconnectedCallback` and `UFSClient.DisconnectedCallback` to indicate whether a disconnect was caused by the user, or by another source (Steam servers, bad network connection).
* Updated Steam protobufs.
* Updated game-related GC messages and protobufs.
------------------------------------------------------------------------------
v 1.6.2 Dec 16, 2014
------------------------------------------------------------------------------
* Fixed a crash when receiving a `ServiceMethod` message.
* Fixed `ServiceMethodCallback.RpcName` having a leading '.' character (e.g. '.MethodName' instead of 'MethodName).
* Fixed web responses in `CDNClient` not being closed, which could lead to running out of system resources.
* Added error handling for `ClientMsgHandler`. Any unhandled exceptions will be logged to `DebugLog` and trigger `SteamClient` to disconnect.
* Updated `EMsg` list.
* Updated Steam protobufs.
* Updated game-related GC messages and protobufs.
------------------------------------------------------------------------------
v 1.6.1 Nov 30, 2014
------------------------------------------------------------------------------
* Added support for VZip when decompressing depot chunks.
* Improved thread safety and error handling inside `TcpConnection`.
* Added `DownloadDepotChunk` overload for consumers who insist on connecting to particular CDNs.
* Updated `EResult` with the new field `NotModified`.
* Updated `EMsg` list.
* Updated `EOSType`.
* The short names for Windows versions (e.g. `Win8` instead of `Windows8`) are preferred.
* Addded `MacOS1010` for OS X 10.10 'Yosemite'
* Removed various long-obsolete values from enums where the value was renamed.
* Removed `EUniverse.RC`.
* Updated game related GC messages and protobufs.
------------------------------------------------------------------------------
v 1.6.0 Oct 11, 2014
------------------------------------------------------------------------------
* Updated EOSType for newer Linux and Windows versions.
* A LoggedOnCallback with EResult.NoConnection is now posted when attempting to logon without being
connected to the remote Steam server.
* Fixed anonymous gameserver logon.
* CDNClient.Server's constructor now accepts a DnsEndPoint.
* Updated EResult with the following new fields: AccountLogonDeniedNeedTwoFactorCode, ItemDeleted,
AccountLoginDeniedThrottle, TwoFactorCodeMismatch
* Added public utility class for working with DateTime and unix epochs: DateUtils
* Added GetSingleFileInfo, ShareFile and related callbacks for dealing with Steam cloud files with the
SteamCloud handler.
* Fixed a potential crash when failing to properly deserialize network messages.
* Updated EMsg list.
* Refactored the internals of tcp connections to Steam servers to be more resiliant and threadsafe.
* CallbackMsg.Handle will now return a boolean indiciating that the passed in callback matches the
generic type parameter.
* Added support for logging into accounts with two-factor auth enabled. See the
SteamUser.LogOnDetails.TwoFactorCode field.
* Updated the bootstrap list of Steam CM servers that SteamKit will initially attempt to connect to.
* Added SteamFriends.FriendMsgEchoCallback for echoed messages sent to other logged in client
instances.
* Updated game related GC messages and protobufs.
BREAKING CHANGES
* JobCallback API has been merged with Callback. For help with transitioning code, please see the following
wiki notes: https://github.com/SteamRE/SteamKit/wiki/JobCallback-Transition.
* UFSClient.UploadFileResponseCallback.JobID has been renamed to RemoteJobID in order to not conflict with
CallbackMsg's new JobID member.
* UFSClient.UploadDetails.JobID has been renamed to RemoteJobID.
* CDNClient has been refactored to support multiple authdepot calls for a single instance of the client
and to support CDN servers.
* The following EResult fields have been renamed:
PSNAccountNotLinked -> ExternalAccountUnlinked
InvalidPSNTicket -> PSNTicketInvalid
PSNAccountAlreadyLinked -> ExternalAccountAlreadyLinked
------------------------------------------------------------------------------
v 1.5.1 Mar 15, 2014
------------------------------------------------------------------------------
* Added a parameterless public constructor to DepotManifest.ChunkData to support serialization.
* SteamWorkshop.RequestPublishedFileDetails has been obsoleted and is no longer supported. This functionality will be
dropped in a future SteamKit release. See the the PublishedFile WebAPI service for a functional replacement.
* Added the request and response messages for the PublishedFile service.
* Fixed an unhandled exception when requesting metadata-only PICS product info.
* Exposed the following additional fields in the LoggedOnCallback: VanityURL, NumLoginFailuresToMigrate, NumDisconnectsToMigrate.
* Exposed the HTTP url details for PICS product info, see: PICSProductInfoCallback.PICSProductInfo.HttpUri and UseHttp.
* Added EEconTradeResponse.InitiatorPasswordResetProbation and InitiatorNewDeviceCooldown.
* Fixed SteamGameServer.LogOn and LogOnAnonymous sending the wrong message.
* Added support for token authentication for game server logon.
* Added the request and response messages for the GameServers service.
* Added the ability to specify server type for game servers, see: SteamGameServer.SendStatus.
* Exposed a few more fields on TradeResultCallback: NumDaysSteamGuardRequired, NumDaysNewDeviceCooldown,
DefaultNumDaysPasswordResetProbation, NumDaysPasswordResetProbation.
* Fixed being unable to download depot manifests.
* Added SteamID.SetFromSteam3String.
* Obsoleted SteamApps.SendGuestPass. This functionality will be dropped in a future SteamKit release.
* Updated EResult with the following new fields: UnexpectedError, Disabled, InvalidCEGSubmission, RestrictedDevice.
* Updated EMsg list.
* Updated game related GC messages.
BREAKING CHANGES
* Fixed ServiceMethodResponse.RpcName containing a leading '.'.
------------------------------------------------------------------------------
v 1.5.0 Oct 26, 2013
------------------------------------------------------------------------------
* Added DebugLog.ClearListeners().
* Added WebAPI.AsyncInterface, a .NET TPL'd version of WebAPI.Interface.
* Added SteamClient.ServerListCallback.
* Added SteamUser.WebAPIUserNonceCallback, and a method to request it: SteamUser.RequestWebAPIUserNonce().
* Added SteamUser.MarketingMessageCallback.
* Added a new member to CMClient: CellID. This is the Steam server's recommended CellID.
* Added the ability to specify AccountID in SteamUser.LogOnDetails.
* Added a helper API to SteamUnifiedMessages for service messages.
* Fixed issue where CallbackManager was not triggering for JobCallback<T>.
* Fixed unhandled protobuf-net exception when (de)serializing messages with enums that are out of date.
* Fixed a bug where all WebAPI.Interface requests would instantly timeout.
* Fixed Manifest.HashFileName and Manifest.HashContent being swapped.
* Updated Emsg list.
* Updated game related GC messages.
* Updated the following enums: EResult, EChatEntryType, EAccountFlags, EClanPermission, EFriendFlags, EOSType, EServerType,
EBillingType, EChatMemberStateChange, EDepotFileFlag, EEconTradeResponse.
* The following members of EChatRoomEnterResponse have been obsoleted: NoRankingDataLobby, NoRankingDataUser, RankOutOfRange.
* EOSType.Win7 has been obsoleted and renamed to EOSType.Windows7.
* EEconTradeResponse.InitiatorAlreadyTrading has been obsoleted and renamed to EEconTradeResponse.AlreadyTrading.
* EEconTradeResponse.Error has been obsoleted and renamed to EEconTradeResponse.AlreadyHasTradeRequest.
* EEconTradeResponse.Timeout has been obsoleted and renamed to EEconTradeResponse.NoResponse.
* EChatEntryType.Emote has been obsoleted. Emotes are no longer supported by Steam.
* SteamFriends.ProfileInfoCallback.RecentPlaytime has been obsoleted. This data is no longer sent by the Steam servers.
* Updated to latest protobuf-net.
BREAKING CHANGES
* SteamUser.LoggedOnCallback.Steam2Ticket is now exposed as a byte array, rather than a Steam2Ticket object.
* The SteamKit2.Blob namespace and all related classes have been removed.
* Support for Steam2 servers and the various classes within SteamKit have been removed.
* CDNClient has been heavily refactored to be more developer friendly.
* All DateTimes in callbacks are now DateTimeKind.Utc.
------------------------------------------------------------------------------
v 1.4.1 Jul 15, 2013
------------------------------------------------------------------------------
* Added the ability to manipulate UFS (Steam cloud) files with UFSClient.
* Added SteamScreenshots handler for interacting with user screenshots.
* Added an optional parameter to SteamID.Render() to render SteamIDs to their Steam3 representations.
* Added the ability to specify the timeout of WebAPI requests with Interface.Timeout.
* The RSACrypto and KeyDictionary utility classes are now accessible to consumers.
* Updated EMsg list.
* Updated game related GC messages.
------------------------------------------------------------------------------
v 1.4.0 Jun 08, 2013
------------------------------------------------------------------------------
* KeyValues now correctly writes out strings in UTF8.
* Fixed an exception that could occur with an invalid string passed to SteamID's constructor.
* Added SteamFriends.ClanStateCallback.
* Added EPersonaStateFlag. This value is now exposed in SteamFriends.PersonaStateCallback.
* Added MsgClientCreateChat and MsgClientCreateChatResponse messages.
* Added GlobalID base class for globally unique values (such as JobIDs, UGCHandles) in Steam.
* Updated EMsg list.
* Updated game related GC messages.
* Added initial support for the Steam Cloud file system with UFSClient. This feature should be considered unstable and may
have breaking changes in the future.
BREAKING CHANGES
* STATIC_CALLBACKS builds of SteamKit have now been completely removed.
* Message classes for unified messages have moved namespaces from SteamKit2.Steamworks to SteamKit2.Unified.Internal.
------------------------------------------------------------------------------
v 1.3.1 Mar 10, 2013
------------------------------------------------------------------------------
* Fixed issue where the avatar hash of a clan was always null.
* Introduced better handling of networking related cryptographic exceptions.
* Updated EMsg list.
* Exposed SteamClient.JobCallback<T> for external consumers.
* STATIC_CALLBACK builds of SteamKit and related code has been obsoleted and will be removed in the next version.
* Implemented GameID.ToString().
* Implemented game pass sending and recieving with SteamApps.SendGuestPass(), SteamApps.GuestPassListCallback, and
SteamApps.SendGuestPassCallback.
* Implemented requesting Steam community profile info with SteamFriends.RequestProfileInfo(), and SteamFriends.ProfileInfoCallback
* CMClient now exposes a ConnectionTimeout field to control the timeout when connecting to Steam. The default timeout is 5 seconds.
* Updated the internal list of CM servers to help alleviate some issues with connecting to dead servers.
* Implemented SteamClient.CMListCallback to retrieve the current list of CM servers.
* Implemented initial support for unified messages through the SteamUnifiedMessages handler.
BREAKING CHANGES
* CMClient.Connect has been refactored significantly. It is no longer possible to use unencrypted connections. The Connect function
now accepts an IPEndPoint to allow consumers to specify which Steam server they wish to connect to. Along with this,
CMClient.Servers is now exposed as a collection of IPEndPoints, instead of IPAddresses.
* SteamApps.PackageInfoCallback now exposes the immediate child KeyValue for the data, to be more consistent with
SteamApps.AppInfoCallback.
------------------------------------------------------------------------------
v 1.3.0 Jan 16, 2013
------------------------------------------------------------------------------
* Fixed case where friend and chat messages were incorrectly trimming the last character.
* Steam2 ServerClient now exposes a IsConnected property.
* Steam2 ContentServerClient can now optionally not perform a server handshake when opening a storage session.
* Added various enums: EClanPermission, EMarketingMessageFlags, ENewsUpdateType, ESystemIMType, EChatFlags,
ERemoteStoragePlatform, EDRMBlobDownloadType, EDRMBlobDownloadErrorDetail, EClientStat, EClientStatAggregateMethod,
ELeaderboardDataRequest, ELeaderboardSortMethod, ELeaderboardUploadScoreMethod, and EChatPermission.
* Fixed case where SteamKit was throwing an unhandled exception during Steam3 tcp connection teardown.
* Added PICS support to the SteamApps handler: PICSGetAccessTokens, PICSGetChangesSince, and PICSGetProductInfo.
* Added anonymous download support to CDNClient.
* Updated the following enums: EMsg, EUniverse, EChatEntryType, EPersonaState, EFriendRelationship, EFriendFlags,
EClientPersonaStateFlag, ELicenseFlags, ELicenseType, EPaymentMethod, EIntroducerRouting, EClanRank, EClanRelationship,
EAppInfoSection, EContentDownloadSourceType, EOSType, EServerType, ECurrencyCode, EDepotFileFlag, EEconTradeResponse,
ESystemIMType, ERemoteStoragePlatform, and EResult.
* Exposed the following properties in SteamUser.LoggedOnCallback: CellIDPingThreshold, UsePICS, WebAPIUserNonce, and
IPCountryCode.
* Fixed case where SteamKit was incorrectly handling certain logoff messages during Steam server unavailability.
* Fixed potential crash in Steam2 ContentServerClient when opening a storage session.
* Updated to latest protobuf-net.
BREAKING CHANGES
* DepotManifest.ChunkData.CRC is now named DepotManifest.ChunkData.Checksum.
------------------------------------------------------------------------------
v 1.2.2 Nov 11, 2012
------------------------------------------------------------------------------
* Fixed critical issue that occured while serializing protobuf messages.
------------------------------------------------------------------------------
v 1.2.1 Nov 11, 2012
------------------------------------------------------------------------------
* Added EPersonaState.LookingToTrade and EPersonaState.LookingToPlay.
* Added SteamFriends.UnbanChatMember.
* Removed GeneralDSClient.GetAuthServerList as Steam2 auth servers no longer exist.
* Removed dependency on Classless.Hasher.
* Updated to latest protobuf-net.
------------------------------------------------------------------------------
v 1.2.0 Nov 04, 2012
------------------------------------------------------------------------------
* Fixed issue where LoginKeyCallback was being passed incorrect data.
* Fixed ClientGCMsg PacketMessage constructor.
* WebAPI list and array parameters are now accepted and flattened to x[n]=y format.
* Fixed KeyValue issue when multiple duplicate children exist.
* Updated protobuf definitions for internal message classes to their latest definitions.
* Updated EMsgs.
* Fixed critical MsgMulti handling.
* Added EEconTradeResponse.
* Added SteamTrading client message handler.
* Modified Steam3 TCP socket shutdown to play well with Mono.
* Modified CMClient.Connect method to be properly async.
* Implemented friend blocking/unblocking with SteamFriends.IgnoreFriend and SteamFriends.IgnoreFriendCallback.
* Fixed gameserver logon.
* Local user is now given the persona name [unassigned] before SteamUser.AccountInfoCallback comes in.
* Updated SteamKit2's bootstrap CM list, this should reduce how often SK2 will connect to an offline/dead server.
* Steam2 ServerClient's now expose a ConnectionTimeout member.
BREAKING CHANGES
* Dota GC EMsgs are now longer located in SteamKit2.GC.Dota.EGCMsg, they are now in SteamKit2.Gc.Dota.Internal.EDOTAGCMsg.
* Base GC EMsgs are now longer located in SyteamKit2.GC.EGCMsgBase, they are now in multiple enums in the SteamKit2.GC.Internal namespace:
EGCBaseMsg, EGCSystemMsg, EGCSharedMsg, ESOMsg, EGCItemMsg
* SteamApps.AppInfoCallback now exposes the immediate child KeyValue for every Section, instead of an empty root parent.
------------------------------------------------------------------------------
v 1.1.0 May 14, 2012
------------------------------------------------------------------------------
* Added SteamWorkshop for enumerating and requesting details of published workshop files.
* Large overhaul of SteamGameCoordinator to support the sending and receiving of GC messages.
* Added SteamFriends ChatInviteCallback.
* Added SteamFriends KickChatMember and BanChatMember.
* Fixed invalid handling of PackageInfoCallback response.
* Updated protobuf definitions for internal message classes to their latest definitions.
BREAKING CHANGES
* Consumers of SteamClient.JobCallback<T> will have to change their handler functions to take a "JobID" parameter instead of a "ulong".
These are functionally equivalent, and JobIDs can be implicitly casted to and from ulongs.
------------------------------------------------------------------------------
v 1.0.0 Feb 26, 2012
------------------------------------------------------------------------------
* Initial release.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.