Add workaround for Steam profile bug

This is the most ridiculous code I've ever written, God forgive me.
This commit is contained in:
JustArchi
2018-05-12 20:21:52 +02:00
parent ad59ed3e4a
commit 8b44abb31f
3 changed files with 145 additions and 58 deletions

View File

@@ -1306,6 +1306,15 @@ namespace ArchiSteamFarm {
} }
} }
private bool IsProfileUri(Uri uri) {
if (uri == null) {
ASF.ArchiLogger.LogNullError(nameof(uri));
return false;
}
return uri.AbsolutePath.Equals(GetAbsoluteProfileURL());
}
private static bool IsSessionExpiredUri(Uri uri) { private static bool IsSessionExpiredUri(Uri uri) {
if (uri == null) { if (uri == null) {
ASF.ArchiLogger.LogNullError(nameof(uri)); ASF.ArchiLogger.LogNullError(nameof(uri));
@@ -1432,19 +1441,36 @@ namespace ArchiSteamFarm {
return true; return true;
} }
private async Task<bool> UnlockParentalAccountForService(string serviceURL, string parentalPin) { private async Task<bool> UnlockParentalAccountForService(string serviceURL, string parentalPin, byte maxTries = WebBrowser.MaxTries) {
if (string.IsNullOrEmpty(serviceURL) || string.IsNullOrEmpty(parentalPin)) { if (string.IsNullOrEmpty(serviceURL) || string.IsNullOrEmpty(parentalPin)) {
Bot.ArchiLogger.LogNullError(nameof(serviceURL) + " || " + nameof(parentalPin)); Bot.ArchiLogger.LogNullError(nameof(serviceURL) + " || " + nameof(parentalPin));
return false; return false;
} }
// This request doesn't go through UrlPostRetryWithSession as we have no access to session refresh capability (this is in fact session initialization)
const string request = "/parental/ajaxunlock"; const string request = "/parental/ajaxunlock";
if (maxTries == 0) {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, serviceURL + request));
return false;
}
Dictionary<string, string> data = new Dictionary<string, string>(1) { { "pin", parentalPin } }; Dictionary<string, string> data = new Dictionary<string, string>(1) { { "pin", parentalPin } };
// This request doesn't go through UrlPostRetryWithSession as we have no access to session refresh capability (this is in fact session initialization)
WebBrowser.BasicResponse response = await WebLimitRequest(serviceURL, async () => await WebBrowser.UrlPost(serviceURL + request, data, serviceURL).ConfigureAwait(false)).ConfigureAwait(false); WebBrowser.BasicResponse response = await WebLimitRequest(serviceURL, async () => await WebBrowser.UrlPost(serviceURL + request, data, serviceURL).ConfigureAwait(false)).ConfigureAwait(false);
return (response != null) && !IsSessionExpiredUri(response.FinalUri); if ((response == null) || IsSessionExpiredUri(response.FinalUri)) {
// There is no session refresh capability at this stage
return false;
}
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UnlockParentalAccountForService(serviceURL, parentalPin, --maxTries).ConfigureAwait(false);
}
return true;
} }
private async Task<HtmlDocument> UrlGetToHtmlDocumentWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { private async Task<HtmlDocument> UrlGetToHtmlDocumentWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) {
@@ -1478,17 +1504,23 @@ namespace ArchiSteamFarm {
return null; return null;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return response.Content; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlGetToHtmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return null; return null;
} }
return await UrlGetToHtmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlGetToHtmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false);
}
return response.Content;
} }
private async Task<T> UrlGetToJsonObjectWithSession<T>(string host, string request, byte maxTries = WebBrowser.MaxTries) where T : class { private async Task<T> UrlGetToJsonObjectWithSession<T>(string host, string request, byte maxTries = WebBrowser.MaxTries) where T : class {
@@ -1522,17 +1554,23 @@ namespace ArchiSteamFarm {
return default; return default;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return response.Content; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlGetToJsonObjectWithSession<T>(host, request, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return default; return null;
} }
return await UrlGetToJsonObjectWithSession<T>(host, request, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlGetToJsonObjectWithSession<T>(host, request, --maxTries).ConfigureAwait(false);
}
return response.Content;
} }
private async Task<XmlDocument> UrlGetToXmlDocumentWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { private async Task<XmlDocument> UrlGetToXmlDocumentWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) {
@@ -1566,17 +1604,23 @@ namespace ArchiSteamFarm {
return null; return null;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return response.Content; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlGetToXmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return null; return null;
} }
return await UrlGetToXmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlGetToXmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false);
}
return response.Content;
} }
private async Task<bool> UrlHeadWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { private async Task<bool> UrlHeadWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) {
@@ -1610,17 +1654,23 @@ namespace ArchiSteamFarm {
return false; return false;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return true; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlHeadWithSession(host, request, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return false; return false;
} }
return await UrlHeadWithSession(host, request, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlHeadWithSession(host, request, --maxTries).ConfigureAwait(false);
}
return true;
} }
private async Task<HtmlDocument> UrlPostToHtmlDocumentWithSession(string host, string request, Dictionary<string, string> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { private async Task<HtmlDocument> UrlPostToHtmlDocumentWithSession(string host, string request, Dictionary<string, string> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) {
@@ -1683,29 +1733,35 @@ namespace ArchiSteamFarm {
return null; return null;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return response.Content; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlPostToHtmlDocumentWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return null; return null;
} }
return await UrlPostToHtmlDocumentWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlPostToHtmlDocumentWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
return response.Content;
} }
private async Task<T> UrlPostToJsonObjectWithSession<T>(string host, string request, Dictionary<string, string> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) where T : class { private async Task<T> UrlPostToJsonObjectWithSession<T>(string host, string request, Dictionary<string, string> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) where T : class {
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) {
Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request));
return default; return null;
} }
if (maxTries == 0) { if (maxTries == 0) {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return default; return null;
} }
// If session refresh is already in progress, wait for it // If session refresh is already in progress, wait for it
@@ -1719,7 +1775,7 @@ namespace ArchiSteamFarm {
if (SteamID == 0) { if (SteamID == 0) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return default; return null;
} }
if (session != ESession.None) { if (session != ESession.None) {
@@ -1727,7 +1783,7 @@ namespace ArchiSteamFarm {
if (string.IsNullOrEmpty(sessionID)) { if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID)); Bot.ArchiLogger.LogNullError(nameof(sessionID));
return default; return null;
} }
string sessionName; string sessionName;
@@ -1741,7 +1797,7 @@ namespace ArchiSteamFarm {
break; break;
default: default:
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(session), session)); Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(session), session));
return default; return null;
} }
if (data != null) { if (data != null) {
@@ -1753,32 +1809,38 @@ namespace ArchiSteamFarm {
WebBrowser.ObjectResponse<T> response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T>(host + request, data, referer).ConfigureAwait(false)).ConfigureAwait(false); WebBrowser.ObjectResponse<T> response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T>(host + request, data, referer).ConfigureAwait(false)).ConfigureAwait(false);
if (response == null) { if (response == null) {
return default; return null;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return response.Content; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlPostToJsonObjectWithSession<T>(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return default; return null;
} }
return await UrlPostToJsonObjectWithSession<T>(host, request, data, referer, session, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlPostToJsonObjectWithSession<T>(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
return response.Content;
} }
private async Task<T> UrlPostToJsonObjectWithSession<T>(string host, string request, List<KeyValuePair<string, string>> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) where T : class { private async Task<T> UrlPostToJsonObjectWithSession<T>(string host, string request, List<KeyValuePair<string, string>> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) where T : class {
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) {
Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request));
return default; return null;
} }
if (maxTries == 0) { if (maxTries == 0) {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return default; return null;
} }
// If session refresh is already in progress, wait for it // If session refresh is already in progress, wait for it
@@ -1792,7 +1854,7 @@ namespace ArchiSteamFarm {
if (SteamID == 0) { if (SteamID == 0) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return default; return null;
} }
if (session != ESession.None) { if (session != ESession.None) {
@@ -1800,7 +1862,7 @@ namespace ArchiSteamFarm {
if (string.IsNullOrEmpty(sessionID)) { if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID)); Bot.ArchiLogger.LogNullError(nameof(sessionID));
return default; return null;
} }
string sessionName; string sessionName;
@@ -1814,7 +1876,7 @@ namespace ArchiSteamFarm {
break; break;
default: default:
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(session), session)); Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(session), session));
return default; return null;
} }
KeyValuePair<string, string> sessionValue = new KeyValuePair<string, string>(sessionName, sessionID); KeyValuePair<string, string> sessionValue = new KeyValuePair<string, string>(sessionName, sessionID);
@@ -1829,20 +1891,26 @@ namespace ArchiSteamFarm {
WebBrowser.ObjectResponse<T> response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T>(host + request, data, referer).ConfigureAwait(false)).ConfigureAwait(false); WebBrowser.ObjectResponse<T> response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject<T>(host + request, data, referer).ConfigureAwait(false)).ConfigureAwait(false);
if (response == null) { if (response == null) {
return default; return null;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return response.Content; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlPostToJsonObjectWithSession<T>(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return default; return null;
} }
return await UrlPostToJsonObjectWithSession<T>(host, request, data, referer, session, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlPostToJsonObjectWithSession<T>(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
return response.Content;
} }
private async Task<bool> UrlPostWithSession(string host, string request, Dictionary<string, string> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { private async Task<bool> UrlPostWithSession(string host, string request, Dictionary<string, string> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) {
@@ -1905,17 +1973,23 @@ namespace ArchiSteamFarm {
return false; return false;
} }
if (!IsSessionExpiredUri(response.FinalUri)) { if (IsSessionExpiredUri(response.FinalUri)) {
return true; if (await RefreshSession(host).ConfigureAwait(false)) {
} return await UrlPostWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
if (!await RefreshSession(host).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request));
return false; return false;
} }
return await UrlPostWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); // Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if (IsProfileUri(response.FinalUri)) {
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.WarningWorkaroundTriggered, nameof(IsProfileUri)));
return await UrlPostWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false);
}
return true;
} }
private static async Task<T> WebLimitRequest<T>(string service, Func<Task<T>> function) { private static async Task<T> WebLimitRequest<T>(string service, Func<Task<T>> function) {

View File

@@ -1467,6 +1467,15 @@ namespace ArchiSteamFarm.Localization {
} }
} }
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Workaround for {0} bug has been triggered..
/// </summary>
internal static string WarningWorkaroundTriggered {
get {
return ResourceManager.GetString("WarningWorkaroundTriggered", resourceCulture);
}
}
/// <summary> /// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu It looks like it&apos;s your first launch of the program, welcome!. /// Wyszukuje zlokalizowany ciąg podobny do ciągu It looks like it&apos;s your first launch of the program, welcome!.
/// </summary> /// </summary>

View File

@@ -665,4 +665,8 @@ StackTrace:
<value>Accepted donation trade: {0}</value> <value>Accepted donation trade: {0}</value>
<comment>{0} will be replaced by trade's ID (number)</comment> <comment>{0} will be replaced by trade's ID (number)</comment>
</data> </data>
<data name="WarningWorkaroundTriggered" xml:space="preserve">
<value>Workaround for {0} bug has been triggered.</value>
<comment>{0} will be replaced by the bug's name provided by ASF</comment>
</data>
</root> </root>