{"id":13760103,"url":"https://github.com/Smertig/rcmp","last_synced_at":"2025-05-10T10:31:26.718Z","repository":{"id":65774423,"uuid":"300964926","full_name":"Smertig/rcmp","owner":"Smertig","description":"C++17, multi-architecture cross-platform hooking library with clean API.","archived":false,"fork":false,"pushed_at":"2024-05-06T09:54:09.000Z","size":290,"stargazers_count":68,"open_issues_count":2,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-08-03T13:04:14.923Z","etag":null,"topics":["cpp","cpp17","hooking-library","modding-library","rcmp"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Smertig.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}},"created_at":"2020-10-03T19:43:50.000Z","updated_at":"2024-08-03T13:04:19.223Z","dependencies_parsed_at":"2024-06-28T09:32:48.651Z","dependency_job_id":null,"html_url":"https://github.com/Smertig/rcmp","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Smertig%2Frcmp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Smertig%2Frcmp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Smertig%2Frcmp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Smertig%2Frcmp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Smertig","download_url":"https://codeload.github.com/Smertig/rcmp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224949686,"owners_count":17397210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cpp","cpp17","hooking-library","modding-library","rcmp"],"created_at":"2024-08-03T13:01:03.420Z","updated_at":"2024-11-16T17:30:38.226Z","avatar_url":"https://github.com/Smertig.png","language":"C++","funding_links":[],"categories":["C++"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/License-MIT-blue.svg\" alt=\"License: MIT\"\u003e\n  \u003ca href=\"https://github.com/Smertig/rcmp/actions\"\u003e\u003cimg src=\"https://github.com/Smertig/rcmp/workflows/Tagged%20Release/badge.svg\" alt=\"GitHub Actions\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/Smertig/rcmp/actions\"\u003e\u003cimg src=\"https://github.com/Smertig/rcmp/workflows/Build%20On%20Push/badge.svg\" alt=\"GitHub Actions\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003ercmp\u003c/b\u003e - C++17, multi-architecture cross-platform hooking library with clean API.\n\u003c/p\u003e\n\n## Features\n\n- Intuitive, modern, compiler/platform-independent API\n- **x86/x86-64 support** (more soon)\n- **Windows/Linux support**\n- Calling convention support (`cdecl`, `stdcall`, `thiscall`, `fastcall`, `native-x64`)\n\n## Building\n\n### With CMake (as a subproject)\n\nClone repository to subfolder and link `rcmp` to your project:\n```cmake\nadd_subdirectory(path/to/rcmp)\ntarget_link_libraries(your-project-name PRIVATE rcmp)\n```\n\n## Examples\n\n- The most common case: hook function to modify its argument and/or result \n```c++\nint foo(float arg) { /* body */ }\n\nrcmp::hook_function\u003c\u0026foo\u003e([](auto original_foo, float arg) {\n    return original_foo(arg * 2) + 1;\n});\n```\n- However, in most cases you probably want to hook function **knowing only its address and signature** (in fact, that's everything you need to make hook)\n```c++\nrcmp::hook_function\u003c0xDEADBEEF, int(float)\u003e([](auto original_foo, float arg) {\n    return original_foo(arg * 2) + 1;\n});\n```\n\n- Trace function calls\n```c++\nvoid do_something(int id, const char* action) { /* body */ }\n\nrcmp::hook_function\u003c\u0026do_something\u003e([](auto original_function, int id, const char* action) {\n    std::cout \u003c\u003c \"do_something(\" \u003c\u003c id \u003c\u003c \", \" \u003c\u003c action \u003c\u003c \") called..\\n\";\n    original_function(id, action);\n});\n```\n\n- Replace return value\n\n```c++\nbool check_license() { /* body */ }\n\nrcmp::hook_function\u003c\u0026check_license\u003e([](auto /* original_function */) {\n    return true;\n});\n``` \n\n- Accept arguments as a variadic pack\n```c++\ntemplate \u003cclass... Args\u003e void print(const Args\u0026 ...) { /* implementation */ }\n\nrcmp::hook_function\u003c0xDEADBEEF, unsigned int(int, float, bool, double, void*, long)\u003e([](auto original, auto... args) {\n    print(\"args are: \", args...);\n    return original(args...);\n});\n```\n\n- Function address can be known at runtime or compile-time\n```c++\n// compile-time address\nrcmp::hook_function\u003c0xDEADBEEF, int(int)\u003e([](auto original, int arg) { ... });\n\n// runtime address (i.e. from GetProcAddress/dlopen)\nrcmp::hook_function\u003cint(int)\u003e(0xDEADBEEF, [](auto original, int arg) { ... });\n```\n\n- Calling convention support \n```c++\n/// x86, the following calls are synonyms\n\n// good:\nrcmp::hook_function\u003cvoid(int)\u003e(...);                // default convention\nrcmp::hook_function\u003cvoid(*)(int)\u003e(...);             // default convention, but 3 more symbols\nrcmp::hook_function\u003crcmp::cdecl_t\u003cvoid(int)\u003e\u003e(...); // explicit convention (rcmp::cdecl_t\u003cS\u003e is an alias for rcmp::generic_signature_t\u003cS, rcmp::cconv::cdecl_\u003e)\n\n// bad, compiler-specific\nrcmp::hook_function\u003cvoid(__cdecl*)(int)\u003e(...);               // MSVC\nrcmp::hook_function\u003cvoid(__attributes((cdecl))*)(int)\u003e(...); // gcc/clang\n\n// x86 supported conventions\nrcmp::cdecl_t   \u003cvoid(int)\u003e // same as rcmp::generic_signature_t\u003cvoid(int), rcmp::cconv::cdecl_\u003e\nrcmp::stdcall_t \u003cvoid(int)\u003e // same as rcmp::generic_signature_t\u003cvoid(int), rcmp::cconv::stdcall_\u003e\nrcmp::thiscall_t\u003cvoid(int)\u003e // same as rcmp::generic_signature_t\u003cvoid(int), rcmp::cconv::thiscall_\u003e\nrcmp::fastcall_t\u003cvoid(int)\u003e // same as rcmp::generic_signature_t\u003cvoid(int), rcmp::cconv::fastcall_\u003e\n\n// x64\nrcmp::hook_function\u003cvoid(int)\u003e(...);                                                     // default convention\nrcmp::hook_function\u003cvoid(*)(int)\u003e(...);                                                  // default convention, but more letters\nrcmp::hook_function\u003crcmp::generic_signature_t\u003cvoid(int), rcmp::cconv::native_x64\u003e\u003e(...); // explicit convention\n\n```\n\n- VTable hooking (`hook_indirect_function`)\n```c++\n// Let's assume:\n// 5             - index of function in vtable\n// int A::f(int) - function signature\nusing signature_t = rcmp::thiscall_t\u003cint(A*, int)\u003e; // x86, MSVC\nusing signature_t = rcmp::cdecl_t\u003cint(A*, int)\u003e;    // x86, gcc/clang\nusing signature_t = int(A*, int);                   // x64\n\n// vtable address can be known at compile-time (0xDEADBEEF)\nrcmp::hook_indirect_function\u003c0xDEADBEEF + 5 * sizeof(void*), signature_t\u003e([](auto original, A* self, int arg) { ... });\n\n// ..or at runtime\nrcmp::hook_indirect_function\u003csignature_t\u003e(get_vtable_address() + 5 * sizeof(void*), [](auto original, A* self, int arg) { ... });\n```\n\n## Motivation\n\nWhy *yet another* hooking library?\n\nThere are too many libraries with similar or even more powerful features. Most of them have been perfectly designed; however, they don't provide all the features I need.\n\n### Mods, cheats, plugins etc \n\nI like to develop unofficial mods (plugins) ~~and cheats~~ for various games (both for client and server-side).\nThis is a very specific area of development that requires continuous experimentation with function hooking. \n\nSuppose you want to hook a function. All you need to know about this function - its address and signature, that's it.\nJust write an interceptor (replacement function) and call something like `cool_lib::hook_function`. Very simple, isn't it? Of course it's not.\nMost libraries require:\n- Create global variable, that holds pointer to replaced original function.\n- Write global function, that contains interceptor logic.\n- Write same function signature/name multiple times ([DRY](https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself)).\n- Use C-style casts or MACRO to call original function from the interceptor.\n- Manually create and destroy auxiliary context (i.e. disassembler backend) required for hooking.\n\nThat's really annoying. **I want to express my intentions in a single expression without boilerplate, code repetitions and ugly C-style code**. \n\nSo I ended up with developing my own library - `rcmp`.\n\n### Cross-platform and compiler support\n\nAt work I need both windows (`.dll`) and linux (`.so`) support, but most libraries aren't cross-platform (some of them also use compiler-specific extensions, that's not portable). \nMoreover, there are [moddable games for Oculus Quest VR](https://beatsaberquest.com/bmbf/bmbf-mods/bmbf-mods/) that works on ARM64 architecture. \n`rcmp` was designed to be easily extendable, so I was able to use it even for Android apps (ARM64 support comes soon!).\n\n### Modern C++ and canonical code\n\nMost libraries use _not-so-modern_ C++ standards (C++11 and below), so they have limited capabilities.\nModern C++ features allow developer to write compact and type-safe code without boilerplate and repetitions (especially in case of hooking).\nDue to C++17, `rcmp` has convenient API as well as minimalistic and readable implementation. \n \n### Dependencies\n\n`rcmp` has single-header bundled lightweight dependency - [nmd](https://github.com/Nomade040/nmd) by [Nomade040](https://github.com/Nomade040) (only as a length disassembler for x86/x86-64). \nMost of hooking libraries depend on big, verbose or even deprecated frameworks.\n\n### Build \u0026 Install\n\n`rcmp` can be easily added to any cmake-based project. No external requirements or dependencies, no installation or manual non-trivial actions to build - just add two lines in `CMakeLists.txt`. \n\n## Missing features\n\n- No documentation (yet)\n- No way to disable hook\n- No ellipsis (`...`) support\n\n\n## References\n\n- [catch2](https://github.com/catchorg/Catch2) for unit-testing\n- [nmd library](https://github.com/Nomade040/nmd) for x86/x86-64 length disassembly\n- [x86](http://ref.x86asm.net/coder32.html) and [x86-64](http://ref.x86asm.net/coder64.html) opcode and instruction reference\n\n## License\n\n- MIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSmertig%2Frcmp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSmertig%2Frcmp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSmertig%2Frcmp/lists"}