fix(all): stabilize login and proxy-aware game startup

This commit is contained in:
2026-10-04 01:21:58 +08:00
parent 850c95d511
commit e4ce37bd8e
11 changed files with 295 additions and 12 deletions
+89
View File
@@ -0,0 +1,89 @@
package app
import (
"net"
"net/url"
"strings"
)
// Unity reads these variables before managed plugins can run.
func gameProxyEnvironment(environment []string, systemProxy string) []string {
result := append([]string(nil), environment...)
lookup := func(name string) (int, string) {
for index, entry := range result {
key, value, found := strings.Cut(entry, "=")
if found && strings.EqualFold(key, name) {
return index, value
}
}
return -1, ""
}
if index, _ := lookup("UNITY_PROXYSERVER"); index < 0 && validUnityProxy(systemProxy) {
result = append(result, "UNITY_PROXYSERVER="+systemProxy)
}
index, bypass := lookup("UNITY_NOPROXY")
parts := strings.FieldsFunc(bypass, func(character rune) bool { return character == ',' || character == ';' })
for _, local := range []string{"localhost", "127.0.0.1", "::1"} {
present := false
for _, part := range parts {
if strings.EqualFold(strings.TrimSpace(part), local) {
present = true
}
}
if !present {
parts = append(parts, local)
}
}
entry := "UNITY_NOPROXY=" + strings.Join(parts, ",")
if index >= 0 {
result[index] = entry
} else {
result = append(result, entry)
}
return result
}
func validUnityProxy(proxy string) bool {
parsed, err := url.Parse(proxy)
if err != nil || parsed.Scheme != "http" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" || parsed.Path != "" {
return false
}
host, port, err := net.SplitHostPort(parsed.Host)
if err != nil || host == "" || port == "" {
return false
}
_, err = net.LookupPort("tcp", port)
return err == nil
}
// Unity accepts one proxy, so per-scheme configurations must agree.
func sharedWindowsProxy(raw string) string {
if !strings.Contains(raw, "=") {
proxy := "http://" + strings.TrimSpace(raw)
if validUnityProxy(proxy) {
return proxy
}
return ""
}
var httpProxy, httpsProxy string
for _, entry := range strings.Split(raw, ";") {
key, value, found := strings.Cut(strings.TrimSpace(entry), "=")
if !found {
return ""
}
switch strings.ToLower(key) {
case "http":
httpProxy = value
case "https":
httpsProxy = value
}
}
if httpProxy == "" || !strings.EqualFold(httpProxy, httpsProxy) {
return ""
}
proxy := "http://" + httpProxy
if validUnityProxy(proxy) {
return proxy
}
return ""
}
@@ -0,0 +1,38 @@
//go:build darwin
package app
import (
"net"
"os/exec"
"strconv"
"strings"
)
func systemGameProxy() string {
output, err := exec.Command("/usr/sbin/scutil", "--proxy").Output()
if err != nil {
return ""
}
values := map[string]string{}
for _, line := range strings.Split(string(output), "\n") {
key, value, found := strings.Cut(strings.TrimSpace(line), " : ")
if found {
values[key] = strings.TrimSpace(value)
}
}
if values["ProxyAutoConfigEnable"] == "1" || values["ProxyAutoDiscoveryEnable"] == "1" ||
values["HTTPEnable"] != "1" || values["HTTPSEnable"] != "1" ||
values["HTTPProxy"] != values["HTTPSProxy"] || values["HTTPPort"] != values["HTTPSPort"] {
return ""
}
port, err := strconv.Atoi(values["HTTPPort"])
if err != nil || port < 1 || port > 65535 {
return ""
}
proxy := "http://" + net.JoinHostPort(values["HTTPProxy"], strconv.Itoa(port))
if validUnityProxy(proxy) {
return proxy
}
return ""
}
+44
View File
@@ -0,0 +1,44 @@
package app
import (
"reflect"
"strings"
"testing"
)
func TestGameProxyEnvironmentPreservesExplicitAndIsIdempotent(t *testing.T) {
input := []string{"PATH=kept", "UNITY_PROXYSERVER=http://explicit:8080", "UNITY_NOPROXY=example.org;localhost"}
got := gameProxyEnvironment(input, "http://system:8888")
if !reflect.DeepEqual(got, gameProxyEnvironment(got, "http://other:9999")) {
t.Fatal("environment is not idempotent")
}
if got[1] != input[1] || input[2] != "UNITY_NOPROXY=example.org;localhost" {
t.Fatal("explicit environment changed or input mutated")
}
if !strings.Contains(got[2], "127.0.0.1") || !strings.Contains(got[2], "::1") {
t.Fatal("loopback bypass missing")
}
}
func TestSharedWindowsProxy(t *testing.T) {
for _, test := range []struct{ input, want string }{
{"127.0.0.1:12451", "http://127.0.0.1:12451"},
{"http=proxy:8080;https=proxy:8080", "http://proxy:8080"},
{"http=proxy:8080;https=other:8080", ""},
{"https=proxy:8080", ""}, {"user:password@proxy:8080", ""}, {"proxy:99999", ""},
} {
if got := sharedWindowsProxy(test.input); got != test.want {
t.Errorf("proxy configuration result mismatch")
}
}
}
func TestGameProxyEnvironmentIgnoresInvalidSystemProxy(t *testing.T) {
for _, proxy := range []string{"", "https://proxy:443", "http://user:password@proxy:8080", "http://proxy:8080/path"} {
for _, entry := range gameProxyEnvironment(nil, proxy) {
if strings.HasPrefix(entry, "UNITY_PROXYSERVER=") {
t.Fatal("invalid system proxy accepted")
}
}
}
}
@@ -0,0 +1,30 @@
//go:build windows
package app
import (
"golang.org/x/sys/windows"
"syscall"
"unsafe"
)
func systemGameProxy() string {
var config struct {
AutoDetect int32
AutoConfigURL, Proxy, Bypass *uint16
}
procedure := syscall.NewLazyDLL("winhttp.dll").NewProc("WinHttpGetIEProxyConfigForCurrentUser")
globalFree := syscall.NewLazyDLL("kernel32.dll").NewProc("GlobalFree")
result, _, _ := procedure.Call(uintptr(unsafe.Pointer(&config)))
defer func() {
for _, pointer := range []*uint16{config.AutoConfigURL, config.Proxy, config.Bypass} {
if pointer != nil {
globalFree.Call(uintptr(unsafe.Pointer(pointer)))
}
}
}()
if result == 0 || config.AutoDetect != 0 || config.AutoConfigURL != nil || config.Proxy == nil {
return ""
}
return sharedWindowsProxy(windows.UTF16PtrToString(config.Proxy))
}
+11 -1
View File
@@ -25,6 +25,16 @@ func launchGame(target string) error {
if err := exec.Command("pgrep", "-x", "BrownDust II").Run(); err == nil { if err := exec.Command("pgrep", "-x", "BrownDust II").Run(); err == nil {
return errGameAlreadyRunning return errGameAlreadyRunning
} }
args := append([]string{target, "--args"}, gameLaunchArguments()...) // LaunchServices does not inherit open's environment; --env explicitly
// supplies the Unity variables to the newly launched application.
args := []string{target}
for _, entry := range gameProxyEnvironment(os.Environ(), systemGameProxy()) {
key, _, found := strings.Cut(entry, "=")
if found && (strings.EqualFold(key, "UNITY_PROXYSERVER") || strings.EqualFold(key, "UNITY_NOPROXY")) {
args = append(args, "--env", entry)
}
}
args = append(args, "--args")
args = append(args, gameLaunchArguments()...)
return exec.Command("open", args...).Start() return exec.Command("open", args...).Start()
} }
@@ -4,6 +4,7 @@ package app
import ( import (
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -79,6 +80,7 @@ func launchGame(target string) error {
} }
command := visibleCommand(target, gameLaunchArguments()...) command := visibleCommand(target, gameLaunchArguments()...)
command.Dir = filepath.Dir(target) command.Dir = filepath.Dir(target)
command.Env = gameProxyEnvironment(os.Environ(), systemGameProxy())
if err := command.Start(); err != nil { if err := command.Start(); err != nil {
return err return err
} }
+32 -8
View File
@@ -228,6 +228,9 @@ internal static class LoginController
private static void ShowLoginPanel(object introUI) private static void ShowLoginPanel(object introUI)
{ {
// The official manual-login branch cancels the 10-second startup watchdog.
// Browser authentication waits for the user and must not retain that timer.
CancelMaintenanceTimeout.Invoke(introUI, null);
AccessTokens.Clear(); AccessTokens.Clear();
LoginInProgress = false; LoginInProgress = false;
EstablishedGameSession = false; EstablishedGameSession = false;
@@ -269,12 +272,15 @@ internal static class LoginController
private static IEnumerator DeviceLogin(object introUI, string provider) private static IEnumerator DeviceLogin(object introUI, string provider)
{ {
string endpoint = new Uri(ServerRoot, "auth/device").AbsoluteUri; int generation = Volatile.Read(ref RecoveryGeneration);
Uri origin = ServerRoot;
string endpoint = new Uri(origin, "auth/device").AbsoluteUri;
byte[] body = Encoding.UTF8.GetBytes(JsonUtility.ToJson(new DeviceRequest { provider = provider })); byte[] body = Encoding.UTF8.GetBytes(JsonUtility.ToJson(new DeviceRequest { provider = provider }));
{ {
ControlProbeResult request = null; ControlProbeResult request = null;
yield return AuthRequest(introUI, new Uri(endpoint), UnityWebRequest.kHttpVerbPOST, body, null, result => request = result); yield return AuthRequest(introUI, new Uri(endpoint), UnityWebRequest.kHttpVerbPOST, body, null, result => request = result);
if (request == null) yield break; if (request == null) yield break;
if (!IsCurrentLogin(generation, origin)) { request.Body = null; yield break; }
if (!request.Success) if (!request.Success)
{ {
LoginInProgress = false; LoginInProgress = false;
@@ -299,24 +305,24 @@ internal static class LoginController
yield break; yield break;
} }
Application.OpenURL(start.start_url); Application.OpenURL(start.start_url);
yield return PollDevice(introUI, start); yield return PollDevice(introUI, start, generation, origin);
} }
} }
private static IEnumerator PollDevice(object introUI, DeviceStart start) private static IEnumerator PollDevice(object introUI, DeviceStart start, int generation, Uri origin)
{ {
int generation = Volatile.Read(ref RecoveryGeneration);
int delay = Math.Max(1, start.poll_interval); int delay = Math.Max(1, start.poll_interval);
float deadline = Time.realtimeSinceStartup + Math.Max(30, start.expires_in); float deadline = Time.realtimeSinceStartup + Math.Max(30, start.expires_in);
string endpoint = new Uri(ServerRoot, "auth/device/" + Uri.EscapeDataString(start.transaction_id) + "/poll").AbsoluteUri; string endpoint = new Uri(origin, "auth/device/" + Uri.EscapeDataString(start.transaction_id) + "/poll").AbsoluteUri;
while (Time.realtimeSinceStartup < deadline) while (Time.realtimeSinceStartup < deadline)
{ {
yield return new WaitForSecondsRealtime(delay); yield return new WaitForSecondsRealtime(delay);
if (generation != Volatile.Read(ref RecoveryGeneration)) yield break; if (!IsCurrentLogin(generation, origin)) { start.device_secret = null; yield break; }
{ {
ControlProbeResult request = null; ControlProbeResult request = null;
yield return AuthRequest(introUI, new Uri(endpoint), UnityWebRequest.kHttpVerbPOST, Array.Empty<byte>(), "Device " + start.device_secret, result => request = result); yield return AuthRequest(introUI, new Uri(endpoint), UnityWebRequest.kHttpVerbPOST, Array.Empty<byte>(), "Device " + start.device_secret, result => request = result);
if (request == null) yield break; if (request == null) yield break;
if (!IsCurrentLogin(generation, origin)) { request.Body = null; start.device_secret = null; yield break; }
if (request.StatusCode == 202) if (request.StatusCode == 202)
{ {
continue; continue;
@@ -345,7 +351,7 @@ internal static class LoginController
Log?.LogError("Login transaction returned incomplete credentials"); Log?.LogError("Login transaction returned incomplete credentials");
yield break; yield break;
} }
CompleteInteractiveLogin(introUI, result); CompleteInteractiveLogin(introUI, result, generation, origin);
yield break; yield break;
} }
} }
@@ -353,8 +359,17 @@ internal static class LoginController
Log?.LogError("Login transaction expired"); Log?.LogError("Login transaction expired");
} }
private static void CompleteInteractiveLogin(object introUI, TokenResult result) private static bool IsCurrentLogin(int generation, Uri origin) =>
generation == Volatile.Read(ref RecoveryGeneration) && origin != null && ServerRoot != null && SameOrigin(origin, ServerRoot);
private static void CompleteInteractiveLogin(object introUI, TokenResult result, int generation, Uri origin)
{ {
if (!IsCurrentLogin(generation, origin))
{
result.access_token = null;
result.refresh_token = null;
return;
}
if (!RefreshCredentials.IsSupported) if (!RefreshCredentials.IsSupported)
{ {
AccessTokens.Set(result.access_token, NormalizedServerOrigin(), result.provider, result.access_expires_in); AccessTokens.Set(result.access_token, NormalizedServerOrigin(), result.provider, result.access_expires_in);
@@ -370,6 +385,12 @@ internal static class LoginController
} }
Action confirmed = delegate Action confirmed = delegate
{ {
if (!IsCurrentLogin(generation, origin))
{
result.access_token = null;
result.refresh_token = null;
return;
}
try try
{ {
bool autoLogin = PlayerPrefs.GetInt("StandaloneAutoLogin", 0) != 0; bool autoLogin = PlayerPrefs.GetInt("StandaloneAutoLogin", 0) != 0;
@@ -483,6 +504,8 @@ internal static class LoginController
bool credentialRejected = (request.StatusCode == 401 || request.StatusCode == 409) && request.RefreshInvalid; bool credentialRejected = (request.StatusCode == 401 || request.StatusCode == 409) && request.RefreshInvalid;
if (credentialRejected) if (credentialRejected)
{ {
Log?.LogWarning("Automatic-login credential rejected: HTTP=" + request.StatusCode +
", refreshInvalid=" + request.RefreshInvalid + ", classification=invalid-credential");
refreshToken = null; refreshToken = null;
attemptID = null; attemptID = null;
ClearSavedLogin(); ClearSavedLogin();
@@ -576,6 +599,7 @@ internal static class LoginController
try try
{ {
ContinueMaintenance = true; ContinueMaintenance = true;
Log?.LogInfo("Authenticated login is requesting maintenance information: automatic=" + automatic);
SendMaintenance.Invoke(introUI, new object[] { automatic }); SendMaintenance.Invoke(introUI, new object[] { automatic });
} }
finally finally
+1
View File
@@ -14,6 +14,7 @@ internal static class LoginRuntime
internal static ManualLogSource Log; internal static ManualLogSource Log;
internal static MethodInfo SetIntroState; internal static MethodInfo SetIntroState;
internal static MethodInfo SendMaintenance; internal static MethodInfo SendMaintenance;
internal static MethodInfo CancelMaintenanceTimeout;
internal static Uri ServerRoot; internal static Uri ServerRoot;
internal static MethodInfo OpenPCLoginPopup; internal static MethodInfo OpenPCLoginPopup;
internal static MethodInfo EnterGame; internal static MethodInfo EnterGame;
+6 -1
View File
@@ -31,6 +31,9 @@ public sealed class Plugin : BaseUnityPlugin
try try
{ {
Log = Logger; Log = Logger;
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("UNITY_PROXYSERVER")) &&
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("UNITY_NOPROXY")))
Logger.LogInfo("Game loopback transport uses launch-time Unity proxy bypass");
AppDomain.CurrentDomain.ProcessExit += (_, _) => DisposeGameRelay(); AppDomain.CurrentDomain.ProcessExit += (_, _) => DisposeGameRelay();
Game.Validate(typeof(Plugin).Assembly, Bd2Build.Versions.Game, message => Logger.LogInfo(message)); Game.Validate(typeof(Plugin).Assembly, Bd2Build.Versions.Game, message => Logger.LogInfo(message));
EnsureRecoveryHost(); EnsureRecoveryHost();
@@ -49,6 +52,8 @@ public sealed class Plugin : BaseUnityPlugin
throw new MissingMethodException("IntroUI.Awake() was not found (client version mismatch)"); throw new MissingMethodException("IntroUI.Awake() was not found (client version mismatch)");
} }
SendMaintenance = Game.Method<IntroUI>(ui => ui.SendMaintenanceInfo(false)); SendMaintenance = Game.Method<IntroUI>(ui => ui.SendMaintenanceInfo(false));
CancelMaintenanceTimeout = introUI.GetGameMethod(
"CancelMaintenanceTimeout", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
SetIntroState = introUI.GetGameMethod("SetIntroState", BindingFlags.Instance | BindingFlags.NonPublic); SetIntroState = introUI.GetGameMethod("SetIntroState", BindingFlags.Instance | BindingFlags.NonPublic);
EnterGame = introUI.GetGameMethod( EnterGame = introUI.GetGameMethod(
"Enter", "Enter",
@@ -72,7 +77,7 @@ public sealed class Plugin : BaseUnityPlugin
MethodInfo exponentialBackOff = networkManager?.GetGameMethod( MethodInfo exponentialBackOff = networkManager?.GetGameMethod(
"ExponetialBackOff", "ExponetialBackOff",
BindingFlags.Instance | BindingFlags.NonPublic); BindingFlags.Instance | BindingFlags.NonPublic);
if (SendMaintenance == null || SetIntroState == null || OpenPCLoginPopup == null || if (SendMaintenance == null || CancelMaintenanceTimeout == null || CancelMaintenanceTimeout.ReturnType != typeof(void) || SetIntroState == null || OpenPCLoginPopup == null ||
accessTokenGetter == null || clearPCLocalData == null || disposeWebRequest == null || accessTokenGetter == null || clearPCLocalData == null || disposeWebRequest == null ||
clientNetworkError == null || exponentialBackOff == null || EnterGame == null) clientNetworkError == null || exponentialBackOff == null || EnterGame == null)
{ {
+6 -1
View File
@@ -78,7 +78,7 @@ internal sealed class SecureGameRelay : IDisposable
{ {
try { using (client) await Serve(client).ConfigureAwait(false); } try { using (client) await Serve(client).ConfigureAwait(false); }
catch (Exception ex) when (ex is IOException || ex is SocketException || ex is OperationCanceledException || ex is InvalidDataException) catch (Exception ex) when (ex is IOException || ex is SocketException || ex is OperationCanceledException || ex is InvalidDataException)
{ /* Close failed transport: the game's existing ConnectionError recovery handles it. */ } { log?.LogWarning("Native game relay connection failed: " + ex.GetType().Name); }
catch (Exception ex) { log?.LogWarning("Native game transport failed: " + ex.GetType().Name); } catch (Exception ex) { log?.LogWarning("Native game transport failed: " + ex.GetType().Name); }
finally { slots.Release(); } finally { slots.Release(); }
}); });
@@ -108,6 +108,8 @@ internal sealed class SecureGameRelay : IDisposable
throw new InvalidDataException("Invalid relay request"); throw new InvalidDataException("Invalid relay request");
if (!Uri.TryCreate(local, start[1], out Uri target) || !TryResolve(target, out Uri remote)) if (!Uri.TryCreate(local, start[1], out Uri target) || !TryResolve(target, out Uri remote))
throw new InvalidDataException("Invalid relay target"); throw new InvalidDataException("Invalid relay target");
bool maintenance = remote.AbsolutePath.Equals("/game/MaintenanceInfo", StringComparison.Ordinal);
if (maintenance) log?.LogInfo("Game relay received MaintenanceInfo request");
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
int length = 0; int length = 0;
bool lengthSeen = false; bool lengthSeen = false;
@@ -139,6 +141,9 @@ internal sealed class SecureGameRelay : IDisposable
} }
response = await PlatformControlHttp.Send(remote, start[0], body, null, 4, lifetime.Token, response = await PlatformControlHttp.Send(remote, start[0], body, null, 4, lifetime.Token,
lifetime.Token, 64 * 1024 * 1024, headers).ConfigureAwait(false); lifetime.Token, 64 * 1024 * 1024, headers).ConfigureAwait(false);
if (maintenance || response.StatusCode == 0)
log?.LogInfo("Game relay upstream result: path=" + remote.AbsolutePath + " status=" + response.StatusCode +
" error=" + (response.Error ?? "none"));
if (response.StatusCode == 0 || response.Data == null) throw new IOException("Native game transport unavailable"); if (response.StatusCode == 0 || response.Data == null) throw new IOException("Native game transport unavailable");
StringBuilder output = new StringBuilder("HTTP/1.1 ").Append(response.StatusCode.ToString(CultureInfo.InvariantCulture)).Append(" Response\r\nConnection: close\r\nCache-Control: no-store\r\n"); StringBuilder output = new StringBuilder("HTTP/1.1 ").Append(response.StatusCode.ToString(CultureInfo.InvariantCulture)).Append(" Response\r\nConnection: close\r\nCache-Control: no-store\r\n");
foreach (KeyValuePair<string, string> item in response.Headers) foreach (KeyValuePair<string, string> item in response.Headers)
+36 -1
View File
@@ -225,6 +225,39 @@ internal static class SessionRecovery
{ {
try try
{ {
try
{
const BindingFlags diagnosticFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
object packet = __0?.GetType().GetGameProperty("PacketData", diagnosticFlags)?.GetValue(__0, null);
string path = packet?.GetType().GetGameProperty("SendPath", diagnosticFlags)?.GetValue(packet, null) as string;
if (path == "MaintenanceInfo")
{
string server = packet?.GetType().GetGameField("RequestServerURL", diagnosticFlags)?.GetValue(packet) as string;
string endpoint = Uri.TryCreate(server, UriKind.Absolute, out Uri parsed) ? parsed.GetLeftPart(UriPartial.Authority) + parsed.AbsolutePath : "invalid";
string message = __0?.GetType().GetGameProperty("Message", diagnosticFlags)?.GetValue(__0, null) as string ?? string.Empty;
string category = message.IndexOf("tim", StringComparison.OrdinalIgnoreCase) >= 0 ? "Timeout" :
message.IndexOf("cert", StringComparison.OrdinalIgnoreCase) >= 0 || message.IndexOf("TLS", StringComparison.OrdinalIgnoreCase) >= 0 || message.IndexOf("SSL", StringComparison.OrdinalIgnoreCase) >= 0 ? "TLS" : "Other";
Log?.LogDebug("Maintenance backoff: endpoint=" + endpoint + ", category=" + category + ", observedRequests=" + GameRequests.Count);
foreach (WeakReference<UnityWebRequest> reference in GameRequests)
{
if (!reference.TryGetTarget(out UnityWebRequest request) || !request.isDone) continue;
if (!TryGetOwnedRequestUri(request.url, out Uri original) || original.AbsolutePath != "/game/MaintenanceInfo") continue;
string error = request.error ?? string.Empty;
string safeError = error.IndexOf("Insecure", StringComparison.OrdinalIgnoreCase) >= 0 ? "InsecureConnectionBlocked" :
error.IndexOf("connect", StringComparison.OrdinalIgnoreCase) >= 0 ? "ConnectionFailure" :
error.IndexOf("tim", StringComparison.OrdinalIgnoreCase) >= 0 ? "Timeout" :
error.IndexOf("HTTP", StringComparison.OrdinalIgnoreCase) >= 0 ? "HttpFailure" :
error.IndexOf("SSL", StringComparison.OrdinalIgnoreCase) >= 0 || error.IndexOf("cert", StringComparison.OrdinalIgnoreCase) >= 0 ? "TlsFailure" : "Unclassified";
Log?.LogWarning("Maintenance completed transport: result=" + request.result + ", status=" + request.responseCode +
", error=" + safeError + ", errorLength=" + error.Length);
}
}
}
catch (Exception ex)
{
Log?.LogDebug("Maintenance transport diagnostic unavailable: " + ex.GetType().Name);
}
if (ServerRoot == null || !IsConfiguredServerFailure(__0) || !IsTransportFailure(__0)) if (ServerRoot == null || !IsConfiguredServerFailure(__0) || !IsTransportFailure(__0))
{ {
return true; return true;
@@ -443,8 +476,10 @@ internal static class SessionRecovery
} }
} }
internal static bool SuppressNetworkErrorDuringRecovery() internal static bool SuppressNetworkErrorDuringRecovery(object __0, object __1)
{ {
Log?.LogWarning("Client network error: type=" + __0 + ", code=" + __1 +
", recovering=" + (Volatile.Read(ref SessionRecoveryInProgress) != 0));
return Volatile.Read(ref SessionRecoveryInProgress) == 0; return Volatile.Read(ref SessionRecoveryInProgress) == 0;
} }