Executing lua from the main thread native example menu

User Tag List

Results 1 to 3 of 3
  1. #1
    StillAVirgin64's Avatar Active Member CoreCoins Purchaser Authenticator enabled
    Reputation
    28
    Join Date
    Nov 2019
    Posts
    62
    Thanks G/R
    34/17
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Executing lua from the main thread native example

    Some of my previous snippets have received some refinement, so I'm consolidating the lua code here. I was trying to juggle threads to make my own C_Timer.After and C_Timer.NewTicker, but I realized the lua wants to only be called one at a time from the main thread. I made something like a queue to execute commands, and it seems to have avoided the crashes. I'm using Named Pipes to be able to talk to the Dll (GitHub - glubdos/Pipes-standalone: Named Pipes Server/Client). My setup is just a console to feed in commands, but it also loads commands on startup from a lua .txt file.

    Host App
    Code:
    using namespace std;
    
    wchar_t* StringToWChar(string input)
    {
    	auto* const wInput = new wchar_t[input.length() + 1];
    	copy(input.begin(), input.end(), wInput);
    	wInput[input.length()] = 0;
    	return wInput;
    }
    
    vector<string> DelimitVars(const string& varNames)
    {
    	stringstream ss(varNames);
    	vector<string> results;
    
    	while (ss.good())
    	{
    		string substr;
    		getline(ss, substr, ',');
    		results.push_back(substr);
    	}
    
    	return results;
    }
    
    void RunClient()
    {
    	Pipes::Client::Init();
    
    	string input;
    	do
    	{
    		getline(cin, input);
    		if (!input.empty())
    		{
    			auto* const wInput = StringToWChar(input);
    			auto response = Pipes::Client::SendReceive(wInput);
    			cout << response << endl;
    
    			auto varNames = input.substr(0, input.find('='));
    			auto vars = DelimitVars(varNames);
    
    			if (input != varNames)
    			{
    				for (const auto& var : vars)
    				{
    					// todo: add to vector instead of just printing
    					auto varResult = Pipes::Client::SendReceive(StringToWChar(var));
    					cout << var << ": " << varResult << endl;
    				}
    			}
    		}
    
    	} while (input != "@close");
    
    	exit(0);
    }
    
    int main()
    {
            // inject
    	Nydus::Run();
    	
    	CreateThread(nullptr, 0, LPTHREAD_START_ROUTINE(RunClient), nullptr, 0, nullptr);
    
    	while(true) {}
    }
    Dll Main
    Code:
    void RunPipes()
    {
    	if (!Pipes::Server::Request.empty())
    	{
    		if (Pipes::Server::Request == "@close")
    		{
    			Pipes::Server::Response = "#closing";
    			Pipes::Server::Cv.notify_one();
    		}
    	}
    
    	if (!Pipes::Server::Connected)
    		_safeToExit = true;
    	else
    		Window::Send(WM_USER_TICK);
    }
    
    DWORD Init(LPVOID hInstance)
    {
            // SetWindowLongPtr
    	Window::Init();
    
    	auto hPipeThread = Pipes::Server::Init();
    
    	while (!_safeToExit)
    	{
    		RunPipes();
    		this_thread::yield();
    	}
    
    	WaitForSingleObject(hPipeThread, INFINITE);
    
    	// restore window ptr        
            Window::Close();
    
    	FreeLibraryAndExitThread(HMODULE(hInstance), 0);
    }
    
    BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID)
    {
    	switch (fdwReason)
    	{
    	case DLL_PROCESS_ATTACH:
    		DisableThreadLibraryCalls(hInstance);
    
    		CreateThread(nullptr, 0, Init, hInstance, 0, nullptr);
    		break;
    	default:
    		break;
    	}
    
    	return TRUE;
    }
    WndProc Callback
    Code:
    LRESULT WINAPI Window::Hook(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        static auto runOnce = true;
    
        switch (uMsg)
        {
        case WM_USER_TICK:
            if (runOnce)
            {
                runOnce = false;
                
                // register custom functions - make sure to patch invalid ptr on the first one
                LuaScript::RegisterHandler("cycleObjs", LuaScript::IterateObjects);
                LuaScript::RegisterHandler("delay", LuaScript::ExecuteAfterMS);
                LuaScript::RegisterHandler("ticker", LuaScript::ExecuteEveryMS);
                
                 // parses the lua.txt file and executes it
                LuaBase::PushLua();
            }
    
            LuaBase::Execute();
    
            return false;
        default:break;
        }
    
        // Call original WndProc.
        return CallWindowProc(WndProc, hWnd, uMsg, wParam, lParam);
    }
    Timing Scripts
    Code:
    int LuaScript::ExecuteAfterMS(int64_t luaState)
    {
        auto delay = GameMethods::ToNumber(luaState, 1);
        auto function = GameMethods::ToLString(luaState, 2, nullptr) + "()";
    
        if (delay <= 0)
            return 1;
    
        LuaBase::Input(function, delay);
    
        return 1;
    }
    
    int LuaScript::ExecuteEveryMS(int64_t luaState)
    {
        auto sleepTime = GameMethods::ToNumber(luaState, 1);
        auto function = GameMethods::ToLString(luaState, 2, nullptr);
    
        string functionRepeater = "function " + function + "R() " + function + "() delay(" + to_string(sleepTime) + ", '" + function + "R') end " + function + "R()";
    
        LuaBase::Input(functionRepeater, 0);
    
        return 1;
    }
    LuaBase
    Code:
    void LuaBase::CheckPipes()
    {
        if (Pipes::Server::Request.empty() || Pipes::Server::Request.find('@') != string::npos)
            return;
    
        auto request = Pipes::Server::Request;
        Pipes::Server::Request = "";
    
        if (request.find('(') != string::npos)
        {
            GameMethods::Execute(request);
            Pipes::Server::Response = "Working...";
            Pipes::Server::Cv.notify_one();
        }
        else
        {
            Pipes::Server::Response = GameMethods::GetText(request.c_str());
            Pipes::Server::Cv.notify_one();
        }
    }
    
    void LuaBase::Execute()
    {
        CheckPipes();
    
        if (Commands.empty() || !GameMethods::ObjMgrIsValid(0))
            return;
    
        for (auto i = 0; i < Commands.size(); i++)
        {
            auto c = Commands[i];
            if (get<bool>(c) && chrono::high_resolution_clock::now() > get<chrono::time_point<chrono::steady_clock>>(c))
            {
                    GameMethods::Execute(get<string>(c));
                    auto size = Commands.size();
                    Commands.erase(Commands.begin() + i);
                    Commands.resize(size - 1);
            }
        }
    }
    
    void LuaBase::Input(string command, int64_t delayMS)
    {
        auto t = make_tuple(command, chrono::high_resolution_clock::now() + chrono::duration<int64_t, milli>(delayMS), true);
        Commands.push_back(t);
    }
    Last edited by StillAVirgin64; 08-09-2020 at 02:11 AM.

    These ads disappear when you log in.

  2. #2
    xalcon's Avatar Contributor ふたなり
    Authenticator enabled
    Reputation
    132
    Join Date
    Oct 2008
    Posts
    272
    Thanks G/R
    19/35
    Trade Feedback
    0 (0%)
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Interesting concept. You should keep in mind that the mainthread isn't necessarily the lua thread. You'll probably have a high chance getting hold of the lua thread by hooking wndproc, but I suggest checking the thread id as well - especially those people that use a present hook instead. With the integration of multithreaded rendering, there is no guarantee that DXGI::SwapChain::Present is called from the lua thread and the random crashes from calling the lua api from a different thread can be obnoxious to debug - and it's not unthinkable they might move lua to it's own thread in the future.
    "Threads should always commit suicide - they should never be murdered" - DirectX SDK

  3. Thanks StillAVirgin64 (1 members gave Thanks to xalcon for this useful post)
  4. #3
    air999's Avatar Contributor
    Reputation
    108
    Join Date
    Nov 2014
    Posts
    87
    Thanks G/R
    6/57
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This has been discussed many years ago...


    Originally Posted by Cypher View Post
    Originally Posted by flo8464 View Post
    Another reason it has to be executed by WoWs mainthread is that all relevant game-information is stored in the mainthreads TLS.
    Ladies an gentleman we have a winner!

    1. The object manager is thread-local and only available in the main thread.
    2. Lots of the Lua callbacks rely on the object manager.
    3. Ergo, Lua must be executed in the main thread.


    Originally Posted by suicidity View Post
    Lua is not Multi-Threaded capable, at least not in it's current incarnation. Lua must be called from the Main Thread of the game.
    This too.

  5. Thanks StillAVirgin64 (1 members gave Thanks to air999 for this useful post)

Similar Threads

  1. Do LUA addons execute in Wow's main thread?
    By ggg898 in forum WoW Memory Editing
    Replies: 15
    Last Post: 01-12-2020, 01:32 PM
  2. Replies: 6
    Last Post: 04-09-2017, 09:15 PM
  3. [Internal] Executing from the main thread without detours
    By Jadd in forum Wildstar Memory Editing
    Replies: 5
    Last Post: 07-09-2014, 10:01 PM
  4. [Bot] Injection code into wow. Do you have to call functions from the main thread?
    By Miivers in forum World of Warcraft Bots and Programs
    Replies: 2
    Last Post: 01-13-2014, 02:56 PM
  5. [Release] Lua scripts, The ultimative thread v1
    By b!atch in forum World of Warcraft Emulator Servers
    Replies: 6
    Last Post: 05-26-2008, 10:28 AM
All times are GMT -5. The time now is 11:52 AM. Powered by vBulletin® Version 4.2.3
Copyright © 2020 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2020 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search