Skip to content
Denis Kuzmin edited this page Jun 4, 2021 · 4 revisions

🧬 Conari. First steps

Conari project welcomes you and wants to tell a little about its architecture, components, features, and just where to start. πŸ’‘

Conari is officially distributed as a NuGet package

Just follow the installation instruction from the NuGet page. For example, using dotnet

dotnet add package Conari

Automatic / Semi-Automatic processing

First of all, Conari provides several ways to handle your requests to unmanaged memory, to processes, or types, etc. You can use freely everything at the same time without configuring something. Just choose what you need at the moment on the fly and enjoy.

ConariL

ConariL represents the main IConari implementation for semi-automatic processing known as a lambda way. Supports .dll and .exe modules.

using ConariL l = new(...);
//...
l.bind<Func<TCharPtr, string, string, bool>>("replace")(...);

But through IDlrAccessor you can easily access also DLR.

using ConariL l = new(...);
//...
l._.replace<bool>(string, string, string);

ConariX

ConariX is an extended IConari implementation to provide a fully automatic processing through its dynamic features known as a DLR way.

using dynamic l = new ConariX(...);
//...
l.replace<bool>(...)

You can also make it more friendly,

using ConariX l = ConariX.Make(new(...), out dynamic d);

l.PE.Export.Names // l.PE is part of ConariX
string v = d.versionString<CharPtr>(); // while this will be generated at runtime

ConariX is not a sealed class and you can even easily implement your own wrapper in a full integration steps,

class MyConari: ConariX
{
    public MyConari(string lib)
        : base((Config)lib, prefix)
    { }
    // ... powerful engine is ready for your awesome applications
}

or just something similar to ours Accessors below.

Conari Accessors

using net.r_eg.Conari.Accessors...

Accessors are wrappers of the ConariX implementation. They provide the easiest access to anything while they do not contain anything at all. For example, Windows kernel32

dynamic kernel32 = new Kernel32();

kernel32.GetModuleHandleA<IntPtr>("libcurl-x64");
kernel32.GetModuleHandleW<IntPtr>(ustr);

Or Windows user32

dynamic user32 = new User32();

user32.ShowWindow(0x000A0A28, 3);
user32.MessageBoxA(0, "Conari in action", "Hello!", 0);

Easily access whatever you want since everything will be generated and adapted at runtime.

Conari Types

using net.r_eg.Conari.Types;

Conari provides most powerful types and helpers. Some of this may provide several modes for automatic or semi-automatic processing etc.

For example, NativeStruct provides fully automatic way of working with structures without declarations using NativeData chains; E.g.:

using var u = NativeStruct.Make.f<UIntPtr>("start", "end").Struct;
/* Hey! We just generated a structure like
[StructLayout(LayoutKind.Sequential)]
private struct MatchResult
{
    public UIntPtr start;
    public UIntPtr end;
}*/

While NativeStruct<T> provides semi-automatic way of working with structures using CLR types declarations; E.g.:

using var u = new NativeStruct<MatchResult>();

Some types may aggregate a several types at the same type.

For example, TCharPtr can be configured either as WCharPtr or as CharPtr. But once per domain.

TCharPtr.Unicode = true; // allowed to change once per domain

using NativeString<TCharPtr> data = new("Hello world");
TCharPtr tch = data; // will be considered as WCharPtr since Unicode = true

Some types, on the contrary, just extends features of the available built-in types, such as VPtr which supports adding long numbers to IntPtr (IntPtr + long), a complete comparing >,<,>=,<=,==,!= between VPtr and int/long, and more.

VPtr n1 = new IntPtr(0xB);
VPtr n2 = new IntPtr(0x7FFF89EB0110);

n1 + n2
n1 > 0xA

PE features

Starting with 1.5 Conari has two implementations to handle PE modules.

  • PE32/PE32+ through Memory implementation.
  • PE32/PE32+ through NativeStream file system implementation.

This, however, does not cover full PE parsing and just provides most required features that will be used at least in our engine. Such as some addresses, characteristics, sections, export table, etc.

You can also use it independently if you need.

using ConariL l = new(...);

l.PE.Magic // Magic.PE64
l.PE.Machine // MachineTypes.IMAGE_FILE_MACHINE_AMD64
l.PE.Characteristics // IMAGE_FILE_EXECUTABLE_IMAGE 
                     // | IMAGE_FILE_LARGE_ADDRESS_AWARE | IMAGE_FILE_DLL

l.PE.Export.Names
l.PE.Export.Addresses

l.Memory.@goto(l.PE.Addresses.IMAGE_NT_HEADERS). ...
...

But here's what's interesting, our implementations are based on Conari itself for a most quickly accessing, just look at QPe.

Also note, for 1.5+ you can even disable all related to PE features such as mangling, list of exported proc, etc., using PeImplType.

