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/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 d398ace..568d851 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 + v145 DynamicLibrary false true MultiByte - v140 + v145 diff --git a/M3ScriptHook.sln b/M3ScriptHook.sln index d0d341d..98ec341 100644 --- a/M3ScriptHook.sln +++ b/M3ScriptHook.sln @@ -1,26 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio Version 18 +VisualStudioVersion = 18.7.11925.98 stable 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} 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} - {CA29DBC0-E648-4470-AA81-9082B143FBDA} = {CA29DBC0-E648-4470-AA81-9082B143FBDA} - {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 @@ -29,18 +16,10 @@ 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 {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 320a7a1..ee47d7b 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 + v145 DynamicLibrary false true MultiByte - v140 + v145 @@ -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 @@ -64,19 +66,26 @@ $(SolutionDir)build\$(Configuration)\lib\libudis86.lib;%(AdditionalDependencies) - $(SolutionDir)\copydebug.bat + "$(SolutionDir)\copydebug.bat" 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 @@ -86,9 +95,11 @@ + false + $(SolutionDir)build\$(Configuration)\lib\libudis86.lib;%(AdditionalDependencies) - $(SolutionDir)\copyrelease.bat + "$(SolutionDir)\copyrelease.bat" diff --git a/M3ScriptHook/include/M3ScriptHook.h b/M3ScriptHook/include/M3ScriptHook.h index ad8d69a..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,13 +117,29 @@ 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(); 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..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 */ /************************************************************************/ @@ -149,8 +198,25 @@ 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()); + return; + } + + // 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 +250,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 +267,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 +278,7 @@ int32_t LuaFunctions::UnbindKey(lua_State *L) context = plua_tostring(L, 2); } - M3ScriptHook::instance()->DestroyKeyBind(key, context); + M3ScriptHook::instance()->DestroyKeyBind(key); return 0; } @@ -233,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(); @@ -272,12 +338,31 @@ 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); + } +} + +// 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() @@ -293,97 +378,108 @@ 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 pCallAddr = GetPointerFromPattern("lua_pcall", "E8 ? ? ? ? 8B D8 85 C0 75 ? 4C 8B C5"); + logPointer("lua_pcall_callsite", pCallAddr); auto pCall = pCallAddr + *(int32_t *)(pCallAddr + 1) + 5; - logPointer("lua_pcall", pCallAddr); + logPointer("lua_pcall", pCall); 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"); + 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) { + 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/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 44f81c0..cb40609 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(); } @@ -143,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) @@ -165,14 +200,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); @@ -202,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(); @@ -216,10 +291,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 +333,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 +353,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); @@ -311,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/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 26b5bb5..0000000 Binary files a/M3ScriptHookLauncher/resources/M3.rc and /dev/null differ diff --git a/M3ScriptHookLauncher/resources/icon.ico b/M3ScriptHookLauncher/resources/icon.ico deleted file mode 100644 index bca4c66..0000000 Binary files a/M3ScriptHookLauncher/resources/icon.ico and /dev/null differ diff --git a/M3ScriptHookLauncher/resources/resource.h b/M3ScriptHookLauncher/resources/resource.h deleted file mode 100644 index 4643570..0000000 Binary files a/M3ScriptHookLauncher/resources/resource.h and /dev/null differ 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/copydebug.bat b/copydebug.bat index 6697d29..2f5ce4f 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=C:\Program Files (x86)\Steam\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..2c0fcc8 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=C:\Program Files (x86)\Steam\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 diff --git a/shared/include/hooking/libudis86.vcxproj b/shared/include/hooking/libudis86.vcxproj index 6961609..057a95c 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 + v145