Compare commits

..

26 Commits

Author SHA1 Message Date
JustArchi
4bf1462381 Bump 2016-07-29 03:36:32 +02:00
JustArchi
e41d2cf37e Report on confirmation issues 2016-07-29 01:02:58 +02:00
JustArchi
fac5e65035 Fix accepting over 30 confirmations 2016-07-29 00:46:17 +02:00
JustArchi
b44115711b Don't stop keys forwarding if initial bot gets OnCooldown
In this case, move to the next one, try to redeem, and get the package data from it instead
2016-07-28 21:40:40 +02:00
JustArchi
a90573e0ea Implement smart algorithm of avoiding OnCooldown
I really like this approach, as it has only one caveat: memory usage.
We need to keep in memory list of all packages that our account owns, which will result of N * 32bit extra memory usage, where N is equal to number of package licenses the account owns. This results in around 16 KB extra memory usage for my 4k account.
However, apart from that there are no real caveats as checking if we own given packageID is O(1) operation, and I think that apart from this extra memory footprint there can be more benefits of having this field in future, besides, my 16 KB is extreme case, as usually nobody goes that high. Ship it!
2016-07-28 21:20:57 +02:00
JustArchi
5611694b90 Misc 2016-07-28 18:27:01 +02:00
JustArchi
efd7fbd3c0 Style fixes + move ServiceHost to try block 2016-07-28 18:07:26 +02:00
Łukasz Domeradzki
d32882d91e Merge pull request #309 from stackia/master
Add an option to switch WCF metadata publishing
2016-07-28 18:04:29 +02:00
stackia
8208e9aa77 Remove the WCFPublishMetadata config option, make it always on 2016-07-28 23:58:25 +08:00
stackia
c13eb02e51 Add an option to switch WCF metadata publishing 2016-07-28 20:34:35 +08:00
JustArchi
93ac6d5a28 Bump 2016-07-28 00:07:33 +02:00
JustArchi
413d44b42b Those should be errors 2016-07-27 04:53:23 +02:00
JustArchi
c52934ed9a Bring in small ArchiBoT trading enhancements
This mostly fixed forwarded tradeIDs to contain only trades that we indeed accepted and need to confirm, instead of all trades we're dealing with
2016-07-27 04:48:28 +02:00
JustArchi
b853c93c3f Precise Mono versions affected by bug 41701 2016-07-25 15:05:15 +02:00
JustArchi
0a86107804 Misc 2016-07-25 06:53:16 +02:00
JustArchi
cc2289798e Enable PortReuse on Windows + misc 2016-07-25 06:44:10 +02:00
JustArchi
4f01dc39fc Code review 2016-07-25 06:08:45 +02:00
JustArchi
52de999443 Bump 2016-07-25 00:10:17 +02:00
JustArchi
8998bf4832 Add extra support for fatal exceptions 2016-07-25 00:10:06 +02:00
JustArchi
49f6181263 Misc 2016-07-24 02:59:07 +02:00
JustArchi
e094a02762 Misc 2016-07-24 02:51:16 +02:00
JustArchi
410f31f995 Misc 2016-07-24 02:49:59 +02:00
JustArchi
85a70911e1 Add support of command-line-arguments to run.sh 2016-07-24 02:48:49 +02:00
JustArchi
0714c4e575 Add support for specifying --path= 2016-07-24 00:36:15 +02:00
JustArchi
b19a5c533f GetMySteamInventory() hardening 2016-07-23 01:58:56 +02:00
JustArchi
4129db0149 Bump 2016-07-22 06:27:11 +02:00
19 changed files with 399 additions and 244 deletions

View File

