This is the regenny wiki.

What is regenny?

regenny is a reverse engineering tool to interactively reconstruct structures and generate usable C++ header files. Header file generation is done by the sister project sdkgenny.

For those familiar with other tools, it is similar to ReClass.NET. It is written in C++.

Structures are defined in a plaintext .genny file rather than manually inserting types into a tree view. This allows for the structures to be version controlled, shared between users, and are human readable from a normal text editor.

It has a Lua scripting interface to easily read and manipulate the structures externally without compiling a separate program. This allows for easy testing, quick prototyping, or even writing a full blown tool in Lua. The Lua bindings can be used in a separate Lua installation/embedding, they are not tied to regenny.

Precompiled builds can currently be downloaded from the Actions page.

Getting started

To get started using ReGenny, create a project by clicking File->New on the top menu bar and entering a name in the dialog box. This will create a .json project file and a .genny file.

After that, you can click Action->Attach to attach to a running process and then you can edit the .genny file created earlier to start reverse engineering structures.

Once you're finished, you can click Action->Generate SDK to generate corresponding C++ files.

ReGenny is a very powerful tool has many other features, but this should give you a good idea on how to get started with reverse engineering using ReGenny.

What is a .genny file?

.genny files are defined in a C/C++-like syntax. They provide the definitions of the types to be viewed in the GUI, generated, and/or reflected upon by the scripting API.

Structures can be given an explicit size, and fields within the structures can be given explicit offsets without manual padding, and out of order.

Simple C structures can sometimes be directly pasted into a .genny file.

Example

// Explicitly defined types.
// Format is type {type_name} {type_size} [[{metadata}]]
// the metadata determines how the type is displayed in the GUI and read by the Lua API.
type int 4 [[i32]]
type float 4 [[f32]]
type char 1

// Forward declared structure.
struct Bar{};

// Simple structure. Size is explicitly defined. It doesn't need to be, though.
struct Foo 0x100 {
    int a @ 4 // Explicit offset
    char* b @ 8 [[utf8*]]
    int c // implicit offset, it's 0x10, inferred from the previous member
    int d @ 0x70
    float e @ 0x80 
    Bar* bar @ 0x90 // Pointer to a forward declared structure
};

struct Bar {
    Foo* parent
    int b
    int c
};

rexample

The above image is slightly out of date. There is no longer a text editor inside ReGenny itself. .genny files must be opened within ReGenny and edited with an external text editor now. Reason being ImGui's text editor functionality was not that great and caused frustrating issues.

The corresponding generated C++ structure

Foo.hpp

#pragma once
struct Bar;
#pragma pack(push, 1)
struct Foo {
    char pad_0[0x4];
    int a; // 0x4
    // Metadata: utf8*
    char* b; // 0x8
    int c; // 0x10
    char pad_14[0x5c];
    int d; // 0x70
    char pad_74[0xc];
    float e; // 0x80
    char pad_84[0xc];
    Bar* bar; // 0x90
    char pad_98[0x68];
}; // Size: 0x100
#pragma pack(pop)

Bar.hpp

#pragma once
struct Foo;
#pragma pack(push, 1)
struct Bar {
    Foo* parent; // 0x0
    int b; // 0x8
    int c; // 0xc
}; // Size: 0x10
#pragma pack(pop)

Accessing this structure from Lua

Before using this script, the structure in the GUI has to be set to Foo, regenny must be attached to a process, and the address of the structure must be set in the GUI.

-- An overlay is a special Lua type that allows access to the memory of a structure.
-- It has index and newindex metamethods that access to the members of the structure
-- seamlessly, with no magic numbers/offsets, completely externally.
local foo = regenny:overlay()
if not foo or foo:type():name() ~= "Foo" then
    print("Overlay is not a Foo, cannot use this script.")
    return
end

-- Dynamically read the structure memory from the target process.
print(foo.a) -- prints the value of foo.a
print(foo.b) -- prints the value of foo.b, will be a string

-- Setter demonstrations
-- These actually set the values within the target process's structure behind the scenes.
foo.c = 0x12345 -- sets foo.c to 0x12345
foo.e = 3.14159 -- sets foo.e to 3.14159

local bar = foo.bar -- gets the value of foo.bar

