Skip to content

Commit

Permalink
Fix Steam October 2023 breaking change; 3.0.0 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
SeRi0uS007 committed Oct 30, 2023
1 parent 6bf29a7 commit 51b2be5
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 119 deletions.
4 changes: 2 additions & 2 deletions SteamAccountDataFetcher/SteamAccountDataFetcher.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<Authors>Andrii Lavrenko</Authors>
<Description>Utility for collecting Steam account information</Description>
<Copyright>Andrii Lavrenko</Copyright>
<Version>2.0.0</Version>
<Version>3.0.0</Version>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SteamKit2" Version="2.4.1" />
<PackageReference Include="SteamKit2" Version="2.5.0-Beta.2" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions SteamAccountDataFetcher/SteamDataClient/AccountInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class PackageInfo
public string Username { get; set; } = string.Empty;
public ulong SteamId { get; set; } = 0;
public List<PackageInfo> Packages { get; set; } = new();
public bool IsLocked { get; set; }
public bool IsLimited { get; set; }
public bool IsBanned { get; set; }
public string ApiKey { get; set; } = string.Empty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using SteamKit2;
using SteamKit2.Authentication;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace SteamAccountDataFetcher.SteamDataClient;

internal class SteamTwoFactorGenerator
public class AutoTwoFactorAuthenticator: IAuthenticator
{
private const string STEAM_TWO_FACTOR_SERVICE_INTERFACE = "ITwoFactorService";
private const string STEAM_QUERY_TIME_METHOD = "QueryTime";
Expand All @@ -16,20 +17,19 @@ internal class SteamTwoFactorGenerator
private static bool _aligned = false;

private Client _steamClient;
private string _sharedSecret;

internal SteamTwoFactorGenerator(Client steamClient)
public AutoTwoFactorAuthenticator(Client steamClient, string sharedSecret)
{
_steamClient = steamClient;
_sharedSecret = sharedSecret;
}

internal async Task<string> GenerateSteamGuardCodeAsync(string sharedSecret)
public async Task<string> GenerateSteamGuardCodeAsync()
{
if (string.IsNullOrWhiteSpace(sharedSecret))
return string.Empty;

long time = await GetSteamTimeAsync();

string sharedSecretUnescaped = Regex.Unescape(sharedSecret);
string sharedSecretUnescaped = Regex.Unescape(_sharedSecret);
byte[] sharedSecretArray = Convert.FromBase64String(sharedSecretUnescaped);
byte[] timeArray = new byte[8];

Expand Down Expand Up @@ -88,4 +88,10 @@ private async Task AlignTimeAsync()
_aligned = true;
return;
}

public Task<string> GetDeviceCodeAsync(bool previousCodeWasIncorrect) => GenerateSteamGuardCodeAsync();

public Task<string> GetEmailCodeAsync(string email, bool previousCodeWasIncorrect) => throw new NotImplementedException();

public Task<bool> AcceptDeviceConfirmationAsync() => throw new NotImplementedException();
}
70 changes: 36 additions & 34 deletions SteamAccountDataFetcher/SteamDataClient/Client.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System.Runtime.CompilerServices;
using SteamKit2;
using SteamKit2.Authentication;

namespace SteamAccountDataFetcher.SteamDataClient;

internal class Client : IDisposable
public class Client : IDisposable
{
private SteamClient _steamClient;
private SteamTwoFactorGenerator _steamTwoFactorGenerator;
private AutoTwoFactorAuthenticator _autoTwoFactorAuthenticator;
private SteamWebClient _steamWebClient;
private CallbackManager _callbackManager;
private SteamUser _steamUser;
Expand All @@ -26,7 +27,8 @@ internal SteamID? SteamID
}
internal string Username { get; private set; }
internal string Password { get; private set; }
private string SharedSecret { get; set; }
internal string AccessToken { get; private set; } = string.Empty;
internal string RefreshToken { get; private set; } = string.Empty;

private static uint _instance = 0;
private static DateTime _lastConnectionTime = DateTime.MinValue;
Expand All @@ -43,7 +45,6 @@ internal Client(string username, string password, string sharedSecret)

Username = username;
Password = password;
SharedSecret = sharedSecret;

_responseAccountInfo = new()
{
Expand Down Expand Up @@ -74,13 +75,13 @@ internal Client(string username, string password, string sharedSecret)

_callbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnectedAsync);
_callbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnectedAsync);
_callbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOnAsync);
_callbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
_callbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOffAsync);
_callbackManager.Subscribe<SteamApps.LicenseListCallback>(OnLicenseListAsync);