@@ -38,10 +38,10 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler : IDisposable {
private const string SteamCommunityHost = "steamcommunity.com";
private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds
private const byte MinSessionTTL = GlobalConfig.DefaultHttpTimeout / 4; // Assume session is valid for at least that amount of seconds
private static string SteamCommunityURL = "https://" + SteamCommunityHost;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000; // This must be int type
private readonly Bot Bot;
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
@@ -729,7 +729,7 @@ namespace ArchiSteamFarm {
}
Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptionMap = new Dictionary<ulong, Tuple<uint, Steam.Item.EType>>();
foreach (JToken description in descriptions) {
foreach (JToken description in descriptions.Where(description => description != null)) {
string classIDString = description["classid"].ToString();
if (string.IsNullOrEmpty(classIDString)) {
Logging.LogNullError(nameof(classIDString), Bot.BotName);
@@ -782,7 +782,7 @@ namespace ArchiSteamFarm {
return null;
}
foreach (JToken item in items) {
foreach (JToken item in items.Where(item => item != null)) {
Steam.Item steamItem;
try {

View File

@@ -26,6 +26,7 @@ using Newtonsoft.Json;
using SteamKit2;
using SteamKit2.Internal;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -43,7 +44,7 @@ namespace ArchiSteamFarm {
private const ushort CallbackSleep = 500; // In miliseconds
private const ushort MaxSteamMessageLength = 2048;
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
@@ -63,6 +64,7 @@ namespace ArchiSteamFarm {
private readonly CardsFarmer CardsFarmer;
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<uint> OwnedPackageIDs = new ConcurrentHashSet<uint>();
private readonly SteamApps SteamApps;
private readonly SteamFriends SteamFriends;
private readonly SteamUser SteamUser;
@@ -184,6 +186,7 @@ namespace ArchiSteamFarm {
SteamApps = SteamClient.GetHandler<SteamApps>();
CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);
CallbackManager.Subscribe<SteamApps.GuestPassListCallback>(OnGuestPassList);
CallbackManager.Subscribe<SteamApps.LicenseListCallback>(OnLicenseList);
SteamFriends = SteamClient.GetHandler<SteamFriends>();
CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
@@ -250,41 +253,45 @@ namespace ArchiSteamFarm {
Trading?.Dispose();
}
internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
if (BotDatabase.MobileAuthenticator == null) {
return;
return false;
}
HashSet<MobileAuthenticator.Confirmation> confirmations = await BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false);
if ((confirmations == null) || (confirmations.Count == 0)) {
return;
return true;
}
if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) {
if (confirmations.RemoveWhere(confirmation => (confirmation.Type != acceptedType) && (confirmation.Type != Steam.ConfirmationDetails.EType.Other)) > 0) {
if (confirmations.Count == 0) {
return;
return true;
}
}
}
if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) {
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
)).Select(details => details.Confirmation));
if (ignoredConfirmations.Count > 0) {
confirmations.ExceptWith(ignoredConfirmations);
if (confirmations.Count == 0) {
return;
}
}
if ((acceptedSteamID == 0) && ((acceptedTradeIDs == null) || (acceptedTradeIDs.Count == 0))) {
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
)).Select(details => details.Confirmation));
if (ignoredConfirmations.Count == 0) {
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
confirmations.ExceptWith(ignoredConfirmations);
if (confirmations.Count == 0) {
return true;
}
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
internal async Task<bool> RefreshSession() {
@@ -797,8 +804,11 @@ namespace ArchiSteamFarm {
return "That bot doesn't have ASF 2FA enabled!";
}
await AcceptConfirmations(confirm).ConfigureAwait(false);
return "Done!";
if (await AcceptConfirmations(confirm).ConfigureAwait(false)) {
return "Success!";
}
return "Something went wrong!";
}
private static async Task<string> Response2FAConfirm(ulong steamID, string botName, bool confirm) {
@@ -970,7 +980,8 @@ namespace ArchiSteamFarm {
}
bool alreadyHandled = false;
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected)) {
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected && ((result.Items.Count == 0) || result.Items.Keys.Any(packageID => !bot.OwnedPackageIDs.Contains(packageID))))) {
ArchiHandler.PurchaseResponseCallback otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
if (otherResult == null) {
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: Timeout!");
@@ -990,6 +1001,14 @@ namespace ArchiSteamFarm {
if (alreadyHandled) {
break;
}
if (result.Items.Count != 0) {
continue;
}
foreach (KeyValuePair<uint, string> item in otherResult.Items) {
result.Items[item.Key] = item.Value;
}
}
key = reader.ReadLine(); // Next key
@@ -1618,6 +1637,21 @@ namespace ArchiSteamFarm {
}
}
private void OnLicenseList(SteamApps.LicenseListCallback callback) {
if (callback?.LicenseList == null) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList), BotName);
return;
}
OwnedPackageIDs.Clear();
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList) {
OwnedPackageIDs.Add(license.PackageID);
}
OwnedPackageIDs.TrimExcess();
}
private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
if ((callback?.ChatRoomID == null) || (callback.PatronID == null)) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.ChatRoomID) + " || " + nameof(callback.PatronID), BotName);

View File

