Awesome
osmium - A Framework for Windows external cheats, written in modern C++.
-
Used in many of my own open-source external cheat projects, actively maintained and always aimed for fitting my personal needs when developing an external cheat. (See below for my cheat projects)
The following content may change frequently due to the updates of the framework and might be not up2date at all times.
Table of Contents
- A small foreword
- Installation & Setup
- Compilation
- Design of the framework
- Examples of how to use osmium
- Basic cheat module implementation
- Basic process implementation
- How to initialize your process instance
- How to retrieve the image base of an image from the taget process
- How to retrieve the image size of an image from the target process
- How to check if an image exists inside the dumped images
- How to retrieve a pointer to an dumped image with the name of the image
- How to read memory
- How to write memory
- How to search for a signature (byte pattern)
- How to dereference a address with image_x86 and image_x64
- How to hexdump a memory region
- How to read a ascii null terminated string
- How to change the protection of a memory block
- How to write to protected memory inside of the target process
- How to allocate a memory page inside of the target process with Read-Write-Execute rights
- How to allocate a memory page inside of the target process with specific rights
- How to create a x86 hook
- How to destroy a x86 hook
- How to inject a DLL with LoadLibrary
- How to get a pointer to the DOS header of an image
- How to get a pointer to the NT headers of an image
- How to get a pointer to the import descriptor of an image
- How to parse/retrieve all imports of an image
- How to get a pointer to the Process Environment Block (PEB) of the target process
- How to retreive the PEB from the target process
- How to retreive the ImageBaseAddress of the PEB from the target process
- How to retreive all modules inside InMemoryOrderModuleList of the PEB from the target process
- How to search for specific imports inside all modules from InMemoryOrderModuleList of the PEB from the target process
- Inter-Process communication (IPC)
- RegisterDumper x86
- How to create a register dumper x86
- How to start a register dumper x86
- How to stop a register dumper x86
- How to destroy a register dumper x86
- How to check if a registercontext is active or not
- How to retrieve a pointer to a registercontext with the dumped address
- How to access the registers data from the registercontext
- Basic overlay implementation
Foreword
Since I began programming external cheats, I always wanted to have my own framework which I can use for any game and which should be easy and quick to setup. This was and is not an easy task for me but this project should not be a demonstration for best practices. It is a collection of functionalities which I need to get my cheats done and these functionalities are not implemented in the best or stable way possible. I got my own way of doing things and I am learning everyday new stuff, which I want to implement and test right away. As I am still learning this project will be growing in it's own ways and will learn with me while evolving. This description should give you, as an reader, a better feeling for the framework because you read the thoughts of the creator and his individual philosophy.
My goal for the framework is modularity and genericity at the right places. The problem I have is that I don't know currently how I combine all different "modules" of the framework clever enough, so they will still be generic and easy to setup. At some point you need specific definitions and restrictions implemented and these are all individual. So the project might seem a bit more complicated to setup than it needed to be. I hope I can take up on this problem in the future and solve it in a clever way.
Until then I do my best to describe every tricky part as good as possible for me! Thank you very much for reading this foreword, which I think this is important!
If you have any recommendations, encounter any problems or just want to give me some hints - make sure to contact me or open a pull request.
Installation & Setup
For the framework to work out of the box you need some steps. The first thing is the right folder structure, because the relative paths won't work properly if the folder structure is not right. Because I am using Visual Studio as a IDE for C++, I will show the folder structure for this case. Feel free to adapt the paths for yourself, if needed.
The default folder structure is the following (Example for Visual Studio):
project
│ project.vcxproj
│ project.vcxproj.filters
│ project.vcxproj.user
| main.cpp
|
└───osmium
│ README.md
│ .gitignore
│
|───Cheat
| │ cheat.hpp
| │ ...
|
└───Includings
│ custom_data_types.hpp
│ ...
...
...
After you did create your visual studio project, you need to clone this repository into it. With the following command you can clone the repo correctly.
git clone https://github.com/cragson/osmium.git
If you want to use the Overlay of the framework you need the DirectX-SDK, which you can download directly by clicking the link down below. And the project will probably not compile without linking the includings and libs with it. I'll explain these steps later on in detail.
Compilation
To compile a project with this framework you need to prepare some things before.
- Make sure to link the DirectX includings and libs with Visual Studio
- Go to Project -> Properties -> VC++ Directories
- You need to enter your DirectX Includes path under External Include Directories
- You need to enter your DirectX Libraries path under Library Directories
- Also make sure to do these changes for all configurations (Debug and Release) and the correct Platform!
- Another thing to watch out for is the correct library path from DirectX for your current platform!
- Make sure to set the correct C++ Language Standard
- Go to Project -> Properties -> General
- Try setting the C++ Language Standard to /std:c++20, if this won't work for some reason try setting it to /std:c++latest
After these steps you should be able to successfully compile.
Design of the framework
The framework contains the following modules:
- Cheat
- Here you can find your interface for the cheat class, which you need for running the cheat logic, implementing features, dumping offsets and printing them.
- There is also the feature interface which you can use for implementing your features, setting hotkeys, overwriting the render, tick and startup logic of the feature and give it a custom name.
- This module is pretty essential as you need it to run any cheat feature related code.
cheat::setup_features
- Here you are creating your features and adding them to the internal features vector.
- Example: How to setup your features
cheat::setup_offsets
- Here you are preparing your offsets, like searching for patterns etc.
- Example: How to setup your offsets
cheat::run
- The most important method of the class because you run your own cheat logic in here.
- This means checking if the hotkeys of features are pressed, handling the logic of enabling/disabling and tick'ing them when active etc.
- Example: How to run your cheat
cheat::shutdown
- If your features need some special logic when the cheat will be shutdown, you should implement it right here
- Includings
- The most important thing here is the
modules.hpp
because you find a namespace with the testing implementations ofprocess
,cheat
andoverlay
in it. - Also you will find some of my own data type definitions like the
byte_vector
or theWILDCARD_BYTE
in it.
- The most important thing here is the
- Memory
- This is the CORE module of the framework!
- Because it is a wrapper for the WindowsAPI to interact with other processes and all needed operations for an external cheat.
- The process instance will dump ALL images inside of the target process and hold them in
private proces::m_images
for you - You need to initialize a
process
class instance with a proper identifier and after that you are able to read and write memory, find byte patterns, create hooks with shellcode and a lot more. - In most of the time you need this module because your cheat relies on modifying the memory of the target process.
- The
process
class can be used for both x86 and x64 architecture, it uses some macros which change the important lines in it dependent on which platform you use. - Also you find some other nice classes in it like
image_x86
,image_x64
andhook
:image_x86
is a wrapper for Windows x86 PE'simage_x64
is a wrapper for Windows x64 PE'shook
is a class which is used for basic mid function hooking with custom shellcode on a allocated memory page inside the target process.
- I can only suggest you to read into the code on yourself. I tried my best to cover a lot of the functionalities in understandable and easy examples. You find them here Basic process implementation.
- Overlay
- Before I say anything about that, I need to give proper credits to Icew0lf83! I came in contact with him and we shared our ideas and thoughts on a project, so I asked him if I am allowed to rewrite his DirectX9 Overlay and Renderer for my own purposes and he gave me the permissions to do it.
- I did learn a lot while rewriting his code but I can't say this is the way to do a proper DirectX9 Overlay and/or Renderer. I still know too little of DirectX but I only wanted to serve my use cases for drawing. Maybe I will rewrite this whole module in the future, if I have time to dig a lot deeper into DirectX.
- You will need to initialize this overlay with just the window title of the target process or a window handle to it, if you need any inspiration you might wanna have a look at Basic overlay implementation.
- Testing
- Here is my own implementation of the
cheat
andoverlay
logics. - If you don't wanna do it yourself you can just use this and be satisfied with the
modules.hpp
inIncludes
to be ready for your cheat to use.
- Here is my own implementation of the
Examples
-
Basic cheat module implementation
-
How to setup your features
Every feature must inherit of the
feature
class and you need to overwrite every virtual function from the parent class. The header file of a new feature can look like this:#pragma once #include "../osmium/Cheat/Feature/feature.hpp" class empty_feature : public feature { public: void on_disable() override; void on_enable() override; void on_first_activation() override; void tick() override; void on_render() override; void on_shutdown() override; };
The include path to the feature file may differ from your local folder structure!
You see you have five virtual functions which you need to override here.
on_disable
will be executed when the feature gets disabledon_enable
will be executed when the feature gets enabledon_first_activation
will be executed when the feature gets the first time activatedtick
is not called by default but should be called frequently when your feature is active, for an example look here: How to run your cheat.on_render
is not called by default but should be called in your overlay instance when the feature is active and wants to draw something, for an example look here: How to run your overlay.on_shutdown
should be called when the cheat is shutting down, like e.g.cheat::shutdown
.
-
How to add new features to your cheat
After you did finish your feature you want to add it to your
cheat
instance, right? I'll show you how to do this.You need to modify your
cheat::setup_features
function for this but to make sure to modify your own cheat class and not the interface one!If I want to add the
empty_feature
from How to setup your features to the cheat, I am able to configure the feature a bit more before adding.There are multiple functions in the
feature
interface, which you can use to configure the feature:set_status
- Here you can enable or disable the feature, if you pass a
true
to this function the feature will be enabled.
- Here you can enable or disable the feature, if you pass a
set_virtual_key_code
- This is the way to configure which key you need to press to toggle your feature.
- Here is a list of all virtual key codes: https://docs.microsoft.com/de-de/windows/win32/inputdev/virtual-key-codes.
set_activation_delay
- This is the delay in milliseconds, which you need to wait before toggling the feature again with the hotkey
set_name
- here you can set the name of the feature, this will be needed for the
print_features
function of the cheat interface
- here you can set the name of the feature, this will be needed for the
set_print_status
- If you enable this, everytime you enable or disable the feature a small info will be print into the console like:
[empty_feature] is enabled!
or[empty_feature] is disabled!
- If you enable this, everytime you enable or disable the feature a small info will be print into the console like:
enable_drawing
- If you want to render something make sure to call this function, because otherwise the on_render function will not be called.
- This also depends on your implementation of the overlay.
disable_drawing
- Here you can disable drawing for your feature
Here is a small example for adding the
empty_feature
to the cheatbool test_cheat::setup_features() { auto emptyfeature = std::make_unique< empty_feature >(); emptyfeature->set_name( L"Fancy empty feature" ); emptyfeature->set_virtual_key_code( VK_NUMPAD1 ); emptyfeature->set_activation_delay( 1337 ); emptyfeature->enable_print_status(); this->m_features.push_back( std::move( emptyfeature ) ); return true; }
-
How to setup your offsets
Well here you should prepare your offsets by dumping them from the target process or using the dumped images to search for signatures.
If you want to know a bit more about finding a signature, have a look here: How to search for a signature (byte pattern)
I am not going into detail here because this is something very individual here. Just as an example here is the
setup_offsets
function from my Doom: Eternal cheat**Please note that the
Globals
andOffsets
namespaces here are not a part of the framework.bool doom_cheat::setup_offsets() { if ( !Globals::g_pProcess->does_image_exist_in_map( L"DOOMEternalx64vk.exe" ) ) return false; const auto game_image = Globals::g_pProcess->get_image_ptr_by_name( L"DOOMEternalx64vk.exe" ); const auto ammo_patch = game_image->find_pattern( L"39 73 40 7D 07 03 FE" ); if ( !ammo_patch ) return false; Offsets::infinite_ammo_patch = ammo_patch + 3; const auto god_patch = game_image->find_pattern( L"84 C0 75 0C F3 0F 10 44 24 4C" ); if( !god_patch ) return false; Offsets::god_mode_patch = god_patch + 2; const auto upgrades_patch = game_image->find_pattern( L"41 8B E8 48 8B D9 44 01 84 B1" ); if( !upgrades_patch ) return false; Offsets::free_upgrades_patch = upgrades_patch + 7; return true; }
-
How to run your cheat
In this chapter I want to talk about the logic of the
cheat::run
function. This is also something very individual, but I want to show you my own implementation of it and discuss some thoughts of mine while writing it.This code snippet is also from my Doom: Eternal cheat, but I didn't change it in any other cheats.
void doom_cheat::run() { for ( const auto& feature : this->m_features ) { // before tick'ing the feature, check first if the state will eventually change if ( utils::is_key_pressed( feature->get_virtual_key_code() ) ) feature->toggle(); // let the feature tick() when active if ( feature->is_active() ) feature->tick(); } }
What does this function do? Let's split it into pieces.
- Iterate over every feature inside of the
m_features
vector of the cheat - Check if the hotkey is pressed of the current feature and if this is the case, toggle the feature
- If the feature is active/enabled, call the
tick
function of it.
So with this basic procedure I am pretty satisfied already, for my purposes. I call the
doom_cheat::run
function in a infinite loop with a little sleep in it, so the features are quite often tick'ed when they are active.while ( true ) { Globals::g_pCheat->run(); if ( utils::is_key_pressed( VK_END ) ) break; Sleep( 3 ); }
You should see here now why I wrote in How to setup your features that it depends on how
features::tick
is called. You can implement any logic you want. But as I don't want to overcomplicate it for others, you can just stick to the logic here - if you want! - Iterate over every feature inside of the
-
-
Basic process implementation
-
How to initialize your process instance
You have multiple ways of initializing your process instance but I try to break it down for you. First you need to think of an identifier of the target process, how can you identify it for sure? There are a few ways:
- The process id
- The process name
- The window name
You need to choose one of these identifiers and pass them to the
process::setup_process
function.If you want to initialize a process instance with the process id from some process, you can do it like this:
bool init_process() { const auto g_pProcess = std::make_unique< process >(); if( !g_pProcess->setup_process( 1337 ) ) // 1337 is the process id from the target process return false; return true; }
If you want to initialize a process instance with the process name from some process, you can do it like this:
bool init_process() { const auto g_pProcess = std::make_unique< process >(); // Be careful setup_process wants a std::wstring, check your project settings (multibyte/unicode) if( !g_pProcess->setup_process( L"fancyprocess.exe" ) ) return false; return true; }
If you want to initialize a process instance with the window name from some process, you can do it like this:
bool init_process() { const auto g_pProcess = std::make_unique< process >(); if( !g_pProcess->setup_process( L"I am a very fance window title", false ) ) // Make sure to pass the false as second argument, if you want to use a window name! return false; return true; }
-
How to retrieve the image base of an image from the taget process
Here you need to be careful because the
process
class can only get a image base which was already dumped from the process!If the image was not dumped, the
process::get_image_base
function will return 0.void work_with_image_base() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful get_image_base wants a std::wstring, check your project settings (multibyte/unicode) const auto image_base = g_pProcess->get_image_base( L"fancy_image.exe" ); // PrInT iT tO dAh CoNsOlE printf( "[+] image base is: 0x%08X\n", image_base ); }
-
How to retrieve the image size of an image from the target process
Here you need to be careful because the
process
class can only get a image base which was already dumped from the process!If the image was not dumped, the
process::get_image_size
function will return 0.void work_with_image_size() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful get_image_size wants a std::wstring, check your project settings (multibyte/unicode) const auto image_size = g_pProcess->get_image_size( L"fancy_image.exe" ); // PrInT iT tO dAh CoNsOlE printf( "[+] image size is: 0x%08X\n", image_size ); }
-
How to check if an image exists inside the dumped images
You can do this easily with the
process::does_image_exist_in_map
function, which returns true when the image exists or false if not.void check_if_fancy_image_exists() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful does_image_exist_in_map wants a std::wstring, check your project settings (multibyte/unicode) if( g_pProcess->does_image_exist_in_map( L"fancy_image.exe" ) ) printf( "[!] Fancy image exists.\n" ); else printf( "[!] Fuck me, something is wrong.\n" ); }
-
How to retrieve a pointer to an dumped image with the name of the image
You can do this easily with the
process::get_image_ptr_by_name
function, which also checks if the image exists or not.BE FUCKING CAREFUL BECAUSE IF THE IMAGE DOES NOT EXIST, THIS FUNCTION WILL RETURN A NULLPTR
void get_my_fancy_image() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful get_image_ptr_by_name wants a std::wstring, check your project settings (multibyte/unicode) const auto fancy_image = g_pProcess->get_image_ptr_by_name( L"fancy_image.exe" ); // ALWAYS VALIDATE POINTER, my friend if( !fancy_image ) return; const auto fancy_base = fancy_image->get_image_base(); const auto fancy_size = fancy_image->get_image_size(); const auto fancy_run = fancy_image->is_executable(); // etc.. }
-
How to read memory
You can do this by using the
process::read
function. It is pretty much self explaining. You pass the memory address you want to read to the function, give the data type and receive the result.void read_fancy_stuff() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; const auto fancy_byte = g_pProcess->read< uint8_t >( 0xDEADAFFE - 100 ); const auto fancy_int = g_pProcess->read< int32_t >( 0xDEADAFFE ); const auto fancy_float = g_pProcess->read< float >( 0xDEADAFFE + 100 ); const auto fancy_double = g_pProcess->read< double >( 0xDEADAFFE + 200 ); const auto fancy_uint = g_pProcess->read< std::uintptr_t >( 0xDEADAFFE + 300 ); }
-
How to write memory
You can do this by using the
process::write
function. It is pretty much self explaining. You pass the memory address you want to read to the function, give the data type, value to write and receive a bool if the call toWriteProcessMemory
was successful or not.void write_fancy_stuff() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; g_pProcess->write< uint8_t >( 0xDEADAFFE - 100, 0x69 ); g_pProcess->write< int32_t >( 0xDEADAFFE, 1337 ); if( !g_pProcess->write< float >( 0xDEADAFFE + 100, 13.37f ) ) return; // etc.. }
-
How to search for a signature (byte pattern)
First I need to say there are really really many ways of how to search for a signature and even more ways of how to define a byte signature.
I went the way I am most comfortable with by using the "IDA style" of byte signatures. This ways you can declare a sequence of bytes the following way:
- 74 00 90 90 A2 C7
- 74 ? ? ? ? C7
The hex numbers are the value of the bytes and the
?
is a wildcard, which means the bytes there doesn't matter for matching the pattern because they can change at everytime (like a memory address for example).You can search for a signature by using the
image_x86::find_pattern
orimage_x64::find_pattern
function. Also I implemented the functionfind_all_pattern_occurences
, which will return a vector of memory addresses where the pattern was matched successfully. So if you want to use that functions you need the pointer to the image, in where the signature should be matched. Also you can pass a optional bool as the second argument, to decide of the results should be relative to the image base or not.Here is a little code snippet, which should make the usage more clear:
void find_fancy_signatures() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful get_image_ptr_by_name wants a std::wstring, check your project settings (multibyte/unicode) const auto fancy_image = g_pProcess->get_image_ptr_by_name( L"fancy_image.exe" ); // ALWAYS VALIDATE POINTER, my friend if( !fancy_image ) return; // absolute address where the pattern matches (image base + offset) const auto result = fancy_image->find_pattern( "DE AD BE EF" ); // relative address where the pattern matches (offset) const auto result2 = fancy_image->find_pattern( "DE AD BE EF", false ); const auto result3 = fancy_image->find_pattern( "DE AD ? EF" ); const auto result3 = fancy_image->find_pattern( "DE AD ? EF", false ); }
-
How to dereference a address with
image_x86
andimage_x64
You can use the
image_x86::deref_address
or theimage_x64::deref_address
function to dereference a address from a dumped image.Here is a little code snippet, which should make the usage more clear:
void dereference_fancy_signatures() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful get_image_ptr_by_name wants a std::wstring, check your project settings (multibyte/unicode) const auto fancy_image = g_pProcess->get_image_ptr_by_name( L"fancy_image.exe" ); // ALWAYS VALIDATE POINTER, my friend if( !fancy_image ) return; // absolute address where the pattern matches (image base + offset) const auto result = fancy_image->find_pattern( "DE AD BE EF" ); const auto deref = fancy_image->deref_address< std::uintptr_t >( result + 7 ); }
-
How to hexdump a memory region
I implemented this functionality just because I think this pretty damn cool. When I started reading about cheats and Reverse Engineering I saw pretty often the typical hex dump pictures as header in articles. ^^
You can use the
print_memory_region
function insideimage_x86
orimage_x64
to dump a memory region. To the function you need to pass the start address, the size and a bool if you want to print absolut addresses.For example here is a small code snippet:
void print_fancy_region() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful get_image_ptr_by_name wants a std::wstring, check your project settings (multibyte/unicode) const auto fancy_image = g_pProcess->get_image_ptr_by_name( L"fancy_image.exe" ); // ALWAYS VALIDATE POINTER, my friend if( !fancy_image ) return; // Will print the first 256 bytes, starting at the address 0x13376077 as absolute addresses fancy_image->print_memory_region( 0x13376077, 256, true ); }
-
How to read a ascii null terminated string
Here you have multiple ways of reading a string:
- Read it from a dumped image inside
process::m_images
- Read it right from the memory of the target process
If you want to read it from a dumped image you should use the
read_string_from_address
function fromimage_x86
orimage_x64
. But be careful, if the address is not valid the function will returnOSMIUM_INVALID_ADDRESS
and make sure the string is nullterminated! Also the function takes one argument the address and one optional argument, which indicates if the given address should be handled as a absolute one or if it's relative.Here is a little code snippet for you:
void read_fancy_string_from_dumped_image() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // Be careful get_image_ptr_by_name wants a std::wstring, check your project settings (multibyte/unicode) const auto fancy_image = g_pProcess->get_image_ptr_by_name( L"fancy_image.exe" ); // ALWAYS VALIDATE POINTER, my friend if( !fancy_image ) return; // reads string from address 0x4000AFFE - the base of the fancy_image const auto absolute_fancy_string = fancy_image->read_string_from_address( 0x4000AFFE ); const auto relative_fancy_string = fancy_image->read_string_from_address( 0xAFFE, true ); }
If you want to read the string right from the memory of the target process, you need to use the
read_ascii_null_terminated_string
from theprocess
class.Make sure the string is nullterminated in memory!
Also if the chars are not ascii, the function will return
OSMIUM_NO_ASCII
.Here is a little code snippet for you:
void read_string_from_memory() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; const auto fancy_string = g_pProcess->read_ascii_null_terminated_string( 0xAFFEDEAD ); if( !fancy_string.empty() && fancy_string != "OSMIUM_NO_ASCII" ) printf( "[!] String could be read from memory!\n" ); else printf( "[!] Could not read the string from memory!\n" ); }
- Read it from a dumped image inside
-
How to change the protection of a memory block
Here you should use the
change_protection_of_memory_block
function inside theprocess
class. You pass the address, size and protection to this function and if it fails it returns 0 or if it succeeds it will return the old protection rights, so you can restore them later on.Here is a small code snippet for you:
void change_memory_protection() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // In this example I want to patch a memory block inside of fancy_image.exe // so I need the base address of the image first const auto fancy_base = g_pProcess->get_image_base( L"fancy_image.exe" ); if( !fancy_base ) return; // make the memory block writable auto old_protection = g_pProcess->change_protection_of_memory_block( fancy_base + 0x1000, sizeof( uint32_t ), PAGE_EXECUTE_READWRITE ); // modify some bytes yo g_pProcess->write< uint32_t >( fancy_base + 0x1000, 0x90909090 ); // restore old protection of the memory block inside fancy_image.exe old_protection = g_pProcess->change_protection_of_memory_block( fancy_base + 0x1000, sizeof( uint32_t ), old_protection ); }
-
How to write to protected memory insside of the target process
For this use case I got the
write_to_protected_memory
function inside of theprocess
class for you. You need to pass the address, value and optional the size to it and you will have fun. It will return a bool if it could write to the memory or not.Here is a small code snippet for you:
void overwrite_protected_bytes() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // In this example I want to patch a memory block inside of fancy_image.exe // so I need the base address of the image first const auto fancy_base = g_pProcess->get_image_base( L"fancy_image.exe" ); if( !fancy_base ) return; if( g_pProcess->write_to_protected_memory< float >( 0xAFFE1337, 60.77f ) ) printf( "[!] Overwrote the damn hair length of the monkey.\n" ); else printf( "[!] Could not write to the protected memory, what the monkey..\n" ); }
-
How to allocate a memory page inside of the target process with Read-Write-Execute rights
You should use the
allocate_rwx_page_in_process
function inside theprocess
class for this. The function takes only one optional argument, the wanted size of the memory page which should be allocated. It will return a pointer to the allocated page if it succeeds, if it fails it will return a null pointer. Be careful you need to take care of free'ing the allocated memory page to not leak any memory!Here is a small code snippet for you:
void allocate_rwx_monkaeh() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; const auto monkaeh_page = g_pProcess->allocate_rwx_page_in_process(); if( !monkaeh_page ) return; printf( "[+] Allocated the damn monkaaaaaaehs at: 0x%p\n", monkaeh_page ); const auto big_monkey = g_pProcess->allocate_rwx_page_in_process( 0x1000 * 20 ); if( !big_monkey ) return; printf( "[+] Allocated the big fuckin monkey at: 0x%p\n", big_monkey ); }
-
How to allocate a memory page inside of the target process with specific rights
For this purpose you can use the
allocate_page_in_process
function inside of theprocess
class. Just pass the needed page protection and optional the wanted size of the memory page to the function and you should receive a pointer to the fresh allocated memory page inside of the target process. Be careful you need to take care of free'ing the allocated memory page to not leak any memory!Here a small code snippet for you:
void allocate_some_monkeys() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; const auto fresh_island = g_pProcess->allocate_page_in_process( PAGE_EXECUTE_READWRITE ); if( !fresh_island ) return; printf( "[+] Allocated the fresh island at: 0x%p\n", fresh_island ); const auto fresh_small_island = g_pProcess->allocate_page_in_process( PAGE_EXECUTE, 0x2000 ); if( !fresh_small_island ) return; printf( "[+] Allocated a tropical island at: 0x%p\n", fresh_small_island ); }
-
How to create a x86 hook
This is little more complicated than the other things before but actually not that complex. I will try my best to break it down as easy and understandable for you as I can.
The idea is that I want to hook functions inside the target process but as the framework is for external cheats, hooking is a bit more difficult than beeing internal. But this is classic mid function hook concept, because it is very practical and easy to accomplish externally. So my idea was:
- Write shellcode (your hook)
- Allocate a rwx memory page inside of the target process
- Copy the shellcode inside the memory page
- Calculate the jump address to the hooked function after the placed jump to the memory page and copy it after the shellcode on the memory page
- Place jump instruction and the calculated address after the shellcode on the memory page
- Overwrite the hooked function partially with the jump to the allocated memory page
- En - fucking - joy
After the function got hooked a
hook
instance will be created with the given arguments and stored inside theprocess::m_hooks
vector. Don't worry about unhooking, the destructor of theprocess
class automatically unhooks all functions when destroying itself. ;)Basically you don't have to worry about this, just get familiar with it and it should be self explaining.
Here is a small code snippet for you:
void hook_da_monkaeh() { // initialize here the process instance (yes yes it would be only local but idc for this example) if( !init_process() ) return; // In this example I want to hook a specific function inside of fancy_image.exe // so I need the base address of the image first const auto fancy_base = g_pProcess->get_image_base( L"fancy_image.exe" ); if( !fancy_base ) return; // now I need my shellcode, which will be my hook std::vector< uint8_t > shellcode = { 0x50, // push eax 0xB8, 0x39, 0x05, 0x00, 0x00, // mov eax, 1337 0x03, 0x06, // add eax, dword ptr [esi] 0x59 // pop ecx }; // At this address the function I want to hook is located const std::uintptr_t target_function = fancy_base + 0xAFFE; // I need to overwrite exactly 5 bytes on x86 because 1 byte jmp instruction + 4 bytes address and no other bytes needs to be overwritten/ nop'ed const size_t hook_size = 5; if( g_pProcess->create_hook_x86( target_function, hook_size, shellcode ) ) printf( "[!] Successfully hooked the target function!\n" ); else printf( "[!] Bloody hell something went wrong!\n" ); }
-
How to destroy a x86 hook
Well this is quite simple, just unhook it lol.
The
destroy_hook_x86
function inside of theprocess
class will automatically restore the original bytes, which were overwritten, and free the allocated memory page for the shellcode - so don't worry my friend.void unhook_da_monkaeh() { // First hook the shit away hook_da_monkaeh(); // Now unhook and solve the problems away const auto fancy_base = g_pProcess->get_image_base( L"fancy_image.exe" ); if( !fancy_base ) return; // I need the start address of the hook, which is the address I hooked // to unhook the shit const std::uintptr_t target_function = fancy_base + 0xAFFE; // U N H O O K if( g_pProcess->destroy_hook_x86( target_function ) ) printf( "[!] Successfully unhooked :^)!\n" ); else printf( "[!] MAYDAY MAYDAY error hello hi hallo holla bonjour!\n" ); }
-
How to inject a DLL with LoadLibrary
Just provide a valid path of the dll, which you want to inject into the target process, to the function
inject_dll_load_library
.void demo_loadlib() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); if (!proc->inject_dll_load_library("C:\\Users\\craaaaaaaaaaaaagson\\Desktop\\test.dll")) return; std::println("[+] Succesfully injected dll into process"); }
-
How to get a pointer to the DOS header of an image
void demo_dos_header() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto img_ptr = proc->get_image_ptr_by_name(L"dummy.exe"); if (!img_ptr) return; const auto dos_ptr = img_ptr->get_dos_header_ptr(); if (!dos_ptr) return; std::println("[+] magic: {:04X}", dos_ptr->e_magic); }
-
How to get a pointer to the NT headers of an image
void demo_nt_headers() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto img_ptr = proc->get_image_ptr_by_name(L"dummy.exe"); if (!img_ptr) return; const auto nt_ptr = img_ptr->get_nt_headers_ptr(); if (!nt_ptr) return; std::println("[+] machine {:04X}", nt_ptr->FileHeader.Machine); }
-
How to get a pointer to the import descriptor of an image
void demo_import_descriptor() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto img_ptr = proc->get_image_ptr_by_name(L"dummy.exe"); if (!img_ptr) return; const auto id = img_ptr->get_import_descriptor(); if (!id) return; std::println("[+] import descriptor ptr: 0x{:08X}", reinterpret_cast< std::uintptr_t >( id ) ); }
-
How to parse/retrieve all imports of an image
For this case you can use the function
get_imports
of eitherimage_x64
orimage_x86
. It will return astd::vector< std::tuple< std::string, std::string, std::uintptr_t > >
, which basically is a big list of all imports in the Import Address Table (IAT) of an image. The first string is the name of the image, from where the function is imported. The second string is the name of the function, which is imported. The last element of the tuple is pointer to the imported function, which can be used for IAT hooking etc. This function works for bothimage_x64
andimage_x86
.Here is an example of how you can print out all entries from the IAT in a easy way:
void demo_image_iat() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto img_ptr = proc->get_image_ptr_by_name(L"dummy.exe"); if (!img_ptr) return; const auto imports = img_ptr->get_imports(); if (imports.empty()) return; for (const auto& [img, fn_name, fn_ptr] : imports) std::println("[#] {}::{} -> 0x{:08X}", img, fn_name, fn_ptr); std::println("\n[+] Parsed {} imports", imports.size()); }
-
How to get a pointer to the Process Environment Block (PEB) of the target process
void lab() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto peb_ptr = proc->get_peb_ptr(); if (!peb_ptr) return; std::println("[+] peb: 0x{:X}", peb_ptr); }
-
How to retreive the PEB from the target process
void lab() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto peb = proc->get_peb(); std::println("[+] PEB::Ldr -> 0x{:X}", reinterpret_cast< std::uintptr_t >( peb.Ldr ) ); }
-
How to retreive the ImageBaseAddress of the PEB from the target process
void lab() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"firefox.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto peb_image_addr = proc->get_peb_image_base_address(); std::println( "[+] PEB::ImageBaseAddress -> 0x{:X}", peb_image_addr ); }
-
How to retreive all modules inside InMemoryOrderModuleList of the PEB from the target process
void lab() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"dummy.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto modules = proc->get_modules_from_peb(); for (const auto& [module_name, module_base, module_size] : modules) std::println("{} -> 0x{:X} ({} bytes)", module_name, module_base, module_size); }
-
How to search for specific imports inside all modules from InMemoryOrderModuleList of the PEB from the target process
void lab() { const auto proc = std::make_unique< process >(); if (!proc->setup_process(L"firefox.exe")) return; std::println("[+] pid: {}\n", proc->get_process_id()); const auto modules = proc->get_modules_from_peb(); for (const auto& [module_name, module_base, module_size] : modules) { std::println("{} -> 0x{:X} ({} bytes)", module_name, module_base, module_size); const auto temp = std::make_unique< image_x64 >(module_base, module_size); const auto module_name_w = std::wstring(module_name.begin(), module_name.end()); if (!ReadProcessMemory( proc->get_process_handle(), reinterpret_cast< LPCVOID >( module_base ), temp->get_byte_vector_ptr()->data(), module_size, nullptr )) continue; const auto imports = temp->get_imports(); if (imports.empty()) continue; std::ranges::for_each( imports, [&](const auto& tup) { std::apply( [](const auto& image_name, const auto& fn_name, const auto& fn_ptr) { // replace Nt here with the substring you want to filter for in the imports of the modules if (fn_name.contains("Nt")) std::println("{}::{} -> 0x{:X}", image_name, fn_name, fn_ptr); }, tup); } ); std::println(""); } }
-
Inter-Process Communication (IPC)
-
How to setup named shared memory x86
First you need a object name for the Named Shared Memory, if you want to use a "Global\XXX" name: Make sure to have the proper rights on both sides (cheat and game process), otherwise the function will fail! After that you need to know the size of the buffer, which will be split into HIGH- and LOW side. Both sides are DWORDs. First the function will check if the object name is empty or if already an instance with the same object name exists, if so the function returns false. After that these steps will be done:
- Get the image Base of Kernel32
- Get the addresses of CreateFileMappingA and MapViewOfFile
- Allocate a small buffer inside the target process for the return values of the called functions inside the target process and the object name string
- Write the object name string into the allocated memory
- Prepare now the shellcode with it's data (addresses and parameters)
- Execute now the shellcode inside the target process
- Read now the return values from the target process called functions (object handle and view address)
- Open the file mapping handle with OpenFileMappingA
- Map now the view of the mapped file into the address space of the callee process
- Create the shared memory instance
- Push it to the std::vector inside process
- Free the allocated memory page for the return values and the object name string
So tl;dr here is some sample code for you, how to setup a shared memory instance:
printf( "[#] Creating a shared memory instance.." ); if ( Globals::g_pProcess->create_shared_memory_instance_x86( "Local\\osmium_sh", 0, 0x100 ) ) { printf( "success!\n" ); const auto sh_inst = Globals::g_pProcess->get_shared_memory_instance_by_object_name( "Local\\osmium_sh" ); const auto sh_ptr = sh_inst->get_buffer_ptr(); printf( "[+] Wrote to %p (callee), remote buffer @ %p\n", sh_ptr, sh_inst->get_process_buffer_ptr() ); } else printf( "failed!\n" );
So what happens here is a Named Shared Memory with the object name Local\osmium_sh and a total buffer size of 0x100 will be created. If it was successfully created, a pointer to the freshly created shared memory instance will be retrieved and also a pointer to the shared memory buffer from the callee process.
-
How to read and write from/to the shared memory x86
I implemented some methods for reading or writing but you can do it easily on your own, if you want. Just retrieve the pointer to the buffer from the callee process via get_buffer_ptr() from shared_memory_instance and treat it like a normal pointer. I'll show both ways now in this sample code:
printf( "[#] Creating a shared memory instance.." ); if ( Globals::g_pProcess->create_shared_memory_instance_x86( "Local\\osmium_sh", 0, 0x100 ) ) { printf( "success!\n" ); const auto sh_inst = Globals::g_pProcess->get_shared_memory_instance_by_object_name( "Local\\osmium_sh" ); const auto sh_ptr = sh_inst->get_buffer_ptr(); printf( "[+] Wrote to %p (callee), remote buffer @ %p\n", sh_ptr, sh_inst->get_process_buffer_ptr() ); sh_inst->write_string( "iloveasm" ); sh_inst->write< float >( 1337.123f, 0x20 ); *reinterpret_cast< size_t* >( sh_ptr + 0x42 ) = 0x13376077; for( auto i = 0; i < 9; i++ ) printf( "[+] Read from 0x%d (%08X): %c\n", i, sh_ptr + i, sh_inst->read< const char >( sh_ptr + i ) ); printf( "[+] Read from 0x20: %.3f\n", sh_inst->read< float >( sh_ptr + 0x20 ) ); printf( "[+] Read from 0x42: %04X\n", *reinterpret_cast< size_t* >( sh_ptr + 0x42 ) ); } else printf( "failed!\n" );
This snippet will produce the following output:
-
How to clear the shared memory x86
If you want to zero the whole shared memory buffer, just use clear_memory() from shared_memory_instance. Or you can just call memset, because the function clear_memory just does it like this.
void clear_memory() const { memset( this->m_pBuffer, 0, this->m_iBufferSize ); }
-
How to remove named shared memory x86
If you want to remove the Named Shared Memory, just call destroy_shared_memory_instance_x86 from process. This will unmap, free, remove and clean everything up for you.
- Unmap view of mapped file
- Free's allocated memory pages
- Removes shared_memory_instance from instances vector inside process.
printf( "[#] Destroying now the shared memory instance.." ); if ( Globals::g_pProcess->destroy_shared_memory_instance_x86( "Local\\osmium_sh" ) ) printf( "success!\n" ); else printf( "failed!\n" );
-
-
-
RegisterDumper x86
First of all I want to explain my motivation and real use case for this feature here. As this Framework is designed for external cheats some things from internal cheats aren't available easily, like e.g. function hooking and reading/writing the registers at the specific point inside the function. Since today I came across multiple occasions where I didn't find a static pointer for a list but a function where all elements from the list where processed. As I got no static pointer to that list, I could not simply sigscan, read and parse it like a normal external would do. And I also could not just hook the function and grab the elements by reading out the used registers like in a internal cheat. Most of the time I found a workaround which allowed me to circumvent reading the used registers where the elements were stored in but there were also few times where no I couldn't find a workaround and I won't work out.
Well, what can I imagine now under the term "RegisterDumper x86"? Obviously it's for x86 only, at the time. Also this is feature which will allow you to access all GPRs from a point in memory. This works by utilizing a shared memory feature from this framework where all register content will be written to. Also a 5 byte hook is placed to the given address which redirects the code flow to a little shellcode which writes the registers content into the shared memory page, which e.g. looks like this:
After writing the registers content to the shared memory page the overwritten instructions will be executed and then a jump after the placed hook follows, so make sure to not overwrite instructions which depend on a correct callstack etc..
There is an
std::vector< std::unique_ptr< registercontext > >
inside of theprocess
class, which holds all created registercontext's.Now what is a registercontext? This might be a bit confusing right now because I talked only about dumper and not context's but try to think about it like the dumper is the abstract design construct I had in mind, which is integrated into the
process
class by different functions for creating, starting, stopping, destroying or the vector which holds all registercontext's. A registercontext is one smaller part of the dumper, it's purpose is holding relevant data for the accessing/reading of the registers content. I can only suggest you to have a look at it's implementation insideregistercontext.hpp
. Also a registercontext holds a pointer to the shared memory instance which belongs to it, this is so because otherwise it could not map the class for the registers onto the pointer for the shared memory page where the registers content is.To make this more clear, here is the implementation of the
registercontext::get_registers_data()
function:[[nodiscard]] inline auto get_registers_data() const noexcept { return static_cast< register_data* >( this->m_pShInstance->get_buffer_ptr() ); }
register_data is also defined in the same header file as registercontext, so you may have a look at it. Essentially it's just a class which holds unions and variables for the registers. For the moment only the GPRs like e.g. EAX, ECX, ESP, ESI are supported but I plan to add support for the FPU registers also.
Finally I want to say something about the registercontext's active status. I want you to understand that just because you call e.g.
registercontext::enable_dumper()
will not automatically start the dumping process of the register. Please useprocess::start_register_dumper_x86
orprocess::stop_register_dumper_x86
functions to start or stop the dumping. The difference is that when you only set the active boolean from registercontext to true there is no hook applied or otherwise no hook removed and original bytes restored, it's mainly for internal purposes that theprocess
class knows if an registercontext should be dumped or not.I'm sure this current state of my approach is a bit more complicated than it needed to be but just play with it yourself and get familiar with it. Also I want to improve the logic and simplicity in the future.
-
How to create a register dumper x86
The following things will happen here:
- Create a shared memory instance with a custom object name for the specified adress where the register will be dumped.
- Preparing the shellcode which will dump the registers content and write it to the shared memory page.
- Create the hook which will redirect to the dump shellcode and jump back right after the placed hook.
- Create an instance of a registercontext with the given values and enable the dumping for it.
- Append the freshly created instance to the internal vector of the
process
class.
// (Assume g_pProcess is a instance of process) if( Globals::g_pProcess->create_register_dumper_x86( 0xDEADAFFE ) ) printf( "[+] Registercontext was successfully created!\n" ); else printf( "[!] Could not create the registercontext!\n" );
-
How to start a register dumper x86
Here it will just hook the address again and enable the dumping, the shared memory still exists as the registercontext was not destroyed.
// (Assume g_pProcess is a instance of process) if( Globals::g_pProcess->start_register_dumper_x86( 0xDEADAFFE ) ) printf( "[+] Dumping of the register is starting!\n" ); else printf( "[!] Could not start the dumping of the registers!\n" );
-
How to stop a register dumper x86
Here it will destroy the placed hook, restore it's original bytes and disable the dumping for the registercontext.
// (Assume g_pProcess is a instance of process) if( Globals::g_pProcess->stop_register_dumper_x86( 0xDEADAFFE ) ) printf( "[+] Dumping of the register is stopped!\n" ); else printf( "[!] Could not stop the dumping of the registers!\n" );
-
How to destroy a register dumper x86
Here the following things will happen:
- If the registercontext, which the given address belongs to, is still active stop it.
- Destroy the placed hook and restore it's original bytes.
- Destroy the shared memory instance.
- Erase the registercontext element from the internal vector inside
process
class.
// (Assume g_pProcess is a instance of process) if( Globals::g_pProcess->destroy_register_dumper_x86( 0xDEADAFFE ) ) printf( "[+] Registercontext was successfully destroyed!\n" ); else printf( "[!] Could not destroy the registercontext!\n" );
-
How to check if a registercontext is active or not
// (Assume g_pProcess is a instance of process) if( Globals::g_pProcess->is_active_register_dumper_x86( 0xDEADAFFE ) ) printf( "[+] Dumping of the registers is active!\n" ); else printf( "[!] Dumping of the registers is not active!\n" );
-
How to retrieve a pointer to a registercontext with the dumped address
Make sure to always validate the pointer you retrieve!
// (Assume g_pProcess is a instance of process) const auto ptr = Globals::g_pProcess->get_register_dumper_x86_ptr( 0xDEADAFFE ); if( ptr != nullptr ) printf( "[+] Registercontext pointer is valid!\n" ); else printf( "[!] Registercontext is not valid!\n" );
-
How to access the registers data from the registercontext
Make sure to always validate the pointer you retrieve!
// (Assume g_pProcess is a instance of process) const auto reg_data = Globals::g_pProcess->get_data_from_registers_x86( 0xDEADAFFE ); const auto is_active = Globals::g_pProcess->is_active_register_dumper_x86( 0xDEADAFFE ); if( reg_data != nullptr && is_active ) { printf("[+] Dumped Registers from: 0x%08X\n", 0xDEADAFFE); printf("EAX -> 0x%08X\n", reg_data->EAX.eax); printf("\tAX -> 0x%04X\n", reg_data->EAX.ax); printf("\tAH, AL -> 0x%02X, 0x%02X\n", reg_data->EAX.a[0], reg_data->EAX.a[1]); printf("EBX -> 0x%08X\n", reg_data->EBX.ebx); printf("\tBX -> 0x%04X\n", reg_data->EBX.bx); printf("\tBH, BL -> 0x%02X, 0x%02X\n", reg_data->EBX.b[0], reg_data->EBX.b[1]); printf("ECX -> 0x%08X\n", reg_data->ECX.ecx); printf("\tCX -> 0x%04X\n", reg_data->ECX.cx); printf("\tCH, CL -> 0x%02X, 0x%02X\n", reg_data->ECX.c[0], reg_data->ECX.c[1]); printf("EDX -> 0x%08X\n", reg_data->EDX.edx); printf("\tDX -> 0x%04X\n", reg_data->EDX.dx); printf("\tDH, DL -> 0x%02X, 0x%02X\n", reg_data->EDX.d[0], reg_data->EDX.d[1]); printf("ESP -> 0x%08X\n", reg_data->ESP.esp); printf("\tSP -> 0x%04X\n", reg_data->ESP.sp); printf("EBP -> 0x%08X\n", reg_data->EBP.ebp); printf("\tBP -> 0x%04X\n", reg_data->EBP.bp); printf("ESI -> 0x%08X\n", reg_data->ESI.esi); printf("\tSI -> 0x%04X\n", reg_data->ESI.si); printf("EDI -> 0x%08X\n", reg_data->EDI.edi); printf("\tDI -> 0x%04X\n", reg_data->EDI.di); }
Which would result in something like this:
-
-
Basic overlay implementation
-
How to setup your overlay
You can either initialize your overlay by using the window title of the target process or a window handle. Make sure to implement the
overlay
interface correctly, as I did in thetest_overlay
class because you need to define the render logic.But I got you, just have a look at How to run your overlay.
The initialization function will directly create a window, the directx object, device and all of the other needed stuff for having some painty fun. The default class name of the window is
OVCRG
because OV = overlay and CRG = cragson, you know some pretty 300iq stuff right here (fml).Here you can have a look at it:
void init_ofalai() { const auto dx9_overlay = std::make_unique< test_overlay >(); if( dx9_overlay->initialize( "Plants vs. Monkeys" ) ) printf( "[!] Created the overlay!\n" ); else printf( "[!] Could not create the overlay!\n" ); const auto window_handle = FindWindowA( 0, "Plants vs. Monkeys" ); if( !window_handle ) return; if( dx9_overlay->initialize( window_handle ) ) printf( "[!] Created the overlay!\n" ); else printf( "[!] Could not create the overlay!\n" ); }
-
How to draw a string
You can use the
draw_string
function inside of your implementation of theoverlay
interface for it. You need to call this function inside the render loop of youroverlay
implementation.Here is a small example:
void draw_watermark() { // yes yes yes I know it's local but just take it as a global overlay instance pointer init_ofalai(); dx9_overlay->draw_string( "my fancy watermark right here", // the string to be drawn 100, // x coordinate where the string should be drawn 50, // y coordinate where the string should be drawn 0, // red color (0-255) 255, // green color (0-255) 150 // blue color (0-255) ); dx9_overlay->draw_string( "fancy watermark but now half transparent", // the string to be drawn 100, // x coordinate where the string should be drawn 50, // y coordinate where the string should be drawn 0, // red color (0-255) 255, // green color (0-255) 150, // blue color (0-255) 122 // alpha (0-255) ); }
-
How to draw a line
You can use the
draw_line
function inside of your implementation of theoverlay
interface for it. You need to call this function inside the render loop of youroverlay
implementation.Here is a small example:
void draw_a_line() { // yes yes yes I know it's local but just take it as a global overlay instance pointer init_ofalai(); dx9_overlay->draw_line( 400, // start x coordinate 200, // start y coordinate 450, // end x coordinate 250, // end y coordinate 0, // red color (0-255) 0, // green color (0-255) 255, // blue color (0-255) ); dx9_overlay->draw_line( 400, // start x coordinate 200, // start y coordinate 450, // end x coordinate 250, // end y coordinate 0, // red color (0-255) 0, // green color (0-255) 255, // blue color (0-255) 2 // width of the line (optional), default is 1 ); }
-
How to draw a rect
You can use the
draw_rect
function inside of your implementation of theoverlay
interface for it. You need to call this function inside the render loop of youroverlay
implementation.Here is a small example:
void draw_a_rect() { // yes yes yes I know it's local but just take it as a global overlay instance pointer init_ofalai(); dx9_overlay->draw_rect( 400, // start x coordinate 200, // start y coordinate 50, // width of the rect 100, // height of the rect 0, // red color (0-255) 0, // green color (0-255) 255 // blue color (0-255) ); dx9_overlay->draw_rect( 400, // start x coordinate 200, // start y coordinate 50, // width of the rect 100, // height of the rect 0, // red color (0-255) 0, // green color (0-255) 255, // blue color (0-255) 2 // width of the rect (optional), default is 1 ); }
-
How to draw a filled rect
You can use the
draw_filled_rect
function inside of your implementation of theoverlay
interface for it. You need to call this function inside the render loop of youroverlay
implementation.Here is a small example:
void draw_a_filled_rect() { // yes yes yes I know it's local but just take it as a global overlay instance pointer init_ofalai(); dx9_overlay->draw_filled_rect( 400, // start x coordinate 200, // start y coordinate 50, // width of the rect 100, // height of the rect 0, // red color (0-255) 0, // green color (0-255) 255 // blue color (0-255) ); }
-
How to register a new font
If you want to register a new font, you should use the
register_font
function inside theoverlay
interface. It will return true or false, if the function succeeds/ fails.Here is a small example:
void register_a_new_font() { // yes yes yes I know it's local but just take it as a global overlay instance pointer init_ofalai(); const auto result = dx9_overlay->register_font( "Arial", // font name 50, // height 50, // width 10 // weight ); if( result ) printf( "[!] Registered font!\n" ); else printf( "[!] Could not register the font!\n" ); }
-
How to change the current font
If you want to switch the current used font, you need to use this function. Also after you registered a new font and want to use it, you need to use this function.
You can switch the current font by using the
set_used_font
function inside theoverlay
interface. Important is to know that the font, you want to switch to, must be already registered before withregister_font
.If the function could set the new current font, it will return true otherwise it will return false.
Here is a small example for you:
void change_da_current_font() { // yes yes yes I know it's local but just take it as a global overlay instance pointer init_ofalai(); if( dx9_overlay->set_used_font( "Arial" ) ) printf( "[!] The current font is now Arial!\n" ); else printf( "[!] Could not change the current font to Arial!\n" ); }
-
How to run your overlay
This is the same thing like above in How to run your cheat. It depends on your own logic and needs.
I implemented for myself a very basic overlay logic, which works for me really good. What I thought of that I only want to render if the current foreground window is the target window and if this condition is true, I want to iterate over every feature, check if it should be drawn and if so I want to call the
on_render
function from it, if it's active.You can see this logic here:
void test_overlay::render() { this->m_Device->Clear( NULL, nullptr, D3DCLEAR_TARGET, D3DCOLOR_ARGB( 0, 0, 0, 0 ), 1.0f, NULL ); this->m_Device->BeginScene(); const auto fg_hwnd = GetForegroundWindow(); if( fg_hwnd == this->m_TargetWindow ) { const auto features = Modules::g_pCheat->get_features_as_ptr(); for( auto it = features->begin(); it != features->end(); ++it ) { const auto current_feature = it->get(); if( !current_feature->should_be_drawn() ) continue; if( current_feature->is_active() ) current_feature->on_render(); } } this->m_Device->EndScene(); this->m_Device->PresentEx( nullptr, nullptr, nullptr, nullptr, NULL ); }
-