diff --git a/.editorconfig b/.editorconfig index 0c4b20f..705667e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -103,7 +103,7 @@ end_of_line = crlf csharp_using_directive_placement = outside_namespace:suggestion csharp_prefer_simple_using_statement = false:suggestion csharp_prefer_braces = true:suggestion -csharp_style_namespace_declarations = block_scoped:silent +csharp_style_namespace_declarations = file_scoped:suggestion csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = false:silent csharp_style_expression_bodied_methods = true:silent @@ -150,3 +150,6 @@ csharp_indent_case_contents_when_block = false csharp_preserve_single_line_statements = true csharp_style_prefer_primary_constructors = false:suggestion csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_prefer_static_anonymous_function = true:suggestion +csharp_space_around_declaration_statements = ignore diff --git a/YAMDCC.Common/CommonConfig.cs b/YAMDCC.Common/CommonConfig.cs index 402adcc..aff3bed 100644 --- a/YAMDCC.Common/CommonConfig.cs +++ b/YAMDCC.Common/CommonConfig.cs @@ -20,85 +20,83 @@ using System.Xml.Serialization; using YAMDCC.Logs; -namespace YAMDCC.Common +namespace YAMDCC.Common; + +public class CommonConfig { - public class CommonConfig - { - /// - /// The config version expected when loading a config. - /// - [XmlIgnore] - public const int ExpectedVer = 1; + /// + /// The config version expected when loading a config. + /// + [XmlIgnore] + public const int ExpectedVer = 1; - /// - /// The config version. Should be the same as - /// unless the config is newer or invalid. - /// - [XmlAttribute] - public int Ver { get; set; } + /// + /// The config version. Should be the same as + /// unless the config is newer or invalid. + /// + [XmlAttribute] + public int Ver { get; set; } - /// - /// The product this was made for. - /// - [XmlAttribute] - public string App { get; set; } + /// + /// The product this was made for. + /// + [XmlAttribute] + public string App { get; set; } - /// - /// How verbose logs should be. - /// - [XmlElement] - public LogLevel LogLevel { get; set; } = LogLevel.Debug; + /// + /// How verbose logs should be. + /// + [XmlElement] + public LogLevel LogLevel { get; set; } = LogLevel.Debug; - /// - /// Loads the global app config XML and returns a - /// object. - /// - public static CommonConfig Load() + /// + /// Loads the global app config XML and returns a + /// object. + /// + public static CommonConfig Load() + { + XmlSerializer serialiser = new(typeof(CommonConfig)); + try { - XmlSerializer serialiser = new(typeof(CommonConfig)); - try + using (XmlReader reader = XmlReader.Create(Paths.GlobalConf)) { - using (XmlReader reader = XmlReader.Create(Paths.GlobalConf)) - { - CommonConfig cfg = (CommonConfig)serialiser.Deserialize(reader); - return cfg.Ver == ExpectedVer - ? cfg - : throw new InvalidConfigException(); - } - } - catch (Exception ex) - { - if (ex is FileNotFoundException - or InvalidOperationException - or InvalidConfigException) - { - return new CommonConfig(); - } - else - { - throw; - } + CommonConfig cfg = (CommonConfig)serialiser.Deserialize(reader); + return cfg.Ver == ExpectedVer + ? cfg + : throw new InvalidConfigException(); } } - - /// - /// Saves the global app config XML. - /// - /// - public void Save() + catch (Exception ex) { - XmlSerializer serializer = new(typeof(CommonConfig)); - XmlWriterSettings settings = new() + if (ex is FileNotFoundException + or InvalidOperationException + or InvalidConfigException) { - Indent = true, - IndentChars = "\t", - }; - - using (XmlWriter writer = XmlWriter.Create(Paths.GlobalConf, settings)) + return new CommonConfig(); + } + else { - serializer.Serialize(writer, this); + throw; } } + } + /// + /// Saves the global app config XML. + /// + /// + public void Save() + { + XmlSerializer serializer = new(typeof(CommonConfig)); + XmlWriterSettings settings = new() + { + Indent = true, + IndentChars = "\t", + }; + + using (XmlWriter writer = XmlWriter.Create(Paths.GlobalConf, settings)) + { + serializer.Serialize(writer, this); + } } } diff --git a/YAMDCC.Common/Dialogs/CrashDialog.cs b/YAMDCC.Common/Dialogs/CrashDialog.cs index 7f3acee..a685f5e 100644 --- a/YAMDCC.Common/Dialogs/CrashDialog.cs +++ b/YAMDCC.Common/Dialogs/CrashDialog.cs @@ -18,38 +18,37 @@ using System.Diagnostics; using System.Windows.Forms; -namespace YAMDCC.Common.Dialogs +namespace YAMDCC.Common.Dialogs; + +public sealed partial class CrashDialog : Form { - public sealed partial class CrashDialog : Form + public CrashDialog(Exception ex) { - public CrashDialog(Exception ex) - { - InitializeComponent(); - lblError.Text = Strings.GetString("Crash"); - txtReport.Text = $"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}"; - } - - private void btnReportIssue_Click(object sender, EventArgs e) - { - Process.Start($"{Paths.GitHubPage}/issues"); - } + InitializeComponent(); + lblError.Text = Strings.GetString("Crash"); + txtReport.Text = $"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}"; + } - private void btnCopy_Click(object sender, EventArgs e) - { - Clipboard.SetText(txtReport.Text); + private void btnReportIssue_Click(object sender, EventArgs e) + { + Process.Start($"{Paths.GitHubPage}/issues"); + } - // should never fail, but better safe than sorry - // (this is the crash handling dialog after all) - if (sender is Button b) - { - // give confirmation that the crash report has been copied - b.Text = "Copied!"; - } - } + private void btnCopy_Click(object sender, EventArgs e) + { + Clipboard.SetText(txtReport.Text); - private void btnExit_Click(object sender, EventArgs e) + // should never fail, but better safe than sorry + // (this is the crash handling dialog after all) + if (sender is Button b) { - Environment.Exit(0); + // give confirmation that the crash report has been copied + b.Text = "Copied!"; } } + + private void btnExit_Click(object sender, EventArgs e) + { + Environment.Exit(0); + } } diff --git a/YAMDCC.Common/Dialogs/ProgressDialog.cs b/YAMDCC.Common/Dialogs/ProgressDialog.cs index b43d2ea..ff5acef 100644 --- a/YAMDCC.Common/Dialogs/ProgressDialog.cs +++ b/YAMDCC.Common/Dialogs/ProgressDialog.cs @@ -19,174 +19,173 @@ using System.Globalization; using System.Windows.Forms; -namespace YAMDCC.Common.Dialogs +namespace YAMDCC.Common.Dialogs; + +public sealed partial class ProgressDialog : Form { - public sealed partial class ProgressDialog : Form - { - #region Disable close button - private const int CP_NOCLOSE_BUTTON = 0x200; + #region Disable close button + private const int CP_NOCLOSE_BUTTON = 0x200; - protected override CreateParams CreateParams + protected override CreateParams CreateParams + { + get { - get - { - CreateParams myCp = base.CreateParams; - myCp.ClassStyle |= CP_NOCLOSE_BUTTON; - return myCp; - } + CreateParams myCp = base.CreateParams; + myCp.ClassStyle |= CP_NOCLOSE_BUTTON; + return myCp; } - #endregion - - public bool Cancelled { get; set; } - public object Result { get; set; } - - private readonly object Argument; - private readonly string Caption; - private readonly Action DoWork; - - private readonly BackgroundWorker Worker = new(); - private readonly Timer DisplayTimer = new(); - - /// - public ProgressDialog( - string caption, - Action doWork, - bool reportsProgress = false, - bool canCancel = false) - : this(caption, doWork, null, reportsProgress, canCancel) - { } - - /// - /// Initialises a new instance of the class. - /// - /// - /// The window caption to use. - /// - /// - /// The to run when showing this window. - /// - /// - /// The argument to pass to . - /// - /// - /// Set to if - /// reports progress, otherwise . - /// - /// - /// Set to if - /// supports cancellation, otherwise . - /// - /// - public ProgressDialog( - string caption, - Action doWork, - object argument, - bool reportsProgress = false, - bool canCancel = false) + } + #endregion + + public bool Cancelled { get; set; } + public object Result { get; set; } + + private readonly object Argument; + private readonly string Caption; + private readonly Action DoWork; + + private readonly BackgroundWorker Worker = new(); + private readonly Timer DisplayTimer = new(); + + /// + public ProgressDialog( + string caption, + Action doWork, + bool reportsProgress = false, + bool canCancel = false) + : this(caption, doWork, null, reportsProgress, canCancel) + { } + + /// + /// Initialises a new instance of the class. + /// + /// + /// The window caption to use. + /// + /// + /// The to run when showing this window. + /// + /// + /// The argument to pass to . + /// + /// + /// Set to if + /// reports progress, otherwise . + /// + /// + /// Set to if + /// supports cancellation, otherwise . + /// + /// + public ProgressDialog( + string caption, + Action doWork, + object argument, + bool reportsProgress = false, + bool canCancel = false) + { + Opacity = 0; + Argument = argument; + InitializeComponent(); + + // sanity check + if (doWork is null) { - Opacity = 0; - Argument = argument; - InitializeComponent(); - - // sanity check - if (doWork is null) - { - throw new ArgumentNullException(nameof(doWork), "The doWork parameter was null."); - } - DoWork = doWork; - - // set title text - Caption = caption ?? "Please wait..."; - if (reportsProgress && caption is null) - { - Caption += " ({0}% complete)"; - } - SetTitle(Caption); - - // event setup - Worker.DoWork += Worker_DoWork; - Worker.RunWorkerCompleted += Worker_RunWorkerCompleted; - if (reportsProgress) - { - Worker.ProgressChanged += Worker_ProgressChanged; - } - else - { - pbProgress.Style = ProgressBarStyle.Marquee; - } - - // cancel support stuff - if (canCancel) - { - Worker.WorkerSupportsCancellation = true; - } - else - { - btnCancel.Enabled = false; - btnCancel.Visible = false; - } - - DisplayTimer.Interval = 1000; - DisplayTimer.Tick += DisplayTimer_Tick; + throw new ArgumentNullException(nameof(doWork), "The doWork parameter was null."); } + DoWork = doWork; - private void ProgressDialog_Load(object sender, EventArgs e) + // set title text + Caption = caption ?? "Please wait..."; + if (reportsProgress && caption is null) { - DisplayTimer.Start(); - Worker.RunWorkerAsync(Argument); + Caption += " ({0}% complete)"; } + SetTitle(Caption); - private void DisplayTimer_Tick(object sender, EventArgs e) + // event setup + Worker.DoWork += Worker_DoWork; + Worker.RunWorkerCompleted += Worker_RunWorkerCompleted; + if (reportsProgress) { - Opacity = 1; - DisplayTimer.Stop(); + Worker.ProgressChanged += Worker_ProgressChanged; } - - private void Worker_DoWork(object sender, DoWorkEventArgs e) + else { - DoWork?.Invoke(e); + pbProgress.Style = ProgressBarStyle.Marquee; } - private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) + // cancel support stuff + if (canCancel) { - if (e.ProgressPercentage < 0) - { - pbProgress.Style = ProgressBarStyle.Marquee; - } - else - { - pbProgress.Style = ProgressBarStyle.Blocks; - pbProgress.Value = e.ProgressPercentage; - } - SetTitle(Caption); + Worker.WorkerSupportsCancellation = true; } + else + { + btnCancel.Enabled = false; + btnCancel.Visible = false; + } + + DisplayTimer.Interval = 1000; + DisplayTimer.Tick += DisplayTimer_Tick; + } + + private void ProgressDialog_Load(object sender, EventArgs e) + { + DisplayTimer.Start(); + Worker.RunWorkerAsync(Argument); + } + + private void DisplayTimer_Tick(object sender, EventArgs e) + { + Opacity = 1; + DisplayTimer.Stop(); + } + + private void Worker_DoWork(object sender, DoWorkEventArgs e) + { + DoWork?.Invoke(e); + } - private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) + { + if (e.ProgressPercentage < 0) + { + pbProgress.Style = ProgressBarStyle.Marquee; + } + else { - if (e.Error is not null) - { - throw e.Error; - } - Cancelled = e.Cancelled; - Result = e.Result; - Worker.Dispose(); - Close(); + pbProgress.Style = ProgressBarStyle.Blocks; + pbProgress.Value = e.ProgressPercentage; } + SetTitle(Caption); + } - private void btnCancel_Click(object sender, EventArgs e) + private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (e.Error is not null) { - if (Worker.WorkerSupportsCancellation && Worker.IsBusy && !Worker.CancellationPending) - { - btnCancel.Enabled = false; - Worker.CancelAsync(); - Text = "Cancelling..."; - pbProgress.Style = ProgressBarStyle.Marquee; - } + throw e.Error; } + Cancelled = e.Cancelled; + Result = e.Result; + Worker.Dispose(); + Close(); + } - private void SetTitle(string title) + private void btnCancel_Click(object sender, EventArgs e) + { + if (Worker.WorkerSupportsCancellation && Worker.IsBusy && !Worker.CancellationPending) { - lblCaption.Text = string.Format(CultureInfo.InvariantCulture, title, pbProgress.Value); + btnCancel.Enabled = false; + Worker.CancelAsync(); + Text = "Cancelling..."; + pbProgress.Style = ProgressBarStyle.Marquee; } } + + private void SetTitle(string title) + { + lblCaption.Text = string.Format(CultureInfo.InvariantCulture, title, pbProgress.Value); + } } diff --git a/YAMDCC.Common/Dialogs/TextInputDialog.cs b/YAMDCC.Common/Dialogs/TextInputDialog.cs index 668414c..631dc04 100644 --- a/YAMDCC.Common/Dialogs/TextInputDialog.cs +++ b/YAMDCC.Common/Dialogs/TextInputDialog.cs @@ -17,35 +17,34 @@ using System; using System.Windows.Forms; -namespace YAMDCC.Common.Dialogs +namespace YAMDCC.Common.Dialogs; + +public sealed partial class TextInputDialog : Form { - public sealed partial class TextInputDialog : Form - { - /// - /// The text that the user entered in this dialog. - /// - public string Result { get; set; } + /// + /// The text that the user entered in this dialog. + /// + public string Result { get; set; } - public TextInputDialog(string caption, string title, string text, bool multiline = false) - { - InitializeComponent(); - lblCaption.Text = caption; - txtInput.Text = text; - txtInput.Multiline = multiline; - txtInput.Height = (int)(AutoScaleDimensions.Height / 96 * 69); - Text = title; - } + public TextInputDialog(string caption, string title, string text, bool multiline = false) + { + InitializeComponent(); + lblCaption.Text = caption; + txtInput.Text = text; + txtInput.Multiline = multiline; + txtInput.Height = (int)(AutoScaleDimensions.Height / 96 * 69); + Text = title; + } - private void btnOK_Click(object sender, EventArgs e) - { - Result = txtInput.Text; - } + private void btnOK_Click(object sender, EventArgs e) + { + Result = txtInput.Text; + } - private void txtInput_TextChanged(object sender, EventArgs e) - { - // make sure text input isn't empty - // before allowing user to click "OK": - btnOK.Enabled = !string.IsNullOrEmpty(txtInput.Text); - } + private void txtInput_TextChanged(object sender, EventArgs e) + { + // make sure text input isn't empty + // before allowing user to click "OK": + btnOK.Enabled = !string.IsNullOrEmpty(txtInput.Text); } } diff --git a/YAMDCC.Common/Dialogs/VersionDialog.cs b/YAMDCC.Common/Dialogs/VersionDialog.cs index ba49b0e..295688e 100644 --- a/YAMDCC.Common/Dialogs/VersionDialog.cs +++ b/YAMDCC.Common/Dialogs/VersionDialog.cs @@ -18,47 +18,46 @@ using System.Diagnostics; using System.Windows.Forms; -namespace YAMDCC.Common.Dialogs +namespace YAMDCC.Common.Dialogs; + +public sealed partial class VersionDialog : Form { - public sealed partial class VersionDialog : Form + public VersionDialog() { - public VersionDialog() - { - InitializeComponent(); - lblDesc.Text = Strings.GetString("abtDesc"); - lblCopyright.Text = Strings.GetString("abtCopyright"); - lblVersion.Text += Utils.GetVerString(); + InitializeComponent(); + lblDesc.Text = Strings.GetString("abtDesc"); + lblCopyright.Text = Strings.GetString("abtCopyright"); + lblVersion.Text += Utils.GetVerString(); - string revision = Utils.GetRevision(); + string revision = Utils.GetRevision(); - if (string.IsNullOrEmpty(revision)) - { - lblRevision.Hide(); - } - else - { - lblRevision.Text += revision; - } - } - - private void btnLicense_Click(object sender, EventArgs e) + if (string.IsNullOrEmpty(revision)) { - Process.Start("https://www.gnu.org/licenses/gpl-3.0.html#license-text"); + lblRevision.Hide(); } - - private void btnSource_Click(object sender, EventArgs e) + else { - Process.Start(Paths.GitHubPage); + lblRevision.Text += revision; } + } - private void btnFAQ_Click(object sender, EventArgs e) - { - Process.Start($"{Paths.GitHubPage}#faq"); - } + private void btnLicense_Click(object sender, EventArgs e) + { + Process.Start("https://www.gnu.org/licenses/gpl-3.0.html#license-text"); + } - private void btnIssues_Click(object sender, EventArgs e) - { - Process.Start($"{Paths.GitHubPage}/issues"); - } + private void btnSource_Click(object sender, EventArgs e) + { + Process.Start(Paths.GitHubPage); + } + + private void btnFAQ_Click(object sender, EventArgs e) + { + Process.Start($"{Paths.GitHubPage}#faq"); + } + + private void btnIssues_Click(object sender, EventArgs e) + { + Process.Start($"{Paths.GitHubPage}/issues"); } } diff --git a/YAMDCC.Common/InvalidConfigException.cs b/YAMDCC.Common/InvalidConfigException.cs index a26b1b8..54d2b4d 100644 --- a/YAMDCC.Common/InvalidConfigException.cs +++ b/YAMDCC.Common/InvalidConfigException.cs @@ -16,17 +16,16 @@ using System; -namespace YAMDCC.Common +namespace YAMDCC.Common; + +/// +/// The exception thrown when an invalid is loaded. +/// +public sealed class InvalidConfigException : Exception { /// - /// The exception thrown when an invalid is loaded. + /// Initializes a new instance of the class. /// - public sealed class InvalidConfigException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public InvalidConfigException() - : base("The config was not in the expected format.") { } - } + public InvalidConfigException() + : base("The config was not in the expected format.") { } } diff --git a/YAMDCC.Common/Paths.cs b/YAMDCC.Common/Paths.cs index cff06df..a9c997e 100644 --- a/YAMDCC.Common/Paths.cs +++ b/YAMDCC.Common/Paths.cs @@ -17,64 +17,63 @@ using System; using System.IO; -namespace YAMDCC.Common +namespace YAMDCC.Common; + +public static class Paths { - public static class Paths - { - /// - /// The GitHub home page. - /// - public const string GitHubHome = "https://github.com"; + /// + /// The GitHub home page. + /// + public const string GitHubHome = "https://github.com"; - /// - /// The project repository. - /// - public const string ProjectRepo = "Sparronator9999/YAMDCC"; + /// + /// The project repository. + /// + public const string ProjectRepo = "Sparronator9999/YAMDCC"; - /// - /// The URL to this project's GitHub page. - /// - public static readonly string GitHubPage = $"{GitHubHome}/{ProjectRepo}"; + /// + /// The URL to this project's GitHub page. + /// + public static readonly string GitHubPage = $"{GitHubHome}/{ProjectRepo}"; - /// - /// The path where program data is stored. - /// - /// - /// (C:\ProgramData\Sparronator9999\YAMDCC on Windows) - /// - public static readonly string Data = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), - ProjectRepo.Split('/')[0], ProjectRepo.Split('/')[1]); + /// + /// The path where program data is stored. + /// + /// + /// (C:\ProgramData\Sparronator9999\YAMDCC on Windows) + /// + public static readonly string Data = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + ProjectRepo.Split('/')[0], ProjectRepo.Split('/')[1]); - /// - /// The path where YAMDCC service logs are saved. - /// - /// - /// (C:\ProgramData\Sparronator9999\YAMDCC\Logs on Windows) - /// - public static readonly string Logs = Path.Combine(Data, "Logs"); + /// + /// The path where YAMDCC service logs are saved. + /// + /// + /// (C:\ProgramData\Sparronator9999\YAMDCC\Logs on Windows) + /// + public static readonly string Logs = Path.Combine(Data, "Logs"); - public static readonly string GlobalConf = Path.Combine(Data, "GlobalConfig.xml"); + public static readonly string GlobalConf = Path.Combine(Data, "GlobalConfig.xml"); - /// - /// The path where the currently applied YAMDCC config is saved. - /// - /// - /// (C:\ProgramData\Sparronator9999\YAMDCC\CurrentConfig.xml on Windows) - /// - public static readonly string CurrentConfig = Path.Combine(Data, "CurrentConfig.xml"); + /// + /// The path where the currently applied YAMDCC config is saved. + /// + /// + /// (C:\ProgramData\Sparronator9999\YAMDCC\CurrentConfig.xml on Windows) + /// + public static readonly string CurrentConfig = Path.Combine(Data, "CurrentConfig.xml"); - /// - /// The path where the path to the last saved YAMDCC config is saved. - /// - /// - /// (C:\ProgramData\Sparronator9999\YAMDCC\CurrentConfig.xml on Windows) - /// - public static readonly string LastConfig = Path.Combine(Data, "LastConfig"); + /// + /// The path where the path to the last saved YAMDCC config is saved. + /// + /// + /// (C:\ProgramData\Sparronator9999\YAMDCC\CurrentConfig.xml on Windows) + /// + public static readonly string LastConfig = Path.Combine(Data, "LastConfig"); - public static readonly string ECToConfSuccess = Path.Combine(Data, "ECToConfSuccess"); - public static readonly string ECToConfFail = Path.Combine(Data, "ECToConfFail"); - public static readonly string ECToConfPending = Path.Combine(Data, "ECToConfPending"); - } + public static readonly string ECToConfSuccess = Path.Combine(Data, "ECToConfSuccess"); + public static readonly string ECToConfFail = Path.Combine(Data, "ECToConfFail"); + public static readonly string ECToConfPending = Path.Combine(Data, "ECToConfPending"); } diff --git a/YAMDCC.Common/Strings.cs b/YAMDCC.Common/Strings.cs index d3f422f..2420cd6 100644 --- a/YAMDCC.Common/Strings.cs +++ b/YAMDCC.Common/Strings.cs @@ -17,58 +17,57 @@ using System.Globalization; using System.Resources; -namespace YAMDCC.Common +namespace YAMDCC.Common; + +/// +/// A resource class for retrieving strings. +/// +internal static class Strings { + private static ResourceManager resMan; + /// - /// A resource class for retrieving strings. + /// Gets a string from the underlying resource file. /// - internal static class Strings + /// + /// This function internally calls + /// to retrieve the string. + /// + /// + /// The name of the string to find. + /// + /// + /// The value of the specified string name, if found. + /// null if the string couldn't be found. + /// + public static string GetString(string name) { - private static ResourceManager resMan; - - /// - /// Gets a string from the underlying resource file. - /// - /// - /// This function internally calls - /// to retrieve the string. - /// - /// - /// The name of the string to find. - /// - /// - /// The value of the specified string name, if found. - /// null if the string couldn't be found. - /// - public static string GetString(string name) - { - resMan ??= new ResourceManager(typeof(Strings)); - return resMan.GetString(name, CultureInfo.InvariantCulture); - } + resMan ??= new ResourceManager(typeof(Strings)); + return resMan.GetString(name, CultureInfo.InvariantCulture); + } - /// - /// Gets a string from the underlying resource file, and - /// replaces format objects with their string representation. - /// - /// - /// The name of the string to find. - /// - /// - /// The object to format the string with. - /// - /// - /// - /// The formatted string corresponding to - /// the specified string name, if found. - /// - /// null if the string couldn't be found. - /// - public static string GetString(string name, object arg0) - { - string temp = GetString(name); - return temp is null - ? null - : string.Format(CultureInfo.InvariantCulture, temp, arg0); - } + /// + /// Gets a string from the underlying resource file, and + /// replaces format objects with their string representation. + /// + /// + /// The name of the string to find. + /// + /// + /// The object to format the string with. + /// + /// + /// + /// The formatted string corresponding to + /// the specified string name, if found. + /// + /// null if the string couldn't be found. + /// + public static string GetString(string name, object arg0) + { + string temp = GetString(name); + return temp is null + ? null + : string.Format(CultureInfo.InvariantCulture, temp, arg0); } } diff --git a/YAMDCC.Common/Utils.cs b/YAMDCC.Common/Utils.cs index f295650..d5d22cc 100644 --- a/YAMDCC.Common/Utils.cs +++ b/YAMDCC.Common/Utils.cs @@ -22,236 +22,233 @@ using System.ServiceProcess; using System.Windows.Forms; -namespace YAMDCC.Common +namespace YAMDCC.Common; + +/// +/// A collection of miscellaneous useful utilities +/// +public static class Utils { /// - /// A collection of miscellaneous useful utilities + /// Shows an error dialog. /// - public static class Utils + /// + /// The message to show in the error dialog. + /// + /// + /// One of the values. + /// + public static DialogResult ShowError(string message) { - /// - /// Shows an error dialog. - /// - /// - /// The message to show in the error dialog. - /// - /// - /// One of the values. - /// - public static DialogResult ShowError(string message) - { - return MessageBox.Show(message, "Error", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - public static string GetVerString() - { - // format: X.Y.Z-SUFFIX[.W]+REVISION, - // where W is a beta/release candidate version if applicable - string prodVer = Application.ProductVersion; + return MessageBox.Show(message, "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + } - string suffix; - if (prodVer.Contains("-")) - { - // remove the version number (SUFFIX[.W]+REVISION at this point): - suffix = prodVer.Remove(0, prodVer.IndexOf('-') + 1); + public static string GetVerString() + { + // format: X.Y.Z-SUFFIX[.W]+REVISION, + // where W is a beta/release candidate version if applicable + string prodVer = Application.ProductVersion; - // remove Git hash, if it exists (for "dev" detection) - if (suffix.Contains("+")) - { - suffix = suffix.Remove(suffix.IndexOf('+')); - } - } - else - { - // suffix probably doesn't exist... - suffix = string.Empty; - } + string suffix; + if (prodVer.Contains("-")) + { + // remove the version number (SUFFIX[.W]+REVISION at this point): + suffix = prodVer.Remove(0, prodVer.IndexOf('-') + 1); - switch (suffix.ToLowerInvariant()) + // remove Git hash, if it exists (for "dev" detection) + if (suffix.Contains("+")) { - case "release": - // only show the version number (e.g. X.Y.Z): - return prodVer.Remove(prodVer.IndexOf('-')); - case "dev": - return prodVer.Contains("+") - // probably a snapshot release (e.g. X.Y.Z-SNAPSHOT+REVISION); - // show shortened Git commit hash if it exists: - ? prodVer.Remove(prodVer.IndexOf('+') + 8) - // Return the product version if not in expected format - : prodVer; - default: // beta, RC, etc. - return prodVer.Contains(".") && prodVer.Contains("+") - // Beta releases should be in format X.Y.Z-beta.W+REVISION. - // Remove the revision (i.e. only show X.Y.Z-beta.W): - ? prodVer.Remove(prodVer.IndexOf('+')) - // Return the product version if not in expected format - : prodVer; + suffix = suffix.Remove(suffix.IndexOf('+')); } } - - /// - /// Gets the Git revision of this program, if available. - /// - /// - /// The Git hash of the program version if available, - /// otherwise . - /// - public static string GetRevision() + else { - string prodVer = Application.ProductVersion; - - return prodVer.Contains("+") - ? prodVer.Remove(0, prodVer.IndexOf('+') + 1) - : string.Empty; + // suffix probably doesn't exist... + suffix = string.Empty; } - public static bool IsAdmin() + return suffix.ToLowerInvariant() switch { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - try - { - WindowsPrincipal principal = new(identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - catch - { - return false; - } - finally - { - identity.Dispose(); - } - } + // only show the version number (e.g. X.Y.Z): + "release" => prodVer.Remove(prodVer.IndexOf('-')), + "dev" => prodVer.Contains("+") + // probably a snapshot release (e.g. X.Y.Z-SNAPSHOT+REVISION); + // show shortened Git commit hash if it exists: + ? prodVer.Remove(prodVer.IndexOf('+') + 8) + // Return the product version if not in expected format + : prodVer, + // everything else (i.e. beta, RC, etc.) + _ => prodVer.Contains(".") && prodVer.Contains("+") + // Beta releases should be in format X.Y.Z-beta.W+REVISION. + // Remove the revision (i.e. only show X.Y.Z-beta.W): + ? prodVer.Remove(prodVer.IndexOf('+')) + // Return the product version if not in expected format + : prodVer, + }; + } - /// - /// Installs the specified .NET Framework - /// service to the local computer. - /// - /// - /// The service is not started automatically. Use - /// to start it if needed. - /// - /// - /// The path to the service executable. - /// - /// - /// true if the service installation - /// was successful, otherwise false. - /// - public static bool InstallService(string svcExe) - { - string runtimePath = RuntimeEnvironment.GetRuntimeDirectory(); - int exitCode = RunCmd($"{runtimePath}\\installutil.exe", $"{svcExe}.exe"); - DeleteInstallUtilLogs(); - return exitCode == 0; - } + /// + /// Gets the Git revision of this program, if available. + /// + /// + /// The Git hash of the program version if available, + /// otherwise . + /// + public static string GetRevision() + { + string prodVer = Application.ProductVersion; - /// - /// Uninstalls the specified .NET Framework - /// service from the local computer. - /// - /// - /// The path to the service executable. - /// - /// - /// true if the service uninstallation - /// was successful, otherwise false. - /// - public static bool UninstallService(string svcExe) - { - string runtimePath = RuntimeEnvironment.GetRuntimeDirectory(); - int exitCode = RunCmd($"{runtimePath}\\installutil.exe", $"/u {svcExe}.exe"); - DeleteInstallUtilLogs(); - return exitCode == 0; - } + return prodVer.Contains("+") + ? prodVer.Remove(0, prodVer.IndexOf('+') + 1) + : string.Empty; + } - /// - /// Starts the specified service. - /// - /// - /// The service name, as shown in services.msc - /// (NOT to be confused with its display name). - /// - /// - /// true if the service started - /// successfully, otherwise false. - /// - public static bool StartService(string svcName) + public static bool IsAdmin() + { + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + try { - return RunCmd("net.exe", $"start {svcName}") == 0; + WindowsPrincipal principal = new(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); } - - /// - /// Stops the specified service. - /// - /// - /// The service name, as shown in services.msc - /// (NOT to be confused with its display name). - /// - /// - /// true if the service was stopped - /// successfully, otherwise false. - /// - public static bool StopService(string svcName) + catch { - return RunCmd("net.exe", $"stop {svcName}") == 0; + return false; } - - /// - /// Checks to see if the specified service - /// is installed on the computer. - /// - /// - /// The service name, as shown in services.msc - /// (NOT to be confused with its display name). - /// - /// - /// true if the service was - /// found, otherwise false. - /// - public static bool ServiceExists(string svcName) + finally { - return ServiceController.GetServices().Any(s => s.ServiceName == svcName); + identity.Dispose(); } + } - private static void DeleteInstallUtilLogs() + /// + /// Installs the specified .NET Framework + /// service to the local computer. + /// + /// + /// The service is not started automatically. Use + /// to start it if needed. + /// + /// + /// The path to the service executable. + /// + /// + /// true if the service installation + /// was successful, otherwise false. + /// + public static bool InstallService(string svcExe) + { + string runtimePath = RuntimeEnvironment.GetRuntimeDirectory(); + int exitCode = RunCmd($"{runtimePath}\\installutil.exe", $"{svcExe}.exe"); + DeleteInstallUtilLogs(); + return exitCode == 0; + } + + /// + /// Uninstalls the specified .NET Framework + /// service from the local computer. + /// + /// + /// The path to the service executable. + /// + /// + /// true if the service uninstallation + /// was successful, otherwise false. + /// + public static bool UninstallService(string svcExe) + { + string runtimePath = RuntimeEnvironment.GetRuntimeDirectory(); + int exitCode = RunCmd($"{runtimePath}\\installutil.exe", $"/u {svcExe}.exe"); + DeleteInstallUtilLogs(); + return exitCode == 0; + } + + /// + /// Starts the specified service. + /// + /// + /// The service name, as shown in services.msc + /// (NOT to be confused with its display name). + /// + /// + /// true if the service started + /// successfully, otherwise false. + /// + public static bool StartService(string svcName) + { + return RunCmd("net.exe", $"start {svcName}") == 0; + } + + /// + /// Stops the specified service. + /// + /// + /// The service name, as shown in services.msc + /// (NOT to be confused with its display name). + /// + /// + /// true if the service was stopped + /// successfully, otherwise false. + /// + public static bool StopService(string svcName) + { + return RunCmd("net.exe", $"stop {svcName}") == 0; + } + + /// + /// Checks to see if the specified service + /// is installed on the computer. + /// + /// + /// The service name, as shown in services.msc + /// (NOT to be confused with its display name). + /// + /// + /// true if the service was + /// found, otherwise false. + /// + public static bool ServiceExists(string svcName) + { + return ServiceController.GetServices().Any(s => s.ServiceName == svcName); + } + + private static void DeleteInstallUtilLogs() + { + foreach (string file in Directory.GetFiles(".", "*.InstallLog", SearchOption.TopDirectoryOnly)) { - foreach (string file in Directory.GetFiles(".", "*.InstallLog", SearchOption.TopDirectoryOnly)) + try { - try - { - File.Delete(file); - } - catch (DirectoryNotFoundException) { } + File.Delete(file); } + catch (DirectoryNotFoundException) { } } + } - public static int RunCmd(string exe, string args, bool admin = true) + public static int RunCmd(string exe, string args, bool admin = true) + { + bool shellExecute = false; + if (admin && !IsAdmin()) { - bool shellExecute = false; - if (admin && !IsAdmin()) - { - // if running unprivileged, we can't create an admin process - // directly, so use shell execute (creating new cmd window) instead - shellExecute = true; - } + // if running unprivileged, we can't create an admin process + // directly, so use shell execute (creating new cmd window) instead + shellExecute = true; + } - Process p = new() + Process p = new() + { + StartInfo = new ProcessStartInfo(exe) { - StartInfo = new ProcessStartInfo(exe) - { - CreateNoWindow = true, - UseShellExecute = shellExecute, - Verb = admin ? "runas" : string.Empty, - Arguments = args, - }, - }; + CreateNoWindow = true, + UseShellExecute = shellExecute, + Verb = admin ? "runas" : string.Empty, + Arguments = args, + }, + }; - p.Start(); - p.WaitForExit(); + p.Start(); + p.WaitForExit(); - return p.ExitCode; - } + return p.ExitCode; } } diff --git a/YAMDCC.Config/ChargeLimitConf.cs b/YAMDCC.Config/ChargeLimitConf.cs index 0eb888a..ecbd37d 100644 --- a/YAMDCC.Config/ChargeLimitConf.cs +++ b/YAMDCC.Config/ChargeLimitConf.cs @@ -16,35 +16,34 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a charge limit config for a laptop. +/// +public sealed class ChargeLimitConf { /// - /// Represents a charge limit config for a laptop. + /// The register that controls the charge limit. /// - public sealed class ChargeLimitConf - { - /// - /// The register that controls the charge limit. - /// - [XmlElement] - public byte Reg { get; set; } + [XmlElement] + public byte Reg { get; set; } - /// - /// The value that corresponds to 0% charge limit (i.e. disabled). - /// - [XmlElement] - public byte MinVal { get; set; } + /// + /// The value that corresponds to 0% charge limit (i.e. disabled). + /// + [XmlElement] + public byte MinVal { get; set; } - /// - /// The value that corresponds to 100% charge limit. - /// - [XmlElement] - public byte MaxVal { get; set; } + /// + /// The value that corresponds to 100% charge limit. + /// + [XmlElement] + public byte MaxVal { get; set; } - /// - /// The currently set charge limit value. - /// - [XmlElement] - public byte CurVal { get; set; } - } + /// + /// The currently set charge limit value. + /// + [XmlElement] + public byte CurVal { get; set; } } diff --git a/YAMDCC.Config/FanConf.cs b/YAMDCC.Config/FanConf.cs index 069b59c..1a73645 100644 --- a/YAMDCC.Config/FanConf.cs +++ b/YAMDCC.Config/FanConf.cs @@ -16,85 +16,84 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a configuration for a fan in the target laptop. +/// +public sealed class FanConf { /// - /// Represents a configuration for a fan in the target laptop. + /// The display name of the fan in the config editor. /// - public sealed class FanConf - { - /// - /// The display name of the fan in the config editor. - /// - [XmlElement] - public string Name { get; set; } + [XmlElement] + public string Name { get; set; } - /// - /// The minimum possible register value for the fan speed. - /// - [XmlElement] - public byte MinSpeed { get; set; } + /// + /// The minimum possible register value for the fan speed. + /// + [XmlElement] + public byte MinSpeed { get; set; } - /// - /// The maximum possible register value for the fan speed. - /// - [XmlElement] - public byte MaxSpeed { get; set; } + /// + /// The maximum possible register value for the fan speed. + /// + [XmlElement] + public byte MaxSpeed { get; set; } - /// - /// The zero-based index of the to apply for this fan. - /// - [XmlElement] - public int CurveSel { get; set; } + /// + /// The zero-based index of the to apply for this fan. + /// + [XmlElement] + public int CurveSel { get; set; } - /// - /// The register to read to get the fan speed percentage. - /// - [XmlElement] - public byte SpeedReadReg { get; set; } + /// + /// The register to read to get the fan speed percentage. + /// + [XmlElement] + public byte SpeedReadReg { get; set; } - /// - /// The register to read to get the temperature - /// of the component that controls this fan's speed. - /// - [XmlElement] - public byte TempReadReg { get; set; } + /// + /// The register to read to get the temperature + /// of the component that controls this fan's speed. + /// + [XmlElement] + public byte TempReadReg { get; set; } - /// - /// Contains information on how to calculate the fan RPM. - /// - /// - /// May be null. - /// - [XmlElement] - public FanRPMConf RPMConf { get; set; } + /// + /// Contains information on how to calculate the fan RPM. + /// + /// + /// May be null. + /// + [XmlElement] + public FanRPMConf RPMConf { get; set; } - /// - /// The registers that the up thresholds are written to. - /// - [XmlArray] - public byte[] UpThresholdRegs { get; set; } + /// + /// The registers that the up thresholds are written to. + /// + [XmlArray] + public byte[] UpThresholdRegs { get; set; } - /// - /// The registers that the down thresholds are written to. - /// - [XmlArray] - public byte[] DownThresholdRegs { get; set; } + /// + /// The registers that the down thresholds are written to. + /// + [XmlArray] + public byte[] DownThresholdRegs { get; set; } - /// - /// The registers to write a fan speed profile to. - /// - [XmlArray] - public byte[] FanCurveRegs { get; set; } + /// + /// The registers to write a fan speed profile to. + /// + [XmlArray] + public byte[] FanCurveRegs { get; set; } - /// - /// The list of s associated with this fan. - /// - /// - /// If the base config is a template, this may be null, - /// otherwise at least one fan curve (the "default" curve) must exist. - /// - [XmlArray] - public FanCurveConf[] FanCurveConfs { get; set; } - } + /// + /// The list of s associated with this fan. + /// + /// + /// If the base config is a template, this may be null, + /// otherwise at least one fan curve (the "default" curve) must exist. + /// + [XmlArray] + public FanCurveConf[] FanCurveConfs { get; set; } } diff --git a/YAMDCC.Config/FanCurveConf.cs b/YAMDCC.Config/FanCurveConf.cs index c3b1245..6a313e5 100644 --- a/YAMDCC.Config/FanCurveConf.cs +++ b/YAMDCC.Config/FanCurveConf.cs @@ -16,51 +16,50 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a fan profile (a.k.a. fan curve) config. +/// +public sealed class FanCurveConf { /// - /// Represents a fan profile (a.k.a. fan curve) config. + /// The name of the fan profile. /// - public sealed class FanCurveConf - { - /// - /// The name of the fan profile. - /// - [XmlElement] - public string Name { get; set; } + [XmlElement] + public string Name { get; set; } - /// - /// The description of the fan profile. - /// - [XmlElement] - public string Desc { get; set; } + /// + /// The description of the fan profile. + /// + [XmlElement] + public string Desc { get; set; } - /// - /// The fan speeds and associated up and down thresholds. - /// - [XmlArray] - public TempThreshold[] TempThresholds { get; set; } + /// + /// The fan speeds and associated up and down thresholds. + /// + [XmlArray] + public TempThreshold[] TempThresholds { get; set; } - /// - /// Creates a deep copy of this . - /// - /// - /// A copy of this . - /// - public FanCurveConf Copy() - { - // create a shallow copy of this FanCurveConfig - FanCurveConf newCfg = (FanCurveConf)MemberwiseClone(); + /// + /// Creates a deep copy of this . + /// + /// + /// A copy of this . + /// + public FanCurveConf Copy() + { + // create a shallow copy of this FanCurveConfig + FanCurveConf newCfg = (FanCurveConf)MemberwiseClone(); - // create a copy of everything that didn't get copied by the above - newCfg.Name = string.Copy(Name); - newCfg.Desc = string.Copy(Desc); - newCfg.TempThresholds = new TempThreshold[TempThresholds.Length]; - for (int i = 0; i < newCfg.TempThresholds.Length; i++) - { - newCfg.TempThresholds[i] = TempThresholds[i].Copy(); - } - return newCfg; + // create a copy of everything that didn't get copied by the above + newCfg.Name = string.Copy(Name); + newCfg.Desc = string.Copy(Desc); + newCfg.TempThresholds = new TempThreshold[TempThresholds.Length]; + for (int i = 0; i < newCfg.TempThresholds.Length; i++) + { + newCfg.TempThresholds[i] = TempThresholds[i].Copy(); } + return newCfg; } } diff --git a/YAMDCC.Config/FanRPMConf.cs b/YAMDCC.Config/FanRPMConf.cs index e93efcb..6d3775b 100644 --- a/YAMDCC.Config/FanRPMConf.cs +++ b/YAMDCC.Config/FanRPMConf.cs @@ -16,52 +16,51 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a configuration describing how +/// a fan RPM is obtained and displayed. +/// +public sealed class FanRPMConf { /// - /// Represents a configuration describing how - /// a fan RPM is obtained and displayed. + /// The register to read to get the fan RPM. /// - public sealed class FanRPMConf - { - /// - /// The register to read to get the fan RPM. - /// - [XmlElement] - public byte ReadReg { get; set; } + [XmlElement] + public byte ReadReg { get; set; } - /// - /// Is the RPM value stored as a word (16-bit) or byte (8-bit)? - /// - [XmlElement] - public bool Is16Bit { get; set; } + /// + /// Is the RPM value stored as a word (16-bit) or byte (8-bit)? + /// + [XmlElement] + public bool Is16Bit { get; set; } - /// - /// Is the RPM value big-endian? This will only have an - /// effect if is set to true. - /// - [XmlElement] - public bool IsBigEndian { get; set; } + /// + /// Is the RPM value big-endian? This will only have an + /// effect if is set to true. + /// + [XmlElement] + public bool IsBigEndian { get; set; } - /// - /// The value to multiply (or divide, if - /// is true) the read RPM value by. - /// - [XmlElement] - public int RPMMult { get; set; } = 1; + /// + /// The value to multiply (or divide, if + /// is true) the read RPM value by. + /// + [XmlElement] + public int RPMMult { get; set; } = 1; - /// - /// If true, divides the read RPM value by - /// instead of multiplying. - /// - [XmlElement] - public bool DivideByMult { get; set; } + /// + /// If true, divides the read RPM value by + /// instead of multiplying. + /// + [XmlElement] + public bool DivideByMult { get; set; } - /// - /// Set to true if the read RPM value starts high - /// and decreases as the fan speed increases. - /// - [XmlElement] - public bool Invert { get; set; } - } + /// + /// Set to true if the read RPM value starts high + /// and decreases as the fan speed increases. + /// + [XmlElement] + public bool Invert { get; set; } } diff --git a/YAMDCC.Config/FullBlastConf.cs b/YAMDCC.Config/FullBlastConf.cs index e8bd5f2..7582e97 100644 --- a/YAMDCC.Config/FullBlastConf.cs +++ b/YAMDCC.Config/FullBlastConf.cs @@ -16,28 +16,27 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a Full Blast configuration. +/// +public sealed class FullBlastConf { /// - /// Represents a Full Blast configuration. + /// The register that controls the Full Blast function. /// - public sealed class FullBlastConf - { - /// - /// The register that controls the Full Blast function. - /// - [XmlElement] - public byte Reg { get; set; } + [XmlElement] + public byte Reg { get; set; } - /// - /// A bitmask that controls which EC register - /// bits to toggle when toggling Full Blast. - /// - /// - /// For example, 128 (0x80, or 10000000b) would - /// toggle the MSB of the Full Blast register. - /// - [XmlElement] - public byte Mask { get; set; } - } + /// + /// A bitmask that controls which EC register + /// bits to toggle when toggling Full Blast. + /// + /// + /// For example, 128 (0x80, or 10000000b) would + /// toggle the MSB of the Full Blast register. + /// + [XmlElement] + public byte Mask { get; set; } } diff --git a/YAMDCC.Config/KeyLightConf.cs b/YAMDCC.Config/KeyLightConf.cs index 6cc3707..c9aa5a4 100644 --- a/YAMDCC.Config/KeyLightConf.cs +++ b/YAMDCC.Config/KeyLightConf.cs @@ -16,31 +16,30 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a configuration for the keyboard backlight in a laptop. +/// +public sealed class KeyLightConf { /// - /// Represents a configuration for the keyboard backlight in a laptop. + /// The register that controls the keyboard backlight. /// - public sealed class KeyLightConf - { - /// - /// The register that controls the keyboard backlight. - /// - [XmlElement] - public byte Reg { get; set; } + [XmlElement] + public byte Reg { get; set; } - /// - /// The value that turns off the backlight - /// (or reduces it to its minimum brightness). - /// - [XmlElement] - public byte MinVal { get; set; } + /// + /// The value that turns off the backlight + /// (or reduces it to its minimum brightness). + /// + [XmlElement] + public byte MinVal { get; set; } - /// - /// The value that sets the keyboard - /// backlight to the maximum brightness. - /// - [XmlElement] - public byte MaxVal { get; set; } - } + /// + /// The value that sets the keyboard + /// backlight to the maximum brightness. + /// + [XmlElement] + public byte MaxVal { get; set; } } diff --git a/YAMDCC.Config/KeySwapConf.cs b/YAMDCC.Config/KeySwapConf.cs index 679d933..2f7dd05 100644 --- a/YAMDCC.Config/KeySwapConf.cs +++ b/YAMDCC.Config/KeySwapConf.cs @@ -16,35 +16,34 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a configuration for the Win/Fn key swap feature of a laptop. +/// +public sealed class KeySwapConf { /// - /// Represents a configuration for the Win/Fn key swap feature of a laptop. + /// The register that controls the Win/Fn key swap state. /// - public sealed class KeySwapConf - { - /// - /// The register that controls the Win/Fn key swap state. - /// - [XmlElement] - public byte Reg { get; set; } + [XmlElement] + public byte Reg { get; set; } - /// - /// Is the Win/Fn key swap feature enabled? - /// - [XmlElement] - public bool Enabled { get; set; } + /// + /// Is the Win/Fn key swap feature enabled? + /// + [XmlElement] + public bool Enabled { get; set; } - /// - /// The value to turn on Win/Fn key swapping. - /// - [XmlElement] - public byte OnVal { get; set; } + /// + /// The value to turn on Win/Fn key swapping. + /// + [XmlElement] + public byte OnVal { get; set; } - /// - /// The value to turn off Win/Fn key swapping. - /// - [XmlElement] - public byte OffVal { get; set; } - } + /// + /// The value to turn off Win/Fn key swapping. + /// + [XmlElement] + public byte OffVal { get; set; } } diff --git a/YAMDCC.Config/PerfMode.cs b/YAMDCC.Config/PerfMode.cs index f8b1b91..e6ee4e4 100644 --- a/YAMDCC.Config/PerfMode.cs +++ b/YAMDCC.Config/PerfMode.cs @@ -16,31 +16,30 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a configuration for an +/// individual performance mode of a laptop. +/// +public sealed class PerfMode { /// - /// Represents a configuration for an - /// individual performance mode of a laptop. + /// The name of the performance mode. /// - public sealed class PerfMode - { - /// - /// The name of the performance mode. - /// - [XmlElement] - public string Name { get; set; } + [XmlElement] + public string Name { get; set; } - /// - /// The description of the performance mode. - /// - [XmlElement] - public string Desc { get; set; } + /// + /// The description of the performance mode. + /// + [XmlElement] + public string Desc { get; set; } - /// - /// The value to write to the EC register - /// when this performance mode is selected. - /// - [XmlElement] - public byte Value { get; set; } - } + /// + /// The value to write to the EC register + /// when this performance mode is selected. + /// + [XmlElement] + public byte Value { get; set; } } diff --git a/YAMDCC.Config/PerfModeConf.cs b/YAMDCC.Config/PerfModeConf.cs index 0320c1b..f4d28f5 100644 --- a/YAMDCC.Config/PerfModeConf.cs +++ b/YAMDCC.Config/PerfModeConf.cs @@ -16,31 +16,30 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a configuration for the performance modes of a laptop +/// (separate from the Windows power plans). +/// +public sealed class PerfModeConf { /// - /// Represents a configuration for the performance modes of a laptop - /// (separate from the Windows power plans). + /// The register that controls the performance mode. /// - public sealed class PerfModeConf - { - /// - /// The register that controls the performance mode. - /// - [XmlElement] - public byte Reg { get; set; } + [XmlElement] + public byte Reg { get; set; } - /// - /// The currently selected performance mode, as - /// an index of the available performance modes. - /// - [XmlElement] - public int ModeSel { get; set; } + /// + /// The currently selected performance mode, as + /// an index of the available performance modes. + /// + [XmlElement] + public int ModeSel { get; set; } - /// - /// An array of possible performance modes for the laptop. - /// - [XmlArray] - public PerfMode[] PerfModes { get; set; } - } + /// + /// An array of possible performance modes for the laptop. + /// + [XmlArray] + public PerfMode[] PerfModes { get; set; } } diff --git a/YAMDCC.Config/RegConf.cs b/YAMDCC.Config/RegConf.cs index e248cef..2e52508 100644 --- a/YAMDCC.Config/RegConf.cs +++ b/YAMDCC.Config/RegConf.cs @@ -16,45 +16,44 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents miscellaneous EC register configurations for the target +/// computer. May be required to enable fan control via YAMDCC. +/// +/// +/// All RegConfs defined here will be applied on service start (unless disabled). +/// +public sealed class RegConf { /// - /// Represents miscellaneous EC register configurations for the target - /// computer. May be required to enable fan control via YAMDCC. + /// Should this be applied? /// - /// - /// All RegConfs defined here will be applied on service start (unless disabled). - /// - public sealed class RegConf - { - /// - /// Should this be applied? - /// - [XmlElement] - public bool Enabled { get; set; } = true; + [XmlElement] + public bool Enabled { get; set; } = true; - /// - /// A short name for this EC register config. - /// - [XmlElement] - public string Name { get; set; } + /// + /// A short name for this EC register config. + /// + [XmlElement] + public string Name { get; set; } - /// - /// A longer description of what this config does. - /// - [XmlElement] - public string Desc { get; set; } + /// + /// A longer description of what this config does. + /// + [XmlElement] + public string Desc { get; set; } - /// - /// The register to write to. - /// - [XmlElement] - public byte Reg { get; set; } + /// + /// The register to write to. + /// + [XmlElement] + public byte Reg { get; set; } - /// - /// The value to write to the register. - /// - [XmlElement] - public byte Value { get; set; } - } + /// + /// The value to write to the register. + /// + [XmlElement] + public byte Value { get; set; } } diff --git a/YAMDCC.Config/TempThreshold.cs b/YAMDCC.Config/TempThreshold.cs index de9f0d0..baf15f4 100644 --- a/YAMDCC.Config/TempThreshold.cs +++ b/YAMDCC.Config/TempThreshold.cs @@ -16,49 +16,48 @@ using System.Xml.Serialization; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a fan speed/temperature threshold setting for a fan curve. +/// +public sealed class TempThreshold { /// - /// Represents a fan speed/temperature threshold setting for a fan curve. + /// The temperature threshold before the fan speeds up to this fan speed. /// - public sealed class TempThreshold - { - /// - /// The temperature threshold before the fan speeds up to this fan speed. - /// - /// - /// Ignored if this is the last temperature threshold in the list - /// (i.e. this is the highest fan speed that can be set). - /// - [XmlElement] - public byte UpThreshold { get; set; } + /// + /// Ignored if this is the last temperature threshold in the list + /// (i.e. this is the highest fan speed that can be set). + /// + [XmlElement] + public byte UpThreshold { get; set; } - /// - /// The temperature threshold before the fan - /// slows down to the previous fan speed. - /// - /// - /// Ignored if this is the first temperature threshold in the list - /// (i.e. this is the default fan speed). - /// - [XmlElement] - public byte DownThreshold { get; set; } + /// + /// The temperature threshold before the fan + /// slows down to the previous fan speed. + /// + /// + /// Ignored if this is the first temperature threshold in the list + /// (i.e. this is the default fan speed). + /// + [XmlElement] + public byte DownThreshold { get; set; } - /// - /// The target fan speed to set when reaching the up threshold. - /// - [XmlElement] - public byte FanSpeed { get; set; } + /// + /// The target fan speed to set when reaching the up threshold. + /// + [XmlElement] + public byte FanSpeed { get; set; } - /// - /// Creates a copy of this . - /// - /// - /// The copy of this - /// - public TempThreshold Copy() - { - return (TempThreshold)MemberwiseClone(); - } + /// + /// Creates a copy of this . + /// + /// + /// The copy of this + /// + public TempThreshold Copy() + { + return (TempThreshold)MemberwiseClone(); } } diff --git a/YAMDCC.Config/YAMDCC_Config.cs b/YAMDCC.Config/YAMDCC_Config.cs index 1f941fc..787bab2 100644 --- a/YAMDCC.Config/YAMDCC_Config.cs +++ b/YAMDCC.Config/YAMDCC_Config.cs @@ -20,334 +20,333 @@ using System.Xml.Serialization; using YAMDCC.Common; -namespace YAMDCC.Config +namespace YAMDCC.Config; + +/// +/// Represents a YAMDCC configuration. +/// +public sealed class YAMDCC_Config { /// - /// Represents a YAMDCC configuration. + /// The config version expected when loading a config. + /// + [XmlIgnore] + public const int ExpectedVer = 1; + + /// + /// The config version. Should be the same as + /// unless the config is newer or invalid. + /// + [XmlAttribute] + public int Ver { get; set; } + + /// + /// The manufacturer of the laptop the config was made for. + /// + [XmlElement] + public string Manufacturer { get; set; } + + /// + /// The laptop model the config was made for. + /// + [XmlElement] + public string Model { get; set; } + + /// + /// The author of the config file. + /// + [XmlElement] + public string Author { get; set; } + + /// + /// The list of s associated with the laptop. + /// + [XmlArray] + public FanConf[] FanConfs { get; set; } + + /// + /// The laptop's Full Blast config. + /// + /// + /// May be null if not supported on the laptop. + /// + [XmlElement] + public FullBlastConf FullBlastConf { get; set; } + + /// + /// The laptop's charge threshold config. + /// + /// + /// May be null if not supported on the laptop. + /// + [XmlElement] + public ChargeLimitConf ChargeLimitConf { get; set; } + + /// + /// The laptop's performance mode config. + /// + /// + /// May be null if not supported on the laptop. + /// + [XmlElement] + public PerfModeConf PerfModeConf { get; set; } + + /// + /// The laptop's Win/Fn keyboard swap config. + /// + /// + /// May be null if not supported on the laptop. + /// + [XmlElement] + public KeySwapConf KeySwapConf { get; set; } + + /// + /// The laptop's keyboard backlight config. + /// + /// + /// May be null if not supported on the laptop. + /// + [XmlElement] + public KeyLightConf KeyLightConf { get; set; } + + /// + /// A list of registers to write when applying a fan config. /// - public sealed class YAMDCC_Config + /// + /// May be null or empty if not needed. + /// + [XmlArray] + public RegConf[] RegConfs { get; set; } + + /// + /// Parses a YAMDCC config XML and returns a + /// object. + /// + /// + /// The path to an XML config file. + /// + /// + /// + /// + /// + public static YAMDCC_Config Load(string xmlFile) { - /// - /// The config version expected when loading a config. - /// - [XmlIgnore] - public const int ExpectedVer = 1; - - /// - /// The config version. Should be the same as - /// unless the config is newer or invalid. - /// - [XmlAttribute] - public int Ver { get; set; } - - /// - /// The manufacturer of the laptop the config was made for. - /// - [XmlElement] - public string Manufacturer { get; set; } - - /// - /// The laptop model the config was made for. - /// - [XmlElement] - public string Model { get; set; } - - /// - /// The author of the config file. - /// - [XmlElement] - public string Author { get; set; } - - /// - /// The list of s associated with the laptop. - /// - [XmlArray] - public FanConf[] FanConfs { get; set; } - - /// - /// The laptop's Full Blast config. - /// - /// - /// May be null if not supported on the laptop. - /// - [XmlElement] - public FullBlastConf FullBlastConf { get; set; } - - /// - /// The laptop's charge threshold config. - /// - /// - /// May be null if not supported on the laptop. - /// - [XmlElement] - public ChargeLimitConf ChargeLimitConf { get; set; } - - /// - /// The laptop's performance mode config. - /// - /// - /// May be null if not supported on the laptop. - /// - [XmlElement] - public PerfModeConf PerfModeConf { get; set; } - - /// - /// The laptop's Win/Fn keyboard swap config. - /// - /// - /// May be null if not supported on the laptop. - /// - [XmlElement] - public KeySwapConf KeySwapConf { get; set; } - - /// - /// The laptop's keyboard backlight config. - /// - /// - /// May be null if not supported on the laptop. - /// - [XmlElement] - public KeyLightConf KeyLightConf { get; set; } - - /// - /// A list of registers to write when applying a fan config. - /// - /// - /// May be null or empty if not needed. - /// - [XmlArray] - public RegConf[] RegConfs { get; set; } - - /// - /// Parses a YAMDCC config XML and returns a - /// object. - /// - /// - /// The path to an XML config file. - /// - /// - /// - /// - /// - public static YAMDCC_Config Load(string xmlFile) + XmlSerializer serialiser = new(typeof(YAMDCC_Config)); + using (XmlReader reader = XmlReader.Create(xmlFile)) { - XmlSerializer serialiser = new(typeof(YAMDCC_Config)); - using (XmlReader reader = XmlReader.Create(xmlFile)) - { - YAMDCC_Config cfg = (YAMDCC_Config)serialiser.Deserialize(reader); - return cfg.IsValid() ? cfg : throw new InvalidConfigException(); - } + YAMDCC_Config cfg = (YAMDCC_Config)serialiser.Deserialize(reader); + return cfg.IsValid() ? cfg : throw new InvalidConfigException(); } + } - /// - /// Saves a YAMDCC config to the specified location. - /// - /// - /// The XML file to write to. - /// - /// - /// - public void Save(string xmlFile) + /// + /// Saves a YAMDCC config to the specified location. + /// + /// + /// The XML file to write to. + /// + /// + /// + public void Save(string xmlFile) + { + XmlSerializer serializer = new(typeof(YAMDCC_Config)); + XmlWriterSettings settings = new() { - XmlSerializer serializer = new(typeof(YAMDCC_Config)); - XmlWriterSettings settings = new() - { - Indent = true, - IndentChars = "\t", - }; + Indent = true, + IndentChars = "\t", + }; - using (XmlWriter writer = XmlWriter.Create(xmlFile, settings)) - { - serializer.Serialize(writer, this); - } + using (XmlWriter writer = XmlWriter.Create(xmlFile, settings)) + { + serializer.Serialize(writer, this); } + } - /// - /// Performs some validation on the loaded config to make - /// sure it is in the expected format. - /// - /// - /// This does NOT guarantee the loaded config is valid! - /// (e.g. register values are not checked) - /// - /// - /// true if the config is valid, otherwise false. - /// - private bool IsValid() + /// + /// Performs some validation on the loaded config to make + /// sure it is in the expected format. + /// + /// + /// This does NOT guarantee the loaded config is valid! + /// (e.g. register values are not checked) + /// + /// + /// true if the config is valid, otherwise false. + /// + private bool IsValid() + { + // Check the config version. + // Pretty self-explanatory, if the loaded config is older/newer + // than the version expected by the config library, don't bother + // checking anything else as some/all of it is probably invalid. + if (Ver != ExpectedVer) { - // Check the config version. - // Pretty self-explanatory, if the loaded config is older/newer - // than the version expected by the config library, don't bother - // checking anything else as some/all of it is probably invalid. - if (Ver != ExpectedVer) + return false; + } + + if (string.IsNullOrEmpty(Manufacturer) || + string.IsNullOrEmpty(Model) || + string.IsNullOrEmpty(Author)) + { + return false; + } + + // 1. Check if FanConfigs is not null + // 2. Check if there's at least 1 FanConfig + if (FanConfs?.Length < 1) + { + return false; + } + + for (int i = 0; i < FanConfs.Length; i++) + { + FanConf cfg = FanConfs[i]; + + if (string.IsNullOrEmpty(cfg.Name)) { return false; } - if (string.IsNullOrEmpty(Manufacturer) || - string.IsNullOrEmpty(Model) || - string.IsNullOrEmpty(Author)) + // YAMDCC doesn't handle MinSpeed lower than MaxSpeed, + // so return false if MinSpeed is lower or equal to MaxSpeed: + if (cfg.MinSpeed >= cfg.MaxSpeed) { return false; } - // 1. Check if FanConfigs is not null - // 2. Check if there's at least 1 FanConfig - if (FanConfs?.Length < 1) + // the selected fan curve shouldn't be higher than + // the number of fan curves in the config. + if (cfg.CurveSel >= FanConfs[i].FanCurveConfs.Length || + cfg.CurveSel < 0) { - return false; + // if the fan profile selection is out of range, + // silently set it to 0 (the first fan curve) + // which should always exist: + cfg.CurveSel = 0; } - for (int i = 0; i < FanConfs.Length; i++) + // make sure that: + // - there is at least one each of up threshold, down threshold, + // and fan curve registers + // - there are the same amount of up threshold registers + // as down threshold registers + // - there is one more fan curve register than up/down threshold registers + // - there is at least one fan profile to apply (first should be Default) + if (cfg.UpThresholdRegs?.Length < 1 || + cfg.UpThresholdRegs?.Length != cfg.DownThresholdRegs?.Length || + cfg.FanCurveRegs?.Length != cfg.UpThresholdRegs?.Length + 1 || + cfg.FanCurveConfs?.Length < 1) { - FanConf cfg = FanConfs[i]; - - if (string.IsNullOrEmpty(cfg.Name)) - { - return false; - } - - // YAMDCC doesn't handle MinSpeed lower than MaxSpeed, - // so return false if MinSpeed is lower or equal to MaxSpeed: - if (cfg.MinSpeed >= cfg.MaxSpeed) - { - return false; - } - - // the selected fan curve shouldn't be higher than - // the number of fan curves in the config. - if (cfg.CurveSel >= FanConfs[i].FanCurveConfs.Length || - cfg.CurveSel < 0) - { - // if the fan profile selection is out of range, - // silently set it to 0 (the first fan curve) - // which should always exist: - cfg.CurveSel = 0; - } + return false; + } - // make sure that: - // - there is at least one each of up threshold, down threshold, - // and fan curve registers - // - there are the same amount of up threshold registers - // as down threshold registers - // - there is one more fan curve register than up/down threshold registers - // - there is at least one fan profile to apply (first should be Default) - if (cfg.UpThresholdRegs?.Length < 1 || - cfg.UpThresholdRegs?.Length != cfg.DownThresholdRegs?.Length || - cfg.FanCurveRegs?.Length != cfg.UpThresholdRegs?.Length + 1 || - cfg.FanCurveConfs?.Length < 1) + for (int j = 0; j < cfg.FanCurveConfs.Length; j++) + { + FanCurveConf curveCfg = cfg.FanCurveConfs[j]; + if (string.IsNullOrEmpty(curveCfg.Name) || + string.IsNullOrEmpty(curveCfg.Desc) || + // there should be exactly one temperature threshold + // per fan curve register; if there isn't, return false + curveCfg.TempThresholds?.Length != cfg.FanCurveRegs.Length) { return false; } - for (int j = 0; j < cfg.FanCurveConfs.Length; j++) + for (int k = 0; k < curveCfg.TempThresholds.Length; k++) { - FanCurveConf curveCfg = cfg.FanCurveConfs[j]; - if (string.IsNullOrEmpty(curveCfg.Name) || - string.IsNullOrEmpty(curveCfg.Desc) || - // there should be exactly one temperature threshold - // per fan curve register; if there isn't, return false - curveCfg.TempThresholds?.Length != cfg.FanCurveRegs.Length) + if (curveCfg.TempThresholds[k] is null) { return false; } - - for (int k = 0; k < curveCfg.TempThresholds.Length; k++) - { - if (curveCfg.TempThresholds[k] is null) - { - return false; - } - } } } + } - if (FullBlastConf is not null) + if (FullBlastConf is not null) + { + // full blast mask shouldn't be 0, as that would make it impossible + // to change the full blast register's value when full blast toggled on/off + if (FullBlastConf.Mask == 0) { - // full blast mask shouldn't be 0, as that would make it impossible - // to change the full blast register's value when full blast toggled on/off - if (FullBlastConf.Mask == 0) - { - return false; - } + return false; } + } - if (ChargeLimitConf is not null) + if (ChargeLimitConf is not null) + { + // YAMDCC cannot handle a lower min value than max value, + // so return false if that's the case for this config + if (ChargeLimitConf.MinVal >= ChargeLimitConf.MaxVal) { - // YAMDCC cannot handle a lower min value than max value, - // so return false if that's the case for this config - if (ChargeLimitConf.MinVal >= ChargeLimitConf.MaxVal) - { - return false; - } - - // make sure charge limit to apply is within the config's - // defined bounds, but don't fail validation if it's not: - if (ChargeLimitConf.CurVal > ChargeLimitConf.MaxVal - ChargeLimitConf.MinVal) - { - ChargeLimitConf.CurVal = ChargeLimitConf.MaxVal; - } - else if (ChargeLimitConf.CurVal < 0) - { - ChargeLimitConf.CurVal = ChargeLimitConf.MinVal; - } + return false; } - if (PerfModeConf is not null) + // make sure charge limit to apply is within the config's + // defined bounds, but don't fail validation if it's not: + if (ChargeLimitConf.CurVal > ChargeLimitConf.MaxVal - ChargeLimitConf.MinVal) { - if (PerfModeConf.PerfModes?.Length < 1) - { - return false; - } - - // the selected performance mode shouldn't be higher than - // the number of performance modes in the config - if (PerfModeConf.ModeSel >= PerfModeConf.PerfModes.Length || - PerfModeConf.ModeSel < 0) - { - // same as fan profile selection, set the performance - // mode to the first available performance mode: - PerfModeConf.ModeSel = 0; - } - - for (int i = 0; i < PerfModeConf.PerfModes.Length; i++) - { - PerfMode perfMode = PerfModeConf.PerfModes[i]; - - if (string.IsNullOrEmpty(perfMode.Name) || - string.IsNullOrEmpty(perfMode.Desc)) - { - return false; - } - } + ChargeLimitConf.CurVal = ChargeLimitConf.MaxVal; } + else if (ChargeLimitConf.CurVal < 0) + { + ChargeLimitConf.CurVal = ChargeLimitConf.MinVal; + } + } - if (KeySwapConf?.OnVal == KeySwapConf?.OffVal) + if (PerfModeConf is not null) + { + if (PerfModeConf.PerfModes?.Length < 1) { return false; } - if (KeyLightConf?.MinVal >= KeyLightConf?.MaxVal) + // the selected performance mode shouldn't be higher than + // the number of performance modes in the config + if (PerfModeConf.ModeSel >= PerfModeConf.PerfModes.Length || + PerfModeConf.ModeSel < 0) { - return false; + // same as fan profile selection, set the performance + // mode to the first available performance mode: + PerfModeConf.ModeSel = 0; } - if (RegConfs?.Length > 0) + for (int i = 0; i < PerfModeConf.PerfModes.Length; i++) { - for (int i = 0; i < RegConfs.Length; i++) + PerfMode perfMode = PerfModeConf.PerfModes[i]; + + if (string.IsNullOrEmpty(perfMode.Name) || + string.IsNullOrEmpty(perfMode.Desc)) { - if (string.IsNullOrEmpty(RegConfs[i].Name) || - string.IsNullOrEmpty(RegConfs[i].Desc)) - { - return false; - } + return false; } } + } + + if (KeySwapConf?.OnVal == KeySwapConf?.OffVal) + { + return false; + } + + if (KeyLightConf?.MinVal >= KeyLightConf?.MaxVal) + { + return false; + } - // All other values are considered to be valid; return true. - // Note that registers aren't checked and are (almost) always - // expected to be nonzero. - return true; + if (RegConfs?.Length > 0) + { + for (int i = 0; i < RegConfs.Length; i++) + { + if (string.IsNullOrEmpty(RegConfs[i].Name) || + string.IsNullOrEmpty(RegConfs[i].Desc)) + { + return false; + } + } } + + // All other values are considered to be valid; return true. + // Note that registers aren't checked and are (almost) always + // expected to be nonzero. + return true; } } diff --git a/YAMDCC.ConfigEditor/MainWindow.cs b/YAMDCC.ConfigEditor/MainWindow.cs index 0c4c9fe..bb38913 100644 --- a/YAMDCC.ConfigEditor/MainWindow.cs +++ b/YAMDCC.ConfigEditor/MainWindow.cs @@ -28,1406 +28,1405 @@ using YAMDCC.IPC; using YAMDCC.Logs; -namespace YAMDCC.ConfigEditor +namespace YAMDCC.ConfigEditor; + +internal sealed partial class MainWindow : Form { - internal sealed partial class MainWindow : Form - { - #region Fields - private readonly Status AppStatus = new(); - - private readonly CommonConfig GlobalConfig; - - /// - /// The YAMDCC config that is currently open for editing. - /// - private YAMDCC_Config Config; - - /// - /// The client that connects to the YAMDCC Service - /// - private readonly NamedPipeClient IPCClient = - new("YAMDCC-Server"); - - private NumericUpDown[] numUpTs, numDownTs, numFanSpds; - private TrackBar[] tbFanSpds; - - private readonly ToolTip ttMain = new(); - - private readonly Timer tmrPoll, tmrStatusReset, tmrSvcTimeout; - - private int Debug; - #endregion - - public MainWindow() - { - InitializeComponent(); - - // Set the window icon using the application icon. - // Saves about 8-9 KB from not having to embed the same icon twice. - Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); - - // set title text to include program version - Text = $"YAMDCC config editor - v{Utils.GetVerString()}"; - - // set literally every tooltip - tsiLoadConf.ToolTipText = Strings.GetString("ttLoadConf"); - tsiSaveConf.ToolTipText = Strings.GetString("ttSaveConf"); - tsiApply.ToolTipText = Strings.GetString("ttApply"); - tsiRevert.ToolTipText = Strings.GetString("ttRevert"); - tsiExit.ToolTipText = Strings.GetString("ttExit"); - tsiProfAdd.ToolTipText = Strings.GetString("ttProfAdd"); - tsiProfRename.ToolTipText = Strings.GetString("ttProfRen"); - tsiProfChangeDesc.ToolTipText = Strings.GetString("ttProfChangeDesc"); - tsiSwitchAll.ToolTipText = Strings.GetString("ttSwitchAll"); - tsiECtoConf.ToolTipText = Strings.GetString("ttECtoConf"); - tsiProfDel.ToolTipText = Strings.GetString("ttProfDel"); - tsiECMon.ToolTipText = Strings.GetString("ttECMon"); - tsiStopSvc.ToolTipText = Strings.GetString("ttSvcStop"); - tsiUninstall.ToolTipText = Strings.GetString("ttSvcUninstall"); - tsiAbout.ToolTipText = Strings.GetString("ttAbout"); - tsiSource.ToolTipText = Strings.GetString("ttSource"); - ttMain.SetToolTip(cboFanSel, Strings.GetString("ttFanSel")); - ttMain.SetToolTip(btnProfAdd, Strings.GetString("ttProfAdd")); - ttMain.SetToolTip(btnProfDel, Strings.GetString("ttProfDel")); - ttMain.SetToolTip(btnApply, Strings.GetString("ttApply")); - ttMain.SetToolTip(btnRevert, Strings.GetString("ttRevert")); - - tmrPoll = new() - { - Interval = 1000, - }; - tmrPoll.Tick += tmrPoll_Tick; + #region Fields + private readonly Status AppStatus = new(); - tmrStatusReset = new() - { - Interval = 5000, - }; - tmrStatusReset.Tick += tmrStatusReset_Tick; + private readonly CommonConfig GlobalConfig; - tmrSvcTimeout = new() - { - Interval = 10000, - }; - tmrSvcTimeout.Tick += tmrSvcTimeout_Tick; + /// + /// The YAMDCC config that is currently open for editing. + /// + private YAMDCC_Config Config; - GlobalConfig = CommonConfig.Load(); - if (GlobalConfig.App == "YAMDCC") - { - switch (GlobalConfig.LogLevel) - { - case LogLevel.None: - tsiLogNone.Checked = true; - break; - case LogLevel.Fatal: - tsiLogFatal.Checked = true; - break; - case LogLevel.Error: - tsiLogError.Checked = true; - break; - case LogLevel.Warn: - tsiLogWarn.Checked = true; - break; - case LogLevel.Info: - tsiLogInfo.Checked = true; - break; - case LogLevel.Debug: - tsiLogDebug.Checked = true; - break; - } - } - else - { - tsiLogDebug.Checked = true; - } + /// + /// The client that connects to the YAMDCC Service + /// + private readonly NamedPipeClient IPCClient = + new("YAMDCC-Server"); - DisableAll(); - } + private NumericUpDown[] numUpTs, numDownTs, numFanSpds; + private TrackBar[] tbFanSpds; - #region Events - private void MainWindow_Load(object sender, EventArgs e) - { - IPCClient.ServerMessage += IPC_MessageReceived; - IPCClient.Error += IPCClient_Error; - IPCClient.Start(); + private readonly ToolTip ttMain = new(); - ProgressDialog dlg = new("Connecting to YAMDCC service...", - (e) => e.Result = !IPCClient.WaitForConnection(5000)); - dlg.ShowDialog(); + private readonly Timer tmrPoll, tmrStatusReset, tmrSvcTimeout; - if (dlg.Result is bool b && b) - { - throw new TimeoutException(Strings.GetString("exSvcTimeout")); + private int Debug; + #endregion + + public MainWindow() + { + InitializeComponent(); + + // Set the window icon using the application icon. + // Saves about 8-9 KB from not having to embed the same icon twice. + Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); + + // set title text to include program version + Text = $"YAMDCC config editor - v{Utils.GetVerString()}"; + + // set literally every tooltip + tsiLoadConf.ToolTipText = Strings.GetString("ttLoadConf"); + tsiSaveConf.ToolTipText = Strings.GetString("ttSaveConf"); + tsiApply.ToolTipText = Strings.GetString("ttApply"); + tsiRevert.ToolTipText = Strings.GetString("ttRevert"); + tsiExit.ToolTipText = Strings.GetString("ttExit"); + tsiProfAdd.ToolTipText = Strings.GetString("ttProfAdd"); + tsiProfRename.ToolTipText = Strings.GetString("ttProfRen"); + tsiProfChangeDesc.ToolTipText = Strings.GetString("ttProfChangeDesc"); + tsiSwitchAll.ToolTipText = Strings.GetString("ttSwitchAll"); + tsiECtoConf.ToolTipText = Strings.GetString("ttECtoConf"); + tsiProfDel.ToolTipText = Strings.GetString("ttProfDel"); + tsiECMon.ToolTipText = Strings.GetString("ttECMon"); + tsiStopSvc.ToolTipText = Strings.GetString("ttSvcStop"); + tsiUninstall.ToolTipText = Strings.GetString("ttSvcUninstall"); + tsiAbout.ToolTipText = Strings.GetString("ttAbout"); + tsiSource.ToolTipText = Strings.GetString("ttSource"); + ttMain.SetToolTip(cboFanSel, Strings.GetString("ttFanSel")); + ttMain.SetToolTip(btnProfAdd, Strings.GetString("ttProfAdd")); + ttMain.SetToolTip(btnProfDel, Strings.GetString("ttProfDel")); + ttMain.SetToolTip(btnApply, Strings.GetString("ttApply")); + ttMain.SetToolTip(btnRevert, Strings.GetString("ttRevert")); + + tmrPoll = new() + { + Interval = 1000, + }; + tmrPoll.Tick += tmrPoll_Tick; + + tmrStatusReset = new() + { + Interval = 5000, + }; + tmrStatusReset.Tick += tmrStatusReset_Tick; + + tmrSvcTimeout = new() + { + Interval = 10000, + }; + tmrSvcTimeout.Tick += tmrSvcTimeout_Tick; + + GlobalConfig = CommonConfig.Load(); + if (GlobalConfig.App == "YAMDCC") + { + switch (GlobalConfig.LogLevel) + { + case LogLevel.None: + tsiLogNone.Checked = true; + break; + case LogLevel.Fatal: + tsiLogFatal.Checked = true; + break; + case LogLevel.Error: + tsiLogError.Checked = true; + break; + case LogLevel.Warn: + tsiLogWarn.Checked = true; + break; + case LogLevel.Info: + tsiLogInfo.Checked = true; + break; + case LogLevel.Debug: + tsiLogDebug.Checked = true; + break; } - AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + } + else + { + tsiLogDebug.Checked = true; + } - LoadConf(Paths.CurrentConfig); + DisableAll(); + } - if (Config is not null) - { - if (Config.KeyLightConf is null) - { - ttMain.SetToolTip(tbKeyLight, Strings.GetString("ttNotSupported")); - } - else - { - SendServiceMessage(new ServiceCommand(Command.GetKeyLightBright, string.Empty)); - } - } + #region Events + private void MainWindow_Load(object sender, EventArgs e) + { + IPCClient.ServerMessage += IPC_MessageReceived; + IPCClient.Error += IPCClient_Error; + IPCClient.Start(); - if (File.Exists(Paths.ECToConfFail)) - { - Utils.ShowError(Strings.GetString("dlgECtoConfErr", Paths.Logs)); - } - else if (File.Exists(Paths.ECToConfSuccess)) - { - MessageBox.Show(Strings.GetString("dlgECtoConfSuccess"), - "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - try - { - File.Delete(Paths.ECToConfSuccess); - File.Delete(Paths.ECToConfFail); - } - catch (DirectoryNotFoundException) { } + ProgressDialog dlg = new("Connecting to YAMDCC service...", + (e) => e.Result = !IPCClient.WaitForConnection(5000)); + dlg.ShowDialog(); + + if (dlg.Result is bool b && b) + { + throw new TimeoutException(Strings.GetString("exSvcTimeout")); } + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + + LoadConf(Paths.CurrentConfig); - private void MainWindow_Closing(object sender, FormClosingEventArgs e) + if (Config is not null) { - // Disable Full Blast if it was enabled while the program was running: - if (chkFullBlast.Checked) + if (Config.KeyLightConf is null) { - SendServiceMessage(new ServiceCommand(Command.FullBlast, "0")); + ttMain.SetToolTip(tbKeyLight, Strings.GetString("ttNotSupported")); } - GlobalConfig.App = "YAMDCC"; - try + else { - GlobalConfig.Save(); + SendServiceMessage(new ServiceCommand(Command.GetKeyLightBright, string.Empty)); } - // ignore DirectoryNotFoundException, since we probably closed the - // window due to uninstalling with data directory delete enabled - catch (DirectoryNotFoundException) { } } - private void OnProcessExit(object sender, EventArgs e) + if (File.Exists(Paths.ECToConfFail)) { - // Close the connection to the YAMDCC - // Service before exiting the program: - tmrPoll.Stop(); - IPCClient.Stop(); + Utils.ShowError(Strings.GetString("dlgECtoConfErr", Paths.Logs)); + } + else if (File.Exists(Paths.ECToConfSuccess)) + { + MessageBox.Show(Strings.GetString("dlgECtoConfSuccess"), + "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + try + { + File.Delete(Paths.ECToConfSuccess); + File.Delete(Paths.ECToConfFail); + } + catch (DirectoryNotFoundException) { } + } + + private void MainWindow_Closing(object sender, FormClosingEventArgs e) + { + // Disable Full Blast if it was enabled while the program was running: + if (chkFullBlast.Checked) + { + SendServiceMessage(new ServiceCommand(Command.FullBlast, "0")); + } + GlobalConfig.App = "YAMDCC"; + try + { + GlobalConfig.Save(); } + // ignore DirectoryNotFoundException, since we probably closed the + // window due to uninstalling with data directory delete enabled + catch (DirectoryNotFoundException) { } + } + + private void OnProcessExit(object sender, EventArgs e) + { + // Close the connection to the YAMDCC + // Service before exiting the program: + tmrPoll.Stop(); + IPCClient.Stop(); + } - private void IPC_MessageReceived(object sender, PipeMessageEventArgs e) + private void IPC_MessageReceived(object sender, PipeMessageEventArgs e) + { + tmrSvcTimeout.Stop(); + string[] args = e.Message.Value.Split(' '); + if (args.Length == 1) { - tmrSvcTimeout.Stop(); - string[] args = e.Message.Value.Split(' '); - if (args.Length == 1) + switch (e.Message.Response) { - switch (e.Message.Response) + case Response.Nothing: { - case Response.Nothing: - { - UpdateStatus(StatusCode.ServiceResponseEmpty); - break; - } - case Response.Success: + UpdateStatus(StatusCode.ServiceResponseEmpty); + break; + } + case Response.Success: + { + HandleSuccessResponse(args); + break; + } + case Response.Error: + { + if (int.TryParse(args[0], out int value)) { - HandleSuccessResponse(args); - break; + UpdateStatus(StatusCode.ServiceCommandFail, value); } - case Response.Error: + break; + } + case Response.Temp: + { + if (int.TryParse(args[0], out int value)) { - if (int.TryParse(args[0], out int value)) - { - UpdateStatus(StatusCode.ServiceCommandFail, value); - } - break; + UpdateFanMon(value, 0); } - case Response.Temp: + break; + } + case Response.FanSpeed: + { + if (int.TryParse(args[0], out int value)) { - if (int.TryParse(args[0], out int value)) - { - UpdateFanMon(value, 0); - } - break; + UpdateFanMon(value, 1); } - case Response.FanSpeed: + break; + } + case Response.FanRPM: + { + if (int.TryParse(args[0], out int value)) { - if (int.TryParse(args[0], out int value)) - { - UpdateFanMon(value, 1); - } - break; + UpdateFanMon(value, 2); } - case Response.FanRPM: + break; + } + case Response.KeyLightBright: + { + if (int.TryParse(args[0], out int value)) { - if (int.TryParse(args[0], out int value)) + // value received from service should be valid, + // but let's check anyway to avoid potential crashes + // from non-official YAMDCC services + if (value < 0 || value > Config.KeyLightConf.MaxVal - Config.KeyLightConf.MinVal) { - UpdateFanMon(value, 2); + break; } - break; - } - case Response.KeyLightBright: - { - if (int.TryParse(args[0], out int value)) + + tbKeyLight.Invoke(new Action(delegate { - // value received from service should be valid, - // but let's check anyway to avoid potential crashes - // from non-official YAMDCC services - if (value < 0 || value > Config.KeyLightConf.MaxVal - Config.KeyLightConf.MinVal) - { - break; - } - - tbKeyLight.Invoke(new Action(delegate - { - tbKeyLight.Maximum = Config.KeyLightConf.MaxVal - Config.KeyLightConf.MinVal; - tbKeyLight.Value = value; - tbKeyLight.Enabled = lblKeyLightLow.Enabled = lblKeyLightHigh.Enabled = true; - ttMain.SetToolTip(tbKeyLight, Strings.GetString("ttKeyLight")); - })); - } - break; + tbKeyLight.Maximum = Config.KeyLightConf.MaxVal - Config.KeyLightConf.MinVal; + tbKeyLight.Value = value; + tbKeyLight.Enabled = lblKeyLightLow.Enabled = lblKeyLightHigh.Enabled = true; + ttMain.SetToolTip(tbKeyLight, Strings.GetString("ttKeyLight")); + })); } + break; } } } + } - private void IPCClient_Error(object sender, PipeErrorEventArgs e) - { - CrashDialog dlg = new(e.Exception); - dlg.ShowDialog(); - } + private void IPCClient_Error(object sender, PipeErrorEventArgs e) + { + CrashDialog dlg = new(e.Exception); + dlg.ShowDialog(); + } - private void HandleSuccessResponse(string[] args) + private void HandleSuccessResponse(string[] args) + { + if (int.TryParse(args[0], out int value)) { - if (int.TryParse(args[0], out int value)) - { - Command cmd = (Command)value; + Command cmd = (Command)value; - switch (cmd) - { - case Command.ApplyConfig: - btnApply.Invoke(() => btnApply.Enabled = tsiApply.Enabled = true); - UpdateStatus(StatusCode.ConfApplySuccess); - if (Config.KeyLightConf is not null) - { - SendServiceMessage(new ServiceCommand(Command.GetKeyLightBright, "")); - } - break; - case Command.FullBlast: - UpdateStatus(StatusCode.FullBlastToggleSuccess); - break; - } + switch (cmd) + { + case Command.ApplyConfig: + btnApply.Invoke(() => btnApply.Enabled = tsiApply.Enabled = true); + UpdateStatus(StatusCode.ConfApplySuccess); + if (Config.KeyLightConf is not null) + { + SendServiceMessage(new ServiceCommand(Command.GetKeyLightBright, "")); + } + break; + case Command.FullBlast: + UpdateStatus(StatusCode.FullBlastToggleSuccess); + break; } } + } - #region Tool strip menu items + #region Tool strip menu items - #region File - private void tsiLoadConf_Click(object sender, EventArgs e) + #region File + private void tsiLoadConf_Click(object sender, EventArgs e) + { + OpenFileDialog ofd = new() { - OpenFileDialog ofd = new() - { - AddExtension = true, - CheckFileExists = true, - Filter = "YAMDCC config files|*.xml", - Title = "Load config", - }; - - if (ofd.ShowDialog() == DialogResult.OK) - { - LoadConf(ofd.FileName); - SetLastConfPath(ofd.FileName); - btnRevert.Enabled = tsiRevert.Enabled = false; - } - } + AddExtension = true, + CheckFileExists = true, + Filter = "YAMDCC config files|*.xml", + Title = "Load config", + }; - private void tsiSaveConf_Click(object sender, EventArgs e) + if (ofd.ShowDialog() == DialogResult.OK) { - SaveFileDialog sfd = new() - { - AddExtension = true, - Filter = "YAMDCC config files|*.xml", - FileName = Config.Model.Replace(' ', '-'), - Title = "Save config", - }; - - if (sfd.ShowDialog() == DialogResult.OK) - { - Config.ChargeLimitConf.CurVal = (byte)(chkChgLim.Checked - ? numChgLim.Value : 0); - Config.Save(sfd.FileName); - SetLastConfPath(sfd.FileName); - btnRevert.Enabled = tsiRevert.Enabled = false; - } + LoadConf(ofd.FileName); + SetLastConfPath(ofd.FileName); + btnRevert.Enabled = tsiRevert.Enabled = false; } + } - private void tsiApply_Click(object sender, EventArgs e) + private void tsiSaveConf_Click(object sender, EventArgs e) + { + SaveFileDialog sfd = new() { - ApplyConf(); - } + AddExtension = true, + Filter = "YAMDCC config files|*.xml", + FileName = Config.Model.Replace(' ', '-'), + Title = "Save config", + }; - private void tsiRevert_Click(object sender, EventArgs e) + if (sfd.ShowDialog() == DialogResult.OK) { - RevertConf(); + Config.ChargeLimitConf.CurVal = (byte)(chkChgLim.Checked + ? numChgLim.Value : 0); + Config.Save(sfd.FileName); + SetLastConfPath(sfd.FileName); + btnRevert.Enabled = tsiRevert.Enabled = false; } + } - private void tsiExit_Click(object sender, EventArgs e) - { - Close(); - } - #endregion + private void tsiApply_Click(object sender, EventArgs e) + { + ApplyConf(); + } - #region Options - private void tsiProfAdd_Click(object sender, EventArgs e) - { - AddFanProfile(); - } + private void tsiRevert_Click(object sender, EventArgs e) + { + RevertConf(); + } - private void tsiProfRename_Click(object sender, EventArgs e) - { - FanCurveConf curveCfg = Config.FanConfs[cboFanSel.SelectedIndex] - .FanCurveConfs[cboProfSel.SelectedIndex]; + private void tsiExit_Click(object sender, EventArgs e) + { + Close(); + } + #endregion - TextInputDialog dlg = new( - Strings.GetString("dlgProfRen"), - "Change Profile Name", curveCfg.Name); - if (dlg.ShowDialog() == DialogResult.OK) - { - curveCfg.Name = dlg.Result; - cboProfSel.Items[cboProfSel.SelectedIndex] = dlg.Result; - btnRevert.Enabled = tsiRevert.Enabled = true; - } - } + #region Options + private void tsiProfAdd_Click(object sender, EventArgs e) + { + AddFanProfile(); + } - private void tsiProfChangeDesc_Click(object sender, EventArgs e) - { - FanCurveConf curveCfg = Config.FanConfs[cboFanSel.SelectedIndex] - .FanCurveConfs[cboProfSel.SelectedIndex]; - TextInputDialog dlg = new( - Strings.GetString("dlgProfChangeDesc"), - "Change Profile Description", curveCfg.Desc, true); - if (dlg.ShowDialog() == DialogResult.OK) - { - curveCfg.Desc = dlg.Result; - ttMain.SetToolTip(cboProfSel, Strings.GetString( - "ttProfSel", dlg.Result)); - btnRevert.Enabled = tsiRevert.Enabled = true; - } - } + private void tsiProfRename_Click(object sender, EventArgs e) + { + FanCurveConf curveCfg = Config.FanConfs[cboFanSel.SelectedIndex] + .FanCurveConfs[cboProfSel.SelectedIndex]; - private void tsiProfDel_Click(object sender, EventArgs e) + TextInputDialog dlg = new( + Strings.GetString("dlgProfRen"), + "Change Profile Name", curveCfg.Name); + if (dlg.ShowDialog() == DialogResult.OK) { - DelFanProfile(); + curveCfg.Name = dlg.Result; + cboProfSel.Items[cboProfSel.SelectedIndex] = dlg.Result; + btnRevert.Enabled = tsiRevert.Enabled = true; } + } - private void tsiECtoConf_Click(object sender, EventArgs e) - { - if (MessageBox.Show(Strings.GetString("dlgECtoConfStart"), - "Default fan profile from EC?", MessageBoxButtons.YesNo, - MessageBoxIcon.Information) == DialogResult.Yes) - { - StreamWriter sw = new(Paths.ECToConfPending, false); - try - { - sw.Write(1); - sw.Flush(); - } - finally - { - sw.Close(); - } - Application.Exit(); - } + private void tsiProfChangeDesc_Click(object sender, EventArgs e) + { + FanCurveConf curveCfg = Config.FanConfs[cboFanSel.SelectedIndex] + .FanCurveConfs[cboProfSel.SelectedIndex]; + TextInputDialog dlg = new( + Strings.GetString("dlgProfChangeDesc"), + "Change Profile Description", curveCfg.Desc, true); + if (dlg.ShowDialog() == DialogResult.OK) + { + curveCfg.Desc = dlg.Result; + ttMain.SetToolTip(cboProfSel, Strings.GetString( + "ttProfSel", dlg.Result)); + btnRevert.Enabled = tsiRevert.Enabled = true; } + } - private void tsiECMon_Click(object sender, EventArgs e) + private void tsiProfDel_Click(object sender, EventArgs e) + { + DelFanProfile(); + } + + private void tsiECtoConf_Click(object sender, EventArgs e) + { + if (MessageBox.Show(Strings.GetString("dlgECtoConfStart"), + "Default fan profile from EC?", MessageBoxButtons.YesNo, + MessageBoxIcon.Information) == DialogResult.Yes) { - if (tsiECMon.Checked) + StreamWriter sw = new(Paths.ECToConfPending, false); + try { - tmrPoll.Start(); - PollEC(); - lblFanSpd.Visible = true; - lblFanRPM.Visible = true; - lblTemp.Visible = true; + sw.Write(1); + sw.Flush(); } - else + finally { - tmrPoll.Stop(); - lblFanSpd.Visible = false; - lblFanRPM.Visible = false; - lblTemp.Visible = false; + sw.Close(); } + Application.Exit(); } + } - private void tsiStopSvc_Click(object sender, EventArgs e) + private void tsiECMon_Click(object sender, EventArgs e) + { + if (tsiECMon.Checked) { - if (MessageBox.Show( - Strings.GetString("dlgSvcStop"), "Stop Service", - MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) - { - tmrPoll.Stop(); - IPCClient.Stop(); - Hide(); + tmrPoll.Start(); + PollEC(); + lblFanSpd.Visible = true; + lblFanRPM.Visible = true; + lblTemp.Visible = true; + } + else + { + tmrPoll.Stop(); + lblFanSpd.Visible = false; + lblFanRPM.Visible = false; + lblTemp.Visible = false; + } + } + + private void tsiStopSvc_Click(object sender, EventArgs e) + { + if (MessageBox.Show( + Strings.GetString("dlgSvcStop"), "Stop Service", + MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) + { + tmrPoll.Stop(); + IPCClient.Stop(); + Hide(); - ProgressDialog dlg = new(Strings.GetString("dlgSvcStopping"), - static (e) => + ProgressDialog dlg = new(Strings.GetString("dlgSvcStopping"), + static (e) => + { + if (!Utils.StopService("yamdccsvc")) { - if (!Utils.StopService("yamdccsvc")) - { - Utils.ShowError(Strings.GetString("dlgSvcStopErr")); - } - }); - dlg.ShowDialog(); + Utils.ShowError(Strings.GetString("dlgSvcStopErr")); + } + }); + dlg.ShowDialog(); - Close(); - } + Close(); } + } - private void tsiUninstall_Click(object sender, EventArgs e) + private void tsiUninstall_Click(object sender, EventArgs e) + { + if (MessageBox.Show(Strings.GetString("dlgUninstall"), "Uninstall Service", + MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { - if (MessageBox.Show(Strings.GetString("dlgUninstall"), "Uninstall Service", - MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) + bool delData = MessageBox.Show( + Strings.GetString("dlgSvcDelData", Paths.Data), + "Delete configuration data?", + MessageBoxButtons.YesNo, MessageBoxIcon.Warning, + MessageBoxDefaultButton.Button2) == DialogResult.Yes; + + tmrPoll.Stop(); + IPCClient.Stop(); + Hide(); + + // Apparently this fixes the YAMDCC service not uninstalling + // when YAMDCC is launched by certain means + ProgressDialog dlg = new(Strings.GetString("dlgSvcUninstalling"), (e) => { - bool delData = MessageBox.Show( - Strings.GetString("dlgSvcDelData", Paths.Data), - "Delete configuration data?", - MessageBoxButtons.YesNo, MessageBoxIcon.Warning, - MessageBoxDefaultButton.Button2) == DialogResult.Yes; - - tmrPoll.Stop(); - IPCClient.Stop(); - Hide(); - - // Apparently this fixes the YAMDCC service not uninstalling - // when YAMDCC is launched by certain means - ProgressDialog dlg = new(Strings.GetString("dlgSvcUninstalling"), (e) => + if (Utils.StopService("yamdccsvc")) { - if (Utils.StopService("yamdccsvc")) + if (Utils.UninstallService("yamdccsvc")) { - if (Utils.UninstallService("yamdccsvc")) - { - // Only delete service data if the - // service uninstalled successfully - if (delData) - { - Directory.Delete(Paths.Data, true); - } - } - else + // Only delete service data if the + // service uninstalled successfully + if (delData) { - Utils.ShowError(Strings.GetString("dlgUninstallErr")); + Directory.Delete(Paths.Data, true); } } else { - Utils.ShowError(Strings.GetString("dlgSvcStopErr")); + Utils.ShowError(Strings.GetString("dlgUninstallErr")); } - }); - dlg.ShowDialog(); + } + else + { + Utils.ShowError(Strings.GetString("dlgSvcStopErr")); + } + }); + dlg.ShowDialog(); - Close(); - } + Close(); } - #endregion + } + #endregion + + #region Help + private void tsiAbout_Click(object sender, EventArgs e) + { + new VersionDialog().ShowDialog(); + } + + private void tsiSrc_Click(object sender, EventArgs e) + { + Process.Start(Paths.GitHubPage); + } + #endregion - #region Help - private void tsiAbout_Click(object sender, EventArgs e) + #endregion + + private void cboFanSel_IndexChanged(object sender, EventArgs e) + { + if (Config is not null) { - new VersionDialog().ShowDialog(); + UpdateFanCurveDisplay(); } + } + + private void cboProfSel_IndexChanged(object sender, EventArgs e) + { + FanConf cfg = Config.FanConfs[cboFanSel.SelectedIndex]; + FanCurveConf curveCfg = cfg.FanCurveConfs[cboProfSel.SelectedIndex]; - private void tsiSrc_Click(object sender, EventArgs e) + if (tsiSwitchAll.Checked) + { + for (int i = 0; i < Config.FanConfs.Length; i++) + { + Config.FanConfs[i].CurveSel = cboProfSel.SelectedIndex; + } + } + else { - Process.Start(Paths.GitHubPage); + cfg.CurveSel = cboProfSel.SelectedIndex; } - #endregion - #endregion + ttMain.SetToolTip(cboProfSel, Strings.GetString( + "ttProfSel", cfg.FanCurveConfs[cfg.CurveSel].Desc)); + + int numTempThresholds = cfg.UpThresholdRegs.Length; - private void cboFanSel_IndexChanged(object sender, EventArgs e) + // Fan curve + for (int i = 0; i < numFanSpds.Length; i++) { - if (Config is not null) + if (i <= numTempThresholds) { - UpdateFanCurveDisplay(); + numFanSpds[i].Value = tbFanSpds[i].Value + = curveCfg.TempThresholds[i].FanSpeed; + + numFanSpds[i].Enabled = tbFanSpds[i].Enabled = curveCfg.Name != "Default"; } } - private void cboProfSel_IndexChanged(object sender, EventArgs e) + // Temp thresholds + for (int i = 0; i < numUpTs.Length; i++) { - FanConf cfg = Config.FanConfs[cboFanSel.SelectedIndex]; - FanCurveConf curveCfg = cfg.FanCurveConfs[cboProfSel.SelectedIndex]; - - if (tsiSwitchAll.Checked) + if (i <= numTempThresholds) { - for (int i = 0; i < Config.FanConfs.Length; i++) - { - Config.FanConfs[i].CurveSel = cboProfSel.SelectedIndex; - } + TempThreshold t = curveCfg.TempThresholds[i + 1]; + numUpTs[i].Value = t.UpThreshold; + numDownTs[i].Value = t.DownThreshold; + + numUpTs[i].Enabled = numDownTs[i].Enabled = curveCfg.Name != "Default"; } else { - cfg.CurveSel = cboProfSel.SelectedIndex; + numUpTs[i].Enabled = numDownTs[i].Enabled = false; } + } + btnApply.Enabled = tsiApply.Enabled = true; + btnProfDel.Enabled = tsiProfDel.Enabled = curveCfg.Name != "Default"; + } - ttMain.SetToolTip(cboProfSel, Strings.GetString( - "ttProfSel", cfg.FanCurveConfs[cfg.CurveSel].Desc)); - - int numTempThresholds = cfg.UpThresholdRegs.Length; + private void btnProfAdd_Click(object sender, EventArgs e) + { + AddFanProfile(); + } - // Fan curve - for (int i = 0; i < numFanSpds.Length; i++) - { - if (i <= numTempThresholds) + private void btnProfAdd_KeyPress(object sender, KeyPressEventArgs e) + { + // hidden crash test + switch (e.KeyChar) + { + case 'd': + Debug = Debug == 0 ? 1 : 0; + break; + case 'e': + Debug = Debug == 1 ? 2 : 0; + break; + case 'b': + Debug = Debug == 2 ? 3 : 0; + break; + case 'u': + Debug = Debug == 3 ? 4 : 0; + break; + case 'g': + if (Debug == 4) { - numFanSpds[i].Value = tbFanSpds[i].Value - = curveCfg.TempThresholds[i].FanSpeed; + Debug = 0; - numFanSpds[i].Enabled = tbFanSpds[i].Enabled = curveCfg.Name != "Default"; + // should throw a NullReferenceException + // which should get caught by CrashDialog + YAMDCC_Config cfg = new(); + lblFanSpd.Text = cfg.FanConfs[0].Name; } - } - - // Temp thresholds - for (int i = 0; i < numUpTs.Length; i++) - { - if (i <= numTempThresholds) - { - TempThreshold t = curveCfg.TempThresholds[i + 1]; - numUpTs[i].Value = t.UpThreshold; - numDownTs[i].Value = t.DownThreshold; + break; + default: + Debug = 0; + break; + } + } - numUpTs[i].Enabled = numDownTs[i].Enabled = curveCfg.Name != "Default"; - } - else - { - numUpTs[i].Enabled = numDownTs[i].Enabled = false; - } - } - btnApply.Enabled = tsiApply.Enabled = true; - btnProfDel.Enabled = tsiProfDel.Enabled = curveCfg.Name != "Default"; - } + private void btnProfDel_Click(object sender, EventArgs e) + { + DelFanProfile(); + } - private void btnProfAdd_Click(object sender, EventArgs e) - { - AddFanProfile(); - } + private void numFanSpd_Changed(object sender, EventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + int i = (int)nud.Tag; + tbFanSpds[i].Value = (int)numFanSpds[i].Value; - private void btnProfAdd_KeyPress(object sender, KeyPressEventArgs e) - { - // hidden crash test - switch (e.KeyChar) - { - case 'd': - Debug = Debug == 0 ? 1 : 0; - break; - case 'e': - Debug = Debug == 1 ? 2 : 0; - break; - case 'b': - Debug = Debug == 2 ? 3 : 0; - break; - case 'u': - Debug = Debug == 3 ? 4 : 0; - break; - case 'g': - if (Debug == 4) - { - Debug = 0; + Config.FanConfs[cboFanSel.SelectedIndex] + .FanCurveConfs[cboProfSel.SelectedIndex] + .TempThresholds[i].FanSpeed = (byte)numFanSpds[i].Value; - // should throw a NullReferenceException - // which should get caught by CrashDialog - YAMDCC_Config cfg = new(); - lblFanSpd.Text = cfg.FanConfs[0].Name; - } - break; - default: - Debug = 0; - break; - } - } + btnRevert.Enabled = tsiRevert.Enabled = true; + } - private void btnProfDel_Click(object sender, EventArgs e) - { - DelFanProfile(); - } + private void tbFanSpd_Scroll(object sender, EventArgs e) + { + TrackBar tb = (TrackBar)sender; + int i = (int)tb.Tag; + numFanSpds[i].Value = tbFanSpds[i].Value; - private void numFanSpd_Changed(object sender, EventArgs e) - { - NumericUpDown nud = (NumericUpDown)sender; - int i = (int)nud.Tag; - tbFanSpds[i].Value = (int)numFanSpds[i].Value; + Config.FanConfs[cboFanSel.SelectedIndex] + .FanCurveConfs[cboProfSel.SelectedIndex] + .TempThresholds[i].FanSpeed = (byte)numFanSpds[i].Value; - Config.FanConfs[cboFanSel.SelectedIndex] - .FanCurveConfs[cboProfSel.SelectedIndex] - .TempThresholds[i].FanSpeed = (byte)numFanSpds[i].Value; + btnRevert.Enabled = tsiRevert.Enabled = true; + } - btnRevert.Enabled = tsiRevert.Enabled = true; - } + private void numUpT_Changed(object sender, EventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + int i = (int)nud.Tag; - private void tbFanSpd_Scroll(object sender, EventArgs e) - { - TrackBar tb = (TrackBar)sender; - int i = (int)tb.Tag; - numFanSpds[i].Value = tbFanSpds[i].Value; + TempThreshold threshold = Config.FanConfs[cboFanSel.SelectedIndex] + .FanCurveConfs[cboProfSel.SelectedIndex] + .TempThresholds[i + 1]; - Config.FanConfs[cboFanSel.SelectedIndex] - .FanCurveConfs[cboProfSel.SelectedIndex] - .TempThresholds[i].FanSpeed = (byte)numFanSpds[i].Value; + // Update associated down threshold slider + numDownTs[i].Value += nud.Value - threshold.UpThreshold; - btnRevert.Enabled = tsiRevert.Enabled = true; - } + threshold.UpThreshold = (byte)numUpTs[i].Value; - private void numUpT_Changed(object sender, EventArgs e) - { - NumericUpDown nud = (NumericUpDown)sender; - int i = (int)nud.Tag; + btnRevert.Enabled = tsiRevert.Enabled = true; + } + + private void numDownT_Changed(object sender, EventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + int i = (int)nud.Tag; + + Config.FanConfs[cboFanSel.SelectedIndex] + .FanCurveConfs[cboProfSel.SelectedIndex] + .TempThresholds[i + 1].DownThreshold = (byte)numDownTs[i].Value; - TempThreshold threshold = Config.FanConfs[cboFanSel.SelectedIndex] - .FanCurveConfs[cboProfSel.SelectedIndex] - .TempThresholds[i + 1]; + btnRevert.Enabled = tsiRevert.Enabled = true; + } - // Update associated down threshold slider - numDownTs[i].Value += nud.Value - threshold.UpThreshold; + private void chkFullBlast_Toggled(object sender, EventArgs e) + { + SendServiceMessage(new ServiceCommand(Command.FullBlast, chkFullBlast.Checked ? "1" : "0")); + } - threshold.UpThreshold = (byte)numUpTs[i].Value; + private void chkChgLim_CheckedChanged(object sender, EventArgs e) + { + numChgLim.Enabled = chkChgLim.Checked; + } + private void numChgLim_Changed(object sender, EventArgs e) + { + if (Config is not null) + { btnRevert.Enabled = tsiRevert.Enabled = true; } + } - private void numDownT_Changed(object sender, EventArgs e) + private void cboPerfMode_IndexChanged(object sender, EventArgs e) + { + if (Config is not null) { - NumericUpDown nud = (NumericUpDown)sender; - int i = (int)nud.Tag; - - Config.FanConfs[cboFanSel.SelectedIndex] - .FanCurveConfs[cboProfSel.SelectedIndex] - .TempThresholds[i + 1].DownThreshold = (byte)numDownTs[i].Value; - + int idx = cboPerfMode.SelectedIndex; + Config.PerfModeConf.ModeSel = idx; + ttMain.SetToolTip(cboPerfMode, + Strings.GetString("ttPerfMode", Config.PerfModeConf.PerfModes[idx].Desc)); btnRevert.Enabled = tsiRevert.Enabled = true; } + } + + private void chkWinFnSwap_Toggled(object sender, EventArgs e) + { + Config.KeySwapConf.Enabled = chkWinFnSwap.Checked; + btnRevert.Enabled = tsiRevert.Enabled = true; + } + + private void tbKeyLight_Scroll(object sender, EventArgs e) + { + SendServiceMessage(new ServiceCommand(Command.SetKeyLightBright, $"{tbKeyLight.Value}")); + } + + private void btnRevert_Click(object sender, EventArgs e) + { + RevertConf(); + } + + private void btnApply_Click(object sender, EventArgs e) + { + btnApply.Enabled = tsiApply.Enabled = false; + ApplyConf(); + } + + private void tmrPoll_Tick(object sender, EventArgs e) + { + PollEC(); + } + + private void tmrStatusReset_Tick(object sender, EventArgs e) + { + UpdateStatus(StatusCode.None); + tmrStatusReset.Stop(); + } + + private void tmrSvcTimeout_Tick(object sender, EventArgs e) + { + UpdateStatus(StatusCode.ServiceTimeout); + tmrSvcTimeout.Stop(); + } + + #endregion // Events - private void chkFullBlast_Toggled(object sender, EventArgs e) + #region Private methods + private void UpdateFanMon(int value, int i) + { + switch (i) { - SendServiceMessage(new ServiceCommand(Command.FullBlast, chkFullBlast.Checked ? "1" : "0")); + case 0: + lblTemp.Invoke(new Action(delegate + { + lblTemp.Text = $"Temp: {value}°C"; + })); + break; + case 1: + lblFanSpd.Invoke(new Action(delegate + { + lblFanSpd.Text = $"Fan speed: {value}%"; + })); + break; + case 2: + lblFanRPM.Invoke(new Action(delegate + { + lblFanRPM.Text = value == -1 + ? "RPM: 0" + : $"RPM: {value}"; + })); + break; } + } + + private void LoadConf(string confPath) + { + UpdateStatus(StatusCode.ConfLoading); - private void chkChgLim_CheckedChanged(object sender, EventArgs e) + try { - numChgLim.Enabled = chkChgLim.Checked; + Config = YAMDCC_Config.Load(confPath); + LoadConf(Config); } - - private void numChgLim_Changed(object sender, EventArgs e) + catch (Exception ex) { - if (Config is not null) + if (ex is InvalidConfigException or InvalidOperationException or FileNotFoundException) { - btnRevert.Enabled = tsiRevert.Enabled = true; + UpdateStatus(StatusCode.NoConfig); + return; } - } - - private void cboPerfMode_IndexChanged(object sender, EventArgs e) - { - if (Config is not null) + else { - int idx = cboPerfMode.SelectedIndex; - Config.PerfModeConf.ModeSel = idx; - ttMain.SetToolTip(cboPerfMode, - Strings.GetString("ttPerfMode", Config.PerfModeConf.PerfModes[idx].Desc)); - btnRevert.Enabled = tsiRevert.Enabled = true; + throw; } } + } - private void chkWinFnSwap_Toggled(object sender, EventArgs e) - { - Config.KeySwapConf.Enabled = chkWinFnSwap.Checked; - btnRevert.Enabled = tsiRevert.Enabled = true; - } + private void LoadConf(YAMDCC_Config cfg) + { + DisableAll(); + tsiSwitchAll.Checked = FansHaveSameProfileCount(); - private void tbKeyLight_Scroll(object sender, EventArgs e) - { - SendServiceMessage(new ServiceCommand(Command.SetKeyLightBright, $"{tbKeyLight.Value}")); - } + tsiSaveConf.Enabled = true; - private void btnRevert_Click(object sender, EventArgs e) + if (cfg.FullBlastConf is null) { - RevertConf(); + ttMain.SetToolTip(chkFullBlast, Strings.GetString("ttNotSupported")); } - - private void btnApply_Click(object sender, EventArgs e) + else { - btnApply.Enabled = tsiApply.Enabled = false; - ApplyConf(); + ttMain.SetToolTip(chkFullBlast, Strings.GetString("ttFullBlast")); + chkFullBlast.Enabled = true; } - private void tmrPoll_Tick(object sender, EventArgs e) + if (cfg.ChargeLimitConf is null) { - PollEC(); + ttMain.SetToolTip(chkFullBlast, Strings.GetString("ttNotSupported")); } - - private void tmrStatusReset_Tick(object sender, EventArgs e) + else { - UpdateStatus(StatusCode.None); - tmrStatusReset.Stop(); + ttMain.SetToolTip(numChgLim, Strings.GetString("ttChgLim")); + ChargeLimitConf chgLimConf = cfg.ChargeLimitConf; + chkChgLim.Enabled = numChgLim.Enabled = true; + numChgLim.Maximum = Math.Abs(chgLimConf.MaxVal - chgLimConf.MinVal); + if (chgLimConf.CurVal == 0) + { + chkChgLim.Checked = false; + numChgLim.Value = 80; + } + else + { + chkChgLim.Checked = true; + numChgLim.Value = chgLimConf.CurVal; + } } - private void tmrSvcTimeout_Tick(object sender, EventArgs e) + cboPerfMode.Items.Clear(); + if (cfg.PerfModeConf is null) { - UpdateStatus(StatusCode.ServiceTimeout); - tmrSvcTimeout.Stop(); + ttMain.SetToolTip(cboPerfMode, Strings.GetString("ttNotSupported")); } - - #endregion // Events - - #region Private methods - private void UpdateFanMon(int value, int i) + else { - switch (i) + PerfModeConf perfModeConf = cfg.PerfModeConf; + for (int i = 0; i < perfModeConf.PerfModes.Length; i++) { - case 0: - lblTemp.Invoke(new Action(delegate - { - lblTemp.Text = $"Temp: {value}°C"; - })); - break; - case 1: - lblFanSpd.Invoke(new Action(delegate - { - lblFanSpd.Text = $"Fan speed: {value}%"; - })); - break; - case 2: - lblFanRPM.Invoke(new Action(delegate - { - lblFanRPM.Text = value == -1 - ? "RPM: 0" - : $"RPM: {value}"; - })); - break; + cboPerfMode.Items.Add(perfModeConf.PerfModes[i].Name); } + + cboPerfMode.SelectedIndex = perfModeConf.ModeSel; + ttMain.SetToolTip(cboPerfMode, Strings.GetString( + "ttPerfMode", perfModeConf.PerfModes[perfModeConf.ModeSel].Desc)); + cboPerfMode.Enabled = true; } - private void LoadConf(string confPath) + if (cfg.KeySwapConf is null) { - UpdateStatus(StatusCode.ConfLoading); - - try - { - Config = YAMDCC_Config.Load(confPath); - LoadConf(Config); - } - catch (Exception ex) - { - if (ex is InvalidConfigException or InvalidOperationException or FileNotFoundException) - { - UpdateStatus(StatusCode.NoConfig); - return; - } - else - { - throw; - } - } + ttMain.SetToolTip(chkWinFnSwap, Strings.GetString("ttNotSupported")); + } + else + { + chkWinFnSwap.Checked = cfg.KeySwapConf.Enabled; + ttMain.SetToolTip(chkWinFnSwap, Strings.GetString("ttKeySwap")); + chkWinFnSwap.Enabled = true; } - private void LoadConf(YAMDCC_Config cfg) + cboFanSel.Items.Clear(); + for (int i = 0; i < cfg.FanConfs.Length; i++) { - DisableAll(); - tsiSwitchAll.Checked = FansHaveSameProfileCount(); + cboFanSel.Items.Add(cfg.FanConfs[i].Name); + } - tsiSaveConf.Enabled = true; + btnProfAdd.Enabled = true; + tsiProfAdd.Enabled = tsiProfEdit.Enabled = true; + tsiECtoConf.Enabled = true; + cboFanSel.Enabled = true; + cboFanSel.SelectedIndex = 0; + tsiECMon.Enabled = true; - if (cfg.FullBlastConf is null) - { - ttMain.SetToolTip(chkFullBlast, Strings.GetString("ttNotSupported")); - } - else - { - ttMain.SetToolTip(chkFullBlast, Strings.GetString("ttFullBlast")); - chkFullBlast.Enabled = true; - } + UpdateStatus(StatusCode.None); + } + + private void ApplyConf() + { + // Save the updated config + Config.ChargeLimitConf.CurVal = (byte)(chkChgLim.Checked + ? numChgLim.Value : 0); + Config.Save(Paths.CurrentConfig); + + // Tell the service to reload and apply the updated config + SendServiceMessage(new ServiceCommand(Command.ApplyConfig, null)); + } - if (cfg.ChargeLimitConf is null) + private void RevertConf() + { + if (MessageBox.Show( + Strings.GetString("dlgRevert"), + "Revert?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) + == DialogResult.Yes) + { + try { - ttMain.SetToolTip(chkFullBlast, Strings.GetString("ttNotSupported")); + YAMDCC_Config tempConf = YAMDCC_Config.Load(GetLastConfPath()); + LoadConf(tempConf); + Config = tempConf; + UpdateFanCurveDisplay(); + ApplyConf(); + btnRevert.Enabled = tsiRevert.Enabled = false; } - else + catch (Exception ex) { - ttMain.SetToolTip(numChgLim, Strings.GetString("ttChgLim")); - ChargeLimitConf chgLimConf = cfg.ChargeLimitConf; - chkChgLim.Enabled = numChgLim.Enabled = true; - numChgLim.Maximum = Math.Abs(chgLimConf.MaxVal - chgLimConf.MinVal); - if (chgLimConf.CurVal == 0) + if (ex is FileNotFoundException) { - chkChgLim.Checked = false; - numChgLim.Value = 80; + Utils.ShowError(Strings.GetString("dlgOldConfMissing")); } - else + else if (ex is InvalidConfigException or InvalidOperationException) { - chkChgLim.Checked = true; - numChgLim.Value = chgLimConf.CurVal; + Utils.ShowError(Strings.GetString("dlgOldConfInvalid")); } - } - - cboPerfMode.Items.Clear(); - if (cfg.PerfModeConf is null) - { - ttMain.SetToolTip(cboPerfMode, Strings.GetString("ttNotSupported")); - } - else - { - PerfModeConf perfModeConf = cfg.PerfModeConf; - for (int i = 0; i < perfModeConf.PerfModes.Length; i++) + else { - cboPerfMode.Items.Add(perfModeConf.PerfModes[i].Name); + throw; } - - cboPerfMode.SelectedIndex = perfModeConf.ModeSel; - ttMain.SetToolTip(cboPerfMode, Strings.GetString( - "ttPerfMode", perfModeConf.PerfModes[perfModeConf.ModeSel].Desc)); - cboPerfMode.Enabled = true; - } - - if (cfg.KeySwapConf is null) - { - ttMain.SetToolTip(chkWinFnSwap, Strings.GetString("ttNotSupported")); - } - else - { - chkWinFnSwap.Checked = cfg.KeySwapConf.Enabled; - ttMain.SetToolTip(chkWinFnSwap, Strings.GetString("ttKeySwap")); - chkWinFnSwap.Enabled = true; - } - - cboFanSel.Items.Clear(); - for (int i = 0; i < cfg.FanConfs.Length; i++) - { - cboFanSel.Items.Add(cfg.FanConfs[i].Name); } - - btnProfAdd.Enabled = true; - tsiProfAdd.Enabled = tsiProfEdit.Enabled = true; - tsiECtoConf.Enabled = true; - cboFanSel.Enabled = true; - cboFanSel.SelectedIndex = 0; - tsiECMon.Enabled = true; - - UpdateStatus(StatusCode.None); } + } - private void ApplyConf() - { - // Save the updated config - Config.ChargeLimitConf.CurVal = (byte)(chkChgLim.Checked - ? numChgLim.Value : 0); - Config.Save(Paths.CurrentConfig); + private void PollEC() + { + SendServiceMessage(new ServiceCommand(Command.GetTemp, $"{cboFanSel.SelectedIndex}")); + SendServiceMessage(new ServiceCommand(Command.GetFanSpeed, $"{cboFanSel.SelectedIndex}")); + SendServiceMessage(new ServiceCommand(Command.GetFanRPM, $"{cboFanSel.SelectedIndex}")); + } - // Tell the service to reload and apply the updated config - SendServiceMessage(new ServiceCommand(Command.ApplyConfig, null)); + private void AddFanProfile() + { + if (tsiSwitchAll.Checked) + { + bool switchAll = tsiSwitchAll.Checked; + tsiSwitchAll.Checked = false; + AddFanProfImpl(0, cboFanSel.Items.Count); + tsiSwitchAll.Checked = switchAll; } - - private void RevertConf() + else { - if (MessageBox.Show( - Strings.GetString("dlgRevert"), - "Revert?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) - == DialogResult.Yes) - { - try - { - YAMDCC_Config tempConf = YAMDCC_Config.Load(GetLastConfPath()); - LoadConf(tempConf); - Config = tempConf; - UpdateFanCurveDisplay(); - ApplyConf(); - btnRevert.Enabled = tsiRevert.Enabled = false; - } - catch (Exception ex) - { - if (ex is FileNotFoundException) - { - Utils.ShowError(Strings.GetString("dlgOldConfMissing")); - } - else if (ex is InvalidConfigException or InvalidOperationException) - { - Utils.ShowError(Strings.GetString("dlgOldConfInvalid")); - } - else - { - throw; - } - } - } + AddFanProfImpl(cboFanSel.SelectedIndex, cboFanSel.SelectedIndex + 1); } - private void PollEC() + if (!FansHaveSameProfileCount()) { - SendServiceMessage(new ServiceCommand(Command.GetTemp, $"{cboFanSel.SelectedIndex}")); - SendServiceMessage(new ServiceCommand(Command.GetFanSpeed, $"{cboFanSel.SelectedIndex}")); - SendServiceMessage(new ServiceCommand(Command.GetFanRPM, $"{cboFanSel.SelectedIndex}")); + tsiSwitchAll.Checked = false; } - private void AddFanProfile() - { - if (tsiSwitchAll.Checked) - { - bool switchAll = tsiSwitchAll.Checked; - tsiSwitchAll.Checked = false; - AddFanProfImpl(0, cboFanSel.Items.Count); - tsiSwitchAll.Checked = switchAll; - } - else - { - AddFanProfImpl(cboFanSel.SelectedIndex, cboFanSel.SelectedIndex + 1); - } + btnRevert.Enabled = tsiRevert.Enabled = true; + } - if (!FansHaveSameProfileCount()) - { - tsiSwitchAll.Checked = false; - } + private void AddFanProfImpl(int start, int end) + { + FanConf cfg = Config.FanConfs[cboFanSel.SelectedIndex]; + string oldProfName = cfg.FanCurveConfs[cboProfSel.SelectedIndex].Name; - btnRevert.Enabled = tsiRevert.Enabled = true; - } + TextInputDialog dlg = new( + Strings.GetString("dlgProfAdd"), + "New Profile", $"Copy of {oldProfName}"); - private void AddFanProfImpl(int start, int end) + if (dlg.ShowDialog() == DialogResult.OK) { - FanConf cfg = Config.FanConfs[cboFanSel.SelectedIndex]; - string oldProfName = cfg.FanCurveConfs[cboProfSel.SelectedIndex].Name; - - TextInputDialog dlg = new( - Strings.GetString("dlgProfAdd"), - "New Profile", $"Copy of {oldProfName}"); - - if (dlg.ShowDialog() == DialogResult.OK) + for (int i = start; i < end; i++) { - for (int i = start; i < end; i++) - { - cfg = Config.FanConfs[i]; - FanCurveConf oldCurveCfg = cfg.FanCurveConfs[cfg.CurveSel]; + cfg = Config.FanConfs[i]; + FanCurveConf oldCurveCfg = cfg.FanCurveConfs[cfg.CurveSel]; - // Create a copy of the currently selected fan profile: - FanCurveConf newCurveCfg = oldCurveCfg.Copy(); + // Create a copy of the currently selected fan profile: + FanCurveConf newCurveCfg = oldCurveCfg.Copy(); - // Name it according to what the user specified - newCurveCfg.Name = dlg.Result; - newCurveCfg.Desc = $"(Copy of {oldCurveCfg.Name})\n{oldCurveCfg.Desc}"; + // Name it according to what the user specified + newCurveCfg.Name = dlg.Result; + newCurveCfg.Desc = $"(Copy of {oldCurveCfg.Name})\n{oldCurveCfg.Desc}"; - // Add the new fan profile to the config's list - cfg.FanCurveConfs = [.. cfg.FanCurveConfs, newCurveCfg]; - cfg.CurveSel = cfg.FanCurveConfs.Length - 1; + // Add the new fan profile to the config's list + cfg.FanCurveConfs = [.. cfg.FanCurveConfs, newCurveCfg]; + cfg.CurveSel = cfg.FanCurveConfs.Length - 1; - // Add the new fan profile to the UI's profile list and select it: - if (i == cboFanSel.SelectedIndex) - { - cboProfSel.Items.Add(dlg.Result); - cboProfSel.SelectedIndex = cfg.CurveSel; - } + // Add the new fan profile to the UI's profile list and select it: + if (i == cboFanSel.SelectedIndex) + { + cboProfSel.Items.Add(dlg.Result); + cboProfSel.SelectedIndex = cfg.CurveSel; } } } + } - private void DelFanProfile() + private void DelFanProfile() + { + if (tsiSwitchAll.Checked) { - if (tsiSwitchAll.Checked) - { - DelFanProfImpl(0, cboFanSel.Items.Count); - } - else - { - DelFanProfImpl(cboFanSel.SelectedIndex, cboFanSel.SelectedIndex + 1); - } - - if (!FansHaveSameProfileCount()) - { - tsiSwitchAll.Checked = false; - } + DelFanProfImpl(0, cboFanSel.Items.Count); + } + else + { + DelFanProfImpl(cboFanSel.SelectedIndex, cboFanSel.SelectedIndex + 1); + } - btnRevert.Enabled = tsiRevert.Enabled = true; + if (!FansHaveSameProfileCount()) + { + tsiSwitchAll.Checked = false; } - private void DelFanProfImpl(int start, int end) + btnRevert.Enabled = tsiRevert.Enabled = true; + } + + private void DelFanProfImpl(int start, int end) + { + for (int i = start; i < end; i++) { - for (int i = start; i < end; i++) + FanConf cfg = Config.FanConfs[i]; + FanCurveConf curveCfg = cfg.FanCurveConfs[cfg.CurveSel]; + + if (curveCfg.Name != "Default" && MessageBox.Show( + Strings.GetString("dlgProfDel", curveCfg.Name), + $"Delete fan profile? ({cfg.Name})", MessageBoxButtons.YesNo, + MessageBoxIcon.Warning) == DialogResult.Yes) { - FanConf cfg = Config.FanConfs[i]; - FanCurveConf curveCfg = cfg.FanCurveConfs[cfg.CurveSel]; + // Remove the fan profile from the config's list + List curveCfgList = [.. cfg.FanCurveConfs]; + curveCfgList.RemoveAt(cfg.CurveSel); + cfg.FanCurveConfs = [.. curveCfgList]; + cfg.CurveSel -= 1; - if (curveCfg.Name != "Default" && MessageBox.Show( - Strings.GetString("dlgProfDel", curveCfg.Name), - $"Delete fan profile? ({cfg.Name})", MessageBoxButtons.YesNo, - MessageBoxIcon.Warning) == DialogResult.Yes) + // Remove from the list client-side, and select a different fan profile + if (i == cboFanSel.SelectedIndex) { - // Remove the fan profile from the config's list - List curveCfgList = [.. cfg.FanCurveConfs]; - curveCfgList.RemoveAt(cfg.CurveSel); - cfg.FanCurveConfs = [.. curveCfgList]; - cfg.CurveSel -= 1; - - // Remove from the list client-side, and select a different fan profile - if (i == cboFanSel.SelectedIndex) - { - cboProfSel.Items.RemoveAt(cboProfSel.SelectedIndex); - cboProfSel.SelectedIndex = cfg.CurveSel; - } + cboProfSel.Items.RemoveAt(cboProfSel.SelectedIndex); + cboProfSel.SelectedIndex = cfg.CurveSel; } } } + } - private static string GetLastConfPath() + private static string GetLastConfPath() + { + StreamReader sr = new(Paths.LastConfig, Encoding.UTF8); + try { - StreamReader sr = new(Paths.LastConfig, Encoding.UTF8); - try - { - string path = sr.ReadLine(); - return path; - } - finally - { - sr.Close(); - } + string path = sr.ReadLine(); + return path; } - - private static void SetLastConfPath(string path) + finally { - StreamWriter sw = new(Paths.LastConfig, false, Encoding.UTF8); - try - { - sw.WriteLine(path); - } - finally - { - sw.Close(); - } + sr.Close(); } + } - private void UpdateFanCurveDisplay() + private static void SetLastConfPath(string path) + { + StreamWriter sw = new(Paths.LastConfig, false, Encoding.UTF8); + try { - FanConf cfg = Config.FanConfs[cboFanSel.SelectedIndex]; - - cboProfSel.Items.Clear(); - foreach (FanCurveConf curve in cfg.FanCurveConfs) - { - cboProfSel.Items.Add(curve.Name); - } - - if (numUpTs is null || numDownTs is null || numFanSpds is null || tbFanSpds is null || - numUpTs.Length != cfg.UpThresholdRegs.Length || - numDownTs.Length != cfg.DownThresholdRegs.Length || - numFanSpds.Length != cfg.FanCurveRegs.Length || - tbFanSpds.Length != cfg.FanCurveRegs.Length) - { - float scale = CurrentAutoScaleDimensions.Height / 72; + sw.WriteLine(path); + } + finally + { + sw.Close(); + } + } - tblCurve.Controls.Clear(); - numUpTs = new NumericUpDown[cfg.UpThresholdRegs.Length]; - numDownTs = new NumericUpDown[cfg.DownThresholdRegs.Length]; - numFanSpds = new NumericUpDown[cfg.FanCurveRegs.Length]; - tbFanSpds = new TrackBar[cfg.FanCurveRegs.Length]; + private void UpdateFanCurveDisplay() + { + FanConf cfg = Config.FanConfs[cboFanSel.SelectedIndex]; - tblCurve.ColumnStyles.Clear(); - tblCurve.ColumnCount = numFanSpds.Length + 2; + cboProfSel.Items.Clear(); + foreach (FanCurveConf curve in cfg.FanCurveConfs) + { + cboProfSel.Items.Add(curve.Name); + } - // labels on left side - tblCurve.ColumnStyles.Add(new ColumnStyle()); - tblCurve.Controls.Add(FanCurveLabel("Speed (%)", scale, ContentAlignment.MiddleRight), 0, 0); - tblCurve.Controls.Add(FanCurveLabel("Up (°C)", scale, ContentAlignment.MiddleRight), 0, 2); - tblCurve.Controls.Add(FanCurveLabel("Down (°C)", scale, ContentAlignment.MiddleRight), 0, 3); + if (numUpTs is null || numDownTs is null || numFanSpds is null || tbFanSpds is null || + numUpTs.Length != cfg.UpThresholdRegs.Length || + numDownTs.Length != cfg.DownThresholdRegs.Length || + numFanSpds.Length != cfg.FanCurveRegs.Length || + tbFanSpds.Length != cfg.FanCurveRegs.Length) + { + float scale = CurrentAutoScaleDimensions.Height / 72; - for (int i = 0; i < numFanSpds.Length; i++) - { - tblCurve.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F / numFanSpds.Length)); - numFanSpds[i] = FanCurveNUD(i, scale); - ttMain.SetToolTip(numFanSpds[i], Strings.GetString("ttFanSpd")); - numFanSpds[i].ValueChanged += numFanSpd_Changed; - tblCurve.Controls.Add(numFanSpds[i], i + 1, 0); + tblCurve.Controls.Clear(); + numUpTs = new NumericUpDown[cfg.UpThresholdRegs.Length]; + numDownTs = new NumericUpDown[cfg.DownThresholdRegs.Length]; + numFanSpds = new NumericUpDown[cfg.FanCurveRegs.Length]; + tbFanSpds = new TrackBar[cfg.FanCurveRegs.Length]; - tbFanSpds[i] = new TrackBar() - { - Dock = DockStyle.Fill, - LargeChange = 10, - Margin = new Padding((int)(10 * scale), 0, (int)(10 * scale), 0), - Orientation = Orientation.Vertical, - Tag = i, - TickFrequency = 5, - TickStyle = TickStyle.Both, - }; - ttMain.SetToolTip(tbFanSpds[i], Strings.GetString("ttFanSpd")); - tbFanSpds[i].ValueChanged += tbFanSpd_Scroll; - tblCurve.Controls.Add(tbFanSpds[i], i + 1, 1); - - if (i != 0) - { - numUpTs[i - 1] = FanCurveNUD(i - 1, scale); - ttMain.SetToolTip(numUpTs[i - 1], Strings.GetString("ttUpT")); - numUpTs[i - 1].ValueChanged += numUpT_Changed; - tblCurve.Controls.Add(numUpTs[i - 1], i + 1, 2); - } - else - { - tblCurve.Controls.Add(FanCurveLabel("Default", scale), i + 1, 2); - } + tblCurve.ColumnStyles.Clear(); + tblCurve.ColumnCount = numFanSpds.Length + 2; - if (i != numFanSpds.Length - 1) - { - numDownTs[i] = FanCurveNUD(i, scale); - ttMain.SetToolTip(numDownTs[i], Strings.GetString("ttDownT")); - numDownTs[i].ValueChanged += numDownT_Changed; - tblCurve.Controls.Add(numDownTs[i], i + 1, 3); - } - else - { - tblCurve.Controls.Add(FanCurveLabel("Max", scale), i + 1, 3); - } - } - } + // labels on left side + tblCurve.ColumnStyles.Add(new ColumnStyle()); + tblCurve.Controls.Add(FanCurveLabel("Speed (%)", scale, ContentAlignment.MiddleRight), 0, 0); + tblCurve.Controls.Add(FanCurveLabel("Up (°C)", scale, ContentAlignment.MiddleRight), 0, 2); + tblCurve.Controls.Add(FanCurveLabel("Down (°C)", scale, ContentAlignment.MiddleRight), 0, 3); for (int i = 0; i < numFanSpds.Length; i++) { - if (cfg.FanCurveRegs.Length >= i) + tblCurve.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F / numFanSpds.Length)); + numFanSpds[i] = FanCurveNUD(i, scale); + ttMain.SetToolTip(numFanSpds[i], Strings.GetString("ttFanSpd")); + numFanSpds[i].ValueChanged += numFanSpd_Changed; + tblCurve.Controls.Add(numFanSpds[i], i + 1, 0); + + tbFanSpds[i] = new TrackBar() + { + Dock = DockStyle.Fill, + LargeChange = 10, + Margin = new Padding((int)(10 * scale), 0, (int)(10 * scale), 0), + Orientation = Orientation.Vertical, + Tag = i, + TickFrequency = 5, + TickStyle = TickStyle.Both, + }; + ttMain.SetToolTip(tbFanSpds[i], Strings.GetString("ttFanSpd")); + tbFanSpds[i].ValueChanged += tbFanSpd_Scroll; + tblCurve.Controls.Add(tbFanSpds[i], i + 1, 1); + + if (i != 0) { - numFanSpds[i].Maximum = tbFanSpds[i].Maximum - = Math.Abs(cfg.MaxSpeed - cfg.MinSpeed); + numUpTs[i - 1] = FanCurveNUD(i - 1, scale); + ttMain.SetToolTip(numUpTs[i - 1], Strings.GetString("ttUpT")); + numUpTs[i - 1].ValueChanged += numUpT_Changed; + tblCurve.Controls.Add(numUpTs[i - 1], i + 1, 2); } else { - numFanSpds[i].Enabled = tbFanSpds[i].Enabled = false; + tblCurve.Controls.Add(FanCurveLabel("Default", scale), i + 1, 2); } - } - cboProfSel.Enabled = true; - cboProfSel.SelectedIndex = cfg.CurveSel; - - if (tsiECMon.Checked) - { - tmrPoll.Stop(); - PollEC(); - tmrPoll.Start(); + if (i != numFanSpds.Length - 1) + { + numDownTs[i] = FanCurveNUD(i, scale); + ttMain.SetToolTip(numDownTs[i], Strings.GetString("ttDownT")); + numDownTs[i].ValueChanged += numDownT_Changed; + tblCurve.Controls.Add(numDownTs[i], i + 1, 3); + } + else + { + tblCurve.Controls.Add(FanCurveLabel("Max", scale), i + 1, 3); + } } } - private static Label FanCurveLabel(string text, float scale, ContentAlignment textAlign = ContentAlignment.MiddleCenter) + for (int i = 0; i < numFanSpds.Length; i++) { - return new Label + if (cfg.FanCurveRegs.Length >= i) + { + numFanSpds[i].Maximum = tbFanSpds[i].Maximum + = Math.Abs(cfg.MaxSpeed - cfg.MinSpeed); + } + else { - AutoSize = true, - Dock = DockStyle.Fill, - Margin = new Padding((int)(2 * scale)), - Padding = new Padding(0, 0, 0, (int)(3 * scale)), - Text = text, - TextAlign = textAlign, - }; + numFanSpds[i].Enabled = tbFanSpds[i].Enabled = false; + } } - private void tsiLogNone_Click(object sender, EventArgs e) - { - tsiLogNone.Checked = true; - tsiLogDebug.Checked = false; - tsiLogInfo.Checked = false; - tsiLogWarn.Checked = false; - tsiLogError.Checked = false; - tsiLogFatal.Checked = false; - } + cboProfSel.Enabled = true; + cboProfSel.SelectedIndex = cfg.CurveSel; - private void tsiLogDebug_Click(object sender, EventArgs e) + if (tsiECMon.Checked) { - tsiLogNone.Checked = false; - tsiLogDebug.Checked = true; - tsiLogInfo.Checked = false; - tsiLogWarn.Checked = false; - tsiLogError.Checked = false; - tsiLogFatal.Checked = false; + tmrPoll.Stop(); + PollEC(); + tmrPoll.Start(); } + } - private void tsiLogInfo_Click(object sender, EventArgs e) - { - tsiLogNone.Checked = false; - tsiLogDebug.Checked = false; - tsiLogInfo.Checked = true; - tsiLogWarn.Checked = false; - tsiLogError.Checked = false; - tsiLogFatal.Checked = false; - } + private static Label FanCurveLabel(string text, float scale, ContentAlignment textAlign = ContentAlignment.MiddleCenter) + { + return new Label + { + AutoSize = true, + Dock = DockStyle.Fill, + Margin = new Padding((int)(2 * scale)), + Padding = new Padding(0, 0, 0, (int)(3 * scale)), + Text = text, + TextAlign = textAlign, + }; + } - private void tsiLogWarn_Click(object sender, EventArgs e) - { - tsiLogNone.Checked = false; - tsiLogDebug.Checked = false; - tsiLogInfo.Checked = false; - tsiLogWarn.Checked = true; - tsiLogError.Checked = false; - tsiLogFatal.Checked = false; - } + private void tsiLogNone_Click(object sender, EventArgs e) + { + tsiLogNone.Checked = true; + tsiLogDebug.Checked = false; + tsiLogInfo.Checked = false; + tsiLogWarn.Checked = false; + tsiLogError.Checked = false; + tsiLogFatal.Checked = false; + } - private void tsiLogError_Click(object sender, EventArgs e) - { - tsiLogNone.Checked = false; - tsiLogDebug.Checked = false; - tsiLogInfo.Checked = false; - tsiLogWarn.Checked = false; - tsiLogError.Checked = true; - tsiLogFatal.Checked = false; - } + private void tsiLogDebug_Click(object sender, EventArgs e) + { + tsiLogNone.Checked = false; + tsiLogDebug.Checked = true; + tsiLogInfo.Checked = false; + tsiLogWarn.Checked = false; + tsiLogError.Checked = false; + tsiLogFatal.Checked = false; + } - private void tsiLogFatal_Click(object sender, EventArgs e) - { - tsiLogNone.Checked = false; - tsiLogDebug.Checked = false; - tsiLogInfo.Checked = false; - tsiLogWarn.Checked = false; - tsiLogError.Checked = false; - tsiLogFatal.Checked = true; - } + private void tsiLogInfo_Click(object sender, EventArgs e) + { + tsiLogNone.Checked = false; + tsiLogDebug.Checked = false; + tsiLogInfo.Checked = true; + tsiLogWarn.Checked = false; + tsiLogError.Checked = false; + tsiLogFatal.Checked = false; + } + + private void tsiLogWarn_Click(object sender, EventArgs e) + { + tsiLogNone.Checked = false; + tsiLogDebug.Checked = false; + tsiLogInfo.Checked = false; + tsiLogWarn.Checked = true; + tsiLogError.Checked = false; + tsiLogFatal.Checked = false; + } - private void tsiSwitchAll_Click(object sender, EventArgs e) + private void tsiLogError_Click(object sender, EventArgs e) + { + tsiLogNone.Checked = false; + tsiLogDebug.Checked = false; + tsiLogInfo.Checked = false; + tsiLogWarn.Checked = false; + tsiLogError.Checked = true; + tsiLogFatal.Checked = false; + } + + private void tsiLogFatal_Click(object sender, EventArgs e) + { + tsiLogNone.Checked = false; + tsiLogDebug.Checked = false; + tsiLogInfo.Checked = false; + tsiLogWarn.Checked = false; + tsiLogError.Checked = false; + tsiLogFatal.Checked = true; + } + + private void tsiSwitchAll_Click(object sender, EventArgs e) + { + if (!tsiSwitchAll.Checked) { - if (!tsiSwitchAll.Checked) + if (FansHaveSameProfileCount()) { - if (FansHaveSameProfileCount()) - { - tsiSwitchAll.Checked = true; - } - else - { - Utils.ShowError("All fans must have the same number of fan profiles."); - } + tsiSwitchAll.Checked = true; } else { - tsiSwitchAll.Checked = false; + Utils.ShowError("All fans must have the same number of fan profiles."); } - } - - private static NumericUpDown FanCurveNUD(int tag, float scale) + else { - return new NumericUpDown() - { - Dock = DockStyle.Fill, - Height = (int)(23 * scale), - Margin = new Padding((int)(2 * scale)), - Tag = tag, - }; + tsiSwitchAll.Checked = false; } - private void DisableAll() - { - btnProfAdd.Enabled = false; - btnProfDel.Enabled = false; - btnRevert.Enabled = false; - btnApply.Enabled = false; - cboFanSel.Enabled = false; - cboProfSel.Enabled = false; - cboPerfMode.Enabled = false; - chkFullBlast.Enabled = false; - chkWinFnSwap.Enabled = false; - chkChgLim.Enabled = false; - numChgLim.Enabled = false; - lblKeyLightLow.Enabled = false; - lblKeyLightHigh.Enabled = false; - tbKeyLight.Enabled = false; - - tsiSaveConf.Enabled = false; - tsiApply.Enabled = false; - tsiRevert.Enabled = false; - tsiProfAdd.Enabled = false; - tsiProfEdit.Enabled = false; - tsiProfDel.Enabled = false; - tsiECtoConf.Enabled = false; - tsiECMon.Enabled = false; - - if (tbFanSpds is not null) - { - for (int i = 0; i < tbFanSpds.Length; i++) + } + + private static NumericUpDown FanCurveNUD(int tag, float scale) + { + return new NumericUpDown() + { + Dock = DockStyle.Fill, + Height = (int)(23 * scale), + Margin = new Padding((int)(2 * scale)), + Tag = tag, + }; + } + + private void DisableAll() + { + btnProfAdd.Enabled = false; + btnProfDel.Enabled = false; + btnRevert.Enabled = false; + btnApply.Enabled = false; + cboFanSel.Enabled = false; + cboProfSel.Enabled = false; + cboPerfMode.Enabled = false; + chkFullBlast.Enabled = false; + chkWinFnSwap.Enabled = false; + chkChgLim.Enabled = false; + numChgLim.Enabled = false; + lblKeyLightLow.Enabled = false; + lblKeyLightHigh.Enabled = false; + tbKeyLight.Enabled = false; + + tsiSaveConf.Enabled = false; + tsiApply.Enabled = false; + tsiRevert.Enabled = false; + tsiProfAdd.Enabled = false; + tsiProfEdit.Enabled = false; + tsiProfDel.Enabled = false; + tsiECtoConf.Enabled = false; + tsiECMon.Enabled = false; + + if (tbFanSpds is not null) + { + for (int i = 0; i < tbFanSpds.Length; i++) + { + tbFanSpds[i].Enabled = false; + numFanSpds[i].Enabled = false; + if (i != 0) { - tbFanSpds[i].Enabled = false; - numFanSpds[i].Enabled = false; - if (i != 0) - { - numUpTs[i - 1].Enabled = false; - numDownTs[i - 1].Enabled = false; - } + numUpTs[i - 1].Enabled = false; + numDownTs[i - 1].Enabled = false; } } } + } - private bool FansHaveSameProfileCount() + private bool FansHaveSameProfileCount() + { + for (int i = 0; i < Config.FanConfs.Length - 1; i++) { - for (int i = 0; i < Config.FanConfs.Length - 1; i++) - { - FanConf[] fanCfgs = Config.FanConfs; + FanConf[] fanCfgs = Config.FanConfs; - if (fanCfgs[i].FanCurveConfs.Length != fanCfgs[i + 1].FanCurveConfs.Length) - { - return false; - } + if (fanCfgs[i].FanCurveConfs.Length != fanCfgs[i + 1].FanCurveConfs.Length) + { + return false; } - return true; } + return true; + } - private void UpdateStatus(StatusCode status, int data = 0) + private void UpdateStatus(StatusCode status, int data = 0) + { + lblStatus.Invoke(() => { - lblStatus.Invoke(() => + if (AppStatus.Code == status) { - if (AppStatus.Code == status) - { - AppStatus.RepeatCount++; - } - else - { - AppStatus.Code = status; - AppStatus.RepeatCount = 0; - } + AppStatus.RepeatCount++; + } + else + { + AppStatus.Code = status; + AppStatus.RepeatCount = 0; + } - // set status text - bool persist = false; - switch (AppStatus.Code) - { - case StatusCode.ServiceCommandFail: - persist = true; - lblStatus.Text = Strings.GetString("statSvcErr", (Command)data); - break; - case StatusCode.ServiceResponseEmpty: - lblStatus.Text = Strings.GetString("statResponseEmpty"); - break; - case StatusCode.ServiceTimeout: - lblStatus.Text = Strings.GetString("statSvcTimeout"); - break; - case StatusCode.NoConfig: - persist = true; - lblStatus.Text = Strings.GetString("statNoConf"); - break; - case StatusCode.ConfLoading: - lblStatus.Text = Strings.GetString("statConfLoading"); - break; - case StatusCode.ConfApplySuccess: - lblStatus.Text = Strings.GetString("statConfApplied"); - break; - case StatusCode.FullBlastToggleSuccess: - lblStatus.Text = Strings.GetString("statFBToggled"); - break; - default: - persist = true; - AppStatus.RepeatCount = 0; - lblStatus.Text = "Ready"; - break; - } + // set status text + bool persist = false; + switch (AppStatus.Code) + { + case StatusCode.ServiceCommandFail: + persist = true; + lblStatus.Text = Strings.GetString("statSvcErr", (Command)data); + break; + case StatusCode.ServiceResponseEmpty: + lblStatus.Text = Strings.GetString("statResponseEmpty"); + break; + case StatusCode.ServiceTimeout: + lblStatus.Text = Strings.GetString("statSvcTimeout"); + break; + case StatusCode.NoConfig: + persist = true; + lblStatus.Text = Strings.GetString("statNoConf"); + break; + case StatusCode.ConfLoading: + lblStatus.Text = Strings.GetString("statConfLoading"); + break; + case StatusCode.ConfApplySuccess: + lblStatus.Text = Strings.GetString("statConfApplied"); + break; + case StatusCode.FullBlastToggleSuccess: + lblStatus.Text = Strings.GetString("statFBToggled"); + break; + default: + persist = true; + AppStatus.RepeatCount = 0; + lblStatus.Text = "Ready"; + break; + } - if (AppStatus.RepeatCount > 0) - { - lblStatus.Text += $" (x{AppStatus.RepeatCount + 1})"; - } + if (AppStatus.RepeatCount > 0) + { + lblStatus.Text += $" (x{AppStatus.RepeatCount + 1})"; + } - tmrStatusReset.Stop(); - if (!persist) - { - tmrStatusReset.Start(); - } - }); - } + tmrStatusReset.Stop(); + if (!persist) + { + tmrStatusReset.Start(); + } + }); + } - private void SendServiceMessage(ServiceCommand command) - { - IPCClient.PushMessage(command); - tmrSvcTimeout.Start(); - } - #endregion // Private methods + private void SendServiceMessage(ServiceCommand command) + { + IPCClient.PushMessage(command); + tmrSvcTimeout.Start(); } + #endregion // Private methods } diff --git a/YAMDCC.ConfigEditor/Program.cs b/YAMDCC.ConfigEditor/Program.cs index bc08147..69f8e81 100644 --- a/YAMDCC.ConfigEditor/Program.cs +++ b/YAMDCC.ConfigEditor/Program.cs @@ -25,207 +25,206 @@ using YAMDCC.Common; using YAMDCC.Common.Dialogs; -namespace YAMDCC.ConfigEditor -{ - internal static class Program - { - private const int SW_RESTORE = 9; +namespace YAMDCC.ConfigEditor; - /// - /// The main entry point for the application. - /// - [STAThread] - private static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); +internal static class Program +{ + private const int SW_RESTORE = 9; - #region Global exception handlers - Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); - Application.ThreadException += static (sender, e) => - new CrashDialog(e.Exception).ShowDialog(); + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); - AppDomain.CurrentDomain.UnhandledException += static (sender, e) => - { - if (e.ExceptionObject is Exception ex) - { - new CrashDialog(ex).ShowDialog(); - } - }; - #endregion + #region Global exception handlers + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + Application.ThreadException += static (sender, e) => + new CrashDialog(e.Exception).ShowDialog(); - if (!Utils.IsAdmin()) + AppDomain.CurrentDomain.UnhandledException += static (sender, e) => + { + if (e.ExceptionObject is Exception ex) { - Utils.ShowError(Strings.GetString("dlgNoAdmin")); - return; + new CrashDialog(ex).ShowDialog(); } + }; + #endregion - // multi-instance detection - // NOTE: GUID is used to prevent conflicts with potential - // identically named but different program - // based on: https://stackoverflow.com/a/184143 - using (Mutex mutex = new(true, "{10572c4f-894e-4837-b31c-356d70c44e19}", out bool createdNew)) + if (!Utils.IsAdmin()) + { + Utils.ShowError(Strings.GetString("dlgNoAdmin")); + return; + } + + // multi-instance detection + // NOTE: GUID is used to prevent conflicts with potential + // identically named but different program + // based on: https://stackoverflow.com/a/184143 + using (Mutex mutex = new(true, "{10572c4f-894e-4837-b31c-356d70c44e19}", out bool createdNew)) + { + // this instance is the first to open; proceed as normal: + if (createdNew) { - // this instance is the first to open; proceed as normal: - if (createdNew) - { - // Make sure the application data directory structure is set up - // because apparently windows services don't know how to create - // directories: - Directory.CreateDirectory(Paths.Logs); + // Make sure the application data directory structure is set up + // because apparently windows services don't know how to create + // directories: + Directory.CreateDirectory(Paths.Logs); - if (!Utils.ServiceExists("yamdccsvc")) + if (!Utils.ServiceExists("yamdccsvc")) + { + if (File.Exists("yamdccsvc.exe")) { - if (File.Exists("yamdccsvc.exe")) + if (MessageBox.Show( + Strings.GetString("dlgSvcNotInstalled"), + "Service not installed", + MessageBoxButtons.YesNo, + MessageBoxIcon.Information) == DialogResult.Yes) { - if (MessageBox.Show( - Strings.GetString("dlgSvcNotInstalled"), - "Service not installed", - MessageBoxButtons.YesNo, - MessageBoxIcon.Information) == DialogResult.Yes) + ProgressDialog dlg = new(Strings.GetString("dlgSvcInstalling"), (e) => { - ProgressDialog dlg = new(Strings.GetString("dlgSvcInstalling"), (e) => + e.Result = false; + if (Utils.InstallService("yamdccsvc")) { - e.Result = false; - if (Utils.InstallService("yamdccsvc")) + if (Utils.StartService("yamdccsvc")) { - if (Utils.StartService("yamdccsvc")) - { - e.Result = true; - } - else - { - Utils.ShowError(Strings.GetString("dlgSvcStartCrash")); - } + e.Result = true; } else { - Utils.ShowError(Strings.GetString("dlgSvcInstallFail")); + Utils.ShowError(Strings.GetString("dlgSvcStartCrash")); } - }); - dlg.ShowDialog(); - - if (dlg.Result is bool b && b) + } + else { - // Start the program when the service finishes starting: - Start(); + Utils.ShowError(Strings.GetString("dlgSvcInstallFail")); } + }); + dlg.ShowDialog(); + + if (dlg.Result is bool b && b) + { + // Start the program when the service finishes starting: + Start(); } - return; - } - else - { - Utils.ShowError(Strings.GetString("dlgSvcNotFound")); - return; } + return; } + else + { + Utils.ShowError(Strings.GetString("dlgSvcNotFound")); + return; + } + } - // Check if the service is stopped: - ServiceController yamdccSvc = new("yamdccsvc"); - try + // Check if the service is stopped: + ServiceController yamdccSvc = new("yamdccsvc"); + try + { + ServiceControllerStatus status = yamdccSvc.Status; + if (status == ServiceControllerStatus.Stopped) { - ServiceControllerStatus status = yamdccSvc.Status; - if (status == ServiceControllerStatus.Stopped) + if (MessageBox.Show( + Strings.GetString("dlgSvcStopped"), + "Service not running", MessageBoxButtons.YesNo, + MessageBoxIcon.Information) == DialogResult.Yes) { - if (MessageBox.Show( - Strings.GetString("dlgSvcStopped"), - "Service not running", MessageBoxButtons.YesNo, - MessageBoxIcon.Information) == DialogResult.Yes) + ProgressDialog dlg = new(Strings.GetString("dlgSvcStarting"), (e) => { - ProgressDialog dlg = new(Strings.GetString("dlgSvcStarting"), (e) => + if (Utils.StartService("yamdccsvc")) { - if (Utils.StartService("yamdccsvc")) - { - e.Result = false; - } - else - { - Utils.ShowError(Strings.GetString("dlgSvcStartCrash")); - e.Result = true; - } - }); - dlg.ShowDialog(); - - if (dlg.Result is bool b && b) + e.Result = false; + } + else { - return; + Utils.ShowError(Strings.GetString("dlgSvcStartCrash")); + e.Result = true; } - } - else + }); + dlg.ShowDialog(); + + if (dlg.Result is bool b && b) { return; } } + else + { + return; + } } - finally - { - yamdccSvc?.Close(); - } - - // Start the program when the service finishes starting: - Start(); } - else + finally + { + yamdccSvc?.Close(); + } + + // Start the program when the service finishes starting: + Start(); + } + else + { + // YAMDCC is already running, focus + // (and restore, if minimised) its window: + Process current = Process.GetCurrentProcess(); + foreach (Process process in Process.GetProcessesByName(current.ProcessName)) { - // YAMDCC is already running, focus - // (and restore, if minimised) its window: - Process current = Process.GetCurrentProcess(); - foreach (Process process in Process.GetProcessesByName(current.ProcessName)) + if (process.Id != current.Id) { - if (process.Id != current.Id) + if (process.MainWindowHandle != IntPtr.Zero) { - if (process.MainWindowHandle != IntPtr.Zero) - { - ShowWindow(process.MainWindowHandle, SW_RESTORE); - SetForegroundWindow(process.MainWindowHandle); - } - break; + ShowWindow(process.MainWindowHandle, SW_RESTORE); + SetForegroundWindow(process.MainWindowHandle); } + break; } } } } + } - private static void Start() + private static void Start() + { + int rebootFlag = -1; + try { - int rebootFlag = -1; - try + StreamReader sr = new(Paths.ECToConfPending); + if (int.TryParse(sr.ReadToEnd(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int value)) { - StreamReader sr = new(Paths.ECToConfPending); - if (int.TryParse(sr.ReadToEnd(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int value)) - { - rebootFlag = value; - } - sr.Close(); + rebootFlag = value; } - catch (FileNotFoundException) { } - catch (DirectoryNotFoundException) { } + sr.Close(); + } + catch (FileNotFoundException) { } + catch (DirectoryNotFoundException) { } - if (rebootFlag == 1) + if (rebootFlag == 1) + { + if (MessageBox.Show(Strings.GetString("dlgECtoConfReboot"), + "Reboot pending", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, + MessageBoxDefaultButton.Button2) == DialogResult.Yes) { - if (MessageBox.Show(Strings.GetString("dlgECtoConfReboot"), - "Reboot pending", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, - MessageBoxDefaultButton.Button2) == DialogResult.Yes) + try { - try - { - File.Delete(Paths.ECToConfPending); - } - catch (DirectoryNotFoundException) { } - } - else - { - return; + File.Delete(Paths.ECToConfPending); } + catch (DirectoryNotFoundException) { } + } + else + { + return; } - - Application.Run(new MainWindow()); } - [DllImport("user32.dll")] - private static extern bool SetForegroundWindow(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + Application.Run(new MainWindow()); } + + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); } diff --git a/YAMDCC.ConfigEditor/Status.cs b/YAMDCC.ConfigEditor/Status.cs index eb30de3..732431c 100644 --- a/YAMDCC.ConfigEditor/Status.cs +++ b/YAMDCC.ConfigEditor/Status.cs @@ -14,29 +14,28 @@ // You should have received a copy of the GNU General Public License along with // YAMDCC. If not, see . -namespace YAMDCC.ConfigEditor -{ - internal sealed class Status - { - internal StatusCode Code; - internal int RepeatCount; +namespace YAMDCC.ConfigEditor; - internal Status() - { - Code = StatusCode.None; - RepeatCount = 0; - } - } +internal sealed class Status +{ + internal StatusCode Code; + internal int RepeatCount; - internal enum StatusCode + internal Status() { - None = 0, - ServiceCommandFail, - ServiceResponseEmpty, - ServiceTimeout, - ConfLoading, - NoConfig, - ConfApplySuccess, - FullBlastToggleSuccess, + Code = StatusCode.None; + RepeatCount = 0; } } + +internal enum StatusCode +{ + None = 0, + ServiceCommandFail, + ServiceResponseEmpty, + ServiceTimeout, + ConfLoading, + NoConfig, + ConfApplySuccess, + FullBlastToggleSuccess, +} diff --git a/YAMDCC.ConfigEditor/Strings.cs b/YAMDCC.ConfigEditor/Strings.cs index 576b264..a8b7ea4 100644 --- a/YAMDCC.ConfigEditor/Strings.cs +++ b/YAMDCC.ConfigEditor/Strings.cs @@ -17,58 +17,57 @@ using System.Globalization; using System.Resources; -namespace YAMDCC.ConfigEditor +namespace YAMDCC.ConfigEditor; + +/// +/// A resource class for retrieving strings. +/// +internal static class Strings { + private static ResourceManager resMan; + /// - /// A resource class for retrieving strings. + /// Gets a string from the underlying resource file. /// - internal static class Strings + /// + /// This function internally calls + /// to retrieve the string. + /// + /// + /// The name of the string to find. + /// + /// + /// The value of the specified string name, if found. + /// null if the string couldn't be found. + /// + public static string GetString(string name) { - private static ResourceManager resMan; - - /// - /// Gets a string from the underlying resource file. - /// - /// - /// This function internally calls - /// to retrieve the string. - /// - /// - /// The name of the string to find. - /// - /// - /// The value of the specified string name, if found. - /// null if the string couldn't be found. - /// - public static string GetString(string name) - { - resMan ??= new ResourceManager(typeof(Strings)); - return resMan.GetString(name, CultureInfo.InvariantCulture); - } + resMan ??= new ResourceManager(typeof(Strings)); + return resMan.GetString(name, CultureInfo.InvariantCulture); + } - /// - /// Gets a string from the underlying resource file, and - /// replaces format objects with their string representation. - /// - /// - /// The name of the string to find. - /// - /// - /// The object to format the string with. - /// - /// - /// - /// The formatted string corresponding to - /// the specified string name, if found. - /// - /// null if the string couldn't be found. - /// - public static string GetString(string name, object arg0) - { - string temp = GetString(name); - return temp is null - ? null - : string.Format(CultureInfo.InvariantCulture, temp, arg0); - } + /// + /// Gets a string from the underlying resource file, and + /// replaces format objects with their string representation. + /// + /// + /// The name of the string to find. + /// + /// + /// The object to format the string with. + /// + /// + /// + /// The formatted string corresponding to + /// the specified string name, if found. + /// + /// null if the string couldn't be found. + /// + public static string GetString(string name, object arg0) + { + string temp = GetString(name); + return temp is null + ? null + : string.Format(CultureInfo.InvariantCulture, temp, arg0); } } diff --git a/YAMDCC.ECAccess/Driver.cs b/YAMDCC.ECAccess/Driver.cs index db82040..1474285 100644 --- a/YAMDCC.ECAccess/Driver.cs +++ b/YAMDCC.ECAccess/Driver.cs @@ -17,373 +17,371 @@ using System; using System.IO; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using System.Security.AccessControl; using YAMDCC.ECAccess.Win32; -namespace YAMDCC.ECAccess +namespace YAMDCC.ECAccess; + +/// +/// Contains functions to install and manage kernel-level device drivers. +/// +internal sealed class Driver { + private readonly string DeviceName; + private readonly string DriverPath = string.Empty; + private IntPtr hDevice; + + /// + /// Gets whether the driver connection is open. + /// + public bool IsOpen { get; private set; } + + /// + /// Gets whether the driver is installed to the computer. + /// + /// + /// This will be false if the driver has not been + /// installed by this instance of the , + /// even if it is actaully installed to the system. + /// + public bool IsInstalled { get; private set; } + + /// + /// The underlying Win32 Error code generated + /// by the last called method in this class instance. + /// + public int ErrorCode { get; private set; } + /// - /// Contains functions to install and manage kernel-level device drivers. + /// Create an instance of the + /// class with the specified name and driver path, + /// automatically installing the driver to the system. /// - internal sealed class Driver + /// + /// The driver name. This will be used as the device driver service name. + /// + /// + /// The path to the driver file (C:\path\to\driver.sys). + /// + public Driver(string name, string path) { - private readonly string DeviceName; - private readonly string DriverPath = string.Empty; - private IntPtr hDevice; - - /// - /// Gets whether the driver connection is open. - /// - public bool IsOpen { get; private set; } - - /// - /// Gets whether the driver is installed to the computer. - /// - /// - /// This will be false if the driver has not been - /// installed by this instance of the , - /// even if it is actaully installed to the system. - /// - public bool IsInstalled { get; private set; } - - /// - /// The underlying Win32 Error code generated - /// by the last called method in this class instance. - /// - public int ErrorCode { get; private set; } - - /// - /// Create an instance of the - /// class with the specified name and driver path, - /// automatically installing the driver to the system. - /// - /// - /// The driver name. This will be used as the device driver service name. - /// - /// - /// The path to the driver file (C:\path\to\driver.sys). - /// - public Driver(string name, string path) - { - DeviceName = name; - DriverPath = path; - } + DeviceName = name; + DriverPath = path; + } - /// - /// Installs the driver on the local computer. - /// - /// - /// true if the driver was installed - /// successfully, otherwise false. - /// - public bool Install() - { - ErrorCode = 0; + /// + /// Installs the driver on the local computer. + /// + /// + /// true if the driver was installed + /// successfully, otherwise false. + /// + public bool Install() + { + ErrorCode = 0; - if (string.IsNullOrEmpty(DriverPath)) - { - throw new ArgumentException( - "The driver path is set to a null or empty string.", DriverPath); - } + if (string.IsNullOrEmpty(DriverPath)) + { + throw new ArgumentException( + "The driver path is set to a null or empty string.", DriverPath); + } - // Make sure the file we're trying to install actually exists: - string fullPath = Path.GetFullPath(DriverPath); + // Make sure the file we're trying to install actually exists: + string fullPath = Path.GetFullPath(DriverPath); - if (!File.Exists(fullPath)) - { - throw new FileNotFoundException($"{fullPath} was not found.", fullPath); - } + if (!File.Exists(fullPath)) + { + throw new FileNotFoundException($"{fullPath} was not found.", fullPath); + } - // Try to open the Service Control Manager: - IntPtr hSCM = AdvApi32.OpenSCManager(null, null, AdvApi32.SCMAccess.All); - if (hSCM == IntPtr.Zero) - { - // the SCM connection wasn't opened - // successfully, return false: - ErrorCode = Marshal.GetLastWin32Error(); - return false; - } + // Try to open the Service Control Manager: + IntPtr hSCM = AdvApi32.OpenSCManager(null, null, AdvApi32.SCMAccess.All); + if (hSCM == IntPtr.Zero) + { + // the SCM connection wasn't opened + // successfully, return false: + ErrorCode = Marshal.GetLastWin32Error(); + return false; + } - // Try to create the service: - IntPtr hSvc = AdvApi32.CreateService( - hSCM, DeviceName, DeviceName, - AdvApi32.ServiceAccess.All, - AdvApi32.ServiceType.KernelDriver, - AdvApi32.ServiceStartType.DemandStart, - AdvApi32.ServiceError.Normal, - DriverPath, null, null, null, null, null); + // Try to create the service: + IntPtr hSvc = AdvApi32.CreateService( + hSCM, DeviceName, DeviceName, + AdvApi32.ServiceAccess.All, + AdvApi32.ServiceType.KernelDriver, + AdvApi32.ServiceStartType.DemandStart, + AdvApi32.ServiceError.Normal, + DriverPath, null, null, null, null, null); - if (hSvc == IntPtr.Zero) + if (hSvc == IntPtr.Zero) + { + ErrorCode = Marshal.GetLastWin32Error(); + if (ErrorCode == 1073) // ERROR_SERVICE_EXISTS { - ErrorCode = Marshal.GetLastWin32Error(); - if (ErrorCode == 1073) // ERROR_SERVICE_EXISTS - { - hSvc = AdvApi32.OpenService(hSCM, DeviceName, AdvApi32.ServiceAccess.All); - if (hSvc == IntPtr.Zero) - { - ErrorCode = Marshal.GetLastWin32Error(); - AdvApi32.CloseServiceHandle(hSCM); - return false; - } - } - else + hSvc = AdvApi32.OpenService(hSCM, DeviceName, AdvApi32.ServiceAccess.All); + if (hSvc == IntPtr.Zero) { ErrorCode = Marshal.GetLastWin32Error(); AdvApi32.CloseServiceHandle(hSCM); return false; } } - IsInstalled = true; - - // Try to start the service: - if (!AdvApi32.StartService(hSvc, 0, IntPtr.Zero)) + else { - int error = Marshal.GetLastWin32Error(); - if (error != 1056) // ERROR_SERVICE_ALREADY_RUNNING - { - ErrorCode = error; - AdvApi32.CloseServiceHandle(hSvc); - AdvApi32.CloseServiceHandle(hSCM); - return false; - } + ErrorCode = Marshal.GetLastWin32Error(); + AdvApi32.CloseServiceHandle(hSCM); + return false; } - - // Perform some cleanup: - AdvApi32.CloseServiceHandle(hSvc); - AdvApi32.CloseServiceHandle(hSCM); - - // Security fix for WinRing0 access from unprivileged processes. - // This fix is present in the WinRing0 driver itself (WinRing0.sys) - // in an updated fork (https://github.com/GermanAizek/WinRing0), but no - // public production-signed build of the driver exists with the fixes. - // This fix was "borrowed" from OpenHardwareMonitor: - // https://github.com/openhardwaremonitor/openhardwaremonitor/ - FileInfo fi = new($"\\\\.\\{DeviceName}"); - FileSecurity security = fi.GetAccessControl(); - security.SetSecurityDescriptorSddlForm("O:BAG:SYD:(A;;FA;;;SY)(A;;FA;;;BA)"); - fi.SetAccessControl(security); - - return true; } + IsInstalled = true; - /// - /// Uninstalls the driver from the local computer. - /// - /// - /// true if the driver was uninstalled - /// successfully, otherwise false. - /// - public bool Uninstall() + // Try to start the service: + if (!AdvApi32.StartService(hSvc, 0, IntPtr.Zero)) { - ErrorCode = 0; - - // Close the driver file handle (if it's open) - Close(); - - IntPtr hSCM = AdvApi32.OpenSCManager(null, null, AdvApi32.SCMAccess.All); - if (hSCM == IntPtr.Zero) + int error = Marshal.GetLastWin32Error(); + if (error != 1056) // ERROR_SERVICE_ALREADY_RUNNING { - // the SCM connection wasn't opened - // successfully, return false: - ErrorCode = Marshal.GetLastWin32Error(); + ErrorCode = error; + AdvApi32.CloseServiceHandle(hSvc); + AdvApi32.CloseServiceHandle(hSCM); return false; } + } - // Try to open the service: - IntPtr hSvc = AdvApi32.OpenService(hSCM, DeviceName, AdvApi32.ServiceAccess.All); - if (hSvc == IntPtr.Zero) - { - // Ignore ERROR_SERVICE_DOES_NOT_EXIST: - int error = Marshal.GetLastWin32Error(); - bool success = error == 1060; - if (success) - { - IsInstalled = false; - } - else - { - ErrorCode = error; - } + // Perform some cleanup: + AdvApi32.CloseServiceHandle(hSvc); + AdvApi32.CloseServiceHandle(hSCM); + + // Security fix for WinRing0 access from unprivileged processes. + // This fix is present in the WinRing0 driver itself (WinRing0.sys) + // in an updated fork (https://github.com/GermanAizek/WinRing0), but no + // public production-signed build of the driver exists with the fixes. + // This fix was "borrowed" from OpenHardwareMonitor: + // https://github.com/openhardwaremonitor/openhardwaremonitor/ + FileInfo fi = new($"\\\\.\\{DeviceName}"); + FileSecurity security = fi.GetAccessControl(); + security.SetSecurityDescriptorSddlForm("O:BAG:SYD:(A;;FA;;;SY)(A;;FA;;;BA)"); + fi.SetAccessControl(security); + + return true; + } - AdvApi32.CloseServiceHandle(hSCM); - return success; - } + /// + /// Uninstalls the driver from the local computer. + /// + /// + /// true if the driver was uninstalled + /// successfully, otherwise false. + /// + public bool Uninstall() + { + ErrorCode = 0; - // Stop and delete the service: - AdvApi32.ControlService(hSvc, AdvApi32.ServiceControlCode.Stop, out _); - AdvApi32.DeleteService(hSvc); - IsInstalled = false; + // Close the driver file handle (if it's open) + Close(); - // Close service handles - AdvApi32.CloseServiceHandle(hSvc); - AdvApi32.CloseServiceHandle(hSCM); - return true; + IntPtr hSCM = AdvApi32.OpenSCManager(null, null, AdvApi32.SCMAccess.All); + if (hSCM == IntPtr.Zero) + { + // the SCM connection wasn't opened + // successfully, return false: + ErrorCode = Marshal.GetLastWin32Error(); + return false; } - /// - /// Opens a connection to the driver. - /// - /// - /// true if the driver connection was - /// opened successfully, otherwise false. - /// - public bool Open() + // Try to open the service: + IntPtr hSvc = AdvApi32.OpenService(hSCM, DeviceName, AdvApi32.ServiceAccess.All); + if (hSvc == IntPtr.Zero) { - if (IsOpen) + // Ignore ERROR_SERVICE_DOES_NOT_EXIST: + int error = Marshal.GetLastWin32Error(); + bool success = error == 1060; + if (success) { - return true; + IsInstalled = false; } - - ErrorCode = 0; - - if (hDevice == IntPtr.Zero) + else { - hDevice = Kernel32.CreateFile( - $"\\\\.\\{DeviceName}", - Kernel32.GenericAccessRights.Read | Kernel32.GenericAccessRights.Write, - FileShare.None, - IntPtr.Zero, - FileMode.Open, - FileAttributes.Normal, - IntPtr.Zero); - - // Apparently CreateFileW() can return -1 instead of 0 for some reason - if (hDevice == IntPtr.Zero || hDevice == new IntPtr(-1)) - { - ErrorCode = Marshal.GetLastWin32Error(); - return false; - } - - IsOpen = true; - return true; + ErrorCode = error; } - return true; + + AdvApi32.CloseServiceHandle(hSCM); + return success; } - /// - /// Closes the connection to the device driver, if open. - /// - public void Close() + // Stop and delete the service: + AdvApi32.ControlService(hSvc, AdvApi32.ServiceControlCode.Stop, out _); + AdvApi32.DeleteService(hSvc); + IsInstalled = false; + + // Close service handles + AdvApi32.CloseServiceHandle(hSvc); + AdvApi32.CloseServiceHandle(hSCM); + return true; + } + + /// + /// Opens a connection to the driver. + /// + /// + /// true if the driver connection was + /// opened successfully, otherwise false. + /// + public bool Open() + { + if (IsOpen) { - if (hDevice != IntPtr.Zero) - { - Kernel32.CloseHandle(hDevice); - hDevice = IntPtr.Zero; - IsOpen = false; - } + return true; } - public unsafe bool IOControl(uint ctlCode, void* inBuffer, uint inBufSize, void* outBuffer, uint outBufSize, out uint bytesReturned) + ErrorCode = 0; + + if (hDevice == IntPtr.Zero) { - if (hDevice == IntPtr.Zero) + hDevice = Kernel32.CreateFile( + $"\\\\.\\{DeviceName}", + Kernel32.GenericAccessRights.Read | Kernel32.GenericAccessRights.Write, + FileShare.None, + IntPtr.Zero, + FileMode.Open, + FileAttributes.Normal, + IntPtr.Zero); + + // Apparently CreateFileW() can return -1 instead of 0 for some reason + if (hDevice == IntPtr.Zero || hDevice == new IntPtr(-1)) { - bytesReturned = 0; + ErrorCode = Marshal.GetLastWin32Error(); return false; } - bool success = Kernel32.DeviceIoControl( - hDevice, ctlCode, - inBuffer, inBufSize, - outBuffer, outBufSize, - out bytesReturned, null); + IsOpen = true; + return true; + } + return true; + } - ErrorCode = success - ? 0 - : Marshal.GetLastWin32Error(); + /// + /// Closes the connection to the device driver, if open. + /// + public void Close() + { + if (hDevice != IntPtr.Zero) + { + Kernel32.CloseHandle(hDevice); + hDevice = IntPtr.Zero; + IsOpen = false; + } + } - return success; + public unsafe bool IOControl(uint ctlCode, void* inBuffer, uint inBufSize, void* outBuffer, uint outBufSize, out uint bytesReturned) + { + if (hDevice == IntPtr.Zero) + { + bytesReturned = 0; + return false; } - public unsafe bool IOControl(uint ctlCode, ref T inBuffer) - where T : unmanaged + bool success = Kernel32.DeviceIoControl( + hDevice, ctlCode, + inBuffer, inBufSize, + outBuffer, outBufSize, + out bytesReturned, null); + + ErrorCode = success + ? 0 + : Marshal.GetLastWin32Error(); + + return success; + } + + public unsafe bool IOControl(uint ctlCode, ref T inBuffer) + where T : unmanaged + { + if (typeof(T).IsValueType) { - if (typeof(T).IsValueType) + fixed (T* pInBuffer = &inBuffer) { - fixed (T* pInBuffer = &inBuffer) - { - return IOControl(ctlCode, - pInBuffer, (uint)Marshal.SizeOf(inBuffer), - null, 0, - out _); - } + return IOControl(ctlCode, + pInBuffer, (uint)Marshal.SizeOf(inBuffer), + null, 0, + out _); } - else + } + else + { + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(inBuffer)); + try { - IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(inBuffer)); - try - { - Marshal.StructureToPtr(inBuffer, ptr, false); + Marshal.StructureToPtr(inBuffer, ptr, false); - return IOControl(ctlCode, - ptr.ToPointer(), (uint)Marshal.SizeOf(inBuffer), - null, 0, - out _); - } - finally - { - Marshal.DestroyStructure(ptr); - Marshal.FreeHGlobal(ptr); - } + return IOControl(ctlCode, + ptr.ToPointer(), (uint)Marshal.SizeOf(inBuffer), + null, 0, + out _); + } + finally + { + Marshal.DestroyStructure(ptr); + Marshal.FreeHGlobal(ptr); } } + } - public unsafe bool IOControl(uint ctlCode, ref TIn inBuffer, out TOut outBuffer) - where TIn : unmanaged - where TOut : unmanaged + public unsafe bool IOControl(uint ctlCode, ref TIn inBuffer, out TOut outBuffer) + where TIn : unmanaged + where TOut : unmanaged + { + fixed (TIn* pInBuffer = &inBuffer) + fixed (TOut* pOutBuffer = &outBuffer) { - fixed (TIn* pInBuffer = &inBuffer) - fixed (TOut* pOutBuffer = &outBuffer) + IntPtr inPtr = typeof(TIn).IsValueType + ? (IntPtr)pInBuffer + : Marshal.AllocHGlobal(Marshal.SizeOf(inBuffer)); + IntPtr outPtr = typeof(TOut).IsValueType + ? (IntPtr)pOutBuffer + : Marshal.AllocHGlobal(Marshal.SizeOf(outBuffer)); + + try { - IntPtr inPtr = typeof(TIn).IsValueType - ? (IntPtr)pInBuffer - : Marshal.AllocHGlobal(Marshal.SizeOf(inBuffer)); - IntPtr outPtr = typeof(TOut).IsValueType - ? (IntPtr)pOutBuffer - : Marshal.AllocHGlobal(Marshal.SizeOf(outBuffer)); - - try + if (!typeof(TIn).IsValueType) { - if (!typeof(TIn).IsValueType) - { - Marshal.StructureToPtr(inBuffer, inPtr, false); - } - - if (IOControl(ctlCode, - inPtr.ToPointer(), (uint)Marshal.SizeOf(inBuffer), - outPtr.ToPointer(), (uint)Marshal.SizeOf(outBuffer), - out _)) - { - if (!typeof(TOut).IsValueType) - { - Marshal.PtrToStructure(outPtr, outBuffer); - } - return true; - } - return false; + Marshal.StructureToPtr(inBuffer, inPtr, false); } - finally + + if (IOControl(ctlCode, + inPtr.ToPointer(), (uint)Marshal.SizeOf(inBuffer), + outPtr.ToPointer(), (uint)Marshal.SizeOf(outBuffer), + out _)) { - if (!typeof(TIn).IsValueType) - { - Marshal.DestroyStructure(inPtr); - Marshal.FreeHGlobal(inPtr); - } if (!typeof(TOut).IsValueType) { - Marshal.DestroyStructure(outPtr); - Marshal.FreeHGlobal(outPtr); + Marshal.PtrToStructure(outPtr, outBuffer); } + return true; + } + return false; + } + finally + { + if (!typeof(TIn).IsValueType) + { + Marshal.DestroyStructure(inPtr); + Marshal.FreeHGlobal(inPtr); + } + if (!typeof(TOut).IsValueType) + { + Marshal.DestroyStructure(outPtr); + Marshal.FreeHGlobal(outPtr); } } } + } - #region Cleanup code - ~Driver() - { - // Close all open file and service handles - Close(); - } - #endregion + #region Cleanup code + ~Driver() + { + // Close all open file and service handles + Close(); } + #endregion } diff --git a/YAMDCC.ECAccess/EC.cs b/YAMDCC.ECAccess/EC.cs index 0f23ea2..c51d0da 100644 --- a/YAMDCC.ECAccess/EC.cs +++ b/YAMDCC.ECAccess/EC.cs @@ -20,446 +20,445 @@ using System.Runtime.InteropServices; using System.Threading; -namespace YAMDCC.ECAccess +namespace YAMDCC.ECAccess; + +/// +/// Methods to access the embedded controller in a computer. +/// +public sealed class EC { - /// - /// Methods to access the embedded controller in a computer. - /// - public sealed class EC + // See ACPI specs ch 12.2 + [Flags] + private enum ECStatus : byte { - // See ACPI specs ch 12.2 - [Flags] - private enum ECStatus : byte - { - None = 0x00, - OutputBufferFull = 0x01, // OBF - InputBufferFull = 0x02, // IBF - Command = 0x08, // CMD - BurstMode = 0x10, // BURST - SCIEventPending = 0x20, // SCI_EVT - SMIEventPending = 0x40 // SMI_EVT - } + None = 0x00, + OutputBufferFull = 0x01, // OBF + InputBufferFull = 0x02, // IBF + Command = 0x08, // CMD + BurstMode = 0x10, // BURST + SCIEventPending = 0x20, // SCI_EVT + SMIEventPending = 0x40 // SMI_EVT + } - // See ACPI specs ch 12.3 - private enum ECCommand : byte - { - Read = 0x80, // RD_EC - Write = 0x81, // WR_EC - BurstEnable = 0x82, // BE_EC - BurstDisable = 0x83, // BD_EC - Query = 0x84 // QR_EC - } + // See ACPI specs ch 12.3 + private enum ECCommand : byte + { + Read = 0x80, // RD_EC + Write = 0x81, // WR_EC + BurstEnable = 0x82, // BE_EC + BurstDisable = 0x83, // BD_EC + Query = 0x84 // QR_EC + } - private const byte PORT_COMMAND = 0x66; //EC_SC - private const byte PORT_DATA = 0x62; //EC_DATA + private const byte PORT_COMMAND = 0x66; //EC_SC + private const byte PORT_DATA = 0x62; //EC_DATA - /// - /// The maximum number of read/write attempts before returning an error. - /// - private const int RW_MAX_RETRIES = 5; + /// + /// The maximum number of read/write attempts before returning an error. + /// + private const int RW_MAX_RETRIES = 5; - /// - /// The maximum time (in ticks) - /// to wait for an EC status. - /// - private static readonly long ECStatusTimeoutTicks = - Stopwatch.Frequency / 100; // 10 ms, should be plenty for EC status waits + /// + /// The maximum time (in ticks) + /// to wait for an EC status. + /// + private static readonly long ECStatusTimeoutTicks = + Stopwatch.Frequency / 100; // 10 ms, should be plenty for EC status waits - /// - /// Used to synchronise EC access. - /// - private static readonly Mutex EcMutex = new(); + /// + /// Used to synchronise EC access. + /// + private static readonly Mutex EcMutex = new(); - /// - /// The underlying driver interface object. - /// - private readonly Driver _Driver; + /// + /// The underlying driver interface object. + /// + private readonly Driver _Driver; - public EC() - { - _Driver = new Driver("WinRing0_1_2_0", - Path.Combine(AppDomain.CurrentDomain.BaseDirectory, - Environment.Is64BitOperatingSystem - ? "WinRing0x64.sys" - : "WinRing0.sys")); - } + public EC() + { + _Driver = new Driver("WinRing0_1_2_0", + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, + Environment.Is64BitOperatingSystem + ? "WinRing0x64.sys" + : "WinRing0.sys")); + } - /// - /// Loads the WinRing0 driver (if not already loaded). - /// - /// - /// If false was returned by this function, - /// can be called to check for driver errors. - /// - /// - /// true if the WinRing0 driver was loaded successfully - /// (or is already loaded), false otherwise. - /// - public bool LoadDriver() + /// + /// Loads the WinRing0 driver (if not already loaded). + /// + /// + /// If false was returned by this function, + /// can be called to check for driver errors. + /// + /// + /// true if the WinRing0 driver was loaded successfully + /// (or is already loaded), false otherwise. + /// + public bool LoadDriver() + { + // Attempt to open an already installed WinRing0 driver first + if (_Driver.Open()) { - // Attempt to open an already installed WinRing0 driver first - if (_Driver.Open()) - { - return true; - } - - // If opening the driver fails, uninstall (if installed) and reinstall it - if (!_Driver.Uninstall()) - { - return false; - } - - if (!_Driver.Install()) - { - _Driver.Uninstall(); - return false; - } - - return _Driver.Open(); + return true; } - /// - /// Unloads the WinRing0 driver (if loaded). - /// - /// - /// This function should be called when the program exits - /// or otherwise no longer requires EC access. - /// - public void UnloadDriver() + // If opening the driver fails, uninstall (if installed) and reinstall it + if (!_Driver.Uninstall()) { - if (GetRefCount() <= 1) - { - // only uninstall the driver if we're the last program using it - // (Driver.Uninstall() calls Driver.Close() internally) - _Driver.Uninstall(); - } - else - { - // otherwise, just close the handle to the driver - _Driver.Close(); - } + return false; } - /// - /// Reads a byte from the EC at the specified register. - /// - /// - /// The register to read from. - /// - /// - /// If successful, contains the value at the specified register (otherwise zero). - /// - /// - /// true if the operation was successful, otherwise false. - /// - public bool ReadByte(byte reg, out byte value) + if (!_Driver.Install()) { - value = 0; - - // only attempt to read EC if driver connection has been opened - if (_Driver.IsOpen) - { - for (int i = 0; i < RW_MAX_RETRIES; i++) - { - if (TryReadByte(reg, out value)) - { - return true; - } - } - } + _Driver.Uninstall(); return false; } - /// - /// Writes a byte to the EC at the specified register. - /// - /// - /// The register to write to. - /// - /// - /// The value to write to the register. - /// - /// - /// true if the operation was successful, otherwise false. - /// - public bool WriteByte(byte reg, byte value) + return _Driver.Open(); + } + + /// + /// Unloads the WinRing0 driver (if loaded). + /// + /// + /// This function should be called when the program exits + /// or otherwise no longer requires EC access. + /// + public void UnloadDriver() + { + if (GetRefCount() <= 1) { - // only attempt to write EC if driver connection has been opened - if (_Driver.IsOpen) - { - for (int i = 0; i < RW_MAX_RETRIES; i++) - { - if (TryWriteByte(reg, value)) - { - return true; - } - } - } - return false; + // only uninstall the driver if we're the last program using it + // (Driver.Uninstall() calls Driver.Close() internally) + _Driver.Uninstall(); } - - /// - /// Reads a 16-bit integer (aka "word") from the EC at the specified register. - /// - /// - /// The register to read from. - /// - /// - /// Indicates the endianness of the value to be read. - /// Defaults to false (little-endian). - /// - /// - /// If successful, contains the value at the specified register (otherwise zero). - /// - /// - /// true if the operation was successful, otherwise false. - /// - public bool ReadWord(byte reg, out ushort value, bool bigEndian = false) + else { - value = 0; + // otherwise, just close the handle to the driver + _Driver.Close(); + } + } + + /// + /// Reads a byte from the EC at the specified register. + /// + /// + /// The register to read from. + /// + /// + /// If successful, contains the value at the specified register (otherwise zero). + /// + /// + /// true if the operation was successful, otherwise false. + /// + public bool ReadByte(byte reg, out byte value) + { + value = 0; - // only attempt to read EC if driver connection has been opened - if (_Driver.IsOpen) + // only attempt to read EC if driver connection has been opened + if (_Driver.IsOpen) + { + for (int i = 0; i < RW_MAX_RETRIES; i++) { - for (int i = 0; i < RW_MAX_RETRIES; i++) + if (TryReadByte(reg, out value)) { - if (TryReadWord(reg, bigEndian, out value)) - { - return true; - } + return true; } } - return false; } + return false; + } - /// - /// Writes a 16-bit integer (aka "word") to the EC at the specified register. - /// - /// - /// The register to write to. - /// - /// - /// The value to write at the register. - /// - /// - /// Indicates the endianness of the value to be written. - /// Defaults to false (little-endian). - /// - /// - /// true if the operation was successful, otherwise false. - /// - public bool WriteWord(byte reg, ushort value, bool bigEndian = false) + /// + /// Writes a byte to the EC at the specified register. + /// + /// + /// The register to write to. + /// + /// + /// The value to write to the register. + /// + /// + /// true if the operation was successful, otherwise false. + /// + public bool WriteByte(byte reg, byte value) + { + // only attempt to write EC if driver connection has been opened + if (_Driver.IsOpen) { - // only attempt to write EC if driver connection has been opened - if (_Driver.IsOpen) + for (int i = 0; i < RW_MAX_RETRIES; i++) { - for (int i = 0; i < RW_MAX_RETRIES; i++) + if (TryWriteByte(reg, value)) { - if (TryWriteWord(reg, value, bigEndian)) - { - return true; - } + return true; } } - return false; } + return false; + } - private bool TryReadByte(byte reg, out byte value) + /// + /// Reads a 16-bit integer (aka "word") from the EC at the specified register. + /// + /// + /// The register to read from. + /// + /// + /// Indicates the endianness of the value to be read. + /// Defaults to false (little-endian). + /// + /// + /// If successful, contains the value at the specified register (otherwise zero). + /// + /// + /// true if the operation was successful, otherwise false. + /// + public bool ReadWord(byte reg, out ushort value, bool bigEndian = false) + { + value = 0; + + // only attempt to read EC if driver connection has been opened + if (_Driver.IsOpen) { - value = 0; - if (EcMutex.WaitOne(2000)) + for (int i = 0; i < RW_MAX_RETRIES; i++) { - try + if (TryReadWord(reg, bigEndian, out value)) { - return WaitWrite() && WriteIOPort(PORT_COMMAND, (byte)ECCommand.Read) - && WaitWrite() && WriteIOPort(PORT_DATA, reg) - && WaitWrite() && WaitRead() - && ReadIOPort(PORT_DATA, out value); - } - finally - { - EcMutex.ReleaseMutex(); + return true; } } - return false; } + return false; + } - private bool TryWriteByte(byte reg, byte value) + /// + /// Writes a 16-bit integer (aka "word") to the EC at the specified register. + /// + /// + /// The register to write to. + /// + /// + /// The value to write at the register. + /// + /// + /// Indicates the endianness of the value to be written. + /// Defaults to false (little-endian). + /// + /// + /// true if the operation was successful, otherwise false. + /// + public bool WriteWord(byte reg, ushort value, bool bigEndian = false) + { + // only attempt to write EC if driver connection has been opened + if (_Driver.IsOpen) { - if (EcMutex.WaitOne(2000)) + for (int i = 0; i < RW_MAX_RETRIES; i++) { - try - { - return WaitWrite() && WriteIOPort(PORT_COMMAND, (byte)ECCommand.Write) - && WaitWrite() && WriteIOPort(PORT_DATA, reg) - && WaitWrite() && WriteIOPort(PORT_DATA, value); - } - finally + if (TryWriteWord(reg, value, bigEndian)) { - EcMutex.ReleaseMutex(); + return true; } } - return false; } + return false; + } - private bool TryReadWord(byte reg, bool bigEndian, out ushort value) + private bool TryReadByte(byte reg, out byte value) + { + value = 0; + if (EcMutex.WaitOne(2000)) { - value = 0; - - // read least-significant byte - if (!TryReadByte(bigEndian ? (byte)(reg + 1) : reg, out byte result)) + try { - return false; + return WaitWrite() && WriteIOPort(PORT_COMMAND, (byte)ECCommand.Read) + && WaitWrite() && WriteIOPort(PORT_DATA, reg) + && WaitWrite() && WaitRead() + && ReadIOPort(PORT_DATA, out value); } - value = result; - - // read most-significant byte - if (!TryReadByte(bigEndian ? reg : (byte)(reg + 1), out result)) + finally { - return false; + EcMutex.ReleaseMutex(); } - value |= (ushort)(result << 8); - - return true; } + return false; + } - private bool TryWriteWord(byte reg, ushort value, bool bigEndian) + private bool TryWriteByte(byte reg, byte value) + { + if (EcMutex.WaitOne(2000)) { - byte lsb, msb; - - if (bigEndian) + try { - msb = (byte)value; - lsb = (byte)(value >> 8); + return WaitWrite() && WriteIOPort(PORT_COMMAND, (byte)ECCommand.Write) + && WaitWrite() && WriteIOPort(PORT_DATA, reg) + && WaitWrite() && WriteIOPort(PORT_DATA, value); } - else + finally { - msb = (byte)(value >> 8); - lsb = (byte)value; + EcMutex.ReleaseMutex(); } + } + return false; + } - return TryWriteByte(reg, lsb) && TryWriteByte((byte)(reg + 1), msb); + private bool TryReadWord(byte reg, bool bigEndian, out ushort value) + { + value = 0; + + // read least-significant byte + if (!TryReadByte(bigEndian ? (byte)(reg + 1) : reg, out byte result)) + { + return false; } + value = result; - /// - /// Waits for the EC to process a read command. - /// - /// - /// true if the EC is ready to have data read from it, - /// false if the operation timed out. - /// - private bool WaitRead() + // read most-significant byte + if (!TryReadByte(bigEndian ? reg : (byte)(reg + 1), out result)) { - return WaitForECStatus(ECStatus.OutputBufferFull, true); + return false; } + value |= (ushort)(result << 8); - /// - /// Waits for the EC to process a write command. - /// - /// - /// true if the EC is ready to accept data, - /// false if the operation timed out. - /// - private bool WaitWrite() + return true; + } + + private bool TryWriteWord(byte reg, ushort value, bool bigEndian) + { + byte lsb, msb; + + if (bigEndian) + { + msb = (byte)value; + lsb = (byte)(value >> 8); + } + else { - return WaitForECStatus(ECStatus.InputBufferFull, false); + msb = (byte)(value >> 8); + lsb = (byte)value; } - /// - /// Waits for the specified to be set/unset. - /// - /// - /// The to wait for. - /// - /// - /// Whether to wait for the status to be set or unset. - /// - /// - /// true if the EC status was (un)set before timing out, - /// otherwise false. - /// - private bool WaitForECStatus(ECStatus status, bool isSet) + return TryWriteByte(reg, lsb) && TryWriteByte((byte)(reg + 1), msb); + } + + /// + /// Waits for the EC to process a read command. + /// + /// + /// true if the EC is ready to have data read from it, + /// false if the operation timed out. + /// + private bool WaitRead() + { + return WaitForECStatus(ECStatus.OutputBufferFull, true); + } + + /// + /// Waits for the EC to process a write command. + /// + /// + /// true if the EC is ready to accept data, + /// false if the operation timed out. + /// + private bool WaitWrite() + { + return WaitForECStatus(ECStatus.InputBufferFull, false); + } + + /// + /// Waits for the specified to be set/unset. + /// + /// + /// The to wait for. + /// + /// + /// Whether to wait for the status to be set or unset. + /// + /// + /// true if the EC status was (un)set before timing out, + /// otherwise false. + /// + private bool WaitForECStatus(ECStatus status, bool isSet) + { + Stopwatch sw = Stopwatch.StartNew(); + try { - Stopwatch sw = Stopwatch.StartNew(); - try + while (sw.ElapsedTicks < ECStatusTimeoutTicks) { - while (sw.ElapsedTicks < ECStatusTimeoutTicks) + // Read the EC status from the command port + if (ReadIOPort(PORT_COMMAND, out byte value)) { - // Read the EC status from the command port - if (ReadIOPort(PORT_COMMAND, out byte value)) - { - ECStatus ecStatus = (ECStatus)value; - bool ecIsSet = (status & ecStatus) == status; + ECStatus ecStatus = (ECStatus)value; + bool ecIsSet = (status & ecStatus) == status; - if (isSet == ecIsSet) - { - return true; - } + if (isSet == ecIsSet) + { + return true; } } } - finally - { - sw.Stop(); - } - - return false; } - - private bool ReadIOPort(uint port, out byte value) + finally { - bool success = _Driver.IOControl( - (uint)Ring0Control.ReadIOPortByte, - ref port, out uint val); - - // bitwise AND operation prevents integer overflow - value = (byte)(val & 0xFF); - return success; + sw.Stop(); } - private bool WriteIOPort(ushort port, byte value) - { - WriteIOPortInput input = new(port, value); - return _Driver.IOControl((uint)Ring0Control.WriteIOPortByte, ref input); - } + return false; + } - private uint GetRefCount() - { - uint refCount = 0; - return _Driver.IOControl((uint)Ring0Control.GetRefCount, ref refCount, out refCount) ? refCount : 0; - } + private bool ReadIOPort(uint port, out byte value) + { + bool success = _Driver.IOControl( + (uint)Ring0Control.ReadIOPortByte, + ref port, out uint val); - /// - /// Gets the last error produced by the underlying driver library. - /// - /// - /// The last error code produced by the driver library. - /// - public int GetDriverError() - { - return _Driver.ErrorCode; - } + // bitwise AND operation prevents integer overflow + value = (byte)(val & 0xFF); + return success; + } - private enum Ring0Control : uint - { - // DeviceType, Function, Access (1 = Read, 2 = Write, 0 = Any) - GetDriverVersion = 40000u << 16 | 0x800 << 2, - GetRefCount = 40000u << 16 | 0x801 << 2, - ReadIOPortByte = 40000u << 16 | 0x833 << 2 | 1 << 14, - WriteIOPortByte = 40000u << 16 | 0x836 << 2 | 2 << 14, - } + private bool WriteIOPort(ushort port, byte value) + { + WriteIOPortInput input = new(port, value); + return _Driver.IOControl((uint)Ring0Control.WriteIOPortByte, ref input); + } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - private struct WriteIOPortInput - { - public uint Port; - public byte Value; + private uint GetRefCount() + { + uint refCount = 0; + return _Driver.IOControl((uint)Ring0Control.GetRefCount, ref refCount, out refCount) ? refCount : 0; + } - public WriteIOPortInput(uint port, byte value) - { - Port = port; - Value = value; - } + /// + /// Gets the last error produced by the underlying driver library. + /// + /// + /// The last error code produced by the driver library. + /// + public int GetDriverError() + { + return _Driver.ErrorCode; + } + + private enum Ring0Control : uint + { + // DeviceType, Function, Access (1 = Read, 2 = Write, 0 = Any) + GetDriverVersion = 40000u << 16 | 0x800 << 2, + GetRefCount = 40000u << 16 | 0x801 << 2, + ReadIOPortByte = 40000u << 16 | 0x833 << 2 | 1 << 14, + WriteIOPortByte = 40000u << 16 | 0x836 << 2 | 2 << 14, + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct WriteIOPortInput + { + public uint Port; + public byte Value; + + public WriteIOPortInput(uint port, byte value) + { + Port = port; + Value = value; } } } diff --git a/YAMDCC.ECAccess/Win32/AdvApi32.cs b/YAMDCC.ECAccess/Win32/AdvApi32.cs index 2beae0f..7e16bc4 100644 --- a/YAMDCC.ECAccess/Win32/AdvApi32.cs +++ b/YAMDCC.ECAccess/Win32/AdvApi32.cs @@ -17,329 +17,328 @@ using System; using System.Runtime.InteropServices; -namespace YAMDCC.ECAccess.Win32 +namespace YAMDCC.ECAccess.Win32; + +/// +/// Wraps native Win32 functions from advapi32.dll. +/// +internal static class AdvApi32 { /// - /// Wraps native Win32 functions from advapi32.dll. + /// Opens an handle with full access to the local computer's + /// active service control manager (SCM) database. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// An handle (as an ) to the local computer's + /// active SCM database, if the function succeeds. + /// if the function fails. Call + /// to get error information. + /// + [DllImport("advapi32.dll", + CharSet = CharSet.Unicode, ExactSpelling = true, + EntryPoint = "OpenSCManagerW", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr OpenSCManager( + string lpMachineName, + string lpDatabaseName, + [MarshalAs(UnmanagedType.U4)] SCMAccess dwDesiredAccess); + + /// + /// Creates and adds a service to the local computer. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// An handle to the service if the function succeeds. + /// if the function fails. Call + /// for error information. + /// + [DllImport("advapi32.dll", + CharSet = CharSet.Unicode, ExactSpelling = true, + EntryPoint = "CreateServiceW", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr CreateService( + IntPtr hSCManager, + string lpServiceName, + [Optional] string lpDisplayName, + [MarshalAs(UnmanagedType.U4)] ServiceAccess dwDesiredAccess, + [MarshalAs(UnmanagedType.U4)] ServiceType dwServiceType, + [MarshalAs(UnmanagedType.U4)] ServiceStartType dwStartType, + [MarshalAs(UnmanagedType.U4)] ServiceError dwErrorControl, + [Optional] string lpBinaryPathName, + [Optional] string lpLoadOrderGroup, + [Optional] string lpdwTagId, + [Optional] string lpDependencies, + [Optional] string lpServiceStartName, + [Optional] string lpPassword); + + /// + /// Opens an existing service. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// An handle to the service if the function succeeds. + /// if the function fails. Call + /// for error information. + /// + [DllImport("advapi32.dll", + CharSet = CharSet.Unicode, ExactSpelling = true, + EntryPoint = "OpenServiceW", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr OpenService( + IntPtr hSCManager, + string lpServiceName, + [MarshalAs(UnmanagedType.U4)] ServiceAccess dwDesiredAccess); + + /// + /// Starts a service. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// true if the function succeeds, otherwise false. + /// Call to get error information. + /// + [DllImport("advapi32.dll", + CharSet = CharSet.Unicode, ExactSpelling = true, + EntryPoint = "StartServiceW", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool StartService( + IntPtr hService, + uint dwNumServiceArgs, + [Optional] IntPtr lpServiceArgVectors); + + /// + /// Sends a control code to a service. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// true if the function succeeds, otherwise false. + /// Call to get error information. + /// + [DllImport("advapi32.dll", + ExactSpelling = true, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ControlService( + IntPtr hService, + uint dwControl, + out ServiceStatus lpServiceStatus); + + /// + [DllImport("advapi32.dll", + ExactSpelling = true, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ControlService( + IntPtr hService, + [MarshalAs(UnmanagedType.U4)] ServiceControlCode dwControl, + out ServiceStatus lpServiceStatus); + + /// + /// Deletes a service from the system. /// - internal static class AdvApi32 + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// true if the function succeeds, otherwise false. + /// Call to get error information. + /// + [DllImport("advapi32.dll", + ExactSpelling = true, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern bool DeleteService(IntPtr hService); + + /// + /// Closes an handle to a service control manager or service. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// true if the function succeeds, otherwise false. + /// To get error information, call . + /// + [DllImport("advapi32.dll", + ExactSpelling = true, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern bool CloseServiceHandle(IntPtr hSCObject); + + [StructLayout(LayoutKind.Sequential)] + internal struct ServiceStatus + { + internal ServiceType dwServiceType; + internal ServiceState dwCurrentState; + internal uint dwControlsAccepted; + internal uint dwWin32ExitCode; + internal uint dwServiceSpecificExitCode; + internal uint dwCheckPoint; + internal uint dwWaitHint; + } + + internal enum ServiceControlCode : uint + { + Stop = 0x01, + Pause = 0x02, + Continue = 0x03, + Interrogate = 0x04, + ParamChange = 0x06, + NetBindAdd = 0x07, + NetBindRemove = 0x08, + NetBindEnable = 0x09, + NetBindDisable = 0x0A, + } + + [Flags] + internal enum SCMAccess : uint + { + Connect = 0x0001, + CreateService = 0x0002, + EnumerateService = 0x0004, + Lock = 0x0008, + QueryLockStatus = 0x0010, + ModifyBootConfig = 0x0020, + All = 0xF003F, + } + + [Flags] + internal enum ServiceAccess : uint + { + QueryConfig = 0x0001, + ChangeConfig = 0x0002, + QueryStatus = 0x0004, + EnumerateDependents = 0x0008, + Start = 0x0010, + Stop = 0x0020, + PauseContinue = 0x0040, + Interrogate = 0x0080, + UserDefinedControl = 0x0100, + Delete = 0x10000, + ReadControl = 0x20000, + WriteDac = 0x40000, + WriteOwner = 0x80000, + All = 0xF01FF, + } + + [Flags] + internal enum ServiceType : uint { /// - /// Opens an handle with full access to the local computer's - /// active service control manager (SCM) database. + /// A kernel-mode driver service. /// - /// - /// See the MSDN documentation for more info: - /// - /// - /// - /// An handle (as an ) to the local computer's - /// active SCM database, if the function succeeds. - /// if the function fails. Call - /// to get error information. - /// - [DllImport("advapi32.dll", - CharSet = CharSet.Unicode, ExactSpelling = true, - EntryPoint = "OpenSCManagerW", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr OpenSCManager( - string lpMachineName, - string lpDatabaseName, - [MarshalAs(UnmanagedType.U4)] SCMAccess dwDesiredAccess); - + KernelDriver = 0x00000001, /// - /// Creates and adds a service to the local computer. + /// A file system driver service. /// - /// - /// See the MSDN documentation for more info: - /// - /// - /// - /// An handle to the service if the function succeeds. - /// if the function fails. Call - /// for error information. - /// - [DllImport("advapi32.dll", - CharSet = CharSet.Unicode, ExactSpelling = true, - EntryPoint = "CreateServiceW", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr CreateService( - IntPtr hSCManager, - string lpServiceName, - [Optional] string lpDisplayName, - [MarshalAs(UnmanagedType.U4)] ServiceAccess dwDesiredAccess, - [MarshalAs(UnmanagedType.U4)] ServiceType dwServiceType, - [MarshalAs(UnmanagedType.U4)] ServiceStartType dwStartType, - [MarshalAs(UnmanagedType.U4)] ServiceError dwErrorControl, - [Optional] string lpBinaryPathName, - [Optional] string lpLoadOrderGroup, - [Optional] string lpdwTagId, - [Optional] string lpDependencies, - [Optional] string lpServiceStartName, - [Optional] string lpPassword); - + FileSystemDriver = 0x00000002, /// - /// Opens an existing service. + /// A service that runs in its own process. /// - /// - /// See the MSDN documentation for more info: - /// - /// - /// - /// An handle to the service if the function succeeds. - /// if the function fails. Call - /// for error information. - /// - [DllImport("advapi32.dll", - CharSet = CharSet.Unicode, ExactSpelling = true, - EntryPoint = "OpenServiceW", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr OpenService( - IntPtr hSCManager, - string lpServiceName, - [MarshalAs(UnmanagedType.U4)] ServiceAccess dwDesiredAccess); - + Win32OwnProcess = 0x00000010, /// - /// Starts a service. + /// A service that shares a process with one or more other + /// services. /// /// - /// See the MSDN documentation for more info: - /// + /// An example of an executable that spawns multiple services + /// is Windows' svchost.exe, which hosts many internal + /// Windows services. /// - /// - /// true if the function succeeds, otherwise false. - /// Call to get error information. - /// - [DllImport("advapi32.dll", - CharSet = CharSet.Unicode, ExactSpelling = true, - EntryPoint = "StartServiceW", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool StartService( - IntPtr hService, - uint dwNumServiceArgs, - [Optional] IntPtr lpServiceArgVectors); + Win32ShareProcess = 0x00000020, + } + internal enum ServiceState : uint + { + Stopped = 1U, + StartPending = 2U, + StopPending = 3U, + Running = 4U, + ContinuePending = 5U, + PausePending = 6U, + Paused = 7U, + } + + internal enum ServiceStartType : uint + { /// - /// Sends a control code to a service. + /// A device driver started by the system loader. /// /// - /// See the MSDN documentation for more info: - /// + /// Valid only for driver services. /// - /// - /// true if the function succeeds, otherwise false. - /// Call to get error information. - /// - [DllImport("advapi32.dll", - ExactSpelling = true, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ControlService( - IntPtr hService, - uint dwControl, - out ServiceStatus lpServiceStatus); - - /// - [DllImport("advapi32.dll", - ExactSpelling = true, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ControlService( - IntPtr hService, - [MarshalAs(UnmanagedType.U4)] ServiceControlCode dwControl, - out ServiceStatus lpServiceStatus); - + BootStart = 0U, /// - /// Deletes a service from the system. + /// A device driver started by the IoInitSystem function. /// /// - /// See the MSDN documentation for more info: - /// + /// Valid only for driver services. /// - /// - /// true if the function succeeds, otherwise false. - /// Call to get error information. - /// - [DllImport("advapi32.dll", - ExactSpelling = true, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern bool DeleteService(IntPtr hService); - + SystemStart = 1U, + /// + /// A service started automatically by the Service + /// Control Manager during system startup. + /// + AutoStart = 2U, /// - /// Closes an handle to a service control manager or service. + /// A service started by the Service Control Manager when a + /// process calls the StartService function. + /// + DemandStart = 3U, + /// + /// A service that is disabled. /// /// - /// See the MSDN documentation for more info: - /// + /// Disabled services cannot be started. /// - /// - /// true if the function succeeds, otherwise false. - /// To get error information, call . - /// - [DllImport("advapi32.dll", - ExactSpelling = true, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern bool CloseServiceHandle(IntPtr hSCObject); - - [StructLayout(LayoutKind.Sequential)] - internal struct ServiceStatus - { - internal ServiceType dwServiceType; - internal ServiceState dwCurrentState; - internal uint dwControlsAccepted; - internal uint dwWin32ExitCode; - internal uint dwServiceSpecificExitCode; - internal uint dwCheckPoint; - internal uint dwWaitHint; - } - - internal enum ServiceControlCode : uint - { - Stop = 0x01, - Pause = 0x02, - Continue = 0x03, - Interrogate = 0x04, - ParamChange = 0x06, - NetBindAdd = 0x07, - NetBindRemove = 0x08, - NetBindEnable = 0x09, - NetBindDisable = 0x0A, - } - - [Flags] - internal enum SCMAccess : uint - { - Connect = 0x0001, - CreateService = 0x0002, - EnumerateService = 0x0004, - Lock = 0x0008, - QueryLockStatus = 0x0010, - ModifyBootConfig = 0x0020, - All = 0xF003F, - } - - [Flags] - internal enum ServiceAccess : uint - { - QueryConfig = 0x0001, - ChangeConfig = 0x0002, - QueryStatus = 0x0004, - EnumerateDependents = 0x0008, - Start = 0x0010, - Stop = 0x0020, - PauseContinue = 0x0040, - Interrogate = 0x0080, - UserDefinedControl = 0x0100, - Delete = 0x10000, - ReadControl = 0x20000, - WriteDac = 0x40000, - WriteOwner = 0x80000, - All = 0xF01FF, - } - - [Flags] - internal enum ServiceType : uint - { - /// - /// A kernel-mode driver service. - /// - KernelDriver = 0x00000001, - /// - /// A file system driver service. - /// - FileSystemDriver = 0x00000002, - /// - /// A service that runs in its own process. - /// - Win32OwnProcess = 0x00000010, - /// - /// A service that shares a process with one or more other - /// services. - /// - /// - /// An example of an executable that spawns multiple services - /// is Windows' svchost.exe, which hosts many internal - /// Windows services. - /// - Win32ShareProcess = 0x00000020, - } - - internal enum ServiceState : uint - { - Stopped = 1U, - StartPending = 2U, - StopPending = 3U, - Running = 4U, - ContinuePending = 5U, - PausePending = 6U, - Paused = 7U, - } - - internal enum ServiceStartType : uint - { - /// - /// A device driver started by the system loader. - /// - /// - /// Valid only for driver services. - /// - BootStart = 0U, - /// - /// A device driver started by the IoInitSystem function. - /// - /// - /// Valid only for driver services. - /// - SystemStart = 1U, - /// - /// A service started automatically by the Service - /// Control Manager during system startup. - /// - AutoStart = 2U, - /// - /// A service started by the Service Control Manager when a - /// process calls the StartService function. - /// - DemandStart = 3U, - /// - /// A service that is disabled. - /// - /// - /// Disabled services cannot be started. - /// - Disabled = 4U, - } + Disabled = 4U, + } + /// + /// The action to take if a service fails to start. + /// + internal enum ServiceError : uint + { + /// + /// The error is ignored and the service continues to + /// start up. + /// + Ignore = 0U, + /// + /// The error is logged to the event log, but the + /// service continues to start up. + /// + Normal = 1U, + /// + /// The error is logged to the event log. If the last-known-good + /// configuration is being started, the service continues to start + /// up. Otherwise, the service is restarted with the last-known-good + /// configuration. + /// + Severe = 2U, /// - /// The action to take if a service fails to start. + /// The error is logged to the event log. If the last-known-good + /// configuration is being started, the service fails to start. + /// Otherwise, the service is restarted with the last-known-good + /// configuration. /// - internal enum ServiceError : uint - { - /// - /// The error is ignored and the service continues to - /// start up. - /// - Ignore = 0U, - /// - /// The error is logged to the event log, but the - /// service continues to start up. - /// - Normal = 1U, - /// - /// The error is logged to the event log. If the last-known-good - /// configuration is being started, the service continues to start - /// up. Otherwise, the service is restarted with the last-known-good - /// configuration. - /// - Severe = 2U, - /// - /// The error is logged to the event log. If the last-known-good - /// configuration is being started, the service fails to start. - /// Otherwise, the service is restarted with the last-known-good - /// configuration. - /// - Critical = 3U, - } + Critical = 3U, } } diff --git a/YAMDCC.ECAccess/Win32/Kernel32.cs b/YAMDCC.ECAccess/Win32/Kernel32.cs index f181405..70f8407 100644 --- a/YAMDCC.ECAccess/Win32/Kernel32.cs +++ b/YAMDCC.ECAccess/Win32/Kernel32.cs @@ -19,93 +19,92 @@ using System.Runtime.InteropServices; using System.Threading; -namespace YAMDCC.ECAccess.Win32 +namespace YAMDCC.ECAccess.Win32; + +/// +/// Wraps native Win32 functions from kernel32.dll. +/// +internal static class Kernel32 { /// - /// Wraps native Win32 functions from kernel32.dll. + /// Closes an open handle. /// - internal static class Kernel32 - { - /// - /// Closes an open handle. - /// - /// - /// See the MSDN documentation for more info: - /// - /// - /// - /// An handle to close. - /// - /// - /// true if the function succeeds, otherwise false. - /// To get error information, call . - /// - [DllImport("kernel32.dll", - ExactSpelling = true, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern bool CloseHandle(IntPtr hObject); + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// An handle to close. + /// + /// + /// true if the function succeeds, otherwise false. + /// To get error information, call . + /// + [DllImport("kernel32.dll", + ExactSpelling = true, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern bool CloseHandle(IntPtr hObject); - /// - /// Open an installed device driver for direct communication. - /// - /// - /// See the MSDN documentation for more info: - /// - /// - /// - /// - /// An open handle (as an ) to the specified - /// device driver if the function succeeds. - /// - /// - /// if the function fails. Call - /// for error information. - /// - /// - [DllImport("kernel32.dll", - CharSet = CharSet.Unicode, ExactSpelling = true, - EntryPoint = "CreateFileW", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr CreateFile( - string lpFileName, - [MarshalAs(UnmanagedType.U4)] GenericAccessRights dwDesiredAccess, - [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, - [Optional] IntPtr lpSecurityAttributes, - [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, - [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, - [Optional] IntPtr hTemplateFile); + /// + /// Open an installed device driver for direct communication. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// + /// An open handle (as an ) to the specified + /// device driver if the function succeeds. + /// + /// + /// if the function fails. Call + /// for error information. + /// + /// + [DllImport("kernel32.dll", + CharSet = CharSet.Unicode, ExactSpelling = true, + EntryPoint = "CreateFileW", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr CreateFile( + string lpFileName, + [MarshalAs(UnmanagedType.U4)] GenericAccessRights dwDesiredAccess, + [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, + [Optional] IntPtr lpSecurityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, + [Optional] IntPtr hTemplateFile); - /// - /// Sends an IO control code directly to a specified device driver. - /// - /// - /// See the MSDN documentation for more info: - /// - /// - /// - /// true if the operation was successful, otherwise false. - /// - [DllImport("kernel32.dll", - ExactSpelling = true, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern unsafe bool DeviceIoControl( - IntPtr hDevice, - uint dwIoControlCode, - [Optional] void* lpInBuffer, - uint nInBufferSize, - [Optional] void* lpOutBuffer, - uint nOutBufferSize, - [Optional] out uint lpBytesReturned, - [Optional] NativeOverlapped* lpOverlapped); + /// + /// Sends an IO control code directly to a specified device driver. + /// + /// + /// See the MSDN documentation for more info: + /// + /// + /// + /// true if the operation was successful, otherwise false. + /// + [DllImport("kernel32.dll", + ExactSpelling = true, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern unsafe bool DeviceIoControl( + IntPtr hDevice, + uint dwIoControlCode, + [Optional] void* lpInBuffer, + uint nInBufferSize, + [Optional] void* lpOutBuffer, + uint nOutBufferSize, + [Optional] out uint lpBytesReturned, + [Optional] NativeOverlapped* lpOverlapped); - internal enum GenericAccessRights : uint - { - None = 0, - All = 0x10000000, - Execute = 0x20000000, - Write = 0x40000000, - Read = 0x80000000, - } + internal enum GenericAccessRights : uint + { + None = 0, + All = 0x10000000, + Execute = 0x20000000, + Write = 0x40000000, + Read = 0x80000000, } } diff --git a/YAMDCC.ECInspector/ECValue.cs b/YAMDCC.ECInspector/ECValue.cs index be43196..3f8f3d8 100644 --- a/YAMDCC.ECInspector/ECValue.cs +++ b/YAMDCC.ECInspector/ECValue.cs @@ -14,18 +14,17 @@ // You should have received a copy of the GNU General Public License along with // YAMDCC. If not, see . -namespace YAMDCC.ECInspector +namespace YAMDCC.ECInspector; + +internal struct ECValue { - internal struct ECValue - { - /// - /// The EC value itself. - /// - public int Value; + /// + /// The EC value itself. + /// + public int Value; - /// - /// How many EC polls it's been since was last updated. - /// - public int Age; - } + /// + /// How many EC polls it's been since was last updated. + /// + public int Age; } diff --git a/YAMDCC.ECInspector/Program.cs b/YAMDCC.ECInspector/Program.cs index d0c13a9..5a30b4a 100644 --- a/YAMDCC.ECInspector/Program.cs +++ b/YAMDCC.ECInspector/Program.cs @@ -22,251 +22,250 @@ using YAMDCC.Common; using YAMDCC.IPC; -namespace YAMDCC.ECInspector +namespace YAMDCC.ECInspector; + +internal sealed class Program { - internal sealed class Program - { - private static readonly string ExeName = - Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location); + private static readonly string ExeName = + Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location); - private static readonly NamedPipeClient IPCClient = - new("YAMDCC-Server"); + private static readonly NamedPipeClient IPCClient = + new("YAMDCC-Server"); - private static readonly Mutex LogMutex = new(false); + private static readonly Mutex LogMutex = new(false); - private static readonly ECValue[] ECValues = new ECValue[256]; + private static readonly ECValue[] ECValues = new ECValue[256]; - private static int Main(string[] args) + private static int Main(string[] args) + { + if (!Utils.IsAdmin()) { - if (!Utils.IsAdmin()) - { - Console.WriteLine("ERROR: please re-run this program as admin."); - return 255; - } + Console.WriteLine("ERROR: please re-run this program as admin."); + return 255; + } - // check that YAMDCC service is running - ServiceController yamdccSvc = new("yamdccsvc"); - try - { - if (yamdccSvc.Status == ServiceControllerStatus.Stopped) - { - Console.WriteLine( - "ERROR: the YAMDCC service is not running.\n" + - "Please run the YAMDCC config editor to start the service."); - return 1; - } - } - catch + // check that YAMDCC service is running + ServiceController yamdccSvc = new("yamdccsvc"); + try + { + if (yamdccSvc.Status == ServiceControllerStatus.Stopped) { Console.WriteLine( - "ERROR: the YAMDCC service is not installed.\n" + - "Please run the YAMDCC config editor to install the service."); + "ERROR: the YAMDCC service is not running.\n" + + "Please run the YAMDCC config editor to start the service."); return 1; } - finally - { - yamdccSvc?.Close(); - } + } + catch + { + Console.WriteLine( + "ERROR: the YAMDCC service is not installed.\n" + + "Please run the YAMDCC config editor to install the service."); + return 1; + } + finally + { + yamdccSvc?.Close(); + } - if (args.Length == 0) - { - Console.WriteLine("ERROR: no command specified\n"); + if (args.Length == 0) + { + Console.WriteLine("ERROR: no command specified\n"); + PrintHelp(); + return 2; + } + switch (args[0]) + { + case "--version": + case "-v": + Console.WriteLine(Utils.GetVerString()); + return 0; + case "--help": + case "-h": PrintHelp(); - return 2; - } - switch (args[0]) - { - case "--version": - case "-v": - Console.WriteLine(Utils.GetVerString()); + return 0; + case "--dump": + case "-d": + if (ConnectService()) + { + DumpEC(1000, 1); return 0; - case "--help": - case "-h": - PrintHelp(); + } + return 3; + case "--monitor": + case "-m": + if (ConnectService()) + { + DumpEC(1000, -1); return 0; - case "--dump": - case "-d": - if (ConnectService()) - { - DumpEC(1000, 1); - return 0; - } - return 3; - case "--monitor": - case "-m": - if (ConnectService()) - { - DumpEC(1000, -1); - return 0; - } - return 3; - case "": - Console.WriteLine("ERROR: no command specified\n"); - PrintHelp(); - return 2; - default: - Console.WriteLine($"ERROR: unknown command: {args[0]}\n"); - PrintHelp(); - return 2; - } + } + return 3; + case "": + Console.WriteLine("ERROR: no command specified\n"); + PrintHelp(); + return 2; + default: + Console.WriteLine($"ERROR: unknown command: {args[0]}\n"); + PrintHelp(); + return 2; } + } + + private static void PrintHelp() + { + Console.WriteLine( + "YAMDCC EC inspection utility\n\n" + + $"OS version: {Environment.OSVersion}\n" + + $"App version: {Utils.GetVerString()}\n" + + $"Revision (git): {Utils.GetRevision()}\n\n" + + $"Usage: {ExeName} []\n\n" + + "Commands:\n\n" + + " --help, -h Print this help screen\n" + + " --version, -v Print the program version\n" + + " --dump, -d Dump all EC registers\n" + + " --monitor, -m Dump EC and monitor for changes\n" + + " --interval, -i EC polling interval"); + } - private static void PrintHelp() + private static bool ConnectService() + { + IPCClient.ServerMessage += IPCClient_ServerMessage; + IPCClient.Error += IPCClient_Error; + + IPCClient.Start(); + if (!IPCClient.WaitForConnection(5000)) { - Console.WriteLine( - "YAMDCC EC inspection utility\n\n" + - $"OS version: {Environment.OSVersion}\n" + - $"App version: {Utils.GetVerString()}\n" + - $"Revision (git): {Utils.GetRevision()}\n\n" + - $"Usage: {ExeName} []\n\n" + - "Commands:\n\n" + - " --help, -h Print this help screen\n" + - " --version, -v Print the program version\n" + - " --dump, -d Dump all EC registers\n" + - " --monitor, -m Dump EC and monitor for changes\n" + - " --interval, -i EC polling interval"); + Console.WriteLine("ERROR: failed to connect to YAMDCC service!"); + return false; } + return true; + } - private static bool ConnectService() + private static void DumpEC(int interval, int loops) + { + for (int i = 0; i <= byte.MaxValue; i++) { - IPCClient.ServerMessage += IPCClient_ServerMessage; - IPCClient.Error += IPCClient_Error; - - IPCClient.Start(); - if (!IPCClient.WaitForConnection(5000)) - { - Console.WriteLine("ERROR: failed to connect to YAMDCC service!"); - return false; - } - return true; + ECValues[i] = new ECValue(); } + Console.Clear(); - private static void DumpEC(int interval, int loops) + Console.SetCursorPosition(0, 0); + + // write heading + Console.Write("YAMDCC EC inspector\n\n 0x |"); + for (int i = 0; i < 16; i++) { - for (int i = 0; i <= byte.MaxValue; i++) - { - ECValues[i] = new ECValue(); - } - Console.Clear(); + Console.Write($" 0{i:X}"); + } + Console.WriteLine(); + Console.WriteLine("----|------------------------------------------------"); - Console.SetCursorPosition(0, 0); + for (int i = 0; i < 16; i++) + { + Console.WriteLine($" {i:X}0 |"); + } - // write heading - Console.Write("YAMDCC EC inspector\n\n 0x |"); - for (int i = 0; i < 16; i++) - { - Console.Write($" 0{i:X}"); - } - Console.WriteLine(); - Console.WriteLine("----|------------------------------------------------"); + Console.WriteLine("\nPress Ctrl+C to exit"); + Console.CursorVisible = false; + Console.CancelKeyPress += Console_CancelKeyPress; - for (int i = 0; i < 16; i++) + int j = 0; + while (true) + { + if (loops != -1 && j >= loops) { - Console.WriteLine($" {i:X}0 |"); + break; } - - Console.WriteLine("\nPress Ctrl+C to exit"); - Console.CursorVisible = false; - Console.CancelKeyPress += Console_CancelKeyPress; - - int j = 0; - while (true) + for (int i = 0; i <= byte.MaxValue; i++) { - if (loops != -1 && j >= loops) - { - break; - } - for (int i = 0; i <= byte.MaxValue; i++) - { - IPCClient.PushMessage(new ServiceCommand(Command.ReadECByte, $"{i}")); - } - Thread.Sleep(interval); - j++; + IPCClient.PushMessage(new ServiceCommand(Command.ReadECByte, $"{i}")); } - - Console.CursorVisible = true; - IPCClient.Stop(); + Thread.Sleep(interval); + j++; } - private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) - { - Console.CursorVisible = true; - IPCClient.Stop(); - } + Console.CursorVisible = true; + IPCClient.Stop(); + } - private static void IPCClient_ServerMessage(object sender, PipeMessageEventArgs e) + private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) + { + Console.CursorVisible = true; + IPCClient.Stop(); + } + + private static void IPCClient_ServerMessage(object sender, PipeMessageEventArgs e) + { + if (LogMutex.WaitOne()) { - if (LogMutex.WaitOne()) + try { - try + switch (e.Message.Response) { - switch (e.Message.Response) - { - case Response.ReadResult: - if (ParseArgs(e.Message.Value, out int[] args) && args.Length == 2) - { - int lowBits = args[0] & 0x0F, - hiBits = (args[0] & 0xF0) >> 4; - Console.SetCursorPosition(6 + lowBits * 3, 4 + hiBits); + case Response.ReadResult: + if (ParseArgs(e.Message.Value, out int[] args) && args.Length == 2) + { + int lowBits = args[0] & 0x0F, + hiBits = (args[0] & 0xF0) >> 4; + Console.SetCursorPosition(6 + lowBits * 3, 4 + hiBits); - ConsoleColor original = Console.ForegroundColor; + ConsoleColor original = Console.ForegroundColor; - if (ECValues[args[0]].Value == args[1]) - { - ECValues[args[0]].Age++; - Console.ForegroundColor = ConsoleColor.DarkRed; - } - else - { - ECValues[args[0]].Value = args[1]; - ECValues[args[0]].Age = 0; - Console.ForegroundColor = ConsoleColor.Green; - } + if (ECValues[args[0]].Value == args[1]) + { + ECValues[args[0]].Age++; + Console.ForegroundColor = ConsoleColor.DarkRed; + } + else + { + ECValues[args[0]].Value = args[1]; + ECValues[args[0]].Age = 0; + Console.ForegroundColor = ConsoleColor.Green; + } - if (args[1] == 0) - { - Console.ForegroundColor = ConsoleColor.DarkGray; - } - Console.Write($"{args[1]:X2}"); - Console.ForegroundColor = original; - Console.SetCursorPosition(0, 20); + if (args[1] == 0) + { + Console.ForegroundColor = ConsoleColor.DarkGray; } - break; - } - } - finally - { - LogMutex.ReleaseMutex(); + Console.Write($"{args[1]:X2}"); + Console.ForegroundColor = original; + Console.SetCursorPosition(0, 20); + } + break; } } + finally + { + LogMutex.ReleaseMutex(); + } } + } + + private static void IPCClient_Error(object sender, PipeErrorEventArgs e) + { + throw e.Exception; + } - private static void IPCClient_Error(object sender, PipeErrorEventArgs e) + private static bool ParseArgs(string argsIn, out int[] argsOut) + { + if (string.IsNullOrEmpty(argsIn)) { - throw e.Exception; + argsOut = []; } - - private static bool ParseArgs(string argsIn, out int[] argsOut) + else { - if (string.IsNullOrEmpty(argsIn)) - { - argsOut = []; - } - else - { - string[] args_str = argsIn.Split(' '); - argsOut = new int[args_str.Length]; + string[] args_str = argsIn.Split(' '); + argsOut = new int[args_str.Length]; - for (int i = 0; i < args_str.Length; i++) + for (int i = 0; i < args_str.Length; i++) + { + if (!int.TryParse(args_str[i], out argsOut[i])) { - if (!int.TryParse(args_str[i], out argsOut[i])) - { - return false; - } + return false; } } - return true; } + return true; } } diff --git a/YAMDCC.IPC/ConnectionFactory.cs b/YAMDCC.IPC/ConnectionFactory.cs index 2338fd3..68f602e 100644 --- a/YAMDCC.IPC/ConnectionFactory.cs +++ b/YAMDCC.IPC/ConnectionFactory.cs @@ -1,16 +1,15 @@ using System.IO.Pipes; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +internal static class ConnectionFactory { - internal static class ConnectionFactory - { - private static int _lastId; + private static int _lastId; - internal static NamedPipeConnection CreateConnection(PipeStream pipeStream) - where TRead : class - where TWrite : class - { - return new NamedPipeConnection(++_lastId, $"Client {_lastId}", pipeStream); - } + internal static NamedPipeConnection CreateConnection(PipeStream pipeStream) + where TRead : class + where TWrite : class + { + return new NamedPipeConnection(++_lastId, $"Client {_lastId}", pipeStream); } } diff --git a/YAMDCC.IPC/Constants.cs b/YAMDCC.IPC/Constants.cs index 847b2b9..7cac2af 100644 --- a/YAMDCC.IPC/Constants.cs +++ b/YAMDCC.IPC/Constants.cs @@ -1,10 +1,9 @@ using MessagePack; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +internal static class Constants { - internal static class Constants - { - public static readonly MessagePackSerializerOptions SerializerOptions = - MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.TrustedData); - } + public static readonly MessagePackSerializerOptions SerializerOptions = + MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.TrustedData); } diff --git a/YAMDCC.IPC/IO/PipeStreamReader.cs b/YAMDCC.IPC/IO/PipeStreamReader.cs index ac2d624..021e1df 100644 --- a/YAMDCC.IPC/IO/PipeStreamReader.cs +++ b/YAMDCC.IPC/IO/PipeStreamReader.cs @@ -4,87 +4,86 @@ using System.IO.Pipes; using System.Net; -namespace YAMDCC.IPC.IO +namespace YAMDCC.IPC.IO; + +/// +/// Wraps a object and reads from it. +/// +/// +/// Deserializes binary data sent by a +/// into a .NET CLR object specified by . +/// +/// +/// The reference type to deserialize data to. +/// +internal sealed class PipeStreamReader where T : class { /// - /// Wraps a object and reads from it. + /// Gets the underlying object. /// - /// - /// Deserializes binary data sent by a - /// into a .NET CLR object specified by . - /// - /// - /// The reference type to deserialize data to. - /// - internal sealed class PipeStreamReader where T : class - { - /// - /// Gets the underlying object. - /// - internal PipeStream BaseStream { get; private set; } + internal PipeStream BaseStream { get; private set; } - /// - /// Gets a value indicating whether the pipe is connected or not. - /// - internal bool IsConnected => BaseStream.IsConnected; + /// + /// Gets a value indicating whether the pipe is connected or not. + /// + internal bool IsConnected => BaseStream.IsConnected; - private const int SIZE_INT = sizeof(int); + private const int SIZE_INT = sizeof(int); - /// - /// Constructs a new object - /// that reads data from the given . - /// - /// - /// The pipe stream to read from. - /// - internal PipeStreamReader(PipeStream stream) - { - BaseStream = stream; - } + /// + /// Constructs a new object + /// that reads data from the given . + /// + /// + /// The pipe stream to read from. + /// + internal PipeStreamReader(PipeStream stream) + { + BaseStream = stream; + } - /// - /// Reads the next object from the pipe. - /// - /// - /// This method blocks until an object is - /// sent or the pipe is disconnected. - /// - /// - /// The next object read from the pipe, or - /// null if the pipe disconnected. - /// - /// - internal T ReadObject() - { - int len = ReadLength(); - return len == 0 ? default : ReadObject(len); - } + /// + /// Reads the next object from the pipe. + /// + /// + /// This method blocks until an object is + /// sent or the pipe is disconnected. + /// + /// + /// The next object read from the pipe, or + /// null if the pipe disconnected. + /// + /// + internal T ReadObject() + { + int len = ReadLength(); + return len == 0 ? default : ReadObject(len); + } - /// - /// Reads the length of the next message (in bytes) from the client. - /// - /// Number of bytes of data the client will be sending. - /// - /// - private int ReadLength() - { - byte[] lenbuf = new byte[SIZE_INT]; - int bytesRead = BaseStream.Read(lenbuf, 0, SIZE_INT); - return bytesRead == 0 - ? 0 - : bytesRead != SIZE_INT - ? throw new IOException($"Expected {SIZE_INT} bytes, but read {bytesRead}.") - : IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenbuf, 0)); - } + /// + /// Reads the length of the next message (in bytes) from the client. + /// + /// Number of bytes of data the client will be sending. + /// + /// + private int ReadLength() + { + byte[] lenbuf = new byte[SIZE_INT]; + int bytesRead = BaseStream.Read(lenbuf, 0, SIZE_INT); + return bytesRead == 0 + ? 0 + : bytesRead != SIZE_INT + ? throw new IOException($"Expected {SIZE_INT} bytes, but read {bytesRead}.") + : IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenbuf, 0)); + } - /// - private T ReadObject(int len) - { - byte[] data = new byte[len]; - int bytesRead = BaseStream.Read(data, 0, data.Length); - return bytesRead == len - ? MessagePackSerializer.Deserialize(data, Constants.SerializerOptions) - : throw new IOException($"Expected {len} bytes, but read {bytesRead}."); - } + /// + private T ReadObject(int len) + { + byte[] data = new byte[len]; + int bytesRead = BaseStream.Read(data, 0, data.Length); + return bytesRead == len + ? MessagePackSerializer.Deserialize(data, Constants.SerializerOptions) + : throw new IOException($"Expected {len} bytes, but read {bytesRead}."); } } diff --git a/YAMDCC.IPC/IO/PipeStreamWrapper.cs b/YAMDCC.IPC/IO/PipeStreamWrapper.cs index a6e822e..aa54218 100644 --- a/YAMDCC.IPC/IO/PipeStreamWrapper.cs +++ b/YAMDCC.IPC/IO/PipeStreamWrapper.cs @@ -2,151 +2,150 @@ using System.IO; using System.IO.Pipes; -namespace YAMDCC.IPC.IO +namespace YAMDCC.IPC.IO; + +/// +/// Wraps a object +/// to read and write .NET CLR objects. +/// +/// +/// The reference type to read from and write to the pipe. +/// +internal sealed class PipeStreamWrapper : PipeStreamWrapper + where TReadWrite : class { /// - /// Wraps a object - /// to read and write .NET CLR objects. + /// Constructs a new object + /// that reads from and writes to the given . /// - /// - /// The reference type to read from and write to the pipe. - /// - internal sealed class PipeStreamWrapper : PipeStreamWrapper - where TReadWrite : class - { - /// - /// Constructs a new object - /// that reads from and writes to the given . - /// - /// - /// The pipe stream to read from and write to. - /// - public PipeStreamWrapper(PipeStream stream) - : base(stream) { } - } + /// + /// The pipe stream to read from and write to. + /// + public PipeStreamWrapper(PipeStream stream) + : base(stream) { } +} +/// +/// Wraps a object +/// to read and write .NET CLR objects. +/// +/// +/// The reference type to read from the pipe. +/// +/// +/// The reference type to write to the pipe. +/// +internal class PipeStreamWrapper + where TRead : class + where TWrite : class +{ /// - /// Wraps a object - /// to read and write .NET CLR objects. + /// Gets the underlying object. /// - /// - /// The reference type to read from the pipe. - /// - /// - /// The reference type to write to the pipe. - /// - internal class PipeStreamWrapper - where TRead : class - where TWrite : class - { - /// - /// Gets the underlying object. - /// - internal PipeStream BaseStream { get; private set; } + internal PipeStream BaseStream { get; private set; } - /// - /// Gets a value indicating whether the - /// object is connected or not. - /// - /// - /// true if the - /// object is connected, otherwise false. - /// - internal bool IsConnected => BaseStream.IsConnected && _reader.IsConnected; + /// + /// Gets a value indicating whether the + /// object is connected or not. + /// + /// + /// true if the + /// object is connected, otherwise false. + /// + internal bool IsConnected => BaseStream.IsConnected && _reader.IsConnected; - /// - /// Gets a value indicating whether the - /// current stream supports read operations. - /// - /// - /// true if the stream supports read - /// operations, otherwise false. - /// - internal bool CanRead => BaseStream.CanRead; + /// + /// Gets a value indicating whether the + /// current stream supports read operations. + /// + /// + /// true if the stream supports read + /// operations, otherwise false. + /// + internal bool CanRead => BaseStream.CanRead; - /// - /// Gets a value indicating whether the current - /// stream supports write operations. - /// - /// - /// true if the stream supports write - /// operation, otherwise false. - /// - internal bool CanWrite => BaseStream.CanWrite; + /// + /// Gets a value indicating whether the current + /// stream supports write operations. + /// + /// + /// true if the stream supports write + /// operation, otherwise false. + /// + internal bool CanWrite => BaseStream.CanWrite; - private readonly PipeStreamReader _reader; - private readonly PipeStreamWriter _writer; + private readonly PipeStreamReader _reader; + private readonly PipeStreamWriter _writer; - /// - /// Constructs a new - /// object that reads from and writes to the given - /// . - /// - /// - /// The stream to read from and write to. - /// - internal PipeStreamWrapper(PipeStream stream) - { - BaseStream = stream; - _reader = new PipeStreamReader(BaseStream); - _writer = new PipeStreamWriter(BaseStream); - } + /// + /// Constructs a new + /// object that reads from and writes to the given + /// . + /// + /// + /// The stream to read from and write to. + /// + internal PipeStreamWrapper(PipeStream stream) + { + BaseStream = stream; + _reader = new PipeStreamReader(BaseStream); + _writer = new PipeStreamWriter(BaseStream); + } - /// - /// Reads the next object from the pipe. - /// - /// - /// This method blocks until an object - /// is sent or the pipe is disconnected. - /// - /// - /// The next object read from the pipe, or - /// null if the pipe disconnected. - /// - internal TRead ReadObject() - { - return _reader.ReadObject(); - } + /// + /// Reads the next object from the pipe. + /// + /// + /// This method blocks until an object + /// is sent or the pipe is disconnected. + /// + /// + /// The next object read from the pipe, or + /// null if the pipe disconnected. + /// + internal TRead ReadObject() + { + return _reader.ReadObject(); + } - /// - /// Writes an object to the pipe. - /// - /// - /// This method blocks until all data is sent. - /// - /// - /// Tne object to write to the pipe. - /// - internal void WriteObject(TWrite obj) - { - _writer.WriteObject(obj); - } + /// + /// Writes an object to the pipe. + /// + /// + /// This method blocks until all data is sent. + /// + /// + /// Tne object to write to the pipe. + /// + internal void WriteObject(TWrite obj) + { + _writer.WriteObject(obj); + } - /// - /// Waits for the other end of the pipe to read all sent bytes. - /// - /// - /// The pipe is closed. - /// - /// - /// The pipe does not support write operations. - /// - /// - /// The pipe is broken or another I/O error occurred. - /// - internal void WaitForPipeDrain() - { - _writer.WaitForPipeDrain(); - } + /// + /// Waits for the other end of the pipe to read all sent bytes. + /// + /// + /// The pipe is closed. + /// + /// + /// The pipe does not support write operations. + /// + /// + /// The pipe is broken or another I/O error occurred. + /// + internal void WaitForPipeDrain() + { + _writer.WaitForPipeDrain(); + } - /// - /// Closes the current stream and releases any - /// resources (such as sockets and file handles) - /// associated with the current stream. - /// - internal void Close() - { - BaseStream.Close(); - } + /// + /// Closes the current stream and releases any + /// resources (such as sockets and file handles) + /// associated with the current stream. + /// + internal void Close() + { + BaseStream.Close(); } } diff --git a/YAMDCC.IPC/IO/PipeStreamWriter.cs b/YAMDCC.IPC/IO/PipeStreamWriter.cs index 1a02469..9af56a2 100644 --- a/YAMDCC.IPC/IO/PipeStreamWriter.cs +++ b/YAMDCC.IPC/IO/PipeStreamWriter.cs @@ -3,65 +3,64 @@ using System.IO.Pipes; using System.Net; -namespace YAMDCC.IPC.IO +namespace YAMDCC.IPC.IO; + +/// +/// Wraps a object and writes to it. +/// +/// +/// Serializes .NET CLR objects specified by +/// into binary form and sends them over the named pipe for a +/// to read and deserialize. +/// +/// +/// The reference type to serialize. +/// +internal sealed class PipeStreamWriter where T : class { /// - /// Wraps a object and writes to it. + /// Gets the underlying object. + /// + internal PipeStream BaseStream { get; private set; } + + /// + /// Constructs a new + /// object that writes to given . + /// + /// + /// The named pipe to write to. + /// + internal PipeStreamWriter(PipeStream stream) + { + BaseStream = stream; + } + + /// + /// Writes an object to the pipe. /// /// - /// Serializes .NET CLR objects specified by - /// into binary form and sends them over the named pipe for a - /// to read and deserialize. + /// This method blocks until all data is sent. /// - /// - /// The reference type to serialize. - /// - internal sealed class PipeStreamWriter where T : class + /// + /// The object to write to the pipe. + /// + /// + internal void WriteObject(T obj) { - /// - /// Gets the underlying object. - /// - internal PipeStream BaseStream { get; private set; } - - /// - /// Constructs a new - /// object that writes to given . - /// - /// - /// The named pipe to write to. - /// - internal PipeStreamWriter(PipeStream stream) - { - BaseStream = stream; - } - - /// - /// Writes an object to the pipe. - /// - /// - /// This method blocks until all data is sent. - /// - /// - /// The object to write to the pipe. - /// - /// - internal void WriteObject(T obj) + if (obj is not null) { - if (obj is not null) - { - byte[] data = MessagePackSerializer.Serialize(obj, Constants.SerializerOptions); - byte[] lenBuf = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(data.Length)); + byte[] data = MessagePackSerializer.Serialize(obj, Constants.SerializerOptions); + byte[] lenBuf = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(data.Length)); - BaseStream.Write(lenBuf, 0, lenBuf.Length); - BaseStream.Write(data, 0, data.Length); - BaseStream.Flush(); - } + BaseStream.Write(lenBuf, 0, lenBuf.Length); + BaseStream.Write(data, 0, data.Length); + BaseStream.Flush(); } + } - /// - internal void WaitForPipeDrain() - { - BaseStream.WaitForPipeDrain(); - } + /// + internal void WaitForPipeDrain() + { + BaseStream.WaitForPipeDrain(); } } diff --git a/YAMDCC.IPC/NamedPipeClient.cs b/YAMDCC.IPC/NamedPipeClient.cs index 79dd535..15a95fa 100644 --- a/YAMDCC.IPC/NamedPipeClient.cs +++ b/YAMDCC.IPC/NamedPipeClient.cs @@ -4,313 +4,312 @@ using YAMDCC.IPC.IO; using YAMDCC.IPC.Threading; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Wraps a . +/// +/// +/// The reference type to read from and write to the named pipe. +/// +public class NamedPipeClient : NamedPipeClient + where TReadWrite : class { /// - /// Wraps a . + /// Constructs a new to + /// connect to the specified + /// by . /// - /// - /// The reference type to read from and write to the named pipe. - /// - public class NamedPipeClient : NamedPipeClient - where TReadWrite : class + /// + /// The name of the named pipe server. + /// + public NamedPipeClient(string pipeName) : base(pipeName) { } +} + +/// +/// Wraps a . +/// +/// +/// The reference type to read from the named pipe. +/// +/// +/// The reference type to write to the named pipe. +/// +public class NamedPipeClient : IDisposable + where TRead : class + where TWrite : class +{ + /// + /// Gets or sets whether the client should attempt to reconnect when + /// the pipe breaks due to an error or the other end terminating the + /// connection. + /// + /// + /// The default value is true. + /// + public bool AutoReconnect { get; set; } = true; + + /// + /// Gets or sets how long the client + /// waits between a reconnection attempt. + /// + /// + /// The default value is 0. + /// + public int AutoReconnectDelay { get; set; } + + /// + /// Invoked whenever a message is received from the server. + /// + public event EventHandler> ServerMessage; + + /// + /// Invoked when the client connects to a server. + /// + public event EventHandler> Connected; + + /// + /// Invoked when the client disconnects from the server + /// (e.g. when the pipe is closed or broken). + /// + public event EventHandler> Disconnected; + + /// + /// Invoked whenever an exception is thrown during + /// a read or write operation on the named pipe. + /// + public event EventHandler> Error; + + private readonly string _pipeName; + private NamedPipeConnection _connection; + + private readonly AutoResetEvent _connected = new(false); + private readonly AutoResetEvent _disconnected = new(false); + + private volatile bool _closedExplicitly; + + private bool _disposed; + + /// + /// Constructs a new to + /// connect to the + /// specified by . + /// + /// + /// The name of the named pipe server. + /// + public NamedPipeClient(string pipeName) { - /// - /// Constructs a new to - /// connect to the specified - /// by . - /// - /// - /// The name of the named pipe server. - /// - public NamedPipeClient(string pipeName) : base(pipeName) { } + _pipeName = pipeName; + AutoReconnect = true; } /// - /// Wraps a . + /// Connects to the named pipe server asynchronously. /// - /// - /// The reference type to read from the named pipe. - /// - /// - /// The reference type to write to the named pipe. - /// - public class NamedPipeClient : IDisposable - where TRead : class - where TWrite : class + /// + /// This method returns immediately, possibly before the connection + /// has been established. Use to + /// wait until the connection to the server is established. + /// + public void Start() { - /// - /// Gets or sets whether the client should attempt to reconnect when - /// the pipe breaks due to an error or the other end terminating the - /// connection. - /// - /// - /// The default value is true. - /// - public bool AutoReconnect { get; set; } = true; - - /// - /// Gets or sets how long the client - /// waits between a reconnection attempt. - /// - /// - /// The default value is 0. - /// - public int AutoReconnectDelay { get; set; } - - /// - /// Invoked whenever a message is received from the server. - /// - public event EventHandler> ServerMessage; - - /// - /// Invoked when the client connects to a server. - /// - public event EventHandler> Connected; - - /// - /// Invoked when the client disconnects from the server - /// (e.g. when the pipe is closed or broken). - /// - public event EventHandler> Disconnected; - - /// - /// Invoked whenever an exception is thrown during - /// a read or write operation on the named pipe. - /// - public event EventHandler> Error; - - private readonly string _pipeName; - private NamedPipeConnection _connection; - - private readonly AutoResetEvent _connected = new(false); - private readonly AutoResetEvent _disconnected = new(false); - - private volatile bool _closedExplicitly; - - private bool _disposed; - - /// - /// Constructs a new to - /// connect to the - /// specified by . - /// - /// - /// The name of the named pipe server. - /// - public NamedPipeClient(string pipeName) - { - _pipeName = pipeName; - AutoReconnect = true; - } + _closedExplicitly = false; + Worker worker = new(); + worker.Error += WorkerOnError; + worker.DoWork(ListenSync); + } - /// - /// Connects to the named pipe server asynchronously. - /// - /// - /// This method returns immediately, possibly before the connection - /// has been established. Use to - /// wait until the connection to the server is established. - /// - public void Start() - { - _closedExplicitly = false; - Worker worker = new(); - worker.Error += WorkerOnError; - worker.DoWork(ListenSync); - } + /// + /// Closes the named pipe. + /// + public void Stop() + { + _closedExplicitly = true; + _connection?.Close(); + } - /// - /// Closes the named pipe. - /// - public void Stop() - { - _closedExplicitly = true; - _connection?.Close(); - } + /// + /// Sends a message to the server over a named pipe. + /// + /// + /// The message to send to the server. + /// + public void PushMessage(TWrite message) + { + _connection?.PushMessage(message); + } - /// - /// Sends a message to the server over a named pipe. - /// - /// - /// The message to send to the server. - /// - public void PushMessage(TWrite message) - { - _connection?.PushMessage(message); - } + #region Wait for connection/disconnection - #region Wait for connection/disconnection + /// + /// Blocks the current thread until a connection + /// to the named pipe server is established. + /// + public bool WaitForConnection() + { + return _connected.WaitOne(); + } - /// - /// Blocks the current thread until a connection - /// to the named pipe server is established. - /// - public bool WaitForConnection() - { - return _connected.WaitOne(); - } + /// + /// Blocks the current thread until a connection to the + /// named pipe server is established, waiting until at + /// most before returning. + /// + /// + /// The timeout, in milliseconds, to wait for the server connection. + /// + /// + /// true if the server connection was established + /// before the timeout, otherwise false. + /// + public bool WaitForConnection(int timeout) + { + return _connected.WaitOne(timeout); + } - /// - /// Blocks the current thread until a connection to the - /// named pipe server is established, waiting until at - /// most before returning. - /// - /// - /// The timeout, in milliseconds, to wait for the server connection. - /// - /// - /// true if the server connection was established - /// before the timeout, otherwise false. - /// - public bool WaitForConnection(int timeout) - { - return _connected.WaitOne(timeout); - } + /// + /// Blocks the current thread until a connection to the + /// named pipe server is established, waiting until at + /// most before returning. + /// + /// + /// A representing the time + /// (in milliseconds) to wait for the server connection. + /// + /// + /// true if the server connection was established + /// before the timeout, otherwise false. + /// + public bool WaitForConnection(TimeSpan timeout) + { + return _connected.WaitOne(timeout); + } - /// - /// Blocks the current thread until a connection to the - /// named pipe server is established, waiting until at - /// most before returning. - /// - /// - /// A representing the time - /// (in milliseconds) to wait for the server connection. - /// - /// - /// true if the server connection was established - /// before the timeout, otherwise false. - /// - public bool WaitForConnection(TimeSpan timeout) - { - return _connected.WaitOne(timeout); - } + /// + /// Blocks the current thread until the client + /// disconnects from the named pipe server. + /// + public bool WaitForDisconnection() + { + return _disconnected.WaitOne(); + } - /// - /// Blocks the current thread until the client - /// disconnects from the named pipe server. - /// - public bool WaitForDisconnection() - { - return _disconnected.WaitOne(); - } + /// + /// Blocks the current thread until the client disconnects + /// from the named pipe server, waiting until at most + /// before returning. + /// + /// + /// The timeout, in milliseconds, to wait for the server to disconnect. + /// + /// + /// true if the client disconnected + /// before the timeout, otherwise false. + /// + public bool WaitForDisconnection(int timeout) + { + return _disconnected.WaitOne(timeout); + } - /// - /// Blocks the current thread until the client disconnects - /// from the named pipe server, waiting until at most - /// before returning. - /// - /// - /// The timeout, in milliseconds, to wait for the server to disconnect. - /// - /// - /// true if the client disconnected - /// before the timeout, otherwise false. - /// - public bool WaitForDisconnection(int timeout) - { - return _disconnected.WaitOne(timeout); - } + /// + /// Blocks the current thread until the client disconnects + /// from the named pipe server, waiting until at most + /// before returning. + /// + /// + /// A representing the time + /// (in milliseconds) to wait for the server to disconnect. + /// + /// + /// true if the client disconnected + /// before the timeout, otherwise false. + /// + public bool WaitForDisconnection(TimeSpan timeout) + { + return _disconnected.WaitOne(timeout); + } - /// - /// Blocks the current thread until the client disconnects - /// from the named pipe server, waiting until at most - /// before returning. - /// - /// - /// A representing the time - /// (in milliseconds) to wait for the server to disconnect. - /// - /// - /// true if the client disconnected - /// before the timeout, otherwise false. - /// - public bool WaitForDisconnection(TimeSpan timeout) - { - return _disconnected.WaitOne(timeout); - } + #endregion + + #region Private methods + private void ListenSync() + { + // Get the name of the data pipe that should be used from now on by this NamedPipeClient + PipeStreamWrapper handshake = PipeClientFactory.Connect(_pipeName); + string dataPipeName = handshake.ReadObject(); + handshake.Close(); + + // Connect to the actual data pipe + NamedPipeClientStream dataPipe = PipeClientFactory.CreateAndConnectPipe(dataPipeName); + + // Create a Connection object for the data pipe + _connection = ConnectionFactory.CreateConnection(dataPipe); + _connection.Disconnected += OnDisconnected; + _connection.ReceiveMessage += OnReceiveMessage; + _connection.Error += ConnectionOnError; + _connection.Open(); + + _connected.Set(); + Connected?.Invoke(this, new PipeConnectionEventArgs(_connection)); + } + + private void OnDisconnected(object sender, PipeConnectionEventArgs e) + { + Disconnected?.Invoke(sender, e); - #endregion + _disconnected.Set(); - #region Private methods - private void ListenSync() + // Reconnect + if (AutoReconnect && !_closedExplicitly) { - // Get the name of the data pipe that should be used from now on by this NamedPipeClient - PipeStreamWrapper handshake = PipeClientFactory.Connect(_pipeName); - string dataPipeName = handshake.ReadObject(); - handshake.Close(); - - // Connect to the actual data pipe - NamedPipeClientStream dataPipe = PipeClientFactory.CreateAndConnectPipe(dataPipeName); - - // Create a Connection object for the data pipe - _connection = ConnectionFactory.CreateConnection(dataPipe); - _connection.Disconnected += OnDisconnected; - _connection.ReceiveMessage += OnReceiveMessage; - _connection.Error += ConnectionOnError; - _connection.Open(); - - _connected.Set(); - Connected?.Invoke(this, new PipeConnectionEventArgs(_connection)); + Thread.Sleep(AutoReconnectDelay); + Start(); } + } - private void OnDisconnected(object sender, PipeConnectionEventArgs e) - { - Disconnected?.Invoke(sender, e); + private void OnReceiveMessage(object sender, PipeMessageEventArgs e) + { + ServerMessage?.Invoke(sender, e); + } - _disconnected.Set(); + /// + /// Invoked on the UI thread. + /// + private void ConnectionOnError(object sender, PipeErrorEventArgs e) + { + Error?.Invoke(sender, e); + } - // Reconnect - if (AutoReconnect && !_closedExplicitly) - { - Thread.Sleep(AutoReconnectDelay); - Start(); - } - } - private void OnReceiveMessage(object sender, PipeMessageEventArgs e) - { - ServerMessage?.Invoke(sender, e); - } - - /// - /// Invoked on the UI thread. - /// - private void ConnectionOnError(object sender, PipeErrorEventArgs e) - { - Error?.Invoke(sender, e); - } + /// + /// Invoked on the UI thread. + /// + private void WorkerOnError(object sender, WorkerErrorEventArgs e) + { + Error?.Invoke(sender, new PipeErrorEventArgs(_connection, e.Exception)); + } + #endregion + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - /// - /// Invoked on the UI thread. - /// - private void WorkerOnError(object sender, WorkerErrorEventArgs e) + private void Dispose(bool disposing) + { + if (_disposed) { - Error?.Invoke(sender, new PipeErrorEventArgs(_connection, e.Exception)); + return; } - #endregion - public void Dispose() + if (disposing) { - Dispose(true); - GC.SuppressFinalize(this); + _connected.Dispose(); + _disconnected.Dispose(); } - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _connected.Dispose(); - _disconnected.Dispose(); - } - - _disposed = true; - } + _disposed = true; } } diff --git a/YAMDCC.IPC/NamedPipeConnection.cs b/YAMDCC.IPC/NamedPipeConnection.cs index dd5d3c6..20733f4 100644 --- a/YAMDCC.IPC/NamedPipeConnection.cs +++ b/YAMDCC.IPC/NamedPipeConnection.cs @@ -6,211 +6,210 @@ using YAMDCC.IPC.IO; using YAMDCC.IPC.Threading; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Represents a connection between a named pipe client and server. +/// +/// +/// The reference type to read from the named pipe. +/// +/// +/// The reference type to write to the named pipe. +/// +public class NamedPipeConnection : IDisposable + where TRead : class + where TWrite : class { /// - /// Represents a connection between a named pipe client and server. + /// Gets the connection's unique identifier. /// - /// - /// The reference type to read from the named pipe. - /// - /// - /// The reference type to write to the named pipe. - /// - public class NamedPipeConnection : IDisposable - where TRead : class - where TWrite : class - { - /// - /// Gets the connection's unique identifier. - /// - public int ID { get; } + public int ID { get; } - /// - /// Gets the connection's name. - /// - public string Name { get; } + /// + /// Gets the connection's name. + /// + public string Name { get; } - /// - /// Gets the connection's handle. - /// - public SafeHandle Handle => _streamWrapper.BaseStream.SafePipeHandle; + /// + /// Gets the connection's handle. + /// + public SafeHandle Handle => _streamWrapper.BaseStream.SafePipeHandle; - /// - /// Gets a value indicating whether the pipe is connected or not. - /// - public bool IsConnected => _streamWrapper.IsConnected; + /// + /// Gets a value indicating whether the pipe is connected or not. + /// + public bool IsConnected => _streamWrapper.IsConnected; - /// - /// Invoked when the named pipe connection terminates. - /// - public event EventHandler> Disconnected; + /// + /// Invoked when the named pipe connection terminates. + /// + public event EventHandler> Disconnected; - /// - /// Invoked whenever a message is received from the other end of the pipe. - /// - public event EventHandler> ReceiveMessage; + /// + /// Invoked whenever a message is received from the other end of the pipe. + /// + public event EventHandler> ReceiveMessage; - /// - /// Invoked when an exception is thrown during any read/write operation over the named pipe. - /// - public event EventHandler> Error; + /// + /// Invoked when an exception is thrown during any read/write operation over the named pipe. + /// + public event EventHandler> Error; - private readonly PipeStreamWrapper _streamWrapper; + private readonly PipeStreamWrapper _streamWrapper; - private readonly AutoResetEvent _writeSignal = new(false); - private readonly BlockingCollection _writeQueue = new(); + private readonly AutoResetEvent _writeSignal = new(false); + private readonly BlockingCollection _writeQueue = []; - private bool _notifiedSucceeded; + private bool _notifiedSucceeded; - private bool _disposed; + private bool _disposed; - internal NamedPipeConnection(int id, string name, PipeStream serverStream) - { - ID = id; - Name = name; - _streamWrapper = new PipeStreamWrapper(serverStream); - } + internal NamedPipeConnection(int id, string name, PipeStream serverStream) + { + ID = id; + Name = name; + _streamWrapper = new PipeStreamWrapper(serverStream); + } - /// - /// Adds the specified message to the write queue. - /// - /// - /// The message will be written to the named pipe by the - /// background thread at the next available opportunity. - /// - /// - /// The message to write to the named pipe. - /// - public bool PushMessage(TWrite message) + /// + /// Adds the specified message to the write queue. + /// + /// + /// The message will be written to the named pipe by the + /// background thread at the next available opportunity. + /// + /// + /// The message to write to the named pipe. + /// + public bool PushMessage(TWrite message) + { + try { - try - { - return _writeQueue.TryAdd(message) && _writeSignal.Set(); - } - // catch the exception that occurs when trying to add an item to - // the write queue when the named pipe connection has been stopped - catch (InvalidOperationException) - { - return false; - } + return _writeQueue.TryAdd(message) && _writeSignal.Set(); } - - /// - /// Begins reading from and writing to the - /// named pipe on a background thread. - /// - /// - /// This method returns immediately. - /// - internal void Open() + // catch the exception that occurs when trying to add an item to + // the write queue when the named pipe connection has been stopped + catch (InvalidOperationException) { - Worker readWorker = new(); - readWorker.Succeeded += OnSucceeded; - readWorker.Error += OnError; - readWorker.DoWork(ReadPipe); - - Worker writeWorker = new(); - writeWorker.Succeeded += OnSucceeded; - writeWorker.Error += OnError; - writeWorker.DoWork(WritePipe); + return false; } + } - /// - /// Closes the named pipe connection and - /// underlying . - /// - /// - /// Invoked on the background thread. - /// - internal void Close() - { - _streamWrapper.Close(); - _writeQueue.CompleteAdding(); - _writeSignal.Set(); - } + /// + /// Begins reading from and writing to the + /// named pipe on a background thread. + /// + /// + /// This method returns immediately. + /// + internal void Open() + { + Worker readWorker = new(); + readWorker.Succeeded += OnSucceeded; + readWorker.Error += OnError; + readWorker.DoWork(ReadPipe); + + Worker writeWorker = new(); + writeWorker.Succeeded += OnSucceeded; + writeWorker.Error += OnError; + writeWorker.DoWork(WritePipe); + } - /// - /// Invoked on the UI thread. - /// - private void OnSucceeded(object sender, EventArgs e) + /// + /// Closes the named pipe connection and + /// underlying . + /// + /// + /// Invoked on the background thread. + /// + internal void Close() + { + _streamWrapper.Close(); + _writeQueue.CompleteAdding(); + _writeSignal.Set(); + } + + /// + /// Invoked on the UI thread. + /// + private void OnSucceeded(object sender, EventArgs e) + { + // Only notify observers once + if (_notifiedSucceeded) { - // Only notify observers once - if (_notifiedSucceeded) - { - return; - } + return; + } - _notifiedSucceeded = true; + _notifiedSucceeded = true; - PipeConnectionEventArgs e2 = new(this); - Disconnected?.Invoke(sender, e2); - } + PipeConnectionEventArgs e2 = new(this); + Disconnected?.Invoke(sender, e2); + } - /// - /// Invoked on the UI thread. - /// - private void OnError(object sender, WorkerErrorEventArgs e) - { - Error?.Invoke(sender, new PipeErrorEventArgs(this, e.Exception)); - } + /// + /// Invoked on the UI thread. + /// + private void OnError(object sender, WorkerErrorEventArgs e) + { + Error?.Invoke(sender, new PipeErrorEventArgs(this, e.Exception)); + } - /// - /// Invoked on the background thread. - /// - private void ReadPipe() + /// + /// Invoked on the background thread. + /// + private void ReadPipe() + { + while (IsConnected && _streamWrapper.CanRead) { - while (IsConnected && _streamWrapper.CanRead) + TRead obj = _streamWrapper.ReadObject(); + if (obj is null) { - TRead obj = _streamWrapper.ReadObject(); - if (obj is null) - { - Close(); - return; - } - - ReceiveMessage?.Invoke(this, - new PipeMessageEventArgs(this, obj)); + Close(); + return; } + + ReceiveMessage?.Invoke(this, + new PipeMessageEventArgs(this, obj)); } + } - /// - /// Invoked on the background thread. - /// - private void WritePipe() + /// + /// Invoked on the background thread. + /// + private void WritePipe() + { + while (IsConnected && _streamWrapper.CanWrite) { - while (IsConnected && _streamWrapper.CanWrite) + _writeSignal.WaitOne(); + while (_writeQueue.TryTake(out TWrite obj) || _writeQueue.Count > 0) { - _writeSignal.WaitOne(); - while (_writeQueue.TryTake(out TWrite obj) || _writeQueue.Count > 0) - { - _streamWrapper.WriteObject(obj); - _streamWrapper.WaitForPipeDrain(); - } + _streamWrapper.WriteObject(obj); + _streamWrapper.WaitForPipeDrain(); } } + } - public void Dispose() + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) { - Dispose(true); - GC.SuppressFinalize(this); + return; } - private void Dispose(bool disposing) + if (disposing) { - if (_disposed) - { - return; - } - - if (disposing) - { - Close(); - _writeSignal.Dispose(); - _writeQueue.Dispose(); - } - - _disposed = true; + Close(); + _writeSignal.Dispose(); + _writeQueue.Dispose(); } + + _disposed = true; } } diff --git a/YAMDCC.IPC/NamedPipeServer.cs b/YAMDCC.IPC/NamedPipeServer.cs index 0ab7977..ef74245 100644 --- a/YAMDCC.IPC/NamedPipeServer.cs +++ b/YAMDCC.IPC/NamedPipeServer.cs @@ -5,402 +5,401 @@ using YAMDCC.IPC.IO; using YAMDCC.IPC.Threading; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Wraps a and provides +/// multiple simultaneous client connection handling. +/// +/// +/// The reference type to read from and write to the named pipe. +/// +public class NamedPipeServer : NamedPipeServer + where TReadWrite : class { /// - /// Wraps a and provides - /// multiple simultaneous client connection handling. + /// Constructs a new + /// object that listens for client connections on the given + /// . /// - /// - /// The reference type to read from and write to the named pipe. - /// - public class NamedPipeServer : NamedPipeServer - where TReadWrite : class - { - /// - /// Constructs a new - /// object that listens for client connections on the given - /// . - /// - /// - public NamedPipeServer(string pipeName) - : base(pipeName) { } - - /// - /// Constructs a new - /// object that listens for client connections on the given - /// . - /// - /// - public NamedPipeServer(string pipeName, PipeSecurity security, int bufferSize = 0) - : base(pipeName, security, bufferSize) { } - } + /// + public NamedPipeServer(string pipeName) + : base(pipeName) { } /// - /// Wraps a and provides - /// multiple simultaneous client connection handling. + /// Constructs a new + /// object that listens for client connections on the given + /// . /// - /// - /// The reference type to read from the named pipe. - /// - /// - /// The reference type to write to the named pipe. - /// - public class NamedPipeServer - where TRead : class - where TWrite : class + /// + public NamedPipeServer(string pipeName, PipeSecurity security, int bufferSize = 0) + : base(pipeName, security, bufferSize) { } +} + +/// +/// Wraps a and provides +/// multiple simultaneous client connection handling. +/// +/// +/// The reference type to read from the named pipe. +/// +/// +/// The reference type to write to the named pipe. +/// +public class NamedPipeServer + where TRead : class + where TWrite : class +{ + /// + /// Invoked whenever a client connects to the server. + /// + public event EventHandler> ClientConnected; + + /// + /// Invoked whenever a client disconnects from the server. + /// + public event EventHandler> ClientDisconnected; + + /// + /// Invoked whenever a client sends a message to the server. + /// + public event EventHandler> ClientMessage; + + /// + /// Invoked whenever an exception is thrown + /// during a read or write operation. + /// + public event EventHandler> Error; + + private readonly string _pipeName; + private readonly int _bufferSize; + private readonly PipeSecurity _security; + private readonly List> _connections = []; + + private int _nextPipeId; + + private volatile bool _shouldKeepRunning; + + /// + /// Constructs a new + /// object that listens for client connections on the given + /// . + /// + /// + /// The name of the pipe to listen on. + /// + public NamedPipeServer(string pipeName) { - /// - /// Invoked whenever a client connects to the server. - /// - public event EventHandler> ClientConnected; - - /// - /// Invoked whenever a client disconnects from the server. - /// - public event EventHandler> ClientDisconnected; - - /// - /// Invoked whenever a client sends a message to the server. - /// - public event EventHandler> ClientMessage; - - /// - /// Invoked whenever an exception is thrown - /// during a read or write operation. - /// - public event EventHandler> Error; - - private readonly string _pipeName; - private readonly int _bufferSize; - private readonly PipeSecurity _security; - private readonly List> _connections = []; - - private int _nextPipeId; - - private volatile bool _shouldKeepRunning; - - /// - /// Constructs a new - /// object that listens for client connections on the given - /// . - /// - /// - /// The name of the pipe to listen on. - /// - public NamedPipeServer(string pipeName) - { - _pipeName = pipeName; - } + _pipeName = pipeName; + } - /// - /// An object that determine the access control - /// and audit security for the pipe. - /// - /// - /// The size of the input and output buffer. - /// Use 0 for the default buffer size. - /// - /// - public NamedPipeServer(string pipeName, PipeSecurity security, int bufferSize = 0) - { - _pipeName = pipeName; - _security = security; - _bufferSize = bufferSize; - } + /// + /// An object that determine the access control + /// and audit security for the pipe. + /// + /// + /// The size of the input and output buffer. + /// Use 0 for the default buffer size. + /// + /// + public NamedPipeServer(string pipeName, PipeSecurity security, int bufferSize = 0) + { + _pipeName = pipeName; + _security = security; + _bufferSize = bufferSize; + } - /// - /// Begins listening for client connections - /// in a separate background thread. - /// - /// - /// This method returns immediately. - /// - public void Start() - { - _shouldKeepRunning = true; - Worker worker = new(); - worker.Error += WorkerOnError; - worker.DoWork(ListenSync); - } + /// + /// Begins listening for client connections + /// in a separate background thread. + /// + /// + /// This method returns immediately. + /// + public void Start() + { + _shouldKeepRunning = true; + Worker worker = new(); + worker.Error += WorkerOnError; + worker.DoWork(ListenSync); + } - /// - /// Sends a message to all connected clients asynchronously. - /// - /// - /// This method returns immediately, possibly before - /// the message has been sent to all clients. - /// - /// - /// The message to send to the clients. - /// - public void PushMessage(TWrite message) + /// + /// Sends a message to all connected clients asynchronously. + /// + /// + /// This method returns immediately, possibly before + /// the message has been sent to all clients. + /// + /// + /// The message to send to the clients. + /// + public void PushMessage(TWrite message) + { + lock (_connections) { - lock (_connections) + foreach (NamedPipeConnection client in _connections) { - foreach (NamedPipeConnection client in _connections) - { - client.PushMessage(message); - } + client.PushMessage(message); } } + } - /// - /// Sends a message to a specified client asynchronously. - /// - /// - /// The message to send to the client. - /// - /// - /// The client ID to send the message to. - /// - /// - public void PushMessage(TWrite message, int targetId) + /// + /// Sends a message to a specified client asynchronously. + /// + /// + /// The message to send to the client. + /// + /// + /// The client ID to send the message to. + /// + /// + public void PushMessage(TWrite message, int targetId) + { + lock (_connections) { - lock (_connections) + // Can we speed this up with Linq or does that add overhead? + foreach (NamedPipeConnection client in _connections) { - // Can we speed this up with Linq or does that add overhead? - foreach (NamedPipeConnection client in _connections) + if (client.ID == targetId) { - if (client.ID == targetId) - { - client.PushMessage(message); - break; - } + client.PushMessage(message); + break; } } } + } - /// - /// Sends a message to the specified clients asynchronously. - /// - /// - /// An array of client IDs to send the message to. - /// - /// - public void PushMessage(TWrite message, int[] targetIds) - { - PushMessage(message, targetIds.ToList()); - } + /// + /// Sends a message to the specified clients asynchronously. + /// + /// + /// An array of client IDs to send the message to. + /// + /// + public void PushMessage(TWrite message, int[] targetIds) + { + PushMessage(message, targetIds.ToList()); + } - /// - /// A list of client IDs to send the message to. - /// - /// - public void PushMessage(TWrite message, List targetIds) + /// + /// A list of client IDs to send the message to. + /// + /// + public void PushMessage(TWrite message, List targetIds) + { + lock (_connections) { - lock (_connections) + // Can we speed this up with Linq or does that add overhead? + foreach (NamedPipeConnection client in _connections) { - // Can we speed this up with Linq or does that add overhead? - foreach (NamedPipeConnection client in _connections) + if (targetIds.Contains(client.ID)) { - if (targetIds.Contains(client.ID)) - { - client.PushMessage(message); - } + client.PushMessage(message); } } } + } - /// - /// The client name to send the message to. - /// - /// - public void PushMessage(TWrite message, string targetName) + /// + /// The client name to send the message to. + /// + /// + public void PushMessage(TWrite message, string targetName) + { + lock (_connections) { - lock (_connections) + // Can we speed this up with Linq or does that add overhead? + foreach (NamedPipeConnection client in _connections) { - // Can we speed this up with Linq or does that add overhead? - foreach (NamedPipeConnection client in _connections) + if (client.Name == targetName) { - if (client.Name == targetName) - { - client.PushMessage(message); - break; - } + client.PushMessage(message); + break; } } } + } - /// - /// A list of client names to send the message to. - /// - /// - public void PushMessage(TWrite message, List targetNames) + /// + /// A list of client names to send the message to. + /// + /// + public void PushMessage(TWrite message, List targetNames) + { + lock (_connections) { - lock (_connections) + foreach (NamedPipeConnection client in _connections) { - foreach (NamedPipeConnection client in _connections) + if (targetNames.Contains(client.Name)) { - if (targetNames.Contains(client.Name)) - { - client.PushMessage(message); - } + client.PushMessage(message); } } } + } - /// - /// Closes all open client connections and stops listening for new ones. - /// - public void Stop() - { - _shouldKeepRunning = false; + /// + /// Closes all open client connections and stops listening for new ones. + /// + public void Stop() + { + _shouldKeepRunning = false; - lock (_connections) + lock (_connections) + { + foreach (NamedPipeConnection client in _connections.ToArray()) { - foreach (NamedPipeConnection client in _connections.ToArray()) - { - client.Close(); - } + client.Close(); } - - // If background thread is still listening for a client to connect, - // initiate a dummy connection that will allow the thread to exit. - NamedPipeClient dummyClient = new(_pipeName); - dummyClient.Start(); - dummyClient.WaitForConnection(TimeSpan.FromSeconds(2)); - dummyClient.Stop(); - dummyClient.WaitForDisconnection(TimeSpan.FromSeconds(2)); } - #region Private methods + // If background thread is still listening for a client to connect, + // initiate a dummy connection that will allow the thread to exit. + NamedPipeClient dummyClient = new(_pipeName); + dummyClient.Start(); + dummyClient.WaitForConnection(TimeSpan.FromSeconds(2)); + dummyClient.Stop(); + dummyClient.WaitForDisconnection(TimeSpan.FromSeconds(2)); + } - private void ListenSync() + #region Private methods + + private void ListenSync() + { + while (_shouldKeepRunning) { - while (_shouldKeepRunning) - { - WaitForConnection(); - } + WaitForConnection(); } + } - private void WaitForConnection() - { - NamedPipeServerStream handshakePipe = null; - NamedPipeServerStream dataPipe = null; - NamedPipeConnection connection = null; + private void WaitForConnection() + { + NamedPipeServerStream handshakePipe = null; + NamedPipeServerStream dataPipe = null; + NamedPipeConnection connection = null; - string connectionPipeName = GetNextConnectionPipeName(); + string connectionPipeName = GetNextConnectionPipeName(); - try - { - dataPipe = CreatePipe(connectionPipeName); + try + { + dataPipe = CreatePipe(connectionPipeName); - // Send the client the name of the data pipe to use - handshakePipe = CreateAndConnectPipe(); + // Send the client the name of the data pipe to use + handshakePipe = CreateAndConnectPipe(); - PipeStreamWrapper handshakeWrapper = new(handshakePipe); + PipeStreamWrapper handshakeWrapper = new(handshakePipe); - handshakeWrapper.WriteObject(connectionPipeName); - handshakeWrapper.WaitForPipeDrain(); - handshakeWrapper.Close(); + handshakeWrapper.WriteObject(connectionPipeName); + handshakeWrapper.WaitForPipeDrain(); + handshakeWrapper.Close(); - // Wait for the client to connect to the data pipe - dataPipe.WaitForConnection(); + // Wait for the client to connect to the data pipe + dataPipe.WaitForConnection(); - // Add the client's connection to the list of connections - connection = ConnectionFactory.CreateConnection(dataPipe); - connection.ReceiveMessage += ClientOnReceiveMessage; - connection.Disconnected += ClientOnDisconnected; - connection.Error += ConnectionOnError; - connection.Open(); + // Add the client's connection to the list of connections + connection = ConnectionFactory.CreateConnection(dataPipe); + connection.ReceiveMessage += ClientOnReceiveMessage; + connection.Disconnected += ClientOnDisconnected; + connection.Error += ConnectionOnError; + connection.Open(); - lock (_connections) { _connections.Add(connection); } + lock (_connections) { _connections.Add(connection); } - PipeConnectionEventArgs e = new(connection); + PipeConnectionEventArgs e = new(connection); - ClientOnConnected(this, e); - } - // Catch the IOException that is raised if the pipe is broken or disconnected. - catch (Exception ex) - { - Console.Error.WriteLine($"Named pipe is broken or disconnected: {ex}"); + ClientOnConnected(this, e); + } + // Catch the IOException that is raised if the pipe is broken or disconnected. + catch (Exception ex) + { + Console.Error.WriteLine($"Named pipe is broken or disconnected: {ex}"); - Cleanup(handshakePipe); - Cleanup(dataPipe); + Cleanup(handshakePipe); + Cleanup(dataPipe); - PipeConnectionEventArgs e = new(connection); + PipeConnectionEventArgs e = new(connection); - ClientOnDisconnected(this, e); - } + ClientOnDisconnected(this, e); } + } - private NamedPipeServerStream CreateAndConnectPipe() - { - return _security == null - ? PipeServerFactory.CreateAndConnectPipe(_pipeName) - : PipeServerFactory.CreateAndConnectPipe(_pipeName, _bufferSize, _security); - } + private NamedPipeServerStream CreateAndConnectPipe() + { + return _security == null + ? PipeServerFactory.CreateAndConnectPipe(_pipeName) + : PipeServerFactory.CreateAndConnectPipe(_pipeName, _bufferSize, _security); + } - private NamedPipeServerStream CreatePipe(string connectionPipeName) - { - return _security == null - ? PipeServerFactory.CreatePipe(connectionPipeName) - : PipeServerFactory.CreatePipe(connectionPipeName, _bufferSize, _security); - } + private NamedPipeServerStream CreatePipe(string connectionPipeName) + { + return _security == null + ? PipeServerFactory.CreatePipe(connectionPipeName) + : PipeServerFactory.CreatePipe(connectionPipeName, _bufferSize, _security); + } - private void ClientOnConnected(object sender, PipeConnectionEventArgs e) - { - ClientConnected?.Invoke(sender, e); - } + private void ClientOnConnected(object sender, PipeConnectionEventArgs e) + { + ClientConnected?.Invoke(sender, e); + } - private void ClientOnReceiveMessage(object sender, PipeMessageEventArgs e) + private void ClientOnReceiveMessage(object sender, PipeMessageEventArgs e) + { + ClientMessage?.Invoke(sender, e); + } + + private void ClientOnDisconnected(object sender, PipeConnectionEventArgs e) + { + if (e.Connection == null) { - ClientMessage?.Invoke(sender, e); + return; } - private void ClientOnDisconnected(object sender, PipeConnectionEventArgs e) + lock (_connections) { - if (e.Connection == null) - { - return; - } + _connections.Remove(e.Connection); + } - lock (_connections) - { - _connections.Remove(e.Connection); - } + ClientDisconnected?.Invoke(sender, e); + } - ClientDisconnected?.Invoke(sender, e); - } + /// + /// Invoked on the UI thread. + /// + private void ConnectionOnError(object sender, PipeErrorEventArgs e) + { + Error?.Invoke(sender, e); + } - /// - /// Invoked on the UI thread. - /// - private void ConnectionOnError(object sender, PipeErrorEventArgs e) - { - Error?.Invoke(sender, e); - } + /// + /// Invoked on the UI thread. + /// + /// + private void WorkerOnError(object sender, WorkerErrorEventArgs e) + { + PipeErrorEventArgs e2 = new(null, e.Exception); + Error?.Invoke(sender, e2); + } - /// - /// Invoked on the UI thread. - /// - /// - private void WorkerOnError(object sender, WorkerErrorEventArgs e) - { - PipeErrorEventArgs e2 = new(null, e.Exception); - Error?.Invoke(sender, e2); - } + private string GetNextConnectionPipeName() + { + return $"{_pipeName}_{++_nextPipeId}"; + } - private string GetNextConnectionPipeName() + private static void Cleanup(NamedPipeServerStream pipe) + { + if (pipe is null) { - return $"{_pipeName}_{++_nextPipeId}"; + return; } - private static void Cleanup(NamedPipeServerStream pipe) + using (NamedPipeServerStream x = pipe) { - if (pipe is null) - { - return; - } - - using (NamedPipeServerStream x = pipe) - { - x.Close(); - } + x.Close(); } - - #endregion } + + #endregion } diff --git a/YAMDCC.IPC/PipeClientFactory.cs b/YAMDCC.IPC/PipeClientFactory.cs index 9398021..4a6c30c 100644 --- a/YAMDCC.IPC/PipeClientFactory.cs +++ b/YAMDCC.IPC/PipeClientFactory.cs @@ -5,56 +5,55 @@ using System.Threading; using YAMDCC.IPC.IO; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +internal static class PipeClientFactory { - internal static class PipeClientFactory + internal static PipeStreamWrapper Connect(string pipeName) + where TRead : class + where TWrite : class { - internal static PipeStreamWrapper Connect(string pipeName) - where TRead : class - where TWrite : class - { - return new PipeStreamWrapper(CreateAndConnectPipe(pipeName)); - } + return new PipeStreamWrapper(CreateAndConnectPipe(pipeName)); + } - internal static NamedPipeClientStream CreateAndConnectPipe(string pipeName, int timeout = 10) + internal static NamedPipeClientStream CreateAndConnectPipe(string pipeName, int timeout = 10) + { + string normalizedPath = Path.GetFullPath($"\\\\.\\pipe\\{pipeName}"); + while (!NamedPipeExists(normalizedPath)) { - string normalizedPath = Path.GetFullPath($"\\\\.\\pipe\\{pipeName}"); - while (!NamedPipeExists(normalizedPath)) - { - Thread.Sleep(timeout); - } - NamedPipeClientStream pipe = new(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough); - pipe.Connect(1000); - return pipe; + Thread.Sleep(timeout); } + NamedPipeClientStream pipe = new(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough); + pipe.Connect(1000); + return pipe; + } - private static bool NamedPipeExists(string pipeName) + private static bool NamedPipeExists(string pipeName) + { + try { - try + bool exists = WaitNamedPipe(pipeName, -1); + if (!exists) { - bool exists = WaitNamedPipe(pipeName, -1); - if (!exists) + int error = Marshal.GetLastWin32Error(); + if (error is 0 or 2) { - int error = Marshal.GetLastWin32Error(); - if (error is 0 or 2) - { - return false; - } + return false; } - return true; - } - catch (Exception) - { - return false; } + return true; + } + catch (Exception) + { + return false; } - - [DllImport("kernel32.dll", - CharSet = CharSet.Unicode, - EntryPoint = "WaitNamedPipeW", - SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool WaitNamedPipe(string name, int timeout); } + + [DllImport("kernel32.dll", + CharSet = CharSet.Unicode, + EntryPoint = "WaitNamedPipeW", + SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool WaitNamedPipe(string name, int timeout); } diff --git a/YAMDCC.IPC/PipeConnectionEventArgs.cs b/YAMDCC.IPC/PipeConnectionEventArgs.cs index cff3844..4c00eb9 100644 --- a/YAMDCC.IPC/PipeConnectionEventArgs.cs +++ b/YAMDCC.IPC/PipeConnectionEventArgs.cs @@ -1,38 +1,37 @@ using System; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Provides data for the +/// , +/// , and +/// events. +/// +/// +/// The reference type used when reading from the named pipe. +/// +/// +/// The reference type used when writing to the named pipe. +/// +public class PipeConnectionEventArgs : EventArgs + where TRead : class + where TWrite : class { /// - /// Provides data for the - /// , - /// , and - /// events. + /// The connection that caused the (dis)connection event. /// - /// - /// The reference type used when reading from the named pipe. - /// - /// - /// The reference type used when writing to the named pipe. - /// - public class PipeConnectionEventArgs : EventArgs - where TRead : class - where TWrite : class - { - /// - /// The connection that caused the (dis)connection event. - /// - public NamedPipeConnection Connection { get; } + public NamedPipeConnection Connection { get; } - /// - /// Initialises a new instance of the - /// class. - /// - /// - /// The connection that should be associated with the event. - /// - internal PipeConnectionEventArgs(NamedPipeConnection connection) - { - Connection = connection; - } + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The connection that should be associated with the event. + /// + internal PipeConnectionEventArgs(NamedPipeConnection connection) + { + Connection = connection; } } diff --git a/YAMDCC.IPC/PipeErrorEventArgs.cs b/YAMDCC.IPC/PipeErrorEventArgs.cs index 4840097..0f36dd0 100644 --- a/YAMDCC.IPC/PipeErrorEventArgs.cs +++ b/YAMDCC.IPC/PipeErrorEventArgs.cs @@ -1,43 +1,42 @@ using System; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Provides data for the +/// and +/// events. +/// +public class PipeErrorEventArgs : EventArgs + where TRead : class + where TWrite : class { + public NamedPipeConnection Connection { get; } + /// - /// Provides data for the - /// and - /// events. + /// The that caused the error. /// - public class PipeErrorEventArgs : EventArgs - where TRead : class - where TWrite : class - { - public NamedPipeConnection Connection { get; } - - /// - /// The that caused the error. - /// - public Exception Exception { get; } + public Exception Exception { get; } - /// - /// Initialises a new instance of the - /// class. - /// - /// - /// The connection that caused the error. - /// - /// The only time this should be null is if the error was caused - /// by . - /// - /// - /// - /// The exception that caused the error. - /// - internal PipeErrorEventArgs( - NamedPipeConnection connection, - Exception exception) - { - Connection = connection; - Exception = exception; - } + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The connection that caused the error. + /// + /// The only time this should be null is if the error was caused + /// by . + /// + /// + /// + /// The exception that caused the error. + /// + internal PipeErrorEventArgs( + NamedPipeConnection connection, + Exception exception) + { + Connection = connection; + Exception = exception; } } diff --git a/YAMDCC.IPC/PipeMessageEventArgs.cs b/YAMDCC.IPC/PipeMessageEventArgs.cs index 9715647..1376517 100644 --- a/YAMDCC.IPC/PipeMessageEventArgs.cs +++ b/YAMDCC.IPC/PipeMessageEventArgs.cs @@ -1,39 +1,38 @@ -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Provides data for the +/// and +/// events. +/// +/// +/// The reference type used when reading from the named pipe. +/// +/// +/// The reference type used when writing to the named pipe. +/// +public class PipeMessageEventArgs : PipeConnectionEventArgs + where TRead : class + where TWrite : class { /// - /// Provides data for the - /// and - /// events. + /// The message sent by the other end of the pipe. /// - /// - /// The reference type used when reading from the named pipe. - /// - /// - /// The reference type used when writing to the named pipe. - /// - public class PipeMessageEventArgs : PipeConnectionEventArgs - where TRead : class - where TWrite : class - { - /// - /// The message sent by the other end of the pipe. - /// - public TRead Message { get; } + public TRead Message { get; } - /// - /// Initialises a new instance of the - /// class. - /// - /// - /// The connection that sent the message. - /// - /// - /// The message sent by the other end of the pipe. - /// - internal PipeMessageEventArgs(NamedPipeConnection connection, TRead message) - : base(connection) - { - Message = message; - } + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The connection that sent the message. + /// + /// + /// The message sent by the other end of the pipe. + /// + internal PipeMessageEventArgs(NamedPipeConnection connection, TRead message) + : base(connection) + { + Message = message; } } diff --git a/YAMDCC.IPC/PipeServerFactory.cs b/YAMDCC.IPC/PipeServerFactory.cs index 100f2cf..a5b9428 100644 --- a/YAMDCC.IPC/PipeServerFactory.cs +++ b/YAMDCC.IPC/PipeServerFactory.cs @@ -1,35 +1,34 @@ using System.IO.Pipes; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +internal static class PipeServerFactory { - internal static class PipeServerFactory + internal static NamedPipeServerStream CreateAndConnectPipe(string pipeName) { - internal static NamedPipeServerStream CreateAndConnectPipe(string pipeName) - { - NamedPipeServerStream pipe = CreatePipe(pipeName); - pipe.WaitForConnection(); + NamedPipeServerStream pipe = CreatePipe(pipeName); + pipe.WaitForConnection(); - return pipe; - } + return pipe; + } - internal static NamedPipeServerStream CreatePipe(string pipeName) - { - return new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, - PipeOptions.Asynchronous); - } + internal static NamedPipeServerStream CreatePipe(string pipeName) + { + return new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, + PipeOptions.Asynchronous); + } - internal static NamedPipeServerStream CreateAndConnectPipe(string pipeName, int bufferSize, PipeSecurity security) - { - NamedPipeServerStream pipe = CreatePipe(pipeName, bufferSize, security); - pipe.WaitForConnection(); + internal static NamedPipeServerStream CreateAndConnectPipe(string pipeName, int bufferSize, PipeSecurity security) + { + NamedPipeServerStream pipe = CreatePipe(pipeName, bufferSize, security); + pipe.WaitForConnection(); - return pipe; - } + return pipe; + } - internal static NamedPipeServerStream CreatePipe(string pipeName, int bufferSize, PipeSecurity security) - { - return new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, - PipeOptions.Asynchronous, bufferSize, bufferSize, security); - } + internal static NamedPipeServerStream CreatePipe(string pipeName, int bufferSize, PipeSecurity security) + { + return new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, + PipeOptions.Asynchronous, bufferSize, bufferSize, security); } } diff --git a/YAMDCC.IPC/ServiceCommand.cs b/YAMDCC.IPC/ServiceCommand.cs index 70e039f..c324180 100644 --- a/YAMDCC.IPC/ServiceCommand.cs +++ b/YAMDCC.IPC/ServiceCommand.cs @@ -1,159 +1,158 @@ using MessagePack; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Represents a list of possible commands that can +/// be sent to the YAMDCC Service. +/// +public enum Command { /// - /// Represents a list of possible commands that can - /// be sent to the YAMDCC Service. + /// Fallback value if empty (zero-length) message received by server. /// - public enum Command - { - /// - /// Fallback value if empty (zero-length) message received by server. - /// - Nothing = 0, - /// - /// Get the YAMDCC Service version, as a Git revision hash. - /// - /// - /// - /// The result is sent to the caller as a - /// message. - /// - /// - /// This command expects no arguments. - /// - /// - GetVersion, - /// - /// Read a byte from the EC. - /// - /// - /// The result is sent to the caller as a - /// message. - /// - /// - /// This command expects the following arguments as - /// a space-seperated string:
- /// • Register: The EC register to read. - ///
- ///
- ReadECByte, - /// - /// Write a byte to the EC. - /// - /// - /// - /// This command expects the following arguments as - /// a space-seperated string:
- /// • Register: The EC register to write to.
- /// • Value: The value to write. - ///
- ///
- WriteECByte, - /// - /// Get the target speed of a specified system fan in the - /// currently loaded YAMDCC config. - /// - /// - /// - /// This command expects the following arguments as - /// a space-seperated string:
- /// • Fan: The index of the fan to read the target speed from. - ///
- /// - /// The result is sent to the caller as a - /// message. - /// - ///
- GetFanSpeed, - /// - /// Get the RPM of a specified system fan in the - /// currently loaded YAMDCC config. - /// - /// - /// - /// This command expects the following arguments as - /// a space-seperated string:
- /// • Fan: The index of the fan to read the RPM from. - ///
- /// - /// The result is sent to the caller as a - /// message. - /// - ///
- GetFanRPM, - /// - /// Get the temperature of the component (CPU, GPU...) associated - /// with a specified system fan in the currently loaded YAMDCC config. - /// - /// - /// - /// This command expects the following arguments as - /// a space-seperated string:
- /// • Fan: The index of the fan to read the associated component's temperature from. - ///
- /// - /// The result is sent to the caller as a - /// message. - /// - ///
- GetTemp, - /// - /// Reload and apply a YAMDCC config. - /// - ApplyConfig, - /// - /// Enable or disable Full Blast on the system. - /// - /// - /// This command expects the following arguments as - /// a space-seperated string:
- /// • Enable: 1 to enable Full Blast, 0 to disable. - ///
- FullBlast, - /// - /// Gets the brightness of the keyboard backlight, - /// and sends a - /// response with the result. - /// - GetKeyLightBright, - /// - /// Sets the keyboard backlight to the specified value. - /// - /// - /// This command expects the following arguments as - /// a space-seperated string:
- /// • Brightness: A value between the minimum and - /// maximum brightness value (minus offset). - ///
- SetKeyLightBright, - } + Nothing = 0, + /// + /// Get the YAMDCC Service version, as a Git revision hash. + /// + /// + /// + /// The result is sent to the caller as a + /// message. + /// + /// + /// This command expects no arguments. + /// + /// + GetVersion, + /// + /// Read a byte from the EC. + /// + /// + /// The result is sent to the caller as a + /// message. + /// + /// + /// This command expects the following arguments as + /// a space-seperated string:
+ /// • Register: The EC register to read. + ///
+ ///
+ ReadECByte, + /// + /// Write a byte to the EC. + /// + /// + /// + /// This command expects the following arguments as + /// a space-seperated string:
+ /// • Register: The EC register to write to.
+ /// • Value: The value to write. + ///
+ ///
+ WriteECByte, + /// + /// Get the target speed of a specified system fan in the + /// currently loaded YAMDCC config. + /// + /// + /// + /// This command expects the following arguments as + /// a space-seperated string:
+ /// • Fan: The index of the fan to read the target speed from. + ///
+ /// + /// The result is sent to the caller as a + /// message. + /// + ///
+ GetFanSpeed, + /// + /// Get the RPM of a specified system fan in the + /// currently loaded YAMDCC config. + /// + /// + /// + /// This command expects the following arguments as + /// a space-seperated string:
+ /// • Fan: The index of the fan to read the RPM from. + ///
+ /// + /// The result is sent to the caller as a + /// message. + /// + ///
+ GetFanRPM, + /// + /// Get the temperature of the component (CPU, GPU...) associated + /// with a specified system fan in the currently loaded YAMDCC config. + /// + /// + /// + /// This command expects the following arguments as + /// a space-seperated string:
+ /// • Fan: The index of the fan to read the associated component's temperature from. + ///
+ /// + /// The result is sent to the caller as a + /// message. + /// + ///
+ GetTemp, + /// + /// Reload and apply a YAMDCC config. + /// + ApplyConfig, + /// + /// Enable or disable Full Blast on the system. + /// + /// + /// This command expects the following arguments as + /// a space-seperated string:
+ /// • Enable: 1 to enable Full Blast, 0 to disable. + ///
+ FullBlast, + /// + /// Gets the brightness of the keyboard backlight, + /// and sends a + /// response with the result. + /// + GetKeyLightBright, + /// + /// Sets the keyboard backlight to the specified value. + /// + /// + /// This command expects the following arguments as + /// a space-seperated string:
+ /// • Brightness: A value between the minimum and + /// maximum brightness value (minus offset). + ///
+ SetKeyLightBright, +} +/// +/// Represents a command to send to the YAMDCC Service. +/// +[MessagePackObject] +public class ServiceCommand +{ /// - /// Represents a command to send to the YAMDCC Service. + /// The to send to the service. /// - [MessagePackObject] - public class ServiceCommand - { - /// - /// The to send to the service. - /// - [Key(0)] - public Command Command { get; set; } = Command.Nothing; + [Key(0)] + public Command Command { get; set; } = Command.Nothing; - /// - /// The argument(s) to send to the service with the command. - /// The number of parameters for a service command vary depending on the - /// specific command sent to the service. - /// - [Key(1)] - public string Arguments { get; set; } = string.Empty; + /// + /// The argument(s) to send to the service with the command. + /// The number of parameters for a service command vary depending on the + /// specific command sent to the service. + /// + [Key(1)] + public string Arguments { get; set; } = string.Empty; - public ServiceCommand(Command command, string args) - { - Command = command; - Arguments = args; - } + public ServiceCommand(Command command, string args) + { + Command = command; + Arguments = args; } } diff --git a/YAMDCC.IPC/ServiceResponse.cs b/YAMDCC.IPC/ServiceResponse.cs index 5591bb3..475cafc 100644 --- a/YAMDCC.IPC/ServiceResponse.cs +++ b/YAMDCC.IPC/ServiceResponse.cs @@ -1,78 +1,77 @@ using MessagePack; -namespace YAMDCC.IPC +namespace YAMDCC.IPC; + +/// +/// Represents a list of possible responses to a . +/// +public enum Response { /// - /// Represents a list of possible responses to a . + /// Fallback value if empty (zero-length) message received by client. /// - public enum Response - { - /// - /// Fallback value if empty (zero-length) message received by client. - /// - Nothing = 0, - /// - /// Sent when any command that doesn't return data finishes successfully. - /// - Success, - /// - /// Sent when any command encounters an error. - /// - Error, - /// - /// The result of a command. - /// - Version, - /// - /// The result of a command. - /// - Temp, - /// - /// The result of a command. - /// - FanSpeed, - /// - /// The result of a command. - /// - FanRPM, - /// - /// The result of a command. - /// - ReadResult, - /// - /// The result of a command. - /// - KeyLightBright, - } + Nothing = 0, + /// + /// Sent when any command that doesn't return data finishes successfully. + /// + Success, + /// + /// Sent when any command encounters an error. + /// + Error, + /// + /// The result of a command. + /// + Version, + /// + /// The result of a command. + /// + Temp, + /// + /// The result of a command. + /// + FanSpeed, + /// + /// The result of a command. + /// + FanRPM, + /// + /// The result of a command. + /// + ReadResult, + /// + /// The result of a command. + /// + KeyLightBright, +} +/// +/// Represents a response to a . +/// +[MessagePackObject] +public class ServiceResponse +{ /// - /// Represents a response to a . + /// The to send to the service. /// - [MessagePackObject] - public class ServiceResponse - { - /// - /// The to send to the service. - /// - [Key(0)] - public Response Response { get; set; } = Response.Nothing; + [Key(0)] + public Response Response { get; set; } = Response.Nothing; - /// - /// The value associated with the . - /// - [Key(1)] - public string Value { get; set; } = string.Empty; + /// + /// The value associated with the . + /// + [Key(1)] + public string Value { get; set; } = string.Empty; - /// - /// Initialises a new instance of the - /// struct with the specified message and return value. - /// - /// - /// - public ServiceResponse(Response response, string value) - { - Response = response; - Value = value; - } + /// + /// Initialises a new instance of the + /// struct with the specified message and return value. + /// + /// + /// + public ServiceResponse(Response response, string value) + { + Response = response; + Value = value; } } diff --git a/YAMDCC.IPC/Threading/Worker.cs b/YAMDCC.IPC/Threading/Worker.cs index 16b3010..d896692 100644 --- a/YAMDCC.IPC/Threading/Worker.cs +++ b/YAMDCC.IPC/Threading/Worker.cs @@ -2,59 +2,58 @@ using System.Threading; using System.Threading.Tasks; -namespace YAMDCC.IPC.Threading +namespace YAMDCC.IPC.Threading; + +internal sealed class Worker { - internal sealed class Worker - { - private readonly TaskScheduler _callbackThread; + private readonly TaskScheduler _callbackThread; - private static TaskScheduler CurrentTaskScheduler => - SynchronizationContext.Current != null - ? TaskScheduler.FromCurrentSynchronizationContext() - : TaskScheduler.Default; + private static TaskScheduler CurrentTaskScheduler => + SynchronizationContext.Current != null + ? TaskScheduler.FromCurrentSynchronizationContext() + : TaskScheduler.Default; - internal event EventHandler Succeeded; - internal event EventHandler Error; + internal event EventHandler Succeeded; + internal event EventHandler Error; - internal Worker() : this(CurrentTaskScheduler) { } + internal Worker() : this(CurrentTaskScheduler) { } - internal Worker(TaskScheduler callbackThread) - { - _callbackThread = callbackThread; - } + internal Worker(TaskScheduler callbackThread) + { + _callbackThread = callbackThread; + } - internal void DoWork(Action action) - { - new Task(DoWorkImpl, action, CancellationToken.None, TaskCreationOptions.LongRunning).Start(); - } + internal void DoWork(Action action) + { + new Task(DoWorkImpl, action, CancellationToken.None, TaskCreationOptions.LongRunning).Start(); + } - internal void DoWorkImpl(object oAction) + internal void DoWorkImpl(object oAction) + { + Action action = (Action)oAction; + try { - Action action = (Action)oAction; - try - { - action(); - Callback(Succeed); - } - catch (Exception e) - { - Callback(() => Fail(e)); - } + action(); + Callback(Succeed); } - - private void Succeed() + catch (Exception e) { - Succeeded?.Invoke(this, EventArgs.Empty); + Callback(() => Fail(e)); } + } - private void Fail(Exception exception) - { - Error?.Invoke(this, new WorkerErrorEventArgs(exception)); - } + private void Succeed() + { + Succeeded?.Invoke(this, EventArgs.Empty); + } - private void Callback(Action action) - { - Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _callbackThread); - } + private void Fail(Exception exception) + { + Error?.Invoke(this, new WorkerErrorEventArgs(exception)); + } + + private void Callback(Action action) + { + Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _callbackThread); } } diff --git a/YAMDCC.IPC/Threading/WorkerErrorEventArgs.cs b/YAMDCC.IPC/Threading/WorkerErrorEventArgs.cs index 55b8c7b..a28fbcc 100644 --- a/YAMDCC.IPC/Threading/WorkerErrorEventArgs.cs +++ b/YAMDCC.IPC/Threading/WorkerErrorEventArgs.cs @@ -1,29 +1,28 @@ using System; -namespace YAMDCC.IPC.Threading +namespace YAMDCC.IPC.Threading; + +/// +/// Provides data for the +/// and +/// events. +/// +public class WorkerErrorEventArgs : EventArgs { /// - /// Provides data for the - /// and - /// events. + /// The that caused the error. /// - public class WorkerErrorEventArgs : EventArgs - { - /// - /// The that caused the error. - /// - public Exception Exception { get; } + public Exception Exception { get; } - /// - /// Initialises a new instance of the - /// class. - /// - /// - /// The exception that caused the error. - /// - internal WorkerErrorEventArgs(Exception exception) - { - Exception = exception; - } + /// + /// Initialises a new instance of the + /// class. + /// + /// + /// The exception that caused the error. + /// + internal WorkerErrorEventArgs(Exception exception) + { + Exception = exception; } } diff --git a/YAMDCC.Logs/LogLevel.cs b/YAMDCC.Logs/LogLevel.cs index b036681..81dae73 100644 --- a/YAMDCC.Logs/LogLevel.cs +++ b/YAMDCC.Logs/LogLevel.cs @@ -14,41 +14,40 @@ // You should have received a copy of the GNU General Public License along with // YAMDCC. If not, see . -namespace YAMDCC.Logs +namespace YAMDCC.Logs; + +/// +/// The verbosity of logs +/// +public enum LogLevel { /// - /// The verbosity of logs + /// Do not log anything. /// - public enum LogLevel - { - /// - /// Do not log anything. - /// - None = 0, + None = 0, - /// - /// Only log Fatal events. - /// - Fatal = 1, + /// + /// Only log Fatal events. + /// + Fatal = 1, - /// - /// Log Errors and Fatal events. - /// - Error = 2, + /// + /// Log Errors and Fatal events. + /// + Error = 2, - /// - /// Log Warnings, Errors, and Fatal events. - /// - Warn = 3, + /// + /// Log Warnings, Errors, and Fatal events. + /// + Warn = 3, - /// - /// Log all events, except for Debug events. - /// - Info = 4, + /// + /// Log all events, except for Debug events. + /// + Info = 4, - /// - /// Log all events. - /// - Debug = 5, - } + /// + /// Log all events. + /// + Debug = 5, } diff --git a/YAMDCC.Logs/Logger.cs b/YAMDCC.Logs/Logger.cs index b90123c..62be92f 100644 --- a/YAMDCC.Logs/Logger.cs +++ b/YAMDCC.Logs/Logger.cs @@ -20,377 +20,376 @@ using System.IO.Compression; using System.Reflection; -namespace YAMDCC.Logs +namespace YAMDCC.Logs; + +/// +/// A simple logger class for writing logs to +/// the console or a configurable file path. +/// +public sealed class Logger : IDisposable { /// - /// A simple logger class for writing logs to - /// the console or a configurable file path. + /// The to write log files to. + /// + private StreamWriter LogWriter; + + /// + /// Used with to prevent more + /// than one thread writing to the console at once. + /// + private readonly object consoleLock = new(); + + /// + /// The newline characters to split provided log message lines by. + /// + private static readonly char[] NewlineChars = ['\r', '\n']; + + private static string LogString(string text, LogLevel level, bool showDate) => + (showDate ? $"[{DateTime.Now:dd/MM/yyyy @ HH:mm:ss.fff}] " : "") + $"[{level}]".PadRight(8).ToUpper(CultureInfo.InvariantCulture) + text; + + /// + /// The directory in which log files are saved. + /// + public string LogDir { get; set; } = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + + /// + /// The base name of the log file. + /// + /// + /// + /// Log files will have the .log extension appended. + /// + /// + /// Archives will have a number appended before the .log + /// extension, with higher numbers indicating older logs. + /// + /// + public string LogName { get; set; } = Path.GetFileName(Assembly.GetEntryAssembly().Location); + + private string LogPath => Path.Combine(LogDir, LogName); + + /// + /// The maximum number of logs to archive. + /// + public int MaxArchivedLogs { get; set; } = 9; + + /// + /// How verbose should console logs be? + /// + public LogLevel ConsoleLogLevel { get; set; } = LogLevel.Info; + + /// + /// How verbose should logs written to disk be? + /// + public LogLevel FileLogLevel { get; set; } = LogLevel.Info; + + /// + /// Should the log time be shown in console logs? + /// + public bool LogTimeToConsole { get; set; } + + /// + /// Should the log time be shown in logs written to disk? + /// + public bool LogTimeToFile { get; set; } = true; + + /// + /// Writes a Debug event to the . /// - public sealed class Logger : IDisposable + /// + /// The event to write to the log. + /// + public void Debug(string message) { - /// - /// The to write log files to. - /// - private StreamWriter LogWriter; - - /// - /// Used with to prevent more - /// than one thread writing to the console at once. - /// - private readonly object consoleLock = new(); - - /// - /// The newline characters to split provided log message lines by. - /// - private static readonly char[] NewlineChars = ['\r', '\n']; - - private static string LogString(string text, LogLevel level, bool showDate) => - (showDate ? $"[{DateTime.Now:dd/MM/yyyy @ HH:mm:ss.fff}] " : "") + $"[{level}]".PadRight(8).ToUpper(CultureInfo.InvariantCulture) + text; - - /// - /// The directory in which log files are saved. - /// - public string LogDir { get; set; } = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - - /// - /// The base name of the log file. - /// - /// - /// - /// Log files will have the .log extension appended. - /// - /// - /// Archives will have a number appended before the .log - /// extension, with higher numbers indicating older logs. - /// - /// - public string LogName { get; set; } = Path.GetFileName(Assembly.GetEntryAssembly().Location); - - private string LogPath => Path.Combine(LogDir, LogName); - - /// - /// The maximum number of logs to archive. - /// - public int MaxArchivedLogs { get; set; } = 9; - - /// - /// How verbose should console logs be? - /// - public LogLevel ConsoleLogLevel { get; set; } = LogLevel.Info; - - /// - /// How verbose should logs written to disk be? - /// - public LogLevel FileLogLevel { get; set; } = LogLevel.Info; - - /// - /// Should the log time be shown in console logs? - /// - public bool LogTimeToConsole { get; set; } - - /// - /// Should the log time be shown in logs written to disk? - /// - public bool LogTimeToFile { get; set; } = true; - - /// - /// Writes a Debug event to the . - /// - /// - /// The event to write to the log. - /// - public void Debug(string message) + if (FileLogLevel >= LogLevel.Debug) { - if (FileLogLevel >= LogLevel.Debug) - { - WriteFile(message, LogLevel.Debug); - } - - if (ConsoleLogLevel >= LogLevel.Debug) - { - WriteConsole(message, LogLevel.Debug); - } + WriteFile(message, LogLevel.Debug); } - /// - /// Writes a Debug event to the , - /// replacing format items with the objects in . - /// - /// - /// Equivalent to passing a - /// to the argument. - /// - /// The event to write to the log. - /// The objects to format. - public void Debug(string message, params object[] args) + if (ConsoleLogLevel >= LogLevel.Debug) { - Debug(string.Format(CultureInfo.InvariantCulture, message, args)); + WriteConsole(message, LogLevel.Debug); } + } - /// - /// Writes an Info event to the . - /// - /// The event to write to the log. - public void Info(string message) - { - if (FileLogLevel >= LogLevel.Info) - { - WriteFile(message, LogLevel.Info); - } + /// + /// Writes a Debug event to the , + /// replacing format items with the objects in . + /// + /// + /// Equivalent to passing a + /// to the argument. + /// + /// The event to write to the log. + /// The objects to format. + public void Debug(string message, params object[] args) + { + Debug(string.Format(CultureInfo.InvariantCulture, message, args)); + } - if (ConsoleLogLevel >= LogLevel.Info) - { - WriteConsole(message, LogLevel.Info); - } + /// + /// Writes an Info event to the . + /// + /// The event to write to the log. + public void Info(string message) + { + if (FileLogLevel >= LogLevel.Info) + { + WriteFile(message, LogLevel.Info); } - /// - /// Writes an Info event to the , - /// replacing format items with the objects in . - /// - /// - /// Equivalent to passing a - /// to the argument. - /// - /// The event to write to the log. - /// The objects to format. - public void Info(string message, params object[] args) + if (ConsoleLogLevel >= LogLevel.Info) { - Info(string.Format(CultureInfo.InvariantCulture, message, args)); + WriteConsole(message, LogLevel.Info); } + } - /// - /// Writes a Warning to the . - /// - /// The event to write to the log. - public void Warn(string message) - { - if (FileLogLevel >= LogLevel.Warn) - { - WriteFile(message, LogLevel.Warn); - } + /// + /// Writes an Info event to the , + /// replacing format items with the objects in . + /// + /// + /// Equivalent to passing a + /// to the argument. + /// + /// The event to write to the log. + /// The objects to format. + public void Info(string message, params object[] args) + { + Info(string.Format(CultureInfo.InvariantCulture, message, args)); + } - if (ConsoleLogLevel >= LogLevel.Warn) - { - WriteConsole(message, LogLevel.Warn); - } + /// + /// Writes a Warning to the . + /// + /// The event to write to the log. + public void Warn(string message) + { + if (FileLogLevel >= LogLevel.Warn) + { + WriteFile(message, LogLevel.Warn); } - /// - /// Writes a Warning to the , - /// replacing format items with the objects in . - /// - /// - /// Equivalent to passing a - /// to the argument. - /// - /// The event to write to the log. - /// The objects to format. - public void Warn(string message, params object[] args) + if (ConsoleLogLevel >= LogLevel.Warn) { - Warn(string.Format(CultureInfo.InvariantCulture, message, args)); + WriteConsole(message, LogLevel.Warn); } + } - /// - /// Writes an Error to the . - /// - /// The event to write to the log. - public void Error(string message) + /// + /// Writes a Warning to the , + /// replacing format items with the objects in . + /// + /// + /// Equivalent to passing a + /// to the argument. + /// + /// The event to write to the log. + /// The objects to format. + public void Warn(string message, params object[] args) + { + Warn(string.Format(CultureInfo.InvariantCulture, message, args)); + } + + /// + /// Writes an Error to the . + /// + /// The event to write to the log. + public void Error(string message) + { + if (FileLogLevel >= LogLevel.Error) { - if (FileLogLevel >= LogLevel.Error) - { - WriteFile(message, LogLevel.Error); - } + WriteFile(message, LogLevel.Error); + } - if (ConsoleLogLevel >= LogLevel.Error) - { - WriteConsole(message, LogLevel.Error); - } + if (ConsoleLogLevel >= LogLevel.Error) + { + WriteConsole(message, LogLevel.Error); } + } - /// - /// Writes an Error to the , - /// replacing format items with the objects in . - /// - /// - /// Equivalent to passing a - /// to the argument. - /// - /// The event to write to the log. - /// The objects to format. - public void Error(string message, params object[] args) + /// + /// Writes an Error to the , + /// replacing format items with the objects in . + /// + /// + /// Equivalent to passing a + /// to the argument. + /// + /// The event to write to the log. + /// The objects to format. + public void Error(string message, params object[] args) + { + Error(string.Format(CultureInfo.InvariantCulture, message, args)); + } + + /// + /// Writes a Fatal Error to the . Use when an + /// application is about to terminate due to a fatal error. + /// + /// The event to write to the log. + public void Fatal(string message) + { + if (FileLogLevel >= LogLevel.Fatal) { - Error(string.Format(CultureInfo.InvariantCulture, message, args)); + WriteFile(message, LogLevel.Fatal); } - /// - /// Writes a Fatal Error to the . Use when an - /// application is about to terminate due to a fatal error. - /// - /// The event to write to the log. - public void Fatal(string message) + if (ConsoleLogLevel >= LogLevel.Fatal) { - if (FileLogLevel >= LogLevel.Fatal) - { - WriteFile(message, LogLevel.Fatal); - } + WriteConsole(message, LogLevel.Fatal); + } + } - if (ConsoleLogLevel >= LogLevel.Fatal) + /// + /// Writes a Fatal Error to the , + /// replacing format items with the objects in . + /// Use when an application is about to terminate due to a fatal error. + /// + /// + /// Equivalent to passing a + /// to the argument. + /// + /// The event to write to the log. + /// The objects to format. + public void Fatal(string message, params object[] args) + { + Fatal(string.Format(CultureInfo.InvariantCulture, message, args)); + } + + /// + /// Deletes all archived logs (files ending with .[number].log.gz). + /// + public void DeleteArchivedLogs() + { + for (int i = 1; i <= MaxArchivedLogs; i++) + { + try { - WriteConsole(message, LogLevel.Fatal); + File.Delete($"{LogPath}.{i}.log.gz"); } + catch (FileNotFoundException) { } } + } - /// - /// Writes a Fatal Error to the , - /// replacing format items with the objects in . - /// Use when an application is about to terminate due to a fatal error. - /// - /// - /// Equivalent to passing a - /// to the argument. - /// - /// The event to write to the log. - /// The objects to format. - public void Fatal(string message, params object[] args) + private void WriteFile(string message, LogLevel level) + { + if (LogWriter is null) { - Fatal(string.Format(CultureInfo.InvariantCulture, message, args)); + InitLogFile(); } - /// - /// Deletes all archived logs (files ending with .[number].log.gz). - /// - public void DeleteArchivedLogs() + lock (LogWriter) { - for (int i = 1; i <= MaxArchivedLogs; i++) + foreach (string str in message.Split(NewlineChars, StringSplitOptions.RemoveEmptyEntries)) { - try - { - File.Delete($"{LogPath}.{i}.log.gz"); - } - catch (FileNotFoundException) { } + LogWriter.WriteLine(LogString(str, level, LogTimeToFile)); } } + } - private void WriteFile(string message, LogLevel level) + private void WriteConsole(string message, LogLevel level) + { + lock (consoleLock) { - if (LogWriter is null) + ConsoleColor bgColour = Console.BackgroundColor; + ConsoleColor fgColour = Console.ForegroundColor; + + switch (level) { - InitLogFile(); + case LogLevel.Fatal: + Console.BackgroundColor = ConsoleColor.Yellow; + Console.ForegroundColor = ConsoleColor.DarkRed; + break; + case LogLevel.Error: + Console.ForegroundColor = ConsoleColor.Red; + break; + case LogLevel.Warn: + Console.ForegroundColor = ConsoleColor.Yellow; + break; + case LogLevel.Info: + Console.ForegroundColor = ConsoleColor.White; + break; + case LogLevel.Debug: + Console.ForegroundColor = ConsoleColor.DarkGray; + break; } - lock (LogWriter) + foreach (string str in message.Split(NewlineChars, StringSplitOptions.RemoveEmptyEntries)) { - foreach (string str in message.Split(NewlineChars, StringSplitOptions.RemoveEmptyEntries)) - { - LogWriter.WriteLine(LogString(str, level, LogTimeToFile)); - } + Console.WriteLine(LogString(str, level, LogTimeToConsole)); } + + Console.BackgroundColor = bgColour; + Console.ForegroundColor = fgColour; } + } - private void WriteConsole(string message, LogLevel level) + /// + /// Initialises the log file. + /// Call before any attempts to write a log file. + /// + private void InitLogFile() + { + // Rename old log files, and delete the oldest file if + // there's too many log files + for (int i = MaxArchivedLogs; i >= 0; i--) { - lock (consoleLock) + try { - ConsoleColor bgColour = Console.BackgroundColor; - ConsoleColor fgColour = Console.ForegroundColor; - - switch (level) + if (i == MaxArchivedLogs) { - case LogLevel.Fatal: - Console.BackgroundColor = ConsoleColor.Yellow; - Console.ForegroundColor = ConsoleColor.DarkRed; - break; - case LogLevel.Error: - Console.ForegroundColor = ConsoleColor.Red; - break; - case LogLevel.Warn: - Console.ForegroundColor = ConsoleColor.Yellow; - break; - case LogLevel.Info: - Console.ForegroundColor = ConsoleColor.White; - break; - case LogLevel.Debug: - Console.ForegroundColor = ConsoleColor.DarkGray; - break; + File.Delete($"{LogPath}.{i}.log.gz"); } - - foreach (string str in message.Split(NewlineChars, StringSplitOptions.RemoveEmptyEntries)) + else { - Console.WriteLine(LogString(str, level, LogTimeToConsole)); + File.Move($"{LogPath}.{i}.log.gz", $"{LogPath}.{i + 1}.log.gz"); } - - Console.BackgroundColor = bgColour; - Console.ForegroundColor = fgColour; } + catch (FileNotFoundException) { } + catch (DirectoryNotFoundException) { } } - /// - /// Initialises the log file. - /// Call before any attempts to write a log file. - /// - private void InitLogFile() + try { - // Rename old log files, and delete the oldest file if - // there's too many log files - for (int i = MaxArchivedLogs; i >= 0; i--) - { - try - { - if (i == MaxArchivedLogs) - { - File.Delete($"{LogPath}.{i}.log.gz"); - } - else - { - File.Move($"{LogPath}.{i}.log.gz", $"{LogPath}.{i + 1}.log.gz"); - } - } - catch (FileNotFoundException) { } - catch (DirectoryNotFoundException) { } - } - - try - { - FileInfo fi = new($"{LogPath}.log"); - - // Set up file streams - FileStream original = fi.OpenRead(); - FileStream compressed = File.Create($"{LogPath}.{1}.log.gz"); - GZipStream gzStream = new(compressed, CompressionLevel.Optimal); + FileInfo fi = new($"{LogPath}.log"); - // Compress the file - original.CopyTo(gzStream); + // Set up file streams + FileStream original = fi.OpenRead(); + FileStream compressed = File.Create($"{LogPath}.{1}.log.gz"); + GZipStream gzStream = new(compressed, CompressionLevel.Optimal); - // Close file streams - gzStream.Close(); - compressed.Close(); - original.Close(); - - // Delete the unarchived copy of the log - fi.Delete(); - } - catch (FileNotFoundException) - { - // Log files probably don't exist yet, - // do nothing to avoid crash - } + // Compress the file + original.CopyTo(gzStream); - // if anyone knows why the fuck Directory.CreateDirectory - // is doing nothing when running from a windows service and - // pretending everything is ok i would LOVE to know - // (workaround in YAMDCC.GUI/Program.cs) - Directory.CreateDirectory(LogDir); + // Close file streams + gzStream.Close(); + compressed.Close(); + original.Close(); - LogWriter = new StreamWriter($"{LogPath}.log") - { - AutoFlush = true - }; + // Delete the unarchived copy of the log + fi.Delete(); } - - public void Dispose() + catch (FileNotFoundException) { - LogWriter.Dispose(); + // Log files probably don't exist yet, + // do nothing to avoid crash } + + // if anyone knows why the fuck Directory.CreateDirectory + // is doing nothing when running from a windows service and + // pretending everything is ok i would LOVE to know + // (workaround in YAMDCC.GUI/Program.cs) + Directory.CreateDirectory(LogDir); + + LogWriter = new StreamWriter($"{LogPath}.log") + { + AutoFlush = true + }; + } + + public void Dispose() + { + LogWriter.Dispose(); } } diff --git a/YAMDCC.Service/FanControlService.cs b/YAMDCC.Service/FanControlService.cs index c9cadb8..0e44cb4 100644 --- a/YAMDCC.Service/FanControlService.cs +++ b/YAMDCC.Service/FanControlService.cs @@ -29,830 +29,829 @@ using YAMDCC.IPC; using YAMDCC.Logs; -namespace YAMDCC.Service +namespace YAMDCC.Service; + +internal sealed class FanControlService : ServiceBase { - internal sealed class FanControlService : ServiceBase - { - #region Fields + #region Fields - /// - /// The currently loaded YAMDCC config. - /// - private YAMDCC_Config Config; + /// + /// The currently loaded YAMDCC config. + /// + private YAMDCC_Config Config; - /// - /// The named message pipe server that YAMDCC connects to. - /// - private readonly NamedPipeServer IPCServer; + /// + /// The named message pipe server that YAMDCC connects to. + /// + private readonly NamedPipeServer IPCServer; - private readonly Logger Log; + private readonly Logger Log; - private readonly EC _EC; + private readonly EC _EC; - private volatile bool Cooldown; - private readonly Timer CooldownTimer = new() - { - AutoReset = false, - Interval = 1000, - }; - #endregion + private volatile bool Cooldown; + private readonly Timer CooldownTimer = new() + { + AutoReset = false, + Interval = 1000, + }; + #endregion + + /// + /// Initialises a new instance of the class. + /// + /// + /// The instance to write logs to. + /// + public FanControlService(Logger logger) + { + CanHandlePowerEvent = true; + CanShutdown = true; - /// - /// Initialises a new instance of the class. - /// - /// - /// The instance to write logs to. - /// - public FanControlService(Logger logger) - { - CanHandlePowerEvent = true; - CanShutdown = true; + Log = logger; + _EC = new EC(); - Log = logger; - _EC = new EC(); + PipeSecurity security = new(); + // use SDDL descriptor since not everyone uses english Windows. + // the SDDL descriptor should be roughly equivalent to the old + // behaviour (commented out below): + // security.AddAccessRule(new PipeAccessRule( + // "Administrators", PipeAccessRights.ReadWrite, AccessControlType.Allow)); + security.SetSecurityDescriptorSddlForm("O:BAG:SYD:(A;;GA;;;SY)(A;;GRGW;;;BA)"); - PipeSecurity security = new(); - // use SDDL descriptor since not everyone uses english Windows. - // the SDDL descriptor should be roughly equivalent to the old - // behaviour (commented out below): - // security.AddAccessRule(new PipeAccessRule( - // "Administrators", PipeAccessRights.ReadWrite, AccessControlType.Allow)); - security.SetSecurityDescriptorSddlForm("O:BAG:SYD:(A;;GA;;;SY)(A;;GRGW;;;BA)"); + IPCServer = new NamedPipeServer("YAMDCC-Server", security); + } - IPCServer = new NamedPipeServer("YAMDCC-Server", security); - } + #region Events + protected override void OnStart(string[] args) + { + Log.Info(Strings.GetString("svcStarting")); + + // Load the service config. + bool confLoaded = LoadConf(); - #region Events - protected override void OnStart(string[] args) + // Install WinRing0 to get EC access + try { - Log.Info(Strings.GetString("svcStarting")); + Log.Info(Strings.GetString("drvLoad")); + if (!_EC.LoadDriver()) + { + throw new Win32Exception(_EC.GetDriverError()); + } + } + catch (Win32Exception) + { + Log.Fatal(Strings.GetString("drvLoadFail")); + _EC.UnloadDriver(); + ExitCode = 1; + throw; + } + Log.Info(Strings.GetString("drvLoadSuccess")); - // Load the service config. - bool confLoaded = LoadConf(); + CooldownTimer.Elapsed += CooldownElapsed; - // Install WinRing0 to get EC access - try + // Set up IPC server + Log.Info("Starting IPC server..."); + IPCServer.ClientConnected += IPCClientConnect; + IPCServer.ClientDisconnected += IPCClientDisconnect; + IPCServer.Error += IPCServerError; + IPCServer.Start(); + + Log.Info(Strings.GetString("svcStarted")); + + // Attempt to read default fan curve if it's pending: + int rebootFlag = -1; + try + { + StreamReader sr = new(Paths.ECToConfPending); + if (int.TryParse(sr.ReadToEnd(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int value)) { - Log.Info(Strings.GetString("drvLoad")); - if (!_EC.LoadDriver()) - { - throw new Win32Exception(_EC.GetDriverError()); - } + rebootFlag = value; } - catch (Win32Exception) + sr.Close(); + + if (rebootFlag == 0) { - Log.Fatal(Strings.GetString("drvLoadFail")); - _EC.UnloadDriver(); - ExitCode = 1; - throw; + ECToConf(); + File.Delete(Paths.ECToConfPending); } - Log.Info(Strings.GetString("drvLoadSuccess")); + } + catch (FileNotFoundException) { } + catch (DirectoryNotFoundException) { } - CooldownTimer.Elapsed += CooldownElapsed; + // Apply the fan curve and charging threshold: + if (confLoaded) + { + ApplySettings(); + } + } - // Set up IPC server - Log.Info("Starting IPC server..."); - IPCServer.ClientConnected += IPCClientConnect; - IPCServer.ClientDisconnected += IPCClientDisconnect; - IPCServer.Error += IPCServerError; - IPCServer.Start(); + private void CooldownElapsed(object sender, ElapsedEventArgs e) + { + CooldownTimer.Stop(); + Cooldown = false; + } - Log.Info(Strings.GetString("svcStarted")); + protected override void OnStop() + { + StopSvc(); + } - // Attempt to read default fan curve if it's pending: - int rebootFlag = -1; + protected override void OnShutdown() + { + int rebootFlag = -1; + try + { + StreamReader sr = new(Paths.ECToConfPending); try { - StreamReader sr = new(Paths.ECToConfPending); if (int.TryParse(sr.ReadToEnd(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int value)) { rebootFlag = value; } - sr.Close(); - - if (rebootFlag == 0) - { - ECToConf(); - File.Delete(Paths.ECToConfPending); - } } - catch (FileNotFoundException) { } - catch (DirectoryNotFoundException) { } - - // Apply the fan curve and charging threshold: - if (confLoaded) + finally { - ApplySettings(); + sr.Close(); } - } - - private void CooldownElapsed(object sender, ElapsedEventArgs e) - { - CooldownTimer.Stop(); - Cooldown = false; - } - - protected override void OnStop() - { - StopSvc(); - } - protected override void OnShutdown() - { - int rebootFlag = -1; - try + if (rebootFlag == 1) { - StreamReader sr = new(Paths.ECToConfPending); + StreamWriter sw = new(Paths.ECToConfPending); try { - if (int.TryParse(sr.ReadToEnd(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int value)) - { - rebootFlag = value; - } + sw.Write(0); } finally { - sr.Close(); - } - - if (rebootFlag == 1) - { - StreamWriter sw = new(Paths.ECToConfPending); - try - { - sw.Write(0); - } - finally - { - sw.Close(); - } + sw.Close(); } } - catch (FileNotFoundException) { } - catch (DirectoryNotFoundException) { } - StopSvc(); } + catch (FileNotFoundException) { } + catch (DirectoryNotFoundException) { } + StopSvc(); + } - private void StopSvc() + private void StopSvc() + { + if (ExitCode == 0) { - if (ExitCode == 0) - { - Log.Info(Strings.GetString("svcStopping")); + Log.Info(Strings.GetString("svcStopping")); - // Stop the IPC server: - Log.Info("Stopping IPC server..."); - IPCServer.Stop(); - IPCServer.ClientConnected -= IPCClientConnect; - IPCServer.ClientDisconnected -= IPCClientDisconnect; - IPCServer.Error -= IPCServerError; + // Stop the IPC server: + Log.Info("Stopping IPC server..."); + IPCServer.Stop(); + IPCServer.ClientConnected -= IPCClientConnect; + IPCServer.ClientDisconnected -= IPCClientDisconnect; + IPCServer.Error -= IPCServerError; - CooldownTimer.Elapsed -= CooldownElapsed; + CooldownTimer.Elapsed -= CooldownElapsed; - // Uninstall WinRing0 to keep things clean - Log.Info(Strings.GetString("drvUnload")); - _EC.UnloadDriver(); + // Uninstall WinRing0 to keep things clean + Log.Info(Strings.GetString("drvUnload")); + _EC.UnloadDriver(); - Log.Info(Strings.GetString("svcStopped")); - } + Log.Info(Strings.GetString("svcStopped")); } + } - protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) + protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) + { + switch (powerStatus) { - switch (powerStatus) - { - case PowerBroadcastStatus.ResumeCritical: - case PowerBroadcastStatus.ResumeSuspend: - case PowerBroadcastStatus.ResumeAutomatic: - if (!Cooldown) - { - // Re-apply the fan curve after waking up from sleep: - Log.Info(Strings.GetString("svcWake")); - ApplySettings(); - Cooldown = true; - CooldownTimer.Start(); - } - break; - } - return true; + case PowerBroadcastStatus.ResumeCritical: + case PowerBroadcastStatus.ResumeSuspend: + case PowerBroadcastStatus.ResumeAutomatic: + if (!Cooldown) + { + // Re-apply the fan curve after waking up from sleep: + Log.Info(Strings.GetString("svcWake")); + ApplySettings(); + Cooldown = true; + CooldownTimer.Start(); + } + break; } + return true; + } - private void IPCClientConnect(object sender, PipeConnectionEventArgs e) - { - e.Connection.ReceiveMessage += IPCClientMessage; - Log.Info(Strings.GetString("ipcConnect", e.Connection.ID)); - } + private void IPCClientConnect(object sender, PipeConnectionEventArgs e) + { + e.Connection.ReceiveMessage += IPCClientMessage; + Log.Info(Strings.GetString("ipcConnect", e.Connection.ID)); + } - private void IPCClientDisconnect(object sender, PipeConnectionEventArgs e) - { - e.Connection.ReceiveMessage -= IPCClientMessage; - Log.Info(Strings.GetString("ipcDC", e.Connection.ID)); - } + private void IPCClientDisconnect(object sender, PipeConnectionEventArgs e) + { + e.Connection.ReceiveMessage -= IPCClientMessage; + Log.Info(Strings.GetString("ipcDC", e.Connection.ID)); + } - private void IPCServerError(object sender, PipeErrorEventArgs e) - { - Log.Error(Strings.GetString("ipcError", e.Connection.ID, e.Exception)); - } + private void IPCServerError(object sender, PipeErrorEventArgs e) + { + Log.Error(Strings.GetString("ipcError", e.Connection.ID, e.Exception)); + } - private void IPCClientMessage(object sender, PipeMessageEventArgs e) - { - bool parseSuccess = false, - cmdSuccess = false, - sendSuccessMsg = true; + private void IPCClientMessage(object sender, PipeMessageEventArgs e) + { + bool parseSuccess = false, + cmdSuccess = false, + sendSuccessMsg = true; - if (ParseArgs(e.Message.Arguments, out int[] args)) - { - switch (e.Message.Command) - { - case Command.Nothing: - Log.Warn("Empty command received!"); - return; - case Command.GetVersion: - IPCServer.PushMessage(new ServiceResponse( - Response.Version, Utils.GetRevision()), e.Connection.ID); - return; - case Command.ReadECByte: - if (args.Length == 1) - { - parseSuccess = true; - sendSuccessMsg = false; - cmdSuccess = LogECReadByte((byte)args[0], out byte value); - if (cmdSuccess) - { - IPCServer.PushMessage(new ServiceResponse( - Response.ReadResult, $"{args[0]} {value}"), e.Connection.ID); - } - } - break; - case Command.WriteECByte: - if (args.Length == 2) - { - parseSuccess = true; - cmdSuccess = LogECWriteByte((byte)args[0], (byte)args[1]); - } - break; - case Command.GetFanSpeed: - if (args.Length == 1) - { - parseSuccess = true; - sendSuccessMsg = false; - cmdSuccess = GetFanSpeed(e.Connection.ID, args[0]); - } - break; - case Command.GetFanRPM: - if (args.Length == 1) - { - parseSuccess = true; - sendSuccessMsg = false; - cmdSuccess = GetFanRPM(e.Connection.ID, args[0]); - } - break; - case Command.GetTemp: - if (args.Length == 1) - { - parseSuccess = true; - sendSuccessMsg = false; - cmdSuccess = GetTemp(e.Connection.ID, args[0]); - } - break; - case Command.ApplyConfig: + if (ParseArgs(e.Message.Arguments, out int[] args)) + { + switch (e.Message.Command) + { + case Command.Nothing: + Log.Warn("Empty command received!"); + return; + case Command.GetVersion: + IPCServer.PushMessage(new ServiceResponse( + Response.Version, Utils.GetRevision()), e.Connection.ID); + return; + case Command.ReadECByte: + if (args.Length == 1) + { parseSuccess = true; - cmdSuccess = LoadConf() && ApplySettings(); - break; - case Command.FullBlast: - if (args.Length == 1) + sendSuccessMsg = false; + cmdSuccess = LogECReadByte((byte)args[0], out byte value); + if (cmdSuccess) { - parseSuccess = true; - cmdSuccess = SetFullBlast(args[0] == 1); + IPCServer.PushMessage(new ServiceResponse( + Response.ReadResult, $"{args[0]} {value}"), e.Connection.ID); } - break; - case Command.GetKeyLightBright: + } + break; + case Command.WriteECByte: + if (args.Length == 2) + { + parseSuccess = true; + cmdSuccess = LogECWriteByte((byte)args[0], (byte)args[1]); + } + break; + case Command.GetFanSpeed: + if (args.Length == 1) + { parseSuccess = true; sendSuccessMsg = false; - cmdSuccess = GetKeyLightBright(e.Connection.ID); - break; - case Command.SetKeyLightBright: - if (args.Length == 1) - { - parseSuccess = true; - cmdSuccess = SetKeyLightBright((byte)args[0]); - } - break; - default: // Unknown command - Log.Error(Strings.GetString("errBadCmd", e.Message)); - return; - } + cmdSuccess = GetFanSpeed(e.Connection.ID, args[0]); + } + break; + case Command.GetFanRPM: + if (args.Length == 1) + { + parseSuccess = true; + sendSuccessMsg = false; + cmdSuccess = GetFanRPM(e.Connection.ID, args[0]); + } + break; + case Command.GetTemp: + if (args.Length == 1) + { + parseSuccess = true; + sendSuccessMsg = false; + cmdSuccess = GetTemp(e.Connection.ID, args[0]); + } + break; + case Command.ApplyConfig: + parseSuccess = true; + cmdSuccess = LoadConf() && ApplySettings(); + break; + case Command.FullBlast: + if (args.Length == 1) + { + parseSuccess = true; + cmdSuccess = SetFullBlast(args[0] == 1); + } + break; + case Command.GetKeyLightBright: + parseSuccess = true; + sendSuccessMsg = false; + cmdSuccess = GetKeyLightBright(e.Connection.ID); + break; + case Command.SetKeyLightBright: + if (args.Length == 1) + { + parseSuccess = true; + cmdSuccess = SetKeyLightBright((byte)args[0]); + } + break; + default: // Unknown command + Log.Error(Strings.GetString("errBadCmd", e.Message)); + return; } - else + } + else + { + Log.Error(Strings.GetString("errArgsBadType")); + Log.Error(Strings.GetString("errOffendingCmd", e.Message.Command, e.Message.Arguments)); + } + + if (!cmdSuccess) + { + if (!parseSuccess) { - Log.Error(Strings.GetString("errArgsBadType")); + Log.Error(Strings.GetString("errArgsBadLen")); Log.Error(Strings.GetString("errOffendingCmd", e.Message.Command, e.Message.Arguments)); } + IPCServer.PushMessage(new ServiceResponse( + Response.Error, $"{(int)e.Message.Command}"), e.Connection.ID); + } + else if (sendSuccessMsg) + { + IPCServer.PushMessage(new ServiceResponse( + Response.Success, $"{(int)e.Message.Command}"), e.Connection.ID); + } + } + #endregion - if (!cmdSuccess) - { - if (!parseSuccess) - { - Log.Error(Strings.GetString("errArgsBadLen")); - Log.Error(Strings.GetString("errOffendingCmd", e.Message.Command, e.Message.Arguments)); - } - IPCServer.PushMessage(new ServiceResponse( - Response.Error, $"{(int)e.Message.Command}"), e.Connection.ID); - } - else if (sendSuccessMsg) - { - IPCServer.PushMessage(new ServiceResponse( - Response.Success, $"{(int)e.Message.Command}"), e.Connection.ID); - } + private bool LogECReadByte(byte reg, out byte value) + { + bool success = _EC.ReadByte(reg, out value); + if (success) + { + Log.Debug(Strings.GetString("svcECReadSuccess"), reg, value); + } + else + { + Log.Error(Strings.GetString("errECRead", reg, GetWin32Error(_EC.GetDriverError()))); } - #endregion + return success; + } - private bool LogECReadByte(byte reg, out byte value) + private bool LogECReadWord(byte reg, out ushort value, bool bigEndian) + { + bool success = _EC.ReadWord(reg, out value, bigEndian); + if (success) { - bool success = _EC.ReadByte(reg, out value); - if (success) - { - Log.Debug(Strings.GetString("svcECReadSuccess"), reg, value); - } - else - { - Log.Error(Strings.GetString("errECRead", reg, GetWin32Error(_EC.GetDriverError()))); - } - return success; + Log.Debug(Strings.GetString("svcECReadSuccess"), reg, value); + } + else + { + Log.Error(Strings.GetString("errECRead", reg, GetWin32Error(_EC.GetDriverError()))); } + return success; + } - private bool LogECReadWord(byte reg, out ushort value, bool bigEndian) + private bool LogECWriteByte(byte reg, byte value) + { + bool success = _EC.WriteByte(reg, value); + if (success) { - bool success = _EC.ReadWord(reg, out value, bigEndian); - if (success) - { - Log.Debug(Strings.GetString("svcECReadSuccess"), reg, value); - } - else - { - Log.Error(Strings.GetString("errECRead", reg, GetWin32Error(_EC.GetDriverError()))); - } - return success; + Log.Debug(Strings.GetString("svcECWriteSuccess"), reg); + } + else + { + Log.Error(Strings.GetString("errECWrite", reg, GetWin32Error(_EC.GetDriverError()))); } + return success; + } + + private bool LoadConf() + { + Log.Info(Strings.GetString("cfgLoading")); - private bool LogECWriteByte(byte reg, byte value) + try { - bool success = _EC.WriteByte(reg, value); - if (success) + Config = YAMDCC_Config.Load(Paths.CurrentConfig); + } + catch (Exception ex) + { + if (ex is InvalidConfigException or InvalidOperationException) + { + Log.Error(Strings.GetString("cfgInvalid")); + } + else if (ex is FileNotFoundException) { - Log.Debug(Strings.GetString("svcECWriteSuccess"), reg); + Log.Warn(Strings.GetString("cfgNotFound")); } else { - Log.Error(Strings.GetString("errECWrite", reg, GetWin32Error(_EC.GetDriverError()))); + throw; } - return success; + Config = null; + return false; } - private bool LoadConf() + Log.Info(Strings.GetString("cfgLoaded")); + return true; + } + + private bool ApplySettings() + { + if (Config is null) { - Log.Info(Strings.GetString("cfgLoading")); + return false; + } - try - { - Config = YAMDCC_Config.Load(Paths.CurrentConfig); - } - catch (Exception ex) + Log.Info(Strings.GetString("cfgApplying")); + bool success = true; + + // Write custom register values, if configured: + if (Config.RegConfs?.Length > 0) + { + for (int i = 0; i < Config.RegConfs.Length; i++) { - if (ex is InvalidConfigException or InvalidOperationException) - { - Log.Error(Strings.GetString("cfgInvalid")); - } - else if (ex is FileNotFoundException) + RegConf cfg = Config.RegConfs[i]; + Log.Info(Strings.GetString("svcWritingCustomRegs", i + 1, Config.RegConfs.Length)); + if (!LogECWriteByte(cfg.Reg, cfg.Value)) { - Log.Warn(Strings.GetString("cfgNotFound")); - } - else - { - throw; + success = false; } - Config = null; - return false; } - - Log.Info(Strings.GetString("cfgLoaded")); - return true; } - private bool ApplySettings() + // Write the fan curve to the appropriate registers for each fan: + for (int i = 0; i < Config.FanConfs.Length; i++) { - if (Config is null) - { - return false; - } + FanConf cfg = Config.FanConfs[i]; + Log.Info(Strings.GetString("svcWritingFans", cfg.Name, i + 1, Config.FanConfs.Length)); + FanCurveConf curveCfg = cfg.FanCurveConfs[cfg.CurveSel]; - Log.Info(Strings.GetString("cfgApplying")); - bool success = true; - - // Write custom register values, if configured: - if (Config.RegConfs?.Length > 0) + for (int j = 0; j < curveCfg.TempThresholds.Length; j++) { - for (int i = 0; i < Config.RegConfs.Length; i++) + if (!LogECWriteByte(cfg.FanCurveRegs[j], curveCfg.TempThresholds[j].FanSpeed)) { - RegConf cfg = Config.RegConfs[i]; - Log.Info(Strings.GetString("svcWritingCustomRegs", i + 1, Config.RegConfs.Length)); - if (!LogECWriteByte(cfg.Reg, cfg.Value)) - { - success = false; - } + success = false; } - } - - // Write the fan curve to the appropriate registers for each fan: - for (int i = 0; i < Config.FanConfs.Length; i++) - { - FanConf cfg = Config.FanConfs[i]; - Log.Info(Strings.GetString("svcWritingFans", cfg.Name, i + 1, Config.FanConfs.Length)); - FanCurveConf curveCfg = cfg.FanCurveConfs[cfg.CurveSel]; - - for (int j = 0; j < curveCfg.TempThresholds.Length; j++) + if (j > 0) { - if (!LogECWriteByte(cfg.FanCurveRegs[j], curveCfg.TempThresholds[j].FanSpeed)) + if (!LogECWriteByte(cfg.UpThresholdRegs[j - 1], curveCfg.TempThresholds[j].UpThreshold)) { success = false; } - if (j > 0) + byte downT = (byte)(curveCfg.TempThresholds[j].UpThreshold - curveCfg.TempThresholds[j].DownThreshold); + if (!LogECWriteByte(cfg.DownThresholdRegs[j - 1], downT)) { - if (!LogECWriteByte(cfg.UpThresholdRegs[j - 1], curveCfg.TempThresholds[j].UpThreshold)) - { - success = false; - } - byte downT = (byte)(curveCfg.TempThresholds[j].UpThreshold - curveCfg.TempThresholds[j].DownThreshold); - if (!LogECWriteByte(cfg.DownThresholdRegs[j - 1], downT)) - { - success = false; - } + success = false; } } } + } - // Write the charge threshold: - if (Config.ChargeLimitConf is not null) + // Write the charge threshold: + if (Config.ChargeLimitConf is not null) + { + Log.Info(Strings.GetString("svcWritingChgLim")); + byte value = (byte)(Config.ChargeLimitConf.MinVal + Config.ChargeLimitConf.CurVal); + if (!LogECWriteByte(Config.ChargeLimitConf.Reg, value)) { - Log.Info(Strings.GetString("svcWritingChgLim")); - byte value = (byte)(Config.ChargeLimitConf.MinVal + Config.ChargeLimitConf.CurVal); - if (!LogECWriteByte(Config.ChargeLimitConf.Reg, value)) - { - success = false; - } + success = false; } + } - // Write the performance mode - if (Config.PerfModeConf is not null) + // Write the performance mode + if (Config.PerfModeConf is not null) + { + Log.Info(Strings.GetString("svcWritingPerfMode")); + byte value = Config.PerfModeConf.PerfModes[Config.PerfModeConf.ModeSel].Value; + if (!LogECWriteByte(Config.PerfModeConf.Reg, value)) { - Log.Info(Strings.GetString("svcWritingPerfMode")); - byte value = Config.PerfModeConf.PerfModes[Config.PerfModeConf.ModeSel].Value; - if (!LogECWriteByte(Config.PerfModeConf.Reg, value)) - { - success = false; - } + success = false; } + } - // Write the Win/Fn key swap setting - if (Config.KeySwapConf is not null) - { - Log.Info(Strings.GetString("svcWritingKeySwap")); - byte value = Config.KeySwapConf.Enabled - ? Config.KeySwapConf.OnVal - : Config.KeySwapConf.OffVal; + // Write the Win/Fn key swap setting + if (Config.KeySwapConf is not null) + { + Log.Info(Strings.GetString("svcWritingKeySwap")); + byte value = Config.KeySwapConf.Enabled + ? Config.KeySwapConf.OnVal + : Config.KeySwapConf.OffVal; - if (!LogECWriteByte(Config.KeySwapConf.Reg, value)) - { - success = false; - } - } - return success; - } - - /// - /// Parse arguments from a given string. - /// - /// - /// The string containing the space-delimited arguments. - /// - /// - /// The parsed arguments. Will be empty if parsing fails. - /// - /// - /// true if the arguments were parsed successfully, - /// otherise false. - /// - private static bool ParseArgs(string argsIn, out int[] argsOut) - { - if (string.IsNullOrEmpty(argsIn)) + if (!LogECWriteByte(Config.KeySwapConf.Reg, value)) { - argsOut = []; + success = false; } - else - { - string[] args_str = argsIn.Split(' '); - argsOut = new int[args_str.Length]; + } + return success; + } + + /// + /// Parse arguments from a given string. + /// + /// + /// The string containing the space-delimited arguments. + /// + /// + /// The parsed arguments. Will be empty if parsing fails. + /// + /// + /// true if the arguments were parsed successfully, + /// otherise false. + /// + private static bool ParseArgs(string argsIn, out int[] argsOut) + { + if (string.IsNullOrEmpty(argsIn)) + { + argsOut = []; + } + else + { + string[] args_str = argsIn.Split(' '); + argsOut = new int[args_str.Length]; - for (int i = 0; i < args_str.Length; i++) + for (int i = 0; i < args_str.Length; i++) + { + if (!int.TryParse(args_str[i], out argsOut[i])) { - if (!int.TryParse(args_str[i], out argsOut[i])) - { - return false; - } + return false; } } + } + return true; + } + + private bool GetFanSpeed(int clientId, int fan) + { + if (Config is null) + { + return false; + } + + FanConf cfg = Config.FanConfs[fan]; + + if (LogECReadByte(cfg.SpeedReadReg, out byte speed)) + { + IPCServer.PushMessage(new ServiceResponse( + Response.FanSpeed, $"{speed}"), clientId); return true; } + return false; + } - private bool GetFanSpeed(int clientId, int fan) + private bool GetFanRPM(int clientId, int fan) + { + if (Config?.FanConfs[fan].RPMConf is null) { - if (Config is null) - { - return false; - } + return false; + } - FanConf cfg = Config.FanConfs[fan]; + FanConf cfg = Config.FanConfs[fan]; + + bool success; + ushort rpmValue; + if (cfg.RPMConf.Is16Bit) + { + success = LogECReadWord(cfg.RPMConf.ReadReg, out rpmValue, cfg.RPMConf.IsBigEndian); + } + else + { + success = LogECReadByte(cfg.RPMConf.ReadReg, out byte rpmValByte); + rpmValue = rpmValByte; + } - if (LogECReadByte(cfg.SpeedReadReg, out byte speed)) + if (success) + { + float rpm = 0; + if (rpmValue > 0) { - IPCServer.PushMessage(new ServiceResponse( - Response.FanSpeed, $"{speed}"), clientId); - return true; + rpm = cfg.RPMConf.DivideByMult + ? (float)rpmValue / cfg.RPMConf.RPMMult + : (float)rpmValue * cfg.RPMConf.RPMMult; + + if (cfg.RPMConf.Invert) + { + rpm = 1 / rpm; + } } + IPCServer.PushMessage(new ServiceResponse( + Response.FanRPM, $"{(int)rpm}"), clientId); + return true; + } + return false; + } + + private bool GetTemp(int clientId, int fan) + { + if (Config is null) + { return false; } - private bool GetFanRPM(int clientId, int fan) + FanConf cfg = Config.FanConfs[fan]; + + if (LogECReadByte(cfg.TempReadReg, out byte temp)) { - if (Config?.FanConfs[fan].RPMConf is null) - { - return false; - } + IPCServer.PushMessage(new ServiceResponse( + Response.Temp, $"{temp}"), clientId); + return true; + } + return false; + } - FanConf cfg = Config.FanConfs[fan]; + private bool SetFullBlast(bool enable) + { + if (Config?.FullBlastConf is null) + { + return false; + } - bool success; - ushort rpmValue; - if (cfg.RPMConf.Is16Bit) + if (LogECReadByte(Config.FullBlastConf.Reg, out byte value)) + { + if (enable) { - success = LogECReadWord(cfg.RPMConf.ReadReg, out rpmValue, cfg.RPMConf.IsBigEndian); + Log.Debug("Enabling Full Blast..."); + value |= Config.FullBlastConf.Mask; } else { - success = LogECReadByte(cfg.RPMConf.ReadReg, out byte rpmValByte); - rpmValue = rpmValByte; + Log.Debug("Disabling Full Blast..."); + value &= (byte)~Config.FullBlastConf.Mask; } - if (success) + if (LogECWriteByte(Config.FullBlastConf.Reg, value)) { - float rpm = 0; - if (rpmValue > 0) - { - rpm = cfg.RPMConf.DivideByMult - ? (float)rpmValue / cfg.RPMConf.RPMMult - : (float)rpmValue * cfg.RPMConf.RPMMult; - - if (cfg.RPMConf.Invert) - { - rpm = 1 / rpm; - } - } - IPCServer.PushMessage(new ServiceResponse( - Response.FanRPM, $"{(int)rpm}"), clientId); return true; } + } + return false; + } + + private bool GetKeyLightBright(int clientId) + { + if (Config?.KeyLightConf is null) + { return false; } - private bool GetTemp(int clientId, int fan) + Log.Debug(Strings.GetString("svcGetKeyLightBright")); + + if (LogECReadByte(Config.KeyLightConf.Reg, out byte value) && + value >= Config.KeyLightConf.MinVal && value <= Config.KeyLightConf.MaxVal) { - if (Config is null) - { - return false; - } + int brightness = value - Config.KeyLightConf.MinVal; - FanConf cfg = Config.FanConfs[fan]; + IPCServer.PushMessage(new ServiceResponse( + Response.KeyLightBright, $"{brightness}"), clientId); + return true; + } + return false; + } - if (LogECReadByte(cfg.TempReadReg, out byte temp)) - { - IPCServer.PushMessage(new ServiceResponse( - Response.Temp, $"{temp}"), clientId); - return true; - } + private bool SetKeyLightBright(byte brightness) + { + if (Config?.KeyLightConf is null) + { return false; } - private bool SetFullBlast(bool enable) - { - if (Config?.FullBlastConf is null) - { - return false; - } + Log.Debug(Strings.GetString("svcSetKeyLightBright", brightness)); - if (LogECReadByte(Config.FullBlastConf.Reg, out byte value)) - { - if (enable) - { - Log.Debug("Enabling Full Blast..."); - value |= Config.FullBlastConf.Mask; - } - else - { - Log.Debug("Disabling Full Blast..."); - value &= (byte)~Config.FullBlastConf.Mask; - } + return LogECWriteByte(Config.KeyLightConf.Reg, (byte)(brightness + Config.KeyLightConf.MinVal)); + } - if (LogECWriteByte(Config.FullBlastConf.Reg, value)) - { - return true; - } - } + private bool ECToConf() + { + if (Config is null) + { return false; } - private bool GetKeyLightBright(int clientId) + try { - if (Config?.KeyLightConf is null) - { - return false; - } + Log.Info(Strings.GetString("svcReadingModel")); - Log.Debug(Strings.GetString("svcGetKeyLightBright")); + string pcManufacturer = GetPCManufacturer(), + pcModel = GetPCModel(); - if (LogECReadByte(Config.KeyLightConf.Reg, out byte value) && - value >= Config.KeyLightConf.MinVal && value <= Config.KeyLightConf.MaxVal) + if (string.IsNullOrEmpty(pcManufacturer)) { - int brightness = value - Config.KeyLightConf.MinVal; - - IPCServer.PushMessage(new ServiceResponse( - Response.KeyLightBright, $"{brightness}"), clientId); - return true; + Log.Error(Strings.GetString("errReadManufacturer")); } - return false; - } - - private bool SetKeyLightBright(byte brightness) - { - if (Config?.KeyLightConf is null) + else { - return false; + Config.Manufacturer = pcManufacturer.Trim(); } - Log.Debug(Strings.GetString("svcSetKeyLightBright", brightness)); - - return LogECWriteByte(Config.KeyLightConf.Reg, (byte)(brightness + Config.KeyLightConf.MinVal)); - } - - private bool ECToConf() - { - if (Config is null) + if (string.IsNullOrEmpty(pcModel)) + { + Log.Error(Strings.GetString("errReadModel")); + } + else { - return false; + Config.Model = pcModel.Trim(); } - try + for (int i = 0; i < Config.FanConfs.Length; i++) { - Log.Info(Strings.GetString("svcReadingModel")); + Log.Info(Strings.GetString("svcReadingCurves", i + 1, Config.FanConfs.Length)); - string pcManufacturer = GetPCManufacturer(), - pcModel = GetPCModel(); + FanConf cfg = Config.FanConfs[i]; - if (string.IsNullOrEmpty(pcManufacturer)) + FanCurveConf curveCfg = null; + for (int j = 0; j < cfg.FanCurveConfs.Length; j++) { - Log.Error(Strings.GetString("errReadManufacturer")); - } - else - { - Config.Manufacturer = pcManufacturer.Trim(); + if (cfg.FanCurveConfs[j].Name == "Default") + { + curveCfg = cfg.FanCurveConfs[j]; + } } - if (string.IsNullOrEmpty(pcModel)) + // there isn't already a Default fan profile in this config, + // make one and insert it at the start + if (curveCfg is null) { - Log.Error(Strings.GetString("errReadModel")); - } - else - { - Config.Model = pcModel.Trim(); + curveCfg = new() + { + TempThresholds = new TempThreshold[cfg.FanCurveRegs.Length] + }; + List curveCfgList = [.. cfg.FanCurveConfs]; + curveCfgList.Insert(0, curveCfg); + cfg.FanCurveConfs = [.. curveCfgList]; + cfg.CurveSel++; } - for (int i = 0; i < Config.FanConfs.Length; i++) - { - Log.Info(Strings.GetString("svcReadingCurves", i + 1, Config.FanConfs.Length)); - - FanConf cfg = Config.FanConfs[i]; + // reset first fan curve config name and description + curveCfg.Name = "Default"; + curveCfg.Desc = Strings.GetString("confDefaultDesc"); - FanCurveConf curveCfg = null; - for (int j = 0; j < cfg.FanCurveConfs.Length; j++) + for (int j = 0; j < cfg.FanCurveRegs.Length; j++) + { + if (curveCfg.TempThresholds[j] is null) { - if (cfg.FanCurveConfs[j].Name == "Default") - { - curveCfg = cfg.FanCurveConfs[j]; - } + curveCfg.TempThresholds[j] = new(); } - // there isn't already a Default fan profile in this config, - // make one and insert it at the start - if (curveCfg is null) + if (LogECReadByte(cfg.FanCurveRegs[j], out byte value)) { - curveCfg = new() - { - TempThresholds = new TempThreshold[cfg.FanCurveRegs.Length] - }; - List curveCfgList = [.. cfg.FanCurveConfs]; - curveCfgList.Insert(0, curveCfg); - cfg.FanCurveConfs = [.. curveCfgList]; - cfg.CurveSel++; + curveCfg.TempThresholds[j].FanSpeed = value; } - // reset first fan curve config name and description - curveCfg.Name = "Default"; - curveCfg.Desc = Strings.GetString("confDefaultDesc"); - - for (int j = 0; j < cfg.FanCurveRegs.Length; j++) + if (j == 0) { - if (curveCfg.TempThresholds[j] is null) - { - curveCfg.TempThresholds[j] = new(); - } - - if (LogECReadByte(cfg.FanCurveRegs[j], out byte value)) - { - curveCfg.TempThresholds[j].FanSpeed = value; - } - - if (j == 0) + curveCfg.TempThresholds[j].UpThreshold = 0; + curveCfg.TempThresholds[j].DownThreshold = 0; + } + else + { + if (LogECReadByte(cfg.UpThresholdRegs[j - 1], out value)) { - curveCfg.TempThresholds[j].UpThreshold = 0; - curveCfg.TempThresholds[j].DownThreshold = 0; + curveCfg.TempThresholds[j].UpThreshold = value; } - else + if (LogECReadByte(cfg.DownThresholdRegs[j - 1], out value)) { - if (LogECReadByte(cfg.UpThresholdRegs[j - 1], out value)) - { - curveCfg.TempThresholds[j].UpThreshold = value; - } - if (LogECReadByte(cfg.DownThresholdRegs[j - 1], out value)) - { - curveCfg.TempThresholds[j].DownThreshold = (byte)(curveCfg.TempThresholds[j].UpThreshold - value); - } + curveCfg.TempThresholds[j].DownThreshold = (byte)(curveCfg.TempThresholds[j].UpThreshold - value); } } } + } - Log.Info("Saving config..."); - Config.Save(Paths.CurrentConfig); + Log.Info("Saving config..."); + Config.Save(Paths.CurrentConfig); - FileStream fs = File.Create(Paths.ECToConfSuccess); - fs.Close(); - return true; - } - catch - { - FileStream fs = File.Create(Paths.ECToConfFail); - fs.Close(); - return false; - } + FileStream fs = File.Create(Paths.ECToConfSuccess); + fs.Close(); + return true; } - - /// - /// Gets the computer model name from registry. - /// - /// - /// The computer model if the function succeeds, - /// otherwise null. - /// - private static string GetPCModel() + catch { - return GetBIOSRegValue("SystemProductName"); + FileStream fs = File.Create(Paths.ECToConfFail); + fs.Close(); + return false; } + } - /// - /// Gets the computer manufacturer from registry. - /// - /// - /// The computer manufacturer if the function succeeds, - /// otherwise null. - /// - private static string GetPCManufacturer() - { - return GetBIOSRegValue("SystemManufacturer"); - } + /// + /// Gets the computer model name from registry. + /// + /// + /// The computer model if the function succeeds, + /// otherwise null. + /// + private static string GetPCModel() + { + return GetBIOSRegValue("SystemProductName"); + } + + /// + /// Gets the computer manufacturer from registry. + /// + /// + /// The computer manufacturer if the function succeeds, + /// otherwise null. + /// + private static string GetPCManufacturer() + { + return GetBIOSRegValue("SystemManufacturer"); + } - private static string GetBIOSRegValue(string name) + private static string GetBIOSRegValue(string name) + { + RegistryKey biosKey = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS"); + try { - RegistryKey biosKey = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS"); - try - { - return (string)biosKey?.GetValue(name, null); - } - finally - { - biosKey?.Close(); - } + return (string)biosKey?.GetValue(name, null); } - - private static string GetWin32Error(int error) + finally { - return new Win32Exception(error).Message; + biosKey?.Close(); } } + + private static string GetWin32Error(int error) + { + return new Win32Exception(error).Message; + } } diff --git a/YAMDCC.Service/Program.cs b/YAMDCC.Service/Program.cs index c4f39ed..5ad8da7 100644 --- a/YAMDCC.Service/Program.cs +++ b/YAMDCC.Service/Program.cs @@ -20,48 +20,47 @@ using YAMDCC.Common; using YAMDCC.Logs; -namespace YAMDCC.Service +namespace YAMDCC.Service; + +internal static class Program { - internal static class Program + /// + /// The instance to write logs to. + /// + private static readonly Logger Log = new() { - /// - /// The instance to write logs to. - /// - private static readonly Logger Log = new() - { - LogDir = Paths.Logs, - ConsoleLogLevel = LogLevel.None, - FileLogLevel = LogLevel.Debug, - }; + LogDir = Paths.Logs, + ConsoleLogLevel = LogLevel.None, + FileLogLevel = LogLevel.Debug, + }; - /// - /// The main entry point for the application. - /// - private static void Main() + /// + /// The main entry point for the application. + /// + private static void Main() + { + AppDomain.CurrentDomain.UnhandledException += static (sender, e) => + Log.Fatal(Strings.GetString("svcException"), e.ExceptionObject); + + if (Environment.UserInteractive) { - AppDomain.CurrentDomain.UnhandledException += static (sender, e) => - Log.Fatal(Strings.GetString("svcException"), e.ExceptionObject); + MessageBox.Show(Strings.GetString("errDirectRun"), + "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + else + { + CommonConfig cfg = CommonConfig.Load(); + Log.Info( + $"OS version: {Environment.OSVersion}\n" + + $"Service version: {Application.ProductVersion}"); - if (Environment.UserInteractive) + if (cfg.App == "YAMDCC") { - MessageBox.Show(Strings.GetString("errDirectRun"), - "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + Log.FileLogLevel = cfg.LogLevel; } - else - { - CommonConfig cfg = CommonConfig.Load(); - Log.Info( - $"OS version: {Environment.OSVersion}\n" + - $"Service version: {Application.ProductVersion}"); - if (cfg.App == "YAMDCC") - { - Log.FileLogLevel = cfg.LogLevel; - } - - Log.Debug("Log level is set to debug mode."); - ServiceBase.Run(new FanControlService(Log)); - } + Log.Debug("Log level is set to debug mode."); + ServiceBase.Run(new FanControlService(Log)); } } } diff --git a/YAMDCC.Service/ProjectInstaller.cs b/YAMDCC.Service/ProjectInstaller.cs index a0179cc..4cf83d7 100644 --- a/YAMDCC.Service/ProjectInstaller.cs +++ b/YAMDCC.Service/ProjectInstaller.cs @@ -18,31 +18,30 @@ using System.Configuration.Install; using System.ServiceProcess; -namespace YAMDCC.Service +namespace YAMDCC.Service; + +[RunInstaller(true)] +public class ProjectInstaller : Installer { - [RunInstaller(true)] - public class ProjectInstaller : Installer + public ProjectInstaller() { - public ProjectInstaller() + ServiceInstaller svcInstaller = new() { - ServiceInstaller svcInstaller = new() - { - Description = Strings.GetString("svcDesc"), - DisplayName = "YAMDCC Service", - ServiceName = "yamdccsvc", - StartType = ServiceStartMode.Automatic - }; + Description = Strings.GetString("svcDesc"), + DisplayName = "YAMDCC Service", + ServiceName = "yamdccsvc", + StartType = ServiceStartMode.Automatic + }; - ServiceProcessInstaller svcPInstaller = new() - { - Account = ServiceAccount.LocalSystem - }; + ServiceProcessInstaller svcPInstaller = new() + { + Account = ServiceAccount.LocalSystem + }; - Installers.AddRange( - [ - svcPInstaller, - svcInstaller - ]); - } + Installers.AddRange( + [ + svcPInstaller, + svcInstaller + ]); } } diff --git a/YAMDCC.Service/Strings.cs b/YAMDCC.Service/Strings.cs index ade5758..d39e6b9 100644 --- a/YAMDCC.Service/Strings.cs +++ b/YAMDCC.Service/Strings.cs @@ -17,67 +17,66 @@ using System.Globalization; using System.Resources; -namespace YAMDCC.Service +namespace YAMDCC.Service; + +/// +/// A resource class for retrieving strings. +/// +internal static class Strings { + private static ResourceManager resMan; + /// - /// A resource class for retrieving strings. + /// Gets a string from the underlying resource file. /// - internal static class Strings + /// + /// This function internally calls + /// to retrieve the string. + /// + /// + /// The name of the string to find. + /// + /// + /// The value of the specified string name, if found. + /// null if the string couldn't be found. + /// + public static string GetString(string name) { - private static ResourceManager resMan; - - /// - /// Gets a string from the underlying resource file. - /// - /// - /// This function internally calls - /// to retrieve the string. - /// - /// - /// The name of the string to find. - /// - /// - /// The value of the specified string name, if found. - /// null if the string couldn't be found. - /// - public static string GetString(string name) - { - resMan ??= new ResourceManager(typeof(Strings)); - return resMan.GetString(name, CultureInfo.InvariantCulture); - } + resMan ??= new ResourceManager(typeof(Strings)); + return resMan.GetString(name, CultureInfo.InvariantCulture); + } - /// - /// Gets a string from the underlying resource file, and replaces format - /// items with the specified object's string representation. - /// - /// - /// The name of the string to find. - /// - /// - /// The object to format the string with. - /// - /// - /// The formatted string corresponding to the specified string name, if found. - /// null if the string couldn't be found. - /// - public static string GetString(string name, object arg0) - { - string temp = GetString(name); - return temp is null - ? null - : string.Format(CultureInfo.InvariantCulture, temp, arg0); - } + /// + /// Gets a string from the underlying resource file, and replaces format + /// items with the specified object's string representation. + /// + /// + /// The name of the string to find. + /// + /// + /// The object to format the string with. + /// + /// + /// The formatted string corresponding to the specified string name, if found. + /// null if the string couldn't be found. + /// + public static string GetString(string name, object arg0) + { + string temp = GetString(name); + return temp is null + ? null + : string.Format(CultureInfo.InvariantCulture, temp, arg0); + } - /// - /// - /// The objects to format the string with. - /// - public static string GetString(string name, params object[] args) - { - string temp = GetString(name); - return temp is null - ? null - : string.Format(CultureInfo.InvariantCulture, temp, args); - } + /// + /// + /// The objects to format the string with. + /// + public static string GetString(string name, params object[] args) + { + string temp = GetString(name); + return temp is null + ? null + : string.Format(CultureInfo.InvariantCulture, temp, args); } }