using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Drawing; using System.IO; using System.IO.MemoryMappedFiles; using System.Windows.Forms; using ReClassNET.Core; using ReClassNET.Debugger; using ReClassNET.Extensions; using ReClassNET.Memory; using ReClassNET.Plugins; namespace LoadBinaryPlugin { public class LoadBinaryPluginExt : Plugin, ICoreProcessFunctions { private readonly object sync = new object(); private IPluginHost host; private string currentFile; private Dictionary openFiles; public override Image Icon => Properties.Resources.icon; public override bool Initialize(IPluginHost host) { Contract.Requires(host != null); this.host = host ?? throw new ArgumentNullException(nameof(host)); host.Process.CoreFunctions.RegisterFunctions("Load Binary", this); openFiles = new Dictionary(); return true; } public override void Terminate() { foreach (var kv in openFiles) { kv.Value.File.Dispose(); } openFiles.Clear(); host = null; } /// Gets a by its plugin internal identifier. /// The identifier. /// The file or null if the identifier doesn't exist. private MemoryMappedFileInfo GetMappedFileById(IntPtr id) { openFiles.TryGetValue(id, out var file); return file; } /// Logs the exception and removes the file. /// The identifier. /// The exception. private void LogErrorAndRemoveFile(IntPtr id, Exception ex) { Contract.Requires(ex != null); if (openFiles.TryGetValue(id, out var info)) { info.File.Dispose(); } openFiles.Remove(id); host.Logger.Log(ex); } /// Queries if the file is valid. /// The file to check. /// True if the file is valid, false if not. public bool IsProcessValid(IntPtr process) { lock (sync) { return GetMappedFileById(process) != null; } } /// Opens the file. /// The file id. /// The desired access. (ignored) /// A plugin internal handle to the file. public IntPtr OpenRemoteProcess(IntPtr id, ProcessAccess desiredAccess) { lock (sync) { if (currentFile.GetHashCode() == id.ToInt32()) { try { var mappedFile = MemoryMappedFile.CreateFromFile(currentFile); var handle = (IntPtr)mappedFile.SafeMemoryMappedFileHandle.GetHashCode(); openFiles.Add( handle, new MemoryMappedFileInfo( currentFile, (int)new FileInfo(currentFile).Length, mappedFile ) ); return handle; } catch (Exception ex) { host.Logger.Log(ex); } } } return IntPtr.Zero; } /// Closes the file. /// The file to close. public void CloseRemoteProcess(IntPtr process) { lock (sync) { if (openFiles.TryGetValue(process, out var info)) { openFiles.Remove(process); info.File.Dispose(); } } } /// Reads memory of the file. /// The process to read from. /// The address to read from. /// [out] The buffer to read into. /// The offset into the buffer. /// The size of the memory to read. /// True if it succeeds, false if it fails. public bool ReadRemoteMemory(IntPtr process, IntPtr address, ref byte[] buffer, int offset, int size) { lock (sync) { var info = GetMappedFileById(process); if (info != null) { try { using (var stream = info.File.CreateViewStream(address.ToInt64Bits(), size)) { stream.Read(buffer, 0, size); return true; } } catch (UnauthorizedAccessException) { // address + size >= file size } catch (Exception ex) { LogErrorAndRemoveFile(process, ex); } } return false; } } /// Not supported. /// The file to write to. /// The address to write to. /// [in] The memory to write. /// The offset into the buffer. /// The size of the memory to write. /// True if it succeeds, false if it fails. public bool WriteRemoteMemory(IntPtr process, IntPtr address, ref byte[] buffer, int offset, int size) { // Not supported. return false; } /// Opens a file browser dialog and reports the selected file. /// The callback which gets called for the selected file. public void EnumerateProcesses(EnumerateProcessCallback callbackProcess) { if (callbackProcess == null) { return; } using (var ofd = new OpenFileDialog()) { ofd.Filter = "All|*.*"; if (ofd.ShowDialog() == DialogResult.OK) { currentFile = ofd.FileName; var data = new EnumerateProcessData { Id = (IntPtr)currentFile.GetHashCode(), Name = Path.GetFileName(currentFile), Path = currentFile }; callbackProcess(ref data); } } } /// Reports a single module and section for the loaded file. /// The process. /// The callback which gets called for every section. /// The callback which gets called for every module. public void EnumerateRemoteSectionsAndModules(IntPtr process, EnumerateRemoteSectionCallback callbackSection, EnumerateRemoteModuleCallback callbackModule) { lock (sync) { var info = GetMappedFileById(process); if (info != null) { var module = new EnumerateRemoteModuleData { BaseAddress = IntPtr.Zero, Path = info.Path, Size = (IntPtr)info.Size }; callbackModule(ref module); var section = new EnumerateRemoteSectionData { BaseAddress = IntPtr.Zero, Size = (IntPtr)info.Size, Type = SectionType.Image, Category = SectionCategory.Unknown, ModulePath = info.Path, Name = string.Empty, Protection = SectionProtection.Read }; callbackSection(ref section); } } } public void ControlRemoteProcess(IntPtr process, ControlRemoteProcessAction action) { // Not supported. } public bool AttachDebuggerToProcess(IntPtr id) { // Not supported. return false; } public void DetachDebuggerFromProcess(IntPtr id) { // Not supported. } public bool AwaitDebugEvent(ref DebugEvent evt, int timeoutInMilliseconds) { // Not supported. return false; } public void HandleDebugEvent(ref DebugEvent evt) { // Not supported. } public bool SetHardwareBreakpoint(IntPtr id, IntPtr address, HardwareBreakpointRegister register, HardwareBreakpointTrigger trigger, HardwareBreakpointSize size, bool set) { // Not supported. return false; } } }