Files
bd2/plugins/LocalIdentity/ClientPresentation.cs
T
Flechazo 992fa47fd5 feat(all): 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

148 lines
5.1 KiB
C#

using System;
using BD2.GameNames;
using static BD2.GameNames.Game;
using System.Reflection;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
namespace Bd2LocalIdentity;
internal static class ClientPresentation
{
private static ManualLogSource Log;
private static MethodInfo SwitchToFullScreenMethod;
internal static void Initialize(ManualLogSource logger) => Log = logger;
internal static void InstallStartupFullscreen(Harmony harmony, Type appManager)
{
MethodInfo initializeResolution = appManager?.GetGameMethod(
"InitPCResolution",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
Type.EmptyTypes,
null);
SwitchToFullScreenMethod = appManager?.GetGameMethod(
nameof(AppManager.SwitchToFullScreen),
BindingFlags.Instance | BindingFlags.Public,
null,
new[] { typeof(bool) },
null);
if (initializeResolution == null || SwitchToFullScreenMethod == null)
{
throw new MissingMethodException("AppManager fullscreen methods were not found (client version mismatch)");
}
harmony.Patch(
initializeResolution,
postfix: new HarmonyMethod(typeof(ClientPresentation), nameof(InitializeResolutionPostfix)));
Log?.LogInfo("Native startup fullscreen patch installed");
}
private static void InitializeResolutionPostfix(object __instance)
{
try
{
// Use the game's own FullScreenWindow path so the behaviour is
// identical on Windows and macOS. This runs once during startup;
// later user-initiated switches to windowed mode remain intact.
SwitchToFullScreenMethod?.Invoke(__instance, new object[] { false });
}
catch (Exception ex)
{
Log?.LogError("Could not apply native startup fullscreen: " + ex);
}
}
internal static void InstallPerformanceOverlaySuppression(Harmony harmony)
{
Type fpsCheck = typeof(FPS_Check);
MethodInfo start = fpsCheck?.GetGameMethod(
nameof(FPS_Check.StartFPS),
BindingFlags.Instance | BindingFlags.Public,
null,
Type.EmptyTypes,
null);
MethodInfo render = fpsCheck?.GetGameMethod(
"TextRender",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
Type.EmptyTypes,
null);
MethodInfo onEnable = fpsCheck?.GetGameMethod(
"OnEnable",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
Type.EmptyTypes,
null);
MethodInfo onInitialize = fpsCheck?.GetGameMethod(
nameof(FPS_Check.OnInitialize),
BindingFlags.Instance | BindingFlags.Public,
null,
Type.EmptyTypes,
null);
if (start == null || render == null || onEnable == null || onInitialize == null)
{
throw new MissingMethodException("FPS_Check overlay methods were not found (client version mismatch)");
}
HarmonyMethod skip = new HarmonyMethod(typeof(ClientPresentation), nameof(SkipPerformanceOverlay));
HarmonyMethod hide = new HarmonyMethod(typeof(ClientPresentation), nameof(HidePerformanceOverlayPostfix));
harmony.Patch(start, prefix: skip);
harmony.Patch(render, prefix: skip);
harmony.Patch(onEnable, postfix: hide);
harmony.Patch(onInitialize, postfix: hide);
Log?.LogInfo("FPS_Check performance overlay disabled");
}
private static bool SkipPerformanceOverlay(object __instance)
{
HidePerformanceOverlay(__instance);
return false;
}
private static void HidePerformanceOverlayPostfix(object __instance)
{
HidePerformanceOverlay(__instance);
}
private static void HidePerformanceOverlay(object instance)
{
if (instance == null)
{
return;
}
try
{
MethodInfo disposeRecorders = instance.GetType().GetGameMethod(
nameof(FPS_Check.DisposeRecorders),
BindingFlags.Instance | BindingFlags.Public,
null,
Type.EmptyTypes,
null);
disposeRecorders?.Invoke(instance, null);
FieldInfo textField = instance.GetType().GetGameField(
"_text",
BindingFlags.Instance | BindingFlags.Public);
object text = textField?.GetValue(instance);
PropertyInfo textProperty = text?.GetType().GetGameProperty(
"text",
BindingFlags.Instance | BindingFlags.Public);
if (textProperty?.CanWrite == true)
{
textProperty.SetValue(text, string.Empty);
}
if (text is Component component && component != null)
{
component.gameObject.SetActive(false);
}
}
catch (Exception ex)
{
Log?.LogError("Could not suppress FPS_Check overlay: " + ex);
}
}
}