diff --git a/README.md b/README.md index 797371c..5fc4e6c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# SleepStrap 6.9.3 +# SleepStrap 6.9.4 ![SleepStrap icon](SleepStrap/SleepStrap.png) SleepStrap is a purple Windows launcher for Roblox with skyboxes, texture presets, visual modes, fonts, and Rivals display tools. -[Download SleepStrap 6.9.3](https://github.com/SleepyDebug/SleepStrap/releases/tag/sleepstrap-6.9.3) · [Discord](https://discord.gg/W3CMjx8C7s) · [Report an issue](https://github.com/SleepyDebug/SleepStrap/issues) +[Download SleepStrap 6.9.4](https://github.com/SleepyDebug/SleepStrap/releases/tag/sleepstrap-6.9.4) · [Discord](https://discord.gg/W3CMjx8C7s) · [Report an issue](https://github.com/SleepyDebug/SleepStrap/issues) ## Repository layout diff --git a/SleepStrap/Models/Persistable/Settings.cs b/SleepStrap/Models/Persistable/Settings.cs index 4cce525..440d05a 100644 --- a/SleepStrap/Models/Persistable/Settings.cs +++ b/SleepStrap/Models/Persistable/Settings.cs @@ -95,6 +95,7 @@ public class Settings public string MacroQuickLoadoutUtilityWeapon { get; set; } = "Grappler"; public int MacroQuickLoadoutHotkeyModifiers { get; set; } = 3; // Ctrl + Alt public int MacroQuickLoadoutHotkeyVirtualKey { get; set; } = 0x51; // Q + public bool MacroQuickLoadoutEnabled { get; set; } = true; public bool MacroQuickRespawn { get; set; } = false; public bool MacroAutoUtility { get; set; } = false; public bool MacroAutoInspect { get; set; } = false; diff --git a/SleepStrap/Services/MacroAutomationService.cs b/SleepStrap/Services/MacroAutomationService.cs index 6f090fd..a5d01b2 100644 --- a/SleepStrap/Services/MacroAutomationService.cs +++ b/SleepStrap/Services/MacroAutomationService.cs @@ -22,6 +22,14 @@ public static class MacroAutomationService private const int RecordedScreenTop = 0; private const int RecordedScreenWidth = 1920; private const int RecordedScreenHeight = 1080; + private const int TargetClientWidth = 1920; + private const int TargetClientHeight = 1080; + private const int SwRestore = 9; + private const uint SwpNoZOrder = 0x0004; + private const uint SwpNoActivate = 0x0010; + private const uint MonitorDefaultToNearest = 0x00000002; + private const int GwlStyle = -16; + private const int GwlExStyle = -20; private static readonly object AutomaticActionsLock = new(); private static Process? _automaticActionsProcess; @@ -79,10 +87,12 @@ public static async Task RunLoadoutAsync( if (robloxWindow == IntPtr.Zero) throw new InvalidOperationException("Roblox is not running."); + await NormalizeRobloxWindowAsync(robloxWindow, cancellationToken); + // Do not send SW_RESTORE to a fullscreen or maximized Roblox window. Windows can // interpret that as a request to leave its current presentation state. if (IsIconic(robloxWindow)) - ShowWindowAsync(robloxWindow, 9); + ShowWindowAsync(robloxWindow, SwRestore); ActivateWindow(robloxWindow); DateTime activationDeadline = DateTime.UtcNow.AddSeconds(3); @@ -423,6 +433,66 @@ private static MacroPoint MapRecordedPointToWindow(MacroPoint recordedPoint, Int return new MacroPoint(mappedX, mappedY); } + private static async Task NormalizeRobloxWindowAsync(IntPtr window, CancellationToken cancellationToken) + { + if (!GetClientRect(window, out RECT currentClient)) + return; + + int currentWidth = currentClient.Right - currentClient.Left; + int currentHeight = currentClient.Bottom - currentClient.Top; + if (currentWidth == TargetClientWidth && currentHeight == TargetClientHeight) + return; + + // Restore down rather than minimize to the taskbar: a minimized window cannot + // receive the selector hotkey or clicks. The coordinate mapper below still + // scales to the final client size if the monitor cannot fit full 1080p. + ShowWindowAsync(window, SwRestore); + await Task.Delay(150, cancellationToken); + + IntPtr monitor = MonitorFromWindow(window, MonitorDefaultToNearest); + MONITORINFO monitorInfo = new() { Size = Marshal.SizeOf() }; + if (monitor == IntPtr.Zero || !GetMonitorInfo(monitor, ref monitorInfo)) + return; + + int style = GetWindowLong(window, GwlStyle); + int extendedStyle = GetWindowLong(window, GwlExStyle); + RECT targetFrame = new() { Right = TargetClientWidth, Bottom = TargetClientHeight }; + if (!AdjustWindowRectEx(ref targetFrame, style, false, extendedStyle)) + return; + + int chromeWidth = (targetFrame.Right - targetFrame.Left) - TargetClientWidth; + int chromeHeight = (targetFrame.Bottom - targetFrame.Top) - TargetClientHeight; + int workWidth = monitorInfo.Work.Right - monitorInfo.Work.Left; + int workHeight = monitorInfo.Work.Bottom - monitorInfo.Work.Top; + int availableClientWidth = Math.Max(640, workWidth - Math.Max(0, chromeWidth)); + int availableClientHeight = Math.Max(360, workHeight - Math.Max(0, chromeHeight)); + double scale = Math.Min( + 1d, + Math.Min(availableClientWidth / (double)TargetClientWidth, availableClientHeight / (double)TargetClientHeight)); + int targetClientWidth = Math.Max(640, (int)Math.Floor(TargetClientWidth * scale / 2d) * 2); + int targetClientHeight = Math.Max(360, (int)Math.Floor(TargetClientHeight * scale / 2d) * 2); + + targetFrame = new RECT { Right = targetClientWidth, Bottom = targetClientHeight }; + if (!AdjustWindowRectEx(ref targetFrame, style, false, extendedStyle)) + return; + + int outerWidth = targetFrame.Right - targetFrame.Left; + int outerHeight = targetFrame.Bottom - targetFrame.Top; + int x = monitorInfo.Work.Left + Math.Max(0, (workWidth - outerWidth) / 2); + int y = monitorInfo.Work.Top + Math.Max(0, (workHeight - outerHeight) / 2); + if (!SetWindowPos(window, IntPtr.Zero, x, y, outerWidth, outerHeight, SwpNoZOrder | SwpNoActivate)) + return; + + await Task.Delay(150, cancellationToken); + if (GetClientRect(window, out RECT resizedClient)) + { + App.Logger.WriteLine( + "MacroAutomationService", + $"Restored and scaled Roblox client from {currentWidth}x{currentHeight} to " + + $"{resizedClient.Right - resizedClient.Left}x{resizedClient.Bottom - resizedClient.Top}"); + } + } + private static string? FindAutoHotkeyV2() { string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); @@ -555,6 +625,15 @@ private struct RECT public int Bottom; } + [StructLayout(LayoutKind.Sequential)] + private struct MONITORINFO + { + public int Size; + public RECT Monitor; + public RECT Work; + public uint Flags; + } + [StructLayout(LayoutKind.Sequential)] private struct INPUT { @@ -619,6 +698,21 @@ private struct MOUSEINPUT [DllImport("user32.dll", SetLastError = true)] private static extern bool ClientToScreen(IntPtr window, ref POINT point); + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetWindowPos(IntPtr window, IntPtr insertAfter, int x, int y, int width, int height, uint flags); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool AdjustWindowRectEx(ref RECT rectangle, int style, bool hasMenu, int extendedStyle); + + [DllImport("user32.dll", SetLastError = true)] + private static extern int GetWindowLong(IntPtr window, int index); + + [DllImport("user32.dll")] + private static extern IntPtr MonitorFromWindow(IntPtr window, uint flags); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool GetMonitorInfo(IntPtr monitor, ref MONITORINFO monitorInfo); + [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint inputCount, INPUT[] inputs, int inputSize); diff --git a/SleepStrap/SleepStrap.csproj b/SleepStrap/SleepStrap.csproj index 3da28b0..08e5119 100644 --- a/SleepStrap/SleepStrap.csproj +++ b/SleepStrap/SleepStrap.csproj @@ -7,8 +7,8 @@ true True SleepStrap.ico - 6.9.3.0 - 6.9.3.0 + 6.9.4.0 + 6.9.4.0 app.manifest true x64 diff --git a/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml b/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml index 4cda8c9..9bd3e14 100644 --- a/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml +++ b/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml @@ -104,7 +104,13 @@ - + + + + + + + - + App.Settings.Prop.MacroQuickLoadoutEnabled; + set + { + App.Settings.Prop.MacroQuickLoadoutEnabled = value; + SaveAndNotify(nameof(QuickLoadoutEnabled)); + } + } + public bool IsRunning { get => _isRunning; @@ -515,7 +525,7 @@ private void AutomationTimer_Tick(object? sender, EventArgs e) private bool IsQuickLoadoutHotkeyDown() { - if (_quickHotkeyCaptureActive) + if (!QuickLoadoutEnabled || _quickHotkeyCaptureActive) return false; int key = App.Settings.Prop.MacroQuickLoadoutHotkeyVirtualKey;