Initial commit (1.0.0)
Build and Relase / build-release (push) Failing after 38s
Build and Relase / create-release (push) Failing after 10s

This commit is contained in:
2025-01-13 16:27:29 +01:00
commit 26cde137c9
36 changed files with 4875 additions and 0 deletions
@@ -0,0 +1,94 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
namespace SoraV2Utils_Service
{
internal class AgentProcessHandler
{
private Process _notificationHandlerProcess;
public AgentProcessHandler()
{
RunAgentHandler();
}
private void RunAgentHandler()
{
string notificationHandlerPath = GetNotificationHandlerPath();
if (LaunchProcess(notificationHandlerPath))
{
_notificationHandlerProcess = GetRunningProcess("SoraV2Utils_Agent");
if (_notificationHandlerProcess != null)
{
_notificationHandlerProcess.Exited += OnNotificationHandlerExited;
_notificationHandlerProcess.EnableRaisingEvents = true;
}
}
else
{
Console.WriteLine("Failed to start the notification handler process.");
}
}
private string GetNotificationHandlerPath()
{
return AppDomain.CurrentDomain.BaseDirectory + "\\SoraV2Utils_Agent.exe";
}
private bool LaunchProcess(string path)
{
try
{
ApplicationLauncher.CreateProcessInConsoleSession(path, true);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Error launching process: {ex.Message}");
return false;
}
}
private Process GetRunningProcess(string processName)
{
return Process.GetProcessesByName(processName).FirstOrDefault();
}
private void OnNotificationHandlerExited(object sender, EventArgs e)
{
Console.WriteLine("Process exited. Restarting...");
RestartNotificationHandler();
}
private void RestartNotificationHandler()
{
Thread.Sleep(1000); // Delay before restarting the process
RunAgentHandler();
}
public void StopNotificationHandler()
{
if (_notificationHandlerProcess != null && !_notificationHandlerProcess.HasExited)
{
_notificationHandlerProcess.Kill();
_notificationHandlerProcess.WaitForExit(); // Ensure the process is fully terminated
Console.WriteLine("Process stopped.");
}
UnsubscribeFromExitEvent();
}
private void UnsubscribeFromExitEvent()
{
if (_notificationHandlerProcess != null)
{
_notificationHandlerProcess.Exited -= OnNotificationHandlerExited;
Console.WriteLine("Event handler unregistered.");
}
}
}
}
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
+469
View File
@@ -0,0 +1,469 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace SoraV2Utils_Service
{
internal class ApplicationLauncher
{
public enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
MaxTokenInfoClass // MaxTokenInfoClass should always be the last enum
}
public const int READ_CONTROL = 0x00020000;
public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;
public const int STANDARD_RIGHTS_READ = READ_CONTROL;
public const int STANDARD_RIGHTS_WRITE = READ_CONTROL;
public const int STANDARD_RIGHTS_EXECUTE = READ_CONTROL;
public const int STANDARD_RIGHTS_ALL = 0x001F0000;
public const int SPECIFIC_RIGHTS_ALL = 0x0000FFFF;
public const int TOKEN_ASSIGN_PRIMARY = 0x0001;
public const int TOKEN_DUPLICATE = 0x0002;
public const int TOKEN_IMPERSONATE = 0x0004;
public const int TOKEN_QUERY = 0x0008;
public const int TOKEN_QUERY_SOURCE = 0x0010;
public const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
public const int TOKEN_ADJUST_GROUPS = 0x0040;
public const int TOKEN_ADJUST_DEFAULT = 0x0080;
public const int TOKEN_ADJUST_SESSIONID = 0x0100;
public const int TOKEN_ALL_ACCESS_P = (STANDARD_RIGHTS_REQUIRED |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_IMPERSONATE |
TOKEN_QUERY |
TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT);
public const int TOKEN_ALL_ACCESS = TOKEN_ALL_ACCESS_P | TOKEN_ADJUST_SESSIONID;
public const int TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;
public const int TOKEN_WRITE = STANDARD_RIGHTS_WRITE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT;
public const int TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE;
public const uint MAXIMUM_ALLOWED = 0x2000000;
public const int CREATE_NEW_PROCESS_GROUP = 0x00000200;
public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
public const int IDLE_PRIORITY_CLASS = 0x40;
public const int NORMAL_PRIORITY_CLASS = 0x20;
public const int HIGH_PRIORITY_CLASS = 0x80;
public const int REALTIME_PRIORITY_CLASS = 0x100;
public const int CREATE_NEW_CONSOLE = 0x00000010;
public const string SE_DEBUG_NAME = "SeDebugPrivilege";
public const string SE_RESTORE_NAME = "SeRestorePrivilege";
public const string SE_BACKUP_NAME = "SeBackupPrivilege";
public const int SE_PRIVILEGE_ENABLED = 0x0002;
public const int ERROR_NOT_ALL_ASSIGNED = 1300;
private const uint TH32CS_SNAPPROCESS = 0x00000002;
public static int INVALID_HANDLE_VALUE = -1;
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpname,
[MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
public static extern bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,
ref uint TokenInformation, uint TokenInformationLength);
[DllImport("userenv.dll", SetLastError = true)]
public static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
public static bool CreateProcessInConsoleSession(String CommandLine, bool bElevate)
{
PROCESS_INFORMATION pi;
bool bResult = false;
uint dwSessionId, winlogonPid = 0;
IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
Debug.Print("CreateProcessInConsoleSession");
// Log the client on to the local computer.
dwSessionId = WTSGetActiveConsoleSessionId();
// Find the winlogon process
var procEntry = new PROCESSENTRY32();
uint hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
{
return false;
}
procEntry.dwSize = (uint)Marshal.SizeOf(procEntry); //sizeof(PROCESSENTRY32);
if (Process32First(hSnap, ref procEntry) == 0)
{
return false;
}
String strCmp = "explorer.exe";
do
{
if (strCmp.IndexOf(procEntry.szExeFile) == 0)
{
// We found a winlogon process...make sure it's running in the console session
uint winlogonSessId = 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, ref winlogonSessId) &&
winlogonSessId == dwSessionId)
{
winlogonPid = procEntry.th32ProcessID;
break;
}
}
}
while (Process32Next(hSnap, ref procEntry) != 0);
//Get the user token used by DuplicateTokenEx
WTSQueryUserToken(dwSessionId, ref hUserToken);
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "winsta0\\default";
var tp = new TOKEN_PRIVILEGES();
var luid = new LUID();
hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
if (
!OpenProcessToken(hProcess,
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY
| TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, ref hPToken))
{
Debug.Print(String.Format("CreateProcessInConsoleSession OpenProcessToken error: {0}",
Marshal.GetLastWin32Error()));
}
if (!LookupPrivilegeValue(IntPtr.Zero, SE_DEBUG_NAME, ref luid))
{
Debug.Print(String.Format("CreateProcessInConsoleSession LookupPrivilegeValue error: {0}",
Marshal.GetLastWin32Error()));
}
var sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary,
ref hUserTokenDup))
{
Debug.Print(
String.Format(
"CreateProcessInConsoleSession DuplicateTokenEx error: {0} Token does not have the privilege.",
Marshal.GetLastWin32Error()));
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hPToken);
return false;
}
if (bElevate)
{
//tp.Privileges[0].Luid = luid;
//tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.PrivilegeCount = 1;
tp.Privileges = new int[3];
tp.Privileges[2] = SE_PRIVILEGE_ENABLED;
tp.Privileges[1] = luid.HighPart;
tp.Privileges[0] = luid.LowPart;
//Adjust Token privilege
if (
!SetTokenInformation(hUserTokenDup, TOKEN_INFORMATION_CLASS.TokenSessionId, ref dwSessionId,
(uint)IntPtr.Size))
{
Debug.Print(
String.Format(
"CreateProcessInConsoleSession SetTokenInformation error: {0} Token does not have the privilege.",
Marshal.GetLastWin32Error()));
//CloseHandle(hProcess);
//CloseHandle(hUserToken);
//CloseHandle(hPToken);
//CloseHandle(hUserTokenDup);
//return false;
}
if (
!AdjustTokenPrivileges(hUserTokenDup, false, ref tp, Marshal.SizeOf(tp), /*(PTOKEN_PRIVILEGES)*/
IntPtr.Zero, IntPtr.Zero))
{
int nErr = Marshal.GetLastWin32Error();
if (nErr == ERROR_NOT_ALL_ASSIGNED)
{
Debug.Print(
String.Format(
"CreateProcessInConsoleSession AdjustTokenPrivileges error: {0} Token does not have the privilege.",
nErr));
}
else
{
Debug.Print(String.Format("CreateProcessInConsoleSession AdjustTokenPrivileges error: {0}", nErr));
}
}
}
uint dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
IntPtr pEnv = IntPtr.Zero;
if (CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true))
{
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
}
else
{
pEnv = IntPtr.Zero;
}
// Launch the process in the client's logon session.
bResult = CreateProcessAsUser(hUserTokenDup, // client's access token
CommandLine, // file to execute
null, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
(int)dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
null, // name of current directory
ref si, // pointer to STARTUPINFO structure
out pi // receives information about new process
);
// End impersonation of client.
//GetLastError should be 0
int iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
//Close handles task
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
CloseHandle(hPToken);
return (iResultOfCreateProcessAsUser == 0) ? true : false;
}
[DllImport("kernel32.dll")]
private static extern int Process32First(uint hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll")]
private static extern int Process32Next(uint hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hSnapshot);
[DllImport("kernel32.dll")]
private static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
[DllImport("kernel32.dll")]
private static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("advapi32", SetLastError = true)]
[SuppressUnmanagedCodeSecurity]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, // handle to process
int DesiredAccess, // desired access to process
ref IntPtr TokenHandle);
#region Nested type: LUID
[StructLayout(LayoutKind.Sequential)]
internal struct LUID
{
public int LowPart;
public int HighPart;
}
#endregion
//end struct
#region Nested type: LUID_AND_ATRIBUTES
[StructLayout(LayoutKind.Sequential)]
internal struct LUID_AND_ATRIBUTES
{
public LUID Luid;
public int Attributes;
}
#endregion
#region Nested type: PROCESSENTRY32
[StructLayout(LayoutKind.Sequential)]
private struct PROCESSENTRY32
{
public uint dwSize;
public readonly uint cntUsage;
public readonly uint th32ProcessID;
public readonly IntPtr th32DefaultHeapID;
public readonly uint th32ModuleID;
public readonly uint cntThreads;
public readonly uint th32ParentProcessID;
public readonly int pcPriClassBase;
public readonly uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public readonly string szExeFile;
}
#endregion
#region Nested type: PROCESS_INFORMATION
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
#endregion
#region Nested type: SECURITY_ATTRIBUTES
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
#endregion
#region Nested type: SECURITY_IMPERSONATION_LEVEL
private enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
}
#endregion
#region Nested type: STARTUPINFO
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
#endregion
#region Nested type: TOKEN_PRIVILEGES
[StructLayout(LayoutKind.Sequential)]
internal struct TOKEN_PRIVILEGES
{
internal int PrivilegeCount;
//LUID_AND_ATRIBUTES
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
internal int[] Privileges;
}
#endregion
#region Nested type: TOKEN_TYPE
private enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
#endregion
// handle to open access token
}
}
+135
View File
@@ -0,0 +1,135 @@
using EasyPipes;
using SoraV2Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SoraV2Utils_Service
{
public class DeviceTracker
{
public static DeviceTracker Instance;
private bool BatteryWarningSent = false;
private bool BatteryCriticalSent = false;
private bool RechargeCompletedSent = false;
private Dictionary<DateTime, byte> BatteryStats = new Dictionary<DateTime, byte>();
public DeviceStatus _DeviceStatus;
public DeviceTracker()
{
Instance = this;
}
public void processMouseData(DeviceStatus deviceStatus)
{
_DeviceStatus = deviceStatus;
if (deviceStatus.Charging == 0)
{
BatteryStats.Add(DateTime.Now, deviceStatus.Battery);
RechargeCompletedSent = false;
if (deviceStatus.Battery <= ServiceSettings.Instance.criticalThreshold && !BatteryCriticalSent && deviceStatus.Charging != 1)
{
Notification.TwoLine(String.Format("SoraV2 battery is at {0}%",deviceStatus.Battery), "Please recharge now!");
BatteryCriticalSent = true;
}
else if (deviceStatus.Battery <= ServiceSettings.Instance.warningThreshold && !BatteryCriticalSent && !BatteryWarningSent && deviceStatus.Charging != 1)
{
Notification.TwoLine(String.Format("SoraV2 battery is at {0}%", deviceStatus.Battery), "Please consider recharching.");
BatteryWarningSent = true;
}
}
else if (deviceStatus.Charging == 1 && !RechargeCompletedSent)
{
BatteryStats.Clear();
BatteryWarningSent = false;
BatteryCriticalSent = false;
RechargeCompletedSent = true;
if (deviceStatus.Battery == 100)
{
Notification.SingleLine(String.Format("SoraV2 battery fully recharged!"));
}
}
}
public double CalculateConsumptionRate()
{
// Ensure there's enough data to calculate a rate (at least two points)
if (this.BatteryStats.Count > 2)
{
// Get the last two entries in the dictionary
var latestEntry = this.BatteryStats.Last();
var previousEntry = this.BatteryStats.ElementAt(this.BatteryStats.Count - 2);
// Calculate the difference in battery level and time
double batteryLevelDifference = previousEntry.Value - latestEntry.Value;
double timeDifferenceMinutes = (latestEntry.Key - previousEntry.Key).TotalMinutes;
// Calculate consumption rate (percentage per minute)
return batteryLevelDifference / timeDifferenceMinutes;
}
return 0;
}
public double EstimateRemainingRuntime()
{
if (this.BatteryStats == null || this.BatteryStats.Count < 2)
{
return -1;
}
var sortedStats = new SortedDictionary<DateTime, byte>(this.BatteryStats);
double totalConsumptionRate = 0;
int numberOfIntervals = 0;
DateTime previousTime = DateTime.MinValue;
byte previousBatteryLevel = 0;
// Calculate the average consumption rate per second
foreach (var entry in sortedStats)
{
if (previousTime != DateTime.MinValue)
{
// Calculate the time difference (in seconds)
double timeDifferenceInSeconds = (entry.Key - previousTime).TotalSeconds;
// Calculate the battery consumption for this interval
int batteryDifference = previousBatteryLevel - entry.Value;
// Ensure the consumption is positive (in case of rounding errors)
if (batteryDifference > 0)
{
// Calculate consumption rate per second
double consumptionRate = batteryDifference / timeDifferenceInSeconds;
totalConsumptionRate += consumptionRate;
numberOfIntervals++;
}
}
previousTime = entry.Key;
previousBatteryLevel = entry.Value;
}
// Calculate the average consumption rate
double averageConsumptionRate = totalConsumptionRate / numberOfIntervals;
// Get the current battery level (from the last entry in the sorted dictionary)
byte currentBatteryLevel = sortedStats.Values.Last();
// Calculate the remaining runtime (in seconds)
double remainingRuntimeInSeconds = currentBatteryLevel / averageConsumptionRate;
// Convert seconds to a more human-readable format (hours, minutes, seconds)
TimeSpan remainingTime = TimeSpan.FromSeconds(remainingRuntimeInSeconds);
Console.WriteLine($"Estimated remaining battery runtime: {remainingTime.Hours} hours, {remainingTime.Minutes} minutes, {remainingTime.Seconds} seconds.");
return remainingRuntimeInSeconds;
}
}
}
+75
View File
@@ -0,0 +1,75 @@
using EasyPipes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace SoraV2Utils_Service
{
public interface IToastNotification
{
void SendToastNotification(string xml, string title);
}
public class Notification
{
public static void SingleLine(string line1)
{
var client = new Client("SoraV2UtilsNotifcation");
var service = client.GetServiceProxy<IToastNotification>();
string xml = @"
<toast>
<visual>
<binding template='ToastImageAndText01'>
<text id='1'>{0}</text>
</binding>
</visual>
<audio src='ms-winsoundevent:Notification.Default' />
</toast>";
xml = String.Format(xml, line1);
try
{
service.SendToastNotification(xml, "SoraV2 Utils");
}
catch (System.TimeoutException)
{
ServiceLogger.Instance.Log("SoraV2Utils_Agent is not responding...");
}
}
public static void TwoLine(string line1, string line2)
{
var client = new Client("SoraV2UtilsNotifcation");
var service = client.GetServiceProxy<IToastNotification>();
string xml = @"
<toast>
<visual>
<binding template='ToastImageAndText02'>
<text id='1'>{0}</text>
<text id='2'>{1}</text>
</binding>
</visual>
<audio src='ms-winsoundevent:Notification.Default' />
</toast>";
xml = String.Format(xml, line1, line2);
try
{
service.SendToastNotification(xml, "SoraV2 Utils");
}
catch (System.TimeoutException)
{
ServiceLogger.Instance.Log("SoraV2Utils_Agent is not responding...");
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
namespace SoraV2Utils_Service
{
internal static class Program
{
static void Main()
{
new ServiceLogger();
new ServiceSettings();
new AgentProcessHandler();
new ServiceIPC();
//System.Threading.Thread.Sleep(1000);
//Service1 service = new Service1();
//service.OnDebug();
//System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new SoraV2UtilsService()
};
ServiceBase.Run(ServicesToRun);
}
}
}
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die einer Assembly zugeordnet sind.
[assembly: AssemblyTitle("SoraV2Utils_Service")]
[assembly: AssemblyDescription("Service for SoraV2 Utils")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SoraV2Utils_Service")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("8eab0571-6603-4ecf-ad49-93c2b8f6c94c")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
// indem Sie "*" wie unten gezeigt eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+135
View File
@@ -0,0 +1,135 @@
using EasyPipes;
using SoraV2Tools;
using System;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
namespace SoraV2Utils_Service
{
// Interface for mouse data interaction
public interface IMouseData
{
byte[] GetDeviceStatus();
double GetBatteryRuntime();
void Exit();
}
public class ServiceIPC
{
// Creates pipe security for system IO
private PipeSecurity CreateSystemIOPipeSecurity()
{
PipeSecurity pipeSecurity = new PipeSecurity();
// Assign authenticated user read-write access
var authenticatedUserSid = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
pipeSecurity.SetAccessRule(new PipeAccessRule(authenticatedUserSid, PipeAccessRights.ReadWrite, AccessControlType.Allow));
return pipeSecurity;
}
// Opens a custom named pipe with the specified name and buffer size
private NamedPipeServerStream OpenCustomPipe(string pipeName, int bufferSize)
{
try
{
var pipeSecurity = CreateSystemIOPipeSecurity();
var pipeStream = new NamedPipeServerStream(pipeName,
PipeDirection.InOut,
1,
PipeTransmissionMode.Message,
PipeOptions.Asynchronous,
bufferSize,
0x400,
pipeSecurity,
HandleInheritability.Inheritable);
Console.WriteLine($"Named Pipe '{pipeName}' successfully opened.");
return pipeStream;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error opening pipe {pipeName}: {ex.Message}");
throw;
}
}
// Constructor for initializing the IPC service and server
public ServiceIPC()
{
try
{
var server = new Server("SoraV2UtilsDeviceData", OpenCustomPipe);
server.RegisterService<IMouseData>(new ServiceIPCHandler());
server.Start();
Console.WriteLine("IPC Service started successfully.");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Failed to start IPC Service: {ex.Message}");
}
}
}
// Handler class to implement the IMouseData interface
public class ServiceIPCHandler : IMouseData
{
public byte[] GetDeviceStatus()
{
var status = DeviceTracker.Instance._DeviceStatus;
try
{
int size = Marshal.SizeOf(status);
byte[] arr = new byte[size];
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(status, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return arr;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error retrieving battery level: {ex.Message}");
return null;
}
}
public double GetBatteryRuntime()
{
try
{
return DeviceTracker.Instance?.EstimateRemainingRuntime() ?? 0.0;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error estimating battery runtime: {ex.Message}");
return 0.0;
}
}
public void Exit()
{
try
{
// Exit the application with code 1
Console.WriteLine("Exiting application...");
Environment.Exit(1);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error during exit: {ex.Message}");
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
namespace SoraV2Utils_Service
{
partial class ServiceInstaller
{
/// <summary>
/// Erforderliche Designervariable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
/// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>
private void InitializeComponent()
{
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller1
//
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
//
// serviceInstaller1
//
this.serviceInstaller1.Description = "SoraV2 Utils Service";
this.serviceInstaller1.DisplayName = "SoraV2 Utils Service";
this.serviceInstaller1.ServiceName = "SoraV2Utils_Service";
this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1,
this.serviceInstaller1});
this.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.ProjectInstaller_AfterInstall);
this.BeforeUninstall += new System.Configuration.Install.InstallEventHandler(this.ProjectInstaller_BeforeUninstall);
}
#endregion
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
}
}
+46
View File
@@ -0,0 +1,46 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Threading.Tasks;
namespace SoraV2Utils_Service
{
[RunInstaller(true)]
public partial class ServiceInstaller : System.Configuration.Install.Installer
{
public ServiceInstaller()
{
InitializeComponent();
}
private void ProjectInstaller_AfterInstall(object sender, InstallEventArgs e)
{
System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(serviceInstaller1.ServiceName);
sc.Start();
}
private void ProjectInstaller_BeforeUninstall(object sender, InstallEventArgs e)
{
try
{
using (ServiceController sv = new ServiceController(serviceInstaller1.ServiceName))
{
if (sv.Status != ServiceControllerStatus.Stopped)
{
sv.Stop();
sv.WaitForStatus(ServiceControllerStatus.Stopped);
}
}
}
catch (Exception ex)
{
EventLog.WriteEntry("SoraV2Utils_Service", ex.Message, EventLogEntryType.Warning);
}
}
}
}
+129
View File
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="serviceProcessInstaller1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 54</value>
</metadata>
<metadata name="serviceInstaller1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>194, 17</value>
</metadata>
<metadata name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
</root>
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SoraV2Utils_Service
{
public class ServiceLogger
{
public static ServiceLogger Instance { get; private set; }
string LogFilePath = "";
public ServiceLogger()
{
var logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
Directory.CreateDirectory(logDirectory); // Ensures directory exists
string logFileName = $"ServiceLog_{DateTime.Now:yyyy_MM_dd}.txt";
this.LogFilePath = Path.Combine(logDirectory, logFileName);
Instance = this;
}
public void Log(string message)
{
using (StreamWriter sw = new StreamWriter(this.LogFilePath, append: true))
{
sw.WriteLine($"[{DateTime.Now}] {message}");
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
using IniParser;
using IniParser.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Collections.Specialized.BitVector32;
namespace SoraV2Utils_Service
{
public class ServiceSettings
{
public static ServiceSettings Instance { get; private set; }
private IniData data = null;
private FileIniDataParser parser = null;
public int intervall = 60000;
public int warningThreshold = 30;
public int criticalThreshold = 10;
public ServiceSettings()
{
string serviceSettingsFile = AppDomain.CurrentDomain.BaseDirectory + "\\SoraV2Utils.ini";
this.parser = new FileIniDataParser();
this.data = ReadOrCreateConfigFile(serviceSettingsFile);
TryParseConfig<int>("SoraV2Utils", "interval", ref this.intervall);
TryParseConfig<int>("SoraV2Utils", "warningThreshold", ref this.warningThreshold);
TryParseConfig<int>("SoraV2Utils", "criticalThreshold", ref this.criticalThreshold);
Instance = this;
}
public bool TryParseConfig<T>(string section, string key, ref T variable)
{
bool parseResult = false;
string value = this.data[section][key];
if (!string.IsNullOrEmpty(value))
{
try
{
variable = (T)Convert.ChangeType(value, typeof(T)); // Convert the value to the appropriate type.
parseResult = true;
}
catch (FormatException)
{
ServiceLogger.Instance.Log($"{key} setting could not be read from config! (Using default: {variable})");
}
}
return parseResult;
}
private IniData ReadOrCreateConfigFile(string filePath)
{
try
{
var data = parser.ReadFile(filePath);
if (data.Sections.Count == 0)
{
CreateServiceFile(data, filePath);
}
return data;
}
catch (IniParser.Exceptions.ParsingException)
{
var newData = new IniParser.Parser.IniDataParser().Parse("");
CreateServiceFile(newData,filePath);
return newData;
}
}
private IniData CreateServiceFile(IniData _data, string path)
{
_data.Sections.AddSection("SoraV2Utils");
_data["SoraV2Utils"].AddKey("interval", "60000");
_data["SoraV2Utils"].GetKeyData("interval").Comments.Add("Interval to check the mouse battery");
_data["SoraV2Utils"].AddKey("warningThreshold", "30");
_data["SoraV2Utils"].GetKeyData("warningThreshold").Comments.Add("Threshold for first warning");
_data["SoraV2Utils"].AddKey("criticalThreshold", "10");
_data["SoraV2Utils"].GetKeyData("criticalThreshold").Comments.Add("Threshold for second (critial) warning");
this.parser.WriteFile("SoraV2Utils.ini", _data);
return _data;
}
}
}
+102
View File
@@ -0,0 +1,102 @@
using HidLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SoraV2Tools
{
internal class SoraV2Interface
{
private const ushort VID = 0x1915;
private const ushort PID_WIRELESS = 0xAE1C;
private const ushort PID_WIRED = 0xAE11;
public static List<HidDevice> GetDevice()
{
var devices = HidDevices.Enumerate(VID, PID_WIRELESS).ToList();
return devices;
}
public static DeviceStatus GetDeviceStatus()
{
try
{
foreach (var device in GetDevice())
{
// Get the HID device
if (device == null)
{
Console.WriteLine("Device not found.");
continue;
}
// Open the device for communication
device.OpenDevice();
if (!device.IsOpen || !device.IsConnected)
{
continue;
}
// Prepare the report to send to the device
byte[] report = new byte[32];
report[0] = 5; // Report ID
report[1] = 21; // Command or action type
report[4] = 1; // Action flag
// Send the feature report to request battery info
var success = device.WriteFeatureData(report);
if (!success)
{
Console.WriteLine("Failed to send feature report.");
continue;
}
// Wait a bit for the device to respond
System.Threading.Thread.Sleep(90);
// Get the response feature report
device.ReadFeatureData(out byte[] responseData, reportId: 5);
if (responseData == null || responseData.Length < 13)
{
Console.WriteLine("Failed to read feature report.");
continue;
}
// Extract battery information from the response
byte battery = responseData[9];
byte charging = responseData[10];
byte fullCharge = responseData[11];
byte online = responseData[12];
return new DeviceStatus(battery, charging, fullCharge, online);
}
return new DeviceStatus();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return new DeviceStatus();
}
}
}
public struct DeviceStatus
{
public byte Battery;
public byte Charging;
public byte FullCharge;
public byte Online;
public DeviceStatus(byte battery, byte charging, byte fullCharge, byte online)
{
Battery = battery;
Charging = charging;
FullCharge = fullCharge;
Online = online;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
namespace SoraV2Utils_Service
{
partial class SoraV2UtilsService
{
/// <summary>
/// Erforderliche Designervariable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
/// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.ServiceName = "SoraV2Utils_Service";
}
#endregion
}
}
+65
View File
@@ -0,0 +1,65 @@
using EasyPipes;
using IniParser.Model;
using SoraV2Tools;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace SoraV2Utils_Service
{
public partial class SoraV2UtilsService : ServiceBase
{
Timer timer = new Timer();
DeviceTracker deviceTracker = new DeviceTracker();
public SoraV2UtilsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
ServiceLogger.Instance.Log("Started SoraV2Utils_Service");
Notification.SingleLine("SoraV2 Utils running...");
timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
timer.Interval = ServiceSettings.Instance.intervall;
timer.Enabled = true;
OnElapsedTime(null, null);
}
protected override void OnStop()
{
ServiceLogger.Instance.Log("Stopped SoraV2Utils_Service");
Notification.SingleLine("SoraV2 Utils stopped...");
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
var ds = SoraV2Interface.GetDeviceStatus();
//byte charging = 0;
//byte battery = 0;
//new ServiceSettings().TryParseConfig<byte>("debug", "charging", ref charging);
//new ServiceSettings().TryParseConfig<byte>("debug", "battery", ref battery);
//ds.Charging = charging;
//ds.Battery = battery;
deviceTracker.processMouseData(ds);
}
public void OnDebug()
{
OnStart(null);
}
}
}
@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8EAB0571-6603-4ECF-AD49-93C2B8F6C94C}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>SoraV2Utils_Service</RootNamespace>
<targetplatformversion>8.0</targetplatformversion>
<AssemblyName>SoraV2Utils_Service</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<IsWebBootstrapper>false</IsWebBootstrapper>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>1</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup>
<RuntimeIdentifiers>win</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<targetplatformversion>8.0</targetplatformversion>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<targetplatformversion>8.0</targetplatformversion>
<Optimize>true</Optimize>
<OutputPath>..\build\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" />
<Reference Include="Windows.Data" />
<Reference Include="Windows.UI" />
</ItemGroup>
<ItemGroup>
<Compile Include="ApplicationLauncher.cs" />
<Compile Include="DeviceTracker.cs" />
<Compile Include="ServiceIPC.cs" />
<Compile Include="AgentProcessHandler.cs" />
<Compile Include="Notification.cs" />
<Compile Include="ServiceInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ServiceInstaller.Designer.cs">
<DependentUpon>ServiceInstaller.cs</DependentUpon>
</Compile>
<Compile Include="SoraV2UtilsService.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SoraV2UtilsService.Designer.cs">
<DependentUpon>SoraV2UtilsService.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceLogger.cs" />
<Compile Include="ServiceSettings.cs" />
<Compile Include="SoraV2Interface.cs" />
</ItemGroup>
<ItemGroup>
<None Include="..\Scripts\Install-Service.ps1">
<Link>Install-Service.ps1</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ServiceInstaller.resx">
<DependentUpon>ServiceInstaller.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="EasyPipes">
<Version>1.3.0</Version>
</PackageReference>
<PackageReference Include="hidlibrary">
<Version>3.3.40</Version>
</PackageReference>
<PackageReference Include="ini-parser">
<Version>2.5.2</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 und x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>