From 3dd8e23d58bc532248a5676a88f2a3c35b3a7ce1 Mon Sep 17 00:00:00 2001 From: Evasion3356 Date: Fri, 9 May 2025 12:15:40 -0400 Subject: [PATCH 1/3] Updated for Mafia III Definitive edition. Redesigned M3ScriptHook project to compile as a DXGI hijacked DLL instead of an injected binary. Removed M3ScriptHookLauncher since it's no longer needed. Updated logger to include timestamps. Updated CreateKeyBind and DestroyKeyBind to include useful information in the log other than the function was called. Removed main_menu_dev replacement as it doesn't work in the definitive edition. --- .gitignore | 420 ++++++++++++++++-- ExampleDLLPlugin/ExampleDLLPlugin.vcxproj | 6 +- M3ScriptHook.sln | 15 +- M3ScriptHook/M3ScriptHook.vcxproj | 31 +- M3ScriptHook/include/M3ScriptHook.h | 2 +- M3ScriptHook/src/LuaFunctions.cpp | 117 +++-- M3ScriptHook/src/M3ScriptHook.cpp | 51 ++- M3ScriptHook/src/ScriptSystem.cpp | 4 +- .../M3ScriptHookLauncher.vcxproj | 100 ----- .../M3ScriptHookLauncher.vcxproj.filters | 45 -- M3ScriptHookLauncher/resources/M3.rc | Bin 2624 -> 0 bytes M3ScriptHookLauncher/resources/icon.ico | Bin 99678 -> 0 bytes M3ScriptHookLauncher/resources/resource.h | Bin 520 -> 0 bytes M3ScriptHookLauncher/src/main.cpp | 72 --- shared/include/hooking/libudis86.vcxproj | 4 +- 15 files changed, 535 insertions(+), 332 deletions(-) delete mode 100644 M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj delete mode 100644 M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj.filters delete mode 100644 M3ScriptHookLauncher/resources/M3.rc delete mode 100644 M3ScriptHookLauncher/resources/icon.ico delete mode 100644 M3ScriptHookLauncher/resources/resource.h delete mode 100644 M3ScriptHookLauncher/src/main.cpp diff --git a/.gitignore b/.gitignore index ab99f07..3e1335f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,401 @@ -# Compiled Object files -*.slo -*.lo -*.o -*.obj +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Rr]elease-DXGI/ +[Rr]elease-Logging/ +[Rr]eleases-Logging/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c -# Precompiled Headers -*.gch +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj *.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ -# Compiled Dynamic libraries -*.so -*.dylib -*.dll +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ -# Fortran module files -*.mod -*.smod +# Fody - auto-generated XML schema +FodyWeavers.xsd -# Compiled Static libraries -*.lai -*.la -*.a -*.lib +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace -# Executables -*.exe -*.out -*.app +# Local History for Visual Studio Code +.history/ -build/* -enc_temp_folder/* +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp -*.db -*.opendb \ No newline at end of file +# JetBrains Rider +*.sln.iml \ No newline at end of file diff --git a/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj b/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj index d398ace..dcded42 100644 --- a/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj +++ b/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj @@ -13,21 +13,21 @@ {A7650B52-67CB-406C-BD46-731ED539C9FE} ExampleDLLPlugin - 8.1 + 10.0 DynamicLibrary true MultiByte - v140 + v143 DynamicLibrary false true MultiByte - v140 + v143 diff --git a/M3ScriptHook.sln b/M3ScriptHook.sln index d0d341d..1fef9d5 100644 --- a/M3ScriptHook.sln +++ b/M3ScriptHook.sln @@ -1,14 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35913.81 d17.13 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "M3ScriptHookLauncher", "M3ScriptHookLauncher\M3ScriptHookLauncher.vcxproj", "{CA29DBC0-E648-4470-AA81-9082B143FBDA}" - ProjectSection(ProjectDependencies) = postProject - {D4AAA849-F280-2B84-46B2-C9524602F05F} = {D4AAA849-F280-2B84-46B2-C9524602F05F} - {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B} = {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "M3ScriptHook", "M3ScriptHook\M3ScriptHook.vcxproj", "{8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}" ProjectSection(ProjectDependencies) = postProject {D4AAA849-F280-2B84-46B2-C9524602F05F} = {D4AAA849-F280-2B84-46B2-C9524602F05F} @@ -17,7 +11,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExampleDLLPlugin", "ExampleDLLPlugin\ExampleDLLPlugin.vcxproj", "{A7650B52-67CB-406C-BD46-731ED539C9FE}" ProjectSection(ProjectDependencies) = postProject {D4AAA849-F280-2B84-46B2-C9524602F05F} = {D4AAA849-F280-2B84-46B2-C9524602F05F} - {CA29DBC0-E648-4470-AA81-9082B143FBDA} = {CA29DBC0-E648-4470-AA81-9082B143FBDA} {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B} = {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B} EndProjectSection EndProject @@ -29,10 +22,6 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CA29DBC0-E648-4470-AA81-9082B143FBDA}.Debug|x64.ActiveCfg = Debug|x64 - {CA29DBC0-E648-4470-AA81-9082B143FBDA}.Debug|x64.Build.0 = Debug|x64 - {CA29DBC0-E648-4470-AA81-9082B143FBDA}.Release|x64.ActiveCfg = Release|x64 - {CA29DBC0-E648-4470-AA81-9082B143FBDA}.Release|x64.Build.0 = Release|x64 {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}.Debug|x64.ActiveCfg = Debug|x64 {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}.Debug|x64.Build.0 = Debug|x64 {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}.Release|x64.ActiveCfg = Release|x64 diff --git a/M3ScriptHook/M3ScriptHook.vcxproj b/M3ScriptHook/M3ScriptHook.vcxproj index 320a7a1..10103df 100644 --- a/M3ScriptHook/M3ScriptHook.vcxproj +++ b/M3ScriptHook/M3ScriptHook.vcxproj @@ -14,21 +14,21 @@ {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B} M3ScriptHook M3ScriptHook - 8.1 + 10.0 DynamicLibrary true MultiByte - v140 + v143 DynamicLibrary false true MultiByte - v140 + v143 @@ -41,14 +41,16 @@ - C:\Users\Joel\include;$(IncludePath) + $(IncludePath) $(SolutionDir)build\$(Configuration)\ $(SolutionDir)build\$(Configuration)\tmp\$(ProjectName)\ + dxgi - C:\Users\Joel\include;$(IncludePath) + $(IncludePath) $(SolutionDir)build\$(Configuration)\ $(SolutionDir)build\$(Configuration)\tmp\$(ProjectName)\ + dxgi @@ -70,25 +72,34 @@ Level3 - MinSpace + MaxSpeed true true - MultiThreadedDLL - Size + Speed include;..\shared\include;..\vendor\LUA LUA_BUILD_AS_DLL;_CRT_SECURE_NO_WARNINGS;LUA_LIB;%(PreprocessorDefinitions) + true + true + false + Fast + + + + false - true + false true true + false - $(SolutionDir)\copyrelease.bat + + diff --git a/M3ScriptHook/include/M3ScriptHook.h b/M3ScriptHook/include/M3ScriptHook.h index ad8d69a..70acb9a 100644 --- a/M3ScriptHook/include/M3ScriptHook.h +++ b/M3ScriptHook/include/M3ScriptHook.h @@ -114,7 +114,7 @@ class M3ScriptHook : public singleton void Shutdown(); void CreateKeyBind(const char *key, const char *context); - void DestroyKeyBind(const char *key, const char *context); + void DestroyKeyBind(const char *key); void ProcessKeyBinds(); }; diff --git a/M3ScriptHook/src/LuaFunctions.cpp b/M3ScriptHook/src/LuaFunctions.cpp index 6a25a6c..4666087 100644 --- a/M3ScriptHook/src/LuaFunctions.cpp +++ b/M3ScriptHook/src/LuaFunctions.cpp @@ -149,8 +149,24 @@ int32_t lua_gettop_(lua_State *L) // __declspec(dllexport) void logPointer(std::string name, uint64_t pointer) { + if (pointer == NULL) + { + std::stringstream ss; + ss << name << "Failed to find: " << name; + M3ScriptHook::instance()->Log(ss.str()); + } + + // Get the base address of the main module + uint64_t baseAddress = reinterpret_cast(GetModuleHandle(nullptr)); + + // Calculate the offset + uint64_t offset = pointer - baseAddress; + + // Format the log message std::stringstream ss; - ss << name << " (" << std::hex << pointer << ")"; + ss << name << " Mafia3DefinitiveEdition.exe+0x" << std::hex << std::uppercase << offset; + + // Log the message M3ScriptHook::instance()->Log(ss.str().c_str()); } @@ -184,7 +200,6 @@ int32_t LuaFunctions::PrintToLog(lua_State *L) int32_t LuaFunctions::BindKey(lua_State *L) { - M3ScriptHook::instance()->Log(__FUNCTION__); const char *key = ""; const char *context = ""; @@ -202,7 +217,6 @@ int32_t LuaFunctions::BindKey(lua_State *L) int32_t LuaFunctions::UnbindKey(lua_State *L) { - M3ScriptHook::instance()->Log(__FUNCTION__); const char *key = ""; const char *context = ""; @@ -214,7 +228,7 @@ int32_t LuaFunctions::UnbindKey(lua_State *L) context = plua_tostring(L, 2); } - M3ScriptHook::instance()->DestroyKeyBind(key, context); + M3ScriptHook::instance()->DestroyKeyBind(key); return 0; } @@ -272,12 +286,11 @@ int32_t LuaFunctions::FNV32a(lua_State *L) LuaFunctions::LuaFunctions() { M3ScriptHook::instance()->Log(__FUNCTION__); - // Yep, it's thread blocking, but that's what I want, no processing of other stuff until this shit's ready.. - do { - M3ScriptHook::instance()->Log(__FUNCTION__ " Game is not ready, script engine not initialized, retry"); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - std::this_thread::yield(); - } while (!this->LoadPointers()); + if (!this->LoadPointers()) + { + M3ScriptHook::instance()->Log(__FUNCTION__ " Failed to find signatures."); + exit(1); + } } C_ScriptGameMachine *LuaFunctions::GetMainGameScriptMachine() @@ -293,97 +306,105 @@ bool LuaFunctions::IsMainScriptMachineReady() bool LuaFunctions::LoadPointers() { M3ScriptHook::instance()->Log(__FUNCTION__); - uint64_t engineAssignAddress = hooking::pattern("48 89 05 ? ? ? ? 48 8B 10 FF 92 ? ? ? ?").get(0).origaddr(); - uint64_t engine = engineAssignAddress + *(int32_t *)(engineAssignAddress + 3) + 7; - if (*(uintptr_t *)engine == 0) { + + uint64_t engineAssignAddress = hooking::pattern("48 89 3D ? ? ? ? 48 8B 07").get(0).origaddr(); + if (!engineAssignAddress) + { return this->m_mainScriptMachineReady; } - - // - this->m_pMainGameScriptMachine = *(C_ScriptGameMachine **)engine; + uint64_t engine = engineAssignAddress + *(int32_t *)(engineAssignAddress + 3) + 7; logPointer("m_pMainGameScriptMachine", engine); - if (!this->m_pMainGameScriptMachine) { - return this->m_mainScriptMachineReady; + while (*(uintptr_t *)engine == 0) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); } + this->m_pMainGameScriptMachine = *(C_ScriptGameMachine**)engine; - // - auto pCallAddr = GetPointerFromPattern("lua_pcall", "E8 ? ? ? ? 85 C0 74 05 48 83 43 ? ?"); - auto pCall = pCallAddr + *(int32_t *)(pCallAddr + 1) + 5; + auto pCallAddr = GetPointerFromPattern("lua_pcall", "E8 ? ? ? ? 8B D8 85 C0 75 ? 4C 8B C5"); logPointer("lua_pcall", pCallAddr); + auto pCall = pCallAddr + *(int32_t *)(pCallAddr + 1) + 5; plua_pcall2 = (lua_pcall_t)pCall; if (!plua_pcall2) { return this->m_mainScriptMachineReady; } - // - plua_tostring = (lua_tostring_t)GetPointerFromPattern("lua_tostring", "4C 8B C9 81 FA ? ? ? ? 7E 37"); + plua_tostring = (lua_tostring_t)GetPointerFromPattern("lua_tostring", "81 FA ? ? ? ? 7E ? 85 D2 7E ? 48 8B 41 ? 48 63 D2 48 83 C0 ? 48 C1 E2 ? 48 03 D0 48 3B 51 ? 73 ? 45 33 C0"); logPointer("lua_tostring", (uintptr_t)plua_tostring); - if (!plua_tostring) { + if (!plua_tostring) + { return this->m_mainScriptMachineReady; } - // - auto isStringAddr = GetPointerFromPattern("lua_isstring", "E8 ? ? ? ? 85 C0 74 5E 8B D3"); - auto isString = isStringAddr + *(int32_t *)(isStringAddr + 1) + 5; + auto isStringAddr = GetPointerFromPattern("lua_isstring", "E8 ? ? ? ? 85 C0 74 ? BA ? ? ? ? 48 8B CF E8 ? ? ? ? 48 8B D0"); logPointer("lua_isstring", isStringAddr); + auto isString = isStringAddr + *(int32_t *)(isStringAddr + 1) + 5; plua_isstring = (lua_isstring_t)isString; if (!plua_isstring) { return this->m_mainScriptMachineReady; } // - auto loadBufferAddr = GetPointerFromPattern("lua_loadbuffer", "E8 ? ? ? ? 8B F8 85 FF 74 17"); + auto loadBufferAddr = GetPointerFromPattern("lua_loadbuffer", "E8 ? ? ? ? 8B D8 85 C0 75 ? 45 33 C9"); + if (!loadBufferAddr) + { + return this->m_mainScriptMachineReady; + } auto loadBuffer = loadBufferAddr + *(int32_t *)(loadBufferAddr + 1) + 5; logPointer("lua_loadBuffer", loadBuffer); pluaL_loadbuffer = (luaL_loadbuffer_t)loadBuffer; - if (!pluaL_loadbuffer) { - return this->m_mainScriptMachineReady; - } // - plua_newthread = (lua_newthread_t)GetPointerFromPattern("lua_newthread", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 56 41 57 48 83 EC 40 48 8B 79 10 48 8B F1"); + plua_newthread = (lua_newthread_t)GetPointerFromPattern("lua_newthread", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 56 41 57 48 83 EC ? 48 8B 79 ? 48 8B F1 44 8B B9"); logPointer("lua_newthread", (uintptr_t)plua_newthread); if (!plua_newthread) { return this->m_mainScriptMachineReady; } // - auto pushClosureAddr = GetPointerFromPattern("lua_pushcclosure", "E8 ? ? ? ? 48 8B 47 48 45 33 C9"); - auto pushClosure = pushClosureAddr + *(int32_t *)(pushClosureAddr + 1) + 5; - logPointer("lua_pushcclosure", pushClosure); - plua_pushcclosure = (lua_pushcclosure_t)pushClosure; + plua_pushcclosure = (lua_pushcclosure_t)GetPointerFromPattern("plua_pushcclosure", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 41 54 41 56 41 57 48 83 EC ? 49 63 F0"); + logPointer("lua_pushcclosure", (uintptr_t)plua_pushcclosure); if (!plua_pushcclosure) { return this->m_mainScriptMachineReady; } // - auto setFieldAddr = GetPointerFromPattern("lua_setfield", "E8 ? ? ? ? 8B D5 49 8B CF E8 ? ? ? ? 41 8B D6"); + auto setFieldAddr = GetPointerFromPattern("lua_setfield", "E8 ? ? ? ? BE ? ? ? ? 81 BF"); + if (!setFieldAddr) + { + return this->m_mainScriptMachineReady; + } auto setField = setFieldAddr + *(int32_t *)(setFieldAddr + 1) + 5; logPointer("lua_setfield", setField); plua_setfield = (lua_setfield_t)setField; - if (!plua_setfield) { - return this->m_mainScriptMachineReady; - } // auto setGobalAddr = GetPointerFromPattern("lua_setglobal", "E8 ? ? ? ? 48 8B 54 24 ? 48 8B CE E8 ? ? ? ? 85 C0"); + if (!setGobalAddr) + { + return this->m_mainScriptMachineReady; + } auto setGlobal = setGobalAddr + *(int32_t *)(setGobalAddr + 1) + 5; logPointer("lua_setglobal", setGlobal); plua_setglobal = (lua_setglobal_t)setGlobal; - if (!plua_setglobal) { - return this->m_mainScriptMachineReady; - } + + /*static auto pat = hooking::pattern("48 8B 05 ? ? ? ? 48 89 44 24 ? 4C 8B 4C 24").get(0).origaddr(); + auto patAddr = pat + *(int32_t*)(pat + 3) + 7; + patAddr = *(uint64_t*)patAddr; + if (patAddr) + { + logPointer("patAddr", patAddr); + DWORD old_protect; + VirtualProtect((PVOID)patAddr, strlen("/gui/main_menu_dev.swf"), PAGE_EXECUTE_READWRITE, (PDWORD)&old_protect); + memcpy((void*)patAddr, "/gui/main_menu_dev.swf", strlen("/gui/main_menu_dev.swf")); + DWORD unused; + VirtualProtect((PVOID)patAddr, strlen("/gui/main_menu_dev.swf"), old_protect, &unused); + }*/ // this->m_mainScriptMachineReady = true; M3ScriptHook::instance()->Log(__FUNCTION__ " Finished"); return m_mainScriptMachineReady; - - static auto pat = hooking::pattern("4C 8B 0D ? ? ? ? 33 ED").get(0).origaddr(); - auto patAddr = pat + *(int32_t *)(pat + 3) + 7; - patAddr = *(uint64_t *)patAddr; - memcpy((void*)patAddr, "/gui/main_menu_dev.swf", strlen("/gui/main_menu_dev.swf")); /* static auto addr = hooking::pattern("F3 0F 10 0D ? ? ? ? 4C 8D 44 24 ? F3 48 0F 2C D0").get(0).origaddr(); static auto inc = hooking::inject_call((addr + 0x62)); diff --git a/M3ScriptHook/src/M3ScriptHook.cpp b/M3ScriptHook/src/M3ScriptHook.cpp index 44f81c0..ad23a8a 100644 --- a/M3ScriptHook/src/M3ScriptHook.cpp +++ b/M3ScriptHook/src/M3ScriptHook.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,33 @@ #include #include +#ifdef _WIN64 +#define DLLPATH "\\\\.\\GLOBALROOT\\SystemRoot\\System32\\dxgi.dll" +#else +#define DLLPATH "\\\\.\\GLOBALROOT\\SystemRoot\\SysWOW64\\dxgi.dll" +#endif // _WIN64 + +#pragma comment(linker, "/EXPORT:ApplyCompatResolutionQuirking=" DLLPATH ".ApplyCompatResolutionQuirking") +#pragma comment(linker, "/EXPORT:CompatString=" DLLPATH ".CompatString") +#pragma comment(linker, "/EXPORT:CompatValue=" DLLPATH ".CompatValue") +#pragma comment(linker, "/EXPORT:CreateDXGIFactory=" DLLPATH ".CreateDXGIFactory") +#pragma comment(linker, "/EXPORT:CreateDXGIFactory1=" DLLPATH ".CreateDXGIFactory1") +#pragma comment(linker, "/EXPORT:CreateDXGIFactory2=" DLLPATH ".CreateDXGIFactory2") +#pragma comment(linker, "/EXPORT:DXGID3D10CreateDevice=" DLLPATH ".DXGID3D10CreateDevice") +#pragma comment(linker, "/EXPORT:DXGID3D10CreateLayeredDevice=" DLLPATH ".DXGID3D10CreateLayeredDevice") +#pragma comment(linker, "/EXPORT:DXGID3D10GetLayeredDeviceSize=" DLLPATH ".DXGID3D10GetLayeredDeviceSize") +#pragma comment(linker, "/EXPORT:DXGID3D10RegisterLayers=" DLLPATH ".DXGID3D10RegisterLayers") +#pragma comment(linker, "/EXPORT:DXGIDeclareAdapterRemovalSupport=" DLLPATH ".DXGIDeclareAdapterRemovalSupport") +#pragma comment(linker, "/EXPORT:DXGIDisableVBlankVirtualization=" DLLPATH ".DXGIDisableVBlankVirtualization") +#pragma comment(linker, "/EXPORT:DXGIDumpJournal=" DLLPATH ".DXGIDumpJournal") +#pragma comment(linker, "/EXPORT:DXGIGetDebugInterface1=" DLLPATH ".DXGIGetDebugInterface1") +#pragma comment(linker, "/EXPORT:DXGIReportAdapterConfiguration=" DLLPATH ".DXGIReportAdapterConfiguration") +#pragma comment(linker, "/EXPORT:PIXBeginCapture=" DLLPATH ".PIXBeginCapture") +#pragma comment(linker, "/EXPORT:PIXEndCapture=" DLLPATH ".PIXEndCapture") +#pragma comment(linker, "/EXPORT:PIXGetCaptureState=" DLLPATH ".PIXGetCaptureState") +#pragma comment(linker, "/EXPORT:SetAppCompatStringPointer=" DLLPATH ".SetAppCompatStringPointer") +#pragma comment(linker, "/EXPORT:UpdateHMDEmulationStatus=" DLLPATH ".UpdateHMDEmulationStatus") + M3ScriptHook::M3ScriptHook() { Log(__FUNCTION__); @@ -87,8 +115,13 @@ void M3ScriptHook::Log(const char* string, ...) const char* msg = &buffer[thisBuffer * BUFFER_LENGTH]; std::fstream file("ScriptHook.log", std::ios::out | std::ios::app); - file << msg; - file << "\n"; + + auto now = std::chrono::system_clock::now(); + auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + auto timer = std::chrono::system_clock::to_time_t(now); + auto local_time = *std::localtime(&timer); + + file << "[" << std::put_time(&local_time, "%m/%d/%Y %I:%M:%S") << ":" << std::setfill('0') << std::setw(3) << ms.count() << " " << std::put_time(&local_time, "%p") << "] " << msg << std::endl; file.close(); } @@ -165,14 +198,14 @@ LUA_API bool ExecuteLua(lua_State *L, const std::string &lua) bool M3ScriptHook::ExecuteLua(lua_State *L, const std::string &lua) { - this->Log(std::string("Trying to execute: " + lua).c_str()); + this->Log(std::string("Trying to execute: " + lua)); if (!L) { this->Log("BadState"); return false; } - luaL_loadbuffer_(L, const_cast(lua.c_str()), lua.length(), "test"); + luaL_loadbuffer_(L, const_cast(lua.c_str()), lua.length(), "Mafia3ScriptHook"); int32_t result = lua_pcall_(L, 0, LUA_MULTRET, 0); @@ -216,10 +249,10 @@ uint32_t WINAPI M3ScriptHook::mainThread(LPVOID) { if (GetAsyncKeyState(VK_F1) & 1) { ScriptSystem::instance()->ReloadScripts(); } - if (GetAsyncKeyState(VK_F2) & 1) { + /*if (GetAsyncKeyState(VK_F2) & 1) { instance->Shutdown(); PluginSystem::instance()->ReloadPlugins(); - } + }*/ /* if (GetAsyncKeyState(VK_F3) & 1) { instance->Shutdown(); @@ -258,7 +291,7 @@ void M3ScriptHook::Shutdown() void M3ScriptHook::CreateKeyBind(const char *key, const char *context) { - this->Log(__FUNCTION__); + M3ScriptHook::instance()->Log("Binding key %s to function %s", key, context); std::unique_lock lkScr(_keyBindMutex); bool found = false; @@ -278,9 +311,9 @@ void M3ScriptHook::CreateKeyBind(const char *key, const char *context) } } -void M3ScriptHook::DestroyKeyBind(const char *key, const char *context) +void M3ScriptHook::DestroyKeyBind(const char* key) { - this->Log(__FUNCTION__); + M3ScriptHook::instance()->Log("Unbinding key %", key); //std::lock_guard lk{ _keyBindMutex }; std::unique_lock lkScr(_keyBindMutex); diff --git a/M3ScriptHook/src/ScriptSystem.cpp b/M3ScriptHook/src/ScriptSystem.cpp index b75c2cf..c6d72ac 100644 --- a/M3ScriptHook/src/ScriptSystem.cpp +++ b/M3ScriptHook/src/ScriptSystem.cpp @@ -53,14 +53,14 @@ void ScriptSystem::LoadScripts() // Ignore the file if it starts with _ (underscore) if (filename[0] == '_') { - M3ScriptHook::instance()->Log(__FUNCTION__ " ignored script " + filename); + M3ScriptHook::instance()->Log("ignored script " + filename); } else { scripts.push_back(path); M3ScriptHook::instance()->LoadScript(path); - M3ScriptHook::instance()->Log(__FUNCTION__ " loaded script " + filename); + M3ScriptHook::instance()->Log("loaded script " + filename); } } while (file && FindNextFile(file, &data)); } diff --git a/M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj b/M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj deleted file mode 100644 index ba8743a..0000000 --- a/M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj +++ /dev/null @@ -1,100 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - - - - - - {CA29DBC0-E648-4470-AA81-9082B143FBDA} - M3ScriptHookLauncher - M3ScriptHookLauncher - 8.1 - - - - Application - true - MultiByte - v140 - - - Application - false - true - MultiByte - v140 - - - - - - - - - - - - - $(SolutionDir)build\$(Configuration)\ - $(SolutionDir)build\$(Configuration)\tmp\$(ProjectName)\ - - - $(SolutionDir)build\$(Configuration)\ - $(SolutionDir)build\$(Configuration)\tmp\$(ProjectName)\ - - - - Level3 - Disabled - ..\shared\include;..\vendor\LUA - - - true - - - $(SolutionDir)\copydebug.bat - - - - - Level3 - MinSpace - true - true - Size - ..\shared\include;..\vendor\LUA - - - true - true - true - - - $(SolutionDir)\copyrelease.bat - - - - - - \ No newline at end of file diff --git a/M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj.filters b/M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj.filters deleted file mode 100644 index 3766363..0000000 --- a/M3ScriptHookLauncher/M3ScriptHookLauncher.vcxproj.filters +++ /dev/null @@ -1,45 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {c45e4c9f-fdd1-4695-bc83-7ae62139d442} - - - - - Source Files - - - Source Files - - - - - Header Files - - - Resources - - - - - Source Files - - - Source Files - - - - - Resources - - - \ No newline at end of file diff --git a/M3ScriptHookLauncher/resources/M3.rc b/M3ScriptHookLauncher/resources/M3.rc deleted file mode 100644 index 26b5bb57a2ffac09e260fcd8381b5498fef1f033..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2624 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 diff --git a/M3ScriptHookLauncher/src/main.cpp b/M3ScriptHookLauncher/src/main.cpp deleted file mode 100644 index 19f0241..0000000 --- a/M3ScriptHookLauncher/src/main.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Base Application taken from Klusark (GPLv2) -// https://code.google.com/archive/p/mafia2injector/ - -/* - * Copyright (c) 2010 Barzakh (martinjk 'at' outlook 'dot' com) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - - * 3. This notice may not be removed or altered from any source - * distribution. - - * 4. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include -#include -#include - -int main() -{ - uint32_t pid = GetHandleByProcessName("mafia3.exe"); - HANDLE hProcess = INVALID_HANDLE_VALUE; - if (pid) { - hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - } - else { - STARTUPINFO si; - PROCESS_INFORMATION pi; - - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - - CreateProcess(0, "mafia3.exe", 0, 0, false, NORMAL_PRIORITY_CLASS, 0, 0, &si, &pi); - - hProcess = pi.hProcess; - } - - if (hProcess == INVALID_HANDLE_VALUE) { - log("Process could not be opened. Did you use administrator privileges (run as administrator)?"); - return 1; - } - - HMODULE hLibrary = InjectDll(hProcess, "M3ScriptHook.dll"); - if(!hLibrary) { - log("Library could not be loaded into process."); - return 1; - } - - CloseHandle(hProcess); - return 0; -} - diff --git a/shared/include/hooking/libudis86.vcxproj b/shared/include/hooking/libudis86.vcxproj index 6961609..74a0eea 100644 --- a/shared/include/hooking/libudis86.vcxproj +++ b/shared/include/hooking/libudis86.vcxproj @@ -15,7 +15,7 @@ Win32Proj libudis86 true - 8.1 + 10.0 @@ -23,7 +23,7 @@ StaticLibrary - v140 + v143 From 0160e2656b3ba3a54d37f341440b2d55c1d72f8e Mon Sep 17 00:00:00 2001 From: Evasion3356 Date: Fri, 9 May 2025 12:37:34 -0400 Subject: [PATCH 2/3] Updated build steps. Fixed logPointer not returning after failure scenario leading to a crash. --- M3ScriptHook/M3ScriptHook.vcxproj | 5 ++--- M3ScriptHook/src/LuaFunctions.cpp | 1 + copydebug.bat | 11 ++++++----- copyrelease.bat | 11 ++++++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/M3ScriptHook/M3ScriptHook.vcxproj b/M3ScriptHook/M3ScriptHook.vcxproj index 10103df..f95cbaf 100644 --- a/M3ScriptHook/M3ScriptHook.vcxproj +++ b/M3ScriptHook/M3ScriptHook.vcxproj @@ -66,7 +66,7 @@ $(SolutionDir)build\$(Configuration)\lib\libudis86.lib;%(AdditionalDependencies) - $(SolutionDir)\copydebug.bat + "$(SolutionDir)\copydebug.bat" @@ -98,8 +98,7 @@ false - - + "$(SolutionDir)\copyrelease.bat" diff --git a/M3ScriptHook/src/LuaFunctions.cpp b/M3ScriptHook/src/LuaFunctions.cpp index 4666087..decb4c3 100644 --- a/M3ScriptHook/src/LuaFunctions.cpp +++ b/M3ScriptHook/src/LuaFunctions.cpp @@ -154,6 +154,7 @@ __declspec(dllexport) void logPointer(std::string name, uint64_t pointer) std::stringstream ss; ss << name << "Failed to find: " << name; M3ScriptHook::instance()->Log(ss.str()); + return; } // Get the base address of the main module diff --git a/copydebug.bat b/copydebug.bat index 6697d29..375fd1e 100644 --- a/copydebug.bat +++ b/copydebug.bat @@ -1,5 +1,6 @@ -set MAFIAPATH=G:\Steam\steamapps\common\Mafia III -set mypath=C:\Users\Martin\Desktop\Mafia3ScriptHook -copy /Y /B "%mypath%\build\Debug\M3ScriptHook.dll" "%MAFIAPATH%\M3ScriptHook.dll" /B -copy /Y /B "%mypath%\build\Debug\plugins\ExampleDLLPlugin.dll" "%MAFIAPATH%\plugins\ExampleDLLPlugin.dll" /B -copy /Y /B "%mypath%\build\Debug\M3ScriptHookLauncher.exe" "%MAFIAPATH%\M3ScriptHookLauncher.exe" /B \ No newline at end of file +@echo off +set MAFIAPATH=D:\SteamLibrary\steamapps\common\Mafia III +set mypath=%~dp0 + +copy /Y /B "%mypath%\build\Debug\dxgi.dll" "%MAFIAPATH%\dxgi.dll" /B +copy /Y /B "%mypath%\build\Debug\dxgi.pdb" "%MAFIAPATH%\dxgi.pdb" /B \ No newline at end of file diff --git a/copyrelease.bat b/copyrelease.bat index 962360e..edd10ab 100644 --- a/copyrelease.bat +++ b/copyrelease.bat @@ -1,5 +1,6 @@ -set MAFIAPATH=G:\Steam\steamapps\common\Mafia III -set mypath=C:\Users\Martin\Desktop\Mafia3ScriptHook -copy /Y /B "%mypath%\build\Release\M3ScriptHook.dll" "%MAFIAPATH%\M3ScriptHook.dll" /B -copy /Y /B "%mypath%\build\Release\plugins\ExampleDLLPlugin.dll" "%MAFIAPATH%\plugins\ExampleDLLPlugin.dll" /B -copy /Y /B "%mypath%\build\Release\M3ScriptHookLauncher.exe" "%MAFIAPATH%\M3ScriptHookLauncher.exe" /B \ No newline at end of file +@echo off +set MAFIAPATH=D:\SteamLibrary\steamapps\common\Mafia III +set mypath=%~dp0 + +copy /Y /B "%mypath%\build\Release\dxgi.dll" "%MAFIAPATH%\dxgi.dll" /B +del /F /Q "%MAFIAPATH%\dxgi.pdb" \ No newline at end of file From 203632df6260ea39951bb36745dd04f0931a69ec Mon Sep 17 00:00:00 2001 From: Evasion3356 Date: Sat, 4 Jul 2026 11:43:38 -0400 Subject: [PATCH 3/3] Redesigned Lua injector around a pcall hook to flush the Lua queue into it when the game is running other Lua, rather than trying to async inject it from a thread causing a crash. --- Directory.Build.targets | 9 +++ ExampleDLLPlugin/ExampleDLLPlugin.vcxproj | 4 +- M3ScriptHook.sln | 14 +--- M3ScriptHook/M3ScriptHook.vcxproj | 7 +- M3ScriptHook/include/M3ScriptHook.h | 25 +++++++ M3ScriptHook/src/LuaFunctions.cpp | 80 +++++++++++++++++++++- M3ScriptHook/src/LuaStateManager.cpp | 10 ++- M3ScriptHook/src/M3ScriptHook.cpp | 83 ++++++++++++++++++----- copydebug.bat | 2 +- copyrelease.bat | 2 +- shared/include/hooking/libudis86.vcxproj | 2 +- 11 files changed, 195 insertions(+), 43 deletions(-) create mode 100644 Directory.Build.targets diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..7b2e533 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,9 @@ + + + + + diff --git a/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj b/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj index dcded42..568d851 100644 --- a/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj +++ b/ExampleDLLPlugin/ExampleDLLPlugin.vcxproj @@ -20,14 +20,14 @@ DynamicLibrary true MultiByte - v143 + v145 DynamicLibrary false true MultiByte - v143 + v145 diff --git a/M3ScriptHook.sln b/M3ScriptHook.sln index 1fef9d5..98ec341 100644 --- a/M3ScriptHook.sln +++ b/M3ScriptHook.sln @@ -1,19 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35913.81 d17.13 +# Visual Studio Version 18 +VisualStudioVersion = 18.7.11925.98 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "M3ScriptHook", "M3ScriptHook\M3ScriptHook.vcxproj", "{8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}" ProjectSection(ProjectDependencies) = postProject {D4AAA849-F280-2B84-46B2-C9524602F05F} = {D4AAA849-F280-2B84-46B2-C9524602F05F} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExampleDLLPlugin", "ExampleDLLPlugin\ExampleDLLPlugin.vcxproj", "{A7650B52-67CB-406C-BD46-731ED539C9FE}" - ProjectSection(ProjectDependencies) = postProject - {D4AAA849-F280-2B84-46B2-C9524602F05F} = {D4AAA849-F280-2B84-46B2-C9524602F05F} - {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B} = {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libudis86", "shared\include\hooking\libudis86.vcxproj", "{D4AAA849-F280-2B84-46B2-C9524602F05F}" EndProject Global @@ -26,10 +20,6 @@ Global {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}.Debug|x64.Build.0 = Debug|x64 {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}.Release|x64.ActiveCfg = Release|x64 {8D715BC3-5BBC-42E2-9F0D-29FBAFB6490B}.Release|x64.Build.0 = Release|x64 - {A7650B52-67CB-406C-BD46-731ED539C9FE}.Debug|x64.ActiveCfg = Debug|x64 - {A7650B52-67CB-406C-BD46-731ED539C9FE}.Debug|x64.Build.0 = Debug|x64 - {A7650B52-67CB-406C-BD46-731ED539C9FE}.Release|x64.ActiveCfg = Release|x64 - {A7650B52-67CB-406C-BD46-731ED539C9FE}.Release|x64.Build.0 = Release|x64 {D4AAA849-F280-2B84-46B2-C9524602F05F}.Debug|x64.ActiveCfg = Debug|x64 {D4AAA849-F280-2B84-46B2-C9524602F05F}.Debug|x64.Build.0 = Debug|x64 {D4AAA849-F280-2B84-46B2-C9524602F05F}.Release|x64.ActiveCfg = Release|x64 diff --git a/M3ScriptHook/M3ScriptHook.vcxproj b/M3ScriptHook/M3ScriptHook.vcxproj index f95cbaf..ee47d7b 100644 --- a/M3ScriptHook/M3ScriptHook.vcxproj +++ b/M3ScriptHook/M3ScriptHook.vcxproj @@ -21,14 +21,14 @@ DynamicLibrary true MultiByte - v143 + v145 DynamicLibrary false true MultiByte - v143 + v145 @@ -88,7 +88,7 @@ false - false + true true true @@ -96,6 +96,7 @@ false + $(SolutionDir)build\$(Configuration)\lib\libudis86.lib;%(AdditionalDependencies) "$(SolutionDir)\copyrelease.bat" diff --git a/M3ScriptHook/include/M3ScriptHook.h b/M3ScriptHook/include/M3ScriptHook.h index 70acb9a..9937d8e 100644 --- a/M3ScriptHook/include/M3ScriptHook.h +++ b/M3ScriptHook/include/M3ScriptHook.h @@ -37,6 +37,8 @@ #include #include #include +#include +#include static const char *BindableKeys[] = { "VK_LBUTTON", "VK_RBUTTON", "VK_CANCEL", "VK_MBUTTON", "VK_XBUTTON1", @@ -95,8 +97,15 @@ class M3ScriptHook : public singleton bool m_bEnded = false; //std::vector keyBinds = {}; std::map keyBinds = {}; + std::map keyWasDown = {}; std::recursive_mutex _keyBindMutex; + std::deque> m_luaQueue; + std::mutex m_luaQueueMutex; + // Guards the actual execution in DrainLuaQueue, in case the main-thread + // hook site ever turns out to be reachable from more than one thread. + std::mutex m_luaExecMutex; + public: M3ScriptHook(); virtual ~M3ScriptHook() = default; @@ -108,6 +117,22 @@ class M3ScriptHook : public singleton void LoadScript(const std::string &file); void LoadLuaFile(lua_State *L, const std::string &name); bool ExecuteLua(lua_State *L, const std::string &lua); + + // Thread-safe: queues Lua source to be run later on the thread that + // actually owns the game's Lua state (see DrainLuaQueue). Any caller + // that isn't already known to be on that thread must go through this + // instead of calling ExecuteLua directly. + void QueueLua(const std::string &lua); + + // Thread-safe: same as QueueLua but for arbitrary work that needs to + // touch the Lua state (e.g. registering C closures, starting plugins) + // rather than just running a chunk of Lua source. + void QueueWork(std::function work); + + // Must only be called from the thread that owns the game's Lua state + // (i.e. from the main-thread hook installed in LuaFunctions::Process). + void DrainLuaQueue(lua_State *L); + static uint32_t WINAPI mainThread(LPVOID); void StartThreads(); bool HasEnded(); diff --git a/M3ScriptHook/src/LuaFunctions.cpp b/M3ScriptHook/src/LuaFunctions.cpp index decb4c3..ba3a3aa 100644 --- a/M3ScriptHook/src/LuaFunctions.cpp +++ b/M3ScriptHook/src/LuaFunctions.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include /************************************************************************/ /* Find pattern implementation */ @@ -72,6 +74,53 @@ __declspec(dllexport) int32_t lua_pcall_(lua_State *L, int32_t nargs, int32_t nr return plua_pcall2(L, nargs, nresults, errfunc); } +// Drains the cross-thread Lua queue right before the engine's own pcall +// runs, guaranteeing DrainLuaQueue only ever executes on whatever thread +// the engine actually calls pcall from. Installed on sub_144561F50 itself +// (a bare "jmp lua_pcall" trampoline), NOT on any one of its 32+ individual +// call sites - hooking a single caller only catches that caller's own +// frequency (confirmed dead in practice), whereas hooking the trampoline's +// own body catches every one of its callers, which is what actually fires +// hundreds of times/sec during normal play. Its single-instruction body +// also makes it a stable anchor unlikely to get restructured by the +// compiler across game patches. +static int32_t __cdecl PcallDrainThunk(lua_State *L, int32_t nargs, int32_t nresults, int32_t errfunc) +{ + LuaFunctions::instance()->Process(); + return plua_pcall2(L, nargs, nresults, errfunc); +} + +using PcallJumpHook = hooking::inject_jump; +static std::unique_ptr g_pcallDrainHook; + +// One of 32+ call sites to the "jmp lua_pcall" trampoline (sub_144561F50) - +// used only to resolve the trampoline's own address (same +rel32+5 math +// used to resolve lua_pcall itself), not hooked directly. mov rax, rdi; +// neg rax right after the call is a distinctive enough sequence to be +// unique in the binary without needing excessive context. +static const char *kPcallTrampolineCallSitePattern = "E8 ? ? ? ? 48 8B C7 48 F7 D8"; + +static void InstallMainThreadDrainHook() +{ + auto callSiteAddr = GetPointerFromPattern("pcall_trampoline_callsite", kPcallTrampolineCallSitePattern); + if (!callSiteAddr) { + M3ScriptHook::instance()->Log("InstallMainThreadDrainHook: pattern not found - queued Lua (keybinds/setTimeout/script reload) will never run"); + return; + } + + auto trampolineAddr = callSiteAddr + *(int32_t *)(callSiteAddr + 1) + 5; + logPointer("pcall_trampoline", trampolineAddr); + + try { + g_pcallDrainHook = std::make_unique(trampolineAddr); + g_pcallDrainHook->inject(&PcallDrainThunk); + M3ScriptHook::instance()->Log("InstallMainThreadDrainHook installed"); + } + catch (const std::exception &e) { + M3ScriptHook::instance()->Log(std::string("InstallMainThreadDrainHook failed: ") + e.what()); + } +} + /************************************************************************/ /* Lua tolstring implementation */ /************************************************************************/ @@ -248,11 +297,13 @@ int32_t LuaFunctions::DelayBuffer(lua_State *L) } // Pretty hacky implementation, we should consider using a Job queue instead and checking time? - std::thread th = std::thread([L, time, context]() { + std::thread th = std::thread([time, context]() { M3ScriptHook::instance()->Log(__FUNCTION__); auto mtime = std::stoi(time); std::this_thread::sleep_for(std::chrono::milliseconds(mtime)); - M3ScriptHook::instance()->ExecuteLua(L, context); + // This thread doesn't own the game's Lua state - queue the work + // so it runs on MafiaMainThread instead of racing it. + M3ScriptHook::instance()->QueueLua(context); }); th.detach(); @@ -294,6 +345,26 @@ LuaFunctions::LuaFunctions() } } +// Drains any Lua work queued from other threads (keybinds, setTimeout, +// script (re)loads). MUST only be invoked from the main-thread hook +// installed in LoadPointers, since that's the only place we can be sure +// we're on the same thread the engine itself drives its Lua state from. +void LuaFunctions::Process() +{ + static bool loggedAlive = false; + if (!loggedAlive) { + loggedAlive = true; + M3ScriptHook::instance()->Log(__FUNCTION__ " main-thread drain hook is alive (first fire)"); + } + + auto L = GetL(this->m_pMainGameScriptMachine); + if (!L) { + return; + } + + M3ScriptHook::instance()->DrainLuaQueue(L); +} + C_ScriptGameMachine *LuaFunctions::GetMainGameScriptMachine() { return this->m_pMainGameScriptMachine; @@ -322,13 +393,16 @@ bool LuaFunctions::LoadPointers() this->m_pMainGameScriptMachine = *(C_ScriptGameMachine**)engine; auto pCallAddr = GetPointerFromPattern("lua_pcall", "E8 ? ? ? ? 8B D8 85 C0 75 ? 4C 8B C5"); - logPointer("lua_pcall", pCallAddr); + logPointer("lua_pcall_callsite", pCallAddr); auto pCall = pCallAddr + *(int32_t *)(pCallAddr + 1) + 5; + logPointer("lua_pcall", pCall); plua_pcall2 = (lua_pcall_t)pCall; if (!plua_pcall2) { return this->m_mainScriptMachineReady; } + InstallMainThreadDrainHook(); + plua_tostring = (lua_tostring_t)GetPointerFromPattern("lua_tostring", "81 FA ? ? ? ? 7E ? 85 D2 7E ? 48 8B 41 ? 48 63 D2 48 83 C0 ? 48 C1 E2 ? 48 03 D0 48 3B 51 ? 73 ? 45 33 C0"); logPointer("lua_tostring", (uintptr_t)plua_tostring); if (!plua_tostring) diff --git a/M3ScriptHook/src/LuaStateManager.cpp b/M3ScriptHook/src/LuaStateManager.cpp index cb29e7e..c548e80 100644 --- a/M3ScriptHook/src/LuaStateManager.cpp +++ b/M3ScriptHook/src/LuaStateManager.cpp @@ -63,9 +63,15 @@ void LuaStateManager::StateChanged(lua_State *L) ++this->m_stateChangeCount; this->m_pLuaState = L; - LuaFunctions::instance()->Setup(); + // This runs on WatcherThread, not on the thread that owns the game's + // Lua state - Setup()/StartPlugins() touch that state directly, so + // they must be marshaled onto the main thread like everything else. + bool isFirstState = (this->m_stateChangeCount == 1); + M3ScriptHook::instance()->QueueWork([isFirstState](lua_State*) { + LuaFunctions::instance()->Setup(); + isFirstState ? PluginSystem::instance()->StartPlugins() : PluginSystem::instance()->RelaunchPlugins(); + }); - this->m_stateChangeCount == 1 ? PluginSystem::instance()->StartPlugins() :PluginSystem::instance()->RelaunchPlugins(); ScriptSystem::instance()->ReloadScripts(); } diff --git a/M3ScriptHook/src/M3ScriptHook.cpp b/M3ScriptHook/src/M3ScriptHook.cpp index ad23a8a..cb40609 100644 --- a/M3ScriptHook/src/M3ScriptHook.cpp +++ b/M3ScriptHook/src/M3ScriptHook.cpp @@ -176,8 +176,10 @@ void M3ScriptHook::EndThreads() void M3ScriptHook::LoadScript(const std::string &file) { this->Log(__FUNCTION__); - auto threadState = LuaStateManager::instance()->GetState(); - this->LoadLuaFile(threadState, file); + std::string lua = "function dofile (filename)local f = assert(loadfile(filename)) return f() end dofile(\""; + lua.append(file); + lua.append("\")"); + this->QueueLua(lua); } void M3ScriptHook::LoadLuaFile(lua_State *L, const std::string &name) @@ -235,6 +237,46 @@ bool M3ScriptHook::ExecuteLua(lua_State *L, const std::string &lua) return true; } +void M3ScriptHook::QueueWork(std::function work) +{ + std::lock_guard lk(m_luaQueueMutex); + m_luaQueue.push_back(std::move(work)); +} + +void M3ScriptHook::QueueLua(const std::string &lua) +{ + // Deliberately runs against the raw main L, not a fresh coroutine via + // LuaStateManager::GetState()/lua_newthread_. lua_newthread pushes the + // new thread object onto the calling state's stack and nothing ever + // pops it - doing that on every queued item (every keybind press, every + // script load) permanently grows the main stack each time, which + // desyncs engine code that assumes a fixed stack layout. The original + // keybind path used the raw L directly for exactly this reason. + this->QueueWork([lua](lua_State *L) { + M3ScriptHook::instance()->ExecuteLua(L, lua); + }); +} + +// Only safe to call from the thread that owns the game's Lua state - +// see LuaFunctions::Process, which drives this from a hook that runs +// on MafiaMainThread. +void M3ScriptHook::DrainLuaQueue(lua_State *L) +{ + std::deque> pending; + { + std::lock_guard lk(m_luaQueueMutex); + if (m_luaQueue.empty()) { + return; + } + pending.swap(m_luaQueue); + } + + std::lock_guard execLk(m_luaExecMutex); + for (auto &work : pending) { + work(L); + } +} + uint32_t WINAPI M3ScriptHook::mainThread(LPVOID) { static M3ScriptHook *instance = M3ScriptHook::instance(); @@ -344,26 +386,31 @@ void M3ScriptHook::ProcessKeyBinds() //this->Log(__FUNCTION__); std::unique_lock lkScr(_keyBindMutex); - auto L = GetL(); - if (!L) - return; - - //this->Log("%d", keyBinds.size()); if (!keyBinds.size()) return; - /*auto it = keyBinds.begin(); - for (auto bind : keyBinds) //; it != keyBinds.end();) - { - if (GetAsyncKeyState(bind->key) & 1) - { - M3ScriptHook::instance()->ExecuteLua(L, bind->bind); - } - //++it; - }*/ + // NOTE: this runs on our own polling thread, not on MafiaMainThread, + // which also drives the same Lua state every frame. Calling + // ExecuteLua directly from here races with the engine's own Lua/GC + // activity and can corrupt memory (observed as a crash deep in + // unrelated engine code later on). Queue the work instead and let + // DrainLuaQueue run it from the main-thread hook. + // + // Edge detection is done ourselves via keyWasDown rather than trusting + // GetAsyncKeyState's low-order "pressed since last call" bit - that bit + // is a shared, consumable flag, and something else in this process + // (Special K polls global hotkeys aggressively) was eating it before + // our 10ms poll ever saw it, even though the "currently down" high bit + // was observably correct. The high bit isn't consumable, so tracking + // down/up transitions ourselves across polls is reliable regardless of + // who else is also calling GetAsyncKeyState. for (auto it = keyBinds.begin(); it != keyBinds.end(); ++it) { - if (GetAsyncKeyState(it->first) & 1) { - M3ScriptHook::instance()->ExecuteLua(L, it->second); + bool isDown = (GetAsyncKeyState(it->first) & 0x8000) != 0; + bool wasDown = keyWasDown[it->first]; + keyWasDown[it->first] = isDown; + + if (isDown && !wasDown) { + M3ScriptHook::instance()->QueueLua(it->second); } } diff --git a/copydebug.bat b/copydebug.bat index 375fd1e..2f5ce4f 100644 --- a/copydebug.bat +++ b/copydebug.bat @@ -1,5 +1,5 @@ @echo off -set MAFIAPATH=D:\SteamLibrary\steamapps\common\Mafia III +set MAFIAPATH=C:\Program Files (x86)\Steam\steamapps\common\Mafia III set mypath=%~dp0 copy /Y /B "%mypath%\build\Debug\dxgi.dll" "%MAFIAPATH%\dxgi.dll" /B diff --git a/copyrelease.bat b/copyrelease.bat index edd10ab..2c0fcc8 100644 --- a/copyrelease.bat +++ b/copyrelease.bat @@ -1,5 +1,5 @@ @echo off -set MAFIAPATH=D:\SteamLibrary\steamapps\common\Mafia III +set MAFIAPATH=C:\Program Files (x86)\Steam\steamapps\common\Mafia III set mypath=%~dp0 copy /Y /B "%mypath%\build\Release\dxgi.dll" "%MAFIAPATH%\dxgi.dll" /B diff --git a/shared/include/hooking/libudis86.vcxproj b/shared/include/hooking/libudis86.vcxproj index 74a0eea..057a95c 100644 --- a/shared/include/hooking/libudis86.vcxproj +++ b/shared/include/hooking/libudis86.vcxproj @@ -23,7 +23,7 @@ StaticLibrary - v143 + v145