{"id":50458356,"url":"https://github.com/theo-abel/mono-injector","last_synced_at":"2026-06-01T03:32:49.663Z","repository":{"id":360520858,"uuid":"1227400325","full_name":"theo-abel/mono-injector","owner":"theo-abel","description":"Rust Mono assembly injector for Unity and Mono-hosted Windows processes, with CLI and GUI, profiles, and more.","archived":false,"fork":false,"pushed_at":"2026-05-26T18:21:54.000Z","size":5513,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-26T20:14:23.870Z","etag":null,"topics":["cli","dotnet","gui","injector","modding","mono","rust","unity","windows"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theo-abel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-02T16:22:57.000Z","updated_at":"2026-05-26T19:33:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/theo-abel/mono-injector","commit_stats":null,"previous_names":["theo-abel/mono-injector"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/theo-abel/mono-injector","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theo-abel%2Fmono-injector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theo-abel%2Fmono-injector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theo-abel%2Fmono-injector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theo-abel%2Fmono-injector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theo-abel","download_url":"https://codeload.github.com/theo-abel/mono-injector/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theo-abel%2Fmono-injector/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33759178,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","dotnet","gui","injector","modding","mono","rust","unity","windows"],"created_at":"2026-06-01T03:32:49.202Z","updated_at":"2026-06-01T03:32:49.646Z","avatar_url":"https://github.com/theo-abel.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp\u003e\n    \u003cimg src=\"gui/assets/mono-injector-banner.png\" alt=\"mono-injector banner\" width=\"100%\"\u003e\n    \u003cdiv align=\"center\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/Built%20with%20Rust-grey?style=for-the-badge\u0026logo=rust\u0026color=%23282828\"\u003e\n    \u003c/div\u003e\n\u003c/p\u003e\n\n## Description\n\n`mono-injector` is a Rust workspace for injecting managed .NET assemblies into Windows processes that host Mono, especially Unity games and tools.\n\nThe workspace is split into four crates:\n\n| Crate | Purpose |\n| --- | --- |\n| `mono-injector` | Low-level Windows remote Mono API caller. |\n| `mono-injector-core` | Reusable orchestration layer for profiles, process discovery, injection/ejection planning, metadata inspection, remembered handles, Steam launch, and readiness waits. |\n| `mono-injector-cli` | Command-line frontend exposed as the `mono-injector` binary. |\n| `mono-injector-gui` | Iced-based graphical frontend using the same core layer. |\n\nThe default managed entry point is `Loader::Init`. The default eject entry point is `Loader::Unload`. If no namespace is supplied, the core inspects the assembly metadata and prefers the namespace of the selected class before falling back to the most common non-empty namespace.\n\nUse this on software you own, control, or are explicitly allowed to modify. Injection into protected or third-party processes can violate terms of service and can crash the target process.\n\n## Features\n\nInjection and ejection:\n\n- Inject managed assemblies into Windows processes hosting Mono.\n- Eject managed assemblies by invoking a cleanup method and unloading the remembered Mono assembly handle.\n- Support x86 and x64 targets through architecture-specific remote stubs.\n- Execute Mono API calls from outside the target process without injecting a native loader DLL.\n- Configure namespace, class, inject method, and eject method per operation.\n- Use practical defaults for common loaders: class `Loader`, inject method `Init`, eject method `Unload`.\n- Run inject and eject dry-runs that resolve the plan without calling Mono in the target process.\n- Use raw handle ejection for advanced/manual recovery, gated behind `--force`.\n\nProcess and runtime resolution:\n\n- Resolve targets by PID or exact process name.\n- List running processes and filter by process name, module name, Mono runtime modules, or Unity modules.\n- Include matching or loaded module names in process listings.\n- Select the Mono runtime module with a case-insensitive `--mono-module` hint.\n- Configure remote-thread wait timeout with `--timeout-ms`.\n- Configure the base directory passed to `mono_assembly_load_from_full` with `--base-dir`.\n- Validate managed assemblies by reading .NET metadata.\n- Infer the namespace from .NET metadata when it is omitted.\n\nProfiles and remembered state:\n\n- Store reusable TOML profiles for target, assembly, entry point, runtime, wait, settle, and Steam settings.\n- Select profiles by positional name or `--profile` flag.\n- Remember successful injection handles locally for later safe ejection.\n- Protect against PID reuse by recording process start time.\n- Guard ejection by matching remembered handles against the same process instance, namespace, and class.\n- Require `--latest` or an explicit handle when several remembered injections match.\n- Clean stale remembered records or clear all records manually.\n\nLaunch and readiness:\n\n- Wait for a process before injecting.\n- Wait for a module before injecting.\n- Configure process/module wait timeout and poll interval.\n- Configure a settle delay after readiness before injection.\n- Launch Steam apps with `steam://rungameid/\u003cAPP_ID\u003e`.\n- Skip Steam launch when the target process is already running.\n- Use Steam defaults of `d3d11.dll` readiness and `8000ms` settle delay unless overridden.\n\nFrontends and tooling:\n\n- CLI supports JSON output where useful through the global `--json` flag.\n- CLI can run a post-injection command with target process, PID, handle, and assembly path environment variables.\n- GUI supports inject, eject, active-injection status, process browsing, profile management, and logs.\n- CLI and GUI share the same `mono-injector-core` planning, profile, state, and runtime behavior.\n- Workspace uses Rust 2024 with `warnings = deny` and Clippy pedantic enabled.\n\n## Docs\n\n### Usage\n\nThe CLI binary is named `mono-injector`. From source, run it with:\n\n```powershell\ncargo run -p mono-injector-cli -- \u003ccommand\u003e [options]\n```\n\nAfter building, run the binary directly:\n\n```powershell\ntarget\\debug\\mono-injector.exe \u003ccommand\u003e [options]\n```\n\nTop-level usage:\n\n```text\nUsage: mono-injector.exe [OPTIONS] \u003cCOMMAND\u003e\n\nCommands:\n  inject   Inject a managed assembly into a target process\n  eject    Eject a previously injected assembly from a target process\n  list     List running processes\n  status   Show remembered injections\n  clean    Remove stale remembered injections\n  profile  Inspect profile configuration\n  help     Print this message or the help of the given subcommand(s)\n\nOptions:\n      --json     Emit machine-readable JSON output where supported\n  -h, --help     Print help\n  -V, --version  Print version\n```\n\n#### `inject`\n\nInject a managed assembly into a target process.\n\n```text\nUsage: mono-injector.exe inject [OPTIONS] [PROFILE] [-- \u003cPOST_COMMAND\u003e...]\n\nArguments:\n  [PROFILE]          Optional profile name\n  [POST_COMMAND]...  Command to run after successful injection. Pass after `--`\n\nOptions:\n      --json                            Emit machine-readable JSON output where supported\n      --profile \u003cPROFILE_ALIAS\u003e         Profile name alias for scripts that prefer flags\n  -p, --process \u003cPROCESS\u003e               Target process id or exact process name\n  -a, --assembly \u003cASSEMBLY\u003e             Managed assembly to load into the target process\n  -n, --namespace \u003cNAMESPACE\u003e           Namespace containing the loader class\n  -c, --class \u003cCLASS_NAME\u003e              Loader class name\n  -m, --method \u003cMETHOD_NAME\u003e            Loader method to invoke after loading the assembly\n      --eject-method \u003cEJECT_METHOD\u003e     Cleanup method recorded for later default ejection\n      --wait                            Wait for the target process before injecting\n      --wait-timeout \u003cWAIT_TIMEOUT\u003e     Time to wait for process/module readiness, for example 120s or 2m [default: 120s]\n      --poll-interval \u003cPOLL_INTERVAL\u003e   Time between process/module readiness checks, for example 1000ms or 1s [default: 1000ms]\n      --wait-module \u003cWAIT_MODULE\u003e       Wait for a loaded module before injecting, for example UnityPlayer.dll\n      --no-wait-module                  Disable the default readiness-module wait used with --steam-app\n      --settle-ms \u003cSETTLE\u003e              Extra time to wait after readiness before injecting. Use 0ms to disable\n      --steam-app \u003cSTEAM_APP\u003e           Launch a Steam app before waiting for the process\n      --dry-run                         Resolve inputs without calling Mono in the target process\n      --timeout-ms \u003cTIMEOUT_MS\u003e         Remote-thread wait timeout in milliseconds [default: 5000]\n      --mono-module \u003cMONO_MODULE_HINT\u003e  Case-insensitive fragment used to find the target Mono module\n      --base-dir \u003cBASE_DIR\u003e             Base directory passed to `mono_assembly_load_from_full`\n  -h, --help                            Print help\n  -V, --version                         Print version\n```\n\nBasic inject using defaults:\n\n```powershell\nmono-injector inject --process Game.exe --assembly C:\\mods\\MyMod.dll\n```\n\nExplicit entry point:\n\n```powershell\nmono-injector inject -p Game.exe -a C:\\mods\\MyMod.dll -n MyMod -c Loader -m Init --eject-method Unload\n```\n\nDry-run an inject plan:\n\n```powershell\nmono-injector inject --dry-run -p Game.exe -a C:\\mods\\MyMod.dll\n```\n\nWait for the process and a readiness module:\n\n```powershell\nmono-injector inject -p Game.exe -a C:\\mods\\MyMod.dll --wait --wait-module UnityPlayer.dll --settle-ms 3000ms\n```\n\nLaunch a Steam app and inject after default Steam readiness behavior:\n\n```powershell\nmono-injector inject -p Game.exe -a C:\\mods\\MyMod.dll --steam-app 480\n```\n\nRun a command after successful injection:\n\n```powershell\nmono-injector inject -p Game.exe -a C:\\mods\\MyMod.dll -- powershell -NoProfile -Command \"Write-Output $env:MONO_INJECTOR_HANDLE\"\n```\n\nThe post-command receives these environment variables:\n\n| Variable | Value |\n| --- | --- |\n| `MONO_INJECTOR_PROCESS` | Target process name. |\n| `MONO_INJECTOR_PID` | Target PID. |\n| `MONO_INJECTOR_HANDLE` | Assembly handle returned by inject, or an empty string if absent. |\n| `MONO_INJECTOR_ASSEMBLY` | Managed assembly path. |\n\n#### `eject`\n\nEject a previously injected assembly from a target process.\n\n```text\nUsage: mono-injector.exe eject [OPTIONS] [PROFILE]\n\nArguments:\n  [PROFILE]  Optional profile name\n\nOptions:\n      --json                            Emit machine-readable JSON output where supported\n      --profile \u003cPROFILE_ALIAS\u003e         Profile name alias for scripts that prefer flags\n  -p, --process \u003cPROCESS\u003e               Target process id or exact process name\n  -a, --assembly \u003cHANDLE\u003e               Assembly handle returned by inject. Defaults to a matching remembered injection\n      --raw-handle \u003cRAW_HANDLE\u003e         Explicit unsafe handle mode; requires --force\n  -n, --namespace \u003cNAMESPACE\u003e           Namespace containing the loader class\n  -c, --class \u003cCLASS_NAME\u003e              Loader class name\n  -m, --method \u003cMETHOD_NAME\u003e            Cleanup method to invoke before closing the assembly\n      --latest                          Use the latest matching remembered injection when several match\n      --force                           Bypass the local injection-record guard for advanced/manual ejection\n      --dry-run                         Resolve inputs without calling Mono in the target process\n      --timeout-ms \u003cTIMEOUT_MS\u003e         Remote-thread wait timeout in milliseconds [default: 5000]\n      --mono-module \u003cMONO_MODULE_HINT\u003e  Case-insensitive fragment used to find the target Mono module\n      --base-dir \u003cBASE_DIR\u003e             Base directory passed to `mono_assembly_load_from_full`\n  -h, --help                            Print help\n  -V, --version                         Print version\n```\n\nEject using the remembered matching handle:\n\n```powershell\nmono-injector eject --process Game.exe\n```\n\nEject a specific remembered handle:\n\n```powershell\nmono-injector eject -p Game.exe -a 0x12345678\n```\n\nResolve an eject plan without touching the process:\n\n```powershell\nmono-injector eject --dry-run -p Game.exe --latest\n```\n\nUse a raw handle manually:\n\n```powershell\nmono-injector eject -p Game.exe --raw-handle 0x12345678 --force -n MyMod -c Loader -m Unload\n```\n\nSafe eject behavior:\n\n- Without `--force`, `eject` requires the handle to be remembered for the same PID, process start time, namespace, and class.\n- If one remembered injection matches, the handle is selected automatically.\n- If several remembered injections match, pass `--latest` or provide a specific `--assembly \u003cHANDLE\u003e`.\n- `--raw-handle` is intentionally unsafe and requires `--force`.\n\n#### `list`\n\nList running processes.\n\n```text\nUsage: mono-injector.exe list [OPTIONS]\n\nOptions:\n  -f, --filter \u003cFILTER\u003e  Case-insensitive substring used to filter process and module names\n      --json             Emit machine-readable JSON output where supported\n      --mono             Show only processes with a Mono runtime module loaded\n      --unity            Show only Unity processes\n      --modules          Include matching or loaded module names in the output\n  -h, --help             Print help\n  -V, --version          Print version\n```\n\nExamples:\n\n```powershell\nmono-injector list\nmono-injector list --mono\nmono-injector list --unity --modules\nmono-injector list --filter Game --modules\nmono-injector --json list --mono\n```\n\n#### `status`\n\nShow remembered injections.\n\n```text\nUsage: mono-injector.exe status [OPTIONS] [PROFILE]\n\nArguments:\n  [PROFILE]  Optional profile name to resolve the target process\n\nOptions:\n      --json                     Emit machine-readable JSON output where supported\n      --profile \u003cPROFILE_ALIAS\u003e  Profile name alias for scripts that prefer flags\n  -p, --process \u003cPROCESS\u003e        Target process id or exact process name\n  -h, --help                     Print help\n  -V, --version                  Print version\n```\n\nExamples:\n\n```powershell\nmono-injector status\nmono-injector status --process Game.exe\nmono-injector status my-profile\nmono-injector --json status\n```\n\n#### `clean`\n\nRemove remembered injection records.\n\n```text\nUsage: mono-injector.exe clean [OPTIONS]\n\nOptions:\n      --all      Remove all remembered injections, including live ones\n      --json     Emit machine-readable JSON output where supported\n  -h, --help     Print help\n  -V, --version  Print version\n```\n\nExamples:\n\n```powershell\nmono-injector clean\nmono-injector clean --all\nmono-injector --json clean\n```\n\nDefault `clean` removes stale records only. `clean --all` removes every remembered record, including records for live process instances.\n\n#### `profile`\n\nInspect profile configuration.\n\n```text\nUsage: mono-injector.exe profile [OPTIONS] \u003cCOMMAND\u003e\n\nCommands:\n  list  List configured profiles\n  show  Show one configured profile\n  path  Print the profiles file path\n  help  Print this message or the help of the given subcommand(s)\n\nOptions:\n      --json     Emit machine-readable JSON output where supported\n  -h, --help     Print help\n  -V, --version  Print version\n```\n\nProfile subcommands:\n\n```text\nUsage: mono-injector.exe profile list [OPTIONS]\nUsage: mono-injector.exe profile show [OPTIONS] \u003cNAME\u003e\nUsage: mono-injector.exe profile path [OPTIONS]\n```\n\nExamples:\n\n```powershell\nmono-injector profile path\nmono-injector profile list\nmono-injector profile show my-profile\nmono-injector --json profile show my-profile\n```\n\nProfiles are TOML and live at the path printed by `mono-injector profile path`. A typical profile file looks like this:\n\n```toml\n[profiles.my-profile]\nprocess = \"Game.exe\"\nassembly = \"C:\\\\mods\\\\MyMod.dll\"\nnamespace = \"MyMod\"\nclass = \"Loader\"\ninject_method = \"Init\"\neject_method = \"Unload\"\nmono_module = \"mono-2.0-bdwgc\"\nbase_dir = \"C:\\\\mods\"\ntimeout_ms = 5000\nwait_module = \"UnityPlayer.dll\"\nsettle_ms = 3000\nsteam_app = 480\n```\n\nAll profile fields are optional, but an inject operation still needs enough data after combining CLI flags and profile values to resolve a target process and assembly. CLI flags override profile values. Remembered injection records are stored separately in the user local data directory as `mono-injector/injections.json`.\n\n### Build\n\nRequirements:\n\n- Windows target environment.\n- Rust toolchain compatible with workspace `rust-version = \"1.95.0\"` and edition 2024.\n- `just` for the documented task runner commands.\n- A target process with an embedded Mono runtime for real injection tests.\n\nBuild the whole workspace:\n\n```powershell\njust build\n```\n\nBuild release artifacts:\n\n```powershell\njust build release\n```\n\nBuild only the CLI:\n\n```powershell\njust build dev mono-injector-cli\n```\n\nBuild only the GUI:\n\n```powershell\njust build dev mono-injector-gui\n```\n\nEquivalent Cargo commands:\n\n```powershell\ncargo build --workspace --all-targets\ncargo build --release --workspace --all-targets\ncargo build -p mono-injector-cli\ncargo build -p mono-injector-gui\n```\n\nRun from source:\n\n```powershell\njust cli --help\njust gui\ncargo run -p mono-injector-cli -- --help\ncargo run -p mono-injector-gui\n```\n\nFormat the workspace:\n\n```powershell\njust format\n```\n\n### Test\n\nRun the required final check:\n\n```powershell\njust check\n```\n\n`just check` aliases `just lint`, which runs:\n\n```powershell\ncargo clippy --all-targets --all-features --all\n```\n\nRun all tests:\n\n```powershell\njust test\n```\n\nRun tests for one crate:\n\n```powershell\njust test mono-injector-core\ncargo test -p mono-injector-core\n```\n\nUseful CLI smoke tests:\n\n```powershell\ncargo run -p mono-injector-cli -- --help\ncargo run -p mono-injector-cli -- inject --help\ncargo run -p mono-injector-cli -- eject --help\ncargo run -p mono-injector-cli -- profile path\n```\n\nReal injection tests require a live Windows process hosting Mono and a managed assembly with compatible static entry points:\n\n```csharp\npublic static class Loader\n{\n    public static void Init()\n    {\n    }\n\n    public static void Unload()\n    {\n    }\n}\n```\n\n### How does it work\n\nAt a high level, the CLI and GUI collect user intent and pass it to `mono-injector-core`. The core resolves profiles, process identity, assembly metadata, runtime options, wait behavior, and remembered injection state. The low-level `mono-injector` crate performs the actual remote Mono calls.\n\nInjection flow:\n\n1. Resolve a target process from a PID or exact process name.\n2. Resolve the managed assembly path from CLI flags, a profile, or a remembered prior injection.\n3. Read the assembly bytes from disk.\n4. Validate .NET metadata.\n5. Resolve namespace, class, inject method, and eject method.\n6. Optionally launch a Steam app if configured and the target is not already running.\n7. Optionally wait for the target process.\n8. Optionally wait for a readiness module.\n9. Optionally sleep for the settle delay.\n10. Find the target Mono module in the remote process.\n11. Call Mono APIs in the remote process to open the image, load the assembly, resolve the class and method, and invoke the static inject method.\n12. Read the resulting assembly handle.\n13. Store a remembered injection record for safe later ejection.\n\nEjection flow:\n\n1. Resolve the target process.\n2. Resolve the assembly handle from CLI flags or remembered records.\n3. Resolve namespace, class, and eject method from CLI flags, profile values, or remembered records.\n4. Enforce the local record guard unless `--force` is used.\n5. Call the configured static cleanup method in the target process.\n6. Close/unload the assembly through Mono.\n7. Remove the remembered injection record.\n\nThe low-level crate works by allocating memory in the target process, writing a small architecture-specific stub, and running that stub with a remote thread. The stub calls functions exported by the embedded Mono runtime. Arguments and return values are marshaled through remote process memory with Windows APIs such as `ReadProcessMemory`, `WriteProcessMemory`, `VirtualAllocEx`, `VirtualFreeEx`, `CreateRemoteThread`, and module enumeration APIs.\n\nThe managed inject and eject methods should be static and parameterless. They should return quickly. The eject method is responsible for cleaning up objects, hooks, threads, event handlers, and other resources created by the injected assembly.\n\n## Credits\n\n- Techniques: https://github.com/wh0am15533/SharpMonoInjector\n- Original SharpMonoInjector project: https://github.com/warbler/SharpMonoInjector\n\n## License\n\nThis project is licensed under `GPL-3.0-only`. See [`LICENSE`](LICENSE) for the full license text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheo-abel%2Fmono-injector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheo-abel%2Fmono-injector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheo-abel%2Fmono-injector/lists"}