fix: launch game fullscreen and hide debug overlay
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package app
|
||||
|
||||
// gameLaunchArguments asks Unity for borderless fullscreen on both supported
|
||||
// desktop platforms. The LocalIdentity plug-in applies the game's native
|
||||
// fullscreen routine during initialization as a second line of defence.
|
||||
func gameLaunchArguments() []string {
|
||||
return []string{"-screen-fullscreen", "1", "-window-mode", "borderless"}
|
||||
}
|
||||
@@ -25,5 +25,6 @@ func launchGame(target string) error {
|
||||
if err := exec.Command("pgrep", "-x", "BrownDust II").Run(); err == nil {
|
||||
return errGameAlreadyRunning
|
||||
}
|
||||
return exec.Command("open", target).Start()
|
||||
args := append([]string{target, "--args"}, gameLaunchArguments()...)
|
||||
return exec.Command("open", args...).Start()
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ var (
|
||||
enumWindowsProc = platformUser32DLL.NewProc("EnumWindows")
|
||||
getWindowThreadProcessIDProc = platformUser32DLL.NewProc("GetWindowThreadProcessId")
|
||||
isWindowVisibleProc = platformUser32DLL.NewProc("IsWindowVisible")
|
||||
isIconicProc = platformUser32DLL.NewProc("IsIconic")
|
||||
showWindowAsyncProc = platformUser32DLL.NewProc("ShowWindowAsync")
|
||||
setForegroundWindowProc = platformUser32DLL.NewProc("SetForegroundWindow")
|
||||
)
|
||||
@@ -76,7 +77,7 @@ func launchGame(target string) error {
|
||||
}
|
||||
return errGameAlreadyRunning
|
||||
}
|
||||
command := visibleCommand(target)
|
||||
command := visibleCommand(target, gameLaunchArguments()...)
|
||||
command.Dir = filepath.Dir(target)
|
||||
if err := command.Start(); err != nil {
|
||||
return err
|
||||
@@ -114,8 +115,11 @@ func activateProcessWindow(processID uint32, timeout time.Duration) bool {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
if window := topLevelWindowForProcess(processID); window != 0 {
|
||||
const swRestore = 9
|
||||
showWindowAsyncProc.Call(window, swRestore)
|
||||
iconic, _, _ := isIconicProc.Call(window)
|
||||
if iconic != 0 {
|
||||
const swRestore = 9
|
||||
showWindowAsyncProc.Call(window, swRestore)
|
||||
}
|
||||
setForegroundWindowProc.Call(window)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package app
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHiddenCommandNeverAllocatesVisibleConsole(t *testing.T) {
|
||||
command := hiddenCommand("powershell.exe", "-NoProfile")
|
||||
@@ -18,7 +21,7 @@ func TestHiddenCommandNeverAllocatesVisibleConsole(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVisibleCommandDoesNotHideGUIWindow(t *testing.T) {
|
||||
command := visibleCommand("Brown Dust II.exe")
|
||||
command := visibleCommand("Brown Dust II.exe", gameLaunchArguments()...)
|
||||
if command.SysProcAttr == nil {
|
||||
t.Fatal("visible command has no Windows process attributes")
|
||||
}
|
||||
@@ -28,4 +31,12 @@ func TestVisibleCommandDoesNotHideGUIWindow(t *testing.T) {
|
||||
if command.SysProcAttr.CreationFlags&createNoWindow == 0 {
|
||||
t.Fatalf("visible command creation flags %#x omit CREATE_NO_WINDOW", command.SysProcAttr.CreationFlags)
|
||||
}
|
||||
want := []string{
|
||||
"Brown Dust II.exe",
|
||||
"-screen-fullscreen", "1",
|
||||
"-window-mode", "borderless",
|
||||
}
|
||||
if !reflect.DeepEqual(command.Args, want) {
|
||||
t.Fatalf("visible game command args = %#v, want %#v", command.Args, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed class Plugin : BaseUnityPlugin
|
||||
public const string Version = Bd2Build.Versions.Plugin;
|
||||
private static ManualLogSource Log;
|
||||
private static ClientRouting Routing;
|
||||
private static MethodInfo SwitchToFullScreenMethod;
|
||||
private static int ShutdownHooksInstalled;
|
||||
|
||||
internal static void LogWarning(string message)
|
||||
@@ -59,6 +60,8 @@ public sealed class Plugin : BaseUnityPlugin
|
||||
|
||||
Type introUI = FindType("IntroUI");
|
||||
InstallClientRouting(harmony, introUI);
|
||||
TryInstall("startup fullscreen", () => InstallStartupFullscreen(harmony, appManager));
|
||||
TryInstall("performance overlay suppression", () => InstallPerformanceOverlaySuppression(harmony));
|
||||
TryInstall("maintenance timeout guard", () => InstallMaintenanceTimeoutGuard(harmony, introUI));
|
||||
TryInstall("age-gate persistence", () => InstallAgeGatePersistence(harmony));
|
||||
TryInstall("local purchase bypass", () => InstallLocalPurchaseBypass(harmony));
|
||||
@@ -100,6 +103,135 @@ public sealed class Plugin : BaseUnityPlugin
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void InstallStartupFullscreen(Harmony harmony, Type appManager)
|
||||
{
|
||||
MethodInfo initializeResolution = appManager?.GetMethod(
|
||||
"ὪὣὬὤὪὥὣὨὤὦὧ",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic,
|
||||
null,
|
||||
Type.EmptyTypes,
|
||||
null);
|
||||
SwitchToFullScreenMethod = appManager?.GetMethod(
|
||||
"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(Plugin), 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);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InstallPerformanceOverlaySuppression(Harmony harmony)
|
||||
{
|
||||
Type fpsCheck = FindType("FPS_Check");
|
||||
MethodInfo start = fpsCheck?.GetMethod(
|
||||
"StartFPS",
|
||||
BindingFlags.Instance | BindingFlags.Public,
|
||||
null,
|
||||
Type.EmptyTypes,
|
||||
null);
|
||||
MethodInfo render = fpsCheck?.GetMethod(
|
||||
"ὬὡὡὬὢὭὦὣὥὬὯ",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic,
|
||||
null,
|
||||
Type.EmptyTypes,
|
||||
null);
|
||||
MethodInfo onEnable = fpsCheck?.GetMethod(
|
||||
"OnEnable",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic,
|
||||
null,
|
||||
Type.EmptyTypes,
|
||||
null);
|
||||
MethodInfo onInitialize = fpsCheck?.GetMethod(
|
||||
"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(Plugin), nameof(SkipPerformanceOverlay));
|
||||
HarmonyMethod hide = new HarmonyMethod(typeof(Plugin), 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().GetMethod(
|
||||
"DisposeRecorders",
|
||||
BindingFlags.Instance | BindingFlags.Public,
|
||||
null,
|
||||
Type.EmptyTypes,
|
||||
null);
|
||||
disposeRecorders?.Invoke(instance, null);
|
||||
|
||||
FieldInfo textField = instance.GetType().GetField(
|
||||
"_text",
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
object text = textField?.GetValue(instance);
|
||||
PropertyInfo textProperty = text?.GetType().GetProperty(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InstallClientRouting(Harmony harmony, Type introUI)
|
||||
{
|
||||
Type serverURLInfo = FindType("ὫὡὩὤὣὨὯὭὥὠὩ") ?? FindType("BDNetwork.ServerURLInfo");
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"bundle_version": "20260921135230",
|
||||
"seed_directory": "go/seed/v2_35_10",
|
||||
"plugins": {
|
||||
"local_identity": "0.6.0",
|
||||
"local_identity": "0.6.1",
|
||||
"capture_environment": "0.2.0",
|
||||
"login_ui": "0.1.4"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user