@@ -32,12 +32,13 @@ using System.Linq;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig {
[JsonProperty(Required = Required.DisallowNull)]
internal bool Enabled { get; private set; } = false;
internal readonly bool Enabled = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool StartOnLaunch { get; private set; } = true;
internal readonly bool StartOnLaunch = true;
[JsonProperty]
internal string SteamLogin { get; set; }
@@ -53,64 +54,64 @@ namespace ArchiSteamFarm {
internal string SteamParentalPIN { get; set; } = "0";
[JsonProperty]
internal string SteamApiKey { get; private set; } = null;
internal readonly string SteamApiKey = null;
[JsonProperty(Required = Required.DisallowNull)]
internal ulong SteamMasterID { get; private set; } = 0;
internal readonly ulong SteamMasterID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal ulong SteamMasterClanID { get; private set; } = 0;
internal readonly ulong SteamMasterClanID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal bool CardDropsRestricted { get; private set; } = false;
internal readonly bool CardDropsRestricted = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool DismissInventoryNotifications { get; private set; } = true;
internal readonly bool DismissInventoryNotifications = true;
[JsonProperty(Required = Required.DisallowNull)]
internal bool FarmOffline { get; private set; } = false;
internal readonly bool FarmOffline = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool HandleOfflineMessages { get; private set; } = false;
internal readonly bool HandleOfflineMessages = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AcceptGifts { get; private set; } = false;
internal readonly bool AcceptGifts = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool IsBotAccount { get; private set; } = false;
internal readonly bool IsBotAccount = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool SteamTradeMatcher { get; private set; } = false;
internal readonly bool SteamTradeMatcher = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForwardKeysToOtherBots { get; private set; } = false;
internal readonly bool ForwardKeysToOtherBots = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool DistributeKeys { get; private set; } = false;
internal readonly bool DistributeKeys = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
internal readonly bool ShutdownOnFarmingFinished = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool SendOnFarmingFinished { get; private set; } = false;
internal readonly bool SendOnFarmingFinished = false;
[JsonProperty]
internal string SteamTradeToken { get; private set; } = null;
internal readonly string SteamTradeToken = null;
[JsonProperty(Required = Required.DisallowNull)]
internal byte SendTradePeriod { get; private set; } = 0;
internal readonly byte SendTradePeriod = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal byte AcceptConfirmationsPeriod { get; private set; } = 0;
internal readonly byte AcceptConfirmationsPeriod = 0;
[JsonProperty]
internal string CustomGamePlayedWhileFarming { get; private set; } = null;
internal readonly string CustomGamePlayedWhileFarming = null;
[JsonProperty]
internal string CustomGamePlayedWhileIdle { get; private set; } = null;
internal readonly string CustomGamePlayedWhileIdle = null;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>();
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
internal static BotConfig Load(string filePath) {
@@ -145,7 +146,10 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericWarning("Playing more than " + CardsFarmer.MaxGamesPlayedConcurrently + " games concurrently is not possible, only first " + CardsFarmer.MaxGamesPlayedConcurrently + " entries from GamesPlayedWhileIdle will be used");
botConfig.GamesPlayedWhileIdle = new HashSet<uint>(botConfig.GamesPlayedWhileIdle.Take(CardsFarmer.MaxGamesPlayedConcurrently));
HashSet<uint> validGames = new HashSet<uint>(botConfig.GamesPlayedWhileIdle.Take(CardsFarmer.MaxGamesPlayedConcurrently));
botConfig.GamesPlayedWhileIdle.IntersectWith(validGames);
botConfig.GamesPlayedWhileIdle.TrimExcess();
return botConfig;
}

View File

@@ -100,7 +100,17 @@ namespace ArchiSteamFarm {
}
}
public void Dispose() => Lock?.Dispose();
public void TrimExcess() {
Lock.EnterWriteLock();
try {
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
public void Dispose() => Lock.Dispose();
public void CopyTo(T[] array, int arrayIndex) {
Lock.EnterReadLock();

View File

@@ -32,6 +32,7 @@ using System.Net.Sockets;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class GlobalConfig {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum EUpdateChannel : byte {
@@ -51,64 +52,64 @@ namespace ArchiSteamFarm {
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false;
internal readonly bool Debug = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Headless { get; private set; } = false;
internal readonly bool Headless = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true;
internal readonly bool AutoUpdates = true;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoRestart { get; private set; } = true;
internal readonly bool AutoRestart = true;
[JsonProperty(Required = Required.DisallowNull)]
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
[JsonProperty(Required = Required.DisallowNull)]
internal ProtocolType SteamProtocol { get; private set; } = DefaultSteamProtocol;
internal readonly ProtocolType SteamProtocol = DefaultSteamProtocol;
[JsonProperty(Required = Required.DisallowNull)]
internal ulong SteamOwnerID { get; private set; } = 0;
internal readonly ulong SteamOwnerID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxFarmingTime { get; private set; } = DefaultMaxFarmingTime;
internal readonly byte MaxFarmingTime = DefaultMaxFarmingTime;
[JsonProperty(Required = Required.DisallowNull)]
internal byte IdleFarmingPeriod { get; private set; } = 3;
internal readonly byte IdleFarmingPeriod = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte FarmingDelay { get; private set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
internal byte LoginLimiterDelay { get; private set; } = 10;
internal readonly byte LoginLimiterDelay = 10;
[JsonProperty(Required = Required.DisallowNull)]
internal byte InventoryLimiterDelay { get; private set; } = 3;
internal readonly byte InventoryLimiterDelay = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte GiftsLimiterDelay { get; private set; } = 1;
internal readonly byte GiftsLimiterDelay = 1;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxTradeHoldDuration { get; private set; } = 15;
internal readonly byte MaxTradeHoldDuration = 15;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForceHttp { get; private set; } = false;
internal readonly bool ForceHttp = false;
[JsonProperty(Required = Required.DisallowNull)]
internal byte HttpTimeout { get; private set; } = DefaultHttpTimeout;
internal readonly byte HttpTimeout = DefaultHttpTimeout;
[JsonProperty]
internal string WCFHostname { get; set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
internal ushort WCFPort { get; private set; } = DefaultWCFPort;
internal readonly ushort WCFPort = DefaultWCFPort;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true;
internal readonly bool Statistics = true;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint>(GlobalBlacklist);
internal readonly HashSet<uint> Blacklist = new HashSet<uint>(GlobalBlacklist);
internal static GlobalConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@@ -141,21 +142,20 @@ namespace ArchiSteamFarm {
case ProtocolType.Udp:
break;
default:
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol + ". Value of " + DefaultSteamProtocol + " will be used instead");
globalConfig.SteamProtocol = DefaultSteamProtocol;
break;
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol);
return null;
}
// User might not know what he's doing
// Ensure that he can't screw core ASF variables
if (globalConfig.MaxFarmingTime == 0) {
Logging.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime + ". Value of " + DefaultMaxFarmingTime + " will be used instead");
globalConfig.MaxFarmingTime = DefaultMaxFarmingTime;
Logging.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime);
return null;
}
if (globalConfig.FarmingDelay == 0) {
Logging.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay + ". Value of " + DefaultFarmingDelay + " will be used instead");
globalConfig.FarmingDelay = DefaultFarmingDelay;
Logging.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay);
return null;
}
if ((globalConfig.FarmingDelay > 5) && Runtime.RequiresWorkaroundForMonoBug41701()) {
@@ -164,18 +164,16 @@ namespace ArchiSteamFarm {
}
if (globalConfig.HttpTimeout == 0) {
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout + ". Value of " + DefaultHttpTimeout + " will be used instead");
globalConfig.HttpTimeout = DefaultHttpTimeout;
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout);
return null;
}
if (globalConfig.WCFPort != 0) {
return globalConfig;
}
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead");
globalConfig.WCFPort = DefaultWCFPort;
return globalConfig;
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort);
return null;
}
// This constructor is used only by deserializer

