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);
}