mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-16 06:20:34 +00:00
Fix potential STM logic exploit
Previously we calculated amounts (and therefore, differences) based on cards only, which means that user could earn some extra 'points' in our calculation for giving us many trash cards that we miss, and in exchange for that swap cards from some other game that would normally results in non-neutral difference on that game basis, but neutral+ in overall basis (that we operated on). This would not cause any serious problem, because ASF ensures that trade is fair before calculating actual neutrality, but it could result in accepting a trade that is neutral+ only at first sight, and non-neutral+ when we take a closer look what we're actually swapping. The logic was enhanced to handle differences on per-game basis now, and taking minimum in account, so all swaps on per-game basis have to be neutral+ now
This commit is contained in:
@@ -375,4 +375,5 @@
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -477,7 +477,7 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable, HashSet<Steam.Item.EType> wantedTypes) {
|
||||
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable, HashSet<Steam.Item.EType> wantedTypes, HashSet<uint> wantedRealAppIDs = null) {
|
||||
if ((wantedTypes == null) || (wantedTypes.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(wantedTypes));
|
||||
return null;
|
||||
@@ -576,7 +576,7 @@ namespace ArchiSteamFarm {
|
||||
steamItem.Type = description.Item2;
|
||||
}
|
||||
|
||||
if (!wantedTypes.Contains(steamItem.Type)) {
|
||||
if (!wantedTypes.Contains(steamItem.Type) || (wantedRealAppIDs?.Contains(steamItem.RealAppID) == false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -245,75 +245,82 @@ namespace ArchiSteamFarm {
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose);
|
||||
}
|
||||
|
||||
// Get appIDs we're interested in
|
||||
HashSet<uint> appIDs = new HashSet<uint>(tradeOffer.ItemsToGive.Select(item => item.RealAppID));
|
||||
|
||||
// Now check if it's worth for us to do the trade
|
||||
await LimitInventoryRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, new HashSet<Steam.Item.EType> { Steam.Item.EType.TradingCard }).ConfigureAwait(false);
|
||||
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, new HashSet<Steam.Item.EType> { Steam.Item.EType.TradingCard }, appIDs).ConfigureAwait(false);
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
// If we can't check our inventory when not using MatchEverything, this is a temporary failure
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
|
||||
}
|
||||
|
||||
// Get appIDs we're interested in
|
||||
HashSet<uint> appIDs = new HashSet<uint>(tradeOffer.ItemsToGive.Select(item => item.RealAppID));
|
||||
|
||||
// Now remove from our inventory all items we're NOT interested in
|
||||
inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID));
|
||||
|
||||
// If for some reason Valve is talking crap and we can't find mentioned items, this is a temporary failure
|
||||
if (inventory.Count == 0) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
|
||||
}
|
||||
|
||||
// Now let's create a map which maps items to their amount in our EQ
|
||||
Dictionary<ulong, uint> amountMap = new Dictionary<ulong, uint>();
|
||||
// This has to be done as we might have multiple items of given ClassID with multiple amounts
|
||||
Dictionary<ulong, uint> itemAmounts = new Dictionary<ulong, uint>();
|
||||
foreach (Steam.Item item in inventory) {
|
||||
if (amountMap.TryGetValue(item.ClassID, out uint amount)) {
|
||||
amountMap[item.ClassID] = amount + item.Amount;
|
||||
if (itemAmounts.TryGetValue(item.ClassID, out uint amount)) {
|
||||
itemAmounts[item.ClassID] = amount + item.Amount;
|
||||
} else {
|
||||
amountMap[item.ClassID] = item.Amount;
|
||||
itemAmounts[item.ClassID] = item.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate our value of items to give
|
||||
List<uint> amountsToGive = new List<uint>(tradeOffer.ItemsToGive.Count);
|
||||
Dictionary<ulong, uint> amountMapToGive = new Dictionary<ulong, uint>(amountMap);
|
||||
foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) {
|
||||
if (!amountMapToGive.TryGetValue(key, out uint amount)) {
|
||||
// Calculate our value of items to give on per-game basis
|
||||
Dictionary<uint, List<uint>> itemAmountToGivePerGame = new Dictionary<uint, List<uint>>(appIDs.Count);
|
||||
Dictionary<ulong, uint> itemAmountsToGive = new Dictionary<ulong, uint>(itemAmounts);
|
||||
foreach (Steam.Item item in tradeOffer.ItemsToGive) {
|
||||
if (!itemAmountToGivePerGame.TryGetValue(item.RealAppID, out List<uint> amountsToGive)) {
|
||||
amountsToGive = new List<uint>();
|
||||
itemAmountToGivePerGame[item.RealAppID] = amountsToGive;
|
||||
}
|
||||
|
||||
if (!itemAmountsToGive.TryGetValue(item.ClassID, out uint amount)) {
|
||||
amountsToGive.Add(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
amountsToGive.Add(amount);
|
||||
amountMapToGive[key] = amount - 1; // We're giving one, so we have one less
|
||||
itemAmountsToGive[item.ClassID] = amount - 1; // We're giving one, so we have one less
|
||||
}
|
||||
|
||||
// Sort it ascending
|
||||
amountsToGive.Sort();
|
||||
// Sort all the lists of amounts to give on per-game basis ascending
|
||||
foreach (List<uint> amountsToGive in itemAmountToGivePerGame.Values) {
|
||||
amountsToGive.Sort();
|
||||
}
|
||||
|
||||
// Calculate our value of items to receive
|
||||
List<uint> amountsToReceive = new List<uint>(tradeOffer.ItemsToReceive.Count);
|
||||
Dictionary<ulong, uint> amountMapToReceive = new Dictionary<ulong, uint>(amountMap);
|
||||
foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) {
|
||||
if (!amountMapToReceive.TryGetValue(key, out uint amount)) {
|
||||
// Calculate our value of items to receive on per-game basis
|
||||
Dictionary<uint, List<uint>> itemAmountToReceivePerGame = new Dictionary<uint, List<uint>>(appIDs.Count);
|
||||
Dictionary<ulong, uint> itemAmountsToReceive = new Dictionary<ulong, uint>(itemAmounts);
|
||||
foreach (Steam.Item item in tradeOffer.ItemsToReceive) {
|
||||
if (!itemAmountToReceivePerGame.TryGetValue(item.RealAppID, out List<uint> amountsToReceive)) {
|
||||
amountsToReceive = new List<uint>();
|
||||
itemAmountToReceivePerGame[item.RealAppID] = amountsToReceive;
|
||||
}
|
||||
|
||||
if (!itemAmountsToReceive.TryGetValue(item.ClassID, out uint amount)) {
|
||||
amountsToReceive.Add(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
amountsToReceive.Add(amount);
|
||||
amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more
|
||||
itemAmountsToReceive[item.ClassID] = amount + 1; // We're getting one, so we have one more
|
||||
}
|
||||
|
||||
// Sort it ascending
|
||||
amountsToReceive.Sort();
|
||||
// Sort all the lists of amounts to receive on per-game basis ascending
|
||||
foreach (List<uint> amountsToReceive in itemAmountToReceivePerGame.Values) {
|
||||
amountsToReceive.Sort();
|
||||
}
|
||||
|
||||
// Check actual difference
|
||||
// We sum only values at proper indexes of giving, because user might be overpaying
|
||||
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
|
||||
// Calculate final neutrality result
|
||||
// This is quite complex operation of taking minimum difference from all differences on per-game basis
|
||||
// When calculating per-game difference, we sum only amounts at proper indexes, because user might be overpaying
|
||||
int difference = itemAmountToGivePerGame.Min(kv => kv.Value.Select((t, i) => (int) (t - itemAmountToReceivePerGame[kv.Key][i])).Sum());
|
||||
|
||||
// Trade is worth for us if the difference is greater than 0
|
||||
// Trade is neutral+ for us if the difference is greater than 0
|
||||
// If not, we assume that the trade might be good for us in the future, unless we're bot account where we assume that inventory doesn't change
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, difference > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user