- 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.
148 lines
5.1 KiB
C#
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);
|
|
}
|
|
}
|
|
|
|
|
|
}
|