That may increase some speed, for example, https://github.com/KirillOsenkov/Benchmarks/pull/5

Strings

Conari supports both Unicode and Multibyte null-terminated an unmanaged strings.

You can simply access it through CharPtr, WCharPtr, or TCharPtr types.

To create new,

To manage it,

using dynamic l = new ConariX(...);

bool found = l.replace<bool>
(
    "Hello {p}".Do(out TCharPtr result), "{p}", "world!"
);

string data = "number = 888;";
found = l.replace<bool>(ref data, "+??;", "2034;");
using ConariX l = ConariX.Make(new ConariX(...), out dynamic x);
x.replace<bool>
(
    l._T("Hello {p}", out CharPtr result), l._T("{p}"), "world!"
);

NativeString/BufferedString supports concatenations,

using var str = input + " " + "world";

Optional buffers for receiving and updating values,

using BufferedString<TCharPtr> s = new("Hello {p}!");
l.bind<Func<TCharPtr, string, string, bool>>("replace")(s, "{p}", "world!");

Safe and fast reuse of the allocated memory regions,

using var data = new BufferedString<CharPtr>("Hello!", 8);
// data == Hello! at 0x2674F82F630
data.update("Hello world!");
// data == Hello world! at the same 0x2674F82F630

Automatic context based marshaling,

string data = "number = 888;";
l.replace<bool>
(
    ref data, // -> BufferedString<CharPtr> -> CharPtr
    "+??;",   // -> NativeString<CharPtr> -> CharPtr
    "2034;"   // -> NativeString<CharPtr> -> CharPtr
)

Conari Native Chains

using net.r_eg.Conari.Native;

The easiest (most ever) access to any data in unmanaged memory is possible through our Native chains. Automatic and semi-automatic processing.

You can easily build accessing to any data at runtime in used source.

.f<int>("a", "b")
.t<CharPtr>("name")
.Raw
- {byte[0x0000000c]} byte[]
    [0]    0x05    byte --
    [1]    0x00    byte   |
    [2]    0x00    byte   |
    [3]    0x00    byte --^ a = 5
    [4]    0x07    byte --
    [5]    0x00    byte   |
    [6]    0x00    byte   |
    [7]    0x00    byte --^ b = 7
    [8]    0x20    byte --
    [9]    0x78    byte   |_ pointer to allocated string (CharPtr)name
    [10]   0xf0    byte   |
    [11]   0x56    byte --
...

In turn the source can be almost anything since we provide support of unmanaged memory, streams, and local content equally through IAccessor and related.

l.Memory
.move(0x3C, Zone.D)
.read<LONG>(out LONG e_lfanew)
.move(e_lfanew, Zone.D)
.eq('P', 'E', '\0', '\0')
.ifFalse(_ => throw new PECorruptDataException())
. ...

Together with NativeData build a most common type.

memory.Native()
    .f<WORD>("Machine", "NumberOfSections")
    .align<DWORD>(3)
    .t<WORD>("SizeOfOptionalHeader")
    .t<WORD>("Characteristics")
    .region()
    .t<WORD>("Magic")
    .build(out dynamic ifh);

ifh.NumberOfSections // 6
ifh.Characteristics  // IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE | IMAGE_FILE_DLL
ifh.Machine          // IMAGE_FILE_MACHINE_AMD64
ifh.Magic            // PE64

Calling Convention, Name-Decoration, Aliases

Conari use Cdecl by default which can do vararg functions since the stack is cleaned up by the caller.

This, however, can be changed and controlled (using events) at any time. But it may affect some features such as IProviderDLR.TrailingArgs.

And, Mangling and Aliases will help to make easy access to any functions or variables.

using(ConariL l = new("Library.dll", CallingConvention.StdCall))
{
    //...
    l.Mangling = true; // _get_SevenStdCall@0 <-> get_SevenStdCall
    l.Convention = CallingConvention.Cdecl; // return back the default convention
    l.Aliases["Flag"] = l.Aliases["getFlag"] = l.Aliases["xFunc"]; //Flag() -> getFlag() -> xFunc()->...
    // ...
    l._.getFlag<bool>();
}

Lambda binding

You can use any related bind<> methods such

l.bind<...>("proc")

Then invoke it immediately,

l.bind<Action<LuaState, string>>("setglobal")(L, "onKeyDown");

or later,

var set = l.bind<Action<LuaState, string>>("setglobal");
...
set(L, "onKeyDown");

Also note, Conari provides an additional delegates for ref/out arguments when using a semi-automatic way. Find it in Types, for example, ActionOut.

DLR binding

This similar to lambda binding except the fact that everything will be generated at runtime.

dlr.whatever_you_want(L, 123, "arg3", ...)

To use a return value, just specify this type

bool data = dlr.whatever_you_want<bool>(L, 123, "arg3", ...)

Examples