mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-26 11:16:47 +00:00
Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
134aa62952 | ||
|
|
5a4132a679 | ||
|
|
edb047980e | ||
|
|
7d32adac13 | ||
|
|
95637ea3a7 | ||
|
|
02a547e7d2 | ||
|
|
9594357d56 | ||
|
|
ce166baab6 | ||
|
|
1ec0b20604 | ||
|
|
26bd76cc4a | ||
|
|
8e1d02f43f | ||
|
|
b802822699 | ||
|
|
be77e8d380 | ||
|
|
000b902ced | ||
|
|
d09be453f3 | ||
|
|
5f1342ae26 | ||
|
|
00b4c28843 | ||
|
|
cb6cfd08c2 | ||
|
|
f53911bd9a | ||
|
|
527641439b | ||
|
|
647a0ee865 | ||
|
|
b9f2dd1292 | ||
|
|
e675a3a488 | ||
|
|
fb8692d28c | ||
|
|
cf4141dde7 | ||
|
|
963f56ccf2 | ||
|
|
c754a18603 | ||
|
|
eb886e8ca8 | ||
|
|
e8889fb087 | ||
|
|
d627a5ee9d | ||
|
|
35bd36bbd9 | ||
|
|
d79944085f | ||
|
|
4e191367da | ||
|
|
f88bfe9f83 | ||
|
|
86aa9e781d | ||
|
|
6ae7e74daf | ||
|
|
4481fb3a86 | ||
|
|
6bd161359f | ||
|
|
612abef327 | ||
|
|
97224aafaf | ||
|
|
7025659151 | ||
|
|
9918861c66 | ||
|
|
fcfe4495ae | ||
|
|
2c10ebe8b3 | ||
|
|
e2dca65165 | ||
|
|
beef5ec8ed | ||
|
|
14fc01d4ef | ||
|
|
7bd8b8f965 | ||
|
|
85fd18d621 | ||
|
|
d25d4dcba6 | ||
|
|
e2cf04afdd | ||
|
|
d85d41c215 | ||
|
|
6977343906 | ||
|
|
3934a3fdbc | ||
|
|
66e01113bb | ||
|
|
646e52c28b | ||
|
|
2e28319535 | ||
|
|
860b76f720 | ||
|
|
82cbcecb6b | ||
|
|
fda3b1ac31 | ||
|
|
58453e72ac | ||
|
|
d8aad682d5 | ||
|
|
8cd971825e | ||
|
|
a4b238b5a0 | ||
|
|
05f9804138 | ||
|
|
1f58c92b5e | ||
|
|
198c51f3da | ||
|
|
d517562467 | ||
|
|
53dcd22cea | ||
|
|
af050ae67e | ||
|
|
914936acdc | ||
|
|
4bba55e8fd | ||
|
|
3e1358b363 | ||
|
|
fbb24506e2 | ||
|
|
48eae1be1a | ||
|
|
6bbe543fdd | ||
|
|
afe1d57605 | ||
|
|
5596e55c7b | ||
|
|
73cefc697e | ||
|
|
5680929203 | ||
|
|
c94547c68e | ||
|
|
11c748192f | ||
|
|
8cc3fec432 | ||
|
|
7baa54377a | ||
|
|
4bc7fded2d | ||
|
|
24aa1b4873 | ||
|
|
561f8f61df | ||
|
|
c33f575c40 | ||
|
|
2c54f6b051 | ||
|
|
e0385cd343 | ||
|
|
80abd3ed69 | ||
|
|
e726558e72 | ||
|
|
07af05ad00 | ||
|
|
ef5b108b34 | ||
|
|
1ee9fec845 | ||
|
|
4bf1462381 | ||
|
|
e41d2cf37e | ||
|
|
fac5e65035 | ||
|
|
b44115711b | ||
|
|
a90573e0ea | ||
|
|
5611694b90 | ||
|
|
efd7fbd3c0 | ||
|
|
d32882d91e | ||
|
|
8208e9aa77 | ||
|
|
c13eb02e51 | ||
|
|
93ac6d5a28 | ||
|
|
413d44b42b | ||
|
|
c52934ed9a | ||
|
|
b853c93c3f | ||
|
|
0a86107804 | ||
|
|
cc2289798e | ||
|
|
4f01dc39fc | ||
|
|
52de999443 | ||
|
|
8998bf4832 | ||
|
|
49f6181263 | ||
|
|
e094a02762 | ||
|
|
410f31f995 | ||
|
|
85a70911e1 | ||
|
|
0714c4e575 | ||
|
|
b19a5c533f | ||
|
|
4129db0149 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@ out/
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
.vs/
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
@@ -8,6 +8,10 @@ mono:
|
||||
- weekly
|
||||
- latest
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- mono: weekly
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
|
||||
@@ -10,6 +10,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigGenerator", "ConfigGe
|
||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C} = {35AF7887-08B9-40E8-A5EA-797D8B60B30C}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUI", "GUI\GUI.csproj", "{13949B41-787C-4558-90AE-A9F9E7F86B1F}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C} = {35AF7887-08B9-40E8-A5EA-797D8B60B30C}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -24,6 +29,10 @@ Global
|
||||
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CATCH_ON_NEW_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FINALLY_ON_NEW_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AES/@EntryIndexedValue">AES</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASF/@EntryIndexedValue">ASF</s:String>
|
||||
@@ -12,6 +26,7 @@
|
||||
<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/=PICS/@EntryIndexedValue">PICS</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>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SMS/@EntryIndexedValue">SMS</s:String>
|
||||
|
||||
213
ArchiSteamFarm/ASF.cs
Normal file
213
ArchiSteamFarm/ASF.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.JSON;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class ASF {
|
||||
private static Timer AutoUpdatesTimer;
|
||||
|
||||
internal static async Task CheckForUpdate(bool updateOverride = false) {
|
||||
string exeFile = Assembly.GetEntryAssembly().Location;
|
||||
string oldExeFile = exeFile + ".old";
|
||||
|
||||
// We booted successfully so we can now remove old exe file
|
||||
if (File.Exists(oldExeFile)) {
|
||||
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
File.Delete(oldExeFile);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
Logging.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
|
||||
}
|
||||
}
|
||||
|
||||
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) {
|
||||
AutoUpdatesTimer = new Timer(
|
||||
async e => await CheckForUpdate().ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromDays(1), // Delay
|
||||
TimeSpan.FromDays(1) // Period
|
||||
);
|
||||
|
||||
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
|
||||
}
|
||||
|
||||
string releaseURL = SharedInfo.GithubReleaseURL;
|
||||
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
|
||||
releaseURL += "/latest";
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Checking new version...");
|
||||
|
||||
string response = await Program.WebBrowser.UrlGetToContentRetry(releaseURL).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(response)) {
|
||||
Logging.LogGenericWarning("Could not check latest version!");
|
||||
return;
|
||||
}
|
||||
|
||||
GitHub.ReleaseResponse releaseResponse;
|
||||
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
|
||||
try {
|
||||
releaseResponse = JsonConvert.DeserializeObject<GitHub.ReleaseResponse>(response);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
List<GitHub.ReleaseResponse> releases;
|
||||
try {
|
||||
releases = JsonConvert.DeserializeObject<List<GitHub.ReleaseResponse>>(response);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((releases == null) || (releases.Count == 0)) {
|
||||
Logging.LogGenericWarning("Could not check latest version!");
|
||||
return;
|
||||
}
|
||||
|
||||
releaseResponse = releases[0];
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
|
||||
Logging.LogGenericWarning("Could not check latest version!");
|
||||
return;
|
||||
}
|
||||
|
||||
Version newVersion = new Version(releaseResponse.Tag);
|
||||
|
||||
Logging.LogGenericInfo("Local version: " + SharedInfo.Version + " | Remote version: " + newVersion);
|
||||
|
||||
if (SharedInfo.Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version
|
||||
return;
|
||||
}
|
||||
|
||||
if (!updateOverride && !Program.GlobalConfig.AutoUpdates) {
|
||||
Logging.LogGenericInfo("New version is available!");
|
||||
Logging.LogGenericInfo("Consider updating yourself!");
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(oldExeFile)) {
|
||||
Logging.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto update logic starts here
|
||||
if (releaseResponse.Assets == null) {
|
||||
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
|
||||
return;
|
||||
}
|
||||
|
||||
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!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) {
|
||||
Logging.LogGenericWarning("Could not proceed with update because download URL is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Downloading new version...");
|
||||
Logging.LogGenericInfo("While waiting, consider donating if you appreciate the work being done :)");
|
||||
|
||||
byte[] result = await Program.WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
string newExeFile = exeFile + ".new";
|
||||
|
||||
// Firstly we create new exec
|
||||
try {
|
||||
File.WriteAllBytes(newExeFile, result);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we move current -> old
|
||||
try {
|
||||
File.Move(exeFile, oldExeFile);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
try {
|
||||
// Cleanup
|
||||
File.Delete(newExeFile);
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we move new -> current
|
||||
try {
|
||||
File.Move(newExeFile, exeFile);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
try {
|
||||
// Cleanup
|
||||
File.Move(oldExeFile, exeFile);
|
||||
File.Delete(newExeFile);
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Update process finished!");
|
||||
|
||||
if (Program.GlobalConfig.AutoRestart) {
|
||||
Logging.LogGenericInfo("Restarting...");
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Program.Restart();
|
||||
} else {
|
||||
Logging.LogGenericInfo("Exiting...");
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Program.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,7 +223,7 @@ namespace ArchiSteamFarm {
|
||||
PlayGames(new List<uint> { gameID }, gameName);
|
||||
}
|
||||
|
||||
internal void PlayGames(ICollection<uint> gameIDs, string gameName = null) {
|
||||
internal void PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
|
||||
if (gameIDs == null) {
|
||||
Logging.LogNullError(nameof(gameIDs), Bot.BotName);
|
||||
return;
|
||||
|
||||
@@ -115,11 +115,13 @@
|
||||
<Compile Include="ArchiServiceInstaller.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ASF.cs" />
|
||||
<Compile Include="Bot.cs" />
|
||||
<Compile Include="BotConfig.cs" />
|
||||
<Compile Include="ConcurrentEnumerator.cs" />
|
||||
<Compile Include="ConcurrentHashSet.cs" />
|
||||
<Compile Include="CryptoHelper.cs" />
|
||||
<Compile Include="Events.cs" />
|
||||
<Compile Include="GlobalDatabase.cs" />
|
||||
<Compile Include="BotDatabase.cs" />
|
||||
<Compile Include="CardsFarmer.cs" />
|
||||
|
||||
@@ -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);
|
||||
@@ -166,8 +166,8 @@ namespace ArchiSteamFarm {
|
||||
internal void OnDisconnected() => Ready = false;
|
||||
|
||||
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
|
||||
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce)) {
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce), Bot.BotName);
|
||||
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -217,19 +217,29 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Success!", Bot.BotName);
|
||||
|
||||
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
|
||||
|
||||
string steamLogin = authResult["token"].Value;
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
|
||||
if (string.IsNullOrEmpty(steamLogin)) {
|
||||
Logging.LogNullError(nameof(steamLogin), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
string steamLoginSecure = authResult["tokensecure"].Value;
|
||||
if (string.IsNullOrEmpty(steamLoginSecure)) {
|
||||
Logging.LogNullError(nameof(steamLoginSecure), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
|
||||
|
||||
Logging.LogGenericInfo("Success!", Bot.BotName);
|
||||
|
||||
// Unlock Steam Parental if needed
|
||||
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
|
||||
return false;
|
||||
if (!parentalPin.Equals("0")) {
|
||||
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Ready = true;
|
||||
@@ -652,37 +662,38 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
||||
internal async Task AcceptTradeOffer(ulong tradeID) {
|
||||
if (tradeID == 0) {
|
||||
Logging.LogNullError(nameof(tradeID), Bot.BotName);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
|
||||
if (string.IsNullOrEmpty(sessionID)) {
|
||||
Logging.LogNullError(nameof(sessionID), Bot.BotName);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
|
||||
string request = referer + "/accept";
|
||||
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(3) {
|
||||
{ "sessionid", sessionID },
|
||||
{ "serverid", "1" },
|
||||
{ "tradeofferid", tradeID.ToString() }
|
||||
};
|
||||
|
||||
return await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
|
||||
await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal bool DeclineTradeOffer(ulong tradeID) {
|
||||
internal void DeclineTradeOffer(ulong tradeID) {
|
||||
if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
KeyValue response = null;
|
||||
@@ -702,12 +713,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
return true;
|
||||
if (response == null) {
|
||||
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
}
|
||||
|
||||
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable) {
|
||||
@@ -729,7 +737,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 +790,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (JToken item in items) {
|
||||
foreach (JToken item in items.Where(item => item != null)) {
|
||||
Steam.Item steamItem;
|
||||
|
||||
try {
|
||||
@@ -955,10 +963,6 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentalPin.Equals("0")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
|
||||
|
||||
string request = SteamCommunityURL + "/parental/ajaxunlock";
|
||||
|
||||
@@ -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;
|
||||
@@ -39,11 +40,10 @@ using SteamKit2.Discovery;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Bot : IDisposable {
|
||||
private const ulong ArchiSCFarmGroup = 103582791440160998;
|
||||
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,10 +63,11 @@ 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;
|
||||
private readonly Timer AcceptConfirmationsTimer, SendItemsTimer;
|
||||
private readonly Timer AcceptConfirmationsTimer, HeartBeatTimer, SendItemsTimer;
|
||||
private readonly Trading Trading;
|
||||
|
||||
[JsonProperty]
|
||||
@@ -77,6 +78,19 @@ namespace ArchiSteamFarm {
|
||||
private bool FirstTradeSent, InvalidPassword, SkipFirstShutdown;
|
||||
private string AuthCode, TwoFactorCode;
|
||||
|
||||
internal static string GetAPIStatus() {
|
||||
var response = new {
|
||||
Bots
|
||||
};
|
||||
|
||||
try {
|
||||
return JsonConvert.SerializeObject(response);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) {
|
||||
if (serverListProvider == null) {
|
||||
Logging.LogNullError(nameof(serverListProvider));
|
||||
@@ -89,7 +103,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private static bool IsOwner(ulong steamID) {
|
||||
if (steamID != 0) {
|
||||
return steamID == Program.GlobalConfig.SteamOwnerID;
|
||||
return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID));
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(steamID));
|
||||
@@ -130,14 +144,16 @@ namespace ArchiSteamFarm {
|
||||
throw new Exception("That bot is already defined!");
|
||||
}
|
||||
|
||||
string botPath = Path.Combine(Program.ConfigDirectory, botName);
|
||||
string botPath = Path.Combine(SharedInfo.ConfigDirectory, botName);
|
||||
|
||||
BotName = botName;
|
||||
SentryFile = botPath + ".bin";
|
||||
|
||||
BotConfig = BotConfig.Load(botPath + ".json");
|
||||
string botConfigFile = botPath + ".json";
|
||||
|
||||
BotConfig = BotConfig.Load(botConfigFile);
|
||||
if (BotConfig == null) {
|
||||
Logging.LogGenericError("Your bot config is invalid, refusing to start this bot instance!", botName);
|
||||
Logging.LogGenericError("Your bot config is invalid, please verify content of " + botConfigFile + " and try again!", botName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,9 +162,11 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
BotDatabase = BotDatabase.Load(botPath + ".db");
|
||||
string botDatabaseFile = botPath + ".db";
|
||||
|
||||
BotDatabase = BotDatabase.Load(botDatabaseFile);
|
||||
if (BotDatabase == null) {
|
||||
Logging.LogGenericError("Bot database could not be loaded, refusing to start this bot instance!", botName);
|
||||
Logging.LogGenericError("Bot database could not be loaded, refusing to create this bot instance! In order to recreate it, remove " + botDatabaseFile + " and try again!", botName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,10 +183,10 @@ namespace ArchiSteamFarm {
|
||||
// Initialize
|
||||
SteamClient = new SteamClient(Program.GlobalConfig.SteamProtocol);
|
||||
|
||||
if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized && Directory.Exists(Program.DebugDirectory)) {
|
||||
if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized && Directory.Exists(SharedInfo.DebugDirectory)) {
|
||||
try {
|
||||
Debugging.NetHookAlreadyInitialized = true;
|
||||
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
|
||||
SteamClient.DebugNetworkListener = new NetHookNetworkListener(SharedInfo.DebugDirectory);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, botName);
|
||||
}
|
||||
@@ -184,6 +202,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);
|
||||
@@ -191,6 +210,7 @@ namespace ArchiSteamFarm {
|
||||
CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
|
||||
CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
|
||||
CallbackManager.Subscribe<SteamFriends.FriendMsgHistoryCallback>(OnFriendMsgHistory);
|
||||
CallbackManager.Subscribe<SteamFriends.PersonaStateCallback>(OnPersonaState);
|
||||
|
||||
SteamUser = SteamClient.GetHandler<SteamUser>();
|
||||
CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
|
||||
@@ -209,16 +229,23 @@ namespace ArchiSteamFarm {
|
||||
CardsFarmer = new CardsFarmer(this);
|
||||
Trading = new Trading(this);
|
||||
|
||||
if ((AcceptConfirmationsTimer == null) && (BotConfig.AcceptConfirmationsPeriod > 0)) {
|
||||
HeartBeatTimer = new Timer(
|
||||
async e => await HeartBeat().ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromMinutes(1) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
|
||||
TimeSpan.FromMinutes(1) // Period
|
||||
);
|
||||
|
||||
if (BotConfig.AcceptConfirmationsPeriod > 0) {
|
||||
AcceptConfirmationsTimer = new Timer(
|
||||
async e => await AcceptConfirmations(true).ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(Bots.Count), // Delay
|
||||
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
|
||||
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period
|
||||
);
|
||||
}
|
||||
|
||||
if ((SendItemsTimer == null) && (BotConfig.SendTradePeriod > 0)) {
|
||||
if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) {
|
||||
SendItemsTimer = new Timer(
|
||||
async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
|
||||
null,
|
||||
@@ -239,52 +266,58 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
GiftsSemaphore.Dispose();
|
||||
LoginSemaphore.Dispose();
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
ArchiWebHandler.Dispose();
|
||||
CardsFarmer.Dispose();
|
||||
HeartBeatTimer.Dispose();
|
||||
HandledGifts.Dispose();
|
||||
OwnedPackageIDs.Dispose();
|
||||
Trading.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
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) {
|
||||
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() {
|
||||
@@ -298,12 +331,12 @@ namespace ArchiSteamFarm {
|
||||
callback = await SteamUser.RequestWebAPIUserNonce();
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, BotName);
|
||||
Start().Forget();
|
||||
await Start().ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(callback?.Nonce)) {
|
||||
Start().Forget();
|
||||
await Start().ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -311,7 +344,7 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
Start().Forget();
|
||||
await Start().ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -327,7 +360,15 @@ namespace ArchiSteamFarm {
|
||||
SteamClient.Disconnect();
|
||||
}
|
||||
|
||||
Program.OnBotShutdown();
|
||||
Events.OnBotShutdown();
|
||||
}
|
||||
|
||||
internal async Task LootIfNeeded() {
|
||||
if (!BotConfig.SendOnFarmingFinished || (BotConfig.SteamMasterID == 0) || !SteamClient.IsConnected || (BotConfig.SteamMasterID == SteamClient.SteamID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal void OnFarmingStopped() => ResetGamesPlayed();
|
||||
@@ -335,9 +376,9 @@ namespace ArchiSteamFarm {
|
||||
internal async Task OnFarmingFinished(bool farmedSomething) {
|
||||
OnFarmingStopped();
|
||||
|
||||
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
|
||||
if (farmedSomething || !FirstTradeSent) {
|
||||
FirstTradeSent = true;
|
||||
await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false);
|
||||
await LootIfNeeded().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (BotConfig.ShutdownOnFarmingFinished) {
|
||||
@@ -432,6 +473,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
return await ResponseOwns(steamID, BotName, args[1]).ConfigureAwait(false);
|
||||
case "!OWNSALL":
|
||||
return await ResponseOwnsAll(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!PASSWORD":
|
||||
return ResponsePassword(steamID, args[1]);
|
||||
case "!PAUSE":
|
||||
@@ -461,18 +504,40 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HeartBeat() {
|
||||
if (!SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await SteamApps.PICSGetProductInfo(0, null);
|
||||
} catch {
|
||||
if (!SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericWarning("Connection to Steam Network lost, reconnecting...", BotName);
|
||||
|
||||
Task.Run(async () => {
|
||||
await LimitLoginRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
if (!SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamClient.Connect();
|
||||
}).Forget();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Start() {
|
||||
if (!KeepRunning) {
|
||||
KeepRunning = true;
|
||||
Task.Run(() => HandleCallbacks()).Forget();
|
||||
Logging.LogGenericInfo("Starting...", BotName);
|
||||
}
|
||||
|
||||
// 2FA tokens are expiring soon, don't use limiter when user is providing one
|
||||
if ((TwoFactorCode == null) || (BotDatabase.MobileAuthenticator != null)) {
|
||||
await LimitLoginRequestsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Starting...", BotName);
|
||||
await LimitLoginRequestsAsync().ConfigureAwait(false);
|
||||
SteamClient.Connect();
|
||||
}
|
||||
|
||||
@@ -507,8 +572,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
BotDatabase.MobileAuthenticator.Init(this);
|
||||
|
||||
if (!BotDatabase.MobileAuthenticator.HasDeviceID) {
|
||||
string deviceID = Program.GetUserInput(Program.EUserInputType.DeviceID, BotName);
|
||||
if (!BotDatabase.MobileAuthenticator.HasCorrectDeviceID) {
|
||||
Logging.LogGenericWarning("Your DeviceID is incorrect or doesn't exist", BotName);
|
||||
string deviceID = Program.GetUserInput(SharedInfo.EUserInputType.DeviceID, BotName);
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
BotDatabase.MobileAuthenticator = null;
|
||||
return;
|
||||
@@ -621,15 +687,29 @@ namespace ArchiSteamFarm {
|
||||
return "Bot " + BotName + " is not running.";
|
||||
}
|
||||
|
||||
if (PlayingBlocked) {
|
||||
return "Bot " + BotName + " is currently being used.";
|
||||
}
|
||||
|
||||
if (CardsFarmer.ManualMode) {
|
||||
return "Bot " + BotName + " is running in manual mode.";
|
||||
}
|
||||
|
||||
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
|
||||
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
|
||||
if (CardsFarmer.CurrentGamesFarming.Count == 0) {
|
||||
return "Bot " + BotName + " is not farming anything.";
|
||||
}
|
||||
|
||||
return "Bot " + BotName + " is not farming anything.";
|
||||
StringBuilder response = new StringBuilder("Bot " + BotName + " is farming ");
|
||||
|
||||
if (CardsFarmer.CurrentGamesFarming.Count == 1) {
|
||||
CardsFarmer.Game game = CardsFarmer.CurrentGamesFarming.First();
|
||||
response.Append("game " + game.AppID + " (" + game.GameName + ", " + game.CardsRemaining + " card drops remaining)");
|
||||
} else {
|
||||
response.Append("appIDs " + string.Join(", ", CardsFarmer.CurrentGamesFarming.Select(game => game.AppID)));
|
||||
}
|
||||
|
||||
response.Append(" and has a total of " + CardsFarmer.GamesToFarm.Count + " games (" + CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining) + " cards) left to farm.");
|
||||
return response.ToString();
|
||||
}
|
||||
|
||||
private static string ResponseStatus(ulong steamID, string botName) {
|
||||
@@ -663,7 +743,7 @@ namespace ArchiSteamFarm {
|
||||
StringBuilder result = new StringBuilder(Environment.NewLine);
|
||||
|
||||
byte runningBotsCount = 0;
|
||||
foreach (Bot bot in Bots.Values) {
|
||||
foreach (Bot bot in Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value)) {
|
||||
result.Append(bot.ResponseStatus(steamID) + Environment.NewLine);
|
||||
if (bot.KeepRunning) {
|
||||
runningBotsCount++;
|
||||
@@ -743,7 +823,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
await Task.WhenAll(Bots.Values.Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false);
|
||||
await Task.WhenAll(Bots.Values.Where(bot => bot.SteamClient.IsConnected).Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false);
|
||||
return "Done!";
|
||||
}
|
||||
|
||||
@@ -797,8 +877,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) {
|
||||
@@ -820,25 +903,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static string ResponseAPI(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
Logging.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
if (steamID != 0) {
|
||||
return !IsOwner(steamID) ? null : GetAPIStatus();
|
||||
}
|
||||
|
||||
if (!IsOwner(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var response = new {
|
||||
Bots
|
||||
};
|
||||
|
||||
try {
|
||||
return JsonConvert.SerializeObject(response);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
Logging.LogNullError(nameof(steamID));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ResponseExit(ulong steamID) {
|
||||
@@ -924,7 +994,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
using (StringReader reader = new StringReader(message))
|
||||
using (IEnumerator<Bot> iterator = Bots.Values.GetEnumerator()) {
|
||||
using (IEnumerator<Bot> iterator = Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value).GetEnumerator()) {
|
||||
string key = reader.ReadLine();
|
||||
Bot currentBot = this;
|
||||
while (!string.IsNullOrEmpty(key) && (currentBot != null)) {
|
||||
@@ -970,7 +1040,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
bool alreadyHandled = false;
|
||||
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected)) {
|
||||
foreach (Bot bot in Bots.Where(bot => (bot.Value != this) && bot.Value.SteamClient.IsConnected && ((result.Items.Count == 0) || result.Items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.Contains(packageID)))).OrderBy(bot => bot.Key).Select(bot => bot.Value)) {
|
||||
|
||||
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 +1061,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
|
||||
@@ -1141,7 +1220,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if ((ownedGames == null) || (ownedGames.Count == 0)) {
|
||||
return "List of owned games is empty!";
|
||||
return "<" + BotName + "> List of owned games is empty!";
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
@@ -1153,9 +1232,9 @@ namespace ArchiSteamFarm {
|
||||
if (uint.TryParse(game, out appID)) {
|
||||
string ownedName;
|
||||
if (ownedGames.TryGetValue(appID, out ownedName)) {
|
||||
response.Append(Environment.NewLine + "Owned already: " + appID + " | " + ownedName);
|
||||
response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + appID + " | " + ownedName);
|
||||
} else {
|
||||
response.Append(Environment.NewLine + "Not owned yet: " + appID);
|
||||
response.Append(Environment.NewLine + "<" + BotName + "> Not owned yet: " + appID);
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -1163,7 +1242,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// This is a string, so check our entire library
|
||||
foreach (KeyValuePair<uint, string> ownedGame in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) {
|
||||
response.Append(Environment.NewLine + "Owned already: " + ownedGame.Key + " | " + ownedGame.Value);
|
||||
response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + ownedGame.Key + " | " + ownedGame.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1171,7 +1250,7 @@ namespace ArchiSteamFarm {
|
||||
return response.ToString();
|
||||
}
|
||||
|
||||
return "Not owned yet: " + query;
|
||||
return "<" + BotName + "> Not owned yet: " + query;
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseOwns(ulong steamID, string botName, string query) {
|
||||
@@ -1192,6 +1271,26 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseOwnsAll(ulong steamID, string query) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(query));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsOwner(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] responses = await Task.WhenAll(Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value.ResponseOwns(steamID, query))).ConfigureAwait(false);
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
foreach (string response in responses.Where(response => !string.IsNullOrEmpty(response))) {
|
||||
result.Append(response);
|
||||
}
|
||||
|
||||
return result.Length != 0 ? result.ToString() : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponsePlay(ulong steamID, HashSet<uint> gameIDs) {
|
||||
if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) {
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count), BotName);
|
||||
@@ -1349,7 +1448,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
await Program.CheckForUpdate(true).ConfigureAwait(false);
|
||||
await ASF.CheckForUpdate(true).ConfigureAwait(false);
|
||||
return "Done!";
|
||||
}
|
||||
|
||||
@@ -1363,7 +1462,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "ASF V" + Program.Version;
|
||||
return "ASF V" + SharedInfo.Version;
|
||||
}
|
||||
|
||||
private void HandleCallbacks() {
|
||||
@@ -1446,7 +1545,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private bool InitializeLoginAndPassword(bool requiresPassword) {
|
||||
if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
|
||||
BotConfig.SteamLogin = Program.GetUserInput(Program.EUserInputType.Login, BotName);
|
||||
BotConfig.SteamLogin = Program.GetUserInput(SharedInfo.EUserInputType.Login, BotName);
|
||||
if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1456,7 +1555,7 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
BotConfig.SteamPassword = Program.GetUserInput(Program.EUserInputType.Password, BotName);
|
||||
BotConfig.SteamPassword = Program.GetUserInput(SharedInfo.EUserInputType.Password, BotName);
|
||||
return !string.IsNullOrEmpty(BotConfig.SteamPassword);
|
||||
}
|
||||
|
||||
@@ -1569,6 +1668,10 @@ namespace ArchiSteamFarm {
|
||||
// 2FA tokens are expiring soon, don't use limiter when user is providing one
|
||||
if ((TwoFactorCode == null) || (BotDatabase.MobileAuthenticator != null)) {
|
||||
await LimitLoginRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
if (!KeepRunning || SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SteamClient.Connect();
|
||||
@@ -1590,15 +1693,16 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!ArchiWebHandler.Ready) {
|
||||
return;
|
||||
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!ArchiWebHandler.Ready) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool acceptedSomething = false;
|
||||
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
|
||||
HandledGifts.Add(gid);
|
||||
|
||||
@@ -1606,16 +1710,40 @@ namespace ArchiSteamFarm {
|
||||
await LimitGiftsRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
if (await ArchiWebHandler.AcceptGift(gid).ConfigureAwait(false)) {
|
||||
acceptedSomething = true;
|
||||
Logging.LogGenericInfo("Success!", BotName);
|
||||
} else {
|
||||
Logging.LogGenericInfo("Failed!", BotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (acceptedSomething) {
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
private async 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();
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
|
||||
|
||||
if (!ArchiWebHandler.Ready) {
|
||||
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!ArchiWebHandler.Ready) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
|
||||
@@ -1720,6 +1848,19 @@ namespace ArchiSteamFarm {
|
||||
await HandleMessage(callback.SteamID, callback.SteamID, lastMessage.Message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnPersonaState(SteamFriends.PersonaStateCallback callback) {
|
||||
if (callback == null) {
|
||||
Logging.LogNullError(nameof(callback), BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.FriendID != SteamClient.SteamID) {
|
||||
return;
|
||||
}
|
||||
|
||||
Events.OnStateUpdated(this, callback);
|
||||
}
|
||||
|
||||
private void OnAccountInfo(SteamUser.AccountInfoCallback callback) {
|
||||
if (callback == null) {
|
||||
Logging.LogNullError(nameof(callback), BotName);
|
||||
@@ -1751,7 +1892,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
switch (callback.Result) {
|
||||
case EResult.AccountLogonDenied:
|
||||
AuthCode = Program.GetUserInput(Program.EUserInputType.SteamGuard, BotName);
|
||||
AuthCode = Program.GetUserInput(SharedInfo.EUserInputType.SteamGuard, BotName);
|
||||
if (string.IsNullOrEmpty(AuthCode)) {
|
||||
Stop();
|
||||
}
|
||||
@@ -1759,7 +1900,7 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
case EResult.AccountLoginDeniedNeedTwoFactor:
|
||||
if (BotDatabase.MobileAuthenticator == null) {
|
||||
TwoFactorCode = Program.GetUserInput(Program.EUserInputType.TwoFactorAuthentication, BotName);
|
||||
TwoFactorCode = Program.GetUserInput(SharedInfo.EUserInputType.TwoFactorAuthentication, BotName);
|
||||
if (string.IsNullOrEmpty(TwoFactorCode)) {
|
||||
Stop();
|
||||
}
|
||||
@@ -1783,14 +1924,14 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (BotDatabase.MobileAuthenticator == null) {
|
||||
// Support and convert SDA files
|
||||
string maFilePath = Path.Combine(Program.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
|
||||
string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
|
||||
if (File.Exists(maFilePath)) {
|
||||
ImportAuthenticator(maFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
|
||||
BotConfig.SteamParentalPIN = Program.GetUserInput(Program.EUserInputType.SteamParentalPIN, BotName);
|
||||
BotConfig.SteamParentalPIN = Program.GetUserInput(SharedInfo.EUserInputType.SteamParentalPIN, BotName);
|
||||
if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
|
||||
Stop();
|
||||
return;
|
||||
@@ -1815,13 +1956,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (Program.GlobalConfig.Statistics) {
|
||||
ArchiWebHandler.JoinGroup(ArchiSCFarmGroup).Forget();
|
||||
ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).Forget();
|
||||
}
|
||||
|
||||
Trading.CheckTrades().Forget();
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
|
||||
CardsFarmer.StartFarming().Forget();
|
||||
break;
|
||||
case EResult.NoConnection:
|
||||
case EResult.ServiceUnavailable:
|
||||
@@ -1904,7 +2042,7 @@ namespace ArchiSteamFarm {
|
||||
foreach (ArchiHandler.NotificationsCallback.ENotification notification in callback.Notifications) {
|
||||
switch (notification) {
|
||||
case ArchiHandler.NotificationsCallback.ENotification.Items:
|
||||
CardsFarmer.OnNewItemsNotification();
|
||||
CardsFarmer.OnNewItemsNotification().Forget();
|
||||
if (BotConfig.DismissInventoryNotifications) {
|
||||
ArchiWebHandler.MarkInventory().Forget();
|
||||
}
|
||||
@@ -1949,14 +2087,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
|
||||
private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
|
||||
if (callback == null) {
|
||||
Logging.LogNullError(nameof(callback), BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,25 @@ 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 enum EFarmingOrder : byte {
|
||||
Unordered,
|
||||
AppIDsAscending,
|
||||
AppIDsDescending,
|
||||
CardDropsAscending,
|
||||
CardDropsDescending,
|
||||
HoursAscending,
|
||||
HoursDescending,
|
||||
NamesAscending,
|
||||
NamesDescending
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool StartOnLaunch { get; private set; } = true;
|
||||
internal readonly bool Enabled = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool StartOnLaunch = true;
|
||||
|
||||
[JsonProperty]
|
||||
internal string SteamLogin { get; set; }
|
||||
@@ -53,65 +66,67 @@ 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 = true;
|
||||
|
||||
[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 EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool HandleOfflineMessages { get; private set; } = false;
|
||||
internal readonly bool FarmOffline = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool AcceptGifts { get; private set; } = false;
|
||||
internal readonly bool HandleOfflineMessages = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool IsBotAccount { get; private set; } = false;
|
||||
internal readonly bool AcceptGifts = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool SteamTradeMatcher { get; private set; } = false;
|
||||
internal readonly bool IsBotAccount = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ForwardKeysToOtherBots { get; private set; } = false;
|
||||
internal readonly bool SteamTradeMatcher = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool DistributeKeys { get; private set; } = false;
|
||||
internal readonly bool ForwardKeysToOtherBots = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
|
||||
internal readonly bool DistributeKeys = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool SendOnFarmingFinished { get; private set; } = false;
|
||||
internal readonly bool ShutdownOnFarmingFinished = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
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) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
@@ -132,6 +147,11 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (botConfig == null) {
|
||||
Logging.LogNullError(nameof(botConfig));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Support encrypted passwords
|
||||
if ((botConfig.PasswordFormat != CryptoHelper.ECryptoMethod.PlainText) && !string.IsNullOrEmpty(botConfig.SteamPassword)) {
|
||||
// In worst case password will result in null, which will have to be corrected by user during runtime
|
||||
@@ -145,7 +165,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;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
using HtmlAgilityPack;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -35,18 +34,61 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CardsFarmer : IDisposable {
|
||||
internal sealed class Game {
|
||||
[JsonProperty]
|
||||
internal readonly uint AppID;
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly string GameName;
|
||||
|
||||
[JsonProperty]
|
||||
internal float HoursPlayed { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal ushort CardsRemaining { get; set; }
|
||||
|
||||
internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg";
|
||||
|
||||
internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) {
|
||||
if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
|
||||
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
|
||||
}
|
||||
|
||||
AppID = appID;
|
||||
GameName = gameName;
|
||||
HoursPlayed = hoursPlayed;
|
||||
CardsRemaining = cardsRemaining;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (ReferenceEquals(null, obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return obj is Game && Equals((Game) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => (int) AppID;
|
||||
|
||||
private bool Equals(Game other) => AppID == other.AppID;
|
||||
}
|
||||
|
||||
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
|
||||
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
|
||||
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
|
||||
|
||||
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
|
||||
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer Timer;
|
||||
private readonly Timer IdleFarmingTimer;
|
||||
|
||||
[JsonProperty]
|
||||
internal bool ManualMode { get; private set; }
|
||||
@@ -60,22 +102,25 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot = bot;
|
||||
|
||||
if ((Timer == null) && (Program.GlobalConfig.IdleFarmingPeriod > 0)) {
|
||||
Timer = new Timer(
|
||||
if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
|
||||
IdleFarmingTimer = new Timer(
|
||||
e => CheckGamesForFarming(),
|
||||
null,
|
||||
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(Bot.Bots.Count), // Delay
|
||||
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay
|
||||
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
CurrentGamesFarming.Dispose();
|
||||
FarmResetEvent.Dispose();
|
||||
GamesToFarm.Dispose();
|
||||
FarmingSemaphore.Dispose();
|
||||
FarmResetEvent.Dispose();
|
||||
|
||||
Timer?.Dispose();
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
IdleFarmingTimer?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task SwitchToManualMode(bool manualMode) {
|
||||
@@ -113,7 +158,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName);
|
||||
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games (" + GamesToFarm.Sum(game => game.CardsRemaining) + " cards) to farm on this account...", Bot.BotName);
|
||||
|
||||
// This is the last moment for final check if we can farm
|
||||
if (Bot.PlayingBlocked) {
|
||||
@@ -130,20 +175,20 @@ namespace ArchiSteamFarm {
|
||||
if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
|
||||
Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName);
|
||||
while (GamesToFarm.Count > 0) {
|
||||
HashSet<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
|
||||
HashSet<Game> gamesToFarmSolo = new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= 2));
|
||||
if (gamesToFarmSolo.Count > 0) {
|
||||
while (gamesToFarmSolo.Count > 0) {
|
||||
uint appID = gamesToFarmSolo.First();
|
||||
if (await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
gamesToFarmSolo.Remove(appID);
|
||||
Game game = gamesToFarmSolo.First();
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
gamesToFarmSolo.Remove(game);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (FarmMultiple()) {
|
||||
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
|
||||
if (FarmMultiple(GamesToFarm.OrderByDescending(game => game.HoursPlayed).Take(MaxGamesPlayedConcurrently))) {
|
||||
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Select(game => game.AppID)), Bot.BotName);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
@@ -153,8 +198,8 @@ namespace ArchiSteamFarm {
|
||||
} else { // If we have unrestricted card drops, we use simple algorithm
|
||||
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
|
||||
while (GamesToFarm.Count > 0) {
|
||||
uint appID = GamesToFarm.Keys.FirstOrDefault();
|
||||
if (await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
Game game = GamesToFarm.First();
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -203,12 +248,15 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal void OnDisconnected() => StopFarming().Forget();
|
||||
|
||||
internal void OnNewItemsNotification() {
|
||||
if (!NowFarming) {
|
||||
internal async Task OnNewItemsNotification() {
|
||||
if (NowFarming) {
|
||||
FarmResetEvent.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
FarmResetEvent.Set();
|
||||
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
|
||||
// In this case, perform a loot if user wants to do so
|
||||
await Bot.LootIfNeeded().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task OnNewGameAdded() {
|
||||
@@ -218,7 +266,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Values.Min() < 2)) {
|
||||
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) {
|
||||
// If we have Complex algorithm and some games to boost, it's also worth to make a check
|
||||
// That's because we would check for new games after our current round anyway
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
@@ -226,20 +274,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
|
||||
if (gamesToFarm == null) {
|
||||
Logging.LogNullError(nameof(gamesToFarm));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<uint> result = new HashSet<uint>();
|
||||
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm.Where(keyValue => keyValue.Value >= 2)) {
|
||||
result.Add(keyValue.Key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> IsAnythingToFarm() {
|
||||
Logging.LogGenericInfo("Checking badges...", Bot.BotName);
|
||||
|
||||
@@ -267,10 +301,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
GamesToFarm.Clear();
|
||||
GamesToFarm.ClearAndTrim();
|
||||
CheckPage(htmlDocument);
|
||||
|
||||
if (maxPages == 1) {
|
||||
SortGamesToFarm();
|
||||
return GamesToFarm.Count > 0;
|
||||
}
|
||||
|
||||
@@ -283,9 +318,47 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
SortGamesToFarm();
|
||||
return GamesToFarm.Count > 0;
|
||||
}
|
||||
|
||||
private void SortGamesToFarm() {
|
||||
IOrderedEnumerable<Game> gamesToFarm;
|
||||
switch (Bot.BotConfig.FarmingOrder) {
|
||||
case BotConfig.EFarmingOrder.Unordered:
|
||||
return;
|
||||
case BotConfig.EFarmingOrder.AppIDsAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.AppID);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.AppIDsDescending:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(game => game.AppID);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.CardDropsAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.CardDropsDescending:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.HoursAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.HoursDescending:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.NamesAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.GameName);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.NamesDescending:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName);
|
||||
break;
|
||||
default:
|
||||
Logging.LogGenericError("Unhandled case: " + Bot.BotConfig.FarmingOrder, Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing
|
||||
}
|
||||
|
||||
private void CheckPage(HtmlDocument htmlDocument) {
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogNullError(nameof(htmlDocument), Bot.BotName);
|
||||
@@ -293,7 +366,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats']");
|
||||
if (htmlNodes == null) { // For example a page full of non-games badges
|
||||
if (htmlNodes == null) { // No eligible badges
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -303,6 +376,12 @@ namespace ArchiSteamFarm {
|
||||
continue; // This game is not needed for farming
|
||||
}
|
||||
|
||||
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']");
|
||||
if (progressNode == null) {
|
||||
continue; // e.g. Holiday Sale 2015
|
||||
}
|
||||
|
||||
// AppIDs
|
||||
string steamLink = farmingNode.GetAttributeValue("href", null);
|
||||
if (string.IsNullOrEmpty(steamLink)) {
|
||||
Logging.LogNullError(nameof(steamLink), Bot.BotName);
|
||||
@@ -333,6 +412,7 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Hours
|
||||
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
|
||||
if (timeNode == null) {
|
||||
Logging.LogNullError(nameof(timeNode), Bot.BotName);
|
||||
@@ -347,15 +427,64 @@ namespace ArchiSteamFarm {
|
||||
|
||||
float hours = 0;
|
||||
|
||||
Match match = Regex.Match(hoursString, @"[0-9\.,]+");
|
||||
if (match.Success) {
|
||||
if (!float.TryParse(match.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
|
||||
Match hoursMatch = Regex.Match(hoursString, @"[0-9\.,]+");
|
||||
if (hoursMatch.Success) { // Might fail if we have 0.0 hours played
|
||||
if (!float.TryParse(hoursMatch.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
|
||||
Logging.LogNullError(nameof(hours), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GamesToFarm[appID] = hours;
|
||||
// Cards
|
||||
string progress = progressNode.InnerText;
|
||||
if (string.IsNullOrEmpty(progress)) {
|
||||
Logging.LogNullError(nameof(progress), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
Match progressMatch = Regex.Match(progress, @"\d+");
|
||||
if (!progressMatch.Success) {
|
||||
Logging.LogNullError(nameof(progressMatch), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
ushort cardsRemaining;
|
||||
if (!ushort.TryParse(progressMatch.Value, out cardsRemaining) || (cardsRemaining == 0)) {
|
||||
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Names
|
||||
HtmlNode nameNode = htmlNode.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]");
|
||||
if (nameNode == null) {
|
||||
Logging.LogNullError(nameof(nameNode), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
string name = nameNode.InnerText;
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
Logging.LogNullError(nameof(name), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
int nameStartIndex = name.IndexOf(" by playing ", StringComparison.Ordinal);
|
||||
if (nameStartIndex <= 0) {
|
||||
Logging.LogNullError(nameof(nameStartIndex));
|
||||
return;
|
||||
}
|
||||
|
||||
nameStartIndex += 12;
|
||||
|
||||
int nameEndIndex = name.LastIndexOf('.');
|
||||
if (nameEndIndex <= nameStartIndex) {
|
||||
Logging.LogNullError(nameof(nameEndIndex));
|
||||
return;
|
||||
}
|
||||
|
||||
name = name.Substring(nameStartIndex, nameEndIndex - nameStartIndex);
|
||||
|
||||
// Final result
|
||||
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,13 +510,13 @@ namespace ArchiSteamFarm {
|
||||
StartFarming().Forget();
|
||||
}
|
||||
|
||||
private async Task<bool?> ShouldFarm(uint appID) {
|
||||
if (appID == 0) {
|
||||
Logging.LogNullError(nameof(appID), Bot.BotName);
|
||||
private async Task<bool?> ShouldFarm(Game game) {
|
||||
if (game == null) {
|
||||
Logging.LogNullError(nameof(game), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(game.AppID).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -404,90 +533,75 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte cardsRemaining = 0;
|
||||
ushort cardsRemaining = 0;
|
||||
|
||||
Match match = Regex.Match(progress, @"\d+");
|
||||
if (match.Success) {
|
||||
if (!byte.TryParse(match.Value, out cardsRemaining)) {
|
||||
if (!ushort.TryParse(match.Value, out cardsRemaining)) {
|
||||
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
|
||||
game.CardsRemaining = cardsRemaining;
|
||||
|
||||
Logging.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + cardsRemaining + " cards remaining", Bot.BotName);
|
||||
return cardsRemaining > 0;
|
||||
}
|
||||
|
||||
private bool FarmMultiple() {
|
||||
if (GamesToFarm.Count == 0) {
|
||||
return true;
|
||||
private bool FarmMultiple(IEnumerable<Game> games) {
|
||||
if (games == null) {
|
||||
Logging.LogNullError(nameof(games));
|
||||
return false;
|
||||
}
|
||||
|
||||
float maxHour = 0;
|
||||
foreach (KeyValuePair<uint, float> game in GamesToFarm) {
|
||||
CurrentGamesFarming.Add(game.Key);
|
||||
if (game.Value > maxHour) {
|
||||
maxHour = game.Value;
|
||||
}
|
||||
CurrentGamesFarming.ReplaceWith(games);
|
||||
|
||||
if (CurrentGamesFarming.Count >= MaxGamesPlayedConcurrently) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Logging.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)), Bot.BotName);
|
||||
|
||||
if (maxHour >= 2) {
|
||||
CurrentGamesFarming.ClearAndTrim();
|
||||
return true;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming), Bot.BotName);
|
||||
|
||||
bool result = FarmHours(maxHour, CurrentGamesFarming);
|
||||
bool result = FarmHours(CurrentGamesFarming);
|
||||
CurrentGamesFarming.ClearAndTrim();
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> FarmSolo(uint appID) {
|
||||
if (appID == 0) {
|
||||
Logging.LogNullError(nameof(appID), Bot.BotName);
|
||||
private async Task<bool> FarmSolo(Game game) {
|
||||
if (game == null) {
|
||||
Logging.LogNullError(nameof(game), Bot.BotName);
|
||||
return true;
|
||||
}
|
||||
|
||||
CurrentGamesFarming.Add(appID);
|
||||
CurrentGamesFarming.Add(game);
|
||||
|
||||
Logging.LogGenericInfo("Now farming: " + appID, Bot.BotName);
|
||||
Logging.LogGenericInfo("Now farming: " + game.AppID + " (" + game.GameName + ")", Bot.BotName);
|
||||
|
||||
bool result = await Farm(appID).ConfigureAwait(false);
|
||||
bool result = await Farm(game).ConfigureAwait(false);
|
||||
CurrentGamesFarming.ClearAndTrim();
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float hours;
|
||||
if (!GamesToFarm.TryRemove(appID, out hours)) {
|
||||
return false;
|
||||
}
|
||||
GamesToFarm.Remove(game);
|
||||
|
||||
TimeSpan timeSpan = TimeSpan.FromHours(hours);
|
||||
Logging.LogGenericInfo("Done farming: " + appID + " after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
|
||||
TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed);
|
||||
Logging.LogGenericInfo("Done farming: " + game.AppID + " (" + game.GameName + ") after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> Farm(uint appID) {
|
||||
if (appID == 0) {
|
||||
Logging.LogNullError(nameof(appID), Bot.BotName);
|
||||
private async Task<bool> Farm(Game game) {
|
||||
if (game == null) {
|
||||
Logging.LogNullError(nameof(game), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGame(appID, Bot.BotConfig.CustomGamePlayedWhileFarming);
|
||||
Bot.ArchiHandler.PlayGame(game.AppID, Bot.BotConfig.CustomGamePlayedWhileFarming);
|
||||
DateTime endFarmingDate = DateTime.Now.AddHours(Program.GlobalConfig.MaxFarmingTime);
|
||||
|
||||
bool success = true;
|
||||
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false);
|
||||
|
||||
while (keepFarming.GetValueOrDefault(true) && (DateTime.Now < endFarmingDate)) {
|
||||
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
|
||||
Logging.LogGenericInfo("Still farming: " + game.AppID + " (" + game.GameName + ")", Bot.BotName);
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.Now;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
@@ -496,30 +610,41 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
GamesToFarm[appID] += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
game.HoursPlayed += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
|
||||
if (!success) {
|
||||
break;
|
||||
}
|
||||
|
||||
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
keepFarming = await ShouldFarm(game).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
|
||||
Logging.LogGenericInfo("Stopped farming: " + game.AppID + " (" + game.GameName + ")", Bot.BotName);
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool FarmHours(float maxHour, ConcurrentHashSet<uint> appIDs) {
|
||||
if ((maxHour < 0) || (appIDs == null) || (appIDs.Count == 0)) {
|
||||
Logging.LogNullError(nameof(maxHour) + " || " + nameof(appIDs) + " || " + nameof(appIDs.Count), Bot.BotName);
|
||||
private bool FarmHours(ConcurrentHashSet<Game> games) {
|
||||
if ((games == null) || (games.Count == 0)) {
|
||||
Logging.LogNullError(nameof(games), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGames(appIDs, Bot.BotConfig.CustomGamePlayedWhileFarming);
|
||||
float maxHour = games.Max(game => game.HoursPlayed);
|
||||
if (maxHour < 0) {
|
||||
Logging.LogNullError(nameof(maxHour), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maxHour >= 2) {
|
||||
Logging.LogGenericError("Received request for past-2h games!", Bot.BotName);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGames(games.Select(game => game.AppID), Bot.BotConfig.CustomGamePlayedWhileFarming);
|
||||
|
||||
bool success = true;
|
||||
while (maxHour < 2) {
|
||||
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
Logging.LogGenericInfo("Still farming: " + string.Join(", ", games.Select(game => game.AppID)), Bot.BotName);
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.Now;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
@@ -529,8 +654,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
float timePlayed = (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
foreach (uint appID in appIDs) {
|
||||
GamesToFarm[appID] += timePlayed;
|
||||
foreach (Game game in games) {
|
||||
game.HoursPlayed += timePlayed;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
@@ -540,7 +665,7 @@ namespace ArchiSteamFarm {
|
||||
maxHour += timePlayed;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", games.Select(game => game.AppID)), Bot.BotName);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
@@ -36,6 +35,9 @@ namespace ArchiSteamFarm {
|
||||
public bool IsReadOnly => false;
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
|
||||
public int Count {
|
||||
get {
|
||||
Lock.EnterReadLock();
|
||||
@@ -48,17 +50,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
|
||||
public bool Add(T item) {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
return HashSet.Add(item);
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
@@ -69,17 +60,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAndTrim() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.Clear();
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) {
|
||||
Lock.EnterReadLock();
|
||||
|
||||
@@ -100,7 +80,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Lock?.Dispose();
|
||||
public void Dispose() => Lock.Dispose();
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
Lock.EnterReadLock();
|
||||
@@ -112,8 +92,51 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
internal void Add(T item) {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
try {
|
||||
HashSet.Add(item);
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ReplaceWith(IEnumerable<T> items) {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.Clear();
|
||||
|
||||
foreach (T item in items) {
|
||||
HashSet.Add(item);
|
||||
}
|
||||
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearAndTrim() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.Clear();
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void TrimExcess() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
ArchiSteamFarm/Events.cs
Normal file
20
ArchiSteamFarm/Events.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Events {
|
||||
internal static void OnBotShutdown() {
|
||||
if (Program.ShutdownSequenceInitialized || Program.WCF.IsServerRunning() || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("No bots are running, exiting");
|
||||
Task.Delay(5000).Wait();
|
||||
Program.Shutdown();
|
||||
}
|
||||
|
||||
internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -40,12 +41,11 @@ namespace ArchiSteamFarm {
|
||||
private static readonly ConcurrentHashSet<LoggingRule> ConsoleLoggingRules = new ConcurrentHashSet<LoggingRule>();
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private static bool IsUsingCustomConfiguration, IsWaitingForUserInput;
|
||||
private static bool IsWaitingForUserInput;
|
||||
|
||||
internal static void InitCoreLoggers() {
|
||||
internal static void InitLoggers() {
|
||||
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;
|
||||
@@ -60,15 +60,6 @@ namespace ArchiSteamFarm {
|
||||
config.AddTarget(consoleTarget);
|
||||
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget));
|
||||
|
||||
LogManager.Configuration = config;
|
||||
InitConsoleLoggers();
|
||||
}
|
||||
|
||||
internal static void InitEnhancedLoggers() {
|
||||
if (IsUsingCustomConfiguration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Program.IsRunningAsService) {
|
||||
EventLogTarget eventLogTarget = new EventLogTarget("EventLog") {
|
||||
Layout = EventLogLayout,
|
||||
@@ -76,21 +67,21 @@ namespace ArchiSteamFarm {
|
||||
Source = SharedInfo.EventLogSource
|
||||
};
|
||||
|
||||
LogManager.Configuration.AddTarget(eventLogTarget);
|
||||
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, eventLogTarget));
|
||||
} else {
|
||||
config.AddTarget(eventLogTarget);
|
||||
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, eventLogTarget));
|
||||
} else if (Program.Mode != Program.EMode.Client) {
|
||||
FileTarget fileTarget = new FileTarget("File") {
|
||||
DeleteOldFileOnStartup = true,
|
||||
FileName = Program.LogFile,
|
||||
FileName = SharedInfo.LogFile,
|
||||
Layout = GeneralLayout
|
||||
};
|
||||
|
||||
LogManager.Configuration.AddTarget(fileTarget);
|
||||
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
|
||||
config.AddTarget(fileTarget);
|
||||
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
|
||||
}
|
||||
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
LogGenericInfo("Logging module initialized!");
|
||||
LogManager.Configuration = config;
|
||||
InitConsoleLoggers();
|
||||
}
|
||||
|
||||
internal static void OnUserInputStart() {
|
||||
@@ -121,7 +112,7 @@ namespace ArchiSteamFarm {
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
internal static void LogGenericError(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
internal static void LogGenericError(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
@@ -130,7 +121,7 @@ namespace ArchiSteamFarm {
|
||||
Logger.Error($"{botName}|{previousMethodName}() {message}");
|
||||
}
|
||||
|
||||
internal static void LogGenericException(Exception exception, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
internal static void LogGenericException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (exception == null) {
|
||||
LogNullError(nameof(exception), botName);
|
||||
return;
|
||||
@@ -139,16 +130,34 @@ namespace ArchiSteamFarm {
|
||||
Logger.Error(exception, $"{botName}|{previousMethodName}()");
|
||||
}
|
||||
|
||||
internal static void LogFatalException(Exception exception, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
internal static void LogFatalException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (exception == null) {
|
||||
LogNullError(nameof(exception), botName);
|
||||
return;
|
||||
}
|
||||
|
||||
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(SharedInfo.LogFile, DateTime.Now + " ASF V" + SharedInfo.Version + " has run into fatal exception before core logging module was even able to initialize!" + Environment.NewLine);
|
||||
|
||||
while (true) {
|
||||
File.AppendAllText(SharedInfo.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) {
|
||||
internal static void LogGenericWarning(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
@@ -157,7 +166,7 @@ namespace ArchiSteamFarm {
|
||||
Logger.Warn($"{botName}|{previousMethodName}() {message}");
|
||||
}
|
||||
|
||||
internal static void LogGenericInfo(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
internal static void LogGenericInfo(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
@@ -167,7 +176,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
|
||||
internal static void LogNullError(string nullObjectName, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
internal static void LogNullError(string nullObjectName, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(nullObjectName)) {
|
||||
return;
|
||||
}
|
||||
@@ -177,7 +186,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal static void LogGenericDebug(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
internal static void LogGenericDebug(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
|
||||
@@ -66,14 +66,14 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private static short SteamTimeDifference;
|
||||
|
||||
internal bool HasDeviceID => !string.IsNullOrEmpty(DeviceID);
|
||||
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
|
||||
|
||||
#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")]
|
||||
@@ -108,6 +108,11 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasCorrectDeviceID) {
|
||||
Logging.LogGenericWarning("Can't execute properly due to invalid DeviceID!", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
@@ -129,6 +134,11 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!HasCorrectDeviceID) {
|
||||
Logging.LogGenericWarning("Can't execute properly due to invalid DeviceID!", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
@@ -160,6 +170,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task<HashSet<Confirmation>> GetConfirmations() {
|
||||
if (!HasCorrectDeviceID) {
|
||||
Logging.LogGenericWarning("Can't execute properly due to invalid DeviceID!", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
|
||||
@@ -22,241 +22,35 @@
|
||||
|
||||
*/
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.JSON;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Program {
|
||||
internal enum EUserInputType : byte {
|
||||
Unknown,
|
||||
DeviceID,
|
||||
Login,
|
||||
Password,
|
||||
PhoneNumber,
|
||||
SMS,
|
||||
SteamGuard,
|
||||
SteamParentalPIN,
|
||||
RevocationCode,
|
||||
TwoFactorAuthentication,
|
||||
WCFHostname
|
||||
}
|
||||
|
||||
private enum EMode : byte {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
Unknown,
|
||||
internal enum EMode : byte {
|
||||
Normal, // Standard most common usage
|
||||
Client, // WCF client only
|
||||
Server // Normal + WCF server
|
||||
}
|
||||
|
||||
internal const string ASF = "ASF";
|
||||
internal const string ConfigDirectory = "config";
|
||||
internal const string DebugDirectory = "debug";
|
||||
internal const string LogFile = "log.txt";
|
||||
|
||||
private const string GithubReleaseURL = "https://api.github.com/repos/" + SharedInfo.GithubRepo + "/releases"; // GitHub API is HTTPS only
|
||||
private const string GlobalConfigFile = ASF + ".json";
|
||||
private const string GlobalDatabaseFile = ASF + ".db";
|
||||
|
||||
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
|
||||
internal static readonly WCF WCF = new WCF();
|
||||
|
||||
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; }
|
||||
internal static bool ShutdownSequenceInitialized { get; private set; }
|
||||
internal static EMode Mode { get; private set; } = EMode.Normal;
|
||||
internal static GlobalConfig GlobalConfig { get; private set; }
|
||||
internal static GlobalDatabase GlobalDatabase { get; private set; }
|
||||
|
||||
private static bool ShutdownSequenceInitialized;
|
||||
private static Timer AutoUpdatesTimer;
|
||||
private static EMode Mode = EMode.Normal;
|
||||
private static WebBrowser WebBrowser;
|
||||
|
||||
internal static async Task CheckForUpdate(bool updateOverride = false) {
|
||||
string oldExeFile = ExecutableFile + ".old";
|
||||
|
||||
// We booted successfully so we can now remove old exe file
|
||||
if (File.Exists(oldExeFile)) {
|
||||
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
File.Delete(oldExeFile);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
Logging.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
|
||||
}
|
||||
}
|
||||
|
||||
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((AutoUpdatesTimer == null) && GlobalConfig.AutoUpdates) {
|
||||
AutoUpdatesTimer = new Timer(
|
||||
async e => await CheckForUpdate().ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromDays(1), // Delay
|
||||
TimeSpan.FromDays(1) // Period
|
||||
);
|
||||
|
||||
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
|
||||
}
|
||||
|
||||
string releaseURL = GithubReleaseURL;
|
||||
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
|
||||
releaseURL += "/latest";
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Checking new version...");
|
||||
|
||||
string response = await WebBrowser.UrlGetToContentRetry(releaseURL).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(response)) {
|
||||
Logging.LogGenericWarning("Could not check latest version!");
|
||||
return;
|
||||
}
|
||||
|
||||
GitHub.ReleaseResponse releaseResponse;
|
||||
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
|
||||
try {
|
||||
releaseResponse = JsonConvert.DeserializeObject<GitHub.ReleaseResponse>(response);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
List<GitHub.ReleaseResponse> releases;
|
||||
try {
|
||||
releases = JsonConvert.DeserializeObject<List<GitHub.ReleaseResponse>>(response);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((releases == null) || (releases.Count == 0)) {
|
||||
Logging.LogGenericWarning("Could not check latest version!");
|
||||
return;
|
||||
}
|
||||
|
||||
releaseResponse = releases[0];
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
|
||||
Logging.LogGenericWarning("Could not check latest version!");
|
||||
return;
|
||||
}
|
||||
|
||||
Version newVersion = new Version(releaseResponse.Tag);
|
||||
|
||||
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + newVersion);
|
||||
|
||||
if (Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version
|
||||
return;
|
||||
}
|
||||
|
||||
if (!updateOverride && !GlobalConfig.AutoUpdates) {
|
||||
Logging.LogGenericInfo("New version is available!");
|
||||
Logging.LogGenericInfo("Consider updating yourself!");
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(oldExeFile)) {
|
||||
Logging.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto update logic starts here
|
||||
if (releaseResponse.Assets == null) {
|
||||
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
|
||||
return;
|
||||
}
|
||||
|
||||
GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (binaryAsset == null) {
|
||||
Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) {
|
||||
Logging.LogGenericWarning("Could not proceed with update because download URL is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Downloading new version...");
|
||||
Logging.LogGenericInfo("While waiting, consider donating if you appreciate the work being done :)");
|
||||
|
||||
byte[] result = await WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
string newExeFile = ExecutableFile + ".new";
|
||||
|
||||
// Firstly we create new exec
|
||||
try {
|
||||
File.WriteAllBytes(newExeFile, result);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we move current -> old
|
||||
try {
|
||||
File.Move(ExecutableFile, oldExeFile);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
try {
|
||||
// Cleanup
|
||||
File.Delete(newExeFile);
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we move new -> current
|
||||
try {
|
||||
File.Move(newExeFile, ExecutableFile);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
try {
|
||||
// Cleanup
|
||||
File.Move(oldExeFile, ExecutableFile);
|
||||
File.Delete(newExeFile);
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Update process finished!");
|
||||
|
||||
if (GlobalConfig.AutoRestart) {
|
||||
Logging.LogGenericInfo("Restarting...");
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Restart();
|
||||
} else {
|
||||
Logging.LogGenericInfo("Exiting...");
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
internal static WebBrowser WebBrowser { get; private set; }
|
||||
|
||||
internal static void Exit(byte exitCode = 0) {
|
||||
Shutdown();
|
||||
@@ -267,7 +61,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);
|
||||
}
|
||||
@@ -275,8 +69,8 @@ namespace ArchiSteamFarm {
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
internal static string GetUserInput(EUserInputType userInputType, string botName = ASF, string extraInformation = null) {
|
||||
if (userInputType == EUserInputType.Unknown) {
|
||||
internal static string GetUserInput(SharedInfo.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
|
||||
if (userInputType == SharedInfo.EUserInputType.Unknown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -289,35 +83,35 @@ namespace ArchiSteamFarm {
|
||||
lock (ConsoleLock) {
|
||||
Logging.OnUserInputStart();
|
||||
switch (userInputType) {
|
||||
case EUserInputType.DeviceID:
|
||||
case SharedInfo.EUserInputType.DeviceID:
|
||||
Console.Write("<" + botName + "> Please enter your Device ID (including \"android:\"): ");
|
||||
break;
|
||||
case EUserInputType.Login:
|
||||
case SharedInfo.EUserInputType.Login:
|
||||
Console.Write("<" + botName + "> Please enter your login: ");
|
||||
break;
|
||||
case EUserInputType.Password:
|
||||
case SharedInfo.EUserInputType.Password:
|
||||
Console.Write("<" + botName + "> Please enter your password: ");
|
||||
break;
|
||||
case EUserInputType.PhoneNumber:
|
||||
case SharedInfo.EUserInputType.PhoneNumber:
|
||||
Console.Write("<" + botName + "> Please enter your full phone number (e.g. +1234567890): ");
|
||||
break;
|
||||
case EUserInputType.SMS:
|
||||
case SharedInfo.EUserInputType.SMS:
|
||||
Console.Write("<" + botName + "> Please enter SMS code sent on your mobile: ");
|
||||
break;
|
||||
case EUserInputType.SteamGuard:
|
||||
case SharedInfo.EUserInputType.SteamGuard:
|
||||
Console.Write("<" + botName + "> Please enter the auth code sent to your email: ");
|
||||
break;
|
||||
case EUserInputType.SteamParentalPIN:
|
||||
case SharedInfo.EUserInputType.SteamParentalPIN:
|
||||
Console.Write("<" + botName + "> Please enter steam parental PIN: ");
|
||||
break;
|
||||
case EUserInputType.RevocationCode:
|
||||
case SharedInfo.EUserInputType.RevocationCode:
|
||||
Console.WriteLine("<" + botName + "> PLEASE WRITE DOWN YOUR REVOCATION CODE: " + extraInformation);
|
||||
Console.Write("<" + botName + "> Hit enter once ready...");
|
||||
break;
|
||||
case EUserInputType.TwoFactorAuthentication:
|
||||
case SharedInfo.EUserInputType.TwoFactorAuthentication:
|
||||
Console.Write("<" + botName + "> Please enter your 2 factor auth code from your authenticator app: ");
|
||||
break;
|
||||
case EUserInputType.WCFHostname:
|
||||
case SharedInfo.EUserInputType.WCFHostname:
|
||||
Console.Write("<" + botName + "> Please enter your WCF hostname: ");
|
||||
break;
|
||||
default:
|
||||
@@ -337,25 +131,7 @@ namespace ArchiSteamFarm {
|
||||
return !string.IsNullOrEmpty(result) ? result.Trim() : null;
|
||||
}
|
||||
|
||||
internal static void OnBotShutdown() {
|
||||
if (ShutdownSequenceInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (WCF.IsServerRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("No bots are running, exiting");
|
||||
Thread.Sleep(5000);
|
||||
ShutdownResetEvent.Set();
|
||||
}
|
||||
|
||||
private static void Shutdown() {
|
||||
internal static void Shutdown() {
|
||||
if (!InitShutdownSequence()) {
|
||||
return;
|
||||
}
|
||||
@@ -379,16 +155,20 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static void InitServices() {
|
||||
GlobalConfig = GlobalConfig.Load(Path.Combine(ConfigDirectory, GlobalConfigFile));
|
||||
string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName);
|
||||
|
||||
GlobalConfig = GlobalConfig.Load(globalConfigFile);
|
||||
if (GlobalConfig == null) {
|
||||
Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.json exists and is valid!");
|
||||
Logging.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
|
||||
Thread.Sleep(5000);
|
||||
Exit(1);
|
||||
}
|
||||
|
||||
GlobalDatabase = GlobalDatabase.Load(Path.Combine(ConfigDirectory, GlobalDatabaseFile));
|
||||
string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName);
|
||||
|
||||
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
|
||||
if (GlobalDatabase == null) {
|
||||
Logging.LogGenericError("Global database could not be loaded!");
|
||||
Logging.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
|
||||
Thread.Sleep(5000);
|
||||
Exit(1);
|
||||
}
|
||||
@@ -397,10 +177,38 @@ namespace ArchiSteamFarm {
|
||||
WebBrowser.Init();
|
||||
WCF.Init();
|
||||
|
||||
WebBrowser = new WebBrowser(ASF);
|
||||
WebBrowser = new WebBrowser(SharedInfo.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;
|
||||
case "--client":
|
||||
Mode = EMode.Client;
|
||||
break;
|
||||
case "--server":
|
||||
Mode = EMode.Server;
|
||||
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 +229,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 +240,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,49 +264,62 @@ 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);
|
||||
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
if (!string.IsNullOrEmpty(homeDirectory)) {
|
||||
Directory.SetCurrentDirectory(homeDirectory);
|
||||
|
||||
Directory.SetCurrentDirectory(ExecutableDirectory);
|
||||
// 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(SharedInfo.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(SharedInfo.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);
|
||||
}
|
||||
|
||||
Logging.InitLoggers();
|
||||
Logging.LogGenericInfo("ASF V" + SharedInfo.Version);
|
||||
|
||||
if (!Runtime.IsRuntimeSupported) {
|
||||
Logging.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
|
||||
Thread.Sleep(10000);
|
||||
}
|
||||
|
||||
InitServices();
|
||||
|
||||
// If debugging is on, we prepare debug directory prior to running
|
||||
if (GlobalConfig.Debug) {
|
||||
if (Directory.Exists(DebugDirectory)) {
|
||||
Directory.Delete(DebugDirectory, true);
|
||||
if (Directory.Exists(SharedInfo.DebugDirectory)) {
|
||||
Directory.Delete(SharedInfo.DebugDirectory, true);
|
||||
Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync
|
||||
}
|
||||
Directory.CreateDirectory(DebugDirectory);
|
||||
Directory.CreateDirectory(SharedInfo.DebugDirectory);
|
||||
|
||||
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener(Path.Combine(DebugDirectory, "debug.txt")));
|
||||
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener(Path.Combine(SharedInfo.DebugDirectory, "debug.txt")));
|
||||
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
|
||||
@@ -516,24 +328,22 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// From now on it's server mode
|
||||
Logging.InitEnhancedLoggers();
|
||||
|
||||
if (!Directory.Exists(ConfigDirectory)) {
|
||||
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
|
||||
Logging.LogGenericError("Config directory doesn't exist!");
|
||||
Thread.Sleep(5000);
|
||||
Exit(1);
|
||||
}
|
||||
|
||||
CheckForUpdate().Wait();
|
||||
ASF.CheckForUpdate().Wait();
|
||||
|
||||
// Before attempting to connect, initialize our list of CMs
|
||||
Bot.InitializeCMs(GlobalDatabase.CellID, GlobalDatabase.ServerListProvider);
|
||||
|
||||
bool isRunning = false;
|
||||
|
||||
foreach (string botName in Directory.EnumerateFiles(ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
|
||||
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
|
||||
switch (botName) {
|
||||
case ASF:
|
||||
case SharedInfo.ASF:
|
||||
case "example":
|
||||
case "minimal":
|
||||
continue;
|
||||
@@ -551,7 +361,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Check if we got any bots running
|
||||
if (!isRunning) {
|
||||
OnBotShutdown();
|
||||
Events.OnBotShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using ArchiSteamFarm;
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ArchiSteamFarm")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("ArchiSteamFarm")]
|
||||
@@ -32,5 +32,5 @@ using ArchiSteamFarm;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion(SharedInfo.Version)]
|
||||
[assembly: AssemblyFileVersion(SharedInfo.Version)]
|
||||
[assembly: AssemblyVersion(SharedInfo.VersionNumber)]
|
||||
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.Win32;
|
||||
|
||||
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 +41,64 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (Environment.UserInteractive) {
|
||||
_IsUserInteractive = true;
|
||||
return true;
|
||||
} else if (!IsRunningOnMono) {
|
||||
// If it's non-Mono, we can trust the result
|
||||
_IsUserInteractive = 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;
|
||||
}
|
||||
|
||||
// If it's non-Mono, we can trust the result
|
||||
if (!IsRunningOnMono) {
|
||||
_IsUserInteractive = false;
|
||||
return _IsUserInteractive.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool? _IsRuntimeSupported;
|
||||
internal static bool IsRuntimeSupported {
|
||||
get {
|
||||
if (_IsRuntimeSupported.HasValue) {
|
||||
return _IsRuntimeSupported.Value;
|
||||
}
|
||||
|
||||
if (IsRunningOnMono) {
|
||||
Version monoVersion = GetMonoVersion();
|
||||
if (monoVersion == null) {
|
||||
Logging.LogNullError(nameof(monoVersion));
|
||||
return false;
|
||||
}
|
||||
|
||||
Version minMonoVersion = new Version(4, 4);
|
||||
|
||||
if (monoVersion >= minMonoVersion) {
|
||||
Logging.LogGenericInfo("Your Mono version is OK. Required: " + minMonoVersion + " | Found: " + monoVersion);
|
||||
_IsRuntimeSupported = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Logging.LogGenericWarning("Your Mono version is too old. Required: " + minMonoVersion + " | Found: " + monoVersion);
|
||||
_IsRuntimeSupported = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
Version netVersion = GetNetVersion();
|
||||
if (netVersion == null) {
|
||||
Logging.LogNullError(nameof(netVersion));
|
||||
return false;
|
||||
}
|
||||
|
||||
Version minNetVersion = new Version(4, 6, 1);
|
||||
|
||||
if (netVersion >= minNetVersion) {
|
||||
Logging.LogGenericInfo("Your .NET version is OK. Required: " + minNetVersion + " | Found: " + netVersion);
|
||||
_IsRuntimeSupported = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Logging.LogGenericWarning("Your .NET version is too old. Required: " + minNetVersion + " | Found: " + netVersion);
|
||||
_IsRuntimeSupported = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +110,64 @@ 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 GetNetVersion() {
|
||||
uint release;
|
||||
using (RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) {
|
||||
if (registryKey == null) {
|
||||
Logging.LogNullError(nameof(registryKey));
|
||||
return null;
|
||||
}
|
||||
|
||||
object releaseObj = registryKey.GetValue("Release");
|
||||
if (releaseObj == null) {
|
||||
Logging.LogNullError(nameof(releaseObj));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uint.TryParse(releaseObj.ToString(), out release) || (release == 0)) {
|
||||
Logging.LogNullError(nameof(release));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (release >= 394747) {
|
||||
return new Version(4, 6, 2);
|
||||
}
|
||||
|
||||
if (release >= 394254) {
|
||||
return new Version(4, 6, 1);
|
||||
}
|
||||
|
||||
if (release >= 393295) {
|
||||
return new Version(4, 6);
|
||||
}
|
||||
|
||||
if (release >= 379893) {
|
||||
return new Version(4, 5, 2);
|
||||
}
|
||||
|
||||
if (release >= 378675) {
|
||||
return new Version(4, 5, 1);
|
||||
}
|
||||
|
||||
return release >= 378389 ? new Version(4, 5) : null;
|
||||
}
|
||||
|
||||
private static Version GetMonoVersion() {
|
||||
|
||||
@@ -22,9 +22,26 @@
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class SharedInfo {
|
||||
internal const string Version = "2.1.2.9";
|
||||
internal enum EUserInputType : byte {
|
||||
Unknown,
|
||||
DeviceID,
|
||||
Login,
|
||||
Password,
|
||||
PhoneNumber,
|
||||
SMS,
|
||||
SteamGuard,
|
||||
SteamParentalPIN,
|
||||
RevocationCode,
|
||||
TwoFactorAuthentication,
|
||||
WCFHostname
|
||||
}
|
||||
|
||||
internal const string VersionNumber = "2.1.4.2";
|
||||
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
|
||||
|
||||
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
|
||||
@@ -34,5 +51,20 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal const string EventLog = ServiceName;
|
||||
internal const string EventLogSource = EventLog + "Logger";
|
||||
|
||||
internal const string ASF = "ASF";
|
||||
internal const string ASFDirectory = "ArchiSteamFarm";
|
||||
internal const string ConfigDirectory = "config";
|
||||
internal const string DebugDirectory = "debug";
|
||||
internal const string LogFile = "log.txt";
|
||||
|
||||
internal const ulong ArchiSteamID = 76561198006963719;
|
||||
internal const ulong ASFGroupSteamID = 103582791440160998;
|
||||
|
||||
internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
|
||||
internal const string GlobalConfigFileName = ASF + ".json";
|
||||
internal const string GlobalDatabaseFileName = ASF + ".db";
|
||||
|
||||
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,77 +123,91 @@ 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);
|
||||
}
|
||||
|
||||
if (results.Any(result => (result != null) && ((result.Result == ParseTradeResult.EResult.AcceptedWithItemLose) || (result.Result == ParseTradeResult.EResult.AcceptedWithoutItemLose)))) {
|
||||
// If we finished a trade, perform a loot if user wants to do so
|
||||
await Bot.LootIfNeeded().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) {
|
||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
break;
|
||||
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;
|
||||
Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
|
||||
break;
|
||||
}
|
||||
|
||||
IgnoredTrades.Add(tradeOffer.TradeOfferID);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return result;
|
||||
default:
|
||||
return result;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 +216,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 +232,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 +243,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 +296,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,14 @@ using System;
|
||||
using System.Linq;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Channels;
|
||||
using System.ServiceModel.Description;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
[ServiceContract]
|
||||
internal interface IWCF {
|
||||
[OperationContract]
|
||||
string GetStatus();
|
||||
|
||||
[OperationContract]
|
||||
string HandleCommand(string input);
|
||||
}
|
||||
@@ -43,7 +47,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal static void Init() {
|
||||
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
|
||||
Program.GlobalConfig.WCFHostname = Program.GetUserInput(Program.EUserInputType.WCFHostname);
|
||||
Program.GlobalConfig.WCFHostname = Program.GetUserInput(SharedInfo.EUserInputType.WCFHostname);
|
||||
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
|
||||
return;
|
||||
}
|
||||
@@ -58,15 +62,15 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Program.GlobalConfig.SteamOwnerID == 0) {
|
||||
return "Refusing to handle request because SteamOwnerID is not set!";
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -74,9 +78,11 @@ namespace ArchiSteamFarm {
|
||||
return output;
|
||||
}
|
||||
|
||||
public string GetStatus() => Program.GlobalConfig.SteamOwnerID == 0 ? "{}" : Bot.GetAPIStatus();
|
||||
|
||||
public void Dispose() {
|
||||
ServiceHost?.Close();
|
||||
Client?.Close();
|
||||
StopClient();
|
||||
StopServer();
|
||||
}
|
||||
|
||||
internal bool IsServerRunning() => ServiceHost != null;
|
||||
@@ -87,15 +93,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 +118,14 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceHost.Close();
|
||||
if (ServiceHost.State != CommunicationState.Closed) {
|
||||
try {
|
||||
ServiceHost.Close();
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
ServiceHost = null;
|
||||
}
|
||||
|
||||
@@ -125,12 +141,24 @@ namespace ArchiSteamFarm {
|
||||
|
||||
return Client.HandleCommand(input);
|
||||
}
|
||||
|
||||
private void StopClient() {
|
||||
if (Client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Client.State != CommunicationState.Closed) {
|
||||
Client.Close();
|
||||
}
|
||||
|
||||
Client = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Client : ClientBase<IWCF>, IWCF {
|
||||
internal sealed class Client : ClientBase<IWCF> {
|
||||
internal Client(Binding binding, EndpointAddress address) : base(binding, address) { }
|
||||
|
||||
public string HandleCommand(string input) {
|
||||
internal string HandleCommand(string input) {
|
||||
if (string.IsNullOrEmpty(input)) {
|
||||
Logging.LogNullError(nameof(input));
|
||||
return null;
|
||||
|
||||
@@ -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.IsRuntimeSupported && !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/" + SharedInfo.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;
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
"SteamApiKey": null,
|
||||
"SteamMasterID": 0,
|
||||
"SteamMasterClanID": 0,
|
||||
"CardDropsRestricted": false,
|
||||
"CardDropsRestricted": true,
|
||||
"DismissInventoryNotifications": true,
|
||||
"FarmingOrder": 0,
|
||||
"FarmOffline": false,
|
||||
"HandleOfflineMessages": false,
|
||||
"AcceptGifts": false,
|
||||
|
||||
38
CodeStyle.vssettings
Normal file
38
CodeStyle.vssettings
Normal file
File diff suppressed because one or more lines are too long
@@ -26,6 +26,7 @@ using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using ArchiSteamFarm;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
internal abstract class ASFConfig {
|
||||
@@ -33,6 +34,8 @@ namespace ConfigGenerator {
|
||||
|
||||
internal string FilePath { get; set; }
|
||||
|
||||
private readonly object FileLock = new object();
|
||||
|
||||
protected ASFConfig() {
|
||||
ASFConfigs.Add(this);
|
||||
}
|
||||
@@ -46,7 +49,7 @@ namespace ConfigGenerator {
|
||||
}
|
||||
|
||||
internal void Save() {
|
||||
lock (FilePath) {
|
||||
lock (FileLock) {
|
||||
try {
|
||||
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
|
||||
} catch (Exception e) {
|
||||
@@ -57,8 +60,8 @@ namespace ConfigGenerator {
|
||||
|
||||
internal void Remove() {
|
||||
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
|
||||
lock (FilePath) {
|
||||
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
|
||||
lock (FileLock) {
|
||||
foreach (string botFile in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, queryPath + ".*")) {
|
||||
try {
|
||||
File.Delete(botFile);
|
||||
} catch (Exception e) {
|
||||
@@ -77,16 +80,16 @@ namespace ConfigGenerator {
|
||||
}
|
||||
|
||||
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
|
||||
lock (FilePath) {
|
||||
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
|
||||
lock (FileLock) {
|
||||
foreach (string botFile in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, queryPath + ".*")) {
|
||||
try {
|
||||
File.Move(botFile, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(botFile)));
|
||||
File.Move(botFile, Path.Combine(SharedInfo.ConfigDirectory, botName + Path.GetExtension(botFile)));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
FilePath = Path.Combine(Program.ConfigDirectory, botName + ".json");
|
||||
FilePath = Path.Combine(SharedInfo.ConfigDirectory, botName + ".json");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,52 +41,80 @@ namespace ConfigGenerator {
|
||||
ProtectedDataForCurrentUser
|
||||
}
|
||||
|
||||
internal enum EFarmingOrder : byte {
|
||||
Unordered,
|
||||
AppIDsAscending,
|
||||
AppIDsDescending,
|
||||
CardDropsAscending,
|
||||
CardDropsDescending,
|
||||
HoursAscending,
|
||||
HoursDescending,
|
||||
NamesAscending,
|
||||
NamesDescending
|
||||
}
|
||||
|
||||
[Category("\t\tCore")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool StartOnLaunch { get; set; } = true;
|
||||
|
||||
[Category("\t\tCore")]
|
||||
[JsonProperty]
|
||||
public string SteamLogin { get; set; } = null;
|
||||
|
||||
[Category("\t\tCore")]
|
||||
[JsonProperty]
|
||||
[PasswordPropertyText(true)]
|
||||
public string SteamPassword { get; set; } = null;
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ECryptoMethod PasswordFormat { get; set; } = ECryptoMethod.PlainText;
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty]
|
||||
public string SteamParentalPIN { get; set; } = "0";
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty]
|
||||
public string SteamApiKey { get; set; } = null;
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ulong SteamMasterID { get; set; } = 0;
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ulong SteamMasterClanID { get; set; } = 0;
|
||||
|
||||
[Category("\tPerformance")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool CardDropsRestricted { get; set; } = false;
|
||||
public bool CardDropsRestricted { get; set; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool DismissInventoryNotifications { get; set; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public EFarmingOrder FarmingOrder { get; set; } = EFarmingOrder.Unordered;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool FarmOffline { get; set; } = false;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool HandleOfflineMessages { get; set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool AcceptGifts { get; set; } = false;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool IsBotAccount { get; set; } = false;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SteamTradeMatcher { get; set; } = false;
|
||||
|
||||
@@ -102,12 +130,14 @@ namespace ConfigGenerator {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SendOnFarmingFinished { get; set; } = false;
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty]
|
||||
public string SteamTradeToken { get; set; } = null;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte SendTradePeriod { get; set; } = 0;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte AcceptConfirmationsPeriod { get; set; } = 0;
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RunTime.cs" />
|
||||
<Compile Include="Tutorial.cs" />
|
||||
<EmbeddedResource Include="ConfigPage.resx">
|
||||
<DependentUpon>ConfigPage.cs</DependentUpon>
|
||||
@@ -106,9 +107,11 @@
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="cirno.ico" />
|
||||
<None Include="FodyWeavers.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="cirno.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
@@ -50,57 +51,74 @@ namespace ConfigGenerator {
|
||||
// This is hardcoded blacklist which should not be possible to change
|
||||
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
|
||||
|
||||
[Category("\tDebugging")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Debug { get; set; } = false;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Headless { get; set; } = false;
|
||||
|
||||
[Category("\tUpdates")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool AutoUpdates { get; set; } = true;
|
||||
|
||||
[Category("\tUpdates")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool AutoRestart { get; set; } = true;
|
||||
|
||||
[Category("\tUpdates")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ProtocolType SteamProtocol { get; set; } = DefaultSteamProtocol;
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ulong SteamOwnerID { get; set; } = 0;
|
||||
|
||||
[Category("\tPerformance")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte MaxFarmingTime { get; set; } = DefaultMaxFarmingTime;
|
||||
|
||||
[Category("\tPerformance")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte IdleFarmingPeriod { get; set; } = 3;
|
||||
|
||||
[Category("\tPerformance")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
|
||||
|
||||
[Category("\tPerformance")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte LoginLimiterDelay { get; set; } = 10;
|
||||
|
||||
[Category("\tPerformance")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte InventoryLimiterDelay { get; set; } = 3;
|
||||
|
||||
[Category("\tPerformance")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte GiftsLimiterDelay { get; set; } = 1;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte MaxTradeHoldDuration { get; set; } = 15;
|
||||
|
||||
[Category("\tDebugging")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool ForceHttp { get; set; } = false;
|
||||
|
||||
[Category("\tDebugging")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte HttpTimeout { get; set; } = DefaultHttpTimeout;
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty]
|
||||
public string WCFHostname { get; set; } = "localhost";
|
||||
|
||||
[Category("\tAccess")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ushort WCFPort { get; set; } = DefaultWCFPort;
|
||||
|
||||
|
||||
4
ConfigGenerator/MainForm.Designer.cs
generated
4
ConfigGenerator/MainForm.Designer.cs
generated
@@ -36,7 +36,7 @@
|
||||
this.MainTab.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.MainTab.HotTrack = true;
|
||||
this.MainTab.Location = new System.Drawing.Point(14, 14);
|
||||
this.MainTab.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.MainTab.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.MainTab.Multiline = true;
|
||||
this.MainTab.Name = "MainTab";
|
||||
this.MainTab.SelectedIndex = 0;
|
||||
@@ -58,7 +58,7 @@
|
||||
this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.HelpButton = true;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "MainForm";
|
||||
|
||||
@@ -52,14 +52,14 @@ namespace ConfigGenerator {
|
||||
return;
|
||||
}
|
||||
|
||||
ASFTab = new ConfigPage(GlobalConfig.Load(Path.Combine(Program.ConfigDirectory, Program.GlobalConfigFile)));
|
||||
ASFTab = new ConfigPage(GlobalConfig.Load(Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName)));
|
||||
|
||||
MainTab.TabPages.Add(ASFTab);
|
||||
|
||||
foreach (string configFile in Directory.EnumerateFiles(Program.ConfigDirectory, "*.json")) {
|
||||
foreach (string configFile in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json")) {
|
||||
string botName = Path.GetFileNameWithoutExtension(configFile);
|
||||
switch (botName) {
|
||||
case Program.ASF:
|
||||
case SharedInfo.ASF:
|
||||
case "example":
|
||||
case "minimal":
|
||||
continue;
|
||||
@@ -161,7 +161,7 @@ namespace ConfigGenerator {
|
||||
}
|
||||
|
||||
switch (input) {
|
||||
case Program.ASF:
|
||||
case SharedInfo.ASF:
|
||||
case "example":
|
||||
case "minimal":
|
||||
Logging.LogGenericErrorWithoutStacktrace("This name is reserved!");
|
||||
@@ -173,7 +173,7 @@ namespace ConfigGenerator {
|
||||
return;
|
||||
}
|
||||
|
||||
input = Path.Combine(Program.ConfigDirectory, input + ".json");
|
||||
input = Path.Combine(SharedInfo.ConfigDirectory, input + ".json");
|
||||
|
||||
ConfigPage newConfigPage = new ConfigPage(BotConfig.Load(input));
|
||||
MainTab.TabPages.Insert(MainTab.TabPages.Count - ReservedTabs, newConfigPage);
|
||||
|
||||
@@ -32,15 +32,7 @@ using ArchiSteamFarm;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
internal static class Program {
|
||||
internal const string ASF = "ASF";
|
||||
internal const string ConfigDirectory = "config";
|
||||
internal const string GlobalConfigFile = ASF + ".json";
|
||||
|
||||
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;
|
||||
private const string ASFExecutableFile = SharedInfo.ASF + ".exe";
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
@@ -57,29 +49,32 @@ 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(SharedInfo.ASFDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Directory.SetCurrentDirectory(SharedInfo.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(SharedInfo.ConfigDirectory)) {
|
||||
Directory.SetCurrentDirectory(homeDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Directory.Exists(ConfigDirectory)) {
|
||||
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
|
||||
Logging.LogGenericErrorWithoutStacktrace("Config directory could not be found!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
@@ -89,15 +84,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 +104,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 +113,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using ArchiSteamFarm;
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ConfigGenerator")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("ConfigGenerator")]
|
||||
@@ -32,5 +32,5 @@ using ArchiSteamFarm;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion(SharedInfo.Version)]
|
||||
[assembly: AssemblyFileVersion(SharedInfo.Version)]
|
||||
[assembly: AssemblyVersion(SharedInfo.VersionNumber)]
|
||||
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]
|
||||
|
||||
33
ConfigGenerator/RunTime.cs
Normal file
33
ConfigGenerator/RunTime.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
internal static class Runtime {
|
||||
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
|
||||
|
||||
internal static bool IsRunningOnMono => MonoRuntime != null;
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,14 @@ namespace ConfigGenerator {
|
||||
Logging.LogGenericInfoWithoutStacktrace("You can now notice the main ASF Config Generator screen, it's really easy to use!");
|
||||
Logging.LogGenericInfoWithoutStacktrace("At the top of the window you can notice currently loaded configs, and 3 extra buttons for removing, renaming and adding new ones.");
|
||||
Logging.LogGenericInfoWithoutStacktrace("In the middle of the window you will be able to configure all config properties that are available for you.");
|
||||
Logging.LogGenericInfoWithoutStacktrace("In the top right corner you can find help button [?] which will redirect you to ASF wiki where you can find more information.");
|
||||
Logging.LogGenericInfoWithoutStacktrace("Please click the help button to continue.");
|
||||
if (!Runtime.IsRunningOnMono) {
|
||||
Logging.LogGenericInfoWithoutStacktrace("In the top right corner you can find help button [?] which will redirect you to ASF wiki where you can find more information.");
|
||||
Logging.LogGenericInfoWithoutStacktrace("Please click the help button to continue.");
|
||||
} else {
|
||||
Logging.LogGenericInfoWithoutStacktrace("Please visit ASF wiki if you're in doubt - you can find more information there.");
|
||||
Logging.LogGenericInfoWithoutStacktrace("Alright, let's start configuring our ASF. Click on the plus [+] button to add your first steam account to ASF!");
|
||||
NextPhase = EPhase.HelpFinished;
|
||||
}
|
||||
break;
|
||||
case EPhase.Help:
|
||||
Logging.LogGenericInfoWithoutStacktrace("Well done! On ASF wiki you can find detailed help about every config property you're going to configure in a moment.");
|
||||
|
||||
6
GUI/App.config
Normal file
6
GUI/App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
</configuration>
|
||||
63
GUI/BotStatusForm.Designer.cs
generated
Normal file
63
GUI/BotStatusForm.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
namespace GUI {
|
||||
sealed partial class BotStatusForm {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BotStatusForm));
|
||||
this.AvatarPictureBox = new System.Windows.Forms.PictureBox();
|
||||
((System.ComponentModel.ISupportInitialize)(this.AvatarPictureBox)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// AvatarPictureBox
|
||||
//
|
||||
this.AvatarPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
|
||||
this.AvatarPictureBox.ErrorImage = ((System.Drawing.Image)(resources.GetObject("AvatarPictureBox.ErrorImage")));
|
||||
this.AvatarPictureBox.Image = ((System.Drawing.Image)(resources.GetObject("AvatarPictureBox.Image")));
|
||||
this.AvatarPictureBox.Location = new System.Drawing.Point(12, 12);
|
||||
this.AvatarPictureBox.Name = "AvatarPictureBox";
|
||||
this.AvatarPictureBox.Size = new System.Drawing.Size(184, 184);
|
||||
this.AvatarPictureBox.TabIndex = 0;
|
||||
this.AvatarPictureBox.TabStop = false;
|
||||
this.AvatarPictureBox.LoadCompleted += new System.ComponentModel.AsyncCompletedEventHandler(this.AvatarPictureBox_LoadCompleted);
|
||||
//
|
||||
// BotStatusForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.AutoScroll = true;
|
||||
this.ClientSize = new System.Drawing.Size(651, 513);
|
||||
this.Controls.Add(this.AvatarPictureBox);
|
||||
this.DoubleBuffered = true;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.Name = "BotStatusForm";
|
||||
this.Text = "BotStatusForm";
|
||||
((System.ComponentModel.ISupportInitialize)(this.AvatarPictureBox)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal System.Windows.Forms.PictureBox AvatarPictureBox;
|
||||
}
|
||||
}
|
||||
46
GUI/BotStatusForm.cs
Normal file
46
GUI/BotStatusForm.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using ArchiSteamFarm;
|
||||
using SteamKit2;
|
||||
|
||||
namespace GUI {
|
||||
internal sealed partial class BotStatusForm : Form {
|
||||
internal static readonly ConcurrentDictionary<string, BotStatusForm> BotForms = new ConcurrentDictionary<string, BotStatusForm>();
|
||||
|
||||
private readonly Bot Bot;
|
||||
|
||||
internal BotStatusForm(Bot bot) {
|
||||
if (bot == null) {
|
||||
throw new ArgumentNullException(nameof(bot));
|
||||
}
|
||||
|
||||
Bot = bot;
|
||||
|
||||
BotForms[bot.BotName] = this;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Dock = DockStyle.Fill;
|
||||
}
|
||||
|
||||
internal void OnStateUpdated(SteamFriends.PersonaStateCallback callback) {
|
||||
if (callback == null) {
|
||||
Logging.LogNullError(nameof(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.AvatarHash != null) {
|
||||
string avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
|
||||
string avatarURL = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + avatarHash.Substring(0, 2) + "/" + avatarHash + "_full.jpg";
|
||||
AvatarPictureBox.ImageLocation = avatarURL;
|
||||
AvatarPictureBox.LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void AvatarPictureBox_LoadCompleted(object sender, AsyncCompletedEventArgs e) {
|
||||
MainForm.UpdateBotAvatar(Bot.BotName, AvatarPictureBox.Image);
|
||||
}
|
||||
}
|
||||
}
|
||||
247
GUI/BotStatusForm.resx
Normal file
247
GUI/BotStatusForm.resx
Normal file
@@ -0,0 +1,247 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="AvatarPictureBox.ErrorImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
/9j/4AAQSkZJRgABAQEAAAAAAAD/4QCqRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAZKG
|
||||
AAcAAAB0AAAALAAAAABDAFIARQBBAFQATwBSADoAIABnAGQALQBqAHAAZQBnACAAdgAxAC4AMAAgACgA
|
||||
dQBzAGkAbgBnACAASQBKAEcAIABKAFAARQBHACAAdgA2ADIAKQAsACAAcQB1AGEAbABpAHQAeQAgAD0A
|
||||
IAA4ADAACgAAAAAA/9sAQwAGBAUGBQQGBgUGBwcGCAoQCgoJCQoUDg8MEBcUGBgXFBYWGh0lHxobIxwW
|
||||
FiAsICMmJykqKRkfLTAtKDAlKCko/9sAQwEHBwcKCAoTCgoTKBoWGigoKCgoKCgoKCgoKCgoKCgoKCgo
|
||||
KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo/8AAEQgAuAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEB
|
||||
AQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQci
|
||||
cRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm
|
||||
Z2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV
|
||||
1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//E
|
||||
ALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDTh
|
||||
JfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
|
||||
lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5
|
||||
+v/aAAwDAQACEQMRAD8A8V1G9u4dQuYobmeONJWVVWQgAAn3qt/aN9/z+3P/AH9b/GjVv+Qre/8AXZ//
|
||||
AEI1VoAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAt
|
||||
f2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/t
|
||||
z/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj
|
||||
+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9u
|
||||
f+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooA09OvbubULaKa5nkjeVVZWkJBBI96KraT/
|
||||
AMhWy/67J/6EKKADVv8AkK3v/XZ//QjVWrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFFABRRRQAU
|
||||
UUUAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf8AXZP/AEIUUaT/AMhWy/67J/6EKKADVv8AkK3v/XZ//QjV
|
||||
WrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFbXhTwvq/irURZaJaPO4wZHPCRD1Zuw/U9s0AYtFfS
|
||||
Hhn4AaZBEkniPUZru4xkxWuI4we43EFm+vFdavwc8CqoB0QsR1Ju58n/AMfoA+QqK+oNc+Avhu8jJ0q5
|
||||
vdOmx8vzCZPxVuT+BFeJ+PfhvrvgxvNvYhc6cThbyDJTPYMMZU/Xj0JoA4uiiigAooooAKKKKACiiigC
|
||||
1pP/ACFbL/rsn/oQoo0n/kK2X/XZP/QhRQAat/yFb3/rs/8A6Eaq1a1b/kK3v/XZ/wD0I1VoAKKKKACi
|
||||
iigAooooA2/Bnhy78V+I7TSbH5XmbLyEZESDlmP0/U8V9leEvDeneFdFh03SYQkSAF3IG+V8YLMe5OP6
|
||||
DivK/wBmHQUt9B1HXJE/f3cv2eMntGgBOPqx/wDHa9toAKK8U+N/xSu/D96dA8OOkd+EDXNyQGMIIyFU
|
||||
f3sHOSOAeOengNx4k1y4nM8+sai8xOd7XLk5+uaAPumo7mCK6t5Le5ijmglUq8cigqwIwQR3FfMvww+M
|
||||
ep6Vfw2Pii5kv9KkIUzyktLAem4t1ZfUHJ9PQ/TqOrorowZSAwKnII65BoA+S/jX8P8A/hDtZS605SdF
|
||||
vWPlA5JhfvGT6dwT2+ma82r7S+Kugp4i8B6tZFd0yRGeA9xIg3Lj0zgj6Gvi2gAooooAKKKKACiiigC1
|
||||
pP8AyFbL/rsn/oQoo0n/AJCtl/12T/0IUUAGrf8AIVvf+uz/APoRqrVrVv8AkK3v/XZ//QjVWgAooooA
|
||||
KKKKACiiigD64+A9xbRfCrRVeaFHJnLAsAc+e/Xn0xXffbbX/n5h/wC+x/jXwTRQBr+ML5tS8V6xeu28
|
||||
z3crg5yMFjgA+mOKyKKKACvtP4UzTz/Djw89znzPsaLk9SoGFP5AV8ofD/wpd+MfEtvplqCsRO+4mA4h
|
||||
jBG5vr2A7mvtSxtYbGyt7S1QJBBGsUaDoqKAAP0oAlZQylWAZWGCDyCK+Aq+5PGurpoXhLV9Sdgpt7Z2
|
||||
UnjL4wg/Fior4boAKKKKACiiigAooooAtaT/AMhWy/67J/6EKKNJ/wCQrZf9dk/9CFFABq3/ACFb3/rs
|
||||
/wD6Eaq1a1b/AJCt7/12f/0I1VoAKKKKACiiigAooooAKKKKACpbW3mu7qG3tY3luJXEccaDJdieAB3O
|
||||
TUVfQ/7O3gHyIl8V6tD+9kUiwjccqveXHqeg9snuKAPQvhR4Jh8FeG0gcI+p3GJLuVecvjhQf7q5x78n
|
||||
vXa0V5r8bfHw8I6H9j0+T/idXyFYsdYU6GQ+/Ye/PY0Aec/tFeOk1K8HhjTJN1taybruRTw8ozhB7Lnn
|
||||
3/3a8RpWYsxZiWZjkk8kn1NJQAUUUUAFFFFABRRRQBa0n/kK2X/XZP8A0IUUaT/yFbL/AK7J/wChCigA
|
||||
1b/kK3v/AF2f/wBCNVatat/yFb3/AK7P/wChGqtABRRRQAUUUUAFFFFABRRRQB0Pw+0NfEnjTSNJkz5N
|
||||
xMPNA6mNQWcA+u1TX23FGkMSRxIEjRQqqowFAGAAOwxXyV+z2P8Ai6Wm/wDXKb/0W1fW9AGN4w8RWfhX
|
||||
w9datqBzFCvyoDgyueFUe5P5DntXxd4n1298Sa5darqUm+4uH3EDOEHQKo7ADivdf2qbt00vw9ZhiElm
|
||||
mmK9iUVQCf8Avs187UAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf9dk/wDQhRRpP/IVsv8Arsn/AKEKKADV
|
||||
v+Qre/8AXZ//AEI1Vq1q3/IVvf8Ars//AKEaq0AFFFFABRRRQAUUUUAFFFFAHdfBTVrHRfiFY3uq3Mdt
|
||||
aJHMGlk4AJjIH6mvpX/hZfg3/oYbH/vo/wCFfGFFAHs/7RvibRvEX/CPf2JqMN75H2jzfLJOzd5W3PH+
|
||||
ya8YoooAKKKKACiiigAooooAKKKKALWk/wDIVsv+uyf+hCijSf8AkK2X/XZP/QhRQAat/wAhW9/67P8A
|
||||
+hGqtWtW/wCQre/9dn/9CNVaACiiigAooooAKKKKACiiigD0P4BwQ3PxN0+K5ijljMcxKSKGB/dkjivq
|
||||
v+xdK/6Blj/4Dr/hXyf8DL+z034kWFzqN3b2lsscwaWeQRoCYyACxIA5r6g/4TXwt/0Muif+DCL/AOKo
|
||||
A8Y/aisrSz/4Rn7JbQwbvtW7y0C7v9VjOBz1rwivb/2l9a0rWP8AhHP7I1Oxv/K+0+Z9luFl2Z8rG7aT
|
||||
jOD19K8QoAKKKKACiiigAooooAKKKKALWk/8hWy/67J/6EKKNJ/5Ctl/12T/ANCFFABq3/IVvf8Ars//
|
||||
AKEaq1a1b/kK3v8A12f/ANCNVaACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
|
||||
C1pP/IVsv+uyf+hCijSf+QrZf9dk/wDQhRQAat/yFb3/AK7P/wChGqtWtW/5Ct7/ANdn/wDQjVWgAooo
|
||||
oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAtaT/yFbL/rsn/oQoo0n/kK2X/XZP8A
|
||||
0IUUAWdRsrubULmWG2nkjeVmVljJBBJ9qrf2dff8+Vz/AN+m/wAKKKAD+zr7/nyuf+/Tf4Uf2dff8+Vz
|
||||
/wB+m/woooAP7Ovv+fK5/wC/Tf4Uf2dff8+Vz/36b/CiigA/s6+/58rn/v03+FH9nX3/AD5XP/fpv8KK
|
||||
KAD+zr7/AJ8rn/v03+FH9nX3/Plc/wDfpv8ACiigA/s6+/58rn/v03+FH9nX3/Plc/8Afpv8KKKAD+zr
|
||||
7/nyuf8Av03+FH9nX3/Plc/9+m/woooAP7Ovv+fK5/79N/hR/Z19/wA+Vz/36b/CiigA/s6+/wCfK5/7
|
||||
9N/hR/Z19/z5XP8A36b/AAoooAP7Ovv+fK5/79N/hR/Z19/z5XP/AH6b/CiigA/s6+/58rn/AL9N/hR/
|
||||
Z19/z5XP/fpv8KKKALOnWV3DqFtLNbTxxpKrMzRkAAEe1FFFAH//2Q==
|
||||
</value>
|
||||
</data>
|
||||
<data name="AvatarPictureBox.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
/9j/4AAQSkZJRgABAQEAAAAAAAD/4QCqRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAZKG
|
||||
AAcAAAB0AAAALAAAAABDAFIARQBBAFQATwBSADoAIABnAGQALQBqAHAAZQBnACAAdgAxAC4AMAAgACgA
|
||||
dQBzAGkAbgBnACAASQBKAEcAIABKAFAARQBHACAAdgA2ADIAKQAsACAAcQB1AGEAbABpAHQAeQAgAD0A
|
||||
IAA4ADAACgAAAAAA/9sAQwAGBAUGBQQGBgUGBwcGCAoQCgoJCQoUDg8MEBcUGBgXFBYWGh0lHxobIxwW
|
||||
FiAsICMmJykqKRkfLTAtKDAlKCko/9sAQwEHBwcKCAoTCgoTKBoWGigoKCgoKCgoKCgoKCgoKCgoKCgo
|
||||
KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo/8AAEQgAuAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEB
|
||||
AQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQci
|
||||
cRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm
|
||||
Z2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV
|
||||
1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//E
|
||||
ALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDTh
|
||||
JfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
|
||||
lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5
|
||||
+v/aAAwDAQACEQMRAD8A8V1G9u4dQuYobmeONJWVVWQgAAn3qt/aN9/z+3P/AH9b/GjVv+Qre/8AXZ//
|
||||
AEI1VoAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAt
|
||||
f2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/t
|
||||
z/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj
|
||||
+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9u
|
||||
f+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooA09OvbubULaKa5nkjeVVZWkJBBI96KraT/
|
||||
AMhWy/67J/6EKKADVv8AkK3v/XZ//QjVWrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFFABRRRQAU
|
||||
UUUAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf8AXZP/AEIUUaT/AMhWy/67J/6EKKADVv8AkK3v/XZ//QjV
|
||||
WrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFbXhTwvq/irURZaJaPO4wZHPCRD1Zuw/U9s0AYtFfS
|
||||
Hhn4AaZBEkniPUZru4xkxWuI4we43EFm+vFdavwc8CqoB0QsR1Ju58n/AMfoA+QqK+oNc+Avhu8jJ0q5
|
||||
vdOmx8vzCZPxVuT+BFeJ+PfhvrvgxvNvYhc6cThbyDJTPYMMZU/Xj0JoA4uiiigAooooAKKKKACiiigC
|
||||
1pP/ACFbL/rsn/oQoo0n/kK2X/XZP/QhRQAat/yFb3/rs/8A6Eaq1a1b/kK3v/XZ/wD0I1VoAKKKKACi
|
||||
iigAooooA2/Bnhy78V+I7TSbH5XmbLyEZESDlmP0/U8V9leEvDeneFdFh03SYQkSAF3IG+V8YLMe5OP6
|
||||
DivK/wBmHQUt9B1HXJE/f3cv2eMntGgBOPqx/wDHa9toAKK8U+N/xSu/D96dA8OOkd+EDXNyQGMIIyFU
|
||||
f3sHOSOAeOengNx4k1y4nM8+sai8xOd7XLk5+uaAPumo7mCK6t5Le5ijmglUq8cigqwIwQR3FfMvww+M
|
||||
ep6Vfw2Pii5kv9KkIUzyktLAem4t1ZfUHJ9PQ/TqOrorowZSAwKnII65BoA+S/jX8P8A/hDtZS605SdF
|
||||
vWPlA5JhfvGT6dwT2+ma82r7S+Kugp4i8B6tZFd0yRGeA9xIg3Lj0zgj6Gvi2gAooooAKKKKACiiigC1
|
||||
pP8AyFbL/rsn/oQoo0n/AJCtl/12T/0IUUAGrf8AIVvf+uz/APoRqrVrVv8AkK3v/XZ//QjVWgAooooA
|
||||
KKKKACiiigD64+A9xbRfCrRVeaFHJnLAsAc+e/Xn0xXffbbX/n5h/wC+x/jXwTRQBr+ML5tS8V6xeu28
|
||||
z3crg5yMFjgA+mOKyKKKACvtP4UzTz/Djw89znzPsaLk9SoGFP5AV8ofD/wpd+MfEtvplqCsRO+4mA4h
|
||||
jBG5vr2A7mvtSxtYbGyt7S1QJBBGsUaDoqKAAP0oAlZQylWAZWGCDyCK+Aq+5PGurpoXhLV9Sdgpt7Z2
|
||||
UnjL4wg/Fior4boAKKKKACiiigAooooAtaT/AMhWy/67J/6EKKNJ/wCQrZf9dk/9CFFABq3/ACFb3/rs
|
||||
/wD6Eaq1a1b/AJCt7/12f/0I1VoAKKKKACiiigAooooAKKKKACpbW3mu7qG3tY3luJXEccaDJdieAB3O
|
||||
TUVfQ/7O3gHyIl8V6tD+9kUiwjccqveXHqeg9snuKAPQvhR4Jh8FeG0gcI+p3GJLuVecvjhQf7q5x78n
|
||||
vXa0V5r8bfHw8I6H9j0+T/idXyFYsdYU6GQ+/Ye/PY0Aec/tFeOk1K8HhjTJN1taybruRTw8ozhB7Lnn
|
||||
3/3a8RpWYsxZiWZjkk8kn1NJQAUUUUAFFFFABRRRQBa0n/kK2X/XZP8A0IUUaT/yFbL/AK7J/wChCigA
|
||||
1b/kK3v/AF2f/wBCNVatat/yFb3/AK7P/wChGqtABRRRQAUUUUAFFFFABRRRQB0Pw+0NfEnjTSNJkz5N
|
||||
xMPNA6mNQWcA+u1TX23FGkMSRxIEjRQqqowFAGAAOwxXyV+z2P8Ai6Wm/wDXKb/0W1fW9AGN4w8RWfhX
|
||||
w9datqBzFCvyoDgyueFUe5P5DntXxd4n1298Sa5darqUm+4uH3EDOEHQKo7ADivdf2qbt00vw9ZhiElm
|
||||
mmK9iUVQCf8Avs187UAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf9dk/wDQhRRpP/IVsv8Arsn/AKEKKADV
|
||||
v+Qre/8AXZ//AEI1Vq1q3/IVvf8Ars//AKEaq0AFFFFABRRRQAUUUUAFFFFAHdfBTVrHRfiFY3uq3Mdt
|
||||
aJHMGlk4AJjIH6mvpX/hZfg3/oYbH/vo/wCFfGFFAHs/7RvibRvEX/CPf2JqMN75H2jzfLJOzd5W3PH+
|
||||
ya8YoooAKKKKACiiigAooooAKKKKALWk/wDIVsv+uyf+hCijSf8AkK2X/XZP/QhRQAat/wAhW9/67P8A
|
||||
+hGqtWtW/wCQre/9dn/9CNVaACiiigAooooAKKKKACiiigD0P4BwQ3PxN0+K5ijljMcxKSKGB/dkjivq
|
||||
v+xdK/6Blj/4Dr/hXyf8DL+z034kWFzqN3b2lsscwaWeQRoCYyACxIA5r6g/4TXwt/0Muif+DCL/AOKo
|
||||
A8Y/aisrSz/4Rn7JbQwbvtW7y0C7v9VjOBz1rwivb/2l9a0rWP8AhHP7I1Oxv/K+0+Z9luFl2Z8rG7aT
|
||||
jOD19K8QoAKKKKACiiigAooooAKKKKALWk/8hWy/67J/6EKKNJ/5Ctl/12T/ANCFFABq3/IVvf8Ars//
|
||||
AKEaq1a1b/kK3v8A12f/ANCNVaACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
|
||||
C1pP/IVsv+uyf+hCijSf+QrZf9dk/wDQhRQAat/yFb3/AK7P/wChGqtWtW/5Ct7/ANdn/wDQjVWgAooo
|
||||
oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAtaT/yFbL/rsn/oQoo0n/kK2X/XZP8A
|
||||
0IUUAWdRsrubULmWG2nkjeVmVljJBBJ9qrf2dff8+Vz/AN+m/wAKKKAD+zr7/nyuf+/Tf4Uf2dff8+Vz
|
||||
/wB+m/woooAP7Ovv+fK5/wC/Tf4Uf2dff8+Vz/36b/CiigA/s6+/58rn/v03+FH9nX3/AD5XP/fpv8KK
|
||||
KAD+zr7/AJ8rn/v03+FH9nX3/Plc/wDfpv8ACiigA/s6+/58rn/v03+FH9nX3/Plc/8Afpv8KKKAD+zr
|
||||
7/nyuf8Av03+FH9nX3/Plc/9+m/woooAP7Ovv+fK5/79N/hR/Z19/wA+Vz/36b/CiigA/s6+/wCfK5/7
|
||||
9N/hR/Z19/z5XP8A36b/AAoooAP7Ovv+fK5/79N/hR/Z19/z5XP/AH6b/CiigA/s6+/58rn/AL9N/hR/
|
||||
Z19/z5XP/fpv8KKKALOnWV3DqFtLNbTxxpKrMzRkAAEe1FFFAH//2Q==
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
23
GUI/Events.cs
Normal file
23
GUI/Events.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using GUI;
|
||||
using SteamKit2;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Events {
|
||||
internal static void OnBotShutdown() { }
|
||||
|
||||
internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) {
|
||||
if ((bot == null) || (callback == null)) {
|
||||
Logging.LogNullError(nameof(bot) + " || " + nameof(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
BotStatusForm form;
|
||||
if (!BotStatusForm.BotForms.TryGetValue(bot.BotName, out form)) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.OnStateUpdated(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
GUI/FodyWeavers.xml
Normal file
4
GUI/FodyWeavers.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers>
|
||||
<Costura IncludeDebugSymbols='false' />
|
||||
</Weavers>
|
||||
224
GUI/GUI.csproj
Normal file
224
GUI/GUI.csproj
Normal file
@@ -0,0 +1,224 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{13949B41-787C-4558-90AE-A9F9E7F86B1F}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GUI</RootNamespace>
|
||||
<AssemblyName>GUI</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>cirno.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HtmlAgilityPack, Version=1.4.9.5, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.Windows.Forms.4.2.3\lib\net35\NLog.Windows.Forms.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="protobuf-net, Version=2.0.0.668, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<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" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\ArchiSteamFarm\ArchiHandler.cs">
|
||||
<Link>ArchiHandler.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\ArchiWebHandler.cs">
|
||||
<Link>ArchiWebHandler.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\ASF.cs">
|
||||
<Link>ASF.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\Bot.cs">
|
||||
<Link>Bot.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\BotConfig.cs">
|
||||
<Link>BotConfig.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\BotDatabase.cs">
|
||||
<Link>BotDatabase.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\CardsFarmer.cs">
|
||||
<Link>CardsFarmer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\ConcurrentEnumerator.cs">
|
||||
<Link>ConcurrentEnumerator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\ConcurrentHashSet.cs">
|
||||
<Link>ConcurrentHashSet.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\CryptoHelper.cs">
|
||||
<Link>CryptoHelper.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\Debugging.cs">
|
||||
<Link>Debugging.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\GlobalConfig.cs">
|
||||
<Link>GlobalConfig.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\GlobalDatabase.cs">
|
||||
<Link>GlobalDatabase.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\InMemoryServerListProvider.cs">
|
||||
<Link>InMemoryServerListProvider.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\IPAddressConverter.cs">
|
||||
<Link>IPAddressConverter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\IPEndPointConverter.cs">
|
||||
<Link>IPEndPointConverter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\JSON\GitHub.cs">
|
||||
<Link>JSON\GitHub.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\JSON\Steam.cs">
|
||||
<Link>JSON\Steam.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\MobileAuthenticator.cs">
|
||||
<Link>MobileAuthenticator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\Runtime.cs">
|
||||
<Link>Runtime.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\SharedInfo.cs">
|
||||
<Link>SharedInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\Trading.cs">
|
||||
<Link>Trading.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\Utilities.cs">
|
||||
<Link>Utilities.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\ArchiSteamFarm\WebBrowser.cs">
|
||||
<Link>WebBrowser.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="BotStatusForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="BotStatusForm.Designer.cs">
|
||||
<DependentUpon>BotStatusForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Events.cs" />
|
||||
<Compile Include="MainForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainForm.Designer.cs">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Logging.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="BotStatusForm.resx">
|
||||
<DependentUpon>BotStatusForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
</Compile>
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="cirno.ico" />
|
||||
<None Include="FodyWeavers.xml" />
|
||||
<None Include="Resources\SteamUnknownAvatar.jpg" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets" Condition="'$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' AND Exists('..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets')" />
|
||||
<Import Project="..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets" Condition="'$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' AND Exists('..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
186
GUI/Logging.cs
Normal file
186
GUI/Logging.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using NLog.Windows.Forms;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Logging {
|
||||
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} | ${level:uppercase=true} | ${message}${onexception:inner= | ${exception:format=toString,Data}}";
|
||||
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private static bool IsUsingCustomConfiguration;
|
||||
|
||||
internal static void InitCoreLoggers() {
|
||||
if (LogManager.Configuration == null) {
|
||||
LogManager.Configuration = new LoggingConfiguration();
|
||||
} else {
|
||||
// User provided custom NLog config, but we still need to define our own logger
|
||||
IsUsingCustomConfiguration = true;
|
||||
if (LogManager.Configuration.AllTargets.Any(target => target is MessageBoxTarget)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MessageBoxTarget messageBoxTarget = new MessageBoxTarget {
|
||||
Name = "MessageBox",
|
||||
Layout = GeneralLayout
|
||||
};
|
||||
|
||||
LogManager.Configuration.AddTarget(messageBoxTarget);
|
||||
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Fatal, messageBoxTarget));
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
internal static void InitEnhancedLoggers() {
|
||||
if (IsUsingCustomConfiguration) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileTarget fileTarget = new FileTarget("File") {
|
||||
DeleteOldFileOnStartup = true,
|
||||
FileName = SharedInfo.LogFile,
|
||||
Layout = GeneralLayout
|
||||
};
|
||||
|
||||
LogManager.Configuration.AddTarget(fileTarget);
|
||||
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
|
||||
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
internal static void InitFormLogger() {
|
||||
RichTextBoxTarget formControlTarget = new RichTextBoxTarget {
|
||||
AutoScroll = true,
|
||||
ControlName = "LogTextBox",
|
||||
FormName = "MainForm",
|
||||
Layout = GeneralLayout,
|
||||
MaxLines = byte.MaxValue,
|
||||
Name = "RichTextBox"
|
||||
};
|
||||
|
||||
formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Error", "Red", "Black"));
|
||||
formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Warn", "Yellow", "Black"));
|
||||
|
||||
|
||||
LogManager.Configuration.AddTarget(formControlTarget);
|
||||
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, formControlTarget));
|
||||
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
internal static void LogGenericError(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Error($"{botName}|{previousMethodName}() {message}");
|
||||
}
|
||||
|
||||
internal static void LogGenericException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (exception == null) {
|
||||
LogNullError(nameof(exception), botName);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Error(exception, $"{botName}|{previousMethodName}()");
|
||||
}
|
||||
|
||||
internal static void LogFatalException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (exception == null) {
|
||||
LogNullError(nameof(exception), botName);
|
||||
return;
|
||||
}
|
||||
|
||||
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(SharedInfo.LogFile, DateTime.Now + " ASF V" + SharedInfo.Version + " has run into fatal exception before core logging module was even able to initialize!" + Environment.NewLine);
|
||||
|
||||
while (true) {
|
||||
File.AppendAllText(SharedInfo.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 = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Warn($"{botName}|{previousMethodName}() {message}");
|
||||
}
|
||||
|
||||
internal static void LogGenericInfo(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Info($"{botName}|{previousMethodName}() {message}");
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
|
||||
internal static void LogNullError(string nullObjectName, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(nullObjectName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal static void LogGenericDebug(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Debug($"{botName}|{previousMethodName}() {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
128
GUI/MainForm.Designer.cs
generated
Normal file
128
GUI/MainForm.Designer.cs
generated
Normal file
@@ -0,0 +1,128 @@
|
||||
namespace GUI {
|
||||
internal sealed partial class MainForm {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
this.BotListView = new System.Windows.Forms.ListView();
|
||||
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
|
||||
this.MinimizeIcon = new System.Windows.Forms.NotifyIcon(this.components);
|
||||
this.LogTextBox = new System.Windows.Forms.RichTextBox();
|
||||
this.BotStatusPanel = new System.Windows.Forms.Panel();
|
||||
this.AvatarImageList = new System.Windows.Forms.ImageList(this.components);
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// BotListView
|
||||
//
|
||||
this.BotListView.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.BotListView.GridLines = true;
|
||||
this.BotListView.Location = new System.Drawing.Point(0, 24);
|
||||
this.BotListView.MultiSelect = false;
|
||||
this.BotListView.Name = "BotListView";
|
||||
this.BotListView.ShowGroups = false;
|
||||
this.BotListView.Size = new System.Drawing.Size(150, 705);
|
||||
this.BotListView.TabIndex = 0;
|
||||
this.BotListView.UseCompatibleStateImageBehavior = false;
|
||||
this.BotListView.View = System.Windows.Forms.View.SmallIcon;
|
||||
this.BotListView.SelectedIndexChanged += new System.EventHandler(this.BotListView_SelectedIndexChanged);
|
||||
//
|
||||
// menuStrip1
|
||||
//
|
||||
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
|
||||
this.menuStrip1.Name = "menuStrip1";
|
||||
this.menuStrip1.Size = new System.Drawing.Size(1008, 24);
|
||||
this.menuStrip1.TabIndex = 1;
|
||||
this.menuStrip1.Text = "menuStrip1";
|
||||
//
|
||||
// MinimizeIcon
|
||||
//
|
||||
this.MinimizeIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info;
|
||||
this.MinimizeIcon.BalloonTipText = "ASF will keep working in the background...";
|
||||
this.MinimizeIcon.BalloonTipTitle = "ASF";
|
||||
this.MinimizeIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("MinimizeIcon.Icon")));
|
||||
this.MinimizeIcon.Text = "MinimizeIcon";
|
||||
this.MinimizeIcon.Visible = true;
|
||||
this.MinimizeIcon.DoubleClick += new System.EventHandler(this.MinimizeIcon_DoubleClick);
|
||||
//
|
||||
// LogTextBox
|
||||
//
|
||||
this.LogTextBox.BackColor = System.Drawing.Color.Black;
|
||||
this.LogTextBox.CausesValidation = false;
|
||||
this.LogTextBox.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.LogTextBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.LogTextBox.ForeColor = System.Drawing.Color.White;
|
||||
this.LogTextBox.Location = new System.Drawing.Point(150, 529);
|
||||
this.LogTextBox.Name = "LogTextBox";
|
||||
this.LogTextBox.ReadOnly = true;
|
||||
this.LogTextBox.Size = new System.Drawing.Size(858, 200);
|
||||
this.LogTextBox.TabIndex = 2;
|
||||
this.LogTextBox.Text = "";
|
||||
//
|
||||
// BotStatusPanel
|
||||
//
|
||||
this.BotStatusPanel.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.BotStatusPanel.Location = new System.Drawing.Point(150, 24);
|
||||
this.BotStatusPanel.Name = "BotStatusPanel";
|
||||
this.BotStatusPanel.Size = new System.Drawing.Size(858, 496);
|
||||
this.BotStatusPanel.TabIndex = 3;
|
||||
//
|
||||
// AvatarImageList
|
||||
//
|
||||
this.AvatarImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth24Bit;
|
||||
this.AvatarImageList.ImageSize = new System.Drawing.Size(46, 46);
|
||||
this.AvatarImageList.TransparentColor = System.Drawing.Color.Transparent;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1008, 729);
|
||||
this.Controls.Add(this.BotStatusPanel);
|
||||
this.Controls.Add(this.LogTextBox);
|
||||
this.Controls.Add(this.BotListView);
|
||||
this.Controls.Add(this.menuStrip1);
|
||||
this.DoubleBuffered = true;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MainMenuStrip = this.menuStrip1;
|
||||
this.Name = "MainForm";
|
||||
this.Text = "ArchiSteamFarm";
|
||||
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed);
|
||||
this.Load += new System.EventHandler(this.MainForm_Load);
|
||||
this.Resize += new System.EventHandler(this.MainForm_Resize);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.ListView BotListView;
|
||||
private System.Windows.Forms.MenuStrip menuStrip1;
|
||||
private System.Windows.Forms.NotifyIcon MinimizeIcon;
|
||||
private System.Windows.Forms.RichTextBox LogTextBox;
|
||||
private System.Windows.Forms.Panel BotStatusPanel;
|
||||
private System.Windows.Forms.ImageList AvatarImageList;
|
||||
}
|
||||
}
|
||||
|
||||
161
GUI/MainForm.cs
Normal file
161
GUI/MainForm.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using ArchiSteamFarm;
|
||||
|
||||
namespace GUI {
|
||||
internal sealed partial class MainForm : Form {
|
||||
private static readonly ConcurrentDictionary<string, int> BotIndexes = new ConcurrentDictionary<string, int>();
|
||||
|
||||
private static MainForm Form;
|
||||
private string PreviouslySelectedBotName;
|
||||
|
||||
internal MainForm() {
|
||||
Form = this;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
internal static void UpdateBotAvatar(string botName, Image image) {
|
||||
if (string.IsNullOrEmpty(botName) || (image == null)) {
|
||||
Logging.LogNullError(nameof(botName) + " || " + nameof(image));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Form == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index;
|
||||
if (!BotIndexes.TryGetValue(botName, out index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap resizedImage = ResizeImage(image, Form.AvatarImageList.ImageSize.Width, Form.AvatarImageList.ImageSize.Height);
|
||||
if (resizedImage == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Form.Invoke((MethodInvoker) (() => {
|
||||
Form.AvatarImageList.Images[index] = resizedImage;
|
||||
Form.BotListView.Refresh();
|
||||
}));
|
||||
}
|
||||
|
||||
private static Bitmap ResizeImage(Image image, int width, int height) {
|
||||
if ((image == null) || (width <= 0) || (height <= 0)) {
|
||||
Logging.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height));
|
||||
return null;
|
||||
}
|
||||
|
||||
Rectangle destRect = new Rectangle(0, 0, width, height);
|
||||
Bitmap destImage = new Bitmap(width, height);
|
||||
|
||||
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
|
||||
|
||||
using (Graphics graphics = Graphics.FromImage(destImage)) {
|
||||
graphics.CompositingMode = CompositingMode.SourceCopy;
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
|
||||
using (ImageAttributes wrapMode = new ImageAttributes()) {
|
||||
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
|
||||
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
|
||||
}
|
||||
}
|
||||
|
||||
return destImage;
|
||||
}
|
||||
|
||||
private void MainForm_Resize(object sender, EventArgs e) {
|
||||
switch (WindowState) {
|
||||
case FormWindowState.Minimized:
|
||||
MinimizeIcon.Visible = true;
|
||||
MinimizeIcon.ShowBalloonTip(5000);
|
||||
break;
|
||||
case FormWindowState.Normal:
|
||||
MinimizeIcon.Visible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void MainForm_Load(object sender, EventArgs e) {
|
||||
Logging.InitFormLogger();
|
||||
|
||||
BotListView.LargeImageList = BotListView.SmallImageList = AvatarImageList;
|
||||
|
||||
await Task.Run(async () => {
|
||||
Logging.LogGenericInfo("ASF V" + SharedInfo.Version);
|
||||
|
||||
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
|
||||
Logging.LogGenericError("Config directory could not be found!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
await ASF.CheckForUpdate().ConfigureAwait(false);
|
||||
|
||||
// Before attempting to connect, initialize our list of CMs
|
||||
Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider);
|
||||
});
|
||||
|
||||
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
|
||||
switch (botName) {
|
||||
case SharedInfo.ASF:
|
||||
case "example":
|
||||
case "minimal":
|
||||
continue;
|
||||
}
|
||||
|
||||
Bot bot = new Bot(botName);
|
||||
|
||||
BotStatusForm botStatusForm = new BotStatusForm(bot);
|
||||
|
||||
BotIndexes[botName] = AvatarImageList.Images.Count;
|
||||
|
||||
AvatarImageList.Images.Add(botName, botStatusForm.AvatarPictureBox.Image);
|
||||
|
||||
botStatusForm.TopLevel = false;
|
||||
BotStatusPanel.Controls.Add(botStatusForm);
|
||||
|
||||
ListViewItem botListViewItem = new ListViewItem {
|
||||
ImageIndex = BotIndexes[botName],
|
||||
Text = botName
|
||||
};
|
||||
|
||||
BotListView.Items.Add(botListViewItem);
|
||||
}
|
||||
|
||||
if (BotListView.Items.Count > 0) {
|
||||
BotListView.Items[0].Selected = true;
|
||||
BotListView.Select();
|
||||
}
|
||||
}
|
||||
|
||||
private void MinimizeIcon_DoubleClick(object sender, EventArgs e) {
|
||||
Show();
|
||||
WindowState = FormWindowState.Normal;
|
||||
}
|
||||
|
||||
private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence();
|
||||
|
||||
private void BotListView_SelectedIndexChanged(object sender, EventArgs e) {
|
||||
if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) {
|
||||
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false;
|
||||
}
|
||||
|
||||
if (BotListView.SelectedItems.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreviouslySelectedBotName = BotListView.SelectedItems[0].Text;
|
||||
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
12477
GUI/MainForm.resx
Normal file
12477
GUI/MainForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
149
GUI/Program.cs
Normal file
149
GUI/Program.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using GUI;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Program {
|
||||
internal static GlobalConfig GlobalConfig { get; private set; }
|
||||
internal static GlobalDatabase GlobalDatabase { get; private set; }
|
||||
internal static WebBrowser WebBrowser { get; private set; }
|
||||
|
||||
internal static string GetUserInput(SharedInfo.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
|
||||
return null; // TODO
|
||||
}
|
||||
|
||||
internal static void Exit(int exitCode = 0) {
|
||||
InitShutdownSequence();
|
||||
Environment.Exit(exitCode);
|
||||
}
|
||||
|
||||
internal static void Restart() {
|
||||
InitShutdownSequence();
|
||||
|
||||
try {
|
||||
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
internal static void InitShutdownSequence() {
|
||||
foreach (Bot bot in Bot.Bots.Values.Where(bot => bot.KeepRunning)) {
|
||||
bot.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
|
||||
if (args?.ExceptionObject == null) {
|
||||
Logging.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogFatalException((Exception) args.ExceptionObject);
|
||||
}
|
||||
|
||||
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
|
||||
if (args?.Exception == null) {
|
||||
Logging.LogNullError(nameof(args) + " || " + nameof(args.Exception));
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogFatalException(args.Exception);
|
||||
}
|
||||
|
||||
private static void InitServices() {
|
||||
string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName);
|
||||
|
||||
GlobalConfig = GlobalConfig.Load(globalConfigFile);
|
||||
if (GlobalConfig == null) {
|
||||
Logging.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
|
||||
Exit(1);
|
||||
}
|
||||
|
||||
string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName);
|
||||
|
||||
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
|
||||
if (GlobalDatabase == null) {
|
||||
Logging.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
|
||||
Exit(1);
|
||||
}
|
||||
|
||||
ArchiWebHandler.Init();
|
||||
WebBrowser.Init();
|
||||
|
||||
WebBrowser = new WebBrowser(SharedInfo.ASF);
|
||||
}
|
||||
|
||||
private static void Init() {
|
||||
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
|
||||
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
|
||||
|
||||
Logging.InitCoreLoggers();
|
||||
|
||||
if (!Runtime.IsRuntimeSupported) {
|
||||
Logging.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
// 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(SharedInfo.ASFDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Directory.SetCurrentDirectory(SharedInfo.ASFDirectory);
|
||||
break;
|
||||
}
|
||||
|
||||
// If config directory doesn't exist after our adjustment, abort all of that
|
||||
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
|
||||
Directory.SetCurrentDirectory(homeDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InitServices();
|
||||
|
||||
// If debugging is on, we prepare debug directory prior to running
|
||||
if (GlobalConfig.Debug) {
|
||||
if (Directory.Exists(SharedInfo.DebugDirectory)) {
|
||||
Directory.Delete(SharedInfo.DebugDirectory, true);
|
||||
Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync
|
||||
}
|
||||
Directory.CreateDirectory(SharedInfo.DebugDirectory);
|
||||
|
||||
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener(Path.Combine(SharedInfo.DebugDirectory, "debug.txt")));
|
||||
SteamKit2.DebugLog.Enabled = true;
|
||||
}
|
||||
|
||||
Logging.InitEnhancedLoggers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
private static void Main() {
|
||||
Init();
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
}
|
||||
36
GUI/Properties/AssemblyInfo.cs
Normal file
36
GUI/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using ArchiSteamFarm;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("GUI")]
|
||||
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("GUI")]
|
||||
[assembly: AssemblyCopyright(SharedInfo.Copyright)]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("13949b41-787c-4558-90ae-a9f9e7f86b1f")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion(SharedInfo.VersionNumber)]
|
||||
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]
|
||||
73
GUI/Properties/Resources.Designer.cs
generated
Normal file
73
GUI/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace GUI.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GUI.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap SteamUnknownAvatar {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("SteamUnknownAvatar", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
GUI/Properties/Resources.resx
Normal file
124
GUI/Properties/Resources.resx
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="SteamUnknownAvatar" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\SteamUnknownAvatar.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
||||
26
GUI/Properties/Settings.Designer.cs
generated
Normal file
26
GUI/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace GUI.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
GUI/Properties/Settings.settings
Normal file
7
GUI/Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
BIN
GUI/Resources/SteamUnknownAvatar.jpg
Normal file
BIN
GUI/Resources/SteamUnknownAvatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
GUI/cirno.ico
Normal file
BIN
GUI/cirno.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
11
GUI/packages.config
Normal file
11
GUI/packages.config
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" />
|
||||
<package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" />
|
||||
<package id="HtmlAgilityPack" version="1.4.9.5" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.0-betaV15" targetFramework="net461" />
|
||||
<package id="NLog.Windows.Forms" version="4.2.3" targetFramework="net461" />
|
||||
<package id="protobuf-net" version="2.0.0.668" targetFramework="net461" />
|
||||
<package id="SteamKit2" version="1.8.0" targetFramework="net461" />
|
||||
</packages>
|
||||
@@ -6,7 +6,7 @@ ArchiSteamFarm
|
||||
[](https://travis-ci.org/JustArchi/ArchiSteamFarm)
|
||||
[](./LICENSE-2.0.txt)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
||||
|
||||
[](https://www.paypal.me/JustArchi/1usd)
|
||||
[](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
||||
@@ -42,8 +42,10 @@ ASF officially supports Windows, Linux and OS X operating systems, including fol
|
||||
|
||||
- Windows 10 (Native)
|
||||
- Windows 8.1 (Native)
|
||||
- Windows 7 (Native)
|
||||
- Windows Vista (Native)
|
||||
- Windows 8 (Native)
|
||||
- Windows 7 SP1 (Native)
|
||||
- Windows Server 2012 R2 (Native)
|
||||
- Windows Server 2008 R2 SP1 (Native)
|
||||
- Debian 9 Stretch (Mono)
|
||||
- Debian 8 Jessie (Mono)
|
||||
- Ubuntu 16.04 (Mono)
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
packages/NLog.Windows.Forms.4.2.3/NLog.Windows.Forms.4.2.3.nupkg
vendored
Normal file
BIN
packages/NLog.Windows.Forms.4.2.3/NLog.Windows.Forms.4.2.3.nupkg
vendored
Normal file
Binary file not shown.
768
packages/NLog.Windows.Forms.4.2.3/lib/net35/NLog.Windows.Forms.XML
vendored
Normal file
768
packages/NLog.Windows.Forms.4.2.3/lib/net35/NLog.Windows.Forms.XML
vendored
Normal file
@@ -0,0 +1,768 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>NLog.Windows.Forms</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer">
|
||||
<summary>
|
||||
Strings rendered with this rendrer would convert to links in the control. <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>
|
||||
</summary>
|
||||
<remarks>
|
||||
Internally this renderer replaces the rendered text with a GUID and stores the info in <see cref="P:NLog.LogEventInfo.Properties"/> by <see cref="F:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.LinkInfo.PropertyName"/> as a key
|
||||
Actual rendering is done in <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.Append(System.Text.StringBuilder,NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Implementation of a <see cref="M:NLog.LayoutRenderers.LayoutRenderer.Append(System.Text.StringBuilder,NLog.LogEventInfo)"/>
|
||||
</summary>
|
||||
<param name="builder"></param>
|
||||
<param name="logEvent"></param>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.Inner">
|
||||
<summary>
|
||||
Inner layout that actually provides text
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.LinkInfo">
|
||||
<summary>
|
||||
Inernal class storing the captured link info, used by <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/> to convert the text to link and then identify the logEvent
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.LinkInfo.PropertyName">
|
||||
<summary>
|
||||
Used as a key in <see cref="P:NLog.LogEventInfo.Properties"/>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.RichTextBoxWordColoringRule">
|
||||
<summary>
|
||||
Highlighting rule for Win32 colorful console.
|
||||
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxWordColoringRule.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxWordColoringRule"/> class.
|
||||
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxWordColoringRule.#ctor(System.String,System.String,System.String)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxWordColoringRule"/> class.
|
||||
|
||||
</summary>
|
||||
<param name="text">The text to be matched..</param><param name="fontColor">Color of the text.</param><param name="backgroundColor">Color of the background.</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxWordColoringRule.#ctor(System.String,System.String,System.String,System.Drawing.FontStyle)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxWordColoringRule"/> class.
|
||||
|
||||
</summary>
|
||||
<param name="text">The text to be matched..</param><param name="textColor">Color of the text.</param><param name="backgroundColor">Color of the background.</param><param name="fontStyle">The font style.</param>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.Regex">
|
||||
<summary>
|
||||
Gets or sets the regular expression to be matched. You must specify either <c>text</c> or <c>regex</c>.
|
||||
|
||||
</summary>
|
||||
<docgen category="Rule Matching Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.Text">
|
||||
<summary>
|
||||
Gets or sets the text to be matched. You must specify either <c>text</c> or <c>regex</c>.
|
||||
|
||||
</summary>
|
||||
<docgen category="Rule Matching Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.WholeWords">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether to match whole words only.
|
||||
|
||||
</summary>
|
||||
<docgen category="Rule Matching Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.IgnoreCase">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether to ignore case when comparing texts.
|
||||
|
||||
</summary>
|
||||
<docgen category="Rule Matching Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.Style">
|
||||
<summary>
|
||||
Gets or sets the font style of matched text.
|
||||
Possible values are the same as in <c>FontStyle</c> enum in <c>System.Drawing</c>.
|
||||
|
||||
</summary>
|
||||
<docgen category="Formatting Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.CompiledRegex">
|
||||
<summary>
|
||||
Gets the compiled regular expression that matches either Text or Regex property.
|
||||
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.FontColor">
|
||||
<summary>
|
||||
Gets or sets the font color.
|
||||
Names are identical with KnownColor enum extended with Empty value which means that font color won't be changed.
|
||||
|
||||
</summary>
|
||||
<docgen category="Formatting Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.BackgroundColor">
|
||||
<summary>
|
||||
Gets or sets the background color.
|
||||
Names are identical with KnownColor enum extended with Empty value which means that background color won't be changed.
|
||||
|
||||
</summary>
|
||||
<docgen category="Formatting Options" order="10"/>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.RichTextBoxRowColoringRule">
|
||||
<summary>
|
||||
The row-coloring condition.
|
||||
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#cctor">
|
||||
<summary>
|
||||
Initializes static members of the RichTextBoxRowColoringRule class.
|
||||
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxRowColoringRule"/> class.
|
||||
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#ctor(System.String,System.String,System.String,System.Drawing.FontStyle)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxRowColoringRule"/> class.
|
||||
|
||||
</summary>
|
||||
<param name="condition">The condition.</param><param name="fontColor">Color of the foreground text.</param><param name="backColor">Color of the background text.</param><param name="fontStyle">The font style.</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#ctor(System.String,System.String,System.String)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxRowColoringRule"/> class.
|
||||
|
||||
</summary>
|
||||
<param name="condition">The condition.</param><param name="fontColor">Color of the text.</param><param name="backColor">Color of the background.</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.CheckCondition(NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Checks whether the specified log event matches the condition (if any).
|
||||
|
||||
</summary>
|
||||
<param name="logEvent">Log event.
|
||||
</param>
|
||||
<returns>
|
||||
A value of <see langword="true"/> if the condition is not defined or
|
||||
if it matches, <see langword="false"/> otherwise.
|
||||
|
||||
</returns>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.Default">
|
||||
<summary>
|
||||
Gets the default highlighting rule. Doesn't change the color.
|
||||
|
||||
</summary>
|
||||
<docgen category="Rule Matching Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.Condition">
|
||||
<summary>
|
||||
Gets or sets the condition that must be met in order to set the specified font color.
|
||||
|
||||
</summary>
|
||||
<docgen category="Rule Matching Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.FontColor">
|
||||
<summary>
|
||||
Gets or sets the font color.
|
||||
|
||||
</summary>
|
||||
|
||||
<remarks>
|
||||
Names are identical with KnownColor enum extended with Empty value which means that background color won't be changed.
|
||||
|
||||
</remarks>
|
||||
<docgen category="Formatting Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.BackgroundColor">
|
||||
<summary>
|
||||
Gets or sets the background color.
|
||||
|
||||
</summary>
|
||||
|
||||
<remarks>
|
||||
Names are identical with KnownColor enum extended with Empty value which means that background color won't be changed.
|
||||
|
||||
</remarks>
|
||||
<docgen category="Formatting Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.Style">
|
||||
<summary>
|
||||
Gets or sets the font style of matched text.
|
||||
|
||||
</summary>
|
||||
|
||||
<remarks>
|
||||
Possible values are the same as in <c>FontStyle</c> enum in <c>System.Drawing</c>
|
||||
</remarks>
|
||||
<docgen category="Formatting Options" order="10"/>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.FormControlTarget">
|
||||
<summary>
|
||||
Logs text to Windows.Forms.Control.Text property control of specified Name.
|
||||
</summary>
|
||||
<example>
|
||||
<p>
|
||||
To set up the target in the <a href="config.html">configuration file</a>,
|
||||
use the following syntax:
|
||||
</p>
|
||||
<code lang="XML" source="examples/targets/Configuration File/FormControl/NLog.config" />
|
||||
<p>
|
||||
The result is:
|
||||
</p>
|
||||
<img src="examples/targets/Screenshots/FormControl/FormControl.gif" />
|
||||
<p>
|
||||
To set up the log target programmatically similar to above use code like this:
|
||||
</p>
|
||||
<code lang="C#" source="examples/targets/Configuration API/FormControl/Form1.cs" />,
|
||||
</example>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormControlTarget.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Windows.Forms.FormControlTarget"/> class.
|
||||
</summary>
|
||||
<remarks>
|
||||
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormControlTarget.Write(NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Log message to control.
|
||||
</summary>
|
||||
<param name="logEvent">
|
||||
The logging event.
|
||||
</param>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.FormControlTarget.ControlName">
|
||||
<summary>
|
||||
Gets or sets the name of control to which NLog will log write log text.
|
||||
</summary>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.FormControlTarget.Append">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether log text should be appended to the text of the control instead of overwriting it. </summary>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.FormControlTarget.FormName">
|
||||
<summary>
|
||||
Gets or sets the name of the Form on which the control is located.
|
||||
</summary>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.FormControlTarget.ReverseOrder">
|
||||
<summary>
|
||||
Gets or sets whether new log entry are added to the start or the end of the control
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.FormHelper">
|
||||
<summary>
|
||||
Form helper methods.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormHelper.CreateRichTextBox(System.String,System.Windows.Forms.Form)">
|
||||
<summary>
|
||||
Creates RichTextBox and docks in parentForm.
|
||||
</summary>
|
||||
<param name="name">Name of RichTextBox.</param>
|
||||
<param name="parentForm">Form to dock RichTextBox.</param>
|
||||
<returns>Created RichTextBox.</returns>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormHelper.FindControl(System.String,System.Windows.Forms.Control)">
|
||||
<summary>
|
||||
Finds control embedded on searchControl.
|
||||
</summary>
|
||||
<param name="name">Name of the control.</param>
|
||||
<param name="searchControl">Control in which we're searching for control.</param>
|
||||
<returns>A value of null if no control has been found.</returns>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormHelper.FindControl``1(System.String,System.Windows.Forms.Control)">
|
||||
<summary>
|
||||
Finds control of specified type embended on searchControl.
|
||||
</summary>
|
||||
<typeparam name="TControl">The type of the control.</typeparam>
|
||||
<param name="name">Name of the control.</param>
|
||||
<param name="searchControl">Control in which we're searching for control.</param>
|
||||
<returns>
|
||||
A value of null if no control has been found.
|
||||
</returns>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormHelper.CreateForm(System.String,System.Int32,System.Int32,System.Boolean,System.Boolean,System.Boolean)">
|
||||
<summary>
|
||||
Creates a form.
|
||||
</summary>
|
||||
<param name="name">Name of form.</param>
|
||||
<param name="width">Width of form.</param>
|
||||
<param name="height">Height of form.</param>
|
||||
<param name="show">Auto show form.</param>
|
||||
<param name="showMinimized">If set to <c>true</c> the form will be minimized.</param>
|
||||
<param name="toolWindow">If set to <c>true</c> the form will be created as tool window.</param>
|
||||
<returns>Created form.</returns>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormHelper.ChangeSelectionToLink(System.Windows.Forms.RichTextBox,System.String,System.String)">
|
||||
<summary>
|
||||
Replaces currently selected text in the RTB control with a link
|
||||
</summary>
|
||||
<param name="textBox">target control</param>
|
||||
<param name="text">visible text of the new link</param>
|
||||
<param name="hyperlink">hidden part of the new link</param>
|
||||
<remarks>
|
||||
Based on http://www.codeproject.com/info/cpol10.aspx
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.FormHelper.SetSelectionStyle(System.Windows.Forms.RichTextBox,System.UInt32,System.UInt32)">
|
||||
<summary>
|
||||
Sets selection style for RichTextBox
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787883(v=vs.85).aspx
|
||||
</summary>
|
||||
<param name="textBox">target control</param>
|
||||
<param name="mask">Specifies the parts of the CHARFORMAT2 structure that contain valid information.</param>
|
||||
<param name="effect">A set of bit flags that specify character effects.</param>
|
||||
<remarks>
|
||||
Based on http://www.codeproject.com/info/cpol10.aspx
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.FormHelper.CHARFORMAT2_STRUCT">
|
||||
<summary>
|
||||
CHARFORMAT2 structure, contains information about character formatting in a rich edit control.
|
||||
</summary>
|
||||
see https://msdn.microsoft.com/en-us/library/windows/desktop/bb787883(v=vs.85).aspx
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.MessageBoxTarget">
|
||||
<summary>
|
||||
Pops up log messages as message boxes.
|
||||
</summary>
|
||||
<seealso href="https://github.com/nlog/nlog/wiki/MessageBox-target">Documentation on NLog Wiki</seealso>
|
||||
<example>
|
||||
<p>
|
||||
To set up the target in the <a href="config.html">configuration file</a>,
|
||||
use the following syntax:
|
||||
</p>
|
||||
<code lang="XML" source="examples/targets/Configuration File/MessageBox/NLog.config" />
|
||||
<p>
|
||||
This assumes just one target and a single rule. More configuration
|
||||
options are described <a href="config.html">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
The result is a message box:
|
||||
</p>
|
||||
<img src="examples/targets/Screenshots/MessageBox/MessageBoxTarget.gif" />
|
||||
<p>
|
||||
To set up the log target programmatically use code like this:
|
||||
</p>
|
||||
<code lang="C#" source="examples/targets/Configuration API/MessageBox/Simple/Example.cs" />
|
||||
</example>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.MessageBoxTarget.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Windows.Forms.MessageBoxTarget"/> class.
|
||||
</summary>
|
||||
<remarks>
|
||||
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.MessageBoxTarget.Write(NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Displays the message box with the log message and caption specified in the Caption
|
||||
parameter.
|
||||
</summary>
|
||||
<param name="logEvent">The logging event.</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.MessageBoxTarget.Write(NLog.Common.AsyncLogEventInfo[])">
|
||||
<summary>
|
||||
Displays the message box with the array of rendered logs messages and caption specified in the Caption
|
||||
parameter.
|
||||
</summary>
|
||||
<param name="logEvents">The array of logging events.</param>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.MessageBoxTarget.Caption">
|
||||
<summary>
|
||||
Gets or sets the message box title.
|
||||
</summary>
|
||||
<docgen category='UI Options' order='10' />
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.RichTextBoxTarget">
|
||||
<summary>
|
||||
Log text a Rich Text Box control in an existing or new form.
|
||||
</summary>
|
||||
<seealso href="https://github.com/nlog/nlog/wiki/RichTextBox-target">Documentation on NLog Wiki</seealso>
|
||||
<example>
|
||||
<p>
|
||||
To set up the target in the <a href="config.html">configuration file</a>,
|
||||
use the following syntax:
|
||||
</p><code lang="XML" source="examples/targets/Configuration File/RichTextBox/Simple/NLog.config">
|
||||
</code>
|
||||
<p>
|
||||
The result is:
|
||||
</p><img src="examples/targets/Screenshots/RichTextBox/Simple.gif"/><p>
|
||||
To set up the target with coloring rules in the <a href="config.html">configuration file</a>,
|
||||
use the following syntax:
|
||||
</p><code lang="XML" source="examples/targets/Configuration File/RichTextBox/RowColoring/NLog.config">
|
||||
</code>
|
||||
<code lang="XML" source="examples/targets/Configuration File/RichTextBox/WordColoring/NLog.config">
|
||||
</code>
|
||||
<p>
|
||||
The result is:
|
||||
</p><img src="examples/targets/Screenshots/RichTextBox/RowColoring.gif"/><img src="examples/targets/Screenshots/RichTextBox/WordColoring.gif"/><p>
|
||||
To set up the log target programmatically similar to above use code like this:
|
||||
</p><code lang="C#" source="examples/targets/Configuration API/RichTextBox/Simple/Form1.cs">
|
||||
</code>
|
||||
,
|
||||
<code lang="C#" source="examples/targets/Configuration API/RichTextBox/RowColoring/Form1.cs">
|
||||
</code>
|
||||
for RowColoring,
|
||||
<code lang="C#" source="examples/targets/Configuration API/RichTextBox/WordColoring/Form1.cs">
|
||||
</code>
|
||||
for WordColoring
|
||||
</example>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.LinkPrefix">
|
||||
<summary>
|
||||
Internal prefix that is added to the link id in RTF
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.#cctor">
|
||||
<summary>
|
||||
Initializes static members of the RichTextBoxTarget class.
|
||||
</summary>
|
||||
<remarks>
|
||||
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.ReInitializeAllTextboxes(System.Windows.Forms.Form)">
|
||||
<summary>
|
||||
Attempts to attach existing targets that have yet no textboxes to controls that exist on specified form if appropriate
|
||||
</summary>
|
||||
<remarks>
|
||||
Setting <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.AllowAccessoryFormCreation"/> to true (default) actually causes target to always have a textbox
|
||||
(after having <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.InitializeTarget"/> called), so such targets are not affected by this method.
|
||||
</remarks>
|
||||
<param name="form">a Form to check for RichTextBoxes</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.GetTargetByControl(System.Windows.Forms.RichTextBox)">
|
||||
<summary>
|
||||
Returns a target attached to a given RichTextBox control
|
||||
</summary>
|
||||
<param name="control">a RichTextBox control for which the target is to be returned</param>
|
||||
<returns>A RichTextBoxTarget attached to a given control or <code>null</code> if no target is attached</returns>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:NLog.Windows.Forms.RichTextBoxTarget"/> class.
|
||||
</summary>
|
||||
<remarks>
|
||||
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.messageRetention">
|
||||
<summary>
|
||||
Actual value of the <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MessageRetention"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.lastLoggedTextBoxControl">
|
||||
<summary>
|
||||
A textbox to which we have logged last time. Used to prevent duplicating messages in the same textbox in case of config reload and RichTextBoxTargetMessageRetentionStrategy.All
|
||||
see https://github.com/NLog/NLog.Windows.Forms/pull/22
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.messageQueueLock">
|
||||
<summary>
|
||||
a lock object used to synchronize access to <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.messageQueue"/>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.messageQueue">
|
||||
<summary>
|
||||
A queue used to store messages based on <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MessageRetention"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.supportLinks">
|
||||
<summary>
|
||||
Actual value of the <see cref="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked"/> property
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkedEventsLock">
|
||||
<summary>
|
||||
Lock for <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.linkedEvents"/> dictionary access
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkedEvents">
|
||||
<summary>
|
||||
A map from link id to a corresponding log event
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkRegexLock">
|
||||
<summary>
|
||||
Used to synchronize lazy initialization of <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.linkAddRegex"/> and <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.linkRemoveRtfRegex"/> in <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>.set
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkAddRegex">
|
||||
<summary>
|
||||
Used to capture link placeholders in <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/>
|
||||
Lazily initialized in <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>.set(true). Assure checking <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/> before accessing the field
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkRemoveRtfRegex">
|
||||
<summary>
|
||||
Used to parse RTF with links when removing excess lines in <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/>
|
||||
Lazily initialized in <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>.set(true). Assure checking <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/> before accessing the field
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.InitializeTarget">
|
||||
<summary>
|
||||
Initializes the target. Can be used by inheriting classes
|
||||
to initialize logging.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.HandleError(System.String,System.Object[])">
|
||||
<summary>
|
||||
Called from constructor when error is detected. In case LogManager.ThrowExceptions is enabled, throws the exception, otherwise - logs the problem message
|
||||
</summary>
|
||||
<param name="message">exception/log message format</param>
|
||||
<param name="args">message format arguments</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.CreateAccessoryForm">
|
||||
<summary>
|
||||
Used to create accessory form with textbox in case specified form or control were not found during InitializeTarget() and AllowAccessoryFormCreation==true
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.AttachToControl(System.Windows.Forms.Form,System.Windows.Forms.RichTextBox)">
|
||||
<summary>
|
||||
Used to (re)initialize target when attaching it to another RTB control
|
||||
</summary>
|
||||
<param name="form">form owning textboxControl</param>
|
||||
<param name="textboxControl">a new control to attach to</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox_LinkClicked(System.Object,System.Windows.Forms.LinkClickedEventArgs)">
|
||||
<summary>
|
||||
Attached to RTB-control's LinkClicked event to convert link text to logEvent
|
||||
</summary>
|
||||
<param name="sender"></param>
|
||||
<param name="e"></param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.DetachFromControl">
|
||||
<summary>
|
||||
if <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.CreatedForm"/> is true, then destroys created form. Resets <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.CreatedForm"/>, <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetForm"/> and <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox"/> to default values
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.CloseTarget">
|
||||
<summary>
|
||||
Closes the target and releases any unmanaged resources.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.Write(NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Log message to RichTextBox.
|
||||
</summary>
|
||||
<param name="logEvent">The logging event.</param>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.DoSendMessageToTextbox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Actually sends log message to <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox"/>
|
||||
</summary>
|
||||
<param name="logMessage">a message to send</param>
|
||||
<param name="rule">matching coloring rule</param>
|
||||
<param name="logEvent">original logEvent</param>
|
||||
<returns>true if the message was actually sent (i.e. <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox"/> is not null and not disposed, and no exception happened during message send)</returns>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.FindMatchingRule(NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Find first matching rule
|
||||
</summary>
|
||||
<param name="logEvent"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.StoreMessage(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)">
|
||||
<summary>
|
||||
Stores a new message in internal queue, if it exists. Removes overflowing messages.
|
||||
</summary>
|
||||
<param name="logMessage">a message to store</param>
|
||||
<param name="rule">a corresponding coloring rule</param>
|
||||
<param name="logEvent">original LogEvent</param>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.DefaultRowColoringRules">
|
||||
<summary>
|
||||
Gets the default set of row coloring rules which applies when <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.UseDefaultRowColoringRules"/> is set to true.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.ControlName">
|
||||
<summary>
|
||||
Gets or sets the Name of RichTextBox to which Nlog will write.
|
||||
</summary>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.FormName">
|
||||
<summary>
|
||||
Gets or sets the name of the Form on which the control is located.
|
||||
If there is no open form of a specified name than NLog will create a new one.
|
||||
</summary>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.UseDefaultRowColoringRules">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether to use default coloring rules.
|
||||
</summary>
|
||||
<docgen category='Highlighting Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.RowColoringRules">
|
||||
<summary>
|
||||
Gets the row coloring rules.
|
||||
</summary>
|
||||
<docgen category='Highlighting Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.WordColoringRules">
|
||||
<summary>
|
||||
Gets the word highlighting rules.
|
||||
</summary>
|
||||
<docgen category='Highlighting Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.ToolWindow">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether the created window will be a tool window.
|
||||
</summary>
|
||||
<remarks>
|
||||
This parameter is ignored when logging to existing form control.
|
||||
Tool windows have thin border, and do not show up in the task bar.
|
||||
</remarks>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.ShowMinimized">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether the created form will be initially minimized.
|
||||
</summary>
|
||||
<remarks>
|
||||
This parameter is ignored when logging to existing form control.
|
||||
</remarks>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.Width">
|
||||
<summary>
|
||||
Gets or sets the initial width of the form with rich text box.
|
||||
</summary>
|
||||
<remarks>
|
||||
This parameter is ignored when logging to existing form control.
|
||||
</remarks>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.Height">
|
||||
<summary>
|
||||
Gets or sets the initial height of the form with rich text box.
|
||||
</summary>
|
||||
<remarks>
|
||||
This parameter is ignored when logging to existing form control.
|
||||
</remarks>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.AutoScroll">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether scroll bar will be moved automatically to show most recent log entries.
|
||||
</summary>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.MaxLines">
|
||||
<summary>
|
||||
Gets or sets the maximum number of lines the rich text box will store (or 0 to disable this feature).
|
||||
</summary>
|
||||
<remarks>
|
||||
After exceeding the maximum number, first line will be deleted.
|
||||
</remarks>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.TargetForm">
|
||||
<summary>
|
||||
Gets or sets the form to log to.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox">
|
||||
<summary>
|
||||
Gets or sets the rich text box to log to.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.CreatedForm">
|
||||
<summary>
|
||||
Form created (true) or used an existing (false). Set after <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.InitializeTarget"/>. Can be true only if <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.AllowAccessoryFormCreation"/> is set to true (default).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.AllowAccessoryFormCreation">
|
||||
<summary>
|
||||
Gets or sets a value indicating whether to create accessory form if the specified form/control combination was not found during target initialization.
|
||||
</summary>
|
||||
<remarks>
|
||||
If set to false and the control was not found during target initialiation, the target would skip events until the control is found during <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.ReInitializeAllTextboxes(System.Windows.Forms.Form)"/> call
|
||||
</remarks>
|
||||
<docgen category="Form Options" order="10"/>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.MessageRetention">
|
||||
<summary>
|
||||
gets or sets the message retention strategy which determines how the target handles messages when there's no control attached, or when switching between controls
|
||||
</summary>
|
||||
<remarks>
|
||||
</remarks>
|
||||
<docgen category='Form Options' order='10' />
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks">
|
||||
<summary>
|
||||
If set to true, using "rtb-link" renderer (<see cref="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer"/>) would create clickable links in the control.
|
||||
<seealso cref="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked"/>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked">
|
||||
<summary>
|
||||
Event fired when the user clicks on a link in the control created by the "rtb-link" renderer (<see cref="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer"/>).
|
||||
<seealso cref="T:NLog.Windows.Forms.RichTextBoxTarget.DelLinkClicked"/>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.LinkedEventsCount">
|
||||
<summary>
|
||||
Returns number of events stored for active links in the control.
|
||||
Used only in tests to check that non needed events are removed.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.RichTextBoxTarget.DelLinkClicked">
|
||||
<summary>
|
||||
Type of delegate for <see cref="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked"/> event.
|
||||
</summary>
|
||||
<param name="sender">The target that caused the event</param>
|
||||
<param name="linkText">Visible text of the link being clicked</param>
|
||||
<param name="logEvent">Original log event that caused a line with the link</param>
|
||||
</member>
|
||||
<member name="T:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy">
|
||||
<summary>
|
||||
How to handle messages when switching between target controls or no control is attached at all
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy.None">
|
||||
<summary>
|
||||
Just skip logging events when no target control attached. Only new messages would be sent to rich text box after attachement.
|
||||
No additional resources spent on this.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy.OnlyMissed">
|
||||
<summary>
|
||||
Store logging events only during periods when no target control attached. Only these messages would be sent to rich text box after attachment. Messages that were sent to previous textbox are not stored and would not be shown.
|
||||
Number of events stored is limited by <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MaxLines"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy.All">
|
||||
<summary>
|
||||
Store all events in internal queue. After attaching to a new control all the stored messages would be repeated in it, including messages that were sent to previous textbox.
|
||||
Number of messages stored is limited by <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MaxLines"/>.
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
BIN
packages/NLog.Windows.Forms.4.2.3/lib/net35/NLog.Windows.Forms.dll
vendored
Normal file
BIN
packages/NLog.Windows.Forms.4.2.3/lib/net35/NLog.Windows.Forms.dll
vendored
Normal file
Binary file not shown.
11
run.sh
11
run.sh
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user