1

I'm decompiling a very old game, and my goal is to restore 100% native code. As such, I would need to preserve the existing architecture as much as possible (e.g. just using std::function isn't really helpful).

I have a collection of static functions with different number of parameters. They are stored in structure like this:

struct ScriptFunction 
{
    const wchar_t* m_name;
    void* m_func;  // ptr to specific function 
    void* m_returnType;
    int m_argCount;
};

And are populated like this:

ScriptFunction s_functions[206] = {
    {L"AddMoveAroundDesire", &ScriptFunctions::AddMoveAroundDesire, &CScriptType::s_void, 2},
    {L"AddDoNothingDesire", &ScriptFunctions::AddDoNothingDesire, &CScriptType::s_void, 2},
    {L"AddAttackDesire", &ScriptFunctions::AddAttackDesire, &CScriptType::s_void, 3},
    {L"AddAttackDesireEx", &ScriptFunctions::AddAttackDesireEx, &CScriptType::s_void, 4},
    {L"AddGetItemDesire", &ScriptFunctions::AddGetItemDesire, &CScriptType::s_void, 2},
 ...

So, how can I invoke ScriptFunction::m_func with given m_argCount and void** arguments? I have to push parameters somehow on stack and invoke func-call, but I have no idea.

Some asm code? va_list? But how to fill it in run-time?

UPD: Invoker looks like this

void CScriptAccessible::InvokeInternal(ScriptFunction* scriptFunc, void** args)
{
    // here I have to push params from **args** to stack 
    // and somehow call ScriptFunction::m_func
    // In assembly I could see pushes to stack in a loop (driven by 
    // ScriptFunction::m_argCount). But I'm wondering, whether it's 
    // possible to do in C++ (push args on stack and call function by just address)
}
Davis Herring
  • 36,443
  • 4
  • 48
  • 76
yudjin
  • 349
  • 5
  • 14
  • I removed the unneeded tag for you. If you can explain, why it is helpful feel free to re-add it, together with the explanation, and accept my apology. – Yunnosch Jun 24 '18 at 11:57
  • 1
    How do you get the arguments? And how do you want to pass them? – An0num0us Jun 24 '18 at 12:00
  • Are the `ScriptFunctions` functions (`Scriptfunctions::AddMoveAroundDesire`, etc) static or non-static? How does each one work with a supplied `ScriptFunction`? Those questions are rather fundamental in picking options to invoke/call them, given only a member named `m_func` that is a `void *` and a set of `void **` arguments. – Peter Jun 24 '18 at 12:24
  • ScriptFunctions::* are static (free). Changed in description. The only single usage is inside `CScriptAccessible::InvokeInternal`. In assembly I could see pushes to stack in a loop (driven by `ScriptFunction::m_argCount`). But I'm wondering, whether it's possible to do in C++ (push args on stack and call function by just address) – yudjin Jun 24 '18 at 12:30
  • No it is not possible to do it in C++ in general, you either do it with `std::function` or make it platform specific ie using assembly. – Slava Jun 24 '18 at 12:31
  • If all functions take pointers as arguments and return void and the max number of arguments is known then I think the following might work: cast `m_func` to pointer to a variadic function and then use a switch to call it with the right number of arguments. – Mikhail Vasilyev Jun 24 '18 at 12:31
  • @MikhailVasilyev yes, it would be a workaround, to use `switch` and `cast`, depending on `ScriptFunction::m_argCount`. But in original code there is no switch, only loop with `push`es – yudjin Jun 24 '18 at 12:33
  • 1
    You might also want to take a look at [this answer](https://stackoverflow.com/a/1721684/4432988). – Mikhail Vasilyev Jun 24 '18 at 13:09
  • What things are and aren't you willing to change? The declarations of the called functions? The `ScriptFunction` type? The `s_functions` array? Is it okay if one or more need to be "duplicated" with two different types where one just uses the other? – aschepler Jun 24 '18 at 22:08

1 Answers1

1

Within the language, there is only the switch/cast solution. That lets the compiler generate the correct calling convention: e.g., on x86-64 Linux, the first several arguments are passed in predefined registers. (The optimizer might merge the different paths into jumps into a sort of unrolled loop of the kind you’re expecting.) Remember also that in C/C++ alone, there is no “stack” at all.

Incidentally, using void* for functions is not guaranteed either (since it can point to any object but they are not objects), although it usually works (and POSIX requires it for dlsym).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • I ended up with `switch/case/cast-to-proper-func-prototype` solution. I think, in original code they used asm, since it's quite clear from the binary code. – yudjin Jun 25 '18 at 16:04