Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On Vehicle Updated Hook #14

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Debugger/Main.as
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +80 to +82
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I break Maniaplanet/Turbo with this change? 😅 Perhaps this would be good in its own PR if there's a compiler error on the other games.

if (UI::Button("Player nod")) {
ExploreNod(player);
}
Expand Down Expand Up @@ -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
}
}
Expand Down
5 changes: 5 additions & 0 deletions Export.as
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,9 @@ namespace VehicleState
// Get all vehicle vis states. Mostly used for debugging.
import array<CSceneVehicleVisState@> 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";
}
53 changes: 53 additions & 0 deletions Internal/AfterStateUpdated/CallbackProcessing.as
Original file line number Diff line number Diff line change
@@ -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());
}
}
7 changes: 7 additions & 0 deletions Internal/AfterStateUpdated/Cleanup.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//remove any hooks
void OnDestroyed() { _Unload(); }
void OnDisabled() { _Unload(); }
void _Unload()
{
CheckUnhookAllRegisteredHooks();
}
76 changes: 76 additions & 0 deletions Internal/AfterStateUpdated/HookHelper.as
Original file line number Diff line number Diff line change
@@ -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]();
}
}
114 changes: 114 additions & 0 deletions Internal/AfterStateUpdated/UpdatePatterns.as
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions Internal/Vehicle/VehicleNext.as
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace VehicleState
// - 2023-03-03: 0x1E0
// - 2023-12-21: 0x210
uint VehiclesOffset = 0x210;
// uint VehiclesOffset = 0x1E0;

uint GetPlayerVehicleID(CSmPlayer@ player)
{
Expand Down
10 changes: 10 additions & 0 deletions StateWrappers.as
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ shared class CSceneVehicleVisState

#elif TURBO

const uint WheelsStartOffset = 0xFC;
const uint WheelStructLength = 0x24;

shared class CSceneVehicleVisState
{
CMwNod@ m_vis;
Expand Down Expand Up @@ -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);