Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Arvakr v0.1.4
BepInEx/patchers/Arvakr/Arvakr.dll
Decompiled 3 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Mono.Cecil; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Guys Being Vikings")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.4.0")] [assembly: AssemblyInformationalVersion("0.1.4+0c0a50192283e409e6c1e6cb7fee04be90882150")] [assembly: AssemblyProduct("Arvakr")] [assembly: AssemblyTitle("Arvakr")] [assembly: AssemblyVersion("0.1.4.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GBV.Arvakr { internal static class HostConsole { private static bool _hidden; private const int SW_HIDE = 0; private const int SW_SHOWNOACTIVATE = 4; internal static bool Hidden => _hidden; internal static void Hide() { try { IntPtr consoleWindow = GetConsoleWindow(); if (!(consoleWindow == IntPtr.Zero)) { ShowWindow(consoleWindow, 0); _hidden = true; } } catch { } } internal static void Show() { try { IntPtr consoleWindow = GetConsoleWindow(); if (!(consoleWindow == IntPtr.Zero)) { ShowWindow(consoleWindow, 4); _hidden = false; } } catch { } } [DllImport("kernel32.dll")] private static extern IntPtr GetConsoleWindow(); [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hwnd, int cmd); } internal static class Overlay { private delegate IntPtr WndProcDelegate(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam); private delegate bool EnumWindowsDelegate(IntPtr hwnd, IntPtr param); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WNDCLASSEX { public uint cbSize; public uint style; public IntPtr lpfnWndProc; public int cbClsExtra; public int cbWndExtra; public IntPtr hInstance; public IntPtr hIcon; public IntPtr hCursor; public IntPtr hbrBackground; [MarshalAs(UnmanagedType.LPWStr)] public string lpszMenuName; [MarshalAs(UnmanagedType.LPWStr)] public string lpszClassName; public IntPtr hIconSm; } private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } private struct POINT { public int X; public int Y; } private struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam; public IntPtr lParam; public uint time; public POINT pt; } private struct PAINTSTRUCT { public IntPtr hdc; public bool fErase; public RECT rcPaint; public bool fRestore; public bool fIncUpdate; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] rgbReserved; } private const int WidthFloating = 470; private const int WidthDockedMin = 470; private const int WidthDockedMax = 720; private const int HeightPlain = 100; private const int HeightWithError = 138; private const int Pad = 18; private const int TimerId = 1; private const int FrameMs = 33; private const int ResponsiveStreakToClose = 6; private const long QuietToClose = 8000L; private const int PollEveryTicks = 10; private static volatile string _subject = string.Empty; private static volatile string _errorText = string.Empty; private static volatile int _done; private static volatile int _total; private static volatile int _errors; private static volatile bool _gameStage; private static volatile bool _closeRequested; private static volatile bool _closeHot; private static volatile bool _closePressed; private static volatile bool _minHot; private static volatile bool _minPressed; private static volatile bool _minimized; private static volatile bool _stopping; private static IntPtr _hwnd; private static IntPtr _gameHwnd; private static Thread _thread; private static readonly Stopwatch Life = new Stopwatch(); private static long _lastActivityMs; private static int _responsiveStreak; private static RECT _placed; private static WndProcDelegate _wndProc; private static EnumWindowsDelegate _enumProc; private static IntPtr _brushGround; private static IntPtr _brushBorder; private static IntPtr _brushTrack; private static IntPtr _brushFill; private static IntPtr _brushAlert; private static IntPtr _fontStatus; private static IntPtr _fontSub; private static IntPtr _penClose; private static IntPtr _penCloseHot; private static IntPtr _memDc; private static IntPtr _memBmp; private static IntPtr _oldBmp; private static int _bufWidth; private static int _bufHeight; private static int _tick; private static long _deadlineMs; private static readonly int ColorGround = Rgb(20, 17, 14); private static readonly int ColorBorder = Rgb(58, 50, 42); private static readonly int ColorTrack = Rgb(36, 31, 26); private static readonly int ColorFill = Rgb(216, 162, 74); private static readonly int ColorText = Rgb(232, 220, 198); private static readonly int ColorTextDim = Rgb(154, 143, 126); private static readonly int ColorAlert = Rgb(224, 108, 74); private static readonly uint CurrentPid = GetCurrentProcessId(); private const string ClassName = "ArvakrOverlay"; private const uint WM_NULL = 0u; private const uint WM_DESTROY = 2u; private const uint WM_PAINT = 15u; private const uint WM_CLOSE = 16u; private const uint WM_ERASEBKGND = 20u; private const uint WM_LBUTTONDOWN = 513u; private const uint WM_LBUTTONUP = 514u; private const uint WM_TIMER = 275u; private const int PS_SOLID = 0; private const uint CS_VREDRAW = 1u; private const uint CS_HREDRAW = 2u; private const uint WS_POPUP = 2147483648u; private const uint WS_VISIBLE = 268435456u; private const uint WS_EX_TOPMOST = 8u; private const uint WS_EX_TOOLWINDOW = 128u; private const uint WS_EX_LAYERED = 524288u; private const uint WS_EX_APPWINDOW = 262144u; private const uint WS_EX_NOACTIVATE = 134217728u; private const uint LWA_ALPHA = 2u; private const int GWL_EXSTYLE = -20; private const int SW_HIDE = 0; private const int SW_SHOWMINNOACTIVE = 7; private const uint SWP_NOSIZE = 1u; private const uint SWP_NOMOVE = 2u; private const uint SWP_NOZORDER = 4u; private const uint SWP_NOACTIVATE = 16u; private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); private const uint SMTO_BLOCK = 1u; private const uint SMTO_ABORTIFHUNG = 2u; private const int SM_CXSCREEN = 0; private const int SM_CYSCREEN = 1; private const int TRANSPARENT = 1; private const uint SRCCOPY = 13369376u; private const uint DT_RIGHT = 2u; private const uint DT_VCENTER = 4u; private const uint DT_WORDBREAK = 16u; private const uint DT_SINGLELINE = 32u; private const uint DT_NOPREFIX = 2048u; private const uint DT_END_ELLIPSIS = 32768u; private const uint DtLine = 2084u; private const int FW_NORMAL = 400; private const int FW_SEMIBOLD = 600; private const uint DEFAULT_CHARSET = 1u; private const uint CLEARTYPE_QUALITY = 5u; private static readonly IntPtr IDC_ARROW = new IntPtr(32512); internal static void Open() { try { _deadlineMs = (long)Math.Max(10, ArvakrPatcher.TimeoutSeconds.Value) * 1000L; Life.Start(); if (ArvakrPatcher.KeepWindowAlive.Value) { DisableProcessWindowsGhosting(); } _thread = new Thread(Pump) { IsBackground = true, Name = "Arvakr overlay" }; _thread.Start(); } catch (Exception ex) { try { ManualLogSource log = ArvakrPatcher.Log; if (log != null) { log.LogWarning((object)("Could not start the overlay: " + ex.Message)); } } catch { } } } internal static void SetTotal(int total) { _total = total; } internal static void Ping() { _lastActivityMs = Life.ElapsedMilliseconds; } internal static void SetMod(string name, int done) { _subject = name ?? string.Empty; _done = done; _lastActivityMs = Life.ElapsedMilliseconds; } internal static void EnterGameStage() { if (_total > 0) { _done = _total; } _subject = string.Empty; _lastActivityMs = Life.ElapsedMilliseconds; _gameStage = true; } internal static void SetActivity(string source) { _subject = source ?? string.Empty; _lastActivityMs = Life.ElapsedMilliseconds; } internal static void SetError(int count, string source, string message) { _errorText = (string.IsNullOrEmpty(message) ? source : (source + ": " + message)); _errors = count; } internal static void Close() { _closeRequested = true; IntPtr hwnd = _hwnd; if (hwnd != IntPtr.Zero) { try { PostMessage(hwnd, 16u, IntPtr.Zero, IntPtr.Zero); } catch { } } } private static void Pump() { ushort num = 0; IntPtr intPtr = IntPtr.Zero; try { intPtr = GetModuleHandle(null); _wndProc = WindowProc; _enumProc = EnumProc; WNDCLASSEX wc = new WNDCLASSEX { cbSize = (uint)Marshal.SizeOf(typeof(WNDCLASSEX)), style = 3u, lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProc), hInstance = intPtr, hCursor = LoadCursor(IntPtr.Zero, IDC_ARROW), hbrBackground = IntPtr.Zero, lpszClassName = "ArvakrOverlay" }; num = RegisterClassEx(ref wc); if (num == 0) { return; } RECT placed = Target(); _hwnd = CreateWindowEx(134742152u, "ArvakrOverlay", "Arvakr", 2415919104u, placed.Left, placed.Top, placed.Right - placed.Left, placed.Bottom - placed.Top, IntPtr.Zero, IntPtr.Zero, intPtr, IntPtr.Zero); if (!(_hwnd == IntPtr.Zero)) { _placed = placed; SetLayeredWindowAttributes(_hwnd, 0, 240, 2u); SetTimer(_hwnd, (IntPtr)1, 33u, IntPtr.Zero); int message; MSG msg; while ((message = GetMessage(out msg, IntPtr.Zero, 0u, 0u)) != 0 && message != -1) { TranslateMessage(ref msg); DispatchMessage(ref msg); } } } catch { } finally { _hwnd = IntPtr.Zero; try { ReleaseGdi(); if (num != 0) { UnregisterClass("ArvakrOverlay", intPtr); } } catch { } try { if (HostConsole.Hidden && (_errors > 0 || ArvakrPatcher.RestoreConsole.Value)) { HostConsole.Show(); } } catch { } } } private static IntPtr WindowProc(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam) { try { switch (msg) { case 20u: return (IntPtr)1; case 513u: OnMouseDown(hwnd, lParam); return IntPtr.Zero; case 514u: OnMouseUp(hwnd, lParam); return IntPtr.Zero; case 275u: OnTimer(hwnd); return IntPtr.Zero; case 15u: OnPaint(hwnd); return IntPtr.Zero; case 2u: KillTimer(hwnd, (IntPtr)1); PostQuitMessage(0); return IntPtr.Zero; } } catch { } return DefWindowProc(hwnd, msg, wParam, lParam); } private static void OnMouseDown(IntPtr hwnd, IntPtr lParam) { if (!_stopping && ClientWidth(hwnd, out var w)) { MousePoint(lParam, out var x, out var y); if (CloseButtonShown() && Inside(CloseRect(w), x, y)) { _closePressed = true; } else if (MinButtonShown() && Inside(MinRect(w), x, y)) { _minPressed = true; } } } private static void OnMouseUp(IntPtr hwnd, IntPtr lParam) { bool closePressed = _closePressed; bool minPressed = _minPressed; _closePressed = false; _minPressed = false; if ((closePressed || minPressed) && ClientWidth(hwnd, out var w)) { MousePoint(lParam, out var x, out var y); if (closePressed && Inside(CloseRect(w), x, y)) { StopGame(hwnd); } else if (minPressed && Inside(MinRect(w), x, y)) { MinimizeToTaskbar(hwnd); } } } private static void OnTimer(IntPtr hwnd) { if (_closeRequested || Life.ElapsedMilliseconds >= _deadlineMs) { PostMessage(hwnd, 16u, IntPtr.Zero, IntPtr.Zero); return; } _tick++; if (_minimized) { if (IsIconic(hwnd)) { _closeHot = false; _minHot = false; return; } _minimized = false; Reposition(hwnd); } if (CloseButtonShown() || MinButtonShown()) { if (GetCursorPos(out var p) && GetWindowRect(hwnd, out var rect)) { int w = rect.Right - rect.Left; int x = p.X - rect.Left; int y = p.Y - rect.Top; _closeHot = CloseButtonShown() && Inside(CloseRect(w), x, y); _minHot = MinButtonShown() && Inside(MinRect(w), x, y); } } else { _closeHot = false; _minHot = false; } if (_tick % 10 == 0) { Reposition(hwnd); if (ArvakrPatcher.AlwaysOnTop.Value) { SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, 19u); } if (GameSettled()) { Progress.Close(); return; } } InvalidateRect(hwnd, IntPtr.Zero, erase: false); } private static bool GameSettled() { if (Life.ElapsedMilliseconds - _lastActivityMs < 8000) { return false; } if (_gameHwnd == IntPtr.Zero) { return false; } _responsiveStreak = ((SendMessageTimeout(_gameHwnd, 0u, IntPtr.Zero, IntPtr.Zero, 3u, 120u, out var _) != IntPtr.Zero) ? (_responsiveStreak + 1) : 0); return _responsiveStreak >= 6; } private static void Reposition(IntPtr hwnd) { if (_gameHwnd == IntPtr.Zero && ArvakrPatcher.DockToGame.Value) { EnumWindows(_enumProc, IntPtr.Zero); } RECT placed = Target(); if (placed.Left != _placed.Left || placed.Top != _placed.Top || placed.Right != _placed.Right || placed.Bottom != _placed.Bottom) { _placed = placed; SetWindowPos(hwnd, IntPtr.Zero, placed.Left, placed.Top, placed.Right - placed.Left, placed.Bottom - placed.Top, 20u); } } private static RECT Target() { int num = ((_errors > 0) ? 138 : 100); if (_gameHwnd != IntPtr.Zero && ArvakrPatcher.DockToGame.Value && GetWindowRect(_gameHwnd, out var rect)) { int num2 = rect.Right - rect.Left; int num3 = rect.Bottom - rect.Top; if (num2 > 320 && num3 > 240) { int num4 = Math.Max(470, Math.Min(720, num2 / 2)); int num5 = rect.Left + (num2 - num4) / 2; int num6 = rect.Top + (int)((double)num3 * 0.76) - num / 2; return new RECT { Left = num5, Top = num6, Right = num5 + num4, Bottom = num6 + num }; } } int num7 = (GetSystemMetrics(0) - 470) / 2; int num8 = (GetSystemMetrics(1) - num) / 2; return new RECT { Left = num7, Top = num8, Right = num7 + 470, Bottom = num8 + num }; } private static bool EnumProc(IntPtr hwnd, IntPtr param) { try { if (hwnd == _hwnd) { return true; } GetWindowThreadProcessId(hwnd, out var pid); if (pid != CurrentPid) { return true; } if (!IsWindowVisible(hwnd)) { return true; } StringBuilder stringBuilder = new StringBuilder(64); if (GetClassName(hwnd, stringBuilder, stringBuilder.Capacity) == 0) { return true; } if (stringBuilder.ToString() != "UnityWndClass") { return true; } _gameHwnd = hwnd; return false; } catch { } return true; } private static bool CloseButtonShown() { try { return !_stopping && ArvakrPatcher.ShowCloseButton.Value; } catch { return false; } } private static bool MinButtonShown() { try { return !_stopping && ArvakrPatcher.ShowMinimizeButton.Value; } catch { return false; } } private static RECT CloseRect(int w) { return new RECT { Left = w - 34, Top = 8, Right = w - 10, Bottom = 32 }; } private static RECT MinRect(int w) { return new RECT { Left = w - 62, Top = 8, Right = w - 38, Bottom = 32 }; } private static void MousePoint(IntPtr lParam, out int x, out int y) { int num = lParam.ToInt32(); x = (short)(num & 0xFFFF); y = (short)((num >> 16) & 0xFFFF); } private static bool Inside(RECT r, int x, int y) { if (x >= r.Left && x < r.Right && y >= r.Top) { return y < r.Bottom; } return false; } private static bool ClientWidth(IntPtr hwnd, out int w) { w = 0; if (!GetClientRect(hwnd, out var rect)) { return false; } w = rect.Right - rect.Left; return true; } private static void MinimizeToTaskbar(IntPtr hwnd) { _minHot = false; _closeHot = false; _minimized = true; ShowWindow(hwnd, 0); int windowLong = GetWindowLong(hwnd, -20); windowLong &= -129; windowLong |= 0x40000; SetWindowLong(hwnd, -20, windowLong); ShowWindow(hwnd, 7); } private static void StopGame(IntPtr hwnd) { _stopping = true; _closeHot = false; try { ManualLogSource log = ArvakrPatcher.Log; if (log != null) { log.LogMessage((object)"Startup stopped from the loading bar."); } } catch { } try { InvalidateRect(hwnd, IntPtr.Zero, erase: false); UpdateWindow(hwnd); } catch { } try { Thread.Sleep(250); } catch { } TerminateProcess(GetCurrentProcess(), 0u); } private static void OnPaint(IntPtr hwnd) { PAINTSTRUCT ps; IntPtr intPtr = BeginPaint(hwnd, out ps); if (intPtr == IntPtr.Zero) { return; } try { GetClientRect(hwnd, out var rect); int w = rect.Right - rect.Left; int h = rect.Bottom - rect.Top; EnsureGdi(intPtr, w, h); IntPtr intPtr2 = ((_memDc != IntPtr.Zero) ? _memDc : intPtr); Draw(intPtr2, w, h); if (intPtr2 != intPtr) { BitBlt(intPtr, 0, 0, w, h, _memDc, 0, 0, 13369376u); } } catch { } finally { EndPaint(hwnd, ref ps); } } private static void Draw(IntPtr hdc, int w, int h) { RECT rect = new RECT { Left = 0, Top = 0, Right = w, Bottom = h }; FillRect(hdc, ref rect, _brushGround); FrameRect(hdc, ref rect, _brushBorder); bool gameStage = _gameStage; int total = _total; int done = _done; bool flag = !gameStage && total > 0; RECT rect2 = new RECT { Left = 18, Top = 64, Right = w - 18, Bottom = 76 }; FillRect(hdc, ref rect2, _brushTrack); int num = rect2.Right - rect2.Left; if (flag) { int num2 = (int)((double)num * Math.Min(1.0, (double)done / (double)total)); if (num2 > 0) { RECT rect3 = new RECT { Left = rect2.Left, Top = rect2.Top, Right = rect2.Left + num2, Bottom = rect2.Bottom }; FillRect(hdc, ref rect3, _brushFill); } } else { int num3 = num / 4; int num4 = num + num3; int num5 = (int)(Life.ElapsedMilliseconds / 4 % num4) - num3; int num6 = Math.Max(rect2.Left, rect2.Left + num5); int num7 = Math.Min(rect2.Right, rect2.Left + num5 + num3); if (num7 > num6) { RECT rect4 = new RECT { Left = num6, Top = rect2.Top, Right = num7, Bottom = rect2.Bottom }; FillRect(hdc, ref rect4, _brushFill); } } SetBkMode(hdc, 1); int errors = _errors; int num8 = (MinButtonShown() ? (w - 70) : (CloseButtonShown() ? (w - 42) : (w - 18))); SelectObject(hdc, _fontStatus); SetTextColor(hdc, ColorText); RECT rect5 = new RECT { Left = 18, Top = 14, Right = num8 - ((errors > 0) ? 90 : 0), Bottom = 34 }; DrawText(hdc, _stopping ? "Stopping Valheim..." : (gameStage ? "Starting Valheim" : (flag ? "Loading mods" : "Starting mods...")), -1, ref rect5, 2084u); SelectObject(hdc, _fontSub); if (errors > 0) { SetTextColor(hdc, ColorAlert); RECT rect6 = new RECT { Left = num8 - 90, Top = 16, Right = num8, Bottom = 34 }; DrawText(hdc, errors + ((errors == 1) ? " error" : " errors"), -1, ref rect6, 2086u); } if (MinButtonShown()) { RECT rect7 = MinRect(w); if (_minHot) { FillRect(hdc, ref rect7, _brushTrack); } IntPtr obj = SelectObject(hdc, _minHot ? _penCloseHot : _penClose); int y = rect7.Bottom - 9; MoveToEx(hdc, rect7.Left + 7, y, IntPtr.Zero); LineTo(hdc, rect7.Right - 7, y); SelectObject(hdc, obj); } if (CloseButtonShown()) { RECT rect8 = CloseRect(w); if (_closeHot) { FillRect(hdc, ref rect8, _brushTrack); } IntPtr obj2 = SelectObject(hdc, _closeHot ? _penCloseHot : _penClose); MoveToEx(hdc, rect8.Left + 8, rect8.Top + 8, IntPtr.Zero); LineTo(hdc, rect8.Right - 8, rect8.Bottom - 8); MoveToEx(hdc, rect8.Right - 9, rect8.Top + 8, IntPtr.Zero); LineTo(hdc, rect8.Left + 7, rect8.Bottom - 8); SelectObject(hdc, obj2); } SetTextColor(hdc, ColorTextDim); if (flag) { RECT rect9 = new RECT { Left = w - 18 - 100, Top = 36, Right = w - 18, Bottom = 54 }; DrawText(hdc, done + " / " + total, -1, ref rect9, 2086u); } else if (gameStage) { long num9 = Life.ElapsedMilliseconds - _lastActivityMs; if (num9 >= 2000) { RECT rect10 = new RECT { Left = w - 18 - 110, Top = 36, Right = w - 18, Bottom = 54 }; DrawText(hdc, "waiting " + num9 / 1000 + "s", -1, ref rect10, 2086u); } } RECT rect11 = new RECT { Left = 18, Top = 36, Right = w - 18 - 116, Bottom = 54 }; DrawText(hdc, _subject, -1, ref rect11, 34852u); if (errors > 0) { SetTextColor(hdc, ColorAlert); RECT rect12 = new RECT { Left = 18, Top = 86, Right = w - 18, Bottom = h - 10 }; DrawText(hdc, _errorText, -1, ref rect12, 2064u); } } private static void EnsureGdi(IntPtr hdc, int w, int h) { if (_brushGround == IntPtr.Zero) { _brushGround = CreateSolidBrush(ColorGround); _brushBorder = CreateSolidBrush(ColorBorder); _brushTrack = CreateSolidBrush(ColorTrack); _brushFill = CreateSolidBrush(ColorFill); _brushAlert = CreateSolidBrush(ColorAlert); _fontStatus = CreateFont(-15, 0, 0, 0, 600, 0u, 0u, 0u, 1u, 0u, 0u, 5u, 0u, "Segoe UI"); _fontSub = CreateFont(-12, 0, 0, 0, 400, 0u, 0u, 0u, 1u, 0u, 0u, 5u, 0u, "Segoe UI"); _penClose = CreatePen(0, 2, ColorTextDim); _penCloseHot = CreatePen(0, 2, ColorText); } if (_memDc != IntPtr.Zero && _bufWidth == w && _bufHeight == h) { return; } ReleaseBuffer(); _memDc = CreateCompatibleDC(hdc); if (!(_memDc == IntPtr.Zero)) { _memBmp = CreateCompatibleBitmap(hdc, w, h); if (_memBmp == IntPtr.Zero) { DeleteDC(_memDc); _memDc = IntPtr.Zero; } else { _oldBmp = SelectObject(_memDc, _memBmp); _bufWidth = w; _bufHeight = h; } } } private static void ReleaseBuffer() { if (_memDc != IntPtr.Zero) { if (_oldBmp != IntPtr.Zero) { SelectObject(_memDc, _oldBmp); } DeleteDC(_memDc); } if (_memBmp != IntPtr.Zero) { DeleteObject(_memBmp); } _memDc = (_memBmp = (_oldBmp = IntPtr.Zero)); _bufWidth = (_bufHeight = 0); } private static void ReleaseGdi() { ReleaseBuffer(); IntPtr[] array = new IntPtr[9] { _brushGround, _brushBorder, _brushTrack, _brushFill, _brushAlert, _fontStatus, _fontSub, _penClose, _penCloseHot }; foreach (IntPtr intPtr in array) { if (intPtr != IntPtr.Zero) { DeleteObject(intPtr); } } _brushGround = (_brushBorder = (_brushTrack = (_brushFill = (_brushAlert = IntPtr.Zero)))); _fontStatus = (_fontSub = IntPtr.Zero); _penClose = (_penCloseHot = IntPtr.Zero); } private static int Rgb(int r, int g, int b) { return r | (g << 8) | (b << 16); } [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string moduleName); [DllImport("kernel32.dll")] private static extern uint GetCurrentProcessId(); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll")] private static extern bool TerminateProcess(IntPtr process, uint exitCode); [DllImport("user32.dll")] private static extern void DisableProcessWindowsGhosting(); [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern ushort RegisterClassEx(ref WNDCLASSEX wc); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool UnregisterClass(string className, IntPtr instance); [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CreateWindowEx(uint exStyle, string className, string windowName, uint style, int x, int y, int width, int height, IntPtr parent, IntPtr menu, IntPtr instance, IntPtr param); [DllImport("user32.dll")] private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, int key, byte alpha, uint flags); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr DefWindowProc(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetMessage(out MSG msg, IntPtr hwnd, uint filterMin, uint filterMax); [DllImport("user32.dll")] private static extern bool TranslateMessage(ref MSG msg); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr DispatchMessage(ref MSG msg); [DllImport("user32.dll")] private static extern void PostQuitMessage(int exitCode); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool PostMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr SendMessageTimeout(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam, uint flags, uint timeoutMs, out IntPtr result); [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsDelegate callback, IntPtr param); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint pid); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hwnd); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassName(IntPtr hwnd, StringBuilder name, int max); [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hwnd, out RECT rect); [DllImport("user32.dll")] private static extern bool InvalidateRect(IntPtr hwnd, IntPtr rect, bool erase); [DllImport("user32.dll")] private static extern bool UpdateWindow(IntPtr hwnd); [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hwnd, int cmd); [DllImport("user32.dll")] private static extern bool IsIconic(IntPtr hwnd); [DllImport("user32.dll", EntryPoint = "GetWindowLongW")] private static extern int GetWindowLong(IntPtr hwnd, int index); [DllImport("user32.dll", EntryPoint = "SetWindowLongW")] private static extern int SetWindowLong(IntPtr hwnd, int index, int value); [DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT p); [DllImport("user32.dll")] private static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT ps); [DllImport("user32.dll")] private static extern bool EndPaint(IntPtr hwnd, ref PAINTSTRUCT ps); [DllImport("user32.dll")] private static extern int FillRect(IntPtr hdc, ref RECT rect, IntPtr brush); [DllImport("user32.dll")] private static extern int FrameRect(IntPtr hdc, ref RECT rect, IntPtr brush); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int DrawText(IntPtr hdc, string text, int count, ref RECT rect, uint format); [DllImport("user32.dll")] private static extern IntPtr SetTimer(IntPtr hwnd, IntPtr id, uint elapseMs, IntPtr callback); [DllImport("user32.dll")] private static extern bool KillTimer(IntPtr hwnd, IntPtr id); [DllImport("user32.dll")] private static extern int GetSystemMetrics(int index); [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hwnd, IntPtr insertAfter, int x, int y, int cx, int cy, uint flags); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr LoadCursor(IntPtr instance, IntPtr cursorName); [DllImport("user32.dll")] private static extern bool GetClientRect(IntPtr hwnd, out RECT rect); [DllImport("gdi32.dll")] private static extern IntPtr CreateSolidBrush(int color); [DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr obj); [DllImport("gdi32.dll")] private static extern IntPtr CreateCompatibleDC(IntPtr hdc); [DllImport("gdi32.dll")] private static extern bool DeleteDC(IntPtr hdc); [DllImport("gdi32.dll")] private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int w, int h); [DllImport("gdi32.dll")] private static extern bool BitBlt(IntPtr dst, int x, int y, int w, int h, IntPtr src, int sx, int sy, uint rop); [DllImport("gdi32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr CreateFont(int height, int width, int escapement, int orientation, int weight, uint italic, uint underline, uint strikeOut, uint charSet, uint outPrecision, uint clipPrecision, uint quality, uint pitchAndFamily, string faceName); [DllImport("gdi32.dll")] private static extern IntPtr SelectObject(IntPtr hdc, IntPtr obj); [DllImport("gdi32.dll")] private static extern IntPtr CreatePen(int style, int width, int color); [DllImport("gdi32.dll")] private static extern bool MoveToEx(IntPtr hdc, int x, int y, IntPtr previous); [DllImport("gdi32.dll")] private static extern bool LineTo(IntPtr hdc, int x, int y); [DllImport("gdi32.dll")] private static extern int SetBkMode(IntPtr hdc, int mode); [DllImport("gdi32.dll")] private static extern int SetTextColor(IntPtr hdc, int color); } public static class ArvakrPatcher { public const string PluginGuid = "gbv.valheim.arvakr"; public const string PluginName = "Arvakr"; public const string PluginVersion = "0.1.4"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<int> TimeoutSeconds; internal static ConfigEntry<bool> AlwaysOnTop; internal static ConfigEntry<bool> DockToGame; internal static ConfigEntry<bool> ShowCloseButton; internal static ConfigEntry<bool> ShowMinimizeButton; internal static ConfigEntry<bool> KeepWindowAlive; internal static ConfigEntry<int> SlowestCount; internal static ConfigEntry<bool> HideConsole; internal static ConfigEntry<bool> ShowConsoleOnError; internal static ConfigEntry<bool> RestoreConsole; public static IEnumerable<string> TargetDLLs => new string[0]; public static void Patch(AssemblyDefinition _) { } public static void Initialize() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) try { Log = Logger.CreateLogSource("Arvakr"); Log.LogInfo((object)"Arvakr 0.1.4 preloader entry"); ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, "gbv.valheim.arvakr.cfg"), true); Enabled = val.Bind<bool>("General", "Enabled", true, "Show the loading overlay. Turn this off and startup goes back to exactly what it was."); TimeoutSeconds = val.Bind<int>("General", "TimeoutSeconds", 600, "Close the overlay after this many seconds no matter what. A chainload that dies partway never reports finishing, and a stuck window over a dead game is worse than no window."); AlwaysOnTop = val.Bind<bool>("General", "AlwaysOnTop", true, "Keep the overlay above the game window. Turn this off first if the overlay causes trouble with your display mode."); DockToGame = val.Bind<bool>("General", "DockToGame", true, "Sit the bar low in Valheim's own window and follow it about, so it reads as part of the loading screen. Turn this off to keep it centred on the screen instead."); ShowCloseButton = val.Bind<bool>("General", "ShowCloseButton", true, "Put a close button on the bar that stops the game loading, the same as closing the BepInEx console window. Nothing is at risk: it only appears before the main menu, so no world or character has been loaded."); ShowMinimizeButton = val.Bind<bool>("General", "ShowMinimizeButton", true, "Put a minimize button on the bar. It sends the bar to the taskbar and off the screen, and it comes back from the taskbar like any other window."); KeepWindowAlive = val.Bind<bool>("General", "KeepWindowAlive", true, "Stop Windows greying the game out and captioning it \"Not Responding\" while it loads. The loading screen stays looking like a loading screen. This does not make loading any faster, and while it is on you cannot drag or close the window until loading finishes."); SlowestCount = val.Bind<int>("Report", "SlowestCount", 10, "How many of the slowest mods, and the longest pauses, to list in the log once loading finishes. Set to 0 for the totals only."); HideConsole = val.Bind<bool>("Console", "HideWhileLoading", true, "Hide the BepInEx console while the overlay is up. It is showing the same progress in a less readable form. Nothing is lost; the window is hidden rather than closed."); ShowConsoleOnError = val.Bind<bool>("Console", "ShowOnError", true, "Bring the console straight back if anything logs an error during startup. The overlay says what happened, the console has the detail."); RestoreConsole = val.Bind<bool>("Console", "RestoreWhenDone", false, "Show the console again once loading has finished, even if nothing went wrong."); if (!Enabled.Value) { Log.LogInfo((object)"Disabled in config, standing down."); return; } if (HideConsole.Value) { HostConsole.Hide(); } Overlay.Open(); Progress.Install(); } catch (Exception ex) { try { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Failed to start: " + ex)); } } catch { } } } } internal static class Progress { private enum Stage { Mods, Game, Done } private struct Stall { public long Ms; public string Source; public string After; } private sealed class Watcher : ILogListener, IDisposable { public void LogEvent(object sender, LogEventArgs e) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) try { if (_stage == Stage.Done) { return; } ILogSource val = ((e != null) ? e.Source : null); if (val == null) { return; } string sourceName = val.SourceName; Overlay.Ping(); if ((e.Level & 3) != 0) { NoteError(sourceName, e.Data); } if (_stage == Stage.Mods) { if (string.Equals(sourceName, "BepInEx", StringComparison.Ordinal)) { Mods(e.Data as string); } } else { Game(sourceName, e.Data as string); } } catch { } } public void Dispose() { } } private const string LoadingPrefix = "Loading ["; private const string CountSuffix = " to load"; private const string CompleteLine = "Chainloader startup complete"; private const string MenuReadyLine = "Fast load counter set to 0"; private const long StallMs = 2000L; private static readonly Watcher Listener = new Watcher(); private static readonly Stopwatch Clock = Stopwatch.StartNew(); private static readonly object GameLock = new object(); private static readonly List<string> Names = new List<string>(96); private static readonly List<long> StartMs = new List<long>(96); private static readonly List<Stall> Stalls = new List<Stall>(32); private static volatile Stage _stage = Stage.Mods; private static long _chainStartMs = -1L; private static long _modsEndMs = -1L; private static int _total; private static long _lastLineMs; private static string _lastSource = "Valheim"; private static string _lastMessage = string.Empty; private static int _errors; internal static void Install() { Logger.Listeners.Add((ILogListener)(object)Listener); } internal static void Close() { Stage stage; lock (GameLock) { if (_stage == Stage.Done) { return; } stage = _stage; _stage = Stage.Done; } try { Logger.Listeners.Remove((ILogListener)(object)Listener); } catch { } if (stage == Stage.Mods) { try { ManualLogSource log = ArvakrPatcher.Log; if (log != null) { log.LogWarning((object)"Never saw \"Chainloader startup complete\". BepInEx has probably reworded it, so the counts above are wrong and Arvakr needs updating. The bar closed on its backstop."); } } catch { } } SummariseGame(); Overlay.Close(); } private static void Mods(string s) { if (string.IsNullOrEmpty(s)) { return; } if (s.StartsWith("Loading [", StringComparison.Ordinal)) { int num = s.IndexOf(']', "Loading [".Length); if (num > "Loading [".Length) { string text = s.Substring("Loading [".Length, num - "Loading [".Length); Names.Add(text); StartMs.Add(Clock.ElapsedMilliseconds); Overlay.SetMod(StripVersion(text), Names.Count); } } else if (s.EndsWith(" to load", StringComparison.Ordinal)) { int num2 = LeadingInt(s); if (num2 > 0) { _total = num2; _chainStartMs = Clock.ElapsedMilliseconds; Overlay.SetTotal(num2); } } else if (s.StartsWith("Chainloader startup complete", StringComparison.Ordinal)) { EndMods(); } } private static void EndMods() { _modsEndMs = Clock.ElapsedMilliseconds; _lastLineMs = _modsEndMs; SummariseMods(); _stage = Stage.Game; Overlay.EnterGameStage(); } private static void Game(string source, string message) { long elapsedMilliseconds = Clock.ElapsedMilliseconds; if (IsMenuReady(message)) { Close(); return; } lock (GameLock) { if (_stage != Stage.Game) { return; } long num = elapsedMilliseconds - _lastLineMs; if (num >= 2000) { Stalls.Add(new Stall { Ms = num, Source = _lastSource, After = _lastMessage }); } _lastLineMs = elapsedMilliseconds; _lastSource = Friendly(source); _lastMessage = message ?? string.Empty; } Overlay.SetActivity(_lastSource); } private static void NoteError(string source, object data) { _errors++; if (_errors == 1 && ArvakrPatcher.ShowConsoleOnError.Value) { HostConsole.Show(); } string message; try { message = data?.ToString() ?? string.Empty; } catch { message = string.Empty; } Overlay.SetError(_errors, Friendly(source), message); } private static void SummariseMods() { ManualLogSource log = ArvakrPatcher.Log; if (log == null) { return; } try { int count = Names.Count; if (count == 0) { log.LogInfo((object)"Mods finished, but none were seen starting."); return; } long num = ((_chainStartMs >= 0) ? (_modsEndMs - _chainStartMs) : _modsEndMs); log.LogInfo((object)$"Mods loaded in {(double)num / 1000.0:0.0}s: {count} of {((_total > 0) ? _total : count)}."); if (_total > count) { log.LogInfo((object)$"{_total - count} did not start; look for \"Skipping\" above."); } int value = ArvakrPatcher.SlowestCount.Value; if (value > 0) { int[] array = new int[count]; long[] cost = new long[count]; for (int i = 0; i < count; i++) { array[i] = i; cost[i] = ((i + 1 < count) ? StartMs[i + 1] : _modsEndMs) - StartMs[i]; } Array.Sort(array, (int a, int b) => cost[b].CompareTo(cost[a])); log.LogInfo((object)"Slowest mods to start:"); int num2 = Math.Min(value, count); for (int num3 = 0; num3 < num2; num3++) { int num4 = array[num3]; log.LogInfo((object)$" {(double)cost[num4] / 1000.0,6:0.0}s {Names[num4]}"); } } } catch (Exception ex) { try { log.LogWarning((object)("Could not write the mod summary: " + ex.Message)); } catch { } } } private static void SummariseGame() { ManualLogSource log = ArvakrPatcher.Log; if (log == null) { return; } try { long elapsedMilliseconds = Clock.ElapsedMilliseconds; if (_modsEndMs >= 0) { log.LogInfo((object)$"Game started in a further {(double)(elapsedMilliseconds - _modsEndMs) / 1000.0:0.0}s. Total wait {(double)(elapsedMilliseconds - ((_chainStartMs >= 0) ? _chainStartMs : 0)) / 1000.0:0.0}s."); } int value = ArvakrPatcher.SlowestCount.Value; if (value <= 0) { return; } Stall[] array; lock (GameLock) { if (Stalls.Count == 0) { return; } array = Stalls.ToArray(); } Array.Sort(array, (Stall a, Stall b) => b.Ms.CompareTo(a.Ms)); log.LogInfo((object)"Longest pauses after the mods loaded, and the last thing logged before each:"); int num = Math.Min(value, array.Length); for (int num2 = 0; num2 < num; num2++) { log.LogInfo((object)$" {(double)array[num2].Ms / 1000.0,6:0.0}s after {array[num2].Source}: {Trim(array[num2].After, 90)}"); } if (_errors > 0) { log.LogWarning((object)string.Format("{0} error{1} logged during startup. The console was reopened.", _errors, (_errors == 1) ? "" : "s")); } } catch (Exception ex) { try { log.LogWarning((object)("Could not write the startup summary: " + ex.Message)); } catch { } } } private static string Friendly(string source) { if (string.IsNullOrEmpty(source)) { return "Valheim"; } if (!string.Equals(source, "Unity Log", StringComparison.Ordinal)) { return source; } return "Valheim"; } private static bool IsMenuReady(string s) { if (string.IsNullOrEmpty(s)) { return false; } return s.TrimEnd().EndsWith("Fast load counter set to 0", StringComparison.Ordinal); } private static string StripStamp(string s) { for (int num = Math.Min(s.Length - 1, 28) - 1; num > 0; num--) { if (s[num] == ':' && s[num + 1] == ' ') { for (int i = 0; i < num; i++) { char c = s[i]; if (!char.IsDigit(c) && c != '/' && c != '-' && c != ':' && c != '.' && c != ' ') { return s; } } return s.Substring(num + 2); } } return s; } private static string Trim(string s, int max) { if (string.IsNullOrEmpty(s)) { return string.Empty; } s = StripStamp(s); int num = s.IndexOf('\n'); if (num < 0) { num = s.Length; } if (num > max) { num = max; } string text = s.Substring(0, num).TrimEnd(); if (text.Length >= s.TrimEnd().Length) { return text; } return text + "..."; } private static string StripVersion(string full) { int num = full.LastIndexOf(' '); if (num <= 0 || num >= full.Length - 1) { return full; } if (!char.IsDigit(full[num + 1])) { return full; } return full.Substring(0, num); } private static int LeadingInt(string s) { int num = 0; int i; for (i = 0; i < s.Length && s[i] >= '0' && s[i] <= '9'; i++) { num = num * 10 + (s[i] - 48); } if (i != 0) { return num; } return -1; } } }