using WhiteMagic.Discovery;
using Process = System.Diagnostics.Process;
using ProcessModule = System.Diagnostics.ProcessModule;
namespace WhiteMagic;
///
/// A module (loaded DLL/EXE image) in the target process, obtained by indexing the
/// facade with a module name (e.g. magic["user32"]). Exposes the module's base
/// address and resolves exported functions by name.
///
public sealed class RemoteModule
{
private readonly Magic _magic;
/// The module's file name as reported by the OS (e.g. user32.dll).
public string Name { get; }
/// The module's load address in the target process.
public IntPtr BaseAddress { get; }
internal RemoteModule(Magic magic, string moduleName)
{
ArgumentNullException.ThrowIfNull(magic);
ArgumentException.ThrowIfNullOrEmpty(moduleName);
_magic = magic;
(string name, IntPtr baseAddress) = FindModule(magic.Memory.ProcessId, moduleName);
Name = name;
BaseAddress = baseAddress;
}
///
/// Resolves an exported function by name and returns a
/// bound to its address. Export forwarders are followed.
///
public RemoteFunction this[string functionName]
{
get
{
IntPtr address = GetExportAddress(functionName);
return new RemoteFunction(_magic, functionName, address);
}
}
/// Resolves the absolute address of an exported function by name.
public IntPtr GetExportAddress(string functionName)
{
ArgumentException.ThrowIfNullOrEmpty(functionName);
var parser = new PeHeaderParser(_magic.Memory, BaseAddress);
return parser.GetExportAddress(functionName);
}
private static (string Name, IntPtr BaseAddress) FindModule(int processId, string moduleName)
{
using Process process = Process.GetProcessById(processId);
foreach (ProcessModule module in process.Modules)
{
if (NameMatches(module.ModuleName, moduleName))
return (module.ModuleName, module.BaseAddress);
}
throw new DllNotFoundException(
$"Module '{moduleName}' is not loaded in process {processId}.");
}
///
/// Resolves a module's base address by name within a target process, returning
/// if it is not loaded. Used by export-forwarder resolution.
///
internal static IntPtr ResolveBase(int processId, string moduleName)
{
using Process process = Process.GetProcessById(processId);
foreach (ProcessModule module in process.Modules)
{
if (NameMatches(module.ModuleName, moduleName))
return module.BaseAddress;
}
return IntPtr.Zero;
}
///
/// Matches a loaded module's file name against a requested name, tolerating a missing
/// or present .dll extension and ignoring case (e.g. KERNEL32 matches
/// kernel32.dll).
///
private static bool NameMatches(string actual, string requested)
{
if (string.Equals(actual, requested, StringComparison.OrdinalIgnoreCase))
return true;
string actualNoExt = Path.GetFileNameWithoutExtension(actual);
string requestedNoExt = requested.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
? requested[..^4]
: requested;
return string.Equals(actualNoExt, requestedNoExt, StringComparison.OrdinalIgnoreCase);
}
}