-- Because pointers can be null.
if bar ~= nil then
    bar.b = 0x1337 -- sets bar.b to 0x1337
    bar.c = bar.b + 0x100 -- sets bar.c to bar.b + 0x100

    print(bar.parent:ptr() == foo:address()) -- prints true if bar.parent is equal to foo
    if bar.parent:ptr() ~= foo:address() then
        -- sets bar.parent to foo
        -- pointers can be explicitly written to with a raw integer value
        bar.parent = foo:address()
    end
end

In addition to simply reading/modifying structures, the Lua API allows for more advanced features such as:

  • Performing reflection on the structure types
  • Reading and writing to the process memory
  • Allocating memory in the process
  • Parsing another .genny file separately
  • Generating C++ header files from a new .genny file or SDK instance

Supported types

  • Namespaces
  • Enums
  • Structs
  • Classes
  • Function prototypes
  • Static function prototypes
  • Bitfields
  • Arrays
  • Multi-dimensional arrays
  • Pointers
  • Namespaces can be nested within each other
  • Structs, enums and classes can be nested within other structs/classes
  • Bring your own external types

Type metadata

Types and variables can have a metadata attached to them. This metadata is used to determine how the type is displayed in the GUI and read by the Lua API.

For types, the format is type {type_name} {size} [[{metadata}]].

For variables, the format is {type_name} {variable_name} [[{metadata}]].

Example:

type int 4 [[i32]] // i32 metadata, will be displayed as a signed 32-bit integer in the GUI
type float 4 [[f32]] // f32 metadata, will be displayed as a 32-bit float in the GUI
type char 1 // no metadata

struct Foo {
    // will be displayed as a signed 32-bit integer in the GUI, implicit metadata
    int a;
    // utf8* metadata, will be displayed as a utf8 string in the GUI
    // must be explicitly specified, as the type is a pointer to 1 byte
    char* b [[utf8*]]
    // f32 metadata, will be displayed as a 32-bit float in the GUI
    float c
    // metadata does not need to be specified for this pointer
    int* d
};

Address

ReGenny parses addresses in a few different ways. Here you can find some examples.

For these examples, I'm going to be using numbers in the hexidecimal format (base 16), but decimal numbers (base 10) are also supported.

Absolute addresses

Inputting an absolute address will display whatever is at that address in the current virtual address space.

For example, on Windows if your process is based at 0x180000000, then you can input the address 0x180000000 to see the PE (MZ) header and 0x180001000 to see the start of code (or whatever else is after the PE header, assuming the PE header is 0x1000 bytes in size).

Relative addresses

Relative addresses can also be used. The syntax looks something like this: <module_name>+offset->offset->offset.

Module names can contain full paths or partial paths, as long as it ends with the input path. For example, say we want to get the base address of a module loaded as C:\Windows\System32\kernel32.dll, we could do one of the following:

  • <kernel32.dll>+0x0 (Partial ending)
  • <System32\kernel32.dll> (Partial ending)
  • <C:\Windows\System32\kernel32.dll>+0x0 (Full path)

On Windows, Addresses are relative to a loaded DLL's (Dynamic-Link Library) name or EXE (Executable) name of the attached app (basically, any loaded module, including itself).

For example, to get the start of code, after the PE header, you can enter something like: <app.exe>+0x1000.

It also supports dereferencing (denoted by ->), like so: <app.exe>+0x5000->0x24. This will add 0x5000 to app.exe's base address, dereference the result and then read out bytes starting from 0x24 after the resulting dereference.

Example genny files

Example scripts

-- An overlay is a special Lua type that allows access to the memory of a structure.
-- It has index and newindex metamethods that access to the members of the structure
-- seamlessly, with no magic numbers/offsets, completely externally.
local foo = regenny:overlay()
if not foo or foo:type():name() ~= "Foo" then
    print("Overlay is not a Foo, cannot use this script.")
    return
end

-- Dynamically read the structure memory from the target process.
print(foo.a) -- prints the value of foo.a
print(foo.b) -- prints the value of foo.b, will be a string

-- Setter demonstrations
-- These actually set the values within the target process's structure behind the scenes.
foo.c = 0x12345 -- sets foo.c to 0x12345
foo.e = 3.14159 -- sets foo.e to 3.14159

local bar = foo.bar -- gets the value of foo.bar

