first commit
This commit is contained in:
17
NitroxModel/Platforms/OS/MacOS/MacFileSystem.cs
Normal file
17
NitroxModel/Platforms/OS/MacOS/MacFileSystem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Platforms.OS.Shared;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.MacOS;
|
||||
|
||||
public sealed class MacFileSystem : FileSystem
|
||||
{
|
||||
public override IEnumerable<string> GetDefaultPrograms(string file)
|
||||
{
|
||||
yield return "open";
|
||||
}
|
||||
|
||||
public override bool SetFullAccessToCurrentUser(string directory)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
130
NitroxModel/Platforms/OS/Shared/ConfigFileKeyValueStore.cs
Normal file
130
NitroxModel/Platforms/OS/Shared/ConfigFileKeyValueStore.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Shared;
|
||||
|
||||
public class ConfigFileKeyValueStore : IKeyValueStore
|
||||
{
|
||||
private bool hasLoaded = false;
|
||||
private readonly Dictionary<string, object> keyValuePairs = new();
|
||||
public string FolderPath { get; }
|
||||
public string FilePath => Path.Combine(FolderPath, "nitrox.cfg");
|
||||
|
||||
public ConfigFileKeyValueStore()
|
||||
{
|
||||
// LocalApplicationData's default is $HOME/.config under linux and XDG_CONFIG_HOME if set
|
||||
// What is the difference between .config and .local/share?
|
||||
// .config should contain all config files.
|
||||
// .local/share should contain data that isn't config files (binary blobs, downloaded data, server saves).
|
||||
// .cache should house all cache files (files that can be safely deleted to free up space)
|
||||
string localShare = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
if (string.IsNullOrEmpty(localShare))
|
||||
{
|
||||
throw new Exception("Could not determine where to save configs. Check HOME and XDG_CONFIG_HOME variables.");
|
||||
}
|
||||
FolderPath = Path.Combine(localShare, "Nitrox");
|
||||
}
|
||||
|
||||
public T GetValue<T>(string key, T defaultValue)
|
||||
{
|
||||
TryLoadConfig();
|
||||
bool succeeded = keyValuePairs.TryGetValue(key, out object obj);
|
||||
if (!succeeded)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
if (obj is JsonElement element)
|
||||
{
|
||||
// System.Text.Json stores objects as JsonElement
|
||||
try
|
||||
{
|
||||
return element.Deserialize<T>();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
// if a value has been added at runtime and not deserialized, it should be casted directly
|
||||
try
|
||||
{
|
||||
return (T)obj;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetValue<T>(string key, T value)
|
||||
{
|
||||
TryLoadConfig();
|
||||
keyValuePairs[key] = value;
|
||||
TrySaveConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
public (bool success, Exception error) TrySaveConfig()
|
||||
{
|
||||
// saving configs isn't critical, if it fails the values will still exists at runtime, but won't be loaded the next time you start up Nitrox.
|
||||
try
|
||||
{
|
||||
// Create directories if they don't already exist
|
||||
Directory.CreateDirectory(FolderPath);
|
||||
|
||||
// serialize the keyValuePairs
|
||||
string serialized = JsonSerializer.Serialize(keyValuePairs, new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
// try to write the file
|
||||
File.WriteAllText(FilePath, serialized);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return (false, e);
|
||||
}
|
||||
}
|
||||
|
||||
private (bool success, Exception error) TryLoadConfig()
|
||||
{
|
||||
if (hasLoaded)
|
||||
{
|
||||
return (true, null);
|
||||
}
|
||||
Dictionary<string, object> deserialized;
|
||||
try
|
||||
{
|
||||
deserialized = JsonSerializer.Deserialize<Dictionary<string, object>>(File.ReadAllText(FilePath));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return (false, e);
|
||||
}
|
||||
if (deserialized == null)
|
||||
{
|
||||
return (false, new Exception("Deserialized object was null"));
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, object> item in deserialized)
|
||||
{
|
||||
keyValuePairs[item.Key] = item.Value;
|
||||
}
|
||||
hasLoaded = true;
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
public bool DeleteKey(string key)
|
||||
{
|
||||
if (!keyValuePairs.Remove(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TrySaveConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool KeyExists(string key) => keyValuePairs.ContainsKey(key);
|
||||
}
|
279
NitroxModel/Platforms/OS/Shared/FileSystem.cs
Normal file
279
NitroxModel/Platforms/OS/Shared/FileSystem.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using NitroxModel.Platforms.OS.MacOS;
|
||||
using NitroxModel.Platforms.OS.Unix;
|
||||
using NitroxModel.Platforms.OS.Windows;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Shared;
|
||||
|
||||
public abstract class FileSystem
|
||||
{
|
||||
private static readonly Lazy<FileSystem> instance = new(() =>
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return new WinFileSystem();
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return new UnixFileSystem();
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return new MacFileSystem();
|
||||
}
|
||||
throw new NotSupportedException("Current OS is not supported");
|
||||
},
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
public virtual IEnumerable<string> ExecutableFileExtensions => throw new NotSupportedException();
|
||||
public static FileSystem Instance => instance.Value;
|
||||
public virtual string TextEditor => throw new NotSupportedException();
|
||||
|
||||
public virtual IEnumerable<string> GetDefaultPrograms(string file) => throw new NotSupportedException();
|
||||
|
||||
/// <summary>
|
||||
/// Opens the file with the default associated program or the default editor of the OS.
|
||||
/// The returned <see cref="Process" /> should be disposed.
|
||||
/// </summary>
|
||||
/// <param name="file">File or program to open or execute.</param>
|
||||
/// <returns>Instance of a running process. Should be disposed.</returns>
|
||||
public virtual Process OpenOrExecuteFile(string file)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(file))
|
||||
{
|
||||
throw new ArgumentException("File path must not be null or empty.", nameof(file));
|
||||
}
|
||||
|
||||
string editorProgram = GetDefaultPrograms(file).FirstOrDefault() ?? TextEditor;
|
||||
// Handle special arguments for popular editors.
|
||||
string arguments = Path.GetFileName(editorProgram)?.ToLowerInvariant() switch
|
||||
{
|
||||
"code.cmd" => "--wait", // Allow to wait on VS code
|
||||
_ => ""
|
||||
};
|
||||
|
||||
return Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = editorProgram,
|
||||
Arguments = $@"{(arguments.Length > 0 ? $"{arguments} " : "")}""{file}""",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path to a file or program. Searches the PATH environment variables if file could not be found
|
||||
/// relatively. Returns null if not found.
|
||||
/// </summary>
|
||||
/// <param name="fileName">File or program name to get the full path from.</param>
|
||||
/// <returns></returns>
|
||||
public string GetFullPath(string fileName)
|
||||
{
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
return Path.GetFullPath(fileName);
|
||||
}
|
||||
string values = Environment.GetEnvironmentVariable("PATH");
|
||||
if (values == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
fileName = Path.GetFileName(fileName);
|
||||
// Always test filename in system lib root first, then other paths. On UNIX systems the path is case-sensitive.
|
||||
IEnumerable<string> pathsToTools = new[] { Environment.SystemDirectory }.Concat(values.Split(Path.PathSeparator)).Distinct();
|
||||
foreach (string path in pathsToTools)
|
||||
{
|
||||
string fullPath = Path.Combine(path, fileName);
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string MakeRelativePath(string fromPath, string toPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fromPath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fromPath));
|
||||
}
|
||||
if (string.IsNullOrEmpty(toPath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(toPath));
|
||||
}
|
||||
// Ensure postfix so that result becomes relative to entire "from" path.
|
||||
fromPath = fromPath[fromPath.Length - 1] == Path.DirectorySeparatorChar ? fromPath : fromPath + Path.DirectorySeparatorChar;
|
||||
Uri fromUri = new(fromPath);
|
||||
Uri toUri = new(toPath);
|
||||
// Can path be made relative?
|
||||
if (fromUri.Scheme != toUri.Scheme)
|
||||
{
|
||||
return toPath;
|
||||
}
|
||||
|
||||
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
|
||||
string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
|
||||
if (toUri.Scheme.Equals("file", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
}
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zips the files found in the given directory.
|
||||
/// </summary>
|
||||
/// <param name="dir">Directory to search for files to zip.</param>
|
||||
/// <param name="outputPath">
|
||||
/// Name of output zip, optionally including full path. If null, uses the directory name given by
|
||||
/// <see cref="dir" />.
|
||||
/// </param>
|
||||
/// <param name="fileSearchPattern">Search pattern used to filter against files to zip.</param>
|
||||
/// <param name="replaceFile">If true, overwrites the file matching the output path.</param>
|
||||
/// <returns>Full path to the newly created zip or null if no files are found to zip.</returns>
|
||||
/// <exception cref="IOException">If zip file already exists.</exception>
|
||||
public string ZipFilesInDirectory(string dir, string outputPath = null, string fileSearchPattern = "*", bool replaceFile = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
throw new ArgumentException("Directory must not be null or empty", nameof(dir));
|
||||
}
|
||||
dir = Path.GetFullPath(dir);
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
throw new ArgumentException("Path is not a directory", nameof(dir));
|
||||
}
|
||||
// Figure out relative path of output OR use <basename>.zip of directory.
|
||||
outputPath = Path.GetFullPath(outputPath ?? dir);
|
||||
string outZipName = Path.GetFileName(outputPath);
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(outZipName)))
|
||||
{
|
||||
outZipName = Path.ChangeExtension(outZipName, ".zip");
|
||||
}
|
||||
string outZipDir = Path.GetDirectoryName(outputPath) ?? dir;
|
||||
string outZipFullName = Path.Combine(outZipDir, outZipName);
|
||||
if (!replaceFile && File.Exists(outZipFullName))
|
||||
{
|
||||
throw new IOException($"The file '{outZipFullName}' already exists");
|
||||
}
|
||||
string[] files = Directory.GetFiles(dir, fileSearchPattern, SearchOption.AllDirectories);
|
||||
if (files.Length < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create the zip.
|
||||
Directory.CreateDirectory(outZipDir);
|
||||
using ZipArchive zip = new(File.Create(outZipFullName), ZipArchiveMode.Create);
|
||||
foreach (string file in files)
|
||||
{
|
||||
ZipArchiveEntry entry = zip.CreateEntry(MakeRelativePath(dir, file));
|
||||
using Stream sourceStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using Stream targetStream = entry.Open();
|
||||
sourceStream.CopyTo(targetStream);
|
||||
}
|
||||
|
||||
return outZipFullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces target file with source file. If target file does not exist then it moves the file.
|
||||
/// This falls back to a copy if the target is on a different drive.
|
||||
/// The source file will always be deleted.
|
||||
/// </summary>
|
||||
/// <param name="source">Source file to replace with.</param>
|
||||
/// <param name="target">Target file to replace.</param>
|
||||
/// <returns>True if file was moved or replaced successfully.</returns>
|
||||
public bool ReplaceFile(string source, string target)
|
||||
{
|
||||
if (!File.Exists(source))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
source = Path.GetFullPath(source);
|
||||
|
||||
if (!File.Exists(target))
|
||||
{
|
||||
File.Move(source, target);
|
||||
return true;
|
||||
}
|
||||
try
|
||||
{
|
||||
File.Replace(source, target, null, false);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// TODO: Need to test on Linux because the ex.HResult will likely not work or be different number cross-platform.
|
||||
switch ((uint)ex.HResult)
|
||||
{
|
||||
case 0x80070498:
|
||||
// Tried to replace file between drives. This does not work, need to do so in steps.
|
||||
try
|
||||
{
|
||||
string originalExtension = Path.GetExtension(target);
|
||||
originalExtension = string.IsNullOrWhiteSpace(originalExtension) ? "" : $".{originalExtension}";
|
||||
string backupFileName = $"{Path.GetFileNameWithoutExtension(target)}_{Path.GetFileNameWithoutExtension(Path.GetTempFileName())}{originalExtension}.bak";
|
||||
Log.Debug($"Renaming file '{target}' to '{backupFileName}' as backup plan if file replace fails");
|
||||
File.Move(target, backupFileName);
|
||||
File.Copy(source, target);
|
||||
|
||||
// Cleanup redundant files, ignoring errors.
|
||||
try
|
||||
{
|
||||
File.Delete(source);
|
||||
File.Delete(backupFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Log.Error(ex2, $"Failed to replace file '{source}' with '{target}' which is on another drive");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// No special handling implemented for error, abort.
|
||||
Log.Warn($"Unhandled file replace of '{source}' with '{target}' with HRESULT: 0x{ex.HResult:X}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool IsWritable(string directory)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
string randFileName = Path.GetRandomFileName();
|
||||
try
|
||||
{
|
||||
File.Create(Path.Combine(directory, randFileName)).Close();
|
||||
File.Delete(Path.Combine(directory, randFileName));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract bool SetFullAccessToCurrentUser(string directory);
|
||||
|
||||
public virtual bool IsTrustedFile(string file) => true;
|
||||
}
|
738
NitroxModel/Platforms/OS/Shared/ProcessEx.cs
Normal file
738
NitroxModel/Platforms/OS/Shared/ProcessEx.cs
Normal file
@@ -0,0 +1,738 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Shared;
|
||||
|
||||
public class ProcessEx : IDisposable
|
||||
{
|
||||
private readonly ProcessExBase implementation;
|
||||
|
||||
public int Id => implementation.Id;
|
||||
public string Name => implementation.Name;
|
||||
public IntPtr Handle => implementation.Handle;
|
||||
public ProcessModuleEx MainModule => implementation.MainModule;
|
||||
public string MainModuleFileName => implementation.MainModuleFileName;
|
||||
public IntPtr MainWindowHandle => implementation.MainWindowHandle;
|
||||
public string MainWindowTitle => implementation.MainWindowTitle;
|
||||
|
||||
/// <summary>
|
||||
/// True if process is running and in a recoverable state.
|
||||
/// </summary>
|
||||
public bool IsRunning => implementation.IsRunning;
|
||||
|
||||
public ProcessEx(int pid)
|
||||
{
|
||||
implementation = ProcessExFactory.Create(pid);
|
||||
}
|
||||
|
||||
public ProcessEx(Process process)
|
||||
{
|
||||
implementation = ProcessExFactory.Create(process.Id);
|
||||
}
|
||||
|
||||
public static bool ProcessExists(string procName, Func<ProcessEx, bool> predicate = null)
|
||||
{
|
||||
ProcessEx proc = null;
|
||||
try
|
||||
{
|
||||
proc = GetFirstProcess(procName, predicate);
|
||||
return proc != null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
proc?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static ProcessEx Start(string fileName = null, IEnumerable<(string, string)> environmentVariables = null, string workingDirectory = null, string commandLine = null, bool createWindow = true)
|
||||
{
|
||||
ProcessStartInfo startInfo = new()
|
||||
{
|
||||
FileName = fileName,
|
||||
WorkingDirectory = workingDirectory,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = !createWindow
|
||||
};
|
||||
|
||||
if (environmentVariables != null)
|
||||
{
|
||||
foreach ((string key, string value) in environmentVariables)
|
||||
{
|
||||
startInfo.EnvironmentVariables[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(commandLine))
|
||||
{
|
||||
startInfo.Arguments = commandLine;
|
||||
}
|
||||
|
||||
Process process = Process.Start(startInfo);
|
||||
return new ProcessEx(process);
|
||||
}
|
||||
|
||||
public static ProcessEx GetFirstProcess(string procName, Func<ProcessEx, bool> predicate = null)
|
||||
{
|
||||
ProcessEx found = null;
|
||||
foreach (Process proc in Process.GetProcessesByName(procName))
|
||||
{
|
||||
// Already found, dispose all other process handles.
|
||||
if (found != null)
|
||||
{
|
||||
proc.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
ProcessEx procEx = new(proc);
|
||||
if (predicate != null && !predicate(procEx))
|
||||
{
|
||||
procEx.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
found = procEx;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public byte[] ReadMemory(IntPtr address, int size) => implementation.ReadMemory(address, size);
|
||||
|
||||
public int WriteMemory(IntPtr address, byte[] data) => implementation.WriteMemory(address, data);
|
||||
|
||||
public IEnumerable<ProcessModuleEx> GetModules() => implementation.GetModules();
|
||||
|
||||
public void Suspend() => implementation.Suspend();
|
||||
|
||||
public void Resume() => implementation.Resume();
|
||||
|
||||
public void Terminate() => implementation.Terminate();
|
||||
|
||||
public void Dispose() => implementation.Dispose();
|
||||
}
|
||||
|
||||
public abstract class ProcessExBase : IDisposable
|
||||
{
|
||||
protected readonly Process Process;
|
||||
public virtual int Id => Process?.Id ?? -1;
|
||||
public virtual string Name => Process?.ProcessName;
|
||||
public virtual IntPtr Handle => Process?.Handle ?? IntPtr.Zero;
|
||||
public abstract ProcessModuleEx MainModule { get; }
|
||||
public virtual string MainModuleFileName => Process?.MainModule?.FileName;
|
||||
public virtual IntPtr MainWindowHandle => Process?.MainWindowHandle ?? IntPtr.Zero;
|
||||
public virtual string MainWindowTitle => Process?.MainWindowTitle;
|
||||
|
||||
public virtual bool IsRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Process == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Process.Refresh();
|
||||
if (!Process.HasExited || Process.Responding)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected ProcessExBase(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process = Process.GetProcessById(id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public abstract byte[] ReadMemory(IntPtr address, int size);
|
||||
public abstract int WriteMemory(IntPtr address, byte[] data);
|
||||
public abstract IEnumerable<ProcessModuleEx> GetModules();
|
||||
public abstract void Suspend();
|
||||
public abstract void Resume();
|
||||
public abstract void Terminate();
|
||||
|
||||
public static bool IsElevated()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return WindowsProcessEx.IsElevated();
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return LinuxProcessEx.IsElevated();
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return MacOSProcessEx.IsElevated();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ProcessModuleEx
|
||||
{
|
||||
public IntPtr BaseAddress { get; set; }
|
||||
public string ModuleName { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public int ModuleMemorySize { get; set; }
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||
#endif
|
||||
public class WindowsProcessEx : ProcessExBase
|
||||
{
|
||||
private bool disposed;
|
||||
private IntPtr handle;
|
||||
|
||||
public override IntPtr Handle => handle;
|
||||
|
||||
public override ProcessModuleEx MainModule
|
||||
{
|
||||
get
|
||||
{
|
||||
ProcessModule mainModule = Process.MainModule;
|
||||
if (mainModule == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new ProcessModuleEx
|
||||
{
|
||||
BaseAddress = mainModule.BaseAddress,
|
||||
ModuleName = mainModule.ModuleName,
|
||||
FileName = mainModule.FileName,
|
||||
ModuleMemorySize = mainModule.ModuleMemorySize
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override string MainModuleFileName => Process.MainModule?.FileName;
|
||||
public override IntPtr MainWindowHandle => Process.MainWindowHandle;
|
||||
public override string MainWindowTitle => Process.MainWindowTitle;
|
||||
|
||||
public WindowsProcessEx(int id) : base(id)
|
||||
{
|
||||
if (!IsElevated())
|
||||
{
|
||||
throw new UnauthorizedAccessException("Elevated privileges required.");
|
||||
}
|
||||
|
||||
handle = OpenProcess(0x1F0FFF, false, id);
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
public new static bool IsElevated()
|
||||
{
|
||||
try
|
||||
{
|
||||
using WindowsIdentity identity = WindowsIdentity.GetCurrent();
|
||||
|
||||
WindowsPrincipal principal = new(identity);
|
||||
// If process has explicit admin privileges
|
||||
if (principal.IsInRole(WindowsBuiltInRole.Administrator))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise check if user is in admin group (https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers)
|
||||
string admininistratorSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Value;
|
||||
return principal.Claims.Any(claim => claim.Value == admininistratorSid);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override byte[] ReadMemory(IntPtr address, int size)
|
||||
{
|
||||
byte[] buffer = new byte[size];
|
||||
if (!ReadProcessMemory(handle, address, buffer, size, out int bytesRead) || bytesRead != size)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public override int WriteMemory(IntPtr address, byte[] data)
|
||||
{
|
||||
if (!WriteProcessMemory(handle, address, data, data.Length, out int bytesWritten))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
public override IEnumerable<ProcessModuleEx> GetModules()
|
||||
{
|
||||
return Process.Modules.Cast<ProcessModule>().Select(m => new ProcessModuleEx
|
||||
{
|
||||
BaseAddress = m.BaseAddress,
|
||||
ModuleName = m.ModuleName,
|
||||
FileName = m.FileName,
|
||||
ModuleMemorySize = m.ModuleMemorySize
|
||||
});
|
||||
}
|
||||
|
||||
public override void Suspend()
|
||||
{
|
||||
foreach (ProcessThread thread in Process.Threads)
|
||||
{
|
||||
IntPtr threadHandle = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||
if (threadHandle != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (SuspendThread(threadHandle) == uint.MaxValue)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(threadHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Resume()
|
||||
{
|
||||
foreach (ProcessThread thread in Process.Threads)
|
||||
{
|
||||
IntPtr threadHandle = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||
if (threadHandle != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ResumeThread(threadHandle) == -1)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(threadHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Terminate() => Process.Kill();
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (handle != IntPtr.Zero)
|
||||
{
|
||||
CloseHandle(handle);
|
||||
handle = IntPtr.Zero;
|
||||
}
|
||||
Process.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint SuspendThread(IntPtr hThread);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern int ResumeThread(IntPtr hThread);
|
||||
}
|
||||
|
||||
public class LinuxProcessEx : ProcessExBase
|
||||
{
|
||||
private readonly int pid;
|
||||
|
||||
public override int Id => pid;
|
||||
public override IntPtr Handle => IntPtr.Zero; // Linux doesn't use handles
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
string status = File.ReadAllText($"/proc/{pid}/status");
|
||||
string[] lines = status.Split('\n');
|
||||
return lines.FirstOrDefault(l => l.StartsWith("Name:", StringComparison.OrdinalIgnoreCase))?.Substring("Name:".Length).Trim();
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// If we can't read the status file, try to get the name from the command line
|
||||
try
|
||||
{
|
||||
string cmdline = File.ReadAllText($"/proc/{pid}/cmdline");
|
||||
return Path.GetFileName(cmdline.Split('\0')[0]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!base.IsRunning)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
string[] lines = File.ReadAllLines($"/proc/{pid}/status");
|
||||
string procState = lines.FirstOrDefault(l => l.StartsWith("State:", StringComparison.OrdinalIgnoreCase))?.Substring("State:".Length).Trim();
|
||||
return procState?.FirstOrDefault() switch
|
||||
{
|
||||
'Z' => false, // Zombie process
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override ProcessModuleEx MainModule =>
|
||||
// This is a simplified implementation. You might need to parse /proc/{pid}/maps
|
||||
// to get more accurate information about the main module.
|
||||
new()
|
||||
{
|
||||
BaseAddress = IntPtr.Zero,
|
||||
ModuleName = Name,
|
||||
FileName = MainModuleFileName,
|
||||
ModuleMemorySize = 0
|
||||
};
|
||||
|
||||
public override string MainModuleFileName
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return ReadSymbolicLink($"/proc/{pid}/exe");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// If we don't have permission to read the symlink, return null
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LinuxProcessEx(int pid) : base(pid)
|
||||
{
|
||||
this.pid = pid;
|
||||
if (!File.Exists($"/proc/{this.pid}/status"))
|
||||
{
|
||||
throw new ArgumentException("Process does not exist.", nameof(pid));
|
||||
}
|
||||
}
|
||||
|
||||
public new static bool IsElevated() => geteuid() == 0;
|
||||
|
||||
public override byte[] ReadMemory(IntPtr address, int size)
|
||||
{
|
||||
byte[] buffer = new byte[size];
|
||||
try
|
||||
{
|
||||
using FileStream fs = new($"/proc/{pid}/mem", FileMode.Open, FileAccess.Read);
|
||||
fs.Seek(address.ToInt64(), SeekOrigin.Begin);
|
||||
if (fs.Read(buffer, 0, size) != size)
|
||||
{
|
||||
throw new IOException("Failed to read the specified amount of memory.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to read process memory.", ex);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public override int WriteMemory(IntPtr address, byte[] data)
|
||||
{
|
||||
int result = ptrace(PtraceRequest.PTRACE_ATTACH, pid, IntPtr.Zero, IntPtr.Zero);
|
||||
if (result < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to attach to the process.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < data.Length; i += sizeof(long))
|
||||
{
|
||||
long value = BitConverter.ToInt64(data, i);
|
||||
if (ptrace(PtraceRequest.PTRACE_POKEDATA, pid, address + i, (IntPtr)value) < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to write memory.");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ptrace(PtraceRequest.PTRACE_DETACH, pid, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
return data.Length;
|
||||
}
|
||||
|
||||
public override IEnumerable<ProcessModuleEx> GetModules()
|
||||
{
|
||||
List<ProcessModuleEx> modules = [];
|
||||
string[] lines = File.ReadAllLines($"/proc/{pid}/maps");
|
||||
foreach (string line in lines)
|
||||
{
|
||||
string[] parts = line.Split(' ');
|
||||
if (parts.Length >= 6)
|
||||
{
|
||||
string[] addresses = parts[0].Split('-');
|
||||
modules.Add(new ProcessModuleEx
|
||||
{
|
||||
BaseAddress = (IntPtr)long.Parse(addresses[0], NumberStyles.HexNumber),
|
||||
ModuleName = parts[5],
|
||||
FileName = parts[5],
|
||||
ModuleMemorySize = (int)(long.Parse(addresses[1], NumberStyles.HexNumber) - long.Parse(addresses[0], NumberStyles.HexNumber))
|
||||
});
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
public override void Suspend()
|
||||
{
|
||||
if (kill(pid, 19) != 0) // SIGSTOP
|
||||
{
|
||||
throw new InvalidOperationException("Failed to suspend the process.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Resume()
|
||||
{
|
||||
if (kill(pid, 18) != 0) // SIGCONT
|
||||
{
|
||||
throw new InvalidOperationException("Failed to resume the process.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Terminate()
|
||||
{
|
||||
if (kill(pid, 9) != 0) // SIGKILL
|
||||
{
|
||||
throw new InvalidOperationException("Failed to terminate the process.");
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern uint geteuid();
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int ptrace(PtraceRequest request, int pid, IntPtr addr, IntPtr data);
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int kill(int pid, int sig);
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int readlink(string path, byte[] buf, int bufsiz);
|
||||
|
||||
private static string ReadSymbolicLink(string path)
|
||||
{
|
||||
const int BUFFER_SIZE = 1024;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead = readlink(path, buffer, BUFFER_SIZE);
|
||||
if (bytesRead < 0)
|
||||
{
|
||||
throw new IOException("Failed to read symbolic link.");
|
||||
}
|
||||
return Encoding.UTF8.GetString(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
public class MacOSProcessEx : ProcessExBase
|
||||
{
|
||||
private bool disposed;
|
||||
public override IntPtr Handle => IntPtr.Zero;
|
||||
|
||||
public override ProcessModuleEx MainModule =>
|
||||
// This is a placeholder implementation. You'll need to use macOS-specific APIs
|
||||
// to get accurate information about the main module.
|
||||
new()
|
||||
{
|
||||
BaseAddress = IntPtr.Zero,
|
||||
ModuleName = Name,
|
||||
FileName = MainModuleFileName,
|
||||
ModuleMemorySize = 0
|
||||
};
|
||||
|
||||
public MacOSProcessEx(int pid) : base(pid)
|
||||
{
|
||||
}
|
||||
|
||||
public new static bool IsElevated() => geteuid() == 0;
|
||||
|
||||
public override byte[] ReadMemory(IntPtr address, int size)
|
||||
{
|
||||
byte[] buffer = new byte[size];
|
||||
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
if (vm_read_overwrite(Handle, address, new IntPtr(size), handle.AddrOfPinnedObject(), out IntPtr _) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to read process memory.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public override int WriteMemory(IntPtr address, byte[] data)
|
||||
{
|
||||
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
if (vm_write(Handle, address, handle.AddrOfPinnedObject(), new IntPtr(data.Length)) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to write process memory.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
return data.Length;
|
||||
}
|
||||
|
||||
public override IEnumerable<ProcessModuleEx> GetModules()
|
||||
{
|
||||
// This is a simplified implementation. In a real scenario, you'd use dyld APIs to get the loaded modules.
|
||||
throw new NotImplementedException("Getting modules is not implemented for macOS.");
|
||||
}
|
||||
|
||||
public override void Suspend()
|
||||
{
|
||||
if (task_suspend(Handle) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to suspend the process.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Resume()
|
||||
{
|
||||
if (task_resume(Handle) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to resume the process.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Terminate()
|
||||
{
|
||||
if (task_terminate(Handle) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to terminate the process.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
// In a real implementation, you'd release the task port here
|
||||
disposed = true;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern uint geteuid();
|
||||
|
||||
[DllImport("libSystem.dylib")]
|
||||
private static extern int vm_read_overwrite(IntPtr targetTask, IntPtr address, IntPtr size, IntPtr data, out IntPtr outsize);
|
||||
|
||||
[DllImport("libSystem.dylib")]
|
||||
private static extern int vm_write(IntPtr targetTask, IntPtr address, IntPtr data, IntPtr size);
|
||||
|
||||
[DllImport("libSystem.dylib")]
|
||||
private static extern int task_suspend(IntPtr task);
|
||||
|
||||
[DllImport("libSystem.dylib")]
|
||||
private static extern int task_resume(IntPtr task);
|
||||
|
||||
[DllImport("libSystem.dylib")]
|
||||
private static extern int task_terminate(IntPtr task);
|
||||
}
|
||||
|
||||
public static class ProcessExFactory
|
||||
{
|
||||
public static ProcessExBase Create(int pid)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return new WindowsProcessEx(pid);
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return new LinuxProcessEx(pid);
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return new MacOSProcessEx(pid);
|
||||
}
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
17
NitroxModel/Platforms/OS/Unix/UnixFileSystem.cs
Normal file
17
NitroxModel/Platforms/OS/Unix/UnixFileSystem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Platforms.OS.Shared;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Unix;
|
||||
|
||||
public sealed class UnixFileSystem : FileSystem
|
||||
{
|
||||
public override IEnumerable<string> GetDefaultPrograms(string file)
|
||||
{
|
||||
yield return "xdg-open";
|
||||
}
|
||||
|
||||
public override bool SetFullAccessToCurrentUser(string directory)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
24
NitroxModel/Platforms/OS/Windows/Internal/CoffFileHeader.cs
Normal file
24
NitroxModel/Platforms/OS/Windows/Internal/CoffFileHeader.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// See:
|
||||
/// <a href="https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image">MSDN</a>
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CoffFileHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Machine type that is targeted by this image.
|
||||
/// </summary>
|
||||
public MachineType Machine;
|
||||
|
||||
public ushort NumberOfSections;
|
||||
public uint TimeDateStamp;
|
||||
public uint PointerToSymbolTable;
|
||||
public uint NumberOfSymbols;
|
||||
public ushort SizeOfOptionalHeader;
|
||||
public ushort Characteristics;
|
||||
}
|
||||
}
|
78
NitroxModel/Platforms/OS/Windows/Internal/ImageDosHeader.cs
Normal file
78
NitroxModel/Platforms/OS/Windows/Internal/ImageDosHeader.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ImageDosHeader
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
public char[] e_magic_byte; // Magic number
|
||||
|
||||
/// <summary>
|
||||
/// Bytes on last page of file
|
||||
/// </summary>
|
||||
public ushort e_cblp;
|
||||
|
||||
/// <summary>
|
||||
/// Pages in file
|
||||
/// </summary>
|
||||
public ushort e_cp;
|
||||
|
||||
public ushort e_crlc;
|
||||
|
||||
/// <summary>
|
||||
/// Size of header in paragraphs
|
||||
/// </summary>
|
||||
public ushort e_cparhdr;
|
||||
|
||||
public ushort e_minalloc;
|
||||
public ushort e_maxalloc;
|
||||
public ushort e_ss;
|
||||
public ushort e_sp;
|
||||
|
||||
/// <summary>
|
||||
/// Checksum
|
||||
/// </summary>
|
||||
public ushort e_csum;
|
||||
|
||||
/// <summary>
|
||||
/// Initial IP value
|
||||
/// </summary>
|
||||
public ushort e_ip; //
|
||||
|
||||
/// <summary>
|
||||
/// Initial (relative) CS value
|
||||
/// </summary>
|
||||
public ushort e_cs;
|
||||
|
||||
/// <summary>
|
||||
/// File address of relocation table
|
||||
/// </summary>
|
||||
public ushort e_lfarlc;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay number
|
||||
/// </summary>
|
||||
public ushort e_ovno;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved words
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public ushort[] e_res1;
|
||||
|
||||
public ushort e_oemid;
|
||||
public ushort e_oeminfo;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved words
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
|
||||
public ushort[] e_res2;
|
||||
|
||||
/// <summary>
|
||||
/// Image base offset to address of PE header.
|
||||
/// </summary>
|
||||
public int e_lfanew;
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ImageExportDirectory
|
||||
{
|
||||
public uint Characteristics;
|
||||
public uint TimeDateStamp;
|
||||
public ushort MajorVersion;
|
||||
public ushort MinorVersion;
|
||||
|
||||
/// <summary>
|
||||
/// RVA of image name.
|
||||
/// </summary>
|
||||
public uint Name;
|
||||
|
||||
public uint Base;
|
||||
public uint NumberOfFunctions;
|
||||
public uint NumberOfNames;
|
||||
public uint AddressOfFunctions;
|
||||
public uint AddressOfNames;
|
||||
public uint AddressOfNameOrdinals;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"{nameof(Characteristics)}: {Characteristics}, {nameof(TimeDateStamp)}: {TimeDateStamp}, {nameof(MajorVersion)}: {MajorVersion}, {nameof(MinorVersion)}: {MinorVersion}, {nameof(Name)}: {Name}, {nameof(Base)}: {Base}, {nameof(NumberOfFunctions)}: {NumberOfFunctions}, {nameof(NumberOfNames)}: {NumberOfNames}, {nameof(AddressOfFunctions)}: {AddressOfFunctions:X}, {nameof(AddressOfNames)}: {AddressOfNames:X}, {nameof(AddressOfNameOrdinals)}: {AddressOfNameOrdinals:X}";
|
||||
}
|
||||
}
|
||||
}
|
22
NitroxModel/Platforms/OS/Windows/Internal/ImageNtHeader64.cs
Normal file
22
NitroxModel/Platforms/OS/Windows/Internal/ImageNtHeader64.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// See: https://www.vergiliusproject.com/kernels/x64/Windows%2010%20|%202016/2009%2020H2%20(October%202020%20Update)/_IMAGE_NT_HEADERS64
|
||||
/// And MSDN:
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ImageNtHeader64
|
||||
{
|
||||
/// <summary>
|
||||
/// Expected to be "PE" string for NT systems.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
[FieldOffset(0x0)]
|
||||
public char[] Magic;
|
||||
|
||||
[FieldOffset(0x18)]
|
||||
public OptionalHeader64 OptionalHeader;
|
||||
}
|
||||
}
|
22
NitroxModel/Platforms/OS/Windows/Internal/LdrData.cs
Normal file
22
NitroxModel/Platforms/OS/Windows/Internal/LdrData.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct LdrData
|
||||
{
|
||||
[FieldOffset(0x0)]
|
||||
public int Length;
|
||||
[FieldOffset(0x4)]
|
||||
public int Initialized;
|
||||
[FieldOffset(0x8)]
|
||||
public short SsHandle;
|
||||
[FieldOffset(0x10)]
|
||||
public IntPtr InLoadOrderModuleList;
|
||||
[FieldOffset(0x20)]
|
||||
public IntPtr InMemoryOrderModuleList;
|
||||
[FieldOffset(0x30)]
|
||||
public IntPtr InInitializationOrderModuleList;
|
||||
}
|
||||
}
|
21
NitroxModel/Platforms/OS/Windows/Internal/MachineType.cs
Normal file
21
NitroxModel/Platforms/OS/Windows/Internal/MachineType.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// See: <a href="https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types">MSDN</a>
|
||||
/// </summary>
|
||||
public enum MachineType : ushort
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
AM33 = 0x1d3,
|
||||
AMD64 = 0x8664,
|
||||
ARM = 0x1c0,
|
||||
ARM64 = 0xaa64,
|
||||
ARMNT = 0xc4,
|
||||
EBC = 0xebc, // EFI byte code
|
||||
I386 = 0x14c,
|
||||
IA64 = 0x200,
|
||||
M32R = 0x9041,
|
||||
MIPS16 = 0x266,
|
||||
POWER_PC = 0x1f1
|
||||
}
|
||||
}
|
24
NitroxModel/Platforms/OS/Windows/Internal/ModuleEntry32.cs
Normal file
24
NitroxModel/Platforms/OS/Windows/Internal/ModuleEntry32.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct ModuleEntry32
|
||||
{
|
||||
public int dwSize;
|
||||
public uint th32ModuleID;
|
||||
public uint th32ProcessID;
|
||||
public uint GlblcntUsage;
|
||||
public uint ProccntUsage;
|
||||
public IntPtr modBaseAddr;
|
||||
public uint modBaseSize;
|
||||
public IntPtr hModule;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
public string szModule;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string szExePath;
|
||||
}
|
||||
}
|
343
NitroxModel/Platforms/OS/Windows/Internal/NtStatus.cs
Normal file
343
NitroxModel/Platforms/OS/Windows/Internal/NtStatus.cs
Normal file
@@ -0,0 +1,343 @@
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
public enum NtStatus : uint
|
||||
{
|
||||
// Success
|
||||
SUCCESS = 0x00000000,
|
||||
WAIT0 = 0x00000000,
|
||||
WAIT1 = 0x00000001,
|
||||
WAIT2 = 0x00000002,
|
||||
WAIT3 = 0x00000003,
|
||||
WAIT63 = 0x0000003f,
|
||||
ABANDONED = 0x00000080,
|
||||
ABANDONED_WAIT0 = 0x00000080,
|
||||
ABANDONED_WAIT1 = 0x00000081,
|
||||
ABANDONED_WAIT2 = 0x00000082,
|
||||
ABANDONED_WAIT3 = 0x00000083,
|
||||
ABANDONED_WAIT63 = 0x000000bf,
|
||||
USER_APC = 0x000000c0,
|
||||
KERNEL_APC = 0x00000100,
|
||||
ALERTED = 0x00000101,
|
||||
TIMEOUT = 0x00000102,
|
||||
PENDING = 0x00000103,
|
||||
REPARSE = 0x00000104,
|
||||
MORE_ENTRIES = 0x00000105,
|
||||
NOT_ALL_ASSIGNED = 0x00000106,
|
||||
SOME_NOT_MAPPED = 0x00000107,
|
||||
OP_LOCK_BREAK_IN_PROGRESS = 0x00000108,
|
||||
VOLUME_MOUNTED = 0x00000109,
|
||||
RX_ACT_COMMITTED = 0x0000010a,
|
||||
NOTIFY_CLEANUP = 0x0000010b,
|
||||
NOTIFY_ENUM_DIR = 0x0000010c,
|
||||
NO_QUOTAS_FOR_ACCOUNT = 0x0000010d,
|
||||
PRIMARY_TRANSPORT_CONNECT_FAILED = 0x0000010e,
|
||||
PAGE_FAULT_TRANSITION = 0x00000110,
|
||||
PAGE_FAULT_DEMAND_ZERO = 0x00000111,
|
||||
PAGE_FAULT_COPY_ON_WRITE = 0x00000112,
|
||||
PAGE_FAULT_GUARD_PAGE = 0x00000113,
|
||||
PAGE_FAULT_PAGING_FILE = 0x00000114,
|
||||
CRASH_DUMP = 0x00000116,
|
||||
REPARSE_OBJECT = 0x00000118,
|
||||
NOTHING_TO_TERMINATE = 0x00000122,
|
||||
PROCESS_NOT_IN_JOB = 0x00000123,
|
||||
PROCESS_IN_JOB = 0x00000124,
|
||||
PROCESS_CLONED = 0x00000129,
|
||||
FILE_LOCKED_WITH_ONLY_READERS = 0x0000012a,
|
||||
FILE_LOCKED_WITH_WRITERS = 0x0000012b,
|
||||
|
||||
// Informational
|
||||
INFORMATIONAL = 0x40000000,
|
||||
OBJECT_NAME_EXISTS = 0x40000000,
|
||||
THREAD_WAS_SUSPENDED = 0x40000001,
|
||||
WORKING_SET_LIMIT_RANGE = 0x40000002,
|
||||
IMAGE_NOT_AT_BASE = 0x40000003,
|
||||
REGISTRY_RECOVERED = 0x40000009,
|
||||
|
||||
// Warning
|
||||
WARNING = 0x80000000,
|
||||
GUARD_PAGE_VIOLATION = 0x80000001,
|
||||
DATATYPE_MISALIGNMENT = 0x80000002,
|
||||
BREAKPOINT = 0x80000003,
|
||||
SINGLE_STEP = 0x80000004,
|
||||
BUFFER_OVERFLOW = 0x80000005,
|
||||
NO_MORE_FILES = 0x80000006,
|
||||
HANDLES_CLOSED = 0x8000000a,
|
||||
PARTIAL_COPY = 0x8000000d,
|
||||
DEVICE_BUSY = 0x80000011,
|
||||
INVALID_EA_NAME = 0x80000013,
|
||||
EA_LIST_INCONSISTENT = 0x80000014,
|
||||
NO_MORE_ENTRIES = 0x8000001a,
|
||||
LONG_JUMP = 0x80000026,
|
||||
DLL_MIGHT_BE_INSECURE = 0x8000002b,
|
||||
|
||||
// Error
|
||||
ERROR = 0xc0000000,
|
||||
UNSUCCESSFUL = 0xc0000001,
|
||||
NOT_IMPLEMENTED = 0xc0000002,
|
||||
INVALID_INFO_CLASS = 0xc0000003,
|
||||
INFO_LENGTH_MISMATCH = 0xc0000004,
|
||||
ACCESS_VIOLATION = 0xc0000005,
|
||||
IN_PAGE_ERROR = 0xc0000006,
|
||||
PAGEFILE_QUOTA = 0xc0000007,
|
||||
INVALID_HANDLE = 0xc0000008,
|
||||
BAD_INITIAL_STACK = 0xc0000009,
|
||||
BAD_INITIAL_PC = 0xc000000a,
|
||||
INVALID_CID = 0xc000000b,
|
||||
TIMER_NOT_CANCELED = 0xc000000c,
|
||||
INVALID_PARAMETER = 0xc000000d,
|
||||
NO_SUCH_DEVICE = 0xc000000e,
|
||||
NO_SUCH_FILE = 0xc000000f,
|
||||
INVALID_DEVICE_REQUEST = 0xc0000010,
|
||||
END_OF_FILE = 0xc0000011,
|
||||
WRONG_VOLUME = 0xc0000012,
|
||||
NO_MEDIA_IN_DEVICE = 0xc0000013,
|
||||
NO_MEMORY = 0xc0000017,
|
||||
NOT_MAPPED_VIEW = 0xc0000019,
|
||||
UNABLE_TO_FREE_VM = 0xc000001a,
|
||||
UNABLE_TO_DELETE_SECTION = 0xc000001b,
|
||||
ILLEGAL_INSTRUCTION = 0xc000001d,
|
||||
ALREADY_COMMITTED = 0xc0000021,
|
||||
ACCESS_DENIED = 0xc0000022,
|
||||
BUFFER_TOO_SMALL = 0xc0000023,
|
||||
OBJECT_TYPE_MISMATCH = 0xc0000024,
|
||||
NON_CONTINUABLE_EXCEPTION = 0xc0000025,
|
||||
BAD_STACK = 0xc0000028,
|
||||
NOT_LOCKED = 0xc000002a,
|
||||
NOT_COMMITTED = 0xc000002d,
|
||||
INVALID_PARAMETER_MIX = 0xc0000030,
|
||||
OBJECT_NAME_INVALID = 0xc0000033,
|
||||
OBJECT_NAME_NOT_FOUND = 0xc0000034,
|
||||
OBJECT_NAME_COLLISION = 0xc0000035,
|
||||
OBJECT_PATH_INVALID = 0xc0000039,
|
||||
OBJECT_PATH_NOT_FOUND = 0xc000003a,
|
||||
OBJECT_PATH_SYNTAX_BAD = 0xc000003b,
|
||||
DATA_OVERRUN = 0xc000003c,
|
||||
DATA_LATE = 0xc000003d,
|
||||
DATA_ERROR = 0xc000003e,
|
||||
CRC_ERROR = 0xc000003f,
|
||||
SECTION_TOO_BIG = 0xc0000040,
|
||||
PORT_CONNECTION_REFUSED = 0xc0000041,
|
||||
INVALID_PORT_HANDLE = 0xc0000042,
|
||||
SHARING_VIOLATION = 0xc0000043,
|
||||
QUOTA_EXCEEDED = 0xc0000044,
|
||||
INVALID_PAGE_PROTECTION = 0xc0000045,
|
||||
MUTANT_NOT_OWNED = 0xc0000046,
|
||||
SEMAPHORE_LIMIT_EXCEEDED = 0xc0000047,
|
||||
PORT_ALREADY_SET = 0xc0000048,
|
||||
SECTION_NOT_IMAGE = 0xc0000049,
|
||||
SUSPEND_COUNT_EXCEEDED = 0xc000004a,
|
||||
THREAD_IS_TERMINATING = 0xc000004b,
|
||||
BAD_WORKING_SET_LIMIT = 0xc000004c,
|
||||
INCOMPATIBLE_FILE_MAP = 0xc000004d,
|
||||
SECTION_PROTECTION = 0xc000004e,
|
||||
EAS_NOT_SUPPORTED = 0xc000004f,
|
||||
EA_TOO_LARGE = 0xc0000050,
|
||||
NON_EXISTENT_EA_ENTRY = 0xc0000051,
|
||||
NO_EAS_ON_FILE = 0xc0000052,
|
||||
EA_CORRUPT_ERROR = 0xc0000053,
|
||||
FILE_LOCK_CONFLICT = 0xc0000054,
|
||||
LOCK_NOT_GRANTED = 0xc0000055,
|
||||
DELETE_PENDING = 0xc0000056,
|
||||
CTL_FILE_NOT_SUPPORTED = 0xc0000057,
|
||||
UNKNOWN_REVISION = 0xc0000058,
|
||||
REVISION_MISMATCH = 0xc0000059,
|
||||
INVALID_OWNER = 0xc000005a,
|
||||
INVALID_PRIMARY_GROUP = 0xc000005b,
|
||||
NO_IMPERSONATION_TOKEN = 0xc000005c,
|
||||
CANT_DISABLE_MANDATORY = 0xc000005d,
|
||||
NO_LOGON_SERVERS = 0xc000005e,
|
||||
NO_SUCH_LOGON_SESSION = 0xc000005f,
|
||||
NO_SUCH_PRIVILEGE = 0xc0000060,
|
||||
PRIVILEGE_NOT_HELD = 0xc0000061,
|
||||
INVALID_ACCOUNT_NAME = 0xc0000062,
|
||||
USER_EXISTS = 0xc0000063,
|
||||
NO_SUCH_USER = 0xc0000064,
|
||||
GROUP_EXISTS = 0xc0000065,
|
||||
NO_SUCH_GROUP = 0xc0000066,
|
||||
MEMBER_IN_GROUP = 0xc0000067,
|
||||
MEMBER_NOT_IN_GROUP = 0xc0000068,
|
||||
LAST_ADMIN = 0xc0000069,
|
||||
WRONG_PASSWORD = 0xc000006a,
|
||||
ILL_FORMED_PASSWORD = 0xc000006b,
|
||||
PASSWORD_RESTRICTION = 0xc000006c,
|
||||
LOGON_FAILURE = 0xc000006d,
|
||||
ACCOUNT_RESTRICTION = 0xc000006e,
|
||||
INVALID_LOGON_HOURS = 0xc000006f,
|
||||
INVALID_WORKSTATION = 0xc0000070,
|
||||
PASSWORD_EXPIRED = 0xc0000071,
|
||||
ACCOUNT_DISABLED = 0xc0000072,
|
||||
NONE_MAPPED = 0xc0000073,
|
||||
TOO_MANY_LUIDS_REQUESTED = 0xc0000074,
|
||||
LUIDS_EXHAUSTED = 0xc0000075,
|
||||
INVALID_SUB_AUTHORITY = 0xc0000076,
|
||||
INVALID_ACL = 0xc0000077,
|
||||
INVALID_SID = 0xc0000078,
|
||||
INVALID_SECURITY_DESCR = 0xc0000079,
|
||||
PROCEDURE_NOT_FOUND = 0xc000007a,
|
||||
INVALID_IMAGE_FORMAT = 0xc000007b,
|
||||
NO_TOKEN = 0xc000007c,
|
||||
BAD_INHERITANCE_ACL = 0xc000007d,
|
||||
RANGE_NOT_LOCKED = 0xc000007e,
|
||||
DISK_FULL = 0xc000007f,
|
||||
SERVER_DISABLED = 0xc0000080,
|
||||
SERVER_NOT_DISABLED = 0xc0000081,
|
||||
TOO_MANY_GUIDS_REQUESTED = 0xc0000082,
|
||||
GUIDS_EXHAUSTED = 0xc0000083,
|
||||
INVALID_ID_AUTHORITY = 0xc0000084,
|
||||
AGENTS_EXHAUSTED = 0xc0000085,
|
||||
INVALID_VOLUME_LABEL = 0xc0000086,
|
||||
SECTION_NOT_EXTENDED = 0xc0000087,
|
||||
NOT_MAPPED_DATA = 0xc0000088,
|
||||
RESOURCE_DATA_NOT_FOUND = 0xc0000089,
|
||||
RESOURCE_TYPE_NOT_FOUND = 0xc000008a,
|
||||
RESOURCE_NAME_NOT_FOUND = 0xc000008b,
|
||||
ARRAY_BOUNDS_EXCEEDED = 0xc000008c,
|
||||
FLOAT_DENORMAL_OPERAND = 0xc000008d,
|
||||
FLOAT_DIVIDE_BY_ZERO = 0xc000008e,
|
||||
FLOAT_INEXACT_RESULT = 0xc000008f,
|
||||
FLOAT_INVALID_OPERATION = 0xc0000090,
|
||||
FLOAT_OVERFLOW = 0xc0000091,
|
||||
FLOAT_STACK_CHECK = 0xc0000092,
|
||||
FLOAT_UNDERFLOW = 0xc0000093,
|
||||
INTEGER_DIVIDE_BY_ZERO = 0xc0000094,
|
||||
INTEGER_OVERFLOW = 0xc0000095,
|
||||
PRIVILEGED_INSTRUCTION = 0xc0000096,
|
||||
TOO_MANY_PAGING_FILES = 0xc0000097,
|
||||
FILE_INVALID = 0xc0000098,
|
||||
INSTANCE_NOT_AVAILABLE = 0xc00000ab,
|
||||
PIPE_NOT_AVAILABLE = 0xc00000ac,
|
||||
INVALID_PIPE_STATE = 0xc00000ad,
|
||||
PIPE_BUSY = 0xc00000ae,
|
||||
ILLEGAL_FUNCTION = 0xc00000af,
|
||||
PIPE_DISCONNECTED = 0xc00000b0,
|
||||
PIPE_CLOSING = 0xc00000b1,
|
||||
PIPE_CONNECTED = 0xc00000b2,
|
||||
PIPE_LISTENING = 0xc00000b3,
|
||||
INVALID_READ_MODE = 0xc00000b4,
|
||||
IO_TIMEOUT = 0xc00000b5,
|
||||
FILE_FORCED_CLOSED = 0xc00000b6,
|
||||
PROFILING_NOT_STARTED = 0xc00000b7,
|
||||
PROFILING_NOT_STOPPED = 0xc00000b8,
|
||||
NOT_SAME_DEVICE = 0xc00000d4,
|
||||
FILE_RENAMED = 0xc00000d5,
|
||||
CANT_WAIT = 0xc00000d8,
|
||||
PIPE_EMPTY = 0xc00000d9,
|
||||
CANT_TERMINATE_SELF = 0xc00000db,
|
||||
INTERNAL_ERROR = 0xc00000e5,
|
||||
INVALID_PARAMETER1 = 0xc00000ef,
|
||||
INVALID_PARAMETER2 = 0xc00000f0,
|
||||
INVALID_PARAMETER3 = 0xc00000f1,
|
||||
INVALID_PARAMETER4 = 0xc00000f2,
|
||||
INVALID_PARAMETER5 = 0xc00000f3,
|
||||
INVALID_PARAMETER6 = 0xc00000f4,
|
||||
INVALID_PARAMETER7 = 0xc00000f5,
|
||||
INVALID_PARAMETER8 = 0xc00000f6,
|
||||
INVALID_PARAMETER9 = 0xc00000f7,
|
||||
INVALID_PARAMETER10 = 0xc00000f8,
|
||||
INVALID_PARAMETER11 = 0xc00000f9,
|
||||
INVALID_PARAMETER12 = 0xc00000fa,
|
||||
MAPPED_FILE_SIZE_ZERO = 0xc000011e,
|
||||
TOO_MANY_OPENED_FILES = 0xc000011f,
|
||||
CANCELLED = 0xc0000120,
|
||||
CANNOT_DELETE = 0xc0000121,
|
||||
INVALID_COMPUTER_NAME = 0xc0000122,
|
||||
FILE_DELETED = 0xc0000123,
|
||||
SPECIAL_ACCOUNT = 0xc0000124,
|
||||
SPECIAL_GROUP = 0xc0000125,
|
||||
SPECIAL_USER = 0xc0000126,
|
||||
MEMBERS_PRIMARY_GROUP = 0xc0000127,
|
||||
FILE_CLOSED = 0xc0000128,
|
||||
TOO_MANY_THREADS = 0xc0000129,
|
||||
THREAD_NOT_IN_PROCESS = 0xc000012a,
|
||||
TOKEN_ALREADY_IN_USE = 0xc000012b,
|
||||
PAGEFILE_QUOTA_EXCEEDED = 0xc000012c,
|
||||
COMMITMENT_LIMIT = 0xc000012d,
|
||||
INVALID_IMAGE_LE_FORMAT = 0xc000012e,
|
||||
INVALID_IMAGE_NOT_MZ = 0xc000012f,
|
||||
INVALID_IMAGE_PROTECT = 0xc0000130,
|
||||
INVALID_IMAGE_WIN16 = 0xc0000131,
|
||||
LOGON_SERVER = 0xc0000132,
|
||||
DIFFERENCE_AT_DC = 0xc0000133,
|
||||
SYNCHRONIZATION_REQUIRED = 0xc0000134,
|
||||
DLL_NOT_FOUND = 0xc0000135,
|
||||
IO_PRIVILEGE_FAILED = 0xc0000137,
|
||||
ORDINAL_NOT_FOUND = 0xc0000138,
|
||||
ENTRY_POINT_NOT_FOUND = 0xc0000139,
|
||||
CONTROL_C_EXIT = 0xc000013a,
|
||||
PORT_NOT_SET = 0xc0000353,
|
||||
DEBUGGER_INACTIVE = 0xc0000354,
|
||||
CALLBACK_BYPASS = 0xc0000503,
|
||||
PORT_CLOSED = 0xc0000700,
|
||||
MESSAGE_LOST = 0xc0000701,
|
||||
INVALID_MESSAGE = 0xc0000702,
|
||||
REQUEST_CANCELED = 0xc0000703,
|
||||
RECURSIVE_DISPATCH = 0xc0000704,
|
||||
LPC_RECEIVE_BUFFER_EXPECTED = 0xc0000705,
|
||||
LPC_INVALID_CONNECTION_USAGE = 0xc0000706,
|
||||
LPC_REQUESTS_NOT_ALLOWED = 0xc0000707,
|
||||
RESOURCE_IN_USE = 0xc0000708,
|
||||
PROCESS_IS_PROTECTED = 0xc0000712,
|
||||
VOLUME_DIRTY = 0xc0000806,
|
||||
FILE_CHECKED_OUT = 0xc0000901,
|
||||
CHECK_OUT_REQUIRED = 0xc0000902,
|
||||
BAD_FILE_TYPE = 0xc0000903,
|
||||
FILE_TOO_LARGE = 0xc0000904,
|
||||
FORMS_AUTH_REQUIRED = 0xc0000905,
|
||||
VIRUS_INFECTED = 0xc0000906,
|
||||
VIRUS_DELETED = 0xc0000907,
|
||||
TRANSACTIONAL_CONFLICT = 0xc0190001,
|
||||
INVALID_TRANSACTION = 0xc0190002,
|
||||
TRANSACTION_NOT_ACTIVE = 0xc0190003,
|
||||
TM_INITIALIZATION_FAILED = 0xc0190004,
|
||||
RM_NOT_ACTIVE = 0xc0190005,
|
||||
RM_METADATA_CORRUPT = 0xc0190006,
|
||||
TRANSACTION_NOT_JOINED = 0xc0190007,
|
||||
DIRECTORY_NOT_RM = 0xc0190008,
|
||||
COULD_NOT_RESIZE_LOG = 0xc0190009,
|
||||
TRANSACTIONS_UNSUPPORTED_REMOTE = 0xc019000a,
|
||||
LOG_RESIZE_INVALID_SIZE = 0xc019000b,
|
||||
REMOTE_FILE_VERSION_MISMATCH = 0xc019000c,
|
||||
CRM_PROTOCOL_ALREADY_EXISTS = 0xc019000f,
|
||||
TRANSACTION_PROPAGATION_FAILED = 0xc0190010,
|
||||
CRM_PROTOCOL_NOT_FOUND = 0xc0190011,
|
||||
TRANSACTION_SUPERIOR_EXISTS = 0xc0190012,
|
||||
TRANSACTION_REQUEST_NOT_VALID = 0xc0190013,
|
||||
TRANSACTION_NOT_REQUESTED = 0xc0190014,
|
||||
TRANSACTION_ALREADY_ABORTED = 0xc0190015,
|
||||
TRANSACTION_ALREADY_COMMITTED = 0xc0190016,
|
||||
TRANSACTION_INVALID_MARSHALL_BUFFER = 0xc0190017,
|
||||
CURRENT_TRANSACTION_NOT_VALID = 0xc0190018,
|
||||
LOG_GROWTH_FAILED = 0xc0190019,
|
||||
OBJECT_NO_LONGER_EXISTS = 0xc0190021,
|
||||
STREAM_MINIVERSION_NOT_FOUND = 0xc0190022,
|
||||
STREAM_MINIVERSION_NOT_VALID = 0xc0190023,
|
||||
MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION = 0xc0190024,
|
||||
CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT = 0xc0190025,
|
||||
CANT_CREATE_MORE_STREAM_MINIVERSIONS = 0xc0190026,
|
||||
HANDLE_NO_LONGER_VALID = 0xc0190028,
|
||||
NO_TXF_METADATA = 0xc0190029,
|
||||
LOG_CORRUPTION_DETECTED = 0xc0190030,
|
||||
CANT_RECOVER_WITH_HANDLE_OPEN = 0xc0190031,
|
||||
RM_DISCONNECTED = 0xc0190032,
|
||||
ENLISTMENT_NOT_SUPERIOR = 0xc0190033,
|
||||
RECOVERY_NOT_NEEDED = 0xc0190034,
|
||||
RM_ALREADY_STARTED = 0xc0190035,
|
||||
FILE_IDENTITY_NOT_PERSISTENT = 0xc0190036,
|
||||
CANT_BREAK_TRANSACTIONAL_DEPENDENCY = 0xc0190037,
|
||||
CANT_CROSS_RM_BOUNDARY = 0xc0190038,
|
||||
TXF_DIR_NOT_EMPTY = 0xc0190039,
|
||||
INDOUBT_TRANSACTIONS_EXIST = 0xc019003a,
|
||||
TM_VOLATILE = 0xc019003b,
|
||||
ROLLBACK_TIMER_EXPIRED = 0xc019003c,
|
||||
TXF_ATTRIBUTE_CORRUPT = 0xc019003d,
|
||||
EFS_NOT_ALLOWED_IN_TRANSACTION = 0xc019003e,
|
||||
TRANSACTIONAL_OPEN_NOT_ALLOWED = 0xc019003f,
|
||||
TRANSACTED_MAPPING_UNSUPPORTED_REMOTE = 0xc0190040,
|
||||
TXF_METADATA_ALREADY_PRESENT = 0xc0190041,
|
||||
TRANSACTION_SCOPE_CALLBACKS_NOT_SET = 0xc0190042,
|
||||
TRANSACTION_REQUIRED_PROMOTION = 0xc0190043,
|
||||
CANNOT_EXECUTE_FILE_IN_TRANSACTION = 0xc0190044,
|
||||
TRANSACTIONS_NOT_FROZEN = 0xc0190045,
|
||||
MAXIMUM_NT_STATUS = 0xffffffff
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Structs from (copy paste url in browser to visit):
|
||||
/// <a
|
||||
/// href="https://www.vergiliusproject.com/kernels/x64/Windows 10 |%202016%2F2104 21H1 (May 2021 Update)/_IMAGE_OPTIONAL_HEADER64">
|
||||
/// _IMAGE_OPTIONAL_HEADER64
|
||||
/// </a>
|
||||
/// Also see:
|
||||
/// <a href="https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only">MSDN: PE Header</a>
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct OptionalHeader64
|
||||
{
|
||||
/// <summary>
|
||||
/// Expected to be "PE" string for NT systems.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
public char[] Magic;
|
||||
|
||||
public byte MajorLinkerVersion;
|
||||
public byte MinorLinkerVersion;
|
||||
public uint SizeOfCode;
|
||||
public uint SizeOfInitializedData;
|
||||
public uint SizeOfUninitializedData;
|
||||
public uint AddressOfEntryPoint;
|
||||
public uint BaseOfCode;
|
||||
public ulong ImageBase;
|
||||
public uint SectionAlignment;
|
||||
public uint FileAlignment;
|
||||
public ushort MajorOperatingSystemVersion;
|
||||
public ushort MinorOperatingSystemVersion;
|
||||
public ushort MajorImageVersion;
|
||||
public ushort MinorImageVersion;
|
||||
public ushort MajorSubsystemVersion;
|
||||
public ushort MinorSubsystemVersion;
|
||||
public uint Win32VersionValue;
|
||||
public uint SizeOfImage;
|
||||
public uint SizeOfHeaders;
|
||||
public uint CheckSum;
|
||||
public ushort Subsystem;
|
||||
public ushort DllCharacteristics;
|
||||
public ulong SizeOfStackReserve;
|
||||
public ulong SizeOfStackCommit;
|
||||
public ulong SizeOfHeapReserve;
|
||||
public ulong SizeOfHeapCommit;
|
||||
public uint LoaderFlags;
|
||||
public uint NumberOfRvaAndSizes;
|
||||
public ImageDataDirectory ExportTable;
|
||||
public ImageDataDirectory ImportTable;
|
||||
public ImageDataDirectory ResourceTable;
|
||||
public ImageDataDirectory ExceptionTable;
|
||||
public ImageDataDirectory CertificateTable;
|
||||
public ImageDataDirectory BaseRelocationTable;
|
||||
public ImageDataDirectory Debug;
|
||||
public ImageDataDirectory Architecture;
|
||||
public ImageDataDirectory GlobalPtr;
|
||||
public ImageDataDirectory TLSTable;
|
||||
public ImageDataDirectory LoadConfigTable;
|
||||
public ImageDataDirectory BoundImport;
|
||||
public ImageDataDirectory IAT;
|
||||
public ImageDataDirectory DelayImportDescriptor;
|
||||
public ImageDataDirectory CLRRuntimeHeader;
|
||||
public ImageDataDirectory Reserved;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"{nameof(Magic)}: {new string(Magic)}, {nameof(MajorLinkerVersion)}: {MajorLinkerVersion}, {nameof(MinorLinkerVersion)}: {MinorLinkerVersion}, {nameof(SizeOfCode)}: {SizeOfCode}, {nameof(SizeOfInitializedData)}: {SizeOfInitializedData}, {nameof(SizeOfUninitializedData)}: {SizeOfUninitializedData}, {nameof(AddressOfEntryPoint)}: {AddressOfEntryPoint}, {nameof(BaseOfCode)}: {BaseOfCode}, {nameof(ImageBase)}: {ImageBase}, {nameof(SectionAlignment)}: {SectionAlignment}, {nameof(FileAlignment)}: {FileAlignment}, {nameof(MajorOperatingSystemVersion)}: {MajorOperatingSystemVersion}, {nameof(MinorOperatingSystemVersion)}: {MinorOperatingSystemVersion}, {nameof(MajorImageVersion)}: {MajorImageVersion}, {nameof(MinorImageVersion)}: {MinorImageVersion}, {nameof(MajorSubsystemVersion)}: {MajorSubsystemVersion}, {nameof(MinorSubsystemVersion)}: {MinorSubsystemVersion}, {nameof(Win32VersionValue)}: {Win32VersionValue}, {nameof(SizeOfImage)}: {SizeOfImage}, {nameof(SizeOfHeaders)}: {SizeOfHeaders}, {nameof(CheckSum)}: {CheckSum}, {nameof(Subsystem)}: {Subsystem}, {nameof(DllCharacteristics)}: {DllCharacteristics}, {nameof(SizeOfStackReserve)}: {SizeOfStackReserve}, {nameof(SizeOfStackCommit)}: {SizeOfStackCommit}, {nameof(SizeOfHeapReserve)}: {SizeOfHeapReserve}, {nameof(SizeOfHeapCommit)}: {SizeOfHeapCommit}, {nameof(LoaderFlags)}: {LoaderFlags}, {nameof(NumberOfRvaAndSizes)}: {NumberOfRvaAndSizes}";
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ImageDataDirectory
|
||||
{
|
||||
/// <summary>
|
||||
/// Location of the data in this directory. Add this value to the base address of the image to get the real address.
|
||||
/// </summary>
|
||||
public uint VirtualAddress;
|
||||
|
||||
public uint Size;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[Flags]
|
||||
public enum ProcessAccessFlags : uint
|
||||
{
|
||||
ALL = 0x001F0FFF,
|
||||
TERMINATE = 0x00000001,
|
||||
CREATE_THREAD = 0x00000002,
|
||||
VIRTUAL_MEMORY_OPERATION = 0x00000008,
|
||||
VIRTUAL_MEMORY_READ = 0x00000010,
|
||||
VIRTUAL_MEMORY_WRITE = 0x00000020,
|
||||
DUPLICATE_HANDLE = 0x00000040,
|
||||
CREATE_PROCESS = 0x000000080,
|
||||
SET_QUOTA = 0x00000100,
|
||||
SET_INFORMATION = 0x00000200,
|
||||
QUERY_INFORMATION = 0x00000400,
|
||||
QUERY_LIMITED_INFORMATION = 0x00001000,
|
||||
SYNCHRONIZE = 0x00100000
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct ProcessBasicInformation
|
||||
{
|
||||
public NtStatus ExitStatus;
|
||||
public IntPtr PebBaseAddress;
|
||||
public UIntPtr AffinityMask;
|
||||
public int BasePriority;
|
||||
public UIntPtr UniqueProcessId;
|
||||
public UIntPtr InheritedFromUniqueProcessId;
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[Flags]
|
||||
public enum ProcessCreationFlags : uint
|
||||
{
|
||||
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
|
||||
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
|
||||
CREATE_NEW_PROCESS_GROUP = 0x00000200,
|
||||
CREATE_NEW_CONSOLE = 0x00000010,
|
||||
CREATE_NO_WINDOW = 0x08000000,
|
||||
CREATE_PROTECTED_PROCESS = 0x00040000,
|
||||
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
|
||||
CREATE_SECURE_PROCESS = 0x00400000,
|
||||
CREATE_SEPARATE_WOW_VDM = 0x00000800,
|
||||
CREATE_SHARED_WOW_VDM = 0x00001000,
|
||||
CREATE_SUSPENDED = 0x00000004,
|
||||
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
||||
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
|
||||
DEBUG_PROCESS = 0x00000001,
|
||||
DETACHED_PROCESS = 0x00000008,
|
||||
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
|
||||
INHERIT_PARENT_AFFINITY = 0x00010000
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// PEB structure in a used by Windows in 64-bit processes.
|
||||
/// See full structure here:
|
||||
/// https://ntopcode.wordpress.com/2018/02/26/anatomy-of-the-process-environment-block-peb-windows-internals/
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ProcessEnvironmentBlock64
|
||||
{
|
||||
[FieldOffset(0x0)]
|
||||
public byte Reserved1;
|
||||
[FieldOffset(0x1)]
|
||||
public byte Reserved2;
|
||||
[FieldOffset(0x2)]
|
||||
public byte BeingDebugged;
|
||||
[FieldOffset(0x3)]
|
||||
public byte Reserved3;
|
||||
|
||||
[FieldOffset(0x10)]
|
||||
public IntPtr ImageBaseAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Ptr to structure <see cref="Windows.Internal.LdrData" />.
|
||||
/// </summary>
|
||||
[FieldOffset(0x18)]
|
||||
public IntPtr LdrData;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public IntPtr ProcessParameters;
|
||||
}
|
||||
}
|
14
NitroxModel/Platforms/OS/Windows/Internal/ProcessInfo.cs
Normal file
14
NitroxModel/Platforms/OS/Windows/Internal/ProcessInfo.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct ProcessInfo
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public Int32 ProcessId;
|
||||
public Int32 ThreadId;
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
public enum PtraceRequest : int
|
||||
{
|
||||
PTRACE_ATTACH = 16,
|
||||
PTRACE_DETACH = 17,
|
||||
PTRACE_POKEDATA = 5
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ThreadAccess : int
|
||||
{
|
||||
SUSPEND_RESUME = 0x0002
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct SecurityAttributes
|
||||
{
|
||||
public int length;
|
||||
public IntPtr lpSecurityDescriptor;
|
||||
public bool bInheritHandle;
|
||||
}
|
||||
}
|
17
NitroxModel/Platforms/OS/Windows/Internal/SnapshotFlags.cs
Normal file
17
NitroxModel/Platforms/OS/Windows/Internal/SnapshotFlags.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[Flags]
|
||||
public enum SnapshotFlags : uint
|
||||
{
|
||||
HEAP_LIST = 0x00000001,
|
||||
PROCESS = 0x00000002,
|
||||
THREAD = 0x00000004,
|
||||
MODULE = 0x00000008,
|
||||
MODULE32 = 0x00000010,
|
||||
INHERIT = 0x80000000,
|
||||
ALL = 0x0000001F,
|
||||
NO_HEAPS = 0x40000000
|
||||
}
|
||||
}
|
28
NitroxModel/Platforms/OS/Windows/Internal/StartupInfo.cs
Normal file
28
NitroxModel/Platforms/OS/Windows/Internal/StartupInfo.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct StartupInfo
|
||||
{
|
||||
public int cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public uint dwX;
|
||||
public uint dwY;
|
||||
public uint dwXSize;
|
||||
public uint dwYSize;
|
||||
public uint dwXCountChars;
|
||||
public uint dwYCountChars;
|
||||
public uint dwFillAttribute;
|
||||
public uint dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
}
|
18
NitroxModel/Platforms/OS/Windows/Internal/ThreadAccess.cs
Normal file
18
NitroxModel/Platforms/OS/Windows/Internal/ThreadAccess.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal
|
||||
{
|
||||
[Flags]
|
||||
public enum ThreadAccess
|
||||
{
|
||||
TERMINATE = 0x0001,
|
||||
SUSPEND_RESUME = 0x0002,
|
||||
GET_CONTEXT = 0x0008,
|
||||
SET_CONTEXT = 0x0010,
|
||||
SET_INFORMATION = 0x0020,
|
||||
QUERY_INFORMATION = 0x0040,
|
||||
SET_THREAD_TOKEN = 0x0080,
|
||||
IMPERSONATE = 0x0100,
|
||||
DIRECT_IMPERSONATION = 0x0200
|
||||
}
|
||||
}
|
494
NitroxModel/Platforms/OS/Windows/Internal/Win32Native.cs
Normal file
494
NitroxModel/Platforms/OS/Windows/Internal/Win32Native.cs
Normal file
@@ -0,0 +1,494 @@
|
||||
using System;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows.Internal;
|
||||
|
||||
internal static class Win32Native
|
||||
{
|
||||
[Flags]
|
||||
public enum AssocF : uint
|
||||
{
|
||||
None = 0,
|
||||
Init_NoRemapCLSID = 0x1,
|
||||
Init_ByExeName = 0x2,
|
||||
Open_ByExeName = 0x2,
|
||||
Init_DefaultToStar = 0x4,
|
||||
Init_DefaultToFolder = 0x8,
|
||||
NoUserSettings = 0x10,
|
||||
NoTruncate = 0x20,
|
||||
Verify = 0x40,
|
||||
RemapRunDll = 0x80,
|
||||
NoFixUps = 0x100,
|
||||
IgnoreBaseClass = 0x200,
|
||||
Init_IgnoreUnknown = 0x400,
|
||||
Init_FixedProgId = 0x800,
|
||||
IsProtocol = 0x1000,
|
||||
InitForFile = 0x2000
|
||||
}
|
||||
|
||||
public enum AssocStr
|
||||
{
|
||||
Command = 1,
|
||||
Executable,
|
||||
FriendlyDocName,
|
||||
FriendlyAppName,
|
||||
NoOpen,
|
||||
ShellNewValue,
|
||||
DDECommand,
|
||||
DDEIfExec,
|
||||
DDEApplication,
|
||||
DDETopic,
|
||||
InfoTip,
|
||||
QuickTip,
|
||||
TileInfo,
|
||||
ContentType,
|
||||
DefaultIcon,
|
||||
ShellExtension,
|
||||
DropTarget,
|
||||
DelegateExecute,
|
||||
SupportedUriProtocols,
|
||||
|
||||
// The values below ('Max' excluded) have been introduced in W10 1511
|
||||
ProgID,
|
||||
AppID,
|
||||
AppPublisher,
|
||||
AppIconReference,
|
||||
Max
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the associated program for the file extension.
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <param name="extension"></param>
|
||||
/// <returns></returns>
|
||||
public static string AssocQueryString(AssocStr str, string extension)
|
||||
{
|
||||
const int S_OK = 0;
|
||||
const int S_FALSE = 1;
|
||||
|
||||
uint length = 0;
|
||||
uint ret = AssocQueryString(AssocF.None, str, extension, null, null, ref length);
|
||||
if (ret != S_FALSE)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new((int)length);
|
||||
ret = AssocQueryString(AssocF.None, str, extension, null, sb, ref length);
|
||||
if (ret != S_OK)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
/// <summary>
|
||||
/// If true, kills the debugged process when debugger detaches.
|
||||
/// </summary>
|
||||
/// <param name="killOnExit"></param>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern void DebugSetProcessKillOnExit(bool killOnExit);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool IsWow64Process(
|
||||
[In] SafeHandle hProcess,
|
||||
[Out] [MarshalAs(UnmanagedType.Bool)] out bool wow64Process
|
||||
);
|
||||
|
||||
public static string QueryFullProcessImageName(SafeHandle process, uint flags = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
StringBuilder fileNameBuilder = new(1024);
|
||||
int size = fileNameBuilder.Capacity;
|
||||
return QueryFullProcessImageName(process, flags, fileNameBuilder, ref size) ? fileNameBuilder.ToString() : null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool TerminateProcess(SafeHandle hProcess, int exitCode);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint SuspendThread(SafeHandle hThread);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint ResumeThread(SafeHandle hThread);
|
||||
|
||||
[DllImport("ntdll.dll", PreserveSig = false)]
|
||||
public static extern void NtSuspendProcess(SafeHandle processHandle);
|
||||
|
||||
[DllImport("ntdll.dll", PreserveSig = false, SetLastError = true)]
|
||||
public static extern void NtResumeProcess(SafeHandle processHandle);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool CreateProcess(
|
||||
string lpApplicationName,
|
||||
string lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
ProcessCreationFlags dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
string lpCurrentDirectory,
|
||||
ref StartupInfo lpStartupInfo,
|
||||
out ProcessInfo lpProcessInformation
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern SafeProcessHandle OpenProcess(
|
||||
ProcessAccessFlags processAccess,
|
||||
bool bInheritHandle,
|
||||
int processId
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool ReadProcessMemory(
|
||||
SafeHandle hProcess,
|
||||
IntPtr lpBaseAddress,
|
||||
[Out] byte[] lpBuffer,
|
||||
int dwSize,
|
||||
out IntPtr lpNumberOfBytesRead);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
public static extern NtStatus NtQueryInformationProcess(SafeHandle processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, IntPtr returnLength);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr CreateRemoteThread(
|
||||
SafeHandle hProcess,
|
||||
IntPtr lpThreadAttributes,
|
||||
uint dwStackSize,
|
||||
IntPtr lpStartAddress,
|
||||
IntPtr lpParameter,
|
||||
uint dwCreationFlags,
|
||||
out IntPtr lpThreadId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool FlushInstructionCache(SafeHandle hProcess, IntPtr lpBaseAddress, uint dwSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern SafeAccessTokenHandle OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
|
||||
|
||||
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern uint AssocQueryString(AssocF flags, AssocStr str, string extension, string pszExtra, [Out] StringBuilder pszOut, ref uint pcchOut);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool QueryFullProcessImageName([In] SafeHandle hProcess, [In] uint dwFlags, [Out] StringBuilder lpExeName, [In] [Out] ref int lpdwSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool QueryFullProcessImageName([In] SafeHandle hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize);
|
||||
|
||||
[DllImport("Wintrust.dll", PreserveSig = true, SetLastError = false)]
|
||||
private static extern uint WinVerifyTrust(IntPtr hWnd, IntPtr pgActionID, IntPtr pWinTrustData);
|
||||
|
||||
public static bool IsTrusted(string fileName)
|
||||
{
|
||||
return WinVerifyTrust(fileName) == 0;
|
||||
}
|
||||
|
||||
private static uint WinVerifyTrust(string fileName)
|
||||
{
|
||||
Guid wintrust_action_generic_verify_v2 = new("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}");
|
||||
uint result = 0;
|
||||
using (WINTRUST_FILE_INFO fileInfo = new(fileName,
|
||||
Guid.Empty))
|
||||
using (UnmanagedPointer guidPtr = new(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid))),
|
||||
AllocMethod.HGlobal))
|
||||
using (UnmanagedPointer wvtDataPtr = new(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_DATA))),
|
||||
AllocMethod.HGlobal))
|
||||
{
|
||||
WINTRUST_DATA data = new(fileInfo);
|
||||
IntPtr pGuid = guidPtr;
|
||||
IntPtr pData = wvtDataPtr;
|
||||
Marshal.StructureToPtr(wintrust_action_generic_verify_v2,
|
||||
pGuid,
|
||||
true);
|
||||
Marshal.StructureToPtr(data,
|
||||
pData,
|
||||
true);
|
||||
result = WinVerifyTrust(IntPtr.Zero,
|
||||
pGuid,
|
||||
pData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal struct WINTRUST_FILE_INFO : IDisposable
|
||||
{
|
||||
public WINTRUST_FILE_INFO(string fileName, Guid subject)
|
||||
{
|
||||
cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_FILE_INFO));
|
||||
|
||||
pcwszFilePath = fileName;
|
||||
|
||||
if (subject != Guid.Empty)
|
||||
{
|
||||
pgKnownSubject = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
|
||||
|
||||
Marshal.StructureToPtr(subject, pgKnownSubject, true);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
pgKnownSubject = IntPtr.Zero;
|
||||
}
|
||||
|
||||
hFile = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public uint cbStruct;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPTStr)] public string pcwszFilePath;
|
||||
|
||||
public IntPtr hFile;
|
||||
|
||||
public IntPtr pgKnownSubject;
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (pgKnownSubject != IntPtr.Zero)
|
||||
{
|
||||
Marshal.DestroyStructure(pgKnownSubject, typeof(Guid));
|
||||
|
||||
Marshal.FreeHGlobal(pgKnownSubject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum AllocMethod
|
||||
{
|
||||
HGlobal,
|
||||
CoTaskMem
|
||||
}
|
||||
|
||||
private enum UnionChoice
|
||||
{
|
||||
File = 1,
|
||||
Catalog,
|
||||
Blob,
|
||||
Signer,
|
||||
Cert
|
||||
}
|
||||
|
||||
private enum UiChoice
|
||||
{
|
||||
All = 1,
|
||||
NoUI,
|
||||
NoBad,
|
||||
NoGood
|
||||
}
|
||||
|
||||
private enum RevocationCheckFlags
|
||||
{
|
||||
None = 0,
|
||||
WholeChain
|
||||
}
|
||||
|
||||
private enum StateAction
|
||||
{
|
||||
Ignore = 0,
|
||||
Verify,
|
||||
Close,
|
||||
AutoCache,
|
||||
AutoCacheFlush
|
||||
}
|
||||
|
||||
private enum TrustProviderFlags
|
||||
{
|
||||
UseIE4Trust = 1,
|
||||
NoIE4Chain = 2,
|
||||
NoPolicyUsage = 4,
|
||||
RevocationCheckNone = 16,
|
||||
RevocationCheckEndCert = 32,
|
||||
RevocationCheckChain = 64,
|
||||
RecovationCheckChainExcludeRoot = 128,
|
||||
Safer = 256,
|
||||
HashOnly = 512,
|
||||
UseDefaultOSVerCheck = 1024,
|
||||
LifetimeSigning = 2048
|
||||
}
|
||||
|
||||
private enum UIContext
|
||||
{
|
||||
Execute = 0,
|
||||
Install
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
|
||||
internal static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
|
||||
internal static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, long dwNewLong);
|
||||
|
||||
[Flags]
|
||||
public enum WS : long
|
||||
{
|
||||
WS_BORDER = 0x00800000L,
|
||||
WS_CAPTION = 0x00C00000L,
|
||||
WS_CHILD = 0x40000000L,
|
||||
WS_CHILDWINDOW = 0x40000000L,
|
||||
WS_CLIPCHILDREN = 0x02000000L,
|
||||
WS_CLIPSIBLINGS = 0x04000000L,
|
||||
WS_DISABLED = 0x08000000L,
|
||||
WS_DLGFRAME = 0x00400000L,
|
||||
WS_GROUP = 0x00020000L,
|
||||
WS_HSCROLL = 0x00100000L,
|
||||
WS_ICONIC = 0x20000000L,
|
||||
WS_MAXIMIZE = 0x01000000L,
|
||||
WS_MAXIMIZEBOX = 0x00010000L,
|
||||
WS_MINIMIZE = 0x20000000L,
|
||||
WS_MINIMIZEBOX = 0x00020000L,
|
||||
WS_OVERLAPPED = 0x00000000L,
|
||||
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
|
||||
WS_POPUP = 0x80000000L,
|
||||
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
|
||||
WS_SIZEBOX = 0x00040000L,
|
||||
WS_SYSMENU = 0x00080000L,
|
||||
WS_TABSTOP = 0x00010000L,
|
||||
WS_THICKFRAME = 0x00040000L,
|
||||
WS_TILED = 0x00000000L,
|
||||
WS_TILEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
|
||||
WS_VISIBLE = 0x10000000L,
|
||||
WS_VSCROLL = 0x00200000L
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WINTRUST_DATA : IDisposable
|
||||
{
|
||||
public WINTRUST_DATA(WINTRUST_FILE_INFO fileInfo)
|
||||
{
|
||||
cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_DATA));
|
||||
|
||||
pInfoStruct = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_FILE_INFO)));
|
||||
|
||||
Marshal.StructureToPtr(fileInfo, pInfoStruct, false);
|
||||
|
||||
dwUnionChoice = UnionChoice.File;
|
||||
|
||||
pPolicyCallbackData = IntPtr.Zero;
|
||||
|
||||
pSIPCallbackData = IntPtr.Zero;
|
||||
|
||||
dwUIChoice = UiChoice.NoUI;
|
||||
|
||||
fdwRevocationChecks = RevocationCheckFlags.None;
|
||||
|
||||
dwStateAction = StateAction.Ignore;
|
||||
|
||||
hWVTStateData = IntPtr.Zero;
|
||||
|
||||
pwszURLReference = IntPtr.Zero;
|
||||
|
||||
dwProvFlags = TrustProviderFlags.Safer;
|
||||
|
||||
dwUIContext = UIContext.Execute;
|
||||
}
|
||||
|
||||
public uint cbStruct;
|
||||
|
||||
public IntPtr pPolicyCallbackData;
|
||||
|
||||
public IntPtr pSIPCallbackData;
|
||||
|
||||
public UiChoice dwUIChoice;
|
||||
|
||||
public RevocationCheckFlags fdwRevocationChecks;
|
||||
|
||||
public UnionChoice dwUnionChoice;
|
||||
|
||||
public IntPtr pInfoStruct;
|
||||
|
||||
public StateAction dwStateAction;
|
||||
|
||||
public IntPtr hWVTStateData;
|
||||
|
||||
private readonly IntPtr pwszURLReference;
|
||||
|
||||
public TrustProviderFlags dwProvFlags;
|
||||
|
||||
public UIContext dwUIContext;
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (dwUnionChoice == UnionChoice.File)
|
||||
{
|
||||
WINTRUST_FILE_INFO info = new();
|
||||
|
||||
Marshal.PtrToStructure(pInfoStruct, info);
|
||||
|
||||
info.Dispose();
|
||||
|
||||
Marshal.DestroyStructure(pInfoStruct, typeof(WINTRUST_FILE_INFO));
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(pInfoStruct);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class UnmanagedPointer : IDisposable
|
||||
{
|
||||
private readonly AllocMethod m_meth;
|
||||
|
||||
private IntPtr m_ptr;
|
||||
|
||||
public UnmanagedPointer(IntPtr ptr, AllocMethod method)
|
||||
{
|
||||
m_meth = method;
|
||||
|
||||
m_ptr = ptr;
|
||||
}
|
||||
|
||||
public static implicit operator IntPtr(UnmanagedPointer ptr) => ptr.m_ptr;
|
||||
|
||||
~UnmanagedPointer()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (m_ptr != IntPtr.Zero)
|
||||
{
|
||||
if (m_meth == AllocMethod.HGlobal)
|
||||
{
|
||||
Marshal.FreeHGlobal(m_ptr);
|
||||
}
|
||||
|
||||
else if (m_meth == AllocMethod.CoTaskMem)
|
||||
{
|
||||
Marshal.FreeCoTaskMem(m_ptr);
|
||||
}
|
||||
|
||||
m_ptr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
}
|
||||
}
|
268
NitroxModel/Platforms/OS/Windows/RegistryEx.cs
Normal file
268
NitroxModel/Platforms/OS/Windows/RegistryEx.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||
#endif
|
||||
public static class RegistryEx
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the value of the registry key or returns the default value of <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="pathWithValue">
|
||||
/// Full path to the registry key. If the registry hive is omitted then "current user" is used.
|
||||
/// </param>
|
||||
/// <param name="defaultValue">The default value if the registry key is not found or failed to convert to <see cref="T" />.</param>
|
||||
/// <typeparam name="T">Type of value to read. If the value in the registry key does not match it will try to convert.</typeparam>
|
||||
/// <returns>Value as read from registry or null if not found.</returns>
|
||||
public static T Read<T>(string pathWithValue, T defaultValue = default)
|
||||
{
|
||||
(RegistryKey baseKey, string valueKey) = GetKey(pathWithValue, false);
|
||||
if (baseKey == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object value = baseKey.GetValue(valueKey);
|
||||
if (value == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
Type typeOfT = typeof(T);
|
||||
return value.GetType() == typeOfT ? (T)value : (T)TypeDescriptor.GetConverter(typeOfT).ConvertFrom(value);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
finally
|
||||
{
|
||||
baseKey.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the whole subtree or value, whichever exists.
|
||||
/// </summary>
|
||||
/// <param name="pathWithOptionalValue">If no value name is given it will delete the key instead.</param>
|
||||
/// <returns>True if something was deleted.</returns>
|
||||
public static bool Delete(string pathWithOptionalValue)
|
||||
{
|
||||
(RegistryKey key, string valueKey) = GetKey(pathWithOptionalValue);
|
||||
if (key == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to delete the key.
|
||||
RegistryKey prev = key;
|
||||
key = key.OpenSubKey(valueKey);
|
||||
if (key != null)
|
||||
{
|
||||
key.DeleteSubKeyTree(valueKey);
|
||||
key.Dispose();
|
||||
prev.Dispose();
|
||||
return true;
|
||||
}
|
||||
key = prev; // Restore state for next step
|
||||
|
||||
// Not a key, delete the value if it exists.
|
||||
if (key.GetValue(valueKey) != null)
|
||||
{
|
||||
key.DeleteValue(valueKey);
|
||||
key.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Nothing to delete.
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Write<T>(string pathWithKey, T value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
(RegistryKey baseKey, string valueKey) = GetKey(pathWithKey, true, true);
|
||||
if (baseKey == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Figure out what kind of value to store.
|
||||
RegistryValueKind? kind = value switch
|
||||
{
|
||||
int => RegistryValueKind.DWord,
|
||||
long => RegistryValueKind.QWord,
|
||||
byte[] => RegistryValueKind.Binary,
|
||||
string => RegistryValueKind.String,
|
||||
_ => null
|
||||
};
|
||||
// If regKey already exists, and we don't know how to parse the value, use existing kind.
|
||||
if (!kind.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
kind = baseKey.GetValueKind(valueKey);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored - thrown when key does not exist.
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
baseKey.SetValue(valueKey, value, kind.GetValueOrDefault(RegistryValueKind.String));
|
||||
}
|
||||
finally
|
||||
{
|
||||
baseKey.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a key exists.
|
||||
/// </summary>
|
||||
public static bool Exists(string pathWithValue)
|
||||
{
|
||||
(RegistryKey baseKey, string valueKey) = GetKey(pathWithValue, false);
|
||||
if (baseKey == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return baseKey.GetValue(valueKey) != null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
baseKey.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for a registry value to have the given value.
|
||||
/// </summary>
|
||||
public static async Task CompareWaitAsync<T>(string pathWithKey, Func<T, bool> predicate, CancellationToken token)
|
||||
{
|
||||
CancellationTokenSource innerCts = null;
|
||||
if (token == default)
|
||||
{
|
||||
innerCts = new(TimeSpan.FromSeconds(10));
|
||||
token = innerCts.Token;
|
||||
}
|
||||
|
||||
RegistryKey baseKey = null;
|
||||
try
|
||||
{
|
||||
// Test once before in-case it is already successful.
|
||||
(baseKey, string valueKey) = GetKey(pathWithKey, false);
|
||||
if (Test(baseKey, valueKey, predicate))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for predicate to be successful.
|
||||
while (true)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
// If regkey didn't exist yet it might later.
|
||||
if (baseKey == null)
|
||||
{
|
||||
(baseKey, valueKey) = GetKey(pathWithKey, false);
|
||||
}
|
||||
|
||||
if (Test(baseKey, valueKey, predicate))
|
||||
{
|
||||
break;
|
||||
}
|
||||
await Task.Delay(100, token);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
baseKey?.Dispose();
|
||||
innerCts?.Dispose();
|
||||
}
|
||||
|
||||
static bool Test(RegistryKey regKey, string regKeyName, Func<T, bool> testPredicate)
|
||||
{
|
||||
T preTestVal = regKey?.GetValue(regKeyName) is T typedValue ? typedValue : default;
|
||||
return testPredicate(preTestVal);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task CompareWaitAsync<T>(string pathWithKey, Func<T, bool> predicate, TimeSpan timeout = default)
|
||||
{
|
||||
CancellationTokenSource source = new(timeout == default ? TimeSpan.FromSeconds(10) : timeout);
|
||||
return CompareWaitAsync(pathWithKey, predicate, source.Token);
|
||||
}
|
||||
|
||||
private static (RegistryKey baseKey, string valueKey) GetKey(string path, bool needsWriteAccess = true, bool createIfNotExists = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
path = path.Trim();
|
||||
|
||||
// Parse path to get the registry key instance.
|
||||
Span<string> parts = path.Split(Path.DirectorySeparatorChar);
|
||||
Span<string> partsWithoutHive;
|
||||
RegistryHive hive = RegistryHive.CurrentUser;
|
||||
string regPathWithoutHiveOrKey;
|
||||
if (!parts[0].Equals("Computer", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
partsWithoutHive = parts[..^1];
|
||||
regPathWithoutHiveOrKey = string.Join(Path.DirectorySeparatorChar.ToString(), partsWithoutHive.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
partsWithoutHive = parts[2..^1];
|
||||
regPathWithoutHiveOrKey = string.Join(Path.DirectorySeparatorChar.ToString(), partsWithoutHive.ToArray());
|
||||
hive = parts[1].ToLower() switch
|
||||
{
|
||||
"hkey_classes_root" => RegistryHive.ClassesRoot,
|
||||
"hkey_local_machine" => RegistryHive.LocalMachine,
|
||||
"hkey_current_user" => RegistryHive.CurrentUser,
|
||||
"hkey_users" => RegistryHive.Users,
|
||||
"hkey_current_config" => RegistryHive.CurrentConfig,
|
||||
_ => throw new ArgumentException($"Path must contain a valid registry hive but was given '{parts[1]}'", nameof(path))
|
||||
};
|
||||
}
|
||||
|
||||
RegistryKey hiveRef = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64);
|
||||
RegistryKey key = hiveRef.OpenSubKey(regPathWithoutHiveOrKey, needsWriteAccess);
|
||||
// Should the key (and its path leading to it) be created?
|
||||
if (key == null && createIfNotExists)
|
||||
{
|
||||
key = hiveRef;
|
||||
foreach (string part in partsWithoutHive)
|
||||
{
|
||||
RegistryKey prev = key;
|
||||
try
|
||||
{
|
||||
key = key?.OpenSubKey(part, needsWriteAccess) ?? key?.CreateSubKey(part, needsWriteAccess);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup old/parent key reference
|
||||
prev?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (key, parts[^1]);
|
||||
}
|
||||
}
|
31
NitroxModel/Platforms/OS/Windows/RegistryKeyValueStore.cs
Normal file
31
NitroxModel/Platforms/OS/Windows/RegistryKeyValueStore.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||
#endif
|
||||
public class RegistryKeyValueStore : IKeyValueStore
|
||||
{
|
||||
public static string KeyToRegistryPath(string key) => @$"SOFTWARE\Nitrox\{key}";
|
||||
|
||||
public T GetValue<T>(string key, T defaultValue) => RegistryEx.Read(KeyToRegistryPath(key), defaultValue);
|
||||
|
||||
public bool SetValue<T>(string key, T value)
|
||||
{
|
||||
try
|
||||
{
|
||||
RegistryEx.Write(KeyToRegistryPath(key), value);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool DeleteKey(string key) => RegistryEx.Delete(KeyToRegistryPath(key));
|
||||
|
||||
public bool KeyExists(string key) => RegistryEx.Exists(KeyToRegistryPath(key));
|
||||
}
|
99
NitroxModel/Platforms/OS/Windows/WinFileSystem.cs
Normal file
99
NitroxModel/Platforms/OS/Windows/WinFileSystem.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.Win32;
|
||||
using NitroxModel.Platforms.OS.Shared;
|
||||
using NitroxModel.Platforms.OS.Windows.Internal;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows;
|
||||
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||
#endif
|
||||
internal class WinFileSystem : FileSystem
|
||||
{
|
||||
public override IEnumerable<string> ExecutableFileExtensions { get; } = ["exe", "cmd", "bat"];
|
||||
public override string TextEditor => GetFullPath("notepad.exe");
|
||||
|
||||
public override IEnumerable<string> GetDefaultPrograms(string file)
|
||||
{
|
||||
string SearchExecutableInSameDirectory(string path)
|
||||
{
|
||||
if (Path.GetExtension(path) != "")
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
foreach (string ext in ExecutableFileExtensions)
|
||||
{
|
||||
string newPath = Path.ChangeExtension(path, ext);
|
||||
if (File.Exists(newPath))
|
||||
{
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
string extension = Path.GetExtension(file);
|
||||
if (string.IsNullOrWhiteSpace(extension))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
string defaultProgramOnDblClick = Win32Native.AssocQueryString(Win32Native.AssocStr.Executable, extension);
|
||||
if (!string.IsNullOrWhiteSpace(defaultProgramOnDblClick) && File.Exists(defaultProgramOnDblClick))
|
||||
{
|
||||
yield return Path.GetFullPath(defaultProgramOnDblClick);
|
||||
}
|
||||
|
||||
string baseKey = $@"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{extension}";
|
||||
using RegistryKey rk = Registry.CurrentUser.OpenSubKey($@"{baseKey}\OpenWithList");
|
||||
if (rk?.GetValue("MRUList") is not string mruList)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (char c in mruList)
|
||||
{
|
||||
string fullPath = SearchExecutableInSameDirectory(GetFullPath(rk.GetValue(c.ToString()).ToString()));
|
||||
if (fullPath == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds full access flag to the directory (and sub files/directories) for the current user.
|
||||
/// </summary>
|
||||
/// <param name="directory"></param>
|
||||
/// <returns>True if set, false if program is not allowed to change permissions.</returns>
|
||||
public override bool SetFullAccessToCurrentUser(string directory)
|
||||
{
|
||||
try
|
||||
{
|
||||
string identity = WindowsIdentity.GetCurrent().Name;
|
||||
|
||||
DirectoryInfo dir = new(directory);
|
||||
DirectorySecurity flags = dir.GetAccessControl();
|
||||
flags.AddAccessRule(new(identity, FileSystemRights.FullControl, InheritanceFlags.None, PropagationFlags.InheritOnly, AccessControlType.Allow));
|
||||
flags.AddAccessRule(new (identity, FileSystemRights.FullControl, InheritanceFlags.ContainerInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
|
||||
flags.AddAccessRule(new (identity, FileSystemRights.FullControl, InheritanceFlags.ObjectInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
|
||||
dir.SetAccessControl(flags);
|
||||
return true;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsTrustedFile(string file) => Win32Native.IsTrusted(file);
|
||||
}
|
63
NitroxModel/Platforms/OS/Windows/WindowsApi.cs
Normal file
63
NitroxModel/Platforms/OS/Windows/WindowsApi.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static NitroxModel.Platforms.OS.Windows.Internal.Win32Native;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows;
|
||||
|
||||
public class WindowsApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies default OS animations to the window handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note on Windows OS: it will force enable resizing of a Window if <see cref="canResize"/> is true. Make sure to set it correctly.
|
||||
/// </remarks>
|
||||
public static void EnableDefaultWindowAnimations(nint windowHandle, bool canResize)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WS dwNewLong = WS.WS_CAPTION | WS.WS_CLIPCHILDREN | WS.WS_MINIMIZEBOX | WS.WS_MAXIMIZEBOX | WS.WS_SYSMENU;
|
||||
if (canResize)
|
||||
{
|
||||
dwNewLong |= WS.WS_SIZEBOX;
|
||||
}
|
||||
|
||||
HandleRef handle = new(null, windowHandle);
|
||||
switch (IntPtr.Size)
|
||||
{
|
||||
case 8:
|
||||
SetWindowLongPtr64(handle, -16, (long)dwNewLong);
|
||||
break;
|
||||
default:
|
||||
SetWindowLong32(handle, -16, (int)dwNewLong);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void BringProcessToFront(IntPtr windowHandle)
|
||||
{
|
||||
if (windowHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const int SW_RESTORE = 9;
|
||||
if (IsIconic(windowHandle))
|
||||
{
|
||||
ShowWindow(windowHandle, SW_RESTORE);
|
||||
}
|
||||
|
||||
SetForegroundWindow(windowHandle);
|
||||
}
|
||||
|
||||
[DllImport("User32.dll")]
|
||||
private static extern bool SetForegroundWindow(IntPtr handle);
|
||||
[DllImport("User32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr handle, int nCmdShow);
|
||||
[DllImport("User32.dll")]
|
||||
private static extern bool IsIconic(IntPtr handle);
|
||||
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
}
|
Reference in New Issue
Block a user