Files
bd2/plugins/LoginUI/ControlRequests.cs
T
Flechazo 5d86657c75 feat: add game SDK and harden client login and game rules
- Add costume burst upgrades with durable replay and fix permanent SR equipment gacha.
- Add server game.json rules for optional collaboration UR weapons, with packaging and documentation.
- Add GameNames/GameSdk NuGet tooling, readable types, reobfuscation, complete source/PDB navigation and shared-runtime installation.
- Split plugin responsibilities, prefer typeof/nameof, centralize MSBuild settings and ignore local paths; remove the unused LoginUI test project.
- Fix login recovery and TLS through native platform HTTP and a private game relay; read device region directly from the OS time zone.

Validation: go test ./... -count=1, go vet ./..., plugin Release builds, SDK/runtime self-tests, Roslyn embedded-source navigation, gofmt check and git diff --check.
2026-10-03 23:04:32 +08:00

134 lines
5.4 KiB
C#

using System;
using System.Collections;
using System.Threading;
using UnityEngine;
using static Bd2LoginUI.LoginRuntime;
using static Bd2LoginUI.SessionRecovery;
namespace Bd2LoginUI;
// Runs native HTTP work and delivers results to Unity coroutines with owner cancellation.
internal static class ControlRequests
{
internal sealed class ControlProbeResult
{
public bool Success;
public string Body;
public string Error;
public int StatusCode;
public bool RefreshInvalid;
}
// Native HTTP avoids UnityTls for owned auth/control endpoints. The coroutine
// applies completed results on the Unity thread and rejects cancelled generations.
internal static IEnumerator RequestControlEndpoint(Uri uri, string method, byte[] body,
string authorization, int timeoutSeconds, CancellationToken lifetime, CancellationToken generation,
Action<ControlProbeResult> completed)
{
try { ValidateOAuthTransport(uri); }
catch
{
if (body != null) Array.Clear(body, 0, body.Length);
throw;
}
using (CancellationTokenSource cancel = CancellationTokenSource.CreateLinkedTokenSource(lifetime, generation))
{
byte[] payload = body == null ? null : (byte[])body.Clone();
if (body != null) Array.Clear(body, 0, body.Length);
System.Threading.Tasks.Task<PlatformControlHttp.Response> task = null;
bool responseConsumed = false;
try
{
task = PlatformControlHttp.Send(uri, method, payload, authorization, timeoutSeconds, cancel.Token, generation);
// Native work may finish after its Unity owner is destroyed. Observe it
// and clear the upload clone only when transport no longer reads it.
task.ContinueWith(finished =>
{
if (payload != null) Array.Clear(payload, 0, payload.Length);
if (finished.IsFaulted) { _ = finished.Exception; }
}, System.Threading.Tasks.TaskScheduler.Default);
while (!task.IsCompleted && !cancel.IsCancellationRequested)
{
yield return null;
}
if (cancel.IsCancellationRequested) yield break;
if (task.IsFaulted || task.IsCanceled)
{
completed(new ControlProbeResult { Error = task.IsCanceled ? "Cancelled" : "NativeTransportFailure" });
yield break;
}
PlatformControlHttp.Response result = task.GetAwaiter().GetResult();
try
{
completed(new ControlProbeResult
{
Success = result.Success,
StatusCode = result.StatusCode,
Body = result.Body,
RefreshInvalid = result.RefreshInvalid,
Error = result.Error
});
}
finally
{
ClearNativeResponse(result);
responseConsumed = true;
}
}
finally
{
cancel.Cancel();
if (task == null && payload != null) Array.Clear(payload, 0, payload.Length);
if (task != null && !responseConsumed)
{
task.ContinueWith(finished =>
{
if (finished.Status == System.Threading.Tasks.TaskStatus.RanToCompletion) ClearNativeResponse(finished.Result);
if (finished.IsFaulted) { _ = finished.Exception; }
}, System.Threading.Tasks.TaskScheduler.Default);
}
}
}
}
private static void ClearNativeResponse(PlatformControlHttp.Response response)
{
if (response == null) return;
if (response.Data != null) Array.Clear(response.Data, 0, response.Data.Length);
response.Data = null;
response.Body = null;
}
public sealed class AuthenticationRequestLifetime : MonoBehaviour
{
internal readonly CancellationTokenSource Lifetime = new CancellationTokenSource();
private void OnDestroy()
{
Lifetime.Cancel();
Lifetime.Dispose();
}
}
internal static IEnumerator AuthRequest(object introUI, Uri uri, string method, byte[] body,
string authorization, Action<ControlProbeResult> completed)
{
MonoBehaviour intro = introUI as MonoBehaviour;
if (intro == null)
{
if (body != null) Array.Clear(body, 0, body.Length);
yield break;
}
AuthenticationRequestLifetime owner = intro.GetComponent<AuthenticationRequestLifetime>() ??
intro.gameObject.AddComponent<AuthenticationRequestLifetime>();
int expectedGeneration = Volatile.Read(ref RecoveryGeneration);
CancellationToken lifetime = owner.Lifetime.Token;
ControlProbeResult result = null;
yield return RequestControlEndpoint(uri, method, body, authorization, 15, lifetime, ControlProbeCancellation.Token,
response => result = response);
if (result != null && intro != null && !lifetime.IsCancellationRequested &&
expectedGeneration == Volatile.Read(ref RecoveryGeneration)) completed(result);
}
}