UnresolvedCalling game functions with C#

Posts 1–15 of 16 · Page 1 of 2
Calling game functions with C#
I'm writing a little bot for myself, and because i'm good in c# but not that great in c++ i wrote myself a little bootstrap dll that basically hosts the clr in the game process and then proceeds to load my C# dll which contains the bot logic. The C# dll also loads EasyHook, which enables me to easily hook functions.

After a bit of derping around in x64dbg i found a function in the games that takes a string pointer as argument and writes that string into chat or does all kinds of command interpretation (/ability, / laugh, ...) so it's pretty powerful. I'd like to call that function from my C# dll.
What I did:
* Find the main game loop
* Hook the first function in the loop, so I can have a "OnUpdate" event in C# thats called every frame and runs in the game thread
* Create a delegate that describes the target function
* Get the delegate from the function pointer:
Code:
var func = Marshal.GetDelegateForFunctionPointer<ChatFunctionDlg>(new IntPtr(GetModuleBaseAddress("Game.dll").ToInt64() + 0x1234ABC));
* Call the function in my OnUpdate once: func("Kappa");

Results: It works the string is sent into chat as intended

Problem: After a few seconds, the game crashes with an exception of type STATUS_INVALID_PARAMETER. x64dbg shows a huge call stack that doesn't make much sense to me:
i.?imgur?.?com/?pmRlHrT.png (am i seriously not allowed to post images?)

I thought that maybe easyhook didn't clean stuff up, but thats not the case (checked manually with debugger). If I just hook the function and do trivial stuff, for example count frames and print them to the screen everything is fine. Only if I call a game function everything goes wrong.

I'd like to hear any ideas you have regarding this, especially:
* Do you think this is because I'm calling from C#?
* Do you think this is because I'm calling the function from a wrong place, which causes global stuff to be modified?
That huge call stack is probably just marshalling and error handling the CLR does. Are you sure that calling the function once every frame is a good idea? (Might just have misunderstood you)

I do not know how well the CLR handles the calling convention used, you might check this.
I'm not calling the function every frame, only once (first frame the dll is injected) (using a simple bool check). The CLR seems to handle the calling convention well, the parameters get passed as expected and after returning everything is cleaned up (easyhook ...). I'm pretty confident that I'm dealing with something different here, for example the game modifying global variables, and because i'm calling from an unexpected place the game crashes. Maybe i'll try with c++ just to make sure ...
Didn't read, only read title.
But you can do this with C++ and control the C++ DLL with a "Named Network Pipe" from your dedicated C# Program.

You can send a command to the DLL to call the Function.

But you will need to make your own class for named network pipes in c++ tho.
EDIT: As a side note, no C++ bootstrapper is needed to do what you want. Compile and inject this project (search github/lo lp1 repos and look for domainwrapper, since I can not link it due to being new here) and call the export HostDomain(string appToHostPath) with the appToHostPath being your C# app. Just make it a form or console project so it has an entry point for ease.

My first suggestion is to drop EasyHook for what you are doing. I would instead just inject and host your C# app and then override the WndProc procedure to intercept custom messages. This allows you to call code from the main thread in a way that is thread safe.

Example:
Code:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
 
namespace Test
{
 
     public enum UserMessage
    {
        Startup,
        Shutdown
    }
 
    public class Window
    {
        private const int WmUser = 0x0400; // WM_USER can be defined as anything between 0x0400 and 0x7FFF!
        private const int GwlWndproc = -4; 

        private readonly IntPtr _handle;

        private WindowProc _newCallback;
        private IntPtr _oldCallback;
 
        public Window(IntPtr handle)
        {
            _handle = handle;
        }
 
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
 
        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
 
        [DllImport("user32.dll")]
        public static extern int CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, int msg, int wParam, int lParam);
 
