diff --git a/code/client/clrcore-v2/BaseScript.cs b/code/client/clrcore-v2/BaseScript.cs index 97a42f774b..cc604661ec 100644 --- a/code/client/clrcore-v2/BaseScript.cs +++ b/code/client/clrcore-v2/BaseScript.cs @@ -278,7 +278,6 @@ internal void RegisterKeyMap(string command, string description, string inputMap #else if (inputMapper != null && inputParameter != null) { - Debug.WriteLine(command); Native.CoreNatives.RegisterKeyMapping(command, description, inputMapper, inputParameter); } m_commands.Add(new KeyValuePair(ReferenceFunctionManager.CreateCommand(command, dynFunc, false), dynFunc)); diff --git a/code/client/clrcore-v2/Coroutine/Scheduler.cs b/code/client/clrcore-v2/Coroutine/Scheduler.cs index 3a8a17a6cd..161ef4163c 100644 --- a/code/client/clrcore-v2/Coroutine/Scheduler.cs +++ b/code/client/clrcore-v2/Coroutine/Scheduler.cs @@ -43,6 +43,7 @@ public static void Schedule(Action coroutine) lock (s_nextFrame) { s_nextFrame.Add(coroutine); + ScriptInterface.RequestTickNextFrame(); } } else @@ -72,14 +73,30 @@ public static void Schedule(Action coroutine, TimePoint time) lock (s_queue) { // linear ordered insert, performance improvement might be a binary tree (i.e.: priority queue) - for (var it = s_queue.First; it != null; it = it.Next) + var it = s_queue.First; + if (it != null) { + // if added to the front, we'll also need to request for a tick/call-in if (time < it.Value.Item1) { - s_queue.AddBefore(it, new Tuple(time, coroutine)); + s_queue.AddFirst(new Tuple(time, coroutine)); + ScriptInterface.RequestTick(time); + return; } + + // check next + for (it = it.Next; it != null; it = it.Next) + { + if (time < it.Value.Item1) + { + s_queue.AddBefore(it, new Tuple(time, coroutine)); + return; + } + } } + else + ScriptInterface.RequestTick(time); s_queue.AddLast(new Tuple(time, coroutine)); } @@ -168,7 +185,10 @@ internal static void Update() } } else + { + ScriptInterface.RequestTick(curIt.Value.Item1); return; + } } } } diff --git a/code/client/clrcore-v2/Interop/Types/ScriptSharedData.cs b/code/client/clrcore-v2/Interop/Types/ScriptSharedData.cs new file mode 100644 index 0000000000..c985bf2faa --- /dev/null +++ b/code/client/clrcore-v2/Interop/Types/ScriptSharedData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; +using System.Threading; + +namespace CitizenFX.Core +{ + [StructLayout(LayoutKind.Explicit)] + internal struct ScriptSharedData + { + /// + /// Next time when our host needs to call in again + /// + [FieldOffset(0)] public ulong m_scheduledTime; + + /// + /// Same as but used in methods like who miss a overload. + /// + [FieldOffset(0)] public long m_scheduledTimeAsLong; + }; +} diff --git a/code/client/clrcore-v2/ScriptInterface.cs b/code/client/clrcore-v2/ScriptInterface.cs index 595531199e..07e6d9535e 100644 --- a/code/client/clrcore-v2/ScriptInterface.cs +++ b/code/client/clrcore-v2/ScriptInterface.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reflection; using System.Runtime.CompilerServices; using System.Security; +using System.Threading; #if IS_FXSERVER using ContextType = CitizenFX.Core.fxScriptContext; @@ -12,8 +12,6 @@ /* * Notes while working on this environment: -* - Scheduling: any function that can potentially add tasks to the C#'s scheduler needs to return the time -* of when it needs to be activated again, which then needs to be scheduled in the core scheduler (bookmark). */ namespace CitizenFX.Core @@ -25,6 +23,8 @@ internal static class ScriptInterface internal static string ResourceName { get; private set; } internal static CString CResourceName { get; private set; } + private static unsafe ScriptSharedData* s_sharedData; + #region Callable from C# [SecurityCritical, MethodImpl(MethodImplOptions.InternalCall)] @@ -71,16 +71,37 @@ internal static class ScriptInterface [SecurityCritical] internal static unsafe bool ReadAssembly(string file, out byte[] assembly, out byte[] symbols) => ReadAssembly(s_runtime, file, out assembly, out symbols); + /// + /// Schedule a call-in at the given time, uses CAS to make sure we only overwrite if the given time is earlier than the stored one. + /// + /// Next time to request a call in + [SecuritySafeCritical] + internal static unsafe void RequestTick(ulong time) + { + ulong prevTime = (ulong)Interlocked.Read(ref s_sharedData->m_scheduledTimeAsLong); + while (time < prevTime) + { + prevTime = (ulong)Interlocked.CompareExchange(ref s_sharedData->m_scheduledTimeAsLong, (long)time, (long)prevTime); + } + } + + /// + /// Schedule a call-in for the next frame. + /// + [SecuritySafeCritical] + public static unsafe void RequestTickNextFrame() => s_sharedData->m_scheduledTime = 0UL; // 64 bit read/writes – while aligned – are atomic on 64 bit machines + #endregion #region Called by Native [SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")] - internal static void Initialize(string resourceName, UIntPtr runtime, int instanceId) + private static unsafe void Initialize(string resourceName, UIntPtr runtime, int instanceId, ScriptSharedData* sharedData) { s_runtime = runtime; InstanceId = instanceId; ResourceName = resourceName; CResourceName = resourceName; + s_sharedData = sharedData; Resource.Current = new Resource(resourceName); Debug.Initialize(resourceName); @@ -93,7 +114,7 @@ internal static void Initialize(string resourceName, UIntPtr runtime, int instan } [SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")] - internal static ulong Tick(ulong hostTime, bool profiling) + internal static void Tick(ulong hostTime, bool profiling) { Scheduler.CurrentTime = (TimePoint)hostTime; Profiler.IsProfiling = profiling; @@ -107,12 +128,10 @@ internal static ulong Tick(ulong hostTime, bool profiling) { Debug.PrintError(e, "Tick()"); } - - return Scheduler.NextTaskTime(); } [SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")] - internal static unsafe ulong TriggerEvent(string eventName, byte* argsSerialized, int serializedSize, string sourceString, ulong hostTime, bool profiling) + internal static unsafe void TriggerEvent(string eventName, byte* argsSerialized, int serializedSize, string sourceString, ulong hostTime, bool profiling) { Scheduler.CurrentTime = (TimePoint)hostTime; Profiler.IsProfiling = profiling; @@ -136,30 +155,24 @@ internal static unsafe ulong TriggerEvent(string eventName, byte* argsSerialized EventsManager.IncomingEvent(eventName, sourceString, origin, argsSerialized, serializedSize, args); } } - - return Scheduler.NextTaskTime(); } [SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")] - internal static unsafe ulong LoadAssembly(string name, ulong hostTime, bool profiling) + internal static unsafe void LoadAssembly(string name, ulong hostTime, bool profiling) { Scheduler.CurrentTime = (TimePoint)hostTime; Profiler.IsProfiling = profiling; ScriptManager.LoadAssembly(name, true); - - return Scheduler.NextTaskTime(); } [SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")] - internal static unsafe ulong CallRef(int refIndex, byte* argsSerialized, uint argsSize, out IntPtr retvalSerialized, out uint retvalSize, ulong hostTime, bool profiling) + internal static unsafe void CallRef(int refIndex, byte* argsSerialized, uint argsSize, out IntPtr retvalSerialized, out uint retvalSize, ulong hostTime, bool profiling) { Scheduler.CurrentTime = (TimePoint)hostTime; Profiler.IsProfiling = profiling; ReferenceFunctionManager.IncomingCall(refIndex, argsSerialized, argsSize, out retvalSerialized, out retvalSize); - - return Scheduler.NextTaskTime(); } [SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")] diff --git a/code/components/citizen-resources-gta/src/ResourcesTest.cpp b/code/components/citizen-resources-gta/src/ResourcesTest.cpp index 54862be1b8..3eda413fc0 100644 --- a/code/components/citizen-resources-gta/src/ResourcesTest.cpp +++ b/code/components/citizen-resources-gta/src/ResourcesTest.cpp @@ -57,6 +57,11 @@ namespace streaming void RemoveDataFileFromLoadList(const std::string& type, const std::string& path); void SetNextLevelPath(const std::string& path); + +#if defined(IS_RDR3) + void SetTrainTrackFilePath(const std::string& path); + void SetTrolleyCableFilePath(const std::string& path); +#endif } #endif @@ -188,6 +193,18 @@ static InitFunction initFunction([] () streaming::SetNextLevelPath(resourceRoot + meta.second); } +#if defined(IS_RDR3) + for (auto& meta : metaData->GetEntries("replace_traintrack_file")) + { + streaming::SetTrainTrackFilePath(resourceRoot + meta.second); + } + + for (auto& meta : metaData->GetEntries("replace_trolley_cable_file")) + { + streaming::SetTrolleyCableFilePath(resourceRoot + meta.second); + } +#endif + if (!RangeLengthMatches(metaData->GetEntries("data_file"), metaData->GetEntries("data_file_extra"))) { GlobalError("data_file entry count mismatch in resource %s", resource->GetName()); diff --git a/code/components/citizen-scripting-mono-v2/include/MonoScriptRuntime.h b/code/components/citizen-scripting-mono-v2/include/MonoScriptRuntime.h index de2383912b..127722df19 100644 --- a/code/components/citizen-scripting-mono-v2/include/MonoScriptRuntime.h +++ b/code/components/citizen-scripting-mono-v2/include/MonoScriptRuntime.h @@ -18,10 +18,11 @@ #include #include "MonoMethods.h" +#include "ScriptSharedData.h" namespace fx::mono { -class MonoScriptRuntime : public fx::OMClass { private: @@ -35,22 +36,22 @@ class MonoScriptRuntime : public fx::OMClass m_handler; fx::Resource* m_parentObject; IDebugEventListener* m_debugListener; std::unordered_map m_scriptIds; - uint64_t m_scheduledTime = ~uint64_t(0); + + ScriptSharedData m_sharedData; // method targets Method m_loadAssembly; // method thunks, these are for calls that require performance - Thunk m_tick; - Thunk m_triggerEvent; + Thunk m_tick; + Thunk m_triggerEvent; - Thunk m_callRef; + Thunk m_callRef; Thunk m_duplicateRef = nullptr; Thunk m_removeRef = nullptr; @@ -75,9 +76,7 @@ class MonoScriptRuntime : public fx::OMClassScheduleBookmark(this, 0, timeMilliseconds * 1000); // positive values are expected to me microseconds and absolute - } -} } diff --git a/code/components/citizen-scripting-mono-v2/include/ScriptSharedData.h b/code/components/citizen-scripting-mono-v2/include/ScriptSharedData.h new file mode 100644 index 0000000000..d8137fc839 --- /dev/null +++ b/code/components/citizen-scripting-mono-v2/include/ScriptSharedData.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace fx::mono +{ +struct ScriptSharedData +{ +public: + std::atomic m_scheduledTime = ~uint64_t(0); +}; +} diff --git a/code/components/citizen-scripting-mono-v2/src/MonoScriptRuntime.cpp b/code/components/citizen-scripting-mono-v2/src/MonoScriptRuntime.cpp index 28e60dad65..3c4ea2c0a3 100644 --- a/code/components/citizen-scripting-mono-v2/src/MonoScriptRuntime.cpp +++ b/code/components/citizen-scripting-mono-v2/src/MonoScriptRuntime.cpp @@ -104,11 +104,6 @@ result_t MonoScriptRuntime::Create(IScriptHost* host) fx::OMPtr manifestPtr; ptr.As(&manifestPtr); m_manifestHost = manifestPtr.GetRef(); - - fx::OMPtr bookmarkPtr; - ptr.As(&bookmarkPtr); - m_bookmarkHost = bookmarkPtr.GetRef(); - m_bookmarkHost->CreateBookmarks(this); } char* resourceName = nullptr; @@ -134,7 +129,7 @@ result_t MonoScriptRuntime::Create(IScriptHost* host) auto* thisPtr = this; MonoException* exc; auto initialize = Method::Find(image, "CitizenFX.Core.ScriptInterface:Initialize"); - initialize({ mono_string_new(m_appDomain, resourceName), &thisPtr, &m_instanceId }, &exc); + initialize({ mono_string_new(m_appDomain, resourceName), &thisPtr, &m_instanceId, &m_sharedData }, &exc); mono_domain_set_internal(mono_get_root_domain()); // back to root for v1 @@ -172,16 +167,26 @@ result_t MonoScriptRuntime::Destroy() m_appDomain = nullptr; m_scriptHost = nullptr; - m_bookmarkHost->RemoveBookmarks(this); - m_bookmarkHost = nullptr; mono_domain_set_internal(mono_get_root_domain()); // back to root for v1 return ReturnOrError(exc); } -result_t MonoScriptRuntime::TickBookmarks(uint64_t* bookmarks, int32_t numBookmarks) +result_t MonoScriptRuntime::Tick() { + // Tick-less: we don't pay for runtime entry and exit costs if there's nothing to do. + { + auto nextScheduledTime = m_sharedData.m_scheduledTime.load(); + if (GetCurrentSchedulerTime() < nextScheduledTime) + { + return FX_S_OK; + } + + // We can ignore the time between the load above and the store below as the runtime will set this value again if there's still work to do + m_sharedData.m_scheduledTime.store(~uint64_t(0)); + } + m_handler->PushRuntime(static_cast(this)); if (m_parentObject) m_parentObject->OnActivate(); @@ -190,12 +195,8 @@ result_t MonoScriptRuntime::TickBookmarks(uint64_t* bookmarks, int32_t numBookma MONO_BOUNDARY_START - // reset scheduled time, nextTick will set the next time - m_scheduledTime = ~uint64_t(0); - MonoException* exc; - uint64_t nextTick = m_tick(GetCurrentSchedulerTime(), IsProfiling(), &exc); - ScheduleTick(nextTick); + m_tick(GetCurrentSchedulerTime(), IsProfiling(), &exc); MONO_BOUNDARY_END @@ -216,12 +217,10 @@ result_t MonoScriptRuntime::TriggerEvent(char* eventName, char* argsSerialized, MONO_BOUNDARY_START MonoException* exc = nullptr; - uint64_t nextTick = m_triggerEvent(mono_string_new(m_appDomain, eventName), + m_triggerEvent(mono_string_new(m_appDomain, eventName), argsSerialized, serializedSize, mono_string_new(m_appDomain, sourceId), GetCurrentSchedulerTime(), IsProfiling(), &exc); - ScheduleTick(nextTick); - MONO_BOUNDARY_END return ReturnOrError(exc); @@ -327,9 +326,7 @@ result_t MonoScriptRuntime::LoadFile(char* scriptFile) bool isProfiling = IsProfiling(); MonoException* exc = nullptr; - MonoObject* nextTickObject = m_loadAssembly({ mono_string_new(m_appDomain, scriptFile), ¤tTime, &isProfiling }, &exc); - uint64_t nextTick = *reinterpret_cast(mono_object_unbox(nextTickObject)); - ScheduleTick(nextTick); + m_loadAssembly({ mono_string_new(m_appDomain, scriptFile), ¤tTime, &isProfiling }, &exc); console::PrintWarning(_CFX_NAME_STRING(_CFX_COMPONENT_NAME), "Assembly %s has been loaded into the mono rt2 runtime. This runtime is still in beta and shouldn't be used in production, " @@ -348,8 +345,7 @@ result_t MonoScriptRuntime::CallRef(int32_t refIndex, char* argsSerialized, uint MonoDomainScope scope(m_appDomain); MonoException* exc = nullptr; - uint64_t nextTick = m_callRef(refIndex, argsSerialized, argsSize, retvalSerialized, retvalSize, GetCurrentSchedulerTime(), IsProfiling(), &exc); - ScheduleTick(nextTick); + m_callRef(refIndex, argsSerialized, argsSize, retvalSerialized, retvalSize, GetCurrentSchedulerTime(), IsProfiling(), &exc); return ReturnOrError(exc); } diff --git a/code/components/conhost-v2/src/ConsoleHostImpl.cpp b/code/components/conhost-v2/src/ConsoleHostImpl.cpp index 245a8197eb..be9550d9ac 100644 --- a/code/components/conhost-v2/src/ConsoleHostImpl.cpp +++ b/code/components/conhost-v2/src/ConsoleHostImpl.cpp @@ -363,6 +363,10 @@ void OnConsoleFrameDraw(int width, int height, bool usedSharedD3D11) { return; } + else if (g_pd3dDeviceContext == nullptr) + { + return; + } #ifndef IS_FXSERVER static ConVar winConsoleVar("con_winconsole", ConVar_Archive | ConVar_UserPref, false, &g_winConsole); diff --git a/code/components/conhost-v2/src/DrawPerf.cpp b/code/components/conhost-v2/src/DrawPerf.cpp index e57b47a1a8..b7a6fb5a2f 100644 --- a/code/components/conhost-v2/src/DrawPerf.cpp +++ b/code/components/conhost-v2/src/DrawPerf.cpp @@ -74,7 +74,7 @@ static LUID GetAdapterLUID() auto adapter = GetAdapter(); DXGI_ADAPTER_DESC desc; - if (SUCCEEDED(adapter->GetDesc(&desc))) + if (adapter && SUCCEEDED(adapter->GetDesc(&desc))) { adapterLuid = desc.AdapterLuid; } @@ -387,10 +387,10 @@ static InitFunction initFunction([]() { if (IsWindows10OrGreater()) { - static auto adapter = GetAdapter(); + auto adapter = GetAdapter(); Microsoft::WRL::ComPtr adapter3; - if (SUCCEEDED(adapter.As(&adapter3))) + if (adapter && SUCCEEDED(adapter.As(&adapter3))) { DXGI_ADAPTER_DESC desc; adapter->GetDesc(&desc); diff --git a/code/components/extra-natives-five/src/NativeEdits.cpp b/code/components/extra-natives-five/src/NativeEdits.cpp index 0042f7ed96..8f04a59a3d 100644 --- a/code/components/extra-natives-five/src/NativeEdits.cpp +++ b/code/components/extra-natives-five/src/NativeEdits.cpp @@ -1,12 +1,111 @@ #include #include + #include + #include +#include + +namespace rage +{ +class aiTaskTree +{ +public: + bool FindActiveTaskByType(int taskIndex); +}; +}; + +class CPedIntelligence +{ +public: + inline static ptrdiff_t kMotionTreeOffset; + inline static ptrdiff_t kPedIntelligenceOffset; + +public: + inline rage::aiTaskTree* GetMotionTaskTree() + { + auto location = reinterpret_cast(this) + kMotionTreeOffset; + return *reinterpret_cast(location); + } + + inline bool HasMotionTaskByType(int32_t taskIndex) + { + if (auto tree = GetMotionTaskTree()) + { + return tree->FindActiveTaskByType(taskIndex); + } + return false; + } + + // Utility function that should eventually be moved to CPed + static inline CPedIntelligence* GetIntelligence(CPed* ped) + { + auto location = reinterpret_cast(ped) + kPedIntelligenceOffset; + return *reinterpret_cast(location); + } +}; + +static hook::cdecl_stub _findTaskByType([]() +{ + return hook::get_pattern("83 79 10 FF 74 20 48 63 41 10 48 8B 4C C1"); +}); + +bool rage::aiTaskTree::FindActiveTaskByType(int taskIndex) +{ + return _findTaskByType(this, taskIndex); +} + +// GH-2401: Have GET_IS_TASK_ACTIVE search the motion-tree for TASK_COMBAT_ROLL. +// CTaskCombatRoll is used only as a subtask of CTaskMotionAiming which will not +// be exposed via GET_IS_TASK_ACTIVE. +static void EditIsTaskActive() +{ + constexpr uint64_t GET_IS_TASK_ACTIVE = 0xB0760331C7AA4155; + + const auto originalHandler = fx::ScriptEngine::GetNativeHandler(GET_IS_TASK_ACTIVE); + if (!originalHandler) + { + return; + } + + const auto handler = *originalHandler; + fx::ScriptEngine::RegisterNativeHandler(GET_IS_TASK_ACTIVE, [handler](fx::ScriptContext& ctx) + { + constexpr int32_t TASK_COMBAT_ROLL = 0x3; + uint32_t pedHandle = ctx.GetArgument(0); + uint32_t taskIndex = ctx.GetArgument(1); + + handler(ctx); + if (taskIndex != TASK_COMBAT_ROLL || ctx.GetResult()) + { + return; + } + + // Could not find TASK_COMBAT_ROLL check the peds motion task tree. + auto ped = rage::fwScriptGuid::GetBaseFromGuid(pedHandle); + if (ped && ped->IsOfType()) + { + if (auto intelligence = CPedIntelligence::GetIntelligence(reinterpret_cast(ped))) + { + if (intelligence->HasMotionTaskByType(TASK_COMBAT_ROLL)) + { + ctx.SetResult(true); + } + } + } + }); +} static bool* g_ropesCreateNetworkWorldState; static HookFunction hookFunction([]() { + { + auto location = hook::get_pattern("84 C0 0F 84 ? ? ? ? 48 8B 82 ? ? ? ? BA"); + CPedIntelligence::kPedIntelligenceOffset = *reinterpret_cast(location + 0xB); + CPedIntelligence::kMotionTreeOffset = *reinterpret_cast(location + 0x17); + } + g_ropesCreateNetworkWorldState = (bool*)hook::AllocateStubMemory(1); *g_ropesCreateNetworkWorldState = false; @@ -24,4 +123,9 @@ static HookFunction hookFunction([]() { *g_ropesCreateNetworkWorldState = false; }); + + rage::scrEngine::OnScriptInit.Connect([]() + { + EditIsTaskActive(); + }); }); diff --git a/code/components/extra-natives-five/src/NuiAudioSink.cpp b/code/components/extra-natives-five/src/NuiAudioSink.cpp index 30dac53f53..0e3d8076d1 100644 --- a/code/components/extra-natives-five/src/NuiAudioSink.cpp +++ b/code/components/extra-natives-five/src/NuiAudioSink.cpp @@ -1074,14 +1074,15 @@ namespace rage static audCategoryControllerManager* GetInstance(); }; + static audCategoryControllerManager** g_audCategoryControllerManager = nullptr; audCategoryControllerManager* audCategoryControllerManager::GetInstance() { -#ifdef GTA_FIVE - static auto patternRef = hook::get_address(hook::get_pattern("45 33 C0 BA 90 1C E2 44 E8", -4)); -#elif IS_RDR3 - static auto patternRef = hook::get_address(hook::get_pattern("48 8B 0D ? ? ? ? E8 ? ? ? ? 8B 15 ? ? ? ? 48 8D 0D ? ? ? ? E8", 3)); -#endif - return *patternRef; + if (!g_audCategoryControllerManager) + { + return nullptr; + } + + return *g_audCategoryControllerManager; } static hook::thiscall_stub _audCategoryControllerManager_CreateController([]() @@ -1108,6 +1109,8 @@ namespace rage initParamVal = hook::get_address(hook::get_pattern("BA 11 CC 23 C3 E8 ? ? ? ? 48 8D", 0x16)); audDriver::sm_Mixer = hook::get_address(hook::get_pattern("75 64 44 0F B7 45 06 48 8B 0D", 10)); + + g_audCategoryControllerManager = hook::get_address(hook::get_pattern("45 33 C0 BA 90 1C E2 44 E8", -4)); #elif IS_RDR3 g_frontendAudioEntity = hook::get_address(hook::get_pattern("48 8D 0D ? ? ? ? E8 ? ? ? ? 45 84 E4 74 ? 39 1D"), 3, 7); @@ -1116,6 +1119,8 @@ namespace rage initParamVal = hook::get_address(hook::get_pattern("8A 05 ? ? ? ? 48 8B CF F3 0F 11 45 ? 88 45 66"), 2, 6); audDriver::sm_Mixer = hook::get_address(hook::get_pattern("48 8B 05 ? ? ? ? 44 38 8C 01 ? ? ? ? 0F"), 3, 7); + + g_audCategoryControllerManager = hook::get_address(hook::get_pattern("48 8B 0D ? ? ? ? E8 ? ? ? ? 8B 15 ? ? ? ? 48 8D 0D ? ? ? ? E8", 3)); #endif }); #ifdef GTA_FIVE diff --git a/code/components/extra-natives-five/src/WeaponExtraNatives.cpp b/code/components/extra-natives-five/src/WeaponExtraNatives.cpp index d218242634..c834e677ad 100644 --- a/code/components/extra-natives-five/src/WeaponExtraNatives.cpp +++ b/code/components/extra-natives-five/src/WeaponExtraNatives.cpp @@ -97,13 +97,13 @@ static flashlightProcessFn origFlashlightProcess; static std::atomic_bool g_SET_FLASH_LIGHT_KEEP_ON_WHILE_MOVING = false; +static unsigned char* g_flashlightAndByte = nullptr; static void Flashlight_Process(CWeaponComponentFlashlight* thisptr, CPed* ped) { // Copy every flag except for FLASHLIGHT_ON which is 1 // 80 63 49 FE and byte ptr [rbx+49h], 0FEh // change to 0xFF so it copies the ON flag as well // (This byte is located in the original Flashlight::Process() function.) - static unsigned char* g_flashlightAndByte = hook::get_pattern("80 63 49 FE EB", 3); if (!g_SET_FLASH_LIGHT_KEEP_ON_WHILE_MOVING) { @@ -222,14 +222,11 @@ static void* CTaskGun_Stage1_1_TransitionStage(void* CTask, int newStage) typedef void* (*CTaskAimGunOnFootProcessStagesFn)(unsigned char*, int, int); static CTaskAimGunOnFootProcessStagesFn origCTaskAimGunOnFoot_ProcessStages; +typedef CWeapon* (*GetWeaponFn)(void*); +static uint32_t pedOffsetToWeaponMgr = 0; +static GetWeaponFn GetWeapon = nullptr; static void* CTaskAimGunOnFoot_ProcessStages(unsigned char* thisptr, int stage, int substage) { - // Borrow the GetWeapon() and offset into Ped from another vfunc in this class. - static unsigned char* addr = hook::get_pattern("0F 84 ? ? ? ? 48 8B 8F ? ? ? ? E8 ? ? ? ? 48 8B F0"); - static uint32_t pedOffsetToWeaponMgr = *(uint32_t*)(addr + 9); - typedef CWeapon* (*GetWeaponFn)(void*); - static GetWeaponFn GetWeapon = hook::get_address((uintptr_t)addr + 13, 1, 5); - CPed* ped = *(CPed**)(thisptr + 16); if (!(g_SET_WEAPONS_NO_AUTOSWAP || g_SET_WEAPONS_NO_AUTORELOAD) || ped != getLocalPlayerPed()) @@ -473,6 +470,17 @@ static HookFunction hookFunction([]() hook::put(&flashlightVtable[index], (uintptr_t)Flashlight_Process); } + // Borrow the GetWeapon() and offset into Ped from another vfunc in this class. + { + unsigned char* addr = hook::get_pattern("0F 84 ? ? ? ? 48 8B 8F ? ? ? ? E8 ? ? ? ? 48 8B F0"); + pedOffsetToWeaponMgr = *(uint32_t*)(addr + 9); + GetWeapon = hook::get_address((uintptr_t)addr + 13, 1, 5); + } + + { + g_flashlightAndByte = hook::get_pattern("80 63 49 FE EB", 3); + } + // Disable auto-swaps { void* autoswap; diff --git a/code/components/gta-core-five/include/RageInput.h b/code/components/gta-core-five/include/RageInput.h new file mode 100644 index 0000000000..6ce81b6c86 --- /dev/null +++ b/code/components/gta-core-five/include/RageInput.h @@ -0,0 +1,64 @@ +/* + * This file is part of the CitizenFX project - http://citizen.re/ + * + * See LICENSE and MENTIONS in the root of the source tree for information + * regarding licensing. + */ + +#pragma once + +#include + +namespace rage +{ +static constexpr size_t INPUT_PUSH_TO_TALK = 249; + +/// parEnumDefinition can be found at "0C 00 01 00 E0 7D 86 3D" - 0x10 +enum ioParameterMask : int32_t +{ + IOMT_KEYBOARD = 0x0, // 0xE38B382A + IOMT_MOUSE_AXIS = 0x200, // 0x140D3D7E + IOMT_MOUSE_WHEEL = 0x400, // 0x2AE06A4A + IOMT_MOUSE_BUTTON = 0x800, // 0xEC193160 + IOMT_PAD_AXIS = 0x1000, // 0x5A190AB4 + IOMT_PAD_INDEX = 0x2000, // 0x6A1EB78C + IOMT_JOYSTICK_POV = 0x4000, // 0x68BA5FB3 + IOMT_JOYSTICK_BUTTON = 0x8000, // 0xB0629D0A + IOMT_JOYSTICK_AXIS = 0x10000, // 0x2D02D576 + IOMT_PAD_BUTTON = 0x20000, // 0xB1639048 + IOMT_JOYSTICK_AXIS_NEGATIVE = 0x40000, // 0xFC6BC167 + IOMT_JOYSTICK_AXIS_POSITIVE = 0x80000, // 0x0722DC0F +}; + +enum ioMapperSource : int32_t +{ + IOMS_UNDEFINED = -1, + IOMS_KEYBOARD = 0, + IOMS_MOUSE_ABSOLUTEAXIS = 1, + IOMS_MOUSE_CENTEREDAXIS = 2, + IOMS_MOUSE_RELATIVEAXIS = 3, + IOMS_MOUSE_SCALEDAXIS = 4, + IOMS_MOUSE_NORMALIZED = 5, + IOMS_MOUSE_WHEEL = 6, + IOMS_MOUSE_BUTTON = 7, + IOMS_MOUSE_BUTTONANY = 8, + IOMS_PAD_DIGITALBUTTON = 9, + IOMS_PAD_DIGITALBUTTONANY = 10, + IOMS_PAD_ANALOGBUTTON = 11, + IOMS_PAD_AXIS = 12, + IOMS_JOYSTICK_BUTTON = 13, + IOMS_JOYSTICK_AXIS = 14, + IOMS_JOYSTICK_IAXIS = 15, + IOMS_JOYSTICK_AXIS_NEGATIVE = 16, + IOMS_JOYSTICK_AXIS_POSITIVE = 17, + IOMS_JOYSTICK_POV = 18, + IOMS_JOYSTICK_POV_AXIS = 19, + IOMS_PAD_DEBUGBUTTON = 20, + IOMS_DIGITALBUTTON_AXIS = 21, + IOMS_MKB_AXIS = 22, + IOMS_TOUCHPAD_ABSOLUTE_AXIS = 23, + IOMS_TOUCHPAD_CENTERED_AXIS = 24, + IOMS_GAME_CONTROLLED = 25, // Added in prior to b1604 + IOMS_FORCE32 = 2147483647, +}; +} diff --git a/code/components/gta-core-five/src/BlockLoadSetters.cpp b/code/components/gta-core-five/src/BlockLoadSetters.cpp index 2d18257170..bb1304c60a 100644 --- a/code/components/gta-core-five/src/BlockLoadSetters.cpp +++ b/code/components/gta-core-five/src/BlockLoadSetters.cpp @@ -507,36 +507,34 @@ static hook::cdecl_stub _kickRender([] static void (*g_origCtrlInit)(bool, bool); +static uint8_t* ctrlInit_location1 = nullptr; +static uint8_t* ctrlInit_location3 = nullptr; +static void* ctrlInit_rti = nullptr; + static void OnCtrlInit(bool isWindowed, bool isExclusive) { uint8_t orig1, orig2, orig3; - static auto location1 = hook::get_pattern("E8 ? ? ? ? 84 C0 74 1F 48 8B 05 ? ? ? ? 48 8B 40", 7); - { - orig1 = location1[0]; - orig2 = location1[-0x5F]; - hook::put(location1, 0xEB); - hook::put(location1 - 0x5F, 0xEB); + orig1 = ctrlInit_location1[0]; + orig2 = ctrlInit_location1[-0x5F]; + hook::put(ctrlInit_location1, 0xEB); + hook::put(ctrlInit_location1 - 0x5F, 0xEB); } - static auto location3 = hook::get_pattern("33 D2 89 7C 24 44 66 C7", -0x37); - { - orig3 = *location3; - hook::return_function(location3); + orig3 = *ctrlInit_location3; + hook::return_function(ctrlInit_location3); } - static auto rti = hook::get_address(hook::get_pattern("E8 ? ? ? ? 45 33 FF 44 38 7B 0D 74 0E", -6)); - _setRenderDeleg(); - _kickRender(rti, true); + _kickRender(ctrlInit_rti, true); OnMainGameFrame.Connect([orig1, orig2, orig3] { - hook::put(location1, orig1); - hook::put(location1 - 0x5F, orig2); - hook::put(location3, orig3); + hook::put(ctrlInit_location1, orig1); + hook::put(ctrlInit_location1 - 0x5F, orig2); + hook::put(ctrlInit_location3, orig3); }); // orig @@ -782,6 +780,10 @@ static HookFunction hookFunction([] () // kick renderer before slow dinput code { + ctrlInit_location1 = hook::get_pattern("E8 ? ? ? ? 84 C0 74 1F 48 8B 05 ? ? ? ? 48 8B 40", 7); + ctrlInit_location3 = hook::get_pattern("33 D2 89 7C 24 44 66 C7", -0x37); + ctrlInit_rti = hook::get_address(hook::get_pattern("E8 ? ? ? ? 45 33 FF 44 38 7B 0D 74 0E", -6)); + auto location = hook::get_pattern("74 0B E8 ? ? ? ? 8A 0D ? ? ? ? 80", 2); hook::set_call(&g_origCtrlInit, location); hook::call(location, OnCtrlInit); diff --git a/code/components/gta-core-five/src/GameInput.cpp b/code/components/gta-core-five/src/GameInput.cpp index 73ea78d27c..f90c00687a 100644 --- a/code/components/gta-core-five/src/GameInput.cpp +++ b/code/components/gta-core-five/src/GameInput.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -27,17 +28,17 @@ namespace rage { struct ioInputSource { - uint32_t source; + rage::ioMapperSource source; uint32_t parameter; int unk; inline ioInputSource() - : source(0), parameter(0), unk(-1) + : source(IOMS_KEYBOARD), parameter(0), unk(-1) { } - inline ioInputSource(uint32_t source, uint32_t parameter, int unk) + inline ioInputSource(rage::ioMapperSource source, uint32_t parameter, int unk) : source(source), parameter(parameter), unk(unk) { @@ -87,6 +88,22 @@ namespace rage void UpdateMap(int max_k, const ioInputSource& info, ioValue& value); void RemoveDeviceMappings(ioValue& value, int a3); + + public: + // + // Given a source/parameter, gets the parameter to use when serializing a control. + // + static uint32_t GetParameterIndex(rage::ioMapperSource source, uint32_t parameter); + + // + // Given a source/serialized parameter, gets the native parameter to use. + // + static uint32_t UngetParameterIndex(rage::ioMapperSource source, uint32_t parameter); + + // + // Ensure the pairing form a valid matching. + // + static bool ValidateParameter(rage::ioMapperSource source, uint32_t parameter); }; } @@ -160,15 +177,12 @@ uint32_t ControlSourceToMapperSource(uint32_t source) return _controlSourceToMapperSource(source); } -static hook::cdecl_stub _getParameterIndex([]() +static hook::cdecl_stub _getParameterIndex([]() { return hook::get_pattern("83 F9 01 74 2C 0F 8E E8 00 00 00", -0xF); }); -// -// Given a source/parameter, gets the parameter to use when serializing a control. -// -uint32_t GetParameterIndex(uint32_t source, uint32_t parameter) +uint32_t rage::ioMapper::GetParameterIndex(rage::ioMapperSource source, uint32_t parameter) { return _getParameterIndex(source, parameter); } @@ -178,14 +192,114 @@ static hook::cdecl_stub _ungetParameterIndex([]() return hook::get_pattern("74 28 7E 36 83 F9", -0x12); }); -// -// Given a source/serialized parameter, gets the native parameter to use. -// -uint32_t UngetParameterIndex(uint32_t source, uint32_t parameter) +uint32_t rage::ioMapper::UngetParameterIndex(rage::ioMapperSource source, uint32_t parameter) { return _ungetParameterIndex(source, parameter); } +// GH-1577: Sanitize invalid mapper/parameter pairings. In the reported case +// IOMS_PAD_DIGITALBUTTON may cause invalid keys to overflow into being valid. +// This function attempts to rebuild the switch statement from _getParameterIndex. +bool rage::ioMapper::ValidateParameter(rage::ioMapperSource source, uint32_t parameter) +{ + switch (source) + { + case IOMS_KEYBOARD: + return (parameter & IOMT_KEYBOARD) == IOMT_KEYBOARD; + case IOMS_MOUSE_ABSOLUTEAXIS: + return (parameter & IOMT_MOUSE_AXIS) == IOMT_MOUSE_AXIS; + case IOMS_MOUSE_CENTEREDAXIS: + case IOMS_MOUSE_RELATIVEAXIS: + case IOMS_MOUSE_SCALEDAXIS: + case IOMS_MOUSE_NORMALIZED: + return (parameter & IOMT_MOUSE_AXIS) == IOMT_MOUSE_AXIS; + case IOMS_MOUSE_WHEEL: + return (parameter & IOMT_MOUSE_WHEEL) == IOMT_MOUSE_WHEEL; + case IOMS_MOUSE_BUTTON: + return (parameter & IOMT_MOUSE_BUTTON) == IOMT_MOUSE_BUTTON; + case IOMS_MOUSE_BUTTONANY: + case IOMS_PAD_DIGITALBUTTONANY: + return true; + case IOMS_PAD_DIGITALBUTTON: + case IOMS_PAD_DEBUGBUTTON: + return (parameter & IOMT_PAD_BUTTON) == IOMT_PAD_BUTTON; + case IOMS_PAD_ANALOGBUTTON: + return (parameter & IOMT_PAD_INDEX) == IOMT_PAD_INDEX; + case IOMS_PAD_AXIS: + return (parameter & IOMT_PAD_AXIS) == IOMT_PAD_AXIS; + case IOMS_JOYSTICK_BUTTON: + return (parameter & IOMT_JOYSTICK_BUTTON) == IOMT_JOYSTICK_BUTTON; + case IOMS_JOYSTICK_AXIS: + case IOMS_JOYSTICK_IAXIS: + case IOMS_JOYSTICK_AXIS_NEGATIVE: + case IOMS_JOYSTICK_AXIS_POSITIVE: + return (parameter & IOMT_JOYSTICK_AXIS) == IOMT_JOYSTICK_AXIS; + case IOMS_JOYSTICK_POV: + case IOMS_JOYSTICK_POV_AXIS: + return (parameter & IOMT_JOYSTICK_POV) == IOMT_JOYSTICK_POV; + case IOMS_MKB_AXIS: + return (parameter & IOMT_KEYBOARD) == IOMT_KEYBOARD + || (parameter & IOMT_MOUSE_BUTTON) == IOMT_MOUSE_BUTTON + || (parameter & IOMT_MOUSE_WHEEL) == IOMT_MOUSE_WHEEL; + case IOMS_TOUCHPAD_ABSOLUTE_AXIS: + case IOMS_TOUCHPAD_CENTERED_AXIS: + return (parameter & IOMT_MOUSE_AXIS) == IOMT_MOUSE_AXIS; + default: + return false; + } +} + +class CControl +{ +public: + inline static size_t kInputOffset; + inline static size_t kMapperOffset; + +public: + inline rage::ioValue* GetValue(int keyIndex) + { + auto controlPtr = (uint8_t*)this; + return (rage::ioValue*)&controlPtr[kInputOffset + (keyIndex * 72)]; + } + + inline rage::ioMapper* GetInputMappers() + { + auto location = reinterpret_cast(this) + kMapperOffset; + return reinterpret_cast(location); + } + + rage::ioInputSource* GetBinding(rage::ioInputSource& outParam, int controlIdx, int unkN1, bool secondaryBinding, bool fallback); +}; + +class CControlMgr +{ +public: + inline static void* Controls; + inline static size_t kControlSize; + +public: + static inline CControl* GetPlayerControls() + { + return reinterpret_cast(Controls); + } + + static inline CControl* GetFrontendControls() + { + auto location = (static_cast(Controls)) + kControlSize; + return reinterpret_cast(location); + } +}; + +static hook::thiscall_stub _control_getBinding([]() +{ + return hook::get_call(hook::get_pattern("40 88 6C 24 28 40 88 6C 24 20 E8 ? ? ? ? 41 8D", 10)); +}); + +rage::ioInputSource* CControl::GetBinding(rage::ioInputSource& outParam, int controlIdx, int unkN1, bool secondaryBinding, bool fallback) +{ + return _control_getBinding(this, &outParam, controlIdx, unkN1, secondaryBinding, fallback); +} + class Button { public: @@ -195,7 +309,7 @@ class Button void UpdateOnControl(); - void SetFromControl(void* control, int keyIndex); + void SetFromControl(CControl* control, int keyIndex); private: std::string m_name; @@ -218,13 +332,13 @@ Button::Button(const std::string& name) m_downCommand = std::make_unique("+" + name, [this]() { - rage::ioInputSource source{ 0, 1, -4 }; // keyboard? + rage::ioInputSource source{ rage::IOMS_KEYBOARD, 1, -4 }; // keyboard? UpdateIoValues(1.0f, source); }); m_upCommand = std::make_unique("-" + name, [this]() { - rage::ioInputSource source{ 0, 1, -4 }; // keyboard? + rage::ioInputSource source{ rage::IOMS_KEYBOARD, 1, -4 }; // keyboard? UpdateIoValues(0.0f, source); }); } @@ -242,7 +356,7 @@ void Button::UpdateOnControl() { if (copy) { - rage::ioInputSource source{ 0, 1, -4 }; // keyboard? + rage::ioInputSource source{ rage::IOMS_KEYBOARD, 1, -4 }; if (!copy->IsDown(0.5f, rage::ioValue::NO_DEAD_ZONE)) { @@ -253,11 +367,9 @@ void Button::UpdateOnControl() } } -void Button::SetFromControl(void* control, int keyIndex) +void Button::SetFromControl(CControl* control, int keyIndex) { - auto controlPtr = (uint8_t*)control; - - rage::ioValue* value = (rage::ioValue*) & controlPtr[33704 + (keyIndex * 72)]; + rage::ioValue* value = control->GetValue(keyIndex); for (auto& copy : m_ioValueCopies) { @@ -296,7 +408,7 @@ class Binding bool IsActive() const; - inline void SetBinding(std::tuple binding) + inline void SetBinding(std::tuple binding) { m_binding = binding; } @@ -335,7 +447,7 @@ class Binding std::string m_tag; - std::tuple m_binding; + std::tuple m_binding; std::set m_mappers; }; @@ -351,8 +463,6 @@ Binding::~Binding() Unmap(); } -static void* g_control; -static uint32_t g_mapperOffset; static std::multiset> g_downSet; static hook::cdecl_stub _isMenuActive([] @@ -365,11 +475,6 @@ static hook::cdecl_stub _isTextInputBoxActive([] return hook::get_call(hook::get_pattern("E8 ? ? ? ? 40 84 FF 74 15 E8", 10)); }); -static hook::thiscall_stub _control_getBinding([]() -{ - return hook::get_call(hook::get_pattern("40 88 6C 24 28 40 88 6C 24 20 E8 ? ? ? ? 41 8D", 10)); -}); - bool IsTagActive(const std::string& tag); void Binding::Unmap() @@ -396,7 +501,7 @@ bool Binding::PreUpdate(rage::ioMapper* mapper) return false; } - if (Instance::Get()->GetGameLoaded() && mapper == (rage::ioMapper*)((char*)g_control + g_mapperOffset) && !_isMenuActive(1)) + if (Instance::Get()->GetGameLoaded() && mapper == CControlMgr::GetPlayerControls()->GetInputMappers() && !_isMenuActive(1)) { UpdateMap(mapper); } @@ -530,7 +635,7 @@ class BindingManager void Update(rage::ioMapper* mapper, uint32_t time); - std::shared_ptr Bind(int source, int parameter, const std::string& command); + std::shared_ptr Bind(rage::ioMapperSource source, int parameter, const std::string& command); inline auto& GetBindings() { @@ -549,14 +654,14 @@ class BindingManager std::unique_ptr m_unbindAllCommand; std::unique_ptr m_listBindsCommand; - std::multimap, std::shared_ptr> m_bindings; + std::multimap, std::shared_ptr> m_bindings; std::list> m_buttons; concurrency::concurrent_queue> m_queue; }; -static std::map ioSourceMap; +static std::map ioSourceMap; static std::map ioParameterMap; void BindingManager::Initialize() @@ -568,7 +673,7 @@ void BindingManager::Initialize() rage::ioInputSource source; binding.second->GetBinding(source); - auto parameterIndex = _getParameterIndex(source.source, source.parameter); + auto parameterIndex = rage::ioMapper::GetParameterIndex(source.source, source.parameter); std::string ioSource = ""; std::string ioParameter = ""; @@ -597,7 +702,7 @@ void BindingManager::Initialize() auto bind = [=](const std::string& ioSourceName, const std::string& ioParameterName, const std::string& commandString, const std::string& tag) { - int ioSource; + rage::ioMapperSource ioSource; { auto it = ioSourceMap.find(ioSourceName); @@ -621,8 +726,13 @@ void BindingManager::Initialize() console::Printf("IO", "Invalid key name %s\n", ioParameterName.c_str()); return; } + else if (!rage::ioMapper::ValidateParameter(ioSource, it->second)) + { + console::Printf("IO", "Invalid I/O parameter pairing: <%s, %s>\n", ioSourceName, ioParameterName); + return; + } - ioParameter = _ungetParameterIndex(ioSource, it->second); + ioParameter = rage::ioMapper::UngetParameterIndex(ioSource, it->second); } for (const auto& binding : m_bindings) @@ -657,7 +767,7 @@ void BindingManager::Initialize() m_unbindCommand = std::make_unique("unbind", [=](const std::string& ioSourceName, const std::string& ioParameterName) { - int ioSource; + rage::ioMapperSource ioSource; { auto it = ioSourceMap.find(ioSourceName); @@ -681,8 +791,13 @@ void BindingManager::Initialize() console::Printf("IO", "Invalid key name %s\n", ioParameterName.c_str()); return; } + else if (!rage::ioMapper::ValidateParameter(ioSource, it->second)) + { + console::Printf("IO", "Invalid I/O parameter pairing: <%s, %s>\n", ioSourceName, ioParameterName); + return; + } - ioParameter = _ungetParameterIndex(ioSource, it->second); + ioParameter = rage::ioMapper::UngetParameterIndex(ioSource, it->second); } m_bindings.erase({ ioSource, ioParameter }); @@ -696,7 +811,7 @@ void BindingManager::Initialize() }); } -std::shared_ptr BindingManager::Bind(int ioSource, int ioParameter, const std::string& commandString) +std::shared_ptr BindingManager::Bind(rage::ioMapperSource ioSource, int ioParameter, const std::string& commandString) { for (auto it = m_bindings.begin(); it != m_bindings.end(); ) { @@ -819,7 +934,7 @@ void BindingManager::OnGameInit() std::string_view thisName = *name; thisName = thisName.substr(thisName.find_first_of('_') + 1); - ioSourceMap[std::string(thisName)] = field->index; + ioSourceMap[std::string(thisName)] = static_cast(field->index); name++; } @@ -843,16 +958,8 @@ void BindingManager::CreateButtons() std::transform(thisNameStr.begin(), thisNameStr.end(), thisNameStr.begin(), ToLower); auto button = std::make_unique