_callbackManager.Subscribe<DataFetcher.IsLimitedAccountCallback>(OnIsLimitedAccount);

_steamTwoFactorGenerator = new(this);
_autoTwoFactorAuthenticator = new(this, sharedSecret);
_steamWebClient = new(this);
}

Expand Down Expand Up @@ -117,12 +118,32 @@ internal async Task RunAsync()

private async Task LoginAsync()
{
string twoFactorCode = await _steamTwoFactorGenerator.GenerateSteamGuardCodeAsync(SharedSecret);
_steamUser.LogOn(new()
CredentialsAuthSession authSession = await _steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new()
{
Username = Username,
Password = Password,
TwoFactorCode = twoFactorCode
Authenticator = _autoTwoFactorAuthenticator
});

AuthPollResult authPollResult;
try
{
authPollResult = await authSession.PollingWaitForResultAsync();
}
catch (AuthenticationException e)
{
Log($"Unable to authenticate user to Steam Client with error {e.Message}.", Logger.Level.Error);
_steamClient.Disconnect();
return;
}

AccessToken = authPollResult.AccessToken;
RefreshToken = authPollResult.RefreshToken;

_steamUser.LogOn(new()
{
Username = Username,
AccessToken = RefreshToken
});
}

Expand Down Expand Up @@ -170,7 +191,7 @@ private async void OnDisconnectedAsync(SteamClient.DisconnectedCallback callback
_steamClient.Connect();
}

private async void OnLoggedOnAsync(SteamUser.LoggedOnCallback callback)
private void OnLoggedOn(SteamUser.LoggedOnCallback callback)
{
if (callback.Result != EResult.OK)
{
Expand All @@ -179,44 +200,25 @@ private async void OnLoggedOnAsync(SteamUser.LoggedOnCallback callback)
return;
}

if (string.IsNullOrEmpty(callback.WebAPIUserNonce))
{
Log($"{nameof(callback.WebAPIUserNonce)} is empty.", Logger.Level.Error);
_steamClient.Disconnect();
return;
}

if (callback.ClientSteamID == null)
{
Log($"{nameof(callback.ClientSteamID)} is empty.", Logger.Level.Error);
_steamClient.Disconnect();
return;
}
_responseAccountInfo.SteamId = callback.ClientSteamID.ConvertToUInt64();

Log("Logged into Steam. Log-in into Web Api.");
var webLoginResult = await _steamWebClient.InitAsync(callback.WebAPIUserNonce);
if (!webLoginResult)
{
Log("Unable to log-in into Web Api.", Logger.Level.Error);
_steamClient.Disconnect();
return;
}
Log("Logged into Web Api.");
_steamWebClient.InitAsync();
}

