Compare commits

...

18 Commits

Author SHA1 Message Date
JustArchi
a823471771 Bump version to test updates 2016-03-09 03:40:57 +01:00
JustArchi
dac057d242 World Domination is close 2016-03-09 03:28:05 +01:00
JustArchi
3d9fe36245 The end is near... #48 2016-03-09 03:10:33 +01:00
JustArchi
5c1da24def Misc 2016-03-08 03:29:48 +01:00
JustArchi
1e961a5945 Add CustomGamePlayedWhileIdle 2016-03-08 03:18:50 +01:00
JustArchi
84898146d2 Add more debugging tools 2016-03-08 02:22:00 +01:00
JustArchi
552613e977 Add AccountPlayingDelay, closes #122 2016-03-07 23:10:07 +01:00
JustArchi
07687df91f Add debugging and NetHookAnalyzer 2016-03-07 18:12:05 +01:00
JustArchi
1a4d941a2c Fix derp, #141 2016-03-07 16:40:34 +01:00
JustArchi
cfbd880995 Add more support for SDA 2016-03-07 15:53:46 +01:00
JustArchi
09abe77495 Support SDA files 2016-03-07 15:42:38 +01:00
JustArchi
ac9943ff94 Misc 2016-03-07 14:48:59 +01:00
JustArchi
741dd2adb7 Add Holiday Sale 2013 to blacklist 2016-03-07 14:47:28 +01:00
JustArchi
1ad5d3676f Add GlobalDatabase 2016-03-07 02:39:55 +01:00
JustArchi
27254aa31e Copy ASF.json to out 2016-03-06 23:34:46 +01:00
JustArchi
bb90dc1c01 Bugfixes 2016-03-06 23:32:17 +01:00
JustArchi
292ec97b1c Work on GlobalConfig, #131 2016-03-06 23:28:56 +01:00
JustArchi
238cc2ad46 Code review 2016-03-06 22:14:02 +01:00
25 changed files with 620 additions and 81 deletions

4
.gitignore vendored
View File