-- Because pointers can be null.
if bar ~= nil then
    bar.b = 0x1337 -- sets bar.b to 0x1337
    bar.c = bar.b + 0x100 -- sets bar.c to bar.b + 0x100

    print(bar.parent:ptr() == foo:address()) -- prints true if bar.parent is equal to foo
    if bar.parent:ptr() ~= foo:address() then
        -- sets bar.parent to foo
        -- pointers can be explicitly written to with a raw integer value
        bar.parent = foo:address()
    end
end

Globals

These can be accessed from anywhere in a script.

  • regenny - The global API for ReGenny.
  • sdkgenny - The global API for luagenny.

regenny

This is the global API for ReGenny.

sdkgenny

This is the global API for luagenny.

sdkgenny.Sdk

The Sdk is the top level class required to generate the SDK and/or perform reflection.

sdkgenny.StructOverlay

An StructOverlay is a special Lua type that allows access to the memory of a structure. It has index and newindex metamethods that access to the members of the structure seamlessly, with no magic numbers/offsets, completely externally*.

* This is not true if using luagenny outside of regenny. By default it operates on memory as if it was in the same context as the current process. regenny overrides luagenny's read and write functions to read and write from the target process.

Example assuming Foo is:

struct Foo {
    int a;
    int b;
};
local sdk = regenny:sdk() -- in another application, this would be different.
local overlay = sdkgenny.StructOverlay(0xdeadbeef, sdk:global_ns():find_type("Foo"))
overlay.a = 123
overlay.b = 456

Static methods

sdkgenny.StructOverlay(address: number, struct: sdkgenny.Struct)

Creates and returns a new StructOverlay.

Methods

self.index(key: string or number)

Or in other words, self.foo or self[1].

When key is a string, it returns the value of the member at the given key. Structs and Pointers are automatically returned as StructOverlay or PointerOverlay respectively.

When key is a number, it treats the structure as an inlined array and returns a new StructOverlay at the given index. Essentially, it returns self:address() + (key * self:type():size()).

self.newindex(key: string, value: any)

Or in other words, self.foo = 123

When key is a string, it sets the value of the member at the given key.

self:type()

Returns the sdkgenny.Struct that this overlay is for.

sdkgenny.PointerOverlay

A PointerOverlay is a special Lua type for interacting with pointers. When the pointed to structure is an sdkgenny.Struct, it is not much different from an sdkgenny.StructOverlay, but when the pointed to type is a primitive type or a pointer, it has different behavior.

Methods

self.index(key: string or number)

Or in other words, self.foo or self[1].

When key is a string

It returns the value of the member at the given key. This will only work if the pointed to type is an sdkgenny.Struct.

When key is a number

It treats the pointer as an array.

If the pointed to type is a primitive type, it returns the value at the given index. Essentially, it returns self:ptr() + (key * self:type():to():size()), and dereferences the pointer.

If the pointed to type is a pointer, it returns a new sdkgenny.PointerOverlay at the given index. Essentially, it returns self:ptr() + (key * platform_pointer_size), and dereferences the pointer.

self.newindex(key: string, value: any)

Or in other words, self.foo = 123

When key is a string, it sets the value of the member at the given key.

self:address()

Returns the address of the pointer itself.

self:ptr()

Returns the pointed to address.

self:type()

Returns the sdkgenny.Pointer that this overlay is for.

Types

sdkgenny.Object

All types inherit from this.

Most methods with an asterisk * take many different strings, e.g.

object:is_struct() -- returns true if the object is a struct
object:is_variable() -- returns true if the object is a variable
object:is_function() -- returns true if the object is a function

Methods

self:is_*()

Returns true if the object is of the given type.

self:as_*()

Returns the object as the given type, or nil if it is not of the given type.

self:find_*()

Searches the children of the object for the given type. Returns nil if not found.

Typename

Namespace

Type

sdkgenny.Struct

Methods

Class

sdkgenny.Pointer

Reference

Enum

EnumClass

Constant

Function

VirtualFunction

Parameter

Variable

Types

ReGenny

This is the class used to access the ReGenny API. It can be accessed through the global regenny variable.

Methods

self:process()

Returns the current Process. Gets upcasted to a WindowsProcess if the process is a Windows process.

self:address()

Returns the current address set in the GUI.

self:type()

Returns the current sdkgenny.Type set in the GUI.