private async void OnIsLimitedAccount(DataFetcher.IsLimitedAccountCallback callback)
{
if (callback.Locked)
{
Log("Account is locked.", Logger.Level.Error);
_steamClient.Disconnect();
return;
}

_responseAccountInfo.IsLocked = callback.Locked;
_responseAccountInfo.IsBanned = callback.CommunityBanned;
_responseAccountInfo.IsLimited = callback.Limited;

if (callback.Locked)
Log("Account is locked.", Logger.Level.Warning);

if (callback.CommunityBanned)
Log("Account is banned.", Logger.Level.Warning);

Expand Down
87 changes: 11 additions & 76 deletions SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using SteamKit2;
using System.Web;

namespace SteamAccountDataFetcher.SteamDataClient;

internal class SteamWebClient: IDisposable
{
private const string STEAM_USER_AUTH_INTERFACE = "ISteamUserAuth";
private const string STEAM_COMMUNITY_SERVICE_INTERFACE = "ICommunityService";

private const string STEAM_AUTH_USER_METHOD = "AuthenticateUser";
private const string STEAM_GET_APPS_METHOD = "GetApps";

private static readonly Uri API_KEY_URI = new("https://steamcommunity.com/dev/apikey");
private static readonly Uri REGISTER_API_KEY_URI = new("https://steamcommunity.com/dev/registerkey");
private const string API_GROUP_KEY = "apiKey";
Expand Down Expand Up @@ -179,78 +173,23 @@ private string MatchApiKey(string html)
return apiKey;
}

internal async Task<bool> InitAsync(string webApiUserNonce)
internal void InitAsync()
{
if (_steamClient.SteamID == null || !_steamClient.SteamID.IsValid || !_steamClient.SteamID.IsIndividualAccount)
{
_steamClient.Log($"{nameof(_steamClient.SteamID)} is invalid.", Logger.Level.Error);
return false;
var msg = $"{nameof(_steamClient.SteamID)} is invalid.";
_steamClient.Log(msg, Logger.Level.Error);
throw new InvalidOperationException(msg);
}

if (string.IsNullOrEmpty(webApiUserNonce))
{
_steamClient.Log($"{nameof(webApiUserNonce)} is empty.", Logger.Level.Error);
return false;
}

await RunOrSleep();

var publicKey = KeyDictionary.GetPublicKey(EUniverse.Public);
if (publicKey == null || publicKey.Length == 0)
if (string.IsNullOrEmpty(_steamClient.AccessToken))
{
_steamClient.Log($"{nameof(KeyDictionary)} is empty.", Logger.Level.Error);
return false;
var msg = $"{nameof(_steamClient.AccessToken)} is empty.";
_steamClient.Log(msg, Logger.Level.Error);
throw new InvalidOperationException(msg);
}

var sessionKey = CryptoHelper.GenerateRandomBlock(32);
byte[] encryptedSessionKey;

using (RSACrypto rsa = new(publicKey))
encryptedSessionKey = rsa.Encrypt(sessionKey);

var loginKey = Encoding.UTF8.GetBytes(webApiUserNonce);
var encryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);

Dictionary<string, object?> postData = new(3, StringComparer.Ordinal)
{
{ "encrypted_loginkey", encryptedLoginKey },
{ "sessionkey", encryptedSessionKey },
{ "steamid", _steamClient.SteamID.ConvertToUInt64() }
};

KeyValue response;
using (var steamUserAuthInterface = _steamClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_USER_AUTH_INTERFACE))
{
try
{
response = await steamUserAuthInterface.CallAsync(HttpMethod.Post, STEAM_AUTH_USER_METHOD, args: postData);
}
catch (HttpRequestException e)
{
_steamClient.Log($"Unable to authenticate user to web with status code {e.StatusCode}.", Logger.Level.Error);
return (false);
}

if (response == null)
{
_steamClient.Log("Unable to authenticate user to web.", Logger.Level.Error);
return false;
}
}

string? steamLogin = response["token"].AsString();
if (string.IsNullOrWhiteSpace(steamLogin))
{
_steamClient.Log($"{nameof(steamLogin)} is empty.", Logger.Level.Error);
return false;
}

string? steamLoginSecure = response["tokensecure"].AsString();
if (string.IsNullOrWhiteSpace(steamLoginSecure))
{
_steamClient.Log($"{nameof(steamLoginSecure)} is empty.", Logger.Level.Error);
return false;
}
string steamLoginSecure = HttpUtility.UrlEncode($"{_steamClient.SteamID.ConvertToUInt64()}||{_steamClient.AccessToken}");

Random rnd = new Random();
byte[] sessionBytes = new byte[12];
Expand All @@ -264,10 +203,6 @@ internal async Task<bool> InitAsync(string webApiUserNonce)
new Cookie("sessionid", SessionID, "/", ".steamcommunity.com"),
new Cookie("sessionid", SessionID, "/", ".help.steampowered.com"),
new Cookie("sessionid", SessionID, "/", ".store.steampowered.com"),
new Cookie("steamLogin", steamLogin, "/", ".checkout.steampowered.com"),
new Cookie("steamLogin", steamLogin, "/", ".steamcommunity.com"),
new Cookie("steamLogin", steamLogin, "/", ".help.steampowered.com"),
new Cookie("steamLogin", steamLogin, "/", ".store.steampowered.com"),
new Cookie("steamLoginSecure", steamLoginSecure, "/", ".checkout.steampowered.com"),
new Cookie("steamLoginSecure", steamLoginSecure, "/", ".steamcommunity.com"),
new Cookie("steamLoginSecure", steamLoginSecure, "/", ".help.steampowered.com"),
Expand All @@ -283,7 +218,7 @@ internal async Task<bool> InitAsync(string webApiUserNonce)

_httpClient = new HttpClient(httpClientHandler, true);

return true;
return;
}

private async Task RunOrSleep()
Expand Down

0 comments on commit 51b2be5

Please sign in to comment.