@@ -4,11 +4,13 @@
# Ignore all config files, apart from ones we want to include
ArchiSteamFarm/config/*
!ArchiSteamFarm/config/ASF.json
!ArchiSteamFarm/config/example.json
!ArchiSteamFarm/config/minimal.json
# Ignore local debugging log file
# Ignore local debugging
ArchiSteamFarm/log.txt
ArchiSteamFarm/debug/*
#################
## Eclipse

View File

@@ -50,7 +50,11 @@ namespace ArchiSteamFarm {
Items = 514
}
internal ENotificationType NotificationType { get; set; }
internal readonly ENotificationType NotificationType;
internal Notification(ENotificationType notificationType) {
NotificationType = notificationType;
}
}
internal readonly List<Notification> Notifications;
@@ -64,9 +68,7 @@ namespace ArchiSteamFarm {
Notifications = new List<Notification>(msg.notifications.Count);
foreach (var notification in msg.notifications) {
Notifications.Add(new Notification {
NotificationType = (Notification.ENotificationType) notification.user_notification_type
});
Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type));
}
}
@@ -79,7 +81,7 @@ namespace ArchiSteamFarm {
if (msg.count_new_items > 0) {
Notifications = new List<Notification>(1) {
new Notification { NotificationType = Notification.ENotificationType.Items }
new Notification(Notification.ENotificationType.Items)
};
}
}
@@ -102,7 +104,7 @@ namespace ArchiSteamFarm {
}
internal sealed class PurchaseResponseCallback : CallbackMsg {
internal enum EPurchaseResult {
internal enum EPurchaseResult : int {
Unknown = -1,
OK = 0,
AlreadyOwned = 9,
@@ -128,6 +130,10 @@ namespace ArchiSteamFarm {
Result = (EResult) msg.eresult;
PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
if (msg.purchase_receipt_info == null) {
return;
}
ReceiptInfo = new KeyValue();
using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
if (!ReceiptInfo.TryReadAsBinary(ms)) {
@@ -180,6 +186,27 @@ namespace ArchiSteamFarm {
Client.Send(request);
}
internal void PlayGame(string gameName) {
if (!Client.IsConnected) {
return;
}
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
var gamePlayed = new CMsgClientGamesPlayed.GamePlayed();
if (!string.IsNullOrEmpty(gameName)) {
gamePlayed.game_id = new GameID() {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
};
gamePlayed.game_extra_info = gameName;
}
request.Body.games_played.Add(gamePlayed);
Client.Send(request);
}
internal void PlayGames(params uint[] gameIDs) {
if (!Client.IsConnected) {
return;
@@ -200,7 +227,7 @@ namespace ArchiSteamFarm {
}
internal void PlayGames(ICollection<uint> gameIDs) {
if (gameIDs == null || gameIDs.Count == 0 || !Client.IsConnected) {
if (gameIDs == null || !Client.IsConnected) {
return;
}

View File

@@ -12,6 +12,7 @@
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper>
<TargetFrameworkProfile />
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
@@ -26,7 +27,6 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -102,10 +102,13 @@
<Compile Include="ArchiWebHandler.cs" />
<Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" />
<Compile Include="CMsgClientClanInviteAction.cs" />
<Compile Include="Debugging.cs" />
<Compile Include="GlobalConfig.cs" />
<Compile Include="JSON\GitHub.cs" />
<Compile Include="Logging.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -120,6 +123,9 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="config\ASF.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="config\example.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -158,6 +164,7 @@
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir "$(TargetDir)out" "$(TargetDir)out\config"
copy "$(TargetDir)config\ASF.json" "$(TargetDir)out\config"
copy "$(TargetDir)config\example.json" "$(TargetDir)out\config"
copy "$(TargetDir)config\minimal.json" "$(TargetDir)out\config"
"$(SolutionDir)tools\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
@@ -165,6 +172,7 @@
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir -p "$(TargetDir)out" "$(TargetDir)out/config"
cp "$(TargetDir)config/ASF.json" "$(TargetDir)out/config"
cp "$(TargetDir)config/example.json" "$(TargetDir)out/config"
cp "$(TargetDir)config/minimal.json" "$(TargetDir)out/config"
mono -O=all "$(SolutionDir)tools/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out/ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"

View File

@@ -34,13 +34,17 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler {
private const int Timeout = 1000 * WebBrowser.HttpTimeout; // In miliseconds
private static int Timeout = 30 * 1000;
private readonly Bot Bot;
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4);
private ulong SteamID;
internal static void Init() {
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
}
internal ArchiWebHandler(Bot bot) {
Bot = bot;
}
@@ -134,7 +138,7 @@ namespace ArchiSteamFarm {
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.HasValue && !isLoggedIn.Value) {
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
var restart = Task.Run(async () => await Bot.Restart().ConfigureAwait(false));
Task.Run(async () => await Bot.Restart().ConfigureAwait(false)).Forget();
return true;
}

View File

@@ -74,12 +74,12 @@ namespace ArchiSteamFarm {
return null;
}
internal static async Task RefreshCMs() {
internal static async Task RefreshCMs(uint cellID) {
bool initialized = false;
for (byte i = 0; i < 3 && !initialized; i++) {
try {
Logging.LogGenericInfo("Refreshing list of CMs...");
await SteamDirectory.Initialize().ConfigureAwait(false);
await SteamDirectory.Initialize(cellID).ConfigureAwait(false);
initialized = true;
} catch (Exception e) {
Logging.LogGenericException(e);
@@ -178,9 +178,34 @@ namespace ArchiSteamFarm {
BotDatabase = BotDatabase.Load(botPath + ".db");
SentryFile = botPath + ".bin";
// Support and convert SDA files
if (BotDatabase.SteamGuardAccount == null && File.Exists(botPath + ".maFile")) {
Logging.LogGenericInfo("Converting SDA .maFile into ASF format...", botName);
try {
BotDatabase.SteamGuardAccount = JsonConvert.DeserializeObject<SteamGuardAccount>(File.ReadAllText(botPath + ".maFile"));
File.Delete(botPath + ".maFile");
Logging.LogGenericInfo("Success!", botName);
} catch (Exception e) {
Logging.LogGenericException(e, botName);
}
}
// Initialize
SteamClient = new SteamClient();
if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized) {
try {
if (Directory.Exists(Program.DebugDirectory)) {
Directory.Delete(Program.DebugDirectory, true);
}
Directory.CreateDirectory(Program.DebugDirectory);
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
Debugging.NetHookAlreadyInitialized = true;
} catch (Exception e) {
Logging.LogGenericException(e, botName);
}
}
ArchiHandler = new ArchiHandler();
SteamClient.AddHandler(ArchiHandler);
@@ -253,8 +278,8 @@ namespace ArchiSteamFarm {
}
internal void ResetGamesPlayed() {
if (BotConfig.GamesPlayedWhileIdle.Contains(0)) {
ArchiHandler.PlayGames(0);
if (!string.IsNullOrEmpty(BotConfig.CustomGamePlayedWhileIdle)) {
ArchiHandler.PlayGame(BotConfig.CustomGamePlayedWhileIdle);
} else {
ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle);
}
@@ -355,7 +380,7 @@ namespace ArchiSteamFarm {
if (!KeepRunning) {
KeepRunning = true;
var handleCallbacks = Task.Run(() => HandleCallbacks());
Task.Run(() => HandleCallbacks()).Forget();
}
Logging.LogGenericInfo("Starting...", BotName);
@@ -975,8 +1000,8 @@ namespace ArchiSteamFarm {
}
} else if (LoggedInElsewhere) {
LoggedInElsewhere = false;
Logging.LogGenericWarning("Account is being used elsewhere, will try reconnecting in 30 minutes...", BotName);
await Utilities.SleepAsync(30 * 60 * 1000).ConfigureAwait(false);
Logging.LogGenericWarning("Account is being used elsewhere, ASF will try to resume farming in " + Program.GlobalConfig.AccountPlayingDelay + " minutes...", BotName);
await Utilities.SleepAsync(Program.GlobalConfig.AccountPlayingDelay * 60 * 1000).ConfigureAwait(false);
}
Logging.LogGenericInfo("Reconnecting...", BotName);
@@ -1152,6 +1177,23 @@ namespace ArchiSteamFarm {
case EResult.OK:
Logging.LogGenericInfo("Successfully logged on!", BotName);
if (callback.CellID != 0) {
Program.GlobalDatabase.CellID = callback.CellID;
}
// Support and convert SDA files
string maFilePath = Path.Combine(Program.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
if (BotDatabase.SteamGuardAccount == null && File.Exists(maFilePath)) {
Logging.LogGenericInfo("Converting SDA .maFile into ASF format...", BotName);
try {
BotDatabase.SteamGuardAccount = JsonConvert.DeserializeObject<SteamGuardAccount>(File.ReadAllText(maFilePath));
File.Delete(maFilePath);
Logging.LogGenericInfo("Success!", BotName);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
}
}
if (BotConfig.UseAsfAsMobileAuthenticator && TwoFactorAuth == null && BotDatabase.SteamGuardAccount == null) {
LinkMobileAuthenticator();
}
@@ -1183,7 +1225,7 @@ namespace ArchiSteamFarm {
Trading.CheckTrades();
var start = Task.Run(async () => await CardsFarmer.StartFarming().ConfigureAwait(false));
Task.Run(async () => await CardsFarmer.StartFarming().ConfigureAwait(false)).Forget();
break;
case EResult.NoConnection:
case EResult.ServiceUnavailable:

View File

@@ -30,8 +30,6 @@ using System.Xml;
namespace ArchiSteamFarm {
internal sealed class BotConfig {
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Enabled { get; private set; } = false;
@@ -89,6 +87,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal byte SendTradePeriod { get; private set; } = 0;
[JsonProperty]
internal string CustomGamePlayedWhileIdle { get; private set; } = null;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 };

View File

@@ -34,6 +34,10 @@ namespace ArchiSteamFarm {
return _LoginKey;
}
set {
if (_LoginKey == value) {
return;
}
_LoginKey = value;
Save();
}
@@ -44,6 +48,10 @@ namespace ArchiSteamFarm {
return _SteamGuardAccount;
}
set {
if (_SteamGuardAccount == value) {
return;
}
_SteamGuardAccount = value;
Save();
}

View File

@@ -51,12 +51,14 @@ namespace ArchiSteamFarm {
internal CardsFarmer(Bot bot) {
Bot = bot;
Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(15), // Delay
TimeSpan.FromMinutes(60) // Period
);
if (Timer == null) {
Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(15), // Delay
TimeSpan.FromMinutes(60) // Period
);
}
}
internal static List<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
@@ -98,7 +100,7 @@ namespace ArchiSteamFarm {
await StopFarming().ConfigureAwait(false);
} else {
Logging.LogGenericInfo("Now running in Automatic Farming mode", Bot.BotName);
var start = Task.Run(async () => await StartFarming().ConfigureAwait(false));
Task.Run(async () => await StartFarming().ConfigureAwait(false)).Forget();
}
return true;
@@ -327,7 +329,7 @@ namespace ArchiSteamFarm {
continue;
}
if (BotConfig.GlobalBlacklist.Contains(appID)) {
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
continue;
}

View File

@@ -31,5 +31,7 @@ namespace ArchiSteamFarm {
#endif
internal static bool IsReleaseBuild => !IsDebugBuild;
internal static bool NetHookAlreadyInitialized { get; set; } = false;
}
}

View File

@@ -0,0 +1,88 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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 Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
namespace ArchiSteamFarm {
internal sealed class GlobalConfig {
internal enum EUpdateChannel : byte {
Unknown,
Stable,
Experimental
}
// This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal EUpdateChannel UpdateChannel { get; private set; } = GlobalConfig.EUpdateChannel.Stable;
[JsonProperty(Required = Required.DisallowNull)]
internal byte HttpTimeout { get; private set; } = 30;
[JsonProperty(Required = Required.DisallowNull)]
internal byte AccountPlayingDelay { get; private set; } = 5;
[JsonProperty(Required = Required.DisallowNull)]
internal byte RequestLimiterDelay { get; private set; } = 7;
[JsonProperty(Required = Required.DisallowNull)]
internal string WCFHostname { get; private set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
internal ushort WCFPort { get; private set; } = 1242;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint>(GlobalBlacklist);
internal static GlobalConfig Load() {
string filePath = Path.Combine(Program.ConfigDirectory, Program.GlobalConfigFile);
if (!File.Exists(filePath)) {
return null;
}
GlobalConfig globalConfig;
try {
globalConfig = JsonConvert.DeserializeObject<GlobalConfig>(File.ReadAllText(filePath));
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
return globalConfig;
}
// This constructor is used only by deserializer
private GlobalConfig() { }
}
}

View File

@@ -0,0 +1,80 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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 Newtonsoft.Json;
using SteamAuth;
using System;
using System.IO;
namespace ArchiSteamFarm {
internal sealed class GlobalDatabase {
private static readonly string FilePath = Path.Combine(Program.ConfigDirectory, Program.GlobalDatabaseFile);
internal uint CellID {
get {
return _CellID;
}
set {
if (_CellID == value) {
return;
}
_CellID = value;
Save();
}
}
[JsonProperty(Required = Required.DisallowNull)]
private uint _CellID = 0;
internal static GlobalDatabase Load() {
if (!File.Exists(FilePath)) {
return new GlobalDatabase();
}
GlobalDatabase globalDatabase;
try {
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(FilePath));
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
return globalDatabase;
}
// This constructor is used only by deserializer
private GlobalDatabase() { }
private void Save() {
lock (FilePath) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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 Newtonsoft.Json;
using System.Collections.Generic;
namespace ArchiSteamFarm {
internal static class GitHub {
internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal string Name { get; private set; }
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal string DownloadURL { get; private set; }
}
internal sealed class ReleaseResponse {
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
internal string Tag { get; private set; }
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal List<Asset> Assets { get; private set; }
}
}
}

View File

@@ -31,17 +31,18 @@ namespace ArchiSteamFarm {
internal static class Logging {
private static readonly object FileLock = new object();
internal static bool LogToFile { get; set; } = false;
internal static bool? LogToFile { get; set; } = null;
internal static void Init() {
if (!LogToFile.HasValue) {
LogToFile = true;
}
lock (FileLock) {
try {
File.Delete(Program.LogFile);
} catch (Exception e) {
bool logToFile = LogToFile;
LogToFile = false;
LogGenericException(e);
LogToFile = logToFile;
}
}
}
@@ -121,15 +122,14 @@ namespace ArchiSteamFarm {
Console.Write(loggedMessage);
}
if (LogToFile) {
if (LogToFile.GetValueOrDefault()) {
lock (FileLock) {
try {
File.AppendAllText(Program.LogFile, loggedMessage);
} catch (Exception e) {
bool logToFile = LogToFile;
LogToFile = false;
LogGenericException(e);
LogToFile = logToFile;
LogToFile = true;
}
}
}

View File

@@ -22,8 +22,10 @@
*/
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
@@ -48,48 +50,203 @@ namespace ArchiSteamFarm {
Server // Normal + WCF server
}
private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest";
private const string GithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases";
internal const string ASF = "ASF";
internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt";
internal const string GlobalConfigFile = ASF + ".json";
internal const string GlobalDatabaseFile = ASF + ".db";
private static readonly object ConsoleLock = new object();
private static readonly SemaphoreSlim SteamSemaphore = new SemaphoreSlim(1);
private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false);
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
private static readonly string ExecutableFile = Assembly.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 readonly string Version = Assembly.GetName().Version.ToString();
private static EMode Mode;
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static bool ConsoleIsBusy { get; private set; } = false;
private static Timer AutoUpdatesTimer;
private static EMode Mode = EMode.Normal;
private static async Task CheckForUpdate() {
JObject response = await WebBrowser.UrlGetToJObject(LatestGithubReleaseURL).ConfigureAwait(false);
if (response == null) {
string oldExeFile = ExecutableFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
try {
File.Delete(oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
return;
}
}
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) {
return;
}
string remoteVersion = response["tag_name"].ToString();
if (string.IsNullOrEmpty(remoteVersion)) {
string releaseURL = GithubReleaseURL;
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseURL += "/latest";
}
string response = null;
Logging.LogGenericInfo("Checking new version...");
for (byte i = 0; i < WebBrowser.MaxRetries && string.IsNullOrEmpty(response); i++) {
response = await WebBrowser.UrlGetToContent(releaseURL).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(response)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
string localVersion = Version;
GitHub.ReleaseResponse releaseResponse = null;
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;
}
Logging.LogGenericInfo("Local version: " + localVersion);
Logging.LogGenericInfo("Remote version: " + remoteVersion);
if (releases == null || releases.Count == 0) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
int comparisonResult = localVersion.CompareTo(remoteVersion);
if (comparisonResult < 0) {
releaseResponse = releases[0];
}
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + releaseResponse.Tag);
if (Version.CompareTo(releaseResponse.Tag) >= 0) { // If local version is the same or newer than remote version
// Set up a timer that will automatically update ASF on as-needed basis
if (GlobalConfig.AutoUpdates && AutoUpdatesTimer == null) {
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
}
return;
}
if (!GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("New version is available!");
Logging.LogGenericInfo("Consider updating yourself!");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
} else if (comparisonResult > 0) {
Logging.LogGenericInfo("You're currently using pre-release version!");
Logging.LogGenericInfo("Be careful!");
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.Asset binaryAsset = null;
foreach (var asset in releaseResponse.Assets) {
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName)) {
continue;
}
binaryAsset = asset;
break;
}
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...");
Stream newExe = await WebBrowser.UrlGetToStream(binaryAsset.DownloadURL).ConfigureAwait(false);
if (newExe == null) {
Logging.LogGenericWarning("Could not download new version!");
return;
}
// We start deep update logic here
string newExeFile = ExecutableFile + ".new";
// Firstly we create new exec
try {
using (FileStream fileStream = File.Open(newExeFile, FileMode.Create)) {
await newExe.CopyToAsync(fileStream).ConfigureAwait(false);
}
} 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 { }
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 { }
return;
}
Logging.LogGenericInfo("Update process is finished! ASF will now restart itself...");
await Utilities.SleepAsync(5000);
if (!Restart()) {
// Make sure that we won't try updating again in this case
if (AutoUpdatesTimer != null) {
AutoUpdatesTimer.Dispose();
AutoUpdatesTimer = null;
}
// Inform user about failure
Logging.LogGenericWarning("ASF could not restart itself, you may need to restart it manually!");
await Utilities.SleepAsync(5000);
}
}
@@ -97,17 +254,27 @@ namespace ArchiSteamFarm {
Environment.Exit(exitCode);
}
internal static void Restart() {
System.Diagnostics.Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs()));
Exit();
internal static bool Restart() {
try {
// TODO: This probably won't work on Mono, I wonder if I can make it work at some point
if (Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs())) != null) {
Exit();
return true;
} else {
return false;
}
} catch (Exception e) {
Logging.LogGenericException(e);
return false;
}
}
internal static async Task LimitSteamRequestsAsync() {
await SteamSemaphore.WaitAsync().ConfigureAwait(false);
var releaseLater = Task.Run(async () => {
await Utilities.SleepAsync(7000).ConfigureAwait(false); // We must add some delay to not get caught by Steam rate limiter
Task.Run(async () => {
await Utilities.SleepAsync(GlobalConfig.RequestLimiterDelay * 1000).ConfigureAwait(false);
SteamSemaphore.Release();
});
}).Forget();
}
internal static string GetUserInput(string botLogin, EUserInputType userInputType, string extraInformation = null) {
@@ -166,8 +333,23 @@ namespace ArchiSteamFarm {
}
private static void InitServices() {
Logging.Init();
GlobalConfig = GlobalConfig.Load();
if (GlobalConfig == null) {
Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.db exists and is valid!");
Thread.Sleep(5000);
Exit(1);
}
GlobalDatabase = GlobalDatabase.Load();
if (GlobalDatabase == null) {
Logging.LogGenericError("Global database could not be loaded!");
Thread.Sleep(5000);
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init();
WCF.Init();
}
private static void ParseArgs(string[] args) {
@@ -244,11 +426,7 @@ namespace ArchiSteamFarm {
}
}
// By default we're operating on normal mode
Mode = EMode.Normal;
Logging.LogToFile = true;
// But that can be overriden by arguments
// Parse args
ParseArgs(args);
// If we ran ASF as a client, we're done by now
@@ -256,7 +434,8 @@ namespace ArchiSteamFarm {
return;
}
Task.Run(async () => await CheckForUpdate().ConfigureAwait(false)).Wait();
// From now on it's server mode
Logging.Init();
if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericError("Config directory doesn't exist!");
@@ -264,11 +443,17 @@ namespace ArchiSteamFarm {
Exit(1);
}
CheckForUpdate().Wait();
// Before attempting to connect, initialize our list of CMs
Bot.RefreshCMs().Wait();
Bot.RefreshCMs(GlobalDatabase.CellID).Wait();
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.json")) {
string botName = Path.GetFileNameWithoutExtension(configFile);
if (botName.Equals(ASF)) {
continue;
}
Bot bot = new Bot(botName);
if (!bot.BotConfig.Enabled) {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);

View File

@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ArchiSteamFarm")]
[assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015")]
[assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015-2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// 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("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: AssemblyVersion("2.0.0.2")]
[assembly: AssemblyFileVersion("2.0.0.2")]

View File

@@ -39,10 +39,10 @@ namespace ArchiSteamFarm {
internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
var releaseLater = Task.Run(async () => {
await Utilities.SleepAsync(3000).ConfigureAwait(false); // We must add some delay to not get caught by Steam rate limiter
Task.Run(async () => {
await Utilities.SleepAsync(Program.GlobalConfig.RequestLimiterDelay * 1000).ConfigureAwait(false);
InventorySemaphore.Release();
});
}).Forget();
}
internal Trading(Bot bot) {

View File

@@ -27,6 +27,8 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class Utilities {
internal static void Forget(this Task task) { }
internal static async Task SleepAsync(int miliseconds) {
await Task.Delay(miliseconds).ConfigureAwait(false);
}

View File

@@ -35,11 +35,15 @@ namespace ArchiSteamFarm {
internal sealed class WCF : IWCF {
private const string URL = "http://localhost:1242/ASF"; // 1242 = 1024 + A(65) + S(83) + F(70)
private static string URL = "http://localhost:1242/ASF";
private ServiceHost ServiceHost;
private Client Client;
internal static void Init() {
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
}
internal bool IsServerRunning() {
return ServiceHost != null;
}

View File

@@ -26,6 +26,7 @@ using HtmlAgilityPack;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
@@ -33,7 +34,6 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class WebBrowser {
internal const byte HttpTimeout = 180; // In seconds, how long we can wait for server's response
internal 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
internal const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
internal const byte MaxRetries = 5; // Defines maximum number of retries, UrlRequest() does not handle retry by itself (it's app responsibility)
@@ -42,10 +42,12 @@ namespace ArchiSteamFarm {
private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler {
UseCookies = false
}) {
Timeout = TimeSpan.FromSeconds(HttpTimeout)
Timeout = TimeSpan.FromSeconds(30)
};
internal static void Init() {
HttpClient.Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout);
// Most web services expect that UserAgent is set, so we declare it globally
// Any request can override that on as-needed basis (see: RequestOptions.FakeUserAgent)
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
@@ -80,7 +82,7 @@ namespace ArchiSteamFarm {
return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false);
}
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies, string referer = null) {
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
@@ -90,12 +92,28 @@ namespace ArchiSteamFarm {
return null;
}
HttpContent httpContent = httpResponse.Content;
if (httpContent == null) {
if (httpResponse.Content == null) {
return null;
}
return await httpContent.ReadAsStringAsync().ConfigureAwait(false);
return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
internal static async Task<Stream> UrlGetToStream(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false);
if (httpResponse == null) {
return null;
}
if (httpResponse.Content == null) {
return null;
}
return await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null) {
@@ -144,7 +162,7 @@ namespace ArchiSteamFarm {
HttpResponseMessage responseMessage;
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
if (data != null) {
if (data != null && data.Count > 0) {
try {
requestMessage.Content = new FormUrlEncodedContent(data);
} catch (UriFormatException e) {
@@ -161,7 +179,7 @@ namespace ArchiSteamFarm {
requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
}
if (referer != null) {
if (!string.IsNullOrEmpty(referer)) {
requestMessage.Headers.Referrer = new Uri(referer);
}

View File

@@ -0,0 +1,17 @@
{
"Debug": false,
"AutoUpdates": true,
"UpdateChannel": 1,
"HttpTimeout": 30,
"AccountPlayingDelay": 5,
"RequestLimiterDelay": 7,
"WCFHostname": "localhost",
"WCFPort": 1242,
"Blacklist": [
267420,
303700,
335590,
368020,
425280
]
}

View File

@@ -18,6 +18,7 @@
"SendOnFarmingFinished": false,
"SteamTradeToken": null,
"SendTradePeriod": 0,
"CustomGamePlayedWhileIdle": null,
"GamesPlayedWhileIdle": [
0
],

BIN
tools/NetHook2.dll Normal file

Binary file not shown.

BIN
tools/NetHookAnalyzer2.exe Normal file

Binary file not shown.

1
tools/hook.bat Normal file
View File

@@ -0,0 +1 @@
rundll32 NetHook2.dll,Inject

1
tools/unhook.bat Normal file
View File

@@ -0,0 +1 @@
rundll32 NetHook2.dll,Eject