Can usually be casted to an sdkgenny.Struct with self:type():as_struct().

self:sdk()

Returns the current sdkgenny.Sdk for the current project (generated from the editor).

self:overlay()

Constructs and returns an sdkgenny.StructOverlay using the address and current structure set in the GUI.

Any member reads or writes will be done from/to the process memory dynamically on each access.

Example:

local baz = regenny:overlay()
if baz == nil then
    print("Cannot continue test, overlay does not exist.")
    return false
end

if baz:type():name() ~= "Baz" then
    print("Cannot continue test, selected type is not Baz")
    return false
end

print(baz)
print(baz:type())
print(string.format("%x", baz:address()))

print(baz.hello) -- Prints "Hello, world!", which is the value pointed to by baz.hello
baz.foo.a = baz.foo.a + 1 -- Increments baz.foo.a by 1

Process

Class referring to the currently attached process in regenny.

Methods

self:protect(address: number, size: number, flags: number)

Modifies the protection of a region of memory in the process.

On Windows, the flags are the same as the flNewProtect flags in VirtualProtectEx.

Returns the old protection flags of the region.

self:allocate(address: number, size: number, flags: number)

Allocates a region of memory in the process.

address is optional, set it to 0 to let the OS choose the address.

On Windows, the flags are the same as the flProtect flags in VirtualAllocEx.

self:get_module_within(address: number)

Returns the ProcessModule that contains the given address.

self:get_module(name: string)

Returns the ProcessModule with the given name.

self:modules()

Returns a list of ProcessModules in the process.

self:allocations()

Returns a list of ProcessAllocations in the process.

self:read_int8(address: number)

Reads a int8_t from the process memory at the given address.

self:read_int16(address: number)

Reads a int16_t from the process memory at the given address.

self:read_int32(address: number)

Reads a int32_t from the process memory at the given address.

self:read_int64(address: number)

Reads a int64_t from the process memory at the given address.

self:read_uint8(address: number)

Reads a uint8_t from the process memory at the given address.

self:read_uint16(address: number)

Reads a uint16_t from the process memory at the given address.

self:read_uint32(address: number)

Reads a uint32_t from the process memory at the given address.

self:read_uint64(address: number)

Reads a uint64_t from the process memory at the given address.

self:read_float(address: number)

Reads a float from the process memory at the given address.

self:read_double(address: number)

Reads a double from the process memory at the given address.

self:read_string(address: number)

Reads a char* from the process memory at the given address.

Size is automatically deduced using a strlen-like algorithm.

self:write_int8(address: number, value: number)

Writes a int8_t to the process memory at the given address.

self:write_int16(address: number, value: number)

Writes a int16_t to the process memory at the given address.

self:write_int32(address: number, value: number)

Writes a int32_t to the process memory at the given address.

self:write_int64(address: number, value: number)

Writes a int64_t to the process memory at the given address.

self:write_uint8(address: number, value: number)

Writes a uint8_t to the process memory at the given address.

self:write_uint16(address: number, value: number)

Writes a uint16_t to the process memory at the given address.

self:write_uint32(address: number, value: number)

Writes a uint32_t to the process memory at the given address.

self:write_uint64(address: number, value: number)

Writes a uint64_t to the process memory at the given address.

self:write_float(address: number, value: number)

Writes a float to the process memory at the given address.

self:write_double(address: number, value: number)

Writes a double to the process memory at the given address.

WindowsProcess

Windows version of the Process class. Inherits from Process.

Methods

self:get_typename(obj: number)

Returns the RTTI name of the given object at the given address.

self:allocate_rwx(address: number, size: number)

Wrapper over Process.allocate that allocates a region of memory with the PAGE_EXECUTE_READWRITE protection.

self:protect_rwx(address: number, size: number)

Wrapper over Process.protect that changes the protection of a region of memory to PAGE_EXECUTE_READWRITE.

self:create_remote_thread(address: number, param: number)

ProcessModule

Fields

self.name

The name of the module.

self.start

The base address of the module.

self.end

The end address of the module.

self.size

The size of the module.

ProcessAllocation

Fields

self.start

The base address of the allocation.

self.end

The end address of the allocation.

self.size

The size of the allocation.

self.read

Whether the allocation is readable.

self.write

Whether the allocation is writable.

self.execute

Whether the allocation is executable.