RELang is a domain-specific language that allows you to describe memory interfaces to binary executables and transpiles to C++17. The acronym RELang stands for Reverse Engineering Language.
Writing binary interfaces for existing applications you don't have the source of can be annoying with C++ only and either needs a lot of boilerplate code or more generic approaches tend to break auto-completion in IDEs.
Instead, this project follows a different approach: The required interfaces are all written in RELang, the RELang compiler is integrated in your project's toolchain and generates C++ code. This C++ code is then included in your project and you can continue as usual.
RELang is based upon the ANTLR4 parser generator, is written in Golang and its syntax looks very similar to C++. It was primilarily invented for and tested with code generated by and for the MSVC compiler. Support for other compilers may be added in the future.
- Function body generation for memory address-based function calls
- Automatic pad generation for unknown memory regions in struct
- Support for virtual methods
- Strong type system with fixed-size types only
class Vehicle : Element
{
void Fix() @ 0xBEAF;
bool Attach(Element* element) @ 0x123456;
float32 health @ 0x4;
ModelInfo* model;
bool engineOn;
Vehicle* trailer;
bool locked;
int8 color @ 0x50;
};
...transpiles to something like...
class Vehicle : public Element
{
public:
inline void Fix()
{
using Func_t = void(__thiscall *)(decltype(this));
auto f = reinterpret_cast<Func_t>(0xBEAF);
return f(this);
}
inline bool Attach(Element* element)
{
using Func_t = bool(__thiscall *)(decltype(this), Element*);
auto f = reinterpret_cast<Func_t>(0x123456);
return f(this, element);
}
private:
char _pad0[4];
public:
// offset 4
float32 health;
// offset 8
ModelInfo* model;
// offset 12
bool engineOn;
// offset 13
Vehicle* trailer;
// offset 17
bool locked;
private:
char _pad1[62];
public:
// offset 80
int8 color;
};
To be able to build the project, you need:
- golang >= 1.11
- Java runtime (only required if you intend to make changes to the grammar)
- Node.js and globally installed gulp (only required for more convenience)
The following command will transpile examples/Vehicle.relang
to examples/out/Vehicle.h
.
go run cmd/relang.go -p examples/Vehicle.relang -o examples/out/Vehicle.h
Alternatively, if you have gulp installed, you can also compile all examples at once:
gulp run-examples
On assembly level, we only need to distinguish between primitive types, pointers and embedded class types.
Supported primitive types are:
signed | unsigned | other |
---|---|---|
int8 | uint8 | bool |
int16 | uint16 | float32 |
int32 | uint32 | float64 |
Their meaning should be self-explanatory and matches C++'s fixed width integer types.
Pointers are declared by appending an asterisk (*) to the type, just like in C++. Their size is always 32-bit for an x86 target and 64-bit for an x86_64 target.
Classes look very similar to C++, except for:
- No access modifiers like public, protected or private
- No modifiers like const
- What else? (TODO)
// Declare class named 'Tree' and inherit from class named 'Plant'
class Tree : Plant {
// Declare function named 'CountLeafs' that is located
// at address 0x12345678 and returns an uint32
uint32 CountLeafs() @ 0x12345678;
// Arrays are pointers pointing to the first element
// so just return a Leaf pointer here.
// Uses the non-default __stdcall calling convention
Leaf* __stdcall GetLeafs() @ 0x234;
// Declare an attribute named 'color' at offset 0x50
uint32 color @ 0x50;
// Declare another attribute.
// If an offset is not defined, it's automatically calculated
// based on the previous attributes size. In this case,
// it'll lay at offset 0x54 as sizeof(uint32) == 4
bool needsWater;
};
You can inherit from one or more classes. The base classes are embedded into the declared class in the same order as they are listed.
Declaring virtual methods works similarly to declaring attributes. Their memory address is the offset in the vtable (virtual function table).
class Plant {
virtual int8* GetName() @ 0x8;
}
You can also declare functions as static. They have a non-__thiscall calling convention and behave like static functions in C++.
// Declare global function at offset '0xBEAF'
// that returns a PlantFactory ptr and accepts an int32 as parameter
PlantFactory* GetPlantFactory(int32 flags) @ 0xBEAF;
TODO
Sometimes, it might not be possible to express something in RELang, but works well with C++ or you just want to add some logic to your class. In most cases, it might be better to inherit from or embed the generated class. If that doesn't work for you, you can use the raw-block instead.
The raw block just injects a piece of code into the generated C++ code without modifying it.
class PlantFactory {
```
template<std::size_t N>
void AllocatePlants() {
// Do some stuff
}
```
};
Namespaces work exactly like C++ namespaces. C++17's nested namespaces are also supported and compile to pre-C++17 code as well.