mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
Release 0.9.9.12 (#558)
* Release 0.9.9.12 * Move injection to injector executable (#554) * Fix logview missing history (#556) Closes #526 * Detect unresponsive Guild Wars instance (#557) Closes #534 Closes #422
This commit is contained in:
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
targetplatform: [x86]
|
||||
targetplatform: [x64]
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
@@ -97,6 +97,11 @@ jobs:
|
||||
env:
|
||||
RuntimeIdentifier: win-${{ matrix.targetplatform }}
|
||||
|
||||
- name: Create publish injector files
|
||||
run: dotnet publish .\Daybreak.Injector\Daybreak.Injector.csproj -c $env:Configuration -r $env:RuntimeIdentifier --property:SolutionDir=$env:GITHUB_WORKSPACE -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained true -o .\Publish
|
||||
env:
|
||||
RuntimeIdentifier: win-x86
|
||||
|
||||
- name: Pack publish files
|
||||
run: |
|
||||
Write-Host $env
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
targetplatform: [x86]
|
||||
targetplatform: [x64]
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>Daybreak.Injector</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<PublishSingleFile>True</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="SystemExtensions.NetCore" Version="1.0.1" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,469 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
|
||||
namespace Daybreak.Injector;
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
public static uint WM_KEYDOWN = 0x0100;
|
||||
public static uint SWP_SHOWWINDOW = 0x0040;
|
||||
public static IntPtr HWND_TOPMOST = new(-1);
|
||||
public static IntPtr HWND_TOP = IntPtr.Zero;
|
||||
public const int WH_KEYBOARD_LL = 13;
|
||||
public const uint LIST_MODULES_32BIT = 0x01;
|
||||
|
||||
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ProcessEntry32
|
||||
{
|
||||
public uint dwSize;
|
||||
public uint cntUsage;
|
||||
public uint th32ProcessID;
|
||||
public IntPtr th32DefaultHeapID;
|
||||
public uint th32ModuleID;
|
||||
public uint cntThreads;
|
||||
public uint th32ParentProcessID;
|
||||
public int pcPriClassBase;
|
||||
public uint dwFlags;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string szExeFile;
|
||||
};
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct PEB
|
||||
{
|
||||
private readonly byte InheritedAddressSpace;
|
||||
private readonly byte ReadImageFileExecOptions;
|
||||
private readonly byte BeingDebugged;
|
||||
private readonly byte BitField;
|
||||
private readonly IntPtr Mutant;
|
||||
internal IntPtr ImageBaseAddress;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SecurityAttributes
|
||||
{
|
||||
public uint nLength;
|
||||
public IntPtr lpSecurityDescriptor;
|
||||
public bool bInheritHandle;
|
||||
}
|
||||
[Flags]
|
||||
public enum MemoryProtection : uint
|
||||
{
|
||||
PAGE_EXECUTE = 0x10,
|
||||
PAGE_EXECUTE_READ = 0x20,
|
||||
PAGE_EXECUTE_READ_WRITE = 0x40,
|
||||
PAGE_EXECUTE_WRITECOPY = 0x80,
|
||||
PAGE_NOACCESS = 0x01,
|
||||
PAGE_READONLY = 0x02,
|
||||
PAGE_READWRITE = 0x04,
|
||||
PAGE_WRITECOPY = 0x08,
|
||||
PAGE_GUARD = 0x100,
|
||||
PAGE_NOCACHE = 0x200,
|
||||
PAGE_WRITECOMBINE = 0x400
|
||||
}
|
||||
[Flags]
|
||||
public enum MemoryAllocationType : uint
|
||||
{
|
||||
MEM_COMMIT = 0x00001000,
|
||||
MEM_RESERVE = 0x00002000,
|
||||
MEM_RESET = 0x00080000,
|
||||
MEM_RESET_UNDO = 0x1000000,
|
||||
MEM_LARGE_PAGES = 0x20000000,
|
||||
MEM_PHYSICAL = 0x00400000,
|
||||
MEM_TOP_DOWN = 0x00100000,
|
||||
MEM_WRITE_WATCH = 0x00200000
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct RECT
|
||||
{
|
||||
public readonly int Left, Top, Right, Bottom;
|
||||
|
||||
public int Height => this.Bottom - this.Top;
|
||||
|
||||
public int Width => this.Right - this.Left;
|
||||
|
||||
public RECT(int left, int top, int right, int bottom)
|
||||
{
|
||||
this.Left = left;
|
||||
this.Top = top;
|
||||
this.Right = right;
|
||||
this.Bottom = bottom;
|
||||
}
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SystemHandleInformation
|
||||
{
|
||||
public uint OwnerPID;
|
||||
public byte ObjectType;
|
||||
public byte HandleFlags;
|
||||
public ushort HandleValue;
|
||||
public UIntPtr ObjectPointer;
|
||||
public IntPtr AccessMask;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ObjectBasicInformation
|
||||
{
|
||||
public uint Attributes;
|
||||
public uint GrantedAccess;
|
||||
public uint HandleCount;
|
||||
public uint PointerCount;
|
||||
public uint PagedPoolUsage;
|
||||
public uint NonPagedPoolUsage;
|
||||
public uint Reserved1;
|
||||
public uint Reserved2;
|
||||
public uint Reserved3;
|
||||
public uint NameInformationLength;
|
||||
public uint TypeInformationLength;
|
||||
public uint SecurityDescriptorLength;
|
||||
public FILETIME CreateTime;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct IoStatusBlock
|
||||
{
|
||||
public uint Status;
|
||||
public ulong Information;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WindowInfo
|
||||
{
|
||||
public uint cbSize;
|
||||
public RECT rcWindow;
|
||||
public RECT rcClient;
|
||||
public uint dwStyle;
|
||||
public uint dwExStyle;
|
||||
public uint dwWindowStatus;
|
||||
public uint cxWindowBorders;
|
||||
public uint cyWindowBorders;
|
||||
public ushort atomWindowType;
|
||||
public ushort wCreatorVersion;
|
||||
|
||||
public WindowInfo(bool? _) : this() // Allows automatic initialization of "cbSize" with "new WINDOWINFO(null/true/false)".
|
||||
{
|
||||
this.cbSize = (uint)(Marshal.SizeOf(typeof(WindowInfo)));
|
||||
}
|
||||
|
||||
}
|
||||
[Flags]
|
||||
public enum DuplicateOptions : uint
|
||||
{
|
||||
DUPLICATE_CLOSE_SOURCE = 0x00000001,
|
||||
DUPLICATE_SAME_ACCESS = 0x00000002
|
||||
}
|
||||
[Flags]
|
||||
public enum ProcessAccessFlags : uint
|
||||
{
|
||||
All = 0x001F0FFF,
|
||||
Terminate = 0x00000001,
|
||||
CreateThread = 0x00000002,
|
||||
VMOperation = 0x00000008,
|
||||
VMRead = 0x00000010,
|
||||
VMWrite = 0x00000020,
|
||||
DupHandle = 0x00000040,
|
||||
SetInformation = 0x00000200,
|
||||
QueryInformation = 0x00000400,
|
||||
QueryLimitedInformation = 0x00001000,
|
||||
Synchronize = 0x00100000
|
||||
}
|
||||
[Flags]
|
||||
public enum NtStatus : uint
|
||||
{
|
||||
STATUS_SUCCESS = 0x00000000,
|
||||
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
|
||||
}
|
||||
[Flags]
|
||||
public enum ObjectInformationClass : uint
|
||||
{
|
||||
ObjectBasicInformation = 0,
|
||||
ObjectNameInformation = 1,
|
||||
ObjectTypeInformation = 2,
|
||||
ObjectAllTypesInformation = 3,
|
||||
ObjectHandleInformation = 4
|
||||
}
|
||||
[Flags]
|
||||
public enum SystemInformationClass : uint
|
||||
{
|
||||
SystemHandleInformation = 16
|
||||
}
|
||||
[Flags]
|
||||
public enum FileInformationClass
|
||||
{
|
||||
FileNameInformation = 9
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct StartupInfo
|
||||
{
|
||||
public int cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public int dwX;
|
||||
public int dwY;
|
||||
public int dwXSize;
|
||||
public int dwYSize;
|
||||
public int dwXCountChars;
|
||||
public int dwYCountChars;
|
||||
public int dwFillAttribute;
|
||||
public int dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ProcessInformation
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
public enum ProcessInfoClass : uint
|
||||
{
|
||||
ProcessBasicInformation = 0x00,
|
||||
ProcessDebugPort = 0x07,
|
||||
ProcessExceptionPort = 0x08,
|
||||
ProcessAccessToken = 0x09,
|
||||
ProcessWow64Information = 0x1A,
|
||||
ProcessImageFileName = 0x1B,
|
||||
ProcessDebugObjectHandle = 0x1E,
|
||||
ProcessDebugFlags = 0x1F,
|
||||
ProcessExecuteFlags = 0x22,
|
||||
ProcessInstrumentationCallback = 0x28,
|
||||
MaxProcessInfoClass = 0x64
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ProcessBasicInformation
|
||||
{
|
||||
private readonly IntPtr Reserved1;
|
||||
internal IntPtr PebBaseAddress;
|
||||
private readonly IntPtr Reserved2;
|
||||
private readonly IntPtr Reserved3;
|
||||
private readonly UIntPtr UniqueProcessId;
|
||||
private readonly IntPtr Reserved4;
|
||||
}
|
||||
public enum SaferLevel : uint
|
||||
{
|
||||
Disallowed = 0,
|
||||
Untrusted = 0x1000,
|
||||
Constrained = 0x10000,
|
||||
NormalUser = 0x20000,
|
||||
FullyTrusted = 0x40000
|
||||
}
|
||||
public enum SaferLevelScope : uint
|
||||
{
|
||||
Machine = 1,
|
||||
User = 2
|
||||
}
|
||||
public enum SaferOpen : uint
|
||||
{
|
||||
Open = 1
|
||||
}
|
||||
public enum SaferTokenBehaviour : uint
|
||||
{
|
||||
Default = 0x0,
|
||||
NullIfEqual = 0x1,
|
||||
CompareOnly = 0x2,
|
||||
MakeInert = 0x4,
|
||||
WantFlags = 0x8
|
||||
}
|
||||
public enum TokenInformationClass : uint
|
||||
{
|
||||
TokenUser = 1,
|
||||
TokenGroups,
|
||||
TokenPrivileges,
|
||||
TokenOwner,
|
||||
TokenPrimaryGroup,
|
||||
TokenDefaultDacl,
|
||||
TokenSource,
|
||||
TokenType,
|
||||
TokenImpersonationLevel,
|
||||
TokenStatistics,
|
||||
TokenRestrictedSids,
|
||||
TokenSessionId,
|
||||
TokenGroupsAndPrivileges,
|
||||
TokenSessionReference,
|
||||
TokenSandBoxInert,
|
||||
TokenAuditPolicy,
|
||||
TokenOrigin,
|
||||
TokenElevationType,
|
||||
TokenLinkedToken,
|
||||
TokenElevation,
|
||||
TokenHasRestrictions,
|
||||
TokenAccessInformation,
|
||||
TokenVirtualizationAllowed,
|
||||
TokenVirtualizationEnabled,
|
||||
TokenIntegrityLevel,
|
||||
TokenUiAccess,
|
||||
TokenMandatoryPolicy,
|
||||
TokenLogonSid,
|
||||
MaxTokenInfoClass
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TokenMandatoryLabel
|
||||
{
|
||||
public SidAndAttributes Label;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SidAndAttributes
|
||||
{
|
||||
public IntPtr Sid;
|
||||
public int Attributes;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Sid_Identifier_Authority
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public byte[] Value;
|
||||
}
|
||||
[Flags]
|
||||
public enum CreationFlags : uint
|
||||
{
|
||||
CreateSuspended = 0x00000004,
|
||||
DetachedProcess = 0x00000008,
|
||||
CreateNoWindow = 0x08000000,
|
||||
ExtendedStartupInfoPresent = 0x00080000
|
||||
}
|
||||
|
||||
public const int WM_SYSCOMMAND = 0x112;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool Process32First(IntPtr hSnapshot, ref ProcessEntry32 lppe);
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool Process32Next(IntPtr hSnapshot, ref ProcessEntry32 lppe);
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions);
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwProcessID);
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern NtStatus NtQueryInformationFile(IntPtr FileHandle, ref IoStatusBlock IoStatusBlock, IntPtr FileInformation, int FileInformationLength, FileInformationClass FileInformationClass);
|
||||
[DllImport("ntdll.dll")]
|
||||
public static extern NtStatus NtQueryObject(IntPtr ObjectHandle, ObjectInformationClass ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, out int ReturnLength);
|
||||
[DllImport("ntdll.dll")]
|
||||
public static extern NtStatus NtQuerySystemInformation(SystemInformationClass SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, out int ReturnLength);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern bool QueryFullProcessImageName(IntPtr hProcess, uint dwFlags, StringBuilder lpExeName, ref uint lpdwSize);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetWindowPos(IntPtr hwnd, IntPtr insertAfter, int x, int y, int cx, int cy, uint flags);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool ShowWindow(IntPtr hwnd, int cmd);
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern int GetWindowTextLength(IntPtr hWnd);
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr LoadLibrary(string lpFileName);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern bool FreeLibrary(IntPtr hModule);
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool UnhookWindowsHookEx(IntPtr hHook);
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr GetForegroundWindow();
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool ReadProcessMemory(IntPtr hProcess, uint lpBaseAddress, IntPtr lpBuffer, uint nSize, out uint lpNumberOfBytesRead);
|
||||
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool GetWindowInfo(IntPtr hwnd, ref WindowInfo pwi);
|
||||
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool CreateProcess(
|
||||
string lpApplicationName, string lpCommandLine, ref SecurityAttributes lpProcessAttributes,
|
||||
ref SecurityAttributes lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
|
||||
IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref StartupInfo lpStartupInfo,
|
||||
out ProcessInformation lpProcessInformation);
|
||||
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern uint ResumeThread(IntPtr hThread);
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr LocalFree(IntPtr hMem);
|
||||
[DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesWritten);
|
||||
[DllImport("ntdll.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern int NtQueryInformationProcess(IntPtr hProcess, ProcessInfoClass pic, out ProcessBasicInformation pbi, int cb, out int pSize);
|
||||
[DllImport("advapi32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool SaferCreateLevel(SaferLevelScope scopeId, SaferLevel levelId, SaferOpen openFlags, out IntPtr levelHandle, IntPtr reserved);
|
||||
[DllImport("advapi32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool SaferComputeTokenFromLevel(IntPtr levelHandle, IntPtr inAccessToken, out IntPtr outAccessToken, SaferTokenBehaviour flags, IntPtr lpReserved);
|
||||
[DllImport("advapi32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool SaferCloseLevel(IntPtr levelHandle);
|
||||
[DllImport("advapi32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool SetTokenInformation(IntPtr tokenHandle, TokenInformationClass tokenInformationokenInformationClass, ref TokenMandatoryLabel tokenInformation, uint tokenInformationLength);
|
||||
[DllImport("advapi32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool ConvertStringSidToSid(string stringSid, out IntPtr ptrSid);
|
||||
[DllImport("advapi32.dll")]
|
||||
public static extern uint GetLengthSid(IntPtr pSid);
|
||||
[DllImport("advapi32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern bool CreateProcessAsUser(
|
||||
IntPtr hToken,
|
||||
string lpApplicationName,
|
||||
string lpCommandLine,
|
||||
ref SecurityAttributes lpProcessAttributes,
|
||||
ref SecurityAttributes lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
string lpCurrentDirectory,
|
||||
ref StartupInfo lpStartupInfo,
|
||||
out ProcessInformation lpProcessInformation);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr CreateRemoteThread(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpThreadAttributes,
|
||||
uint dwStackSize,
|
||||
IntPtr lpStartAddress,
|
||||
IntPtr lpParameter,
|
||||
uint dwCreationFlags,
|
||||
out IntPtr lpThreadId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
|
||||
public static extern IntPtr VirtualAllocEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpAddress,
|
||||
IntPtr dwSize,
|
||||
uint dwAllocationType,
|
||||
uint dwProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
|
||||
public static extern bool VirtualFreeEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpAddress,
|
||||
uint dwSize,
|
||||
uint dwFreeType);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
|
||||
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint GetExitCodeThread(IntPtr hHandle, out IntPtr dwMilliseconds);
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool WriteProcessMemory(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpBaseAddress,
|
||||
IntPtr lpBuffer,
|
||||
int nSize,
|
||||
out IntPtr lpNumberOfBytesWritten);
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool ReadProcessMemory(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpBaseAddress,
|
||||
IntPtr lpBuffer,
|
||||
int nSize,
|
||||
out IntPtr lpNumberOfBytesRead);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Daybreak.Injector;
|
||||
internal static class ProcessInjector
|
||||
{
|
||||
public static Task<bool> Inject(Process process, string pathToDll, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
return InjectWithWinApi(process, pathToDll);
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
|
||||
private static bool InjectWithWinApi(Process process, string pathToDll)
|
||||
{
|
||||
var modulefullpath = Path.GetFullPath(pathToDll);
|
||||
|
||||
if (!File.Exists(modulefullpath))
|
||||
{
|
||||
Console.WriteLine("Dll to inject not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
var hKernel32 = NativeMethods.GetModuleHandle("kernel32.dll");
|
||||
if (hKernel32 == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("Unable to get a handle of kernel32.dll");
|
||||
return false;
|
||||
}
|
||||
|
||||
var hLoadLib = NativeMethods.GetProcAddress(hKernel32, "LoadLibraryW");
|
||||
if (hLoadLib == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("Unable to get the address of LoadLibraryW");
|
||||
return false;
|
||||
}
|
||||
|
||||
var hStringBuffer = NativeMethods.VirtualAllocEx(process.Handle, IntPtr.Zero, new IntPtr(2 * (modulefullpath.Length + 1)),
|
||||
0x3000 /* MEM_COMMIT | MEM_RESERVE */, 0x4 /* PAGE_READWRITE */);
|
||||
if (hStringBuffer == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("Unable to allocate memory for module path");
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteWString(process, hStringBuffer, modulefullpath);
|
||||
if (ReadWString(process, hStringBuffer, 260) != modulefullpath)
|
||||
{
|
||||
Console.WriteLine("Module path string is not correct");
|
||||
return false;
|
||||
}
|
||||
|
||||
var hThread = NativeMethods.CreateRemoteThread(process.Handle, IntPtr.Zero, 0, hLoadLib, hStringBuffer, 0, out _);
|
||||
if (hThread == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("Unable to create remote thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
var threadResult = NativeMethods.WaitForSingleObject(hThread, 30000u);
|
||||
if (threadResult is 0x102 or 0xFFFFFFFF /* WAIT_FAILED */)
|
||||
{
|
||||
Console.WriteLine($"Exception occurred while waiting for the remote thread. Result is {threadResult}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var dllResult = NativeMethods.GetExitCodeThread(hThread, out _);
|
||||
if (dllResult == 0)
|
||||
{
|
||||
Console.WriteLine($"Injected dll returned non-success status code {dllResult}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var memoryFreeResult = NativeMethods.VirtualFreeEx(process.Handle, hStringBuffer, 0, 0x8000 /* MEM_RELEASE */);
|
||||
if (!memoryFreeResult)
|
||||
{
|
||||
Console.WriteLine($"Failed to free dll memory");
|
||||
}
|
||||
|
||||
return memoryFreeResult;
|
||||
}
|
||||
|
||||
private static void WriteBytes(Process process, IntPtr address, byte[] data)
|
||||
{
|
||||
var size = data.Length;
|
||||
var buffer = Marshal.AllocHGlobal(size);
|
||||
Marshal.Copy(data, 0, buffer, size);
|
||||
|
||||
NativeMethods.WriteProcessMemory(
|
||||
process.Handle,
|
||||
address,
|
||||
buffer,
|
||||
size,
|
||||
out _);
|
||||
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
|
||||
private static void WriteWString(Process process, IntPtr address, string data)
|
||||
{
|
||||
WriteBytes(process, address, Encoding.Unicode.GetBytes(data));
|
||||
}
|
||||
|
||||
private static string ReadWString(Process process, IntPtr address, int maxsize, Encoding? encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.Unicode;
|
||||
var rawbytes = ReadBytes(process, address, maxsize);
|
||||
if (rawbytes.Length == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var ret = encoding.GetString(rawbytes);
|
||||
if (ret.Contains('\0'))
|
||||
{
|
||||
ret = ret[..ret.IndexOf('\0')];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static byte[] ReadBytes(Process process, IntPtr address, int size)
|
||||
{
|
||||
var buffer = Marshal.AllocHGlobal(size);
|
||||
|
||||
NativeMethods.ReadProcessMemory(process.Handle,
|
||||
address,
|
||||
buffer,
|
||||
size,
|
||||
out _
|
||||
);
|
||||
|
||||
var ret = new byte[size];
|
||||
Marshal.Copy(buffer, ret, 0, size);
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Daybreak.Injector;
|
||||
using System.Diagnostics;
|
||||
|
||||
var processId = -1;
|
||||
var pathToDll = string.Empty;
|
||||
|
||||
if (args.Length != 4)
|
||||
{
|
||||
Console.WriteLine("Daybreak Injector");
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine("Daybreak.Injector.exe -p [PROCESS_ID] -d [PATH_TO_DLL]");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (var i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (args[i].ToLower() == "-p" &&
|
||||
int.TryParse(args[i + 1], out var parsedId))
|
||||
{
|
||||
processId = parsedId;
|
||||
i++;
|
||||
}
|
||||
else if (args[i].ToLower() == "-d")
|
||||
{
|
||||
pathToDll = args[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (processId == -1)
|
||||
{
|
||||
Console.WriteLine("Error: Process id was not specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pathToDll == string.Empty)
|
||||
{
|
||||
Console.WriteLine("Error: Path to dll was not specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!File.Exists(pathToDll))
|
||||
{
|
||||
Console.WriteLine("Error: Provided dll could not be found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
var maybeProcess = Process.GetProcessById(processId);
|
||||
if (maybeProcess is null)
|
||||
{
|
||||
Console.WriteLine("Error: Could not find desired process");
|
||||
return -1;
|
||||
}
|
||||
|
||||
var result = await ProcessInjector.Inject(maybeProcess, pathToDll, CancellationToken.None);
|
||||
if (result is false)
|
||||
{
|
||||
Console.WriteLine("Error: Failed to inject dll");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Console.WriteLine("Injected dll");
|
||||
return 0;
|
||||
+18
-4
@@ -34,6 +34,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.7ZipExtractor", "Daybreak.7ZipExtractor\Daybreak.7ZipExtractor.csproj", "{CA60F9AC-A496-4DB6-970E-ECE73B051C05}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.Injector", "Daybreak.Injector\Daybreak.Injector.csproj", "{2C1AB589-A533-41B6-9F8B-4328AD122437}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -70,8 +72,8 @@ Global
|
||||
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|x86.Build.0 = Release|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.ActiveCfg = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.Build.0 = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x86.Build.0 = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -82,8 +84,8 @@ Global
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|x86.Build.0 = Release|x86
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.Build.0 = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x86.Build.0 = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -92,6 +94,18 @@ Global
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x86.ActiveCfg = Release|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x86.Build.0 = Release|x64
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Debug|x64.ActiveCfg = Debug|x86
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Debug|x64.Build.0 = Debug|x86
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Debug|x86.Build.0 = Debug|x86
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2C1AB589-A533-41B6-9F8B-4328AD122437}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<StackPanel
|
||||
Visibility="{Binding ElementName=_this, Path=CanShowFocusView, Mode=OneWay, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
|
||||
Visibility="{Binding ElementName=_this, Path=CanLaunch, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<WrapPanel VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock Text="Launch "
|
||||
@@ -29,10 +29,6 @@
|
||||
<TextBlock Text="{Binding Configuration.Credentials.Username}"
|
||||
FontSize="22"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
|
||||
<TextBlock Text=" running"
|
||||
FontSize="22"
|
||||
Foreground="{StaticResource MahApps.Brushes.Accent}"
|
||||
Visibility="{Binding ElementName=_this, Path=GameRunning, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</WrapPanel>
|
||||
<TextBlock Text="{Binding Configuration.ExecutablePath}"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
|
||||
@@ -41,7 +37,7 @@
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Visibility="{Binding ElementName=_this, Path=CanShowFocusView, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
Visibility="{Binding ElementName=_this, Path=CanAttach, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<WrapPanel VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock Text="Attach "
|
||||
@@ -57,5 +53,22 @@
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Visibility="{Binding ElementName=_this, Path=CanKill, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<WrapPanel VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock Text="Kill "
|
||||
FontSize="22"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" />
|
||||
<TextBlock Text="{Binding Configuration.Credentials.Username}"
|
||||
FontSize="22"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
|
||||
</WrapPanel>
|
||||
<TextBlock Text="{Binding Configuration.ExecutablePath}"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -31,8 +31,14 @@ public partial class LaunchButtonTemplate : UserControl
|
||||
private readonly IApplicationLauncher applicationLauncher;
|
||||
private readonly ILiveOptions<FocusViewOptions> liveOptions;
|
||||
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
private bool canLaunch;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool canShowFocusView;
|
||||
private bool canKill;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool canAttach;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool gameRunning;
|
||||
@@ -101,14 +107,20 @@ public partial class LaunchButtonTemplate : UserControl
|
||||
if (this.DataContext is not LauncherViewContext launcherViewContext ||
|
||||
launcherViewContext.Configuration is null)
|
||||
{
|
||||
this.CanLaunch = false;
|
||||
this.CanAttach = false;
|
||||
this.CanKill = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.applicationLauncher.GetGuildwarsProcess(launcherViewContext.Configuration) is not GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
this.GameRunning = false;
|
||||
this.CanShowFocusView = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
this.CanLaunch = true;
|
||||
this.CanAttach = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
this.CanKill = false;
|
||||
launcherViewContext.CanLaunch = true;
|
||||
launcherViewContext.CanKill = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,8 +131,11 @@ public partial class LaunchButtonTemplate : UserControl
|
||||
catch
|
||||
{
|
||||
this.GameRunning = false;
|
||||
this.CanShowFocusView = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
this.CanLaunch = false;
|
||||
this.CanAttach = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
this.CanKill = this.CanAttach ? false : true;
|
||||
launcherViewContext.CanLaunch = false;
|
||||
launcherViewContext.CanKill = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,13 +143,19 @@ public partial class LaunchButtonTemplate : UserControl
|
||||
if (loginInfo?.Email != context.LaunchConfiguration.Credentials?.Username)
|
||||
{
|
||||
this.GameRunning = false;
|
||||
this.CanShowFocusView = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
this.CanAttach = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
this.CanLaunch = true;
|
||||
this.CanKill = false;
|
||||
launcherViewContext.CanLaunch = false;
|
||||
launcherViewContext.CanKill = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.GameRunning = true;
|
||||
this.CanKill = false;
|
||||
this.CanLaunch = false;
|
||||
launcherViewContext.CanLaunch = true;
|
||||
this.CanShowFocusView = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
launcherViewContext.CanKill = false;
|
||||
this.CanAttach = this.liveOptions.Value.Enabled && this.GameRunning;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
|
||||
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
|
||||
<Version>0.9.9.11</Version>
|
||||
<Version>0.9.9.12</Version>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
|
||||
@@ -5,10 +5,12 @@ namespace Daybreak.Models;
|
||||
public sealed class LauncherViewContext : INotifyPropertyChanged
|
||||
{
|
||||
private bool canLaunch = false;
|
||||
private bool canKill = false;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public LaunchConfigurationWithCredentials? Configuration { get; init; }
|
||||
|
||||
public bool CanLaunch
|
||||
{
|
||||
get => this.canLaunch;
|
||||
@@ -18,4 +20,14 @@ public sealed class LauncherViewContext : INotifyPropertyChanged
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.CanLaunch)));
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanKill
|
||||
{
|
||||
get => this.canKill;
|
||||
set
|
||||
{
|
||||
this.canKill = value;
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.CanKill)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +83,7 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
|
||||
return null;
|
||||
}
|
||||
|
||||
var gwProcess = await this.LaunchGuildwarsProcess(
|
||||
credentials.Username!.ThrowIfNull(),
|
||||
credentials.Password!.ThrowIfNull(),
|
||||
launchConfigurationWithCredentials.ExecutablePath!.ThrowIfNull());
|
||||
var gwProcess = await this.LaunchGuildwarsProcess(launchConfigurationWithCredentials);
|
||||
if (gwProcess is null)
|
||||
{
|
||||
return default;
|
||||
@@ -163,8 +160,11 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private async Task<Process?> LaunchGuildwarsProcess(string email, Models.SecureString password, string executable)
|
||||
private async Task<Process?> LaunchGuildwarsProcess(LaunchConfigurationWithCredentials launchConfigurationWithCredentials)
|
||||
{
|
||||
var email = launchConfigurationWithCredentials.Credentials!.Username;
|
||||
var password = launchConfigurationWithCredentials.Credentials!.Password;
|
||||
var executable = launchConfigurationWithCredentials.ExecutablePath!;
|
||||
if (File.Exists(executable) is false)
|
||||
{
|
||||
throw new ExecutableNotFoundException($"Guildwars executable doesn't exist at {executable}");
|
||||
@@ -293,7 +293,7 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.KillGuildWarsProcess(process);
|
||||
this.KillGuildWarsProcess(new GuildWarsApplicationLaunchContext { GuildWarsProcess = process, LaunchConfiguration = launchConfigurationWithCredentials, ProcessId = (uint)pId });
|
||||
this.logger.LogError(e, $"{mod.Name} unhandled exception");
|
||||
this.notificationService.NotifyError(
|
||||
title: $"{mod.Name} exception",
|
||||
@@ -364,7 +364,7 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.KillGuildWarsProcess(process);
|
||||
this.KillGuildWarsProcess(new GuildWarsApplicationLaunchContext { GuildWarsProcess = process, LaunchConfiguration = launchConfigurationWithCredentials, ProcessId = (uint)pId });
|
||||
this.logger.LogError(e, $"{mod.Name} unhandled exception");
|
||||
this.notificationService.NotifyError(
|
||||
title: $"{mod.Name} exception",
|
||||
@@ -399,18 +399,11 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
|
||||
return Process.GetProcessesByName(ProcessName);
|
||||
}
|
||||
|
||||
public void KillGuildWarsProcess(Process process)
|
||||
public void KillGuildWarsProcess(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext)
|
||||
{
|
||||
process.ThrowIfNull();
|
||||
try
|
||||
{
|
||||
if (process.StartInfo is not null &&
|
||||
process.StartInfo.FileName.Contains("Gw.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
process.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var process = guildWarsApplicationLaunchContext.GuildWarsProcess;
|
||||
if (process.MainModule?.FileName is not null &&
|
||||
process.MainModule.FileName.Contains("Gw.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -418,12 +411,17 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
this.logger.LogError(e, $"Insuficient privileges to kill GuildWars process with id {guildWarsApplicationLaunchContext.ProcessId}");
|
||||
this.privilegeManager.RequestAdminPrivileges<LauncherView>("Insufficient privileges to kill Guild Wars process. Please restart as administrator and try again.");
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
this.logger.LogError(e, $"Failed to kill GuildWars process with id {process?.Id}");
|
||||
this.logger.LogError(e, $"Failed to kill GuildWars process with id {guildWarsApplicationLaunchContext.ProcessId}");
|
||||
this.notificationService.NotifyError(
|
||||
title: "Failed to kill GuildWars process",
|
||||
description: $"Encountered exception while trying to kill GuildWars process with id {process?.Id}. Check logs for details");
|
||||
description: $"Encountered exception while trying to kill GuildWars process with id {guildWarsApplicationLaunchContext.ProcessId}. Check logs for details");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ public interface IApplicationLauncher
|
||||
GuildWarsApplicationLaunchContext? GetGuildwarsProcess(LaunchConfigurationWithCredentials launchConfigurationWithCredentials);
|
||||
IEnumerable<GuildWarsApplicationLaunchContext?> GetGuildwarsProcesses(params LaunchConfigurationWithCredentials[] launchConfigurationWithCredentials);
|
||||
IEnumerable<Process> GetGuildwarsProcesses();
|
||||
void KillGuildWarsProcess(Process process);
|
||||
void KillGuildWarsProcess(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext);
|
||||
Task<GuildWarsApplicationLaunchContext?> LaunchGuildwars(LaunchConfigurationWithCredentials launchConfigurationWithCredentials);
|
||||
void RestartDaybreak();
|
||||
void RestartDaybreakAsAdmin();
|
||||
|
||||
@@ -83,7 +83,7 @@ internal sealed class GWCAClient : IGWCAClient
|
||||
|
||||
return new ConnectionContext(listener.Port, processId);
|
||||
}
|
||||
catch (Exception e) when (e is TaskCanceledException or TimeoutException)
|
||||
catch (Exception e) when (e is TaskCanceledException or TimeoutException or HttpRequestException)
|
||||
{
|
||||
scopedLogger.LogInformation($"Timed out trying to reach port. Continuing");
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
using Daybreak.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Daybreak.Services.Injection;
|
||||
internal sealed class ProcessInjector : IProcessInjector
|
||||
{
|
||||
private const string InjectorExe = "Daybreak.Injector.exe";
|
||||
|
||||
private readonly ILogger<ProcessInjector> logger;
|
||||
|
||||
public ProcessInjector(
|
||||
@@ -23,136 +21,51 @@ internal sealed class ProcessInjector : IProcessInjector
|
||||
|
||||
public Task<bool> Inject(Process process, string pathToDll, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
return this.InjectWithWinApi(process, pathToDll);
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
return Task.Factory.StartNew(() => this.InjectWithInjector(process, pathToDll), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
|
||||
private bool InjectWithWinApi(Process process, string pathToDll)
|
||||
|
||||
private bool InjectWithInjector(Process process, string pathToDll)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(InjectWithWinApi), pathToDll);
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(InjectWithInjector), pathToDll);
|
||||
var modulefullpath = Path.GetFullPath(pathToDll);
|
||||
|
||||
if (!File.Exists(modulefullpath))
|
||||
{
|
||||
scopedLogger.LogError("Dll to inject not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
var hKernel32 = NativeMethods.GetModuleHandle("kernel32.dll");
|
||||
if (hKernel32 == IntPtr.Zero)
|
||||
var injectorPath = Path.GetFullPath(InjectorExe);
|
||||
if (!File.Exists(injectorPath))
|
||||
{
|
||||
scopedLogger.LogError("Unable to get a handle of kernel32.dll");
|
||||
scopedLogger.LogError("Could not find Daybreak injector");
|
||||
return false;
|
||||
}
|
||||
|
||||
var hLoadLib = NativeMethods.GetProcAddress(hKernel32, "LoadLibraryW");
|
||||
if (hLoadLib == IntPtr.Zero)
|
||||
var injectionProcess = new Process
|
||||
{
|
||||
scopedLogger.LogError("Unable to get the address of LoadLibraryW");
|
||||
return false;
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = $"-p {process.Id} -d {pathToDll}",
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
FileName = injectorPath,
|
||||
}
|
||||
};
|
||||
|
||||
injectionProcess.Start();
|
||||
injectionProcess.WaitForExit();
|
||||
var result = injectionProcess.ExitCode;
|
||||
if (result == 0)
|
||||
{
|
||||
scopedLogger.LogInformation("Injected into process");
|
||||
return true;
|
||||
}
|
||||
|
||||
var hStringBuffer = NativeMethods.VirtualAllocEx(process.Handle, IntPtr.Zero, new IntPtr(2 * (modulefullpath.Length + 1)),
|
||||
0x3000 /* MEM_COMMIT | MEM_RESERVE */, 0x4 /* PAGE_READWRITE */);
|
||||
if (hStringBuffer == IntPtr.Zero)
|
||||
{
|
||||
scopedLogger.LogError("Unable to allocate memory for module path");
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteWString(process, hStringBuffer, modulefullpath);
|
||||
if (ReadWString(process, hStringBuffer, 260) != modulefullpath)
|
||||
{
|
||||
scopedLogger.LogError("Module path string is not correct");
|
||||
return false;
|
||||
}
|
||||
|
||||
var hThread = NativeMethods.CreateRemoteThread(process.Handle, IntPtr.Zero, 0, hLoadLib, hStringBuffer, 0, out _);
|
||||
if (hThread == IntPtr.Zero)
|
||||
{
|
||||
scopedLogger.LogError("Unable to create remote thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
var threadResult = NativeMethods.WaitForSingleObject(hThread, 30000u);
|
||||
if (threadResult is 0x102 or 0xFFFFFFFF /* WAIT_FAILED */)
|
||||
{
|
||||
scopedLogger.LogError($"Exception occurred while waiting for the remote thread. Result is {threadResult}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var dllResult = NativeMethods.GetExitCodeThread(hThread, out _);
|
||||
if (dllResult == 0)
|
||||
{
|
||||
scopedLogger.LogError($"Injected dll returned non-success status code {dllResult}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var memoryFreeResult = NativeMethods.VirtualFreeEx(process.Handle, hStringBuffer, 0, 0x8000 /* MEM_RELEASE */);
|
||||
if (!memoryFreeResult)
|
||||
{
|
||||
scopedLogger.LogError($"Failed to free dll memory");
|
||||
}
|
||||
|
||||
return memoryFreeResult;
|
||||
}
|
||||
|
||||
private static void WriteBytes(Process process, IntPtr address, byte[] data)
|
||||
{
|
||||
var size = data.Length;
|
||||
var buffer = Marshal.AllocHGlobal(size);
|
||||
Marshal.Copy(data, 0, buffer, size);
|
||||
|
||||
NativeMethods.WriteProcessMemory(
|
||||
process.Handle,
|
||||
address,
|
||||
buffer,
|
||||
size,
|
||||
out _);
|
||||
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
|
||||
private static void WriteWString(Process process, IntPtr address, string data)
|
||||
{
|
||||
WriteBytes(process, address, Encoding.Unicode.GetBytes(data));
|
||||
}
|
||||
|
||||
private static string ReadWString(Process process, IntPtr address, int maxsize, Encoding? encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.Unicode;
|
||||
var rawbytes = ReadBytes(process, address, maxsize);
|
||||
if (rawbytes.Length == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var ret = encoding.GetString(rawbytes);
|
||||
if (ret.Contains('\0'))
|
||||
{
|
||||
ret = ret[..ret.IndexOf('\0')];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static byte[] ReadBytes(Process process, IntPtr address, int size)
|
||||
{
|
||||
var buffer = Marshal.AllocHGlobal(size);
|
||||
|
||||
NativeMethods.ReadProcessMemory(process.Handle,
|
||||
address,
|
||||
buffer,
|
||||
size,
|
||||
out _
|
||||
);
|
||||
|
||||
var ret = new byte[size];
|
||||
Marshal.Copy(buffer, ret, 0, size);
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
|
||||
return ret;
|
||||
var stdout = injectionProcess.StandardOutput.ReadToEnd();
|
||||
var stderr = injectionProcess.StandardError.ReadToEnd();
|
||||
scopedLogger.LogError($"Failed to inject. Details:\n{stdout}\n{stderr}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,15 +65,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "game", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "game", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var gameData = payload.Deserialize<GameDataPayload>();
|
||||
if (gameData is null)
|
||||
@@ -126,15 +126,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "inventory", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "inventory", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var inventoryData = payload.Deserialize<InventoryPayload>();
|
||||
if (inventoryData is null)
|
||||
@@ -171,15 +171,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "login", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "login", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var loginData = payload.Deserialize<LoginPayload>();
|
||||
if (loginData is null)
|
||||
@@ -210,15 +210,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "game/mainplayer", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "game/mainplayer", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var playerData = payload.Deserialize<MainPlayerPayload>();
|
||||
if (playerData is null)
|
||||
@@ -248,15 +248,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "pathing", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "pathing", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var pathingPayload = payload.Deserialize<PathingPayload>();
|
||||
if (pathingPayload is null)
|
||||
@@ -314,15 +314,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "pathing/metadata", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "pathing/metadata", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var pathingMetadataPayload = payload.Deserialize<PathingMetadataPayload>();
|
||||
if (pathingMetadataPayload is null)
|
||||
@@ -352,15 +352,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "pregame", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "pregame", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var preGamePayload = payload.Deserialize<PreGamePayload>();
|
||||
if (preGamePayload is null)
|
||||
@@ -391,15 +391,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "session", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "session", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var sessionPayload = payload.Deserialize<SessionPayload>();
|
||||
if (sessionPayload is null)
|
||||
@@ -443,15 +443,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "user", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "user", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var userPayload = payload.Deserialize<UserPayload>();
|
||||
if (userPayload is null)
|
||||
@@ -498,15 +498,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "map", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, "map", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var mapPayload = payload.Deserialize<MapPayload>();
|
||||
if (mapPayload is null)
|
||||
@@ -544,15 +544,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, $"game/state", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, $"game/state", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var gameStatePayload = payload.Deserialize<GameStatePayload>();
|
||||
if (gameStatePayload is null ||
|
||||
@@ -600,15 +600,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, $"entities/name?id={entity.Id}", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, $"entities/name?id={entity.Id}", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var namePayload = payload.Deserialize<NamePayload>();
|
||||
if (namePayload is null ||
|
||||
@@ -638,15 +638,15 @@ public sealed partial class GWCAMemoryReader : IGuildwarsMemoryReader
|
||||
return default;
|
||||
}
|
||||
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, $"items/name?id={id}&modifiers={string.Join(',', modifiers?.Select(m => m.ToString()) ?? Array.Empty<string>())}", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.client.GetAsync(this.connectionContextCache.Value, $"items/name?id={id}&modifiers={string.Join(',', modifiers?.Select(m => m.ToString()) ?? Array.Empty<string>())}", cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError($"Received non-success response {response.StatusCode}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
var namePayload = payload.Deserialize<NamePayload>();
|
||||
if (namePayload is null ||
|
||||
|
||||
@@ -6,14 +6,17 @@ using Daybreak.Services.ApplicationLauncher;
|
||||
using Daybreak.Services.BuildTemplates;
|
||||
using Daybreak.Services.Experience;
|
||||
using Daybreak.Services.Navigation;
|
||||
using Daybreak.Services.Notifications;
|
||||
using Daybreak.Services.Scanner;
|
||||
using Daybreak.Views.Trade;
|
||||
using LiveChartsCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -29,10 +32,12 @@ public partial class FocusView : UserControl
|
||||
{
|
||||
private const string NamePlaceholder = "[NamePlaceholder]";
|
||||
private const string WikiUrl = "https://wiki.guildwars.com/wiki/[NamePlaceholder]";
|
||||
private const int MaxRetries = 5;
|
||||
|
||||
private static readonly TimeSpan UninitializedBackoff = TimeSpan.FromSeconds(15);
|
||||
private static readonly TimeSpan GameDataFrequency = TimeSpan.FromSeconds(1);
|
||||
|
||||
private readonly INotificationService notificationService;
|
||||
private readonly IBuildTemplateManager buildTemplateManager;
|
||||
private readonly IApplicationLauncher applicationLauncher;
|
||||
private readonly IGuildwarsMemoryCache guildwarsMemoryCache;
|
||||
@@ -83,6 +88,7 @@ public partial class FocusView : UserControl
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
public FocusView(
|
||||
INotificationService notificationService,
|
||||
IBuildTemplateManager buildTemplateManager,
|
||||
IApplicationLauncher applicationLauncher,
|
||||
IGuildwarsMemoryCache guildwarsMemoryCache,
|
||||
@@ -91,6 +97,7 @@ public partial class FocusView : UserControl
|
||||
ILiveUpdateableOptions<FocusViewOptions> liveUpdateableOptions,
|
||||
ILogger<FocusView> logger)
|
||||
{
|
||||
this.notificationService = notificationService.ThrowIfNull();
|
||||
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
|
||||
this.applicationLauncher = applicationLauncher.ThrowIfNull();
|
||||
this.guildwarsMemoryCache = guildwarsMemoryCache.ThrowIfNull();
|
||||
@@ -135,7 +142,7 @@ public partial class FocusView : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
var pathingMeta = await this.guildwarsMemoryCache.ReadPathingMetaData(this.cancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
var pathingMeta = await this.guildwarsMemoryCache.ReadPathingMetaData(this.cancellationTokenSource?.Token ?? CancellationToken.None) ?? throw new HttpRequestException();
|
||||
if (pathingMeta?.TrapezoidCount == this.PathingData?.Trapezoids?.Count ||
|
||||
this.cancellationTokenSource?.IsCancellationRequested is not false)
|
||||
{
|
||||
@@ -144,7 +151,7 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
|
||||
await this.Dispatcher.InvokeAsync(() => this.LoadingPathingData = true);
|
||||
var maybePathingData = await this.guildwarsMemoryCache.ReadPathingData(this.cancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
var maybePathingData = await this.guildwarsMemoryCache.ReadPathingData(this.cancellationTokenSource?.Token ?? CancellationToken.None) ?? throw new HttpRequestException();
|
||||
if (maybePathingData is not PathingData pathingData ||
|
||||
pathingData.Trapezoids is null ||
|
||||
pathingData.Trapezoids.Count == 0)
|
||||
@@ -162,6 +169,7 @@ public partial class FocusView : UserControl
|
||||
private async void PeriodicallyReadPathingData(CancellationToken cancellationToken)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.PeriodicallyReadPathingData), string.Empty);
|
||||
var retries = 0;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
@@ -179,6 +187,7 @@ public partial class FocusView : UserControl
|
||||
await Task.WhenAll(
|
||||
this.UpdatePathingData(),
|
||||
Task.Delay(1000, cancellationToken)).ConfigureAwait(true);
|
||||
retries = 0;
|
||||
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
@@ -186,7 +195,7 @@ public partial class FocusView : UserControl
|
||||
scopedLogger.LogError(ex, "Encountered invalid operation exception. Cancelling periodic reading");
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or HttpRequestException)
|
||||
{
|
||||
if (this.DataContext is not GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
@@ -200,8 +209,20 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
catch (InvalidOperationException innerEx)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
retries++;
|
||||
if (retries >= MaxRetries)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Returning to launcher view");
|
||||
this.notificationService.NotifyError(
|
||||
title: "GuildWars unresponsive",
|
||||
description: "Could not connect to Guild Wars instance. Returning to Launcher view");
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -214,6 +235,7 @@ public partial class FocusView : UserControl
|
||||
private async void PeriodicallyReadGameState(CancellationToken cancellationToken)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.PeriodicallyReadGameState), string.Empty);
|
||||
var retries = 0;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
@@ -233,21 +255,17 @@ public partial class FocusView : UserControl
|
||||
readGameStateTask,
|
||||
Task.Delay(16, cancellationToken)).ConfigureAwait(true);
|
||||
|
||||
var maybeGameState = await readGameStateTask;
|
||||
if (maybeGameState is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var maybeGameState = await readGameStateTask ?? throw new HttpRequestException();
|
||||
this.GameState = maybeGameState;
|
||||
this.CanRotateMinimap = this.liveUpdateableOptions.Value.MinimapRotationEnabled;
|
||||
retries = 0;
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
scopedLogger.LogError(ex, "Encountered invalid operation exception. Cancelling periodic reading");
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or HttpRequestException)
|
||||
{
|
||||
if (this.DataContext is not GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
@@ -261,8 +279,20 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
catch (InvalidOperationException innerEx)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
retries++;
|
||||
if (retries >= MaxRetries)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Returning to launcher view");
|
||||
this.notificationService.NotifyError(
|
||||
title: "GuildWars unresponsive",
|
||||
description: "Could not connect to Guild Wars instance. Returning to Launcher view");
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -275,6 +305,7 @@ public partial class FocusView : UserControl
|
||||
private async void PeriodicallyReadGameData(CancellationToken cancellationToken)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.PeriodicallyReadGameData), string.Empty);
|
||||
var retries = 0;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
@@ -294,7 +325,7 @@ public partial class FocusView : UserControl
|
||||
readGameDataTask,
|
||||
Task.Delay(GameDataFrequency, cancellationToken)).ConfigureAwait(true);
|
||||
|
||||
var maybeGameData = await readGameDataTask;
|
||||
var maybeGameData = await readGameDataTask ?? throw new HttpRequestException();
|
||||
if (maybeGameData is null)
|
||||
{
|
||||
continue;
|
||||
@@ -323,13 +354,14 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
|
||||
this.GameData = maybeGameData;
|
||||
retries = 0;
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
scopedLogger.LogError(ex, "Encountered invalid operation exception. Cancelling periodic reading");
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or HttpRequestException)
|
||||
{
|
||||
if (this.DataContext is not GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
@@ -343,8 +375,20 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
catch (InvalidOperationException innerEx)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
retries++;
|
||||
if (retries >= MaxRetries)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Returning to launcher view");
|
||||
this.notificationService.NotifyError(
|
||||
title: "GuildWars unresponsive",
|
||||
description: "Could not connect to Guild Wars instance. Returning to Launcher view");
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -357,6 +401,7 @@ public partial class FocusView : UserControl
|
||||
private async void PeriodicallyReadInventoryData(CancellationToken cancellationToken)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.PeriodicallyReadInventoryData), string.Empty);
|
||||
var retries = 0;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
@@ -376,15 +421,16 @@ public partial class FocusView : UserControl
|
||||
readInventoryTask,
|
||||
Task.Delay(1000, cancellationToken)).ConfigureAwait(true);
|
||||
|
||||
var maybeInventoryData = await readInventoryTask;
|
||||
var maybeInventoryData = await readInventoryTask ?? throw new HttpRequestException();
|
||||
if (maybeInventoryData is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.InventoryData = maybeInventoryData;
|
||||
retries = 0;
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or HttpRequestException)
|
||||
{
|
||||
if (this.DataContext is not GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
@@ -398,8 +444,20 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
catch (InvalidOperationException innerEx)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
retries++;
|
||||
if (retries >= MaxRetries)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Returning to launcher view");
|
||||
this.notificationService.NotifyError(
|
||||
title: "GuildWars unresponsive",
|
||||
description: "Could not connect to Guild Wars instance. Returning to Launcher view");
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -412,6 +470,7 @@ public partial class FocusView : UserControl
|
||||
private async void PeriodicallyReadMainPlayerContextData(CancellationToken cancellationToken)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.PeriodicallyReadMainPlayerContextData), string.Empty);
|
||||
var retries = 0;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
@@ -443,9 +502,9 @@ public partial class FocusView : UserControl
|
||||
readMainPlayerDataTask,
|
||||
Task.Delay(16, cancellationToken)).ConfigureAwait(true);
|
||||
|
||||
var userData = await readUserDataTask;
|
||||
var sessionData = await readSessionDataTask;
|
||||
var mainPlayerData = await readMainPlayerDataTask;
|
||||
var userData = await readUserDataTask ?? throw new HttpRequestException();
|
||||
var sessionData = await readSessionDataTask ?? throw new HttpRequestException();
|
||||
var mainPlayerData = await readMainPlayerDataTask ?? throw new HttpRequestException();
|
||||
if (userData?.User is null ||
|
||||
sessionData?.Session is null ||
|
||||
mainPlayerData?.PlayerInformation is null ||
|
||||
@@ -466,13 +525,14 @@ public partial class FocusView : UserControl
|
||||
|
||||
this.MainPlayerDataValid = true;
|
||||
this.Browser.Visibility = this.minimapMaximized ? Visibility.Hidden : Visibility.Visible;
|
||||
retries = 0;
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
scopedLogger.LogError(ex, "Encountered invalid operation exception. Cancelling periodic main player reading");
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or HttpRequestException)
|
||||
{
|
||||
if (this.DataContext is not GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
@@ -486,8 +546,20 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
catch (InvalidOperationException innerEx)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
retries++;
|
||||
if (retries >= MaxRetries)
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Returning to launcher view");
|
||||
this.notificationService.NotifyError(
|
||||
title: "GuildWars unresponsive",
|
||||
description: "Could not connect to Guild Wars instance. Returning to Launcher view");
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
scopedLogger.LogError(innerEx, "Could not ensure connection is initialized. Backing off before retrying");
|
||||
await Task.Delay(UninitializedBackoff, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -101,7 +101,7 @@ public partial class LauncherView : UserControl
|
||||
{
|
||||
if (!this.launching)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = this.LatestConfiguration?.CanLaunch ?? false);
|
||||
await this.SetLaunchButtonState();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), this.cancellationTokenSource.Token);
|
||||
@@ -127,73 +127,110 @@ public partial class LauncherView : UserControl
|
||||
|
||||
private async void DropDownButton_SelectionChanged(object _, object e)
|
||||
{
|
||||
if (e is not LauncherViewContext context)
|
||||
if (e is not LauncherViewContext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.LatestConfiguration is null ||
|
||||
this.LatestConfiguration.CanLaunch is false)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = true);
|
||||
}
|
||||
await this.SetLaunchButtonState();
|
||||
}
|
||||
|
||||
private async void DropDownButton_Clicked(object _, object e)
|
||||
{
|
||||
this.launching = true;
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
var launchingTask = await new TaskFactory().StartNew(async () =>
|
||||
|
||||
if (this.LatestConfiguration.CanKill)
|
||||
{
|
||||
var latestConfig = await this.Dispatcher.InvokeAsync(() => this.LatestConfiguration);
|
||||
if (this.applicationLauncher.GetGuildwarsProcess(latestConfig.Configuration!) is GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
// Detected already running guildwars process
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
if (this.focusViewOptions.Value.Enabled)
|
||||
{
|
||||
this.menuService.CloseMenu();
|
||||
this.viewManager.ShowView<FocusView>(context);
|
||||
}
|
||||
|
||||
this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(latestConfig.Configuration!);
|
||||
return;
|
||||
}
|
||||
|
||||
var killingTask = await new TaskFactory().StartNew(this.KillGuildWars, TaskCreationOptions.LongRunning);
|
||||
try
|
||||
{
|
||||
var launchedContext = await this.applicationLauncher.LaunchGuildwars(latestConfig.Configuration!);
|
||||
if (launchedContext is null)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(latestConfig.Configuration!);
|
||||
if (this.focusViewOptions.Value.Enabled)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
this.menuService.CloseMenu();
|
||||
this.viewManager.ShowView<FocusView>(launchedContext);
|
||||
}
|
||||
await killingTask;
|
||||
}
|
||||
catch (Exception)
|
||||
catch
|
||||
{
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
|
||||
try
|
||||
{
|
||||
await launchingTask;
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
var launchingTask = await new TaskFactory().StartNew(this.LaunchGuildWars, TaskCreationOptions.LongRunning);
|
||||
try
|
||||
{
|
||||
await launchingTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
this.launching = false;
|
||||
}
|
||||
|
||||
private async Task SetLaunchButtonState()
|
||||
{
|
||||
if (this.LatestConfiguration is null)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.LatestConfiguration.CanKill is true)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = true);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = this.LatestConfiguration.CanLaunch);
|
||||
}
|
||||
|
||||
private async Task KillGuildWars()
|
||||
{
|
||||
var latestConfig = await this.Dispatcher.InvokeAsync(() => this.LatestConfiguration);
|
||||
var context = this.applicationLauncher.GetGuildwarsProcess(latestConfig.Configuration!);
|
||||
if (context is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.applicationLauncher.KillGuildWarsProcess(context);
|
||||
}
|
||||
|
||||
private async Task LaunchGuildWars()
|
||||
{
|
||||
var latestConfig = await this.Dispatcher.InvokeAsync(() => this.LatestConfiguration);
|
||||
if (this.applicationLauncher.GetGuildwarsProcess(latestConfig.Configuration!) is GuildWarsApplicationLaunchContext context)
|
||||
{
|
||||
// Detected already running guildwars process
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
if (this.focusViewOptions.Value.Enabled)
|
||||
{
|
||||
this.menuService.CloseMenu();
|
||||
this.viewManager.ShowView<FocusView>(context);
|
||||
}
|
||||
|
||||
this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(latestConfig.Configuration!);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var launchedContext = await this.applicationLauncher.LaunchGuildwars(latestConfig.Configuration!);
|
||||
if (launchedContext is null)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(latestConfig.Configuration!);
|
||||
if (this.focusViewOptions.Value.Enabled)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.CanLaunch = false);
|
||||
this.menuService.CloseMenu();
|
||||
this.viewManager.ShowView<FocusView>(launchedContext);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Extensions;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -31,6 +32,7 @@ public partial class LogsView : UserControl
|
||||
private readonly SimpleHighlightingBrush simpleOrangeHighlightingBrush;
|
||||
private readonly ILogsManager logManager;
|
||||
private readonly ILogger<LogsView> logger;
|
||||
private readonly SemaphoreSlim semaphoreSlim = new(1, 1);
|
||||
|
||||
private ScrollViewer? textEditorScrollViewer;
|
||||
|
||||
@@ -56,8 +58,9 @@ public partial class LogsView : UserControl
|
||||
|
||||
private async void UpdateLogs()
|
||||
{
|
||||
var logs = this.logManager.GetLogs(l => l.LogLevel < LogLevel.Trace).ToArray();
|
||||
var logs = this.logManager.GetLogs().ToArray();
|
||||
this.TextEditor.Clear();
|
||||
this.cachedText.Clear();
|
||||
if (logs.Length > MaximumLookbackPeriod)
|
||||
{
|
||||
var maximumLookbackPeriodMessage = string.Format(MaximumLookbackMessageTemplate, MaximumLookbackPeriod);
|
||||
@@ -72,8 +75,9 @@ public partial class LogsView : UserControl
|
||||
|
||||
private async Task WriteLogs(bool forceScrollToEnd, params Log[] logs)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() =>
|
||||
await this.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
await this.semaphoreSlim.WaitAsync();
|
||||
foreach (var log in logs)
|
||||
{
|
||||
var logTimeComponent = $"[{log.LogTime}]\t";
|
||||
@@ -110,6 +114,8 @@ public partial class LogsView : UserControl
|
||||
{
|
||||
this.TextEditor.ScrollToEnd();
|
||||
}
|
||||
|
||||
this.semaphoreSlim.Release();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,6 +154,7 @@ public partial class LogsView : UserControl
|
||||
.GetField("scrollViewer", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?
|
||||
.GetValue(this.TextEditor)?.Cast<ScrollViewer>();
|
||||
this.logManager.ReceivedLog += this.LogManager_ReceivedLog;
|
||||
this.UpdateLogs();
|
||||
}
|
||||
|
||||
private void LogsView_Unloaded(object sender, RoutedEventArgs _)
|
||||
|
||||
Reference in New Issue
Block a user