mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-16 08:25:28 +00:00
Gigantic code cleanup
Time to enforce some common file layout, as general mess started to annoying me. Sorry in advance for people using custom forks and having merge conflicts, this will help everybody in long-run
This commit is contained in:
@@ -37,61 +37,26 @@ namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
internal sealed class MobileAuthenticator : IDisposable {
|
||||
internal sealed class Confirmation {
|
||||
internal readonly uint ID;
|
||||
internal readonly ulong Key;
|
||||
internal readonly Steam.ConfirmationDetails.EType Type;
|
||||
|
||||
internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) {
|
||||
if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) {
|
||||
throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type));
|
||||
}
|
||||
|
||||
ID = id;
|
||||
Key = key;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
private const byte CodeDigits = 5;
|
||||
private const byte CodeInterval = 30;
|
||||
|
||||
private static readonly char[] CodeCharacters = {
|
||||
'2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
|
||||
'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
|
||||
'R', 'T', 'V', 'W', 'X', 'Y'
|
||||
};
|
||||
|
||||
private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
|
||||
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
private static short? SteamTimeDifference;
|
||||
|
||||
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
|
||||
|
||||
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
|
||||
private readonly string SharedSecret;
|
||||
|
||||
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
|
||||
private readonly string IdentitySecret;
|
||||
#pragma warning restore 649
|
||||
private Bot Bot;
|
||||
|
||||
[JsonProperty(PropertyName = "device_id")]
|
||||
private string DeviceID;
|
||||
|
||||
private Bot Bot;
|
||||
|
||||
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
|
||||
|
||||
private MobileAuthenticator() { }
|
||||
|
||||
internal void Init(Bot bot) {
|
||||
if (bot == null) {
|
||||
throw new ArgumentNullException(nameof(bot));
|
||||
}
|
||||
|
||||
Bot = bot;
|
||||
}
|
||||
public void Dispose() => ConfirmationsSemaphore.Dispose();
|
||||
|
||||
internal void CorrectDeviceID(string deviceID) {
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
@@ -102,56 +67,14 @@ namespace ArchiSteamFarm {
|
||||
DeviceID = deviceID;
|
||||
}
|
||||
|
||||
internal async Task<bool> HandleConfirmations(HashSet<Confirmation> confirmations, bool accept) {
|
||||
if ((confirmations == null) || (confirmations.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(confirmations));
|
||||
return false;
|
||||
internal async Task<string> GenerateToken() {
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time != 0) {
|
||||
return GenerateTokenForTime(time);
|
||||
}
|
||||
|
||||
if (!HasCorrectDeviceID) {
|
||||
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!");
|
||||
return false;
|
||||
}
|
||||
|
||||
await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(time));
|
||||
return false;
|
||||
}
|
||||
|
||||
string confirmationHash = GenerateConfirmationKey(time, "conf");
|
||||
if (string.IsNullOrEmpty(confirmationHash)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(confirmationHash));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
|
||||
if (!result.HasValue) { // Request timed out
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.Value) { // Request succeeded
|
||||
return true;
|
||||
}
|
||||
|
||||
// Our multi request failed, this is almost always Steam fuckup that happens randomly
|
||||
// In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel)
|
||||
// We totally ignore actual result returned by those calls, abort only if request timed out
|
||||
|
||||
foreach (Confirmation confirmation in confirmations) {
|
||||
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
|
||||
if (!confirmationResult.HasValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
ConfirmationsSemaphore.Release();
|
||||
}
|
||||
Bot.ArchiLogger.LogNullError(nameof(time));
|
||||
return null;
|
||||
}
|
||||
|
||||
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {
|
||||
@@ -185,16 +108,6 @@ namespace ArchiSteamFarm {
|
||||
return response;
|
||||
}
|
||||
|
||||
internal async Task<string> GenerateToken() {
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time != 0) {
|
||||
return GenerateTokenForTime(time);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogNullError(nameof(time));
|
||||
return null;
|
||||
}
|
||||
|
||||
internal async Task<HashSet<Confirmation>> GetConfirmations() {
|
||||
if (!HasCorrectDeviceID) {
|
||||
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!");
|
||||
@@ -271,68 +184,64 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<uint> GetSteamTime() {
|
||||
if (SteamTimeDifference.HasValue) {
|
||||
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
|
||||
internal async Task<bool> HandleConfirmations(HashSet<Confirmation> confirmations, bool accept) {
|
||||
if ((confirmations == null) || (confirmations.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(confirmations));
|
||||
return false;
|
||||
}
|
||||
|
||||
await TimeSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
if (!HasCorrectDeviceID) {
|
||||
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!");
|
||||
return false;
|
||||
}
|
||||
|
||||
await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (!SteamTimeDifference.HasValue) {
|
||||
uint serverTime = Bot.ArchiWebHandler.GetServerTime();
|
||||
if (serverTime != 0) {
|
||||
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime());
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(time));
|
||||
return false;
|
||||
}
|
||||
|
||||
string confirmationHash = GenerateConfirmationKey(time, "conf");
|
||||
if (string.IsNullOrEmpty(confirmationHash)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(confirmationHash));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
|
||||
if (!result.HasValue) { // Request timed out
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.Value) { // Request succeeded
|
||||
return true;
|
||||
}
|
||||
|
||||
// Our multi request failed, this is almost always Steam fuckup that happens randomly
|
||||
// In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel)
|
||||
// We totally ignore actual result returned by those calls, abort only if request timed out
|
||||
|
||||
foreach (Confirmation confirmation in confirmations) {
|
||||
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
|
||||
if (!confirmationResult.HasValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
TimeSemaphore.Release();
|
||||
}
|
||||
|
||||
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
|
||||
return true;
|
||||
} finally {
|
||||
ConfirmationsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateTokenForTime(uint time) {
|
||||
if (time == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(time));
|
||||
return null;
|
||||
internal void Init(Bot bot) {
|
||||
if (bot == null) {
|
||||
throw new ArgumentNullException(nameof(bot));
|
||||
}
|
||||
|
||||
byte[] sharedSecret = Convert.FromBase64String(SharedSecret);
|
||||
|
||||
byte[] timeArray = BitConverter.GetBytes((long) time / CodeInterval);
|
||||
if (BitConverter.IsLittleEndian) {
|
||||
Array.Reverse(timeArray);
|
||||
}
|
||||
|
||||
byte[] hash;
|
||||
using (HMACSHA1 hmac = new HMACSHA1(sharedSecret)) {
|
||||
hash = hmac.ComputeHash(timeArray);
|
||||
}
|
||||
|
||||
// The last 4 bits of the mac say where the code starts
|
||||
int start = hash[hash.Length - 1] & 0x0f;
|
||||
|
||||
// Extract those 4 bytes
|
||||
byte[] bytes = new byte[4];
|
||||
|
||||
Array.Copy(hash, start, bytes, 0, 4);
|
||||
|
||||
if (BitConverter.IsLittleEndian) {
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
uint fullCode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff;
|
||||
|
||||
// Build the alphanumeric code
|
||||
StringBuilder code = new StringBuilder();
|
||||
|
||||
for (byte i = 0; i < CodeDigits; i++) {
|
||||
code.Append(CodeCharacters[fullCode % CodeCharacters.Length]);
|
||||
fullCode /= (uint) CodeCharacters.Length;
|
||||
}
|
||||
|
||||
return code.ToString();
|
||||
Bot = bot;
|
||||
}
|
||||
|
||||
private string GenerateConfirmationKey(uint time, string tag = null) {
|
||||
@@ -368,6 +277,92 @@ namespace ArchiSteamFarm {
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
public void Dispose() => ConfirmationsSemaphore.Dispose();
|
||||
private string GenerateTokenForTime(uint time) {
|
||||
if (time == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(time));
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] sharedSecret = Convert.FromBase64String(SharedSecret);
|
||||
|
||||
byte[] timeArray = BitConverter.GetBytes((long) time/CodeInterval);
|
||||
if (BitConverter.IsLittleEndian) {
|
||||
Array.Reverse(timeArray);
|
||||
}
|
||||
|
||||
byte[] hash;
|
||||
using (HMACSHA1 hmac = new HMACSHA1(sharedSecret)) {
|
||||
hash = hmac.ComputeHash(timeArray);
|
||||
}
|
||||
|
||||
// The last 4 bits of the mac say where the code starts
|
||||
int start = hash[hash.Length - 1] & 0x0f;
|
||||
|
||||
// Extract those 4 bytes
|
||||
byte[] bytes = new byte[4];
|
||||
|
||||
Array.Copy(hash, start, bytes, 0, 4);
|
||||
|
||||
if (BitConverter.IsLittleEndian) {
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
uint fullCode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff;
|
||||
|
||||
// Build the alphanumeric code
|
||||
StringBuilder code = new StringBuilder();
|
||||
|
||||
for (byte i = 0; i < CodeDigits; i++) {
|
||||
code.Append(CodeCharacters[fullCode%CodeCharacters.Length]);
|
||||
fullCode /= (uint) CodeCharacters.Length;
|
||||
}
|
||||
|
||||
return code.ToString();
|
||||
}
|
||||
|
||||
private async Task<uint> GetSteamTime() {
|
||||
if (SteamTimeDifference.HasValue) {
|
||||
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
|
||||
}
|
||||
|
||||
await TimeSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (!SteamTimeDifference.HasValue) {
|
||||
uint serverTime = Bot.ArchiWebHandler.GetServerTime();
|
||||
if (serverTime != 0) {
|
||||
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
TimeSemaphore.Release();
|
||||
}
|
||||
|
||||
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
|
||||
}
|
||||
|
||||
internal sealed class Confirmation {
|
||||
internal readonly uint ID;
|
||||
internal readonly ulong Key;
|
||||
internal readonly Steam.ConfirmationDetails.EType Type;
|
||||
|
||||
internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) {
|
||||
if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) {
|
||||
throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type));
|
||||
}
|
||||
|
||||
ID = id;
|
||||
Key = key;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
|
||||
private readonly string SharedSecret;
|
||||
|
||||
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
|
||||
private readonly string IdentitySecret;
|
||||
#pragma warning restore 649
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user