View File

@@ -57,7 +57,7 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
internal InMemoryServerListProvider ServerListProvider { get; private set; } = new InMemoryServerListProvider();
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly object FileLock = new object();

View File

@@ -31,19 +31,21 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ReleaseResponse {
#pragma warning disable 649
internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal string Name { get; private set; }
internal readonly string Name;
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal string DownloadURL { get; private set; }
internal readonly string DownloadURL;
}
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
internal string Tag { get; private set; }
internal readonly string Tag;
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal List<Asset> Assets { get; private set; }
internal readonly List<Asset> Assets;
#pragma warning restore 649
}
}
}

View File

@@ -33,8 +33,7 @@ using SteamKit2;
namespace ArchiSteamFarm.JSON {
internal static class Steam {
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
// Deserialized from JSON (SteamCommunity) and constructed from code
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset | Deserialized from JSON (SteamCommunity) and constructed from code
internal const ushort SteamAppID = 753;
internal const byte SteamContextID = 6;
@@ -219,8 +218,7 @@ namespace ArchiSteamFarm.JSON {
}
}
internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
// Constructed from code
internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer | Constructed from code
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum ETradeOfferState : byte {
Unknown,
@@ -330,8 +328,7 @@ namespace ArchiSteamFarm.JSON {
}
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class TradeOfferRequest {
// Constructed from code
internal sealed class TradeOfferRequest { // Constructed from code
internal sealed class ItemList {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal readonly HashSet<Item> Assets = new HashSet<Item>();
@@ -347,10 +344,11 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationResponse {
// Deserialized from JSON
internal sealed class ConfirmationResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
internal readonly bool Success;
#pragma warning restore 649
private ConfirmationResponse() { }
}
@@ -358,8 +356,7 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationDetails {
// Deserialized from JSON
internal sealed class ConfirmationDetails { // Deserialized from JSON
internal enum EType : byte {
Unknown,
Trade,
@@ -381,8 +378,10 @@ namespace ArchiSteamFarm.JSON {
}
}
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
internal readonly bool Success;
#pragma warning restore 649
private EType _Type;
private EType Type {
@@ -474,8 +473,8 @@ namespace ArchiSteamFarm.JSON {
}
#pragma warning disable 649
[JsonProperty(PropertyName = "html")]
private string HTML;
[JsonProperty(PropertyName = "html", Required = Required.DisallowNull)]
private readonly string HTML;
#pragma warning restore 649
private uint _OtherSteamID3;

View File

@@ -25,6 +25,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using NLog;
@@ -146,6 +147,24 @@ namespace ArchiSteamFarm {
}
Logger.Fatal(exception, $"{botName}|{previousMethodName}()");
// If LogManager has been initialized already, don't do anything else
if (LogManager.Configuration != null) {
return;
}
// Otherwise, if we run into fatal exception before logging module is even initialized, write exception to classic log file
File.WriteAllText(Program.LogFile, DateTime.Now + " ASF V" + Program.Version + " has run into fatal exception before core logging module was even able to initialize!" + Environment.NewLine);
while (true) {
File.AppendAllText(Program.LogFile, "[!] EXCEPTION: " + previousMethodName + "() " + exception.Message + Environment.NewLine + "StackTrace:" + Environment.NewLine + exception.StackTrace);
if (exception.InnerException != null) {
exception = exception.InnerException;
continue;
}
break;
}
}
internal static void LogGenericWarning(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {

View File

@@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
@@ -55,6 +56,7 @@ namespace ArchiSteamFarm {
private const byte CodeDigits = 5;
private const byte CodeInterval = 30;
private const byte MaxConfirmationsPerRequest = 30; // This is limit enforced by Valve
private static readonly char[] CodeCharacters = {
'2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
@@ -69,11 +71,11 @@ namespace ArchiSteamFarm {
internal bool HasDeviceID => !string.IsNullOrEmpty(DeviceID);
#pragma warning disable 649
[JsonProperty(PropertyName = "shared_secret", Required = Required.DisallowNull)]
private string SharedSecret;
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
private readonly string SharedSecret;
[JsonProperty(PropertyName = "identity_secret", Required = Required.DisallowNull)]
private string IdentitySecret;
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
private readonly string IdentitySecret;
#pragma warning restore 649
[JsonProperty(PropertyName = "device_id")]
@@ -115,12 +117,28 @@ namespace ArchiSteamFarm {
}
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (!string.IsNullOrEmpty(confirmationHash)) {
if (string.IsNullOrEmpty(confirmationHash)) {
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return false;
}
if (confirmations.Count <= MaxConfirmationsPerRequest) {
return await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
}
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return false;
HashSet<Confirmation> pendingConfirmations = new HashSet<Confirmation>(confirmations);
do {
HashSet<Confirmation> currentConfirmations = new HashSet<Confirmation>(pendingConfirmations.Take(MaxConfirmationsPerRequest));
if (!await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, currentConfirmations, accept).ConfigureAwait(false)) {
return false;
}
pendingConfirmations.ExceptWith(currentConfirmations);
} while (pendingConfirmations.Count > 0);
return true;
}
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {

View File

@@ -72,9 +72,6 @@ namespace ArchiSteamFarm {
private static readonly object ConsoleLock = new object();
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
private static readonly string ExecutableFile = Assembly.GetEntryAssembly().Location;
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly WCF WCF = new WCF();
internal static bool IsRunningAsService { get; private set; }
@@ -87,7 +84,8 @@ namespace ArchiSteamFarm {
private static WebBrowser WebBrowser;
internal static async Task CheckForUpdate(bool updateOverride = false) {
string oldExeFile = ExecutableFile + ".old";
string exeFile = Assembly.GetEntryAssembly().Location;
string oldExeFile = exeFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
@@ -186,7 +184,8 @@ namespace ArchiSteamFarm {
return;
}
GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase));
string exeFileName = Path.GetFileName(exeFile);
GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(exeFileName, StringComparison.OrdinalIgnoreCase));
if (binaryAsset == null) {
Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
@@ -206,7 +205,7 @@ namespace ArchiSteamFarm {
return;
}
string newExeFile = ExecutableFile + ".new";
string newExeFile = exeFile + ".new";
// Firstly we create new exec
try {
@@ -218,7 +217,7 @@ namespace ArchiSteamFarm {
// Now we move current -> old
try {
File.Move(ExecutableFile, oldExeFile);
File.Move(exeFile, oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
@@ -232,12 +231,12 @@ namespace ArchiSteamFarm {
// Now we move new -> current
try {
File.Move(newExeFile, ExecutableFile);
File.Move(newExeFile, exeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Move(oldExeFile, ExecutableFile);
File.Move(oldExeFile, exeFile);
File.Delete(newExeFile);
} catch {
// Ignored
@@ -267,7 +266,7 @@ namespace ArchiSteamFarm {
InitShutdownSequence();
try {
Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
} catch (Exception e) {
Logging.LogGenericException(e);
}
@@ -400,7 +399,29 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser(ASF);
}
private static void ParseArgs(IEnumerable<string> args) {
private static void ParsePreInitArgs(IEnumerable<string> args) {
if (args == null) {
Logging.LogNullError(nameof(args));
return;
}
foreach (string arg in args) {
switch (arg) {
case "":
break;
default:
if (arg.StartsWith("--", StringComparison.Ordinal)) {
if (arg.StartsWith("--path=", StringComparison.Ordinal) && (arg.Length > 7)) {
Directory.SetCurrentDirectory(arg.Substring(7));
}
}
break;
}
}
}
private static void ParsePostInitArgs(IEnumerable<string> args) {
if (args == null) {
Logging.LogNullError(nameof(args));
return;
@@ -421,8 +442,6 @@ namespace ArchiSteamFarm {
if (arg.StartsWith("--", StringComparison.Ordinal)) {
if (arg.StartsWith("--cryptkey=", StringComparison.Ordinal) && (arg.Length > 11)) {
CryptoHelper.SetEncryptionKey(arg.Substring(11));
} else {
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
}
break;
@@ -434,14 +453,7 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericInfo("Command sent: " + arg);
// We intentionally execute this async block synchronously
Logging.LogGenericInfo("Response received: " + WCF.SendCommand(arg));
/*
Task.Run(async () => {
Logging.LogGenericNotice("WCF", "Response received: " + await WCF.SendCommand(arg).ConfigureAwait(false));
}).Wait();
*/
break;
}
}
@@ -465,30 +477,38 @@ namespace ArchiSteamFarm {
Logging.LogFatalException(args.Exception);
}
private static void Init(IEnumerable<string> args) {
private static void Init(string[] args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
Logging.InitCoreLoggers();
Logging.LogGenericInfo("ASF V" + Version);
Directory.SetCurrentDirectory(ExecutableDirectory);
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (!string.IsNullOrEmpty(homeDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
if (Directory.Exists(ConfigDirectory)) {
break;
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
if (Directory.Exists(ConfigDirectory)) {
break;
}
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(ExecutableDirectory);
}
// Parse pre-init args
if (args != null) {
ParsePreInitArgs(args);
}
InitServices();
@@ -505,9 +525,9 @@ namespace ArchiSteamFarm {
SteamKit2.DebugLog.Enabled = true;
}
// Parse args
// Parse post-init args
if (args != null) {
ParseArgs(args);
ParsePostInitArgs(args);
}
// If we ran ASF as a client, we're done by now

View File

@@ -28,7 +28,8 @@ using System.Reflection;
namespace ArchiSteamFarm {
internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
private static bool IsRunningOnMono => MonoRuntime != null;
internal static bool IsRunningOnMono => MonoRuntime != null;
private static bool? _IsUserInteractive;
internal static bool IsUserInteractive {
@@ -39,20 +40,17 @@ namespace ArchiSteamFarm {
if (Environment.UserInteractive) {
_IsUserInteractive = true;
return true;
}
// If it's non-Mono, we can trust the result
if (!IsRunningOnMono) {
} else if (!IsRunningOnMono) {
// If it's non-Mono, we can trust the result
_IsUserInteractive = false;
return false;
} else {
// In Mono, Environment.UserInteractive is always false
// There is really no reliable way for now, so assume always being interactive
// Maybe in future I find out some awful hack or workaround that could be at least semi-reliable
_IsUserInteractive = true;
}
// In Mono, Environment.UserInteractive is always false
// There is really no reliable way for now, so assume always being interactive
// Maybe in future I find out some awful hack or workaround that could be at least semi-reliable
_IsUserInteractive = true;
return true;
return _IsUserInteractive.Value;
}
}
@@ -64,11 +62,21 @@ namespace ArchiSteamFarm {
}
Version monoVersion = GetMonoVersion();
if (monoVersion == null) {
// The issue affects Mono versions from 4.3.2, 4.4.0 to 4.4.2 (inclusive) and from 4.5.0 to 4.5.2 (inclusive)
if (monoVersion?.Major != 4) {
return false;
}
return (monoVersion >= new Version(4, 4)) && (monoVersion <= new Version(4, 5, 2));
switch (monoVersion.Minor) {
case 3:
return monoVersion.Build >= 2;
case 4:
case 5:
return monoVersion.Build <= 2;
default:
return false;
}
}
private static Version GetMonoVersion() {

View File

@@ -24,7 +24,7 @@
namespace ArchiSteamFarm {
internal static class SharedInfo {
internal const string Version = "2.1.2.9";
internal const string Version = "2.1.3.3";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";

View File

@@ -24,7 +24,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -32,14 +31,26 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class Trading : IDisposable {
private enum ParseTradeResult : byte {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
Unknown,
Error,
AcceptedWithItemLose,
AcceptedWithoutItemLose,
RejectedTemporarily,
RejectedPermanently
private sealed class ParseTradeResult {
internal enum EResult : byte {
Unknown,
AcceptedWithItemLose,
AcceptedWithoutItemLose,
RejectedTemporarily,
RejectedPermanently
}
internal readonly ulong TradeID;
internal readonly EResult Result;
internal ParseTradeResult(ulong tradeID, EResult result) {
if ((tradeID == 0) || (result == EResult.Unknown)) {
throw new ArgumentNullException(nameof(tradeID) + " || " + nameof(result));
}
TradeID = tradeID;
Result = result;
}
}
internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser
@@ -112,35 +123,42 @@ namespace ArchiSteamFarm {
}
ParseTradeResult[] results = await Task.WhenAll(tradeOffers.Select(ParseTrade)).ConfigureAwait(false);
if (results.Any(result => result == ParseTradeResult.AcceptedWithItemLose)) {
HashSet<ulong> acceptedTradeIDs = new HashSet<ulong>(results.Where(result => (result != null) && (result.Result == ParseTradeResult.EResult.AcceptedWithItemLose)).Select(result => result.TradeID));
if (acceptedTradeIDs.Count > 0) {
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
HashSet<ulong> tradeIDs = new HashSet<ulong>(tradeOffers.Select(tradeOffer => tradeOffer.TradeOfferID));
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, tradeIDs).ConfigureAwait(false);
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, acceptedTradeIDs).ConfigureAwait(false);
}
}
private async Task<ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return ParseTradeResult.Error;
return null;
}
if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
return ParseTradeResult.Error;
Logging.LogGenericError("Ignoring trade in non-active state!", Bot.BotName);
return null;
}
ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
switch (result) {
case ParseTradeResult.AcceptedWithItemLose:
case ParseTradeResult.AcceptedWithoutItemLose:
if (result == null) {
Logging.LogNullError(nameof(result), Bot.BotName);
return null;
}
switch (result.Result) {
case ParseTradeResult.EResult.AcceptedWithItemLose:
case ParseTradeResult.EResult.AcceptedWithoutItemLose:
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false) ? result : ParseTradeResult.Error;
case ParseTradeResult.RejectedPermanently:
case ParseTradeResult.RejectedTemporarily:
if (result == ParseTradeResult.RejectedPermanently) {
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false) ? result : null;
case ParseTradeResult.EResult.RejectedPermanently:
case ParseTradeResult.EResult.RejectedTemporarily:
if (result.Result == ParseTradeResult.EResult.RejectedPermanently) {
if (Bot.BotConfig.IsBotAccount) {
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID) ? result : ParseTradeResult.Error;
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID) ? result : null;
}
IgnoredTrades.Add(tradeOffer.TradeOfferID);
@@ -156,33 +174,33 @@ namespace ArchiSteamFarm {
private async Task<ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return ParseTradeResult.Error;
return null;
}
// Always accept trades when we're not losing anything
if (tradeOffer.ItemsToGive.Count == 0) {
// Unless it's steam fuckup and we're dealing with broken trade
return tradeOffer.ItemsToReceive.Count > 0 ? ParseTradeResult.AcceptedWithoutItemLose : ParseTradeResult.RejectedTemporarily;
return tradeOffer.ItemsToReceive.Count > 0 ? new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithoutItemLose) : new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
}
// Always accept trades from SteamMasterID
if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) {
return ParseTradeResult.AcceptedWithItemLose;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose);
}
// If we don't have SteamTradeMatcher enabled, this is the end for us
if (!Bot.BotConfig.SteamTradeMatcher) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
// Decline trade if we're giving more count-wise
if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
// At this point we're sure that STM trade is valid
@@ -191,14 +209,14 @@ namespace ArchiSteamFarm {
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false);
if (!holdDuration.HasValue) {
// If we can't get trade hold duration, reject trade temporarily
return ParseTradeResult.RejectedTemporarily;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
}
// If user has a trade hold, we add extra logic
if (holdDuration.Value > 0) {
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
}
@@ -207,7 +225,7 @@ namespace ArchiSteamFarm {
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return ParseTradeResult.AcceptedWithItemLose; // OK, assume that this trade is valid, we can't check our EQ
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose); // OK, assume that this trade is valid, we can't check our EQ
}
// Get appIDs we're interested in
@@ -218,7 +236,7 @@ namespace ArchiSteamFarm {
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
if (inventory.Count == 0) {
return ParseTradeResult.AcceptedWithItemLose;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose);
}
// Now let's create a map which maps items to their amount in our EQ
@@ -271,7 +289,7 @@ namespace ArchiSteamFarm {
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
// Trade is worth for us if the difference is greater than 0
return difference > 0 ? ParseTradeResult.AcceptedWithItemLose : ParseTradeResult.RejectedTemporarily;
return difference > 0 ? new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose) : new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
}
}
}

View File

@@ -26,6 +26,7 @@ using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
namespace ArchiSteamFarm {
[ServiceContract]
@@ -75,8 +76,8 @@ namespace ArchiSteamFarm {
}
public void Dispose() {
ServiceHost?.Close();
Client?.Close();
StopServer();
}
internal bool IsServerRunning() => ServiceHost != null;
@@ -87,15 +88,18 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericInfo("Starting WCF server...");
ServiceHost = new ServiceHost(typeof(WCF));
ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), URL);
try {
ServiceHost = new ServiceHost(typeof(WCF), new Uri(URL));
ServiceHost.Description.Behaviors.Add(new ServiceMetadataBehavior {
HttpGetEnabled = true
});
ServiceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), string.Empty);
ServiceHost.Open();
} catch (AddressAccessDeniedException) {
Logging.LogGenericWarning("WCF service could not be started because of AddressAccessDeniedException");
Logging.LogGenericWarning("If you want to use WCF service provided by ASF, consider starting ASF as administrator, or giving proper permissions");
return;
} catch (Exception e) {
Logging.LogGenericException(e);
return;
@@ -109,7 +113,14 @@ namespace ArchiSteamFarm {
return;
}
ServiceHost.Close();
if (ServiceHost.State != CommunicationState.Closed) {
try {
ServiceHost.Close();
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
ServiceHost = null;
}

View File

@@ -39,8 +39,6 @@ namespace ArchiSteamFarm {
private const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
private const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
private static readonly string DefaultUserAgent = "ArchiSteamFarm/" + Program.Version;
internal readonly CookieContainer CookieContainer = new CookieContainer();
private readonly HttpClient HttpClient;
@@ -57,11 +55,21 @@ namespace ArchiSteamFarm {
ServicePointManager.Expect100Continue = false;
#if !__MonoCS__
// Reuse ports if possible (since .NET 4.6+)
//ServicePointManager.ReusePort = true;
// We run Windows-compiled ASF on both Windows and Mono. Normally we'd simply put code in the if
// However, if we did that, then we would still crash on Mono due to potentially calling non-existing methods
// Therefore, call mono-incompatible options in their own function to avoid that, and just leave the function call here
// When compiling on Mono, this section is omitted entirely as we never run Mono-compiled ASF on Windows
// Moreover, Mono compiler doesn't even include ReusePort field in ServicePointManager, so it's crucial to avoid compilation error
if (!Runtime.IsRunningOnMono) {
InitNonMonoBehaviour();
}
#endif
}
#if !__MonoCS__
private static void InitNonMonoBehaviour() => ServicePointManager.ReusePort = true;
#endif
internal WebBrowser(string identifier) {
if (string.IsNullOrEmpty(identifier)) {
throw new ArgumentNullException(nameof(identifier));
@@ -79,10 +87,7 @@ namespace ArchiSteamFarm {
};
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
// We should always operate in English language, declare it globally
HttpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.8,en-GB;q=0.6");
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + Program.Version);
}
internal async Task<bool> UrlHeadRetry(string request, string referer = null) {
@@ -308,15 +313,15 @@ namespace ArchiSteamFarm {
return null;
}
string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) {
string json = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
return null;
}
JObject jObject;
try {
jObject = JObject.Parse(content);
jObject = JObject.Parse(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Identifier);
return null;
@@ -340,15 +345,15 @@ namespace ArchiSteamFarm {
return null;
}
string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) {
string xml = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(xml)) {
return null;
}
XmlDocument xmlDocument = new XmlDocument();
try {
xmlDocument.LoadXml(content);
xmlDocument.LoadXml(xml);
} catch (XmlException e) {
Logging.LogGenericException(e, Identifier);
return null;

View File

@@ -33,6 +33,8 @@ namespace ConfigGenerator {
internal string FilePath { get; set; }
private readonly object FileLock = new object();
protected ASFConfig() {
ASFConfigs.Add(this);
}
@@ -46,7 +48,7 @@ namespace ConfigGenerator {
}
internal void Save() {
lock (FilePath) {
lock (FileLock) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
} catch (Exception e) {
@@ -57,7 +59,7 @@ namespace ConfigGenerator {
internal void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) {
lock (FileLock) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try {
File.Delete(botFile);
@@ -77,7 +79,7 @@ namespace ConfigGenerator {
}
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) {
lock (FileLock) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try {
File.Move(botFile, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(botFile)));

View File

@@ -39,9 +39,6 @@ namespace ConfigGenerator {
private const string ASFDirectory = "ArchiSteamFarm";
private const string ASFExecutableFile = ASF + ".exe";
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
private static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
/// <summary>
/// The main entry point for the application.
/// </summary>
@@ -57,25 +54,28 @@ namespace ConfigGenerator {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
Directory.SetCurrentDirectory(ExecutableDirectory);
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (!string.IsNullOrEmpty(homeDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
if (!Directory.Exists(ASFDirectory)) {
continue;
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
if (!Directory.Exists(ASFDirectory)) {
continue;
}
Directory.SetCurrentDirectory(ASFDirectory);
break;
}
Directory.SetCurrentDirectory(ASFDirectory);
break;
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(ExecutableDirectory);
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
@@ -89,15 +89,17 @@ namespace ConfigGenerator {
}
FileVersionInfo asfVersionInfo = FileVersionInfo.GetVersionInfo(ASFExecutableFile);
Version asfVersion = new Version(asfVersionInfo.ProductVersion);
if (Version == asfVersion) {
Version cgVersion = Assembly.GetEntryAssembly().GetName().Version;
if (asfVersion == cgVersion) {
return;
}
Logging.LogGenericErrorWithoutStacktrace(
"Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine +
"ASF version: " + asfVersion + " | ConfigGenerator version: " + Version + Environment.NewLine +
"ASF version: " + asfVersion + " | ConfigGenerator version: " + cgVersion + Environment.NewLine +
Environment.NewLine +
"Please use ConfigGenerator from the same ASF release, I'll redirect you to appropriate ASF release..."
);
@@ -107,8 +109,8 @@ namespace ConfigGenerator {
}
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if ((sender == null) || (args == null) || (args.ExceptionObject == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args) + " || " + nameof(args.ExceptionObject));
if (args?.ExceptionObject == null) {
Logging.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
return;
}
@@ -116,8 +118,8 @@ namespace ConfigGenerator {
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if ((sender == null) || (args == null) || (args.Exception == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args) + " || " + nameof(args.Exception));
if (args?.Exception == null) {
Logging.LogNullError(nameof(args) + " || " + nameof(args.Exception));
return;
}

11
run.sh
View File

@@ -2,12 +2,14 @@
set -eu
BUILD="Release"
UNTIL_CLEAN_EXIT=0
ASF_ARGS=("")
MONO_ARGS=("--llvm" "--server" "-O=all")
PRINT_USAGE() {
echo "Usage: $0 [--until-clean-exit] [debug/release]"
echo "Usage: $0 [--until-clean-exit] [--cryptkey=] [--path=] [--server] [debug/release]"
exit 1
}
@@ -15,6 +17,9 @@ for ARG in "$@"; do
case "$ARG" in
release|Release) BUILD="Release" ;;
debug|Debug) BUILD="Debug" ;;
--cryptkey=*) ASF_ARGS+=("$ARG") ;;
--path=*) ASF_ARGS+=("$ARG") ;;
--server) ASF_ARGS+=("$ARG") ;;
--until-clean-exit) UNTIL_CLEAN_EXIT=1 ;;
*) PRINT_USAGE
esac
@@ -34,12 +39,12 @@ if [[ ! -f "$BINARY" ]]; then
fi
if [[ "$UNTIL_CLEAN_EXIT" -eq 0 ]]; then
mono "${MONO_ARGS[@]}" "$BINARY"
mono "${MONO_ARGS[@]}" "$BINARY" "${ASF_ARGS[@]}"
exit $?
fi
while [[ -f "$BINARY" ]]; do
if mono "${MONO_ARGS[@]}" "$BINARY"; then
if mono "${MONO_ARGS[@]}" "$BINARY" "${ASF_ARGS[@]}"; then
break
fi
sleep 1