Compare commits

..

29 Commits

Author SHA1 Message Date
JustArchi
51abdfb899 Bring back HackedLogOn()
It's still needed...
2016-07-12 03:56:35 +02:00
JustArchi
658d6129a7 Add --until-clean-exit 2016-07-11 19:22:25 +02:00
JustArchi
55c1674309 Add --aot to usage 2016-07-11 19:08:08 +02:00
JustArchi
9efae3edae Don't AOT by default 2016-07-11 19:07:17 +02:00
JustArchi
d1e047edf8 Remove old converter 2016-07-10 20:57:59 +02:00
JustArchi
8742ace3d7 Bump 2016-07-10 20:30:00 +02:00
JustArchi
feb3fbfe59 Get used to null propagation 2016-07-10 20:17:35 +02:00
JustArchi
0b06df4352 Misc 2016-07-10 20:10:14 +02:00
JustArchi
10b1d02a95 Rename JsonStorage -> InMemory 2016-07-09 12:24:21 +02:00
JustArchi
7731360f09 One more correction, thanks @Netshroud 2016-07-09 11:39:54 +02:00
JustArchi
02d3454d1b Avoid enumerating over Servers 2016-07-09 11:15:25 +02:00
JustArchi
54b8d3e6f3 Use extended result where appropriate 2016-07-09 09:20:56 +02:00
Łukasz Domeradzki
0b65e1d9a1 Merge pull request #286 from justin-gerhardt/master
preventing the creation of the default event source
2016-07-08 11:12:10 +02:00
Justin Gerhardt
1f5005f14d preventing the creation of the default event source 2016-07-08 05:04:48 -04:00
JustArchi
6414f375fd Bump 2016-07-08 10:30:08 +02:00
JustArchi
b930f513b4 Fix non-necessary extra event 2016-07-08 09:58:08 +02:00
JustArchi
84419bec2a Use events for handling Save() 2016-07-08 09:48:15 +02:00
JustArchi
deed19e732 Bump 2016-07-08 09:12:14 +02:00
JustArchi
7e9844edab Implement and use persistent ServerList
Initial idea was suggested back in #66, but recently made possible in clean and nice way, thanks to @azuisleet and @Netshroud
2016-07-08 09:02:53 +02:00
JustArchi
ce4733547c Misc 2016-07-08 07:43:02 +02:00
JustArchi
561fb66e7a Correct harmless VS code analysis warnings 2016-07-08 07:41:36 +02:00
JustArchi
77413f13e2 Kill HackIgnoreMachineID with fire 2016-07-08 07:16:42 +02:00
JustArchi
1ca31ee33e Upgrade old SK2 conversions 2016-07-08 07:11:37 +02:00
JustArchi
4059a2cd11 Bring SK2 1.8.0
Finally... :)
2016-07-08 07:08:07 +02:00
JustArchi
23af6efb4b Misc
We expect this to be refilled with nearly the same amount of entries, so trimming is not really needed
2016-07-08 06:48:38 +02:00
JustArchi
09b2b29ff4 Avoid excessive TrimExcess()
It's really good for keeping memory low, but due to limited lifespan of those objects and the fact that we should focus on performance and not memory, it's better to avoid extra overhead and let GC do it's job, by dropping references to those objects ASAP

TrimExcess() is still being used for global objects, as those have longer (possibly infinite) lifespan and can benefit from that.
2016-07-08 06:45:17 +02:00
JustArchi
412028eff7 Misc 2016-07-08 00:02:04 +02:00
JustArchi
a5c85a73bc And when being invoked during waiting too 2016-07-07 23:36:35 +02:00
JustArchi
b40e87b25e Make ASF work with NLog autoReload=true 2016-07-07 23:27:34 +02:00
36 changed files with 596 additions and 337 deletions

View File

@@ -10,6 +10,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FS/@EntryIndexedValue">FS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OK/@EntryIndexedValue">OK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PIN/@EntryIndexedValue">PIN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SC/@EntryIndexedValue">SC</s:String>

View File

