From 99252ef115a253bb6e35cd418c8d82bd6cf8d23d Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Fri, 5 Jan 2024 15:46:23 +1100 Subject: [PATCH 1/2] next and mp4 working, not turbo tho --- Debugger/Main.as | 9 ++ Export.as | 5 + .../AfterStateUpdated/CallbackProcessing.as | 53 ++++++++ Internal/AfterStateUpdated/Cleanup.as | 7 ++ Internal/AfterStateUpdated/HookHelper.as | 76 ++++++++++++ Internal/AfterStateUpdated/UpdatePatterns.as | 114 ++++++++++++++++++ Internal/Vehicle/VehicleNext.as | 3 +- StateWrappers.as | 10 ++ 8 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 Internal/AfterStateUpdated/CallbackProcessing.as create mode 100644 Internal/AfterStateUpdated/Cleanup.as create mode 100644 Internal/AfterStateUpdated/HookHelper.as create mode 100644 Internal/AfterStateUpdated/UpdatePatterns.as diff --git a/Debugger/Main.as b/Debugger/Main.as index 544d4f4..94c1b18 100644 --- a/Debugger/Main.as +++ b/Debugger/Main.as @@ -77,7 +77,9 @@ namespace VehicleDebugger if (UI::CollapsingHeader(player.User.Name)) { UI::LabelText("Entity ID", Text::Format("%08x", VehicleState::GetEntityId(vehicle))); +#if TMNEXT UI::LabelText("Entity ID from player", Text::Format("%08x", VehicleState::GetPlayerVehicleID(player))); +#endif if (UI::Button("Player nod")) { ExploreNod(player); } @@ -166,6 +168,13 @@ namespace VehicleDebugger UI::LabelText("RRGroundContactMaterial", tostring(state.RRGroundContactMaterial)); #endif } +#elif TURBO + UI::LabelText("FLWheelRot", "" + state.FLWheelRot); + UI::LabelText("FLWheelRotSpeed", "" + state.FLWheelRotSpeed); + UI::LabelText("FLSteerAngle", "" + state.FLSteerAngle); + UI::LabelText("FLGroundContact", "" + state.FLGroundContact); + UI::LabelText("FLGroundContactRaw", "" + state.FLGroundContactRaw); + UI::LabelText("FLSlipCoef", "" + state.FLSlipCoef); #endif } } diff --git a/Export.as b/Export.as index 36ab58f..8673cb0 100644 --- a/Export.as +++ b/Export.as @@ -65,4 +65,9 @@ namespace VehicleState // Get all vehicle vis states. Mostly used for debugging. import array GetAllVis(CGameScene@ sceneVis) from "VehicleState"; #endif + + // Register a callback for late in the process when a vehicle state is updated. + import void RegisterOnVehicleStateUpdateCallback(OnVehicleStateUpdated@ func) from "VehicleState"; + // Deregister all callbacks from the calling plugin. + import void DeregisterVehicleStateUpdateCallbacks() from "VehicleState"; } diff --git a/Internal/AfterStateUpdated/CallbackProcessing.as b/Internal/AfterStateUpdated/CallbackProcessing.as new file mode 100644 index 0000000..3db50b9 --- /dev/null +++ b/Internal/AfterStateUpdated/CallbackProcessing.as @@ -0,0 +1,53 @@ +namespace VehicleState +{ + OnVehicleStateUpdated@[] callbacks; + string[] callbackPlugins; + + void RegisterOnVehicleStateUpdateCallback(OnVehicleStateUpdated@ func) + { + if (func is null) { + warn("Null function passed to RegisterOnVehicleStateUpdateCallback"); + return; + } + auto pluginId = Meta::ExecutingPlugin().ID; + callbacks.InsertLast(func); + callbackPlugins.InsertLast(pluginId); + trace('Added OVSU callback for ' + pluginId); + } + + void RunCallbacks(uint visEntId, uint64 visStatePtr) + { + for (uint i = 0; i < callbacks.Length; i++) { + auto f = callbacks[i]; + if (f is null) continue; + f(visEntId, visStatePtr); + } + } + + void DeregisterCallbacksFrom(Meta::Plugin@ plugin) + { + #if DEV + trace('Checking OVSU callbacks plugin for dereg'); + #endif + uint[] removeIxs = {}; + for (uint i = 0; i < callbackPlugins.Length; i++) { + auto p = callbackPlugins[i]; + if (p == plugin.ID) { + removeIxs.InsertLast(i); + trace('Found CB to remove at index ' + i); + } + } + for (int i = removeIxs.Length - 1; i >= 0; i--) { + callbacks.RemoveAt(i); + callbackPlugins.RemoveAt(i); + } + #if DEV + trace('Unregistered OVSU callbacks ('+removeIxs.Length+' total) for ' + plugin.ID); + #endif + } + + void DeregisterVehicleStateUpdateCallbacks() + { + DeregisterCallbacksFrom(Meta::ExecutingPlugin()); + } +} diff --git a/Internal/AfterStateUpdated/Cleanup.as b/Internal/AfterStateUpdated/Cleanup.as new file mode 100644 index 0000000..4bd5328 --- /dev/null +++ b/Internal/AfterStateUpdated/Cleanup.as @@ -0,0 +1,7 @@ +//remove any hooks +void OnDestroyed() { _Unload(); } +void OnDisabled() { _Unload(); } +void _Unload() +{ + CheckUnhookAllRegisteredHooks(); +} diff --git a/Internal/AfterStateUpdated/HookHelper.as b/Internal/AfterStateUpdated/HookHelper.as new file mode 100644 index 0000000..9138fa4 --- /dev/null +++ b/Internal/AfterStateUpdated/HookHelper.as @@ -0,0 +1,76 @@ +dictionary warnTracker; +void warn_every_60_s(const string &in msg) +{ + if (warnTracker is null) return; + if (warnTracker.Exists(msg)) { + uint lastWarn = uint(warnTracker[msg]); + if (Time::Now - lastWarn < 60000) return; + } else { + warn(msg); + } + warnTracker[msg] = Time::Now; + warn(msg); +} + +class HookHelper +{ + protected Dev::HookInfo@ hookInfo; + protected uint64 patternPtr; + + // protected string name; + protected string pattern; + protected uint offset; + protected uint padding; + protected string functionName; + + // const string &in name, + HookHelper(const string &in pattern, uint offset, uint padding, const string &in functionName) + { + this.pattern = pattern; + this.offset = offset; + this.padding = padding; + this.functionName = functionName; + } + + ~HookHelper() + { + Unapply(); + } + + bool Apply() + { + if (hookInfo !is null) return false; + if (patternPtr == 0) patternPtr = Dev::FindPattern(pattern); + if (patternPtr == 0) { + warn_every_60_s("Failed to apply hook for " + functionName); + return false; + } + @hookInfo = Dev::Hook(patternPtr + offset, padding, functionName, Dev::PushRegisters::SSE); + RegisterUnhookFunction(UnapplyHookFn(this.Unapply)); + return true; + } + + bool Unapply() + { + if (hookInfo is null) return false; + Dev::Unhook(hookInfo); + @hookInfo = null; + return true; + } +} + +funcdef bool UnapplyHookFn(); + +UnapplyHookFn@[] unapplyHookFns; +void RegisterUnhookFunction(UnapplyHookFn@ fn) +{ + if (fn is null) throw("null fn passted to reg unhook fn"); + unapplyHookFns.InsertLast(fn); +} + +void CheckUnhookAllRegisteredHooks() +{ + for (uint i = 0; i < unapplyHookFns.Length; i++) { + unapplyHookFns[i](); + } +} diff --git a/Internal/AfterStateUpdated/UpdatePatterns.as b/Internal/AfterStateUpdated/UpdatePatterns.as new file mode 100644 index 0000000..df8e0bd --- /dev/null +++ b/Internal/AfterStateUpdated/UpdatePatterns.as @@ -0,0 +1,114 @@ +#if TMNEXT +// rdx has vehicle vis state ptr +const string VEHICLE_VIS_UPDATED_HOOK_PATTERN = "4C 8B DA F3 0F 59 15 ?? ?? ?? ?? 4C 8B D1 F3 0F 10 89 ?? ?? 00 00 0F 2F D1"; +const uint VVU_OFFSET = 11; +const uint VVU_PADDING = 6; +#elif MP4 +// rcx has vehicle vis state ptr +const string VEHICLE_VIS_UPDATED_HOOK_PATTERN = "48 8B 43 38 48 8B 0C 07 F6 81 90 00 00 00 01 0F 84 95 00 00 00 4C 8D 81 B4 04 00 00"; +const uint VVU_OFFSET = 0; +const uint VVU_PADDING = 3; +#elif TURBO +// can't find a hook that allows you to overrides skids + +// edi has vehicle vis state ptr +// const string VEHICLE_VIS_UPDATED_HOOK_PATTERN = "8B 42 0C 8B 3C 88 F6 47 74 0F"; +// edx +// const string VEHICLE_VIS_UPDATED_HOOK_PATTERN = "8B 14 B0 8B 43 24 8B 4A 58 6B C9 4C 83 C0 40 03 C1 50"; +// const uint VVU_PADDING = 1; +// esi +// const string VEHICLE_VIS_UPDATED_HOOK_PATTERN = "8B 44 24 44 41 89 74 88 FC 89 4C 24 78 8B 4E 74 8B C1"; +// const uint VVU_PADDING = 0; +// const uint VVU_OFFSET = 0; +#else +// unsupported platform +#endif + +HookHelper@ g_VehicleUpdateHook; + +#if TMNEXT || MP4 +HookHelper@ CreateAndApplyVehicleUpdateHookHelper() { + auto hh = HookHelper( + VEHICLE_VIS_UPDATED_HOOK_PATTERN, VVU_OFFSET, VVU_PADDING, "OnVehicleUpdate" + ); + if (!hh.Apply()) return null; + return hh; +} + +void InstallVehicleUpdateHook() { + if (g_VehicleUpdateHook is null) { + @g_VehicleUpdateHook = CreateAndApplyVehicleUpdateHookHelper(); + trace('Vehicle Update hook installed: ' + (g_VehicleUpdateHook !is null)); + } +} +#endif + +#if TMNEXT +// rdx -> CSceneVehicleVisState; rdx - 0x130 -> CSceneVehicleVis +void OnVehicleUpdate(uint64 rdx) +{ + // trace('on vehicle update: ' + Text::FormatPointer(rdx)); + // sanity check pointer. The slightly odd numbers are chosen for ease of reading since `_` in numbers isn't supported. groups of 4 nibbles are used. + if (rdx < 0xff00001111 || rdx & 0x7 != 0 || rdx > 0xdddffffeeee) return; + // sanity check vehicle entity ID. + auto id = Dev::ReadUInt32(rdx); + // id & 0x06000000 == 0 means it does not start with 0x04 or 0x02 (ghosts and players respectively). id & 0xF0000000 != 0 means anything in that nibble results in a failure. + if (id != 0x0FF00000 && (id & 0x06000000 == 0 || id & 0xF0000000 != 0)) return; + // run registered callbacks. + VehicleState::RunCallbacks(id, rdx); +} +#elif MP4 +// rcx has vehicle state ptr +void OnVehicleUpdate(uint64 rcx) +{ + // trace('on vehicle update: ' + Text::FormatPointer(rcx)); + // sanity check pointer. The slightly odd numbers are chosen for ease of reading since `_` in numbers isn't supported. groups of 4 nibbles are used. + if (rcx < 0xff1111 || rcx & 0x7 != 0 || rcx > 0xffffeeee) return; + // sanity check vehicle entity ID. + auto id = Dev::ReadUInt32(rcx); + // run registered callbacks. + VehicleState::RunCallbacks(id, rcx); +} +#elif TURBO +// edi has vehicle state ptr +// turbo register names are wrong?! +// eax is edi :YEK: +// ebx is esi +// ecx is ebp +// edx is esp +// esi is edx +// edi is ecx +// ebp is ebx +// esp unknown hook register +// eip unknown hook register +void OnVehicleUpdate(uint64 esi) +{ + // can't find a hook that allows you to overrides skids + + // trace('on vehicle update: ' + Text::FormatPointer(eax) + " + 4 * " + Text::FormatPointer(ecx)); + // trace('on vehicle update eax: ' + Text::FormatPointer(eax)); + // trace('on vehicle update ebx: ' + Text::FormatPointer(ebx)); + // trace('on vehicle update ecx: ' + Text::FormatPointer(ecx)); + // trace('on vehicle update edx: ' + Text::FormatPointer(edx)); + // trace('on vehicle update esi: ' + Text::FormatPointer(esi)); + // trace('on vehicle update edi: ' + Text::FormatPointer(edi)); + // trace('on vehicle update ebp: ' + Text::FormatPointer(ebp)); + // !! not found trace('on vehicle update eip: ' + Text::FormatPointer(eip)); + // return; + + + // sanity check pointer. The slightly odd numbers are chosen for ease of reading since `_` in numbers isn't supported. groups of 4 nibbles are used. + if (esi < 0xff1111 || esi & 0x3 != 0 || esi > 0xffffeeee) return; + // sanity check vehicle entity ID. + auto id = Dev::ReadUInt32(esi); + // run registered callbacks. + VehicleState::RunCallbacks(id, esi); +} +#endif + + +#if TMNEXT || MP4 +void Main() { + startnew(InstallVehicleUpdateHook); +} +#endif diff --git a/Internal/Vehicle/VehicleNext.as b/Internal/Vehicle/VehicleNext.as index 4ac52b3..34284c4 100644 --- a/Internal/Vehicle/VehicleNext.as +++ b/Internal/Vehicle/VehicleNext.as @@ -11,7 +11,8 @@ namespace VehicleState // - ...: 0x1C8 // - 2023-03-03: 0x1E0 // - 2023-12-21: 0x210 - uint VehiclesOffset = 0x210; + // uint VehiclesOffset = 0x210; + uint VehiclesOffset = 0x1E0; uint GetPlayerVehicleID(CSmPlayer@ player) { diff --git a/StateWrappers.as b/StateWrappers.as index a69d015..654fe4e 100644 --- a/StateWrappers.as +++ b/StateWrappers.as @@ -128,6 +128,9 @@ shared class CSceneVehicleVisState #elif TURBO +const uint WheelsStartOffset = 0xFC; +const uint WheelStructLength = 0x24; + shared class CSceneVehicleVisState { CMwNod@ m_vis; @@ -157,22 +160,29 @@ shared class CSceneVehicleVisState float get_FLWheelRot() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x100); } float get_FLWheelRotSpeed() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x104); } float get_FLSteerAngle() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x108); } + bool get_FLGroundContact() { if (m_vis is null) { return false; } return Dev::GetOffsetUint32(m_vis, 0x110) == 1; } + uint get_FLGroundContactRaw() { if (m_vis is null) { return Time::Now; } return Dev::GetOffsetUint32(m_vis, 0x110); } float get_FLSlipCoef() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x114); } float get_FRWheelRot() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x124); } float get_FRWheelRotSpeed() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x128); } float get_FRSteerAngle() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x12C); } + bool get_FRGroundContact() { if (m_vis is null) { return false; } return Dev::GetOffsetUint32(m_vis, 0x134) == 1; } float get_FRSlipCoef() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x138); } float get_RLWheelRot() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x148); } float get_RLWheelRotSpeed() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x14C); } float get_RLSteerAngle() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x150); } + bool get_RLGroundContact() { if (m_vis is null) { return false; } return Dev::GetOffsetUint32(m_vis, 0x158) == 1; } float get_RLSlipCoef() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x15C); } float get_RRWheelRot() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x16C); } float get_RRWheelRotSpeed() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x170); } float get_RRSteerAngle() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x174); } + bool get_RRGroundContact() { if (m_vis is null) { return false; } return Dev::GetOffsetUint32(m_vis, 0x17C) == 1; } float get_RRSlipCoef() { if (m_vis is null) { return 0; } return Dev::GetOffsetFloat(m_vis, 0x180); } } #endif + +shared funcdef void OnVehicleStateUpdated(uint VehicleEntityId, uint64 VehicleStatePtr); From 16a23264d15bf4940868d0e8f54854ae92ab6a0f Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Fri, 5 Jan 2024 15:52:46 +1100 Subject: [PATCH 2/2] restore updated VehiclesOffset --- Internal/Vehicle/VehicleNext.as | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Internal/Vehicle/VehicleNext.as b/Internal/Vehicle/VehicleNext.as index 34284c4..165470a 100644 --- a/Internal/Vehicle/VehicleNext.as +++ b/Internal/Vehicle/VehicleNext.as @@ -11,8 +11,8 @@ namespace VehicleState // - ...: 0x1C8 // - 2023-03-03: 0x1E0 // - 2023-12-21: 0x210 - // uint VehiclesOffset = 0x210; - uint VehiclesOffset = 0x1E0; + uint VehiclesOffset = 0x210; + // uint VehiclesOffset = 0x1E0; uint GetPlayerVehicleID(CSmPlayer@ player) {