        public void Attach()
        {
            // Pins WndProc - will not be garbage collected.
            _newCallback = WndProc; 
            _oldCallback = SetWindowLong(_handle, GwlWndproc,
                Marshal.GetFunctionPointerForDelegate(_newCallback));
 
            // Just to be sure...
            if (_oldCallback == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
 
        public void Detach()
        {
            if (_newCallback == null)
            {
                return;
            }
 
            SetWindowLong(_handle, GwlWndproc, _oldCallback);
            _newCallback = null;
        }
 
        private int WndProc(IntPtr hWnd, int msg, int wParam, int lParam)
        {
            if (msg == WmUser && HandleUserMessage((UserMessage) wParam))
            {
                // Already handled..
                return 0; 
            }
 
            return CallWindowProc(_oldCallback, hWnd, msg, wParam, lParam);
        }
 
        public void Invoke(UserMessage message)
        {
            SendMessage(_handle, WmUser, (int) message, 0);
        }
 
        private bool HandleUserMessage(UserMessage message)
        {
            switch (message)
            {
                case UserMessage.Startup:
                    // Do startup here.
                    return true;
                case UserMessage.Shutdown:
                    // Do shutdown here.
                    return true;
            }
        }
 
        private delegate int WindowProc(IntPtr hWnd, int msg, int wParam, int lParam);
    }
}
Now you can call your code from the main thread however you want.. e.g

Code:
_mainWindow = new Window(_gameProcess.MainWindowHandle);
_mainWindow.Attach();
_mainWindow.Invoke(UserMessage.Startup);
Code:
_mainWindow.Invoke(UserMessage.Shutdown);
_mainWindow.Detach();
Thanks, will look into that. Also, finding Overlay.net in your repo explains where i know your name from
Also if the function you're calling is using a non standard C wrapper like __stdcall or __cdecl, you'll run into problems and need to write a bit of asm yourself to properly call it.
I am working with 64 bit only for now, as far as i know there is only one convention (correct me if i'm wrong)
Quote Originally Posted by Hitokiri~ View Post
Also if the function you're calling is using a non standard C wrapper like __stdcall or __cdecl, you'll run into problems and need to write a bit of asm yourself to properly call it.
Why? What problem do you think you will run into using an unmanaged function with the __cdecl convention? You should not in my experience and as far as I can tell from msdn documentation.


Quote Originally Posted by whosdatdev View Post
I am working with 64 bit only for now, as far as i know there is only one convention (correct me if i'm wrong)
In x64, your calling convention attribute specified in your unmanaged delegate signature will just be ignored right. Try it by putting __cdecl on a non-cdecl convention in x64 and it works the same.
Quote Originally Posted by lolp3 View Post
Why? What problem do you think you will run into using an unmanaged function with the __cdecl convention? You should not in my experience and as far as I can tell from msdn documentation.
If your function takes its parameters, not pushed onto the stack but as registers, you can imagine what would happen.
Quote Originally Posted by Hitokiri~ View Post

If your function takes its parameters, not pushed onto the stack but as registers, you can imagine what would happen.
What do you mean your function? He is calling another games function.. with an unmanaged delegate.. I'd like to see a case where you need to manually write out assembly code to call a __cdecl function via unmanaged delegate in C#.
Quote Originally Posted by lolp3 View Post
What do you mean your function? He is calling another games function.. with an unmanaged delegate.. I'd like to see a case where you need to manually write out assembly code to call a __cdecl function via unmanaged delegate in C#.
Where and when did he say, to make you assume, the function he's calling uses the C calling convention?
Quote Originally Posted by Hitokiri~ View Post

Where and when did he say, to make you assume, the function he's calling uses the C calling convention?
No where. He clearly said he is running x64, in which case that convention attribute is ignored. Regardless, that is not important. I am asking you to show me an example where you would need to write assembly code manually to properly call a __stdcall or __cdecl function using an unmanaged delegate call in C#. I've never had to and I can not imagine when I would.

I'd like to see an actual example though.
Quote Originally Posted by lolp3 View Post
I am asking you to show me an example where you would need to write assembly code manually to properly call a __stdcall or __cdecl function using an unmanaged delegate call in C#. I've never had to and I can not imagine when I would.

I'd like to see an actual example though.
Quote Originally Posted by lolp3 View Post
if the function you're calling is using a non standard C wrapper like __stdcall or __cdecl
Keep re-reading this till you understand what I was saying.

Also yes, I know I made a typo saying `like`. Anyone worth their salt would know that I meant `unlike`.
I find it funny how you say "anyone worth their salt..". I actually thought I read "standard C conventions...", not non-standard. That is why I was baffled.

Anyways, if you want to manage the stack yourself, here is a good start.. github.(..)/lo lp1/Process.NET/ tree/master/src/Process.NET/Assembly/CallingConventions
Posts 1–15 of 16 · Page 1 of 2

Post a Reply

Similar Threads

Talk with us