@@ -159,7 +159,7 @@ namespace ArchiSteamFarm {
Items = new Dictionary<uint, string>(lineItems.Count);
foreach (KeyValue lineItem in lineItems) {
uint appID = (uint) lineItem["PackageID"].AsUnsignedLong();
uint appID = lineItem["PackageID"].AsUnsignedInteger();
string gameName = lineItem["ItemDescription"].Value;
gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML
Items[appID] = gameName;
@@ -177,6 +177,44 @@ namespace ArchiSteamFarm {
*/
// TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) {
ClientMsgProtobuf<CMsgClientLogon> logon = new ClientMsgProtobuf<CMsgClientLogon>(EMsg.ClientLogon);
SteamID steamId = new SteamID(details.AccountID, details.AccountInstance, Client.ConnectedUniverse, EAccountType.Individual);
if (details.LoginID != null) {
logon.Body.obfustucated_private_ip = details.LoginID.Value;
}
logon.ProtoHeader.client_sessionid = 0;
logon.ProtoHeader.steamid = steamId.ConvertToUInt64();
logon.Body.account_name = details.Username;
logon.Body.password = details.Password;
logon.Body.should_remember_password = details.ShouldRememberPassword;
logon.Body.protocol_version = MsgClientLogon.CurrentProtocol;
logon.Body.client_os_type = (uint) details.ClientOSType;
logon.Body.client_language = details.ClientLanguage;
logon.Body.cell_id = details.CellID;
logon.Body.steam2_ticket_request = details.RequestSteam2Ticket;
logon.Body.client_package_version = 1771;
logon.Body.auth_code = details.AuthCode;
logon.Body.two_factor_code = details.TwoFactorCode;
logon.Body.login_key = details.LoginKey;
logon.Body.sha_sentryfile = details.SentryFileHash;
logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound);
Client.Send(logon);
}
internal void PlayGame(string gameName) {
if (!Client.IsConnected) {
return;
@@ -250,39 +288,6 @@ namespace ArchiSteamFarm {
}
}
// TODO: Please remove me immediately after https://github.com/SteamRE/SteamKit/issues/254 gets fixed
internal void HackedLogOn(SteamUser.LogOnDetails details) {
if (!Client.IsConnected) {
return;
}
SteamID steamID = new SteamID(details.AccountID, details.AccountInstance, Client.ConnectedUniverse, EAccountType.Individual);
ClientMsgProtobuf<CMsgClientLogon> logon = new ClientMsgProtobuf<CMsgClientLogon>(EMsg.ClientLogon);
if (details.LoginID != null) {
logon.Body.obfustucated_private_ip = details.LoginID.Value;
}
logon.ProtoHeader.client_sessionid = 0;
logon.ProtoHeader.steamid = steamID.ConvertToUInt64();
logon.Body.account_name = details.Username;
logon.Body.password = details.Password;
logon.Body.should_remember_password = details.ShouldRememberPassword;
logon.Body.protocol_version = MsgClientLogon.CurrentProtocol;
logon.Body.client_os_type = (uint) details.ClientOSType;
logon.Body.client_language = details.ClientLanguage;
logon.Body.cell_id = details.CellID;
logon.Body.steam2_ticket_request = details.RequestSteam2Ticket;
logon.Body.client_package_version = 1771;
logon.Body.auth_code = details.AuthCode;
logon.Body.two_factor_code = details.TwoFactorCode;
logon.Body.login_key = details.LoginKey;
logon.Body.sha_sentryfile = details.SentryFileHash;
logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound);
Client.Send(logon);
}
/*
_ _ _ _
| | | | __ _ _ __ __| || | ___ _ __ ___

View File

@@ -46,6 +46,8 @@ namespace ArchiSteamFarm {
// System account, requires admin privilege to install
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller.Installers.Clear();
EventLogInstaller logInstaller = new EventLogInstaller {
Log = SharedInfo.EventLog,
Source = SharedInfo.EventLogSource

View File

@@ -39,6 +39,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>3</WarningLevel>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -89,8 +90,8 @@
<HintPath>..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SteamKit2, Version=1.7.0.33680, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SteamKit2.1.7.0\lib\net45\SteamKit2.dll</HintPath>
<Reference Include="SteamKit2, Version=1.8.0.26737, Culture=neutral, PublicKeyToken=ed3ce47ed5aad940, processorArchitecture=MSIL">
<HintPath>..\packages\SteamKit2.1.8.0\lib\net45\SteamKit2.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -124,12 +125,14 @@
<Compile Include="CardsFarmer.cs" />
<Compile Include="Debugging.cs" />
<Compile Include="GlobalConfig.cs" />
<Compile Include="IPAddressConverter.cs" />
<Compile Include="IPEndPointConverter.cs" />
<Compile Include="JSON\GitHub.cs" />
<Compile Include="JSON\Steam.cs" />
<Compile Include="Logging.cs" />
<Compile Include="InMemoryServerListProvider.cs" />
<Compile Include="MobileAuthenticator.cs" />
<Compile Include="Runtime.cs" />
<Compile Include="ObsoleteSteamGuardAccount.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SharedInfo.cs" />

View File

@@ -36,7 +36,7 @@ using System.Threading;
using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler {
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
@@ -111,7 +111,7 @@ namespace ArchiSteamFarm {
}
foreach (KeyValue item in input) {
uint appID = (uint) item["appid"].AsUnsignedLong();
uint appID = item["appid"].AsUnsignedInteger();
if (appID == 0) {
Logging.LogNullError(nameof(appID));
return false;
@@ -129,7 +129,7 @@ namespace ArchiSteamFarm {
return false;
}
uint amount = (uint) item["amount"].AsUnsignedLong();
uint amount = item["amount"].AsUnsignedInteger();
if (amount == 0) {
Logging.LogNullError(nameof(amount));
return false;
@@ -161,6 +161,8 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser(bot.BotName);
}
public void Dispose() => SessionSemaphore.Dispose();
internal void OnDisconnected() => Ready = false;
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
@@ -375,11 +377,8 @@ namespace ArchiSteamFarm {
string request = SteamCommunityURL + "/my/games/?xml=1";
XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false);
if (response == null) {
return null;
}
XmlNodeList xmlNodeList = response.SelectNodes("gamesList/games/game");
XmlNodeList xmlNodeList = response?.SelectNodes("gamesList/games/game");
if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) {
return null;
}
@@ -440,7 +439,7 @@ namespace ArchiSteamFarm {
Dictionary<uint, string> result = new Dictionary<uint, string>(response["games"].Children.Count);
foreach (KeyValue game in response["games"].Children) {
uint appID = (uint) game["appid"].AsUnsignedLong();
uint appID = game["appid"].AsUnsignedInteger();
if (appID == 0) {
Logging.LogNullError(nameof(appID), Bot.BotName);
return null;
@@ -470,7 +469,7 @@ namespace ArchiSteamFarm {
}
if (response != null) {
return (uint) response["server_time"].AsUnsignedLong();
return response["server_time"].AsUnsignedInteger();
}
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
@@ -490,11 +489,8 @@ namespace ArchiSteamFarm {
string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
if (htmlNode == null) { // Trade can be no longer valid
return null;
}
@@ -580,7 +576,7 @@ namespace ArchiSteamFarm {
}
if (appID == 0) {
appID = (uint) description["appid"].AsUnsignedLong();
appID = description["appid"].AsUnsignedInteger();
}
Steam.Item.EType type = Steam.Item.EType.Unknown;
@@ -611,7 +607,7 @@ namespace ArchiSteamFarm {
return null;
}
uint otherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong();
uint otherSteamID3 = trade["accountid_other"].AsUnsignedInteger();
if (otherSteamID3 == 0) {
Logging.LogNullError(nameof(otherSteamID3));
return null;
@@ -711,11 +707,8 @@ namespace ArchiSteamFarm {
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=" + (tradable ? "1" : "0") + "&start=" + currentPage;
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request).ConfigureAwait(false);
if (jObject == null) {
return null;
}
IEnumerable<JToken> descriptions = jObject.SelectTokens("$.rgDescriptions.*");
IEnumerable<JToken> descriptions = jObject?.SelectTokens("$.rgDescriptions.*");
if (descriptions == null) {
return null; // OK, empty inventory
}
@@ -911,11 +904,7 @@ namespace ArchiSteamFarm {
string request = SteamCommunityURL + "/my/videos";
Uri uri = await WebBrowser.UrlHeadToUriRetry(request).ConfigureAwait(false);
if (uri == null) {
return null;
}
return !uri.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
return !uri?.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
}
private async Task<bool> RefreshSessionIfNeeded() {

View File

@@ -35,9 +35,10 @@ using System.Threading.Tasks;
using System.Text;
using System.Text.RegularExpressions;
using ArchiSteamFarm.JSON;
using SteamKit2.Discovery;
namespace ArchiSteamFarm {
internal sealed class Bot {
internal sealed class Bot : IDisposable {
private const ulong ArchiSCFarmGroup = 103582791440160998;
private const ushort CallbackSleep = 500; // In miliseconds
private const ushort MaxSteamMessageLength = 2048;
@@ -76,24 +77,14 @@ namespace ArchiSteamFarm {
private bool FirstTradeSent, InvalidPassword, SkipFirstShutdown;
private string AuthCode, TwoFactorCode;
internal static async Task RefreshCMs(uint cellID) {
bool initialized = false;
for (byte i = 0; (i < WebBrowser.MaxRetries) && !initialized; i++) {
try {
Logging.LogGenericInfo("Refreshing list of CMs...");
await SteamDirectory.Initialize(cellID).ConfigureAwait(false);
initialized = true;
} catch (Exception e) {
Logging.LogGenericException(e);
await Task.Delay(1000).ConfigureAwait(false);
}
internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) {
if (serverListProvider == null) {
Logging.LogNullError(nameof(serverListProvider));
return;
}
if (initialized) {
Logging.LogGenericInfo("Success!");
} else {
Logging.LogGenericWarning("Failed to initialize list of CMs after " + WebBrowser.MaxRetries + " tries, ASF will use built-in SK2 list, it may take a while to connect");
}
CMClient.Servers.CellID = cellID;
CMClient.Servers.ServerListProvider = serverListProvider;
}
private static bool IsOwner(ulong steamID) {
@@ -161,15 +152,6 @@ namespace ArchiSteamFarm {
return;
}
// TODO: Converter code will be removed soon
if (BotDatabase.SteamGuardAccount != null) {
Logging.LogGenericWarning("Converting old ASF 2FA V2.0 format into new ASF 2FA V2.1 format...", botName);
BotDatabase.MobileAuthenticator = MobileAuthenticator.LoadFromSteamGuardAccount(BotDatabase.SteamGuardAccount);
Logging.LogGenericInfo("Done! If you didn't make a copy of your revocation code yet, then it's a good moment to do so: " + BotDatabase.SteamGuardAccount.RevocationCode, botName);
Logging.LogGenericWarning("ASF will not keep this code anymore!", botName);
BotDatabase.SteamGuardAccount = null;
}
if (BotDatabase.MobileAuthenticator != null) {
BotDatabase.MobileAuthenticator.Init(this);
} else {
@@ -256,6 +238,18 @@ namespace ArchiSteamFarm {
Start().Forget();
}
public void Dispose() {
GiftsSemaphore.Dispose();
LoginSemaphore.Dispose();
HandledGifts.Dispose();
AcceptConfirmationsTimer?.Dispose();
ArchiWebHandler?.Dispose();
CardsFarmer?.Dispose();
SendItemsTimer?.Dispose();
Trading?.Dispose();
}
internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
if (BotDatabase.MobileAuthenticator == null) {
return;
@@ -268,7 +262,6 @@ namespace ArchiSteamFarm {
if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) {
if (confirmations.RemoveWhere(confirmation => (confirmation.Type != acceptedType) && (confirmation.Type != Steam.ConfirmationDetails.EType.Other)) > 0) {
confirmations.TrimExcess();
if (confirmations.Count == 0) {
return;
}
@@ -288,7 +281,6 @@ namespace ArchiSteamFarm {
if (ignoredConfirmationIDs.Count > 0) {
if (confirmations.RemoveWhere(confirmation => ignoredConfirmationIDs.Contains(confirmation.ID)) > 0) {
confirmations.TrimExcess();
if (confirmations.Count == 0) {
return;
}
@@ -318,7 +310,7 @@ namespace ArchiSteamFarm {
return false;
}
if ((callback == null) || string.IsNullOrEmpty(callback.Nonce)) {
if (string.IsNullOrEmpty(callback?.Nonce)) {
Start().Forget();
return false;
}
@@ -717,7 +709,6 @@ namespace ArchiSteamFarm {
// Remove from our pending inventory all items that are not steam cards and boosters
inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && (item.Type != Steam.Item.EType.FoilTradingCard) && (item.Type != Steam.Item.EType.BoosterPack));
inventory.TrimExcess();
if (inventory.Count == 0) {
return "Nothing to send, inventory seems empty!";
@@ -1530,46 +1521,23 @@ namespace ArchiSteamFarm {
TwoFactorCode = await BotDatabase.MobileAuthenticator.GenerateToken().ConfigureAwait(false);
}
if (Program.GlobalConfig.HackIgnoreMachineID) {
Logging.LogGenericWarning("Using workaround for broken GenerateMachineID()!", BotName);
ArchiHandler.HackedLogOn(new SteamUser.LogOnDetails {
Username = BotConfig.SteamLogin,
Password = BotConfig.SteamPassword,
AuthCode = AuthCode,
CellID = Program.GlobalDatabase.CellID,
LoginID = LoginID,
LoginKey = BotDatabase.LoginKey,
TwoFactorCode = TwoFactorCode,
SentryFileHash = sentryHash,
ShouldRememberPassword = true
});
return;
}
SteamUser.LogOnDetails logOnDetails = new SteamUser.LogOnDetails {
Username = BotConfig.SteamLogin,
Password = BotConfig.SteamPassword,
AuthCode = AuthCode,
CellID = Program.GlobalDatabase.CellID,
LoginID = LoginID,
LoginKey = BotDatabase.LoginKey,
TwoFactorCode = TwoFactorCode,
SentryFileHash = sentryHash,
ShouldRememberPassword = true
};
try {
SteamUser.LogOn(new SteamUser.LogOnDetails {
Username = BotConfig.SteamLogin,
Password = BotConfig.SteamPassword,
AuthCode = AuthCode,
CellID = Program.GlobalDatabase.CellID,
LoginID = LoginID,
LoginKey = BotDatabase.LoginKey,
TwoFactorCode = TwoFactorCode,
SentryFileHash = sentryHash,
ShouldRememberPassword = true
});
} catch (Exception) {
ArchiHandler.HackedLogOn(new SteamUser.LogOnDetails {
Username = BotConfig.SteamLogin,
Password = BotConfig.SteamPassword,
AuthCode = AuthCode,
CellID = Program.GlobalDatabase.CellID,
LoginID = LoginID,
LoginKey = BotDatabase.LoginKey,
TwoFactorCode = TwoFactorCode,
SentryFileHash = sentryHash,
ShouldRememberPassword = true
});
SteamUser.LogOn(logOnDetails);
} catch {
// TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
ArchiHandler.LogOnWithoutMachineID(logOnDetails);
}
}
@@ -1625,7 +1593,7 @@ namespace ArchiSteamFarm {
}
private async void OnGuestPassList(SteamApps.GuestPassListCallback callback) {
if ((callback == null) || (callback.GuestPasses == null)) {
if (callback?.GuestPasses == null) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.GuestPasses), BotName);
return;
}
@@ -1663,7 +1631,7 @@ namespace ArchiSteamFarm {
}
private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
if ((callback == null) || (callback.ChatRoomID == null) || (callback.PatronID == null)) {
if ((callback?.ChatRoomID == null) || (callback.PatronID == null)) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.ChatRoomID) + " || " + nameof(callback.PatronID), BotName);
return;
}
@@ -1705,7 +1673,7 @@ namespace ArchiSteamFarm {
}
private void OnFriendsList(SteamFriends.FriendsListCallback callback) {
if ((callback == null) || (callback.FriendList == null)) {
if (callback?.FriendList == null) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.FriendList), BotName);
return;
}
@@ -1738,7 +1706,7 @@ namespace ArchiSteamFarm {
}
private async void OnFriendMsgHistory(SteamFriends.FriendMsgHistoryCallback callback) {
if ((callback == null) || (callback.Messages == null) || (callback.SteamID == null)) {
if ((callback?.Messages == null) || (callback.SteamID == null)) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.Messages) + " || " + nameof(callback.SteamID), BotName);
return;
}
@@ -1811,7 +1779,7 @@ namespace ArchiSteamFarm {
break;
case EResult.InvalidPassword:
InvalidPassword = true;
Logging.LogGenericWarning("Unable to login to Steam: " + callback.Result, BotName);
Logging.LogGenericWarning("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult, BotName);
break;
case EResult.OK:
Logging.LogGenericInfo("Successfully logged on!", BotName);
@@ -1871,17 +1839,17 @@ namespace ArchiSteamFarm {
case EResult.ServiceUnavailable:
case EResult.Timeout:
case EResult.TryAnotherCM:
Logging.LogGenericWarning("Unable to login to Steam: " + callback.Result, BotName);
Logging.LogGenericWarning("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult, BotName);
break;
default: // Unexpected result, shutdown immediately
Logging.LogGenericError("Unable to login to Steam: " + callback.Result, BotName);
Logging.LogGenericError("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult, BotName);
Stop();
break;
}
}
private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
if ((callback == null) || string.IsNullOrEmpty(callback.LoginKey)) {
if (string.IsNullOrEmpty(callback?.LoginKey)) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey), BotName);
return;
}

View File

@@ -64,23 +64,7 @@ namespace ArchiSteamFarm {
}
}
// TODO: Converter code will be removed soon
[JsonProperty]
private ObsoleteSteamGuardAccount _SteamGuardAccount;
internal ObsoleteSteamGuardAccount SteamGuardAccount {
get {
return _SteamGuardAccount;
}
set {
if (_SteamGuardAccount == value) {
return;
}
_SteamGuardAccount = value;
Save();
}
}
private readonly object FileLock = new object();
private string FilePath;
@@ -133,7 +117,7 @@ namespace ArchiSteamFarm {
return;
}
lock (FilePath) {
lock (FileLock) {
for (byte i = 0; i < 5; i++) {
try {
File.WriteAllText(FilePath, json);

View File

@@ -34,7 +34,7 @@ using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer {
internal sealed class CardsFarmer : IDisposable {
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
[JsonProperty]
@@ -70,6 +70,14 @@ namespace ArchiSteamFarm {
}
}
public void Dispose() {
CurrentGamesFarming.Dispose();
FarmResetEvent.Dispose();
FarmingSemaphore.Dispose();
Timer?.Dispose();
}
internal async Task SwitchToManualMode(bool manualMode) {
if (ManualMode == manualMode) {
return;
@@ -128,7 +136,6 @@ namespace ArchiSteamFarm {
uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) {
gamesToFarmSolo.Remove(appID);
gamesToFarmSolo.TrimExcess();
} else {
NowFarming = false;
return;

View File

@@ -50,10 +50,6 @@ namespace ArchiSteamFarm {
public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset();
public void Dispose() {
if (Lock != null) {
Lock.ExitReadLock();
}
}
public void Dispose() => Lock?.ExitReadLock();
}
}

View File

@@ -100,11 +100,7 @@ namespace ArchiSteamFarm {
}
}
public void Dispose() {
if (Lock != null) {
Lock.Dispose();
}
}
public void Dispose() => Lock?.Dispose();
public void CopyTo(T[] array, int arrayIndex) {
Lock.EnterReadLock();

View File

@@ -40,6 +40,7 @@ namespace ArchiSteamFarm {
internal static bool NetHookAlreadyInitialized { get; set; }
internal sealed class DebugListener : IDebugListener {
private readonly object FileLock = new object();
private readonly string FilePath;
internal DebugListener(string filePath) {
@@ -56,7 +57,7 @@ namespace ArchiSteamFarm {
return;
}
lock (FilePath) {
lock (FileLock) {
try {
File.AppendAllText(FilePath, category + " | " + msg + Environment.NewLine);
} catch (Exception e) {

View File

@@ -107,10 +107,6 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true;
// TODO: Please remove me immediately after https://github.com/SteamRE/SteamKit/issues/254 gets fixed
[JsonProperty(Required = Required.DisallowNull)]
internal bool HackIgnoreMachineID { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint>(GlobalBlacklist);

View File

@@ -24,18 +24,29 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class GlobalDatabase {
private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings {
Converters = new List<JsonConverter> {
new IPAddressConverter(),
new IPEndPointConverter()
}
};
[JsonProperty(Required = Required.DisallowNull)]
private uint _CellID;
internal uint CellID {
get {
return _CellID;
}
set {
if (_CellID == value) {
if ((value == 0) || (_CellID == value)) {
return;
}
@@ -45,7 +56,10 @@ namespace ArchiSteamFarm {
}
[JsonProperty(Required = Required.DisallowNull)]
private uint _CellID;
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
internal InMemoryServerListProvider ServerListProvider { get; private set; } = new InMemoryServerListProvider();
private readonly object FileLock = new object();
private string FilePath;
@@ -62,7 +76,7 @@ namespace ArchiSteamFarm {
GlobalDatabase globalDatabase;
try {
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath));
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath), CustomSerializerSettings);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
@@ -77,8 +91,10 @@ namespace ArchiSteamFarm {
return globalDatabase;
}
private void OnServerListUpdated(object sender, EventArgs e) => Save();
// This constructor is used when creating new database
private GlobalDatabase(string filePath) {
private GlobalDatabase(string filePath) : this() {
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
@@ -89,16 +105,18 @@ namespace ArchiSteamFarm {
// This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private GlobalDatabase() { }
private GlobalDatabase() {
ServerListProvider.ServerListUpdated += OnServerListUpdated;
}
private void Save() {
string json = JsonConvert.SerializeObject(this);
string json = JsonConvert.SerializeObject(this, CustomSerializerSettings);
if (string.IsNullOrEmpty(json)) {
Logging.LogNullError(nameof(json));
return;
}
lock (FilePath) {
lock (FileLock) {
for (byte i = 0; i < 5; i++) {
try {
File.WriteAllText(FilePath, json);

View File

@@ -0,0 +1,44 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm {
internal sealed class IPAddressConverter : JsonConverter {
public override bool CanConvert(Type objectType) => objectType == typeof(IPAddress);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JToken token = JToken.Load(reader);
return IPAddress.Parse(token.Value<string>());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
IPAddress ip = (IPAddress) value;
writer.WriteValue(ip.ToString());
}
}
}

View File

@@ -0,0 +1,51 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm {
internal sealed class IPEndPointConverter : JsonConverter {
public override bool CanConvert(Type objectType) => objectType == typeof(IPEndPoint);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JObject jo = JObject.Load(reader);
IPAddress address = jo["Address"].ToObject<IPAddress>(serializer);
ushort port = jo["Port"].Value<ushort>();
return new IPEndPoint(address, port);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
IPEndPoint ep = (IPEndPoint) value;
writer.WriteStartObject();
writer.WritePropertyName("Address");
serializer.Serialize(writer, ep.Address);
writer.WritePropertyName("Port");
writer.WriteValue(ep.Port);
writer.WriteEndObject();
}
}
}

View File

@@ -0,0 +1,59 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SteamKit2.Discovery;
namespace ArchiSteamFarm {
internal sealed class InMemoryServerListProvider : IServerListProvider {
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
private HashSet<IPEndPoint> Servers = new HashSet<IPEndPoint>();
internal event EventHandler ServerListUpdated = delegate { };
public Task<IEnumerable<IPEndPoint>> FetchServerListAsync() => Task.FromResult<IEnumerable<IPEndPoint>>(Servers);
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endpoints) {
if (endpoints == null) {
Logging.LogNullError(nameof(endpoints));
return Task.Delay(0);
}
Servers.Clear();
foreach (IPEndPoint endpoint in endpoints) {
Servers.Add(endpoint);
}
ServerListUpdated(this, EventArgs.Empty);
return Task.Delay(0);
}
}
}

View File

@@ -23,7 +23,6 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -38,16 +37,17 @@ namespace ArchiSteamFarm {
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${level:uppercase=true}|" + LayoutMessage;
private const string EventLogLayout = LayoutMessage;
private static readonly HashSet<LoggingRule> ConsoleLoggingRules = new HashSet<LoggingRule>();
private static readonly ConcurrentHashSet<LoggingRule> ConsoleLoggingRules = new ConcurrentHashSet<LoggingRule>();
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static bool IsUsingCustomConfiguration;
private static bool IsUsingCustomConfiguration, IsWaitingForUserInput;
internal static void InitCoreLoggers() {
if (LogManager.Configuration != null) {
// User provided custom NLog config, or we have it set already, so don't override it
IsUsingCustomConfiguration = true;
InitConsoleLoggers();
LogManager.ConfigurationChanged += OnConfigurationChanged;
return;
}
@@ -94,6 +94,8 @@ namespace ArchiSteamFarm {
}
internal static void OnUserInputStart() {
IsWaitingForUserInput = true;
if (ConsoleLoggingRules.Count == 0) {
return;
}
@@ -106,11 +108,13 @@ namespace ArchiSteamFarm {
}
internal static void OnUserInputEnd() {
IsWaitingForUserInput = false;
if (ConsoleLoggingRules.Count == 0) {
return;
}
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) {
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules.Where(consoleLoggingRule => !LogManager.Configuration.LoggingRules.Contains(consoleLoggingRule))) {
LogManager.Configuration.LoggingRules.Add(consoleLoggingRule);
}
@@ -183,9 +187,23 @@ namespace ArchiSteamFarm {
}
private static void InitConsoleLoggers() {
ConsoleLoggingRules.Clear();
foreach (LoggingRule loggingRule in from loggingRule in LogManager.Configuration.LoggingRules from target in loggingRule.Targets where target is ColoredConsoleTarget || target is ConsoleTarget select loggingRule) {
ConsoleLoggingRules.Add(loggingRule);
}
}
private static void OnConfigurationChanged(object sender, LoggingConfigurationChangedEventArgs e) {
if ((sender == null) || (e == null)) {
LogNullError(nameof(sender) + " || " + nameof(e));
return;
}
InitConsoleLoggers();
if (IsWaitingForUserInput) {
OnUserInputStart();
}
}
}
}

View File

@@ -24,6 +24,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
@@ -33,6 +34,8 @@ using HtmlAgilityPack;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class MobileAuthenticator {
internal sealed class Confirmation {
internal readonly uint ID;
@@ -57,30 +60,19 @@ namespace ArchiSteamFarm {
internal bool HasDeviceID => !string.IsNullOrEmpty(DeviceID);
#pragma warning disable 649
[JsonProperty(PropertyName = "shared_secret", Required = Required.DisallowNull)]
private string SharedSecret;
[JsonProperty(PropertyName = "identity_secret", Required = Required.DisallowNull)]
private string IdentitySecret;
#pragma warning restore 649
[JsonProperty(PropertyName = "device_id")]
private string DeviceID;
private Bot Bot;
internal static MobileAuthenticator LoadFromSteamGuardAccount(ObsoleteSteamGuardAccount sga) {
if (sga != null) {
return new MobileAuthenticator {
SharedSecret = sga.SharedSecret,
IdentitySecret = sga.IdentitySecret,
DeviceID = sga.DeviceID
};
}
Logging.LogNullError(nameof(sga));
return null;
}
private MobileAuthenticator() {
}
@@ -173,11 +165,8 @@ namespace ArchiSteamFarm {
}
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetConfirmations(DeviceID, confirmationHash, time).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
HtmlNodeCollection confirmationNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='mobileconf_list_entry']");
HtmlNodeCollection confirmationNodes = htmlDocument?.DocumentNode.SelectNodes("//div[@class='mobileconf_list_entry']");
if (confirmationNodes == null) {
return null;
}

View File

@@ -1,49 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
// TODO: This will be completely removed soon
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class ObsoleteSteamGuardAccount {
[JsonProperty("shared_secret")]
public string SharedSecret { get; set; }
[JsonProperty("serial_number")]
public string SerialNumber { get; set; }
[JsonProperty("revocation_code")]
public string RevocationCode { get; set; }
[JsonProperty("uri")]
public string URI { get; set; }
[JsonProperty("server_time")]
public long ServerTime { get; set; }
[JsonProperty("account_name")]
public string AccountName { get; set; }
[JsonProperty("token_gid")]
public string TokenGID { get; set; }
[JsonProperty("identity_secret")]
public string IdentitySecret { get; set; }
[JsonProperty("secret_1")]
public string Secret1 { get; set; }
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("device_id")]
public string DeviceID { get; set; }
[JsonProperty("fully_enrolled")]
public bool FullyEnrolled { get; set; }
}
}

View File

@@ -437,8 +437,8 @@ namespace ArchiSteamFarm {
}
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;
}
@@ -446,8 +446,8 @@ namespace ArchiSteamFarm {
}
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;
}
@@ -462,7 +462,6 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("ASF V" + Version);
Directory.SetCurrentDirectory(ExecutableDirectory);
InitServices();
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
@@ -481,6 +480,8 @@ namespace ArchiSteamFarm {
}
}
InitServices();
// If debugging is on, we prepare debug directory prior to running
if (GlobalConfig.Debug) {
if (Directory.Exists(DebugDirectory)) {
@@ -515,7 +516,7 @@ namespace ArchiSteamFarm {
CheckForUpdate().Wait();
// Before attempting to connect, initialize our list of CMs
Bot.RefreshCMs(GlobalDatabase.CellID).Wait();
Bot.InitializeCMs(GlobalDatabase.CellID, GlobalDatabase.ServerListProvider);
bool isRunning = false;

View File

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

View File

@@ -31,7 +31,7 @@ using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class Trading {
internal sealed class Trading : IDisposable {
private enum ParseTradeResult : byte {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
Unknown,
@@ -69,6 +69,11 @@ namespace ArchiSteamFarm {
Bot = bot;
}
public void Dispose() {
IgnoredTrades.Dispose();
TradesSemaphore.Dispose();
}
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
internal async Task CheckTrades() {
@@ -101,7 +106,6 @@ namespace ArchiSteamFarm {
}
if (tradeOffers.RemoveWhere(tradeoffer => IgnoredTrades.Contains(tradeoffer.TradeOfferID)) > 0) {
tradeOffers.TrimExcess();
if (tradeOffers.Count == 0) {
return;
}
@@ -211,7 +215,6 @@ namespace ArchiSteamFarm {
// Now remove from our inventory all items we're NOT interested in
inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID));
inventory.TrimExcess();
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
if (inventory.Count == 0) {

View File

@@ -34,7 +34,7 @@ namespace ArchiSteamFarm {
string HandleCommand(string input);
}
internal sealed class WCF : IWCF {
internal sealed class WCF : IWCF, IDisposable {
private static string URL = "http://localhost:1242/ASF";
@@ -52,6 +52,33 @@ namespace ArchiSteamFarm {
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
}
public string HandleCommand(string input) {
if (string.IsNullOrEmpty(input)) {
Logging.LogNullError(nameof(input));
return null;
}
Bot bot = Bot.Bots.Values.FirstOrDefault();
if (bot == null) {
return "ERROR: No bots are enabled!";
}
if (Program.GlobalConfig.SteamOwnerID == 0) {
return "Refusing to handle request because SteamOwnerID is not set!";
}
string command = "!" + input;
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
Logging.LogGenericInfo("Answered to command: " + input + " with: " + output);
return output;
}
public void Dispose() {
ServiceHost?.Close();
Client?.Close();
}
internal bool IsServerRunning() => ServiceHost != null;
internal void StartServer() {
@@ -98,28 +125,6 @@ namespace ArchiSteamFarm {
return Client.HandleCommand(input);
}
public string HandleCommand(string input) {
if (string.IsNullOrEmpty(input)) {
Logging.LogNullError(nameof(input));
return null;
}
Bot bot = Bot.Bots.Values.FirstOrDefault();
if (bot == null) {
return "ERROR: No bots are enabled!";
}
if (Program.GlobalConfig.SteamOwnerID == 0) {
return "Refusing to handle request because SteamOwnerID is not set!";
}
string command = "!" + input;
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
Logging.LogGenericInfo("Answered to command: " + input + " with: " + output);
return output;
}
}
internal sealed class Client : ClientBase<IWCF>, IWCF {

View File

@@ -365,7 +365,7 @@ namespace ArchiSteamFarm {
}
using (HttpResponseMessage response = await UrlHeadToResponse(request, referer).ConfigureAwait(false)) {
return response == null ? null : response.RequestMessage.RequestUri;
return response?.RequestMessage.RequestUri;
}
}

View File

@@ -18,7 +18,6 @@
"WCFHostname": "localhost",
"WCFPort": 1242,
"Statistics": true,
"HackIgnoreMachineID": false,
"Blacklist": [
267420,
303700,

View File

@@ -6,5 +6,5 @@
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
<package id="NLog" version="4.4.0-beta13" targetFramework="net461" />
<package id="protobuf-net" version="2.0.0.668" targetFramework="net45" />
<package id="SteamKit2" version="1.7.0" targetFramework="net452" />
<package id="SteamKit2" version="1.8.0" targetFramework="net461" />
</packages>

View File

@@ -107,9 +107,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool Statistics { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public bool HackIgnoreMachineID { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public List<uint> Blacklist { get; set; } = new List<uint>();

View File

@@ -6,4 +6,4 @@ If my issue is not meeting contributing guidelines specified above, especially i
-----
Feel free to remove everything made up to this point, after all you're not being in a primary school.
Now that you read and understood the notice, feel free to remove it and fill with real issue.

8
cc.sh
View File

@@ -2,6 +2,7 @@
set -eu
BUILD="Release"
AOT=0
CLEAN=0
MONO_ARGS=("--aot" "--llvm" "--server" "-O=all")
@@ -10,7 +11,7 @@ BINARIES=("ArchiSteamFarm/bin/Release/ArchiSteamFarm.exe")
SOLUTION="ArchiSteamFarm.sln"
PRINT_USAGE() {
echo "Usage: $0 [--clean] [debug/release]"
echo "Usage: $0 [--clean] [--aot] [debug/release]"
exit 1
}
@@ -18,6 +19,7 @@ for ARG in "$@"; do
case "$ARG" in
release|Release) BUILD="Release" ;;
debug|Debug) BUILD="Debug" ;;
--aot) AOT=1 ;;
--clean) CLEAN=1 ;;
*) PRINT_USAGE
esac
@@ -51,8 +53,8 @@ if [[ ! -f "${BINARIES[0]}" ]]; then
echo "ERROR: ${BINARIES[0]} binary could not be found!"
fi
# If it's release build, use Mono AOT for output binaries
if [[ "$BUILD" = "Release" ]]; then
# Use Mono AOT for output binaries if needed
if [[ "$AOT" -eq 1 && "$BUILD" = "Release" ]]; then
for BINARY in "${BINARIES[@]}"; do
if [[ ! -f "$BINARY" ]]; then
continue

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1478,81 +1478,174 @@
</summary>
<returns>The data.</returns>
</member>
<member name="T:SteamKit2.ServerQuality">
<member name="T:SteamKit2.Discovery.FileStorageServerListProvider">
<summary>
A server list provider that uses a file to persist the server list using protobuf
</summary>
</member>
<member name="M:SteamKit2.Discovery.FileStorageServerListProvider.#ctor(System.String)">
<summary>
Initialize a new instance of FileStorageServerListProvider
</summary>
</member>
<member name="M:SteamKit2.Discovery.FileStorageServerListProvider.FetchServerListAsync">
<summary>
Read the stored list of servers from the file
</summary>
<returns>List of servers if persisted, otherwise an empty list</returns>
</member>
<member name="M:SteamKit2.Discovery.FileStorageServerListProvider.UpdateServerListAsync(System.Collections.Generic.IEnumerable{System.Net.IPEndPoint})">
<summary>
Writes the supplied list of servers to persistent storage
</summary>
<param name="endpoints">List of server endpoints</param>
<returns>Awaitable task for write completion</returns>
</member>
<member name="T:SteamKit2.Discovery.NullServerListProvider">
<summary>
A server list provider that returns an empty list, for consumers that populate the server list themselves
</summary>
</member>
<member name="M:SteamKit2.Discovery.NullServerListProvider.FetchServerListAsync">
<summary>
No-op implementation that returns an empty server list
</summary>
<returns>Empty server list</returns>
</member>
<member name="M:SteamKit2.Discovery.NullServerListProvider.UpdateServerListAsync(System.Collections.Generic.IEnumerable{System.Net.IPEndPoint})">
<summary>
No-op implementation that does not persist server list
</summary>
<param name="endpoints">Server list</param>
<returns>Completed task</returns>
</member>
<member name="T:SteamKit2.Discovery.ServerQuality">
<summary>
Currently marked quality of a server. All servers start off as Undetermined.
</summary>
</member>
<member name="F:SteamKit2.ServerQuality.Good">
<member name="F:SteamKit2.Discovery.ServerQuality.Good">
<summary>
Known good server.
</summary>
</member>
<member name="F:SteamKit2.ServerQuality.Bad">
<member name="F:SteamKit2.Discovery.ServerQuality.Bad">
<summary>
Known bad server.
</summary>
</member>
<member name="T:SteamKit2.SmartCMServerList">
<member name="T:SteamKit2.Discovery.SmartCMServerList">
<summary>
Smart list of CM servers.
</summary>
</member>
<member name="P:SteamKit2.SmartCMServerList.BadConnectionMemoryTimeSpan">
<member name="M:SteamKit2.Discovery.SmartCMServerList.#ctor(SteamKit2.Discovery.IServerListProvider,System.Boolean)">
<summary>
Initialize SmartCMServerList with a given server list provider
</summary>
<param name="provider">The ServerListProvider to persist servers</param>
<param name="allowDirectoryFetch">Specifies if we can query SteamDirectory to discover more servers</param>
</member>
<member name="M:SteamKit2.Discovery.SmartCMServerList.#ctor">
<summary>
Initialize SmartCMServerList with the default <see cref="T:SteamKit2.Discovery.NullServerListProvider"/> server list provider
</summary>
</member>
<member name="P:SteamKit2.Discovery.SmartCMServerList.ServerListProvider">
<summary>
The server list provider chosen to provide a persistent list of servers to connect to
</summary>
</member>
<member name="P:SteamKit2.Discovery.SmartCMServerList.CellID">
<summary>
The preferred cell id for retrieving the list of servers from the Steam directory
</summary>
</member>
<member name="P:SteamKit2.Discovery.SmartCMServerList.BadConnectionMemoryTimeSpan">
<summary>
Determines how long a server's bad connection state is remembered for.
</summary>
</member>
<member name="M:SteamKit2.SmartCMServerList.ResetOldScores">
<member name="M:SteamKit2.Discovery.SmartCMServerList.ResetOldScores">
<summary>
Resets the scores of all servers which has a last bad connection more than <see cref="P:SteamKit2.SmartCMServerList.BadConnectionMemoryTimeSpan"/> ago.
Resets the scores of all servers which has a last bad connection more than <see cref="P:SteamKit2.Discovery.SmartCMServerList.BadConnectionMemoryTimeSpan"/> ago.
</summary>
</member>
<member name="M:SteamKit2.SmartCMServerList.TryAdd(System.Net.IPEndPoint)">
<member name="M:SteamKit2.Discovery.SmartCMServerList.ReplaceList(System.Collections.Generic.IEnumerable{System.Net.IPEndPoint})">
<summary>
Adds an <see cref="T:System.Net.IPEndPoint" /> to the server list.
Replace the list with a new list of servers provided to us by the Steam servers.
</summary>
<param name="endPoint">The <see cref="T:System.Net.IPEndPoint"/> to add.</param>
<returns>false if the server is already in the list, true otherwise.</returns>
<param name="endpointList">The <see cref="T:System.Net.IPEndPoint"/>s to use for this <see cref="T:SteamKit2.Discovery.SmartCMServerList"/>.</param>
</member>
<member name="M:SteamKit2.SmartCMServerList.TryAddRange(System.Collections.Generic.IEnumerable{System.Net.IPEndPoint})">
<summary>
Adds the elements of the specified collection of <see cref="T:System.Net.IPEndPoint" />s to the server list.
</summary>
<param name="endPoints">The collection of <see cref="T:System.Net.IPEndPoint"/>s to add.</param>
<returns>false if any of the specified servers are already in the list, true otherwise.</returns>
</member>
<member name="M:SteamKit2.SmartCMServerList.MergeWithList(System.Collections.Generic.IEnumerable{System.Net.IPEndPoint})">
<summary>
Merges the list with a new list of servers provided to us by the Steam servers.
This adds the new list of <see cref="T:System.Net.IPEndPoint"/>s to the beginning of the list,
ensuring that any pre-existing servers are moved into their new place in order near
the beginning of the list.
</summary>
<param name="listToMerge">The <see cref="T:System.Net.IPEndPoint"/>s to merge into this <see cref="T:SteamKit2.SmartCMServerList"/>.</param>
</member>
<member name="M:SteamKit2.SmartCMServerList.ResetBadServers">
<member name="M:SteamKit2.Discovery.SmartCMServerList.ResetBadServers">
<summary>
Explicitly resets the known state of all servers.
</summary>
</member>
<member name="M:SteamKit2.SmartCMServerList.Clear">
<member name="M:SteamKit2.Discovery.SmartCMServerList.GetNextServerCandidateInternal">
<summary>
Removes all servers from the list.
Perform the actual score lookup of the server list and return the candidate
</summary>
<returns>IPEndPoint candidate</returns>
</member>
<member name="M:SteamKit2.SmartCMServerList.GetNextServerCandidate">
<member name="M:SteamKit2.Discovery.SmartCMServerList.GetNextServerCandidate">
<summary>
Get the next server in the list.
</summary>
<returns>An <see cref="T:System.Net.IPEndPoint"/>, or null if the list is empty.</returns>
</member>
<member name="M:SteamKit2.SmartCMServerList.GetAllEndPoints">
<member name="M:SteamKit2.Discovery.SmartCMServerList.GetNextServerCandidateAsync">
<summary>
Get the next server in the list.
</summary>
<returns>An <see cref="T:System.Net.IPEndPoint"/>, or null if the list is empty.</returns>
</member>
<member name="M:SteamKit2.Discovery.SmartCMServerList.GetAllEndPoints">
<summary>
Gets the <see cref="T:System.Net.IPEndPoint"/>s of all servers in the server list.
</summary>
<returns>An <see cref="T:System.Net.IPEndPoint[]"/> array contains the <see cref="T:System.Net.IPEndPoint"/>s of the servers in the list</returns>
</member>
<member name="T:SteamKit2.Discovery.IsolatedStorageServerListProvider">
<summary>
A server list provider that uses IsolatedStorage to persist the server list
</summary>
</member>
<member name="M:SteamKit2.Discovery.IsolatedStorageServerListProvider.#ctor">
<summary>
Initialize a new instance of IsolatedStorageServerListProvider using <see cref="M:System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForAssembly"/>
</summary>
</member>
<member name="M:SteamKit2.Discovery.IsolatedStorageServerListProvider.FetchServerListAsync">
<summary>
Read the stored list of servers from IsolatedStore
</summary>
<returns>List of servers if persisted, otherwise an empty list</returns>
</member>
<member name="M:SteamKit2.Discovery.IsolatedStorageServerListProvider.UpdateServerListAsync(System.Collections.Generic.IEnumerable{System.Net.IPEndPoint})">
<summary>
Writes the supplied list of servers to persistent storage
</summary>
<param name="endpoints">List of server endpoints</param>
<returns>Awaitable task for write completion</returns>
</member>
<member name="T:SteamKit2.Discovery.IServerListProvider">
<summary>
An interface for persisting the server list for connection discovery
</summary>
</member>
<member name="M:SteamKit2.Discovery.IServerListProvider.FetchServerListAsync">
<summary>
Ask a provider to fetch any servers that it has available
</summary>
<returns>A list of IPEndPoints representing servers</returns>
</member>
<member name="M:SteamKit2.Discovery.IServerListProvider.UpdateServerListAsync(System.Collections.Generic.IEnumerable{System.Net.IPEndPoint})">
<summary>
Update the persistent list of endpoints
</summary>
<param name="endpoints">List of endpoints</param>
</member>
<member name="T:SteamKit2.CDNClient">
<summary>
The CDNClient class is used for downloading game content from the Steam servers.
@@ -2788,6 +2881,14 @@
<param name="gameId">The GameID to request the number of players for.</param>
<returns>The Job ID of the request. This can be used to find the appropriate <see cref="T:SteamKit2.SteamUserStats.NumberOfPlayersCallback"/>.</returns>
</member>
<member name="M:SteamKit2.SteamUserStats.GetNumberOfCurrentPlayers(System.UInt32)">
<summary>
Retrieves the number of current players for a given app id.
Results are returned in a <see cref="T:SteamKit2.SteamUserStats.NumberOfPlayersCallback"/>.
</summary>
<param name="appId">The app id to request the number of players for.</param>
<returns>The Job ID of the request. This can be used to find the appropriate <see cref="T:SteamKit2.SteamUserStats.NumberOfPlayersCallback"/>.</returns>
</member>
<member name="M:SteamKit2.SteamUserStats.FindLeaderboard(System.UInt32,System.String)">
<summary>
Asks the Steam back-end for a leaderboard by name for a given appid.
@@ -2831,7 +2932,7 @@
</member>
<member name="T:SteamKit2.SteamUserStats.NumberOfPlayersCallback">
<summary>
This callback is fired in response to <see cref="M:SteamKit2.SteamUserStats.GetNumberOfCurrentPlayers(SteamKit2.GameID)" />.
This callback is fired in response to <see cref="M:SteamKit2.SteamUserStats.GetNumberOfCurrentPlayers(System.UInt32)" />.
</summary>
</member>
<member name="P:SteamKit2.SteamUserStats.NumberOfPlayersCallback.Result">
@@ -3074,6 +3175,13 @@
</summary>
<param name="timeout">The length of time to block.</param>
</member>
<member name="M:SteamKit2.CallbackManager.RunWaitAllCallbacks(System.TimeSpan)">
<summary>
Blocks the current thread to run all queued callbacks.
If no callback is queued, the method will block for the given timeout.
</summary>
<param name="timeout">The length of time to block.</param>
</member>
<member name="M:SteamKit2.CallbackManager.RunWaitCallbacks">
<summary>
Blocks the current thread to run a single queued callback.
@@ -6322,8 +6430,8 @@
<summary>
Gets or sets the LoginID. This number is used for identifying logon session.
The purpose of this field is to allow multiple sessions to the same steam account from the same machine.
This is because Steam Network doesn't allow more than one session with the same LoginID to access given account at the same time.
If you want to establish more than one active session to given account, you must make sure that every session (to that account) has unique LoginID.
This is because Steam Network doesn't allow more than one session with the same LoginID to access given account at the same time from the same public IP.
If you want to establish more than one active session to given account, you must make sure that every session (to that account) from the same public IP has a unique LoginID.
By default LoginID is automatically generated based on machine's primary bind address, which is the same for all sessions.
Null value will cause this property to be automatically generated based on default behaviour.
If in doubt, set this property to null.
@@ -6734,6 +6842,14 @@
<param name="timeout">The length of time to block.</param>
<returns>A callback object from the queue if a callback has been posted, or null if the timeout has elapsed.</returns>
</member>
<member name="M:SteamKit2.SteamClient.GetAllCallbacks(System.Boolean,System.TimeSpan)">
<summary>
Blocks the calling thread until the queue contains a callback object. Returns all callbacks, and optionally frees them.
</summary>
<param name="freeLast">if set to <c>true</c> this function also frees all callbacks.</param>
<param name="timeout">The length of time to block.</param>
<returns>All current callback objects in the queue.</returns>
</member>
<member name="M:SteamKit2.SteamClient.FreeLastCallback">
<summary>
Frees the last callback in the queue.
@@ -7031,6 +7147,22 @@
</summary>
<returns>The value of this instance as a string.</returns>
</member>
<member name="M:SteamKit2.KeyValue.AsUnsignedByte(System.Byte)">
<summary>
Attempts to convert and return the value of this instance as an unsigned byte.
If the conversion is invalid, the default value is returned.
</summary>
<param name="defaultValue">The default value to return if the conversion is invalid.</param>
<returns>The value of this instance as an unsigned byte.</returns>
</member>
<member name="M:SteamKit2.KeyValue.AsUnsignedShort(System.UInt16)">
<summary>
Attempts to convert and return the value of this instance as an unsigned short.
If the conversion is invalid, the default value is returned.
</summary>
<param name="defaultValue">The default value to return if the conversion is invalid.</param>
<returns>The value of this instance as an unsigned short.</returns>
</member>
<member name="M:SteamKit2.KeyValue.AsInteger(System.Int32)">
<summary>
Attempts to convert and return the value of this instance as an integer.
@@ -7039,6 +7171,14 @@
<param name="defaultValue">The default value to return if the conversion is invalid.</param>
<returns>The value of this instance as an integer.</returns>
</member>
<member name="M:SteamKit2.KeyValue.AsUnsignedInteger(System.UInt32)">
<summary>
Attempts to convert and return the value of this instance as an unsigned integer.
If the conversion is invalid, the default value is returned.
</summary>
<param name="defaultValue">The default value to return if the conversion is invalid.</param>
<returns>The value of this instance as an unsigned integer.</returns>
</member>
<member name="M:SteamKit2.KeyValue.AsLong(System.Int64)">
<summary>
Attempts to convert and return the value of this instance as a long.
@@ -7325,11 +7465,11 @@
<param name="protobuf">if set to <c>true</c>, the message is protobuf flagged.</param>
<returns>A crafted EMsg, flagged if requested.</returns>
</member>
<member name="M:SteamKit2.TcpConnection.Connect(System.Net.IPEndPoint,System.Int32)">
<member name="M:SteamKit2.TcpConnection.Connect(System.Threading.Tasks.Task{System.Net.IPEndPoint},System.Int32)">
<summary>
Connects to the specified end point.
</summary>
<param name="endPoint">The end point.</param>
<param name="endPointTask">Task returning the end point.</param>
<param name="timeout">Timeout in milliseconds</param>
</member>
<member name="M:SteamKit2.TcpConnection.NetLoop">
@@ -7394,11 +7534,11 @@
The highest sequence number we've processed.
</summary>
</member>
<member name="M:SteamKit2.UdpConnection.Connect(System.Net.IPEndPoint,System.Int32)">
<member name="M:SteamKit2.UdpConnection.Connect(System.Threading.Tasks.Task{System.Net.IPEndPoint},System.Int32)">
<summary>
Connects to the specified CM server.
</summary>
<param name="endPoint">The CM server.</param>
<param name="endPointTask">Task returning the CM server.</param>
<param name="timeout">Timeout in milliseconds</param>
</member>
<member name="M:SteamKit2.UdpConnection.Disconnect">
@@ -7460,7 +7600,7 @@
</summary>
<returns>True if a message was dispatched, false otherwise</returns>
</member>
<member name="M:SteamKit2.UdpConnection.NetLoop">
<member name="M:SteamKit2.UdpConnection.NetLoop(System.Object)">
<summary>
Processes incoming packets, maintains connection consistency, and oversees outgoing packets.
</summary>
@@ -7528,11 +7668,11 @@
Occurs when the physical connection is broken.
</summary>
</member>
<member name="M:SteamKit2.Connection.Connect(System.Net.IPEndPoint,System.Int32)">
<member name="M:SteamKit2.Connection.Connect(System.Threading.Tasks.Task{System.Net.IPEndPoint},System.Int32)">
<summary>
Connects to the specified end point.
</summary>
<param name="endPoint">The end point.</param>
<param name="endPointTask">Task returning the end point.</param>
<param name="timeout">Timeout in milliseconds</param>
</member>
<member name="M:SteamKit2.Connection.Disconnect">

View File

@@ -1,3 +1,25 @@
------------------------------------------------------------------------------
v 1.8.0 Jul 8, 2016
------------------------------------------------------------------------------
* Added `CallbackManager.RunWaitAllCallbacks` (pr #292)
* Added `KeyValue.AsUnsignedByte`. (pr #270)
* Added `KeyValue.AsUnsignedInteger`. (pr #255)
* Added `KeyValue.AsUnsignedShort`. (pr #270)
* Added `SteamUserStats.GetNumberOfCurrentPlayers(GameID)`. (pr #234)
* Added the ability to persist the server list to Isolated Storage. (pr #293)
* Added the ability to persist the server list to a file. (pr #293)
* Added support for fetching server list from the Steam Directory API. (pr #293)
* Fixed a crash on Windows if WMI is unavailable.
* Fixed a memory leak when reconnecting to Steam with the same `SteamClient` instance (pr #292)
* Updated `SteamUserStats.GetNumberOfCurrentPlayers` to use messages that Steam continues to respond to. (pr #234)
* Updated Steam enums and protobufs. (pr #271, pr #274, pr #296)
* Updated game-related GC messages and protobufs.
* Removed the hardcoded list of Steam server addresses. (pr #293)
BREAKING CHANGES
* `SmartCMServerList` APIs have changed to accomodate new server management behaviour.
------------------------------------------------------------------------------
v 1.7.0 Dec 21, 2015
------------------------------------------------------------------------------
@@ -13,7 +35,7 @@ v 1.7.0 Dec 21, 2015
* Server List will now maintain ordering from Steam, increasing the chances of a successful and geographically local connection. (pr #218)
* After calling `SteamUser.LogOff` or `SteamGameServer.LogOff`, `SteamClient.DisconnectedCallback.UserInitiated` will be `true`. (pr #205)
* Fixed a crash when parsing a Steam ID of the format '[i:1:234]'.
* Fixed a crash when logging on in an enviromnent where the hard disk has no serial ID, such as Hyper-V.
* Fixed a crash when logging on in an environment where the hard disk has no serial ID, such as Hyper-V.
* Fixed a bug when parsing a KeyValue file that contains a `/` followed by a newline. (pr #187)
* Updated Steam enums and protobufs.
* Updated game-related GC messages and protobufs.

16
run.sh
View File

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