Fix NPC Vector3 argument marshalling in native proxies - #606
Fix NPC Vector3 argument marshalling in native proxies#606AgustinIbanez00 wants to merge 1 commit into
Conversation
|
Thank you! I'll do some testing and merge this soon |
|
One thing I'm slightly confused by is that the current state of the code matches what's in the open.mp SDK headers |
|
Fair thing to flag — it does match the header exactly, and that's what threw me off at first too. The catch is that the types in a What finally convinced me is that the SDK is inconsistent with itself here: Happy to write up repro steps if that helps when you test. |
Summary
Several NPC APIs that take a
Vector3input argument were unusable because of an ABI mismatch between the native proxy and the source-generated managed P/Invoke.Npc.SetPositioncrashed the server (SIGSEGV), and path/aim/shoot APIs silently received garbage coordinates. The fix is a one-word change per affected proxy insrc/sampsharp-component/proxies/api.cpp(native-only; no managed change required).Affected APIs:
INPC::setPosition(Npc.SetPosition) — crashed the server (SIGSEGV / exit 139).INPCComponent::addPointToPath(INpcService.AddPointToPath) — stored<0, 0, 0>, so an NPC driven withMoveByPathglided to the world origin instead of following the path.hasPathPointInRange,shoot,aimAt,aimAtPlayer.Closes #605
Problem
The managed side declares these methods with a by-value
Vector3parameter:The source generator emits a P/Invoke that passes
Vector3by value (a blittable 12-byte struct: threefloats). The native proxy, however, declared the parameter asconst Vector3&:Root Cause
const Vector3&lowers to a pointer (Vector3*) in the exportedextern "C"signature generated by thePROXYmacro. The managed caller pushes 12 bytes ofx, y, zwhere native expects an 8-byte pointer, and native dereferences those bytes as an address:setPosition→ dereferences a bogus pointer → segfault.addPointToPath→ reads garbage floats → stored point ends up~<0, 0, 0>.Proxies already declared by-value always worked, e.g.
PROXY(INPC, bool, move, Vector3, ...)(Npc.MoveTo). By-value is the established convention across this boundary; the affected proxies were simply the inconsistent ones.Solution
Change the affected input parameters from
const Vector3&toVector3(by value) so the native signature matches the by-value managed marshalling. The open.mp SDK methods still takeconst Vector3&; a by-valueVector3binds to aconst Vector3¶meter, so thePROXYmacro'ssubject->method(...)forwarding still compiles unchanged.Intentionally unchanged
Vector3&— the managed side marshals these asout/ref(a pointer), which correctly matches a non-const reference (getPathPoint,getNodePointPosition).ICheckpointDataBase::setPositionandIRaceCheckpointData::setNextPositionalso take aVector3&input, but they are not affected: their managed declarations useref Vector3(which marshals as a pointer) and therefore already match the nativeVector3&. They are left untouched.Validation
Verified at runtime by driving NPCs in a downstream gamemode with the rebuilt
SampSharp.so(stock managed1.0.0-prerelease2assemblies unchanged). Readback-based and reproducible:SetPosition— spawn NPC (origin<0,0,3.5>),SetPosition(target, true), readPosition:SetPosition(<1260, -2342.25, 16>)→<1260, -2342.25, 16>.AddPointToPath—CreatePath,AddPointToPath(pathId, p, range),GetPathPoint(pathId, 0, out stored, out _):stored == <0, 0, 0>.stored == pexactly (e.g.<1260.875, -2335.375, 16.066>).MoveByPathend-to-end — build a native path (hundreds of points),MoveByPath(pathId, Drive, speed, false):<0, 0, 0>.OnNPCFinishMovePathfires; arrival within 1–2 m across multiple concurrent NPCs (~2.5 km routes).Built with gcc/g++/cmake (RelWithDebInfo) per
.github/workflows/component-linux.yml.