diff --git a/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedRigidbody.cs b/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedRigidbody.cs index e8dd3e747..3df37512c 100644 --- a/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedRigidbody.cs +++ b/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedRigidbody.cs @@ -149,7 +149,11 @@ protected override void OnBeforeTransmit() if (Body == null) return; Vector3 p = Body.position; Quaternion r = Body.rotation; - if (RelativeTo != null) p = RelativeTo.InverseTransformPoint(p); + if (RelativeTo != null) + { + p = RelativeTo.InverseTransformPoint(p); + r = Quaternion.Inverse(RelativeTo.rotation) * r; + } if (_posX.IsValid) LocalSet(_posX, p.x); if (_posY.IsValid) LocalSet(_posY, p.y); @@ -209,11 +213,12 @@ protected override void ApplyInterpolated() Vector3 p = Body.position; Quaternion r = Body.rotation; + bool hasSyncedPosition = _posX.IsValid; + bool hasSyncedRotation = _rotQuat.IsValid || _rotQw.IsValid || _rotEuler; if (_posX.IsValid) p.x = GetFloat(_posX); if (_posY.IsValid) p.y = GetFloat(_posY); if (_posZ.IsValid) p.z = GetFloat(_posZ); - if (RelativeTo != null && _posX.IsValid) p = RelativeTo.TransformPoint(p); if (_rotQuat.IsValid) { @@ -234,6 +239,12 @@ protected override void ApplyInterpolated() r = Quaternion.Euler(GetAngle(_eulerX), GetAngle(_eulerY), GetAngle(_eulerZ)); } + if (RelativeTo != null) + { + if (hasSyncedPosition) p = RelativeTo.TransformPoint(p); + if (hasSyncedRotation) r = RelativeTo.rotation * r; + } + if (DriveRemoteKinematic) { if (!Body.isKinematic) Body.isKinematic = true; diff --git a/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedTransform.cs b/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedTransform.cs index 1aa8cd439..bb0c9acaf 100644 --- a/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedTransform.cs +++ b/Basis/Packages/com.basis.framework/Networking/Sync/BasisSyncedTransform.cs @@ -140,7 +140,9 @@ protected virtual void Awake() if (RotationX || RotationY || RotationZ) { _rotEuler = true; - _heldEuler = WorldSpace ? Target.eulerAngles : Target.localEulerAngles; + _heldEuler = RelativeTo != null + ? (Quaternion.Inverse(RelativeTo.rotation) * Target.rotation).eulerAngles + : WorldSpace ? Target.eulerAngles : Target.localEulerAngles; if (RotationX) _eulerX = RegisterAngle(RotCompX.ToSpec(), InterpolateRotation); if (RotationY) _eulerY = RegisterAngle(RotCompY.ToSpec(), InterpolateRotation); if (RotationZ) _eulerZ = RegisterAngle(RotCompZ.ToSpec(), InterpolateRotation); @@ -188,9 +190,14 @@ protected override void OnBeforeTransmit() Vector3 p; Quaternion r; - if (WorldSpace) Target.GetPositionAndRotation(out p, out r); + if (RelativeTo != null) + { + Target.GetPositionAndRotation(out p, out r); + p = RelativeTo.InverseTransformPoint(p); + r = Quaternion.Inverse(RelativeTo.rotation) * r; + } + else if (WorldSpace) Target.GetPositionAndRotation(out p, out r); else Target.GetLocalPositionAndRotation(out p, out r); - if (RelativeTo != null) p = RelativeTo.InverseTransformPoint(Target.position); if (_posX.IsValid) LocalSet(_posX, p.x); if (_posY.IsValid) LocalSet(_posY, p.y); @@ -209,7 +216,7 @@ protected override void OnBeforeTransmit() } else if (_rotEuler) { - Vector3 e = WorldSpace ? Target.eulerAngles : Target.localEulerAngles; + Vector3 e = r.eulerAngles; if (_eulerX.IsValid) LocalSet(_eulerX, e.x); if (_eulerY.IsValid) LocalSet(_eulerY, e.y); if (_eulerZ.IsValid) LocalSet(_eulerZ, e.z); @@ -279,9 +286,7 @@ protected override void ApplyInterpolated() if (RelativeTo != null) { - Target.position = RelativeTo.TransformPoint(p); - if (WorldSpace) Target.rotation = r; - else Target.localRotation = r; + Target.SetPositionAndRotation(RelativeTo.TransformPoint(p), RelativeTo.rotation * r); } else if (WorldSpace) Target.SetPositionAndRotation(p, r); else Target.SetLocalPositionAndRotation(p, r); @@ -300,10 +305,15 @@ protected bool ComposeSyncedPose(out Vector3 p, out Quaternion r, out Vector3 s) s = Vector3.one; if (Target == null) return false; - if (WorldSpace) Target.GetPositionAndRotation(out p, out r); + if (RelativeTo != null) + { + Target.GetPositionAndRotation(out p, out r); + p = RelativeTo.InverseTransformPoint(p); + r = Quaternion.Inverse(RelativeTo.rotation) * r; + } + else if (WorldSpace) Target.GetPositionAndRotation(out p, out r); else Target.GetLocalPositionAndRotation(out p, out r); s = Target.localScale; - if (RelativeTo != null) p = RelativeTo.InverseTransformPoint(Target.position); if (_posX.IsValid) p.x = GetFloat(_posX); if (_posY.IsValid) p.y = GetFloat(_posY); @@ -354,7 +364,9 @@ protected override bool TryGetSyncGizmoSpatial(BasisSyncValues from, BasisSyncVa // Unsynced axes have no keyframe data — hold them at the Target's live value so the // from/to points sit on the real motion path. - Vector3 baseLocal = WorldSpace ? Target.position : Target.localPosition; + Vector3 baseLocal = RelativeTo != null + ? RelativeTo.InverseTransformPoint(Target.position) + : WorldSpace ? Target.position : Target.localPosition; Vector3 f = baseLocal; Vector3 t = baseLocal; if (_posX.IsValid) { int o = Schema.GetField(_posX.FieldIndex).Offset; f.x = from.Cont[o]; t.x = to.Cont[o]; } @@ -362,7 +374,12 @@ protected override bool TryGetSyncGizmoSpatial(BasisSyncValues from, BasisSyncVa if (_posZ.IsValid) { int o = Schema.GetField(_posZ.FieldIndex).Offset; f.z = from.Cont[o]; t.z = to.Cont[o]; } Transform parent = Target.parent; - if (WorldSpace || parent == null) + if (RelativeTo != null) + { + fromWorld = RelativeTo.TransformPoint(f); + toWorld = RelativeTo.TransformPoint(t); + } + else if (WorldSpace || parent == null) { fromWorld = f; toWorld = t; diff --git a/Basis/Packages/com.basis.imagepickup/BasisDesktopImageDropHook.cs b/Basis/Packages/com.basis.imagepickup/BasisDesktopImageDropHook.cs index 28f50f0fd..451b46a1f 100644 --- a/Basis/Packages/com.basis.imagepickup/BasisDesktopImageDropHook.cs +++ b/Basis/Packages/com.basis.imagepickup/BasisDesktopImageDropHook.cs @@ -21,10 +21,12 @@ public class BasisDesktopImageDropHook : MonoBehaviour private delegate bool EnumThreadWindowProc(IntPtr hWnd, IntPtr lParam); [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); + [DllImport("kernel32.dll")] private static extern void SetLastError(uint dwErrCode); [DllImport("user32.dll")] private static extern bool EnumThreadWindows(uint threadId, EnumThreadWindowProc callback, IntPtr lParam); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int index, IntPtr newLong); [DllImport("user32.dll")] private static extern IntPtr CallWindowProc(IntPtr previous, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + [DllImport("user32.dll")] private static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("shell32.dll")] private static extern void DragAcceptFiles(IntPtr hWnd, bool accept); [DllImport("shell32.dll", CharSet = CharSet.Auto)] private static extern uint DragQueryFile(IntPtr hDrop, uint file, StringBuilder buffer, uint length); [DllImport("shell32.dll")] private static extern void DragFinish(IntPtr hDrop); @@ -32,9 +34,12 @@ public class BasisDesktopImageDropHook : MonoBehaviour private static BasisDesktopImageDropHook _instance; private static readonly WndProcDelegate _wndProcDelegate = HookedWndProc; private static IntPtr _foundWindow; + private static IntPtr _activePreviousWndProc = IntPtr.Zero; + private static IntPtr _activeWindowHandle = IntPtr.Zero; private IntPtr _windowHandle = IntPtr.Zero; private IntPtr _previousWndProc = IntPtr.Zero; + private bool _hookInstalled; private readonly List _pending = new(); private readonly object _pendingLock = new(); @@ -50,19 +55,42 @@ private void OnEnable() return; } - DragAcceptFiles(_windowHandle, true); + SetLastError(0); _previousWndProc = SetWindowLongPtr(_windowHandle, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(_wndProcDelegate)); + int error = Marshal.GetLastWin32Error(); + if (_previousWndProc == IntPtr.Zero && error != 0) + { + BasisDebug.LogWarning($"Image pickup: failed to subclass the player window ({error}); file drop disabled."); + _windowHandle = IntPtr.Zero; + if (_instance == this) _instance = null; + enabled = false; + return; + } + + _hookInstalled = true; + _activeWindowHandle = _windowHandle; + _activePreviousWndProc = _previousWndProc; + DragAcceptFiles(_windowHandle, true); } private void OnDisable() { - if (_windowHandle != IntPtr.Zero && _previousWndProc != IntPtr.Zero) + if (_windowHandle != IntPtr.Zero) { - SetWindowLongPtr(_windowHandle, GWLP_WNDPROC, _previousWndProc); + if (_hookInstalled) + { + SetWindowLongPtr(_windowHandle, GWLP_WNDPROC, _previousWndProc); + } DragAcceptFiles(_windowHandle, false); } + if (_activeWindowHandle == _windowHandle) + { + _activeWindowHandle = IntPtr.Zero; + _activePreviousWndProc = IntPtr.Zero; + } _windowHandle = IntPtr.Zero; _previousWndProc = IntPtr.Zero; + _hookInstalled = false; if (_instance == this) _instance = null; } @@ -84,13 +112,27 @@ private void Update() private static IntPtr HookedWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { BasisDesktopImageDropHook instance = _instance; - if (instance == null) return IntPtr.Zero; + IntPtr previous = instance != null ? instance._previousWndProc : _activePreviousWndProc; - if (msg == WM_DROPFILES) + if (instance != null && msg == WM_DROPFILES) { - instance.CollectDroppedFiles(wParam); + try + { + instance.CollectDroppedFiles(wParam); + } + catch (Exception e) + { + BasisDebug.LogWarning($"Image pickup: file drop handling failed ({e.Message})."); + } } - return CallWindowProc(instance._previousWndProc, hWnd, msg, wParam, lParam); + return ForwardWindowMessage(previous, hWnd, msg, wParam, lParam); + } + + private static IntPtr ForwardWindowMessage(IntPtr previous, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + return previous != IntPtr.Zero + ? CallWindowProc(previous, hWnd, msg, wParam, lParam) + : DefWindowProc(hWnd, msg, wParam, lParam); } private void CollectDroppedFiles(IntPtr hDrop) diff --git a/Basis/Packages/com.basis.imagepickup/BasisImagePickupManager.cs b/Basis/Packages/com.basis.imagepickup/BasisImagePickupManager.cs index 2d78ebaee..d3826b7ff 100644 --- a/Basis/Packages/com.basis.imagepickup/BasisImagePickupManager.cs +++ b/Basis/Packages/com.basis.imagepickup/BasisImagePickupManager.cs @@ -21,6 +21,7 @@ public class BasisImagePickupManager : BasisNetworkBehaviour public static BasisImagePickupManager Instance { get; private set; } private const string FixedNetworkIdentifier = "BasisImagePickupManager"; + private const int MaxIgnoredOwnerNameBytes = 1024; private const byte OpSpawn = 1; private const byte OpChunk = 2; @@ -238,8 +239,8 @@ public override void OnDirectNetworkMessage(ushort senderId, byte[] buffer, Deli private void HandleSpawn(ushort senderId, BinaryReader reader) { Guid id = new Guid(reader.ReadBytes(16)); - ushort ownerId = reader.ReadUInt16(); - string ownerName = reader.ReadString(); + reader.ReadUInt16(); + if (!TrySkipWireString(reader, MaxIgnoredOwnerNameBytes)) return; int width = reader.ReadInt32(); int height = reader.ReadInt32(); int totalBytes = reader.ReadInt32(); @@ -266,8 +267,8 @@ private void HandleSpawn(ushort senderId, BinaryReader reader) TotalChunks = totalChunks, Width = width, Height = height, - OwnerId = ownerId, - OwnerName = ownerName, + OwnerId = senderId, + OwnerName = ResolveOwnerName(senderId), Deadline = Time.unscaledTime + BasisImagePickupSettings.InboundTransferTimeoutSeconds, Position = position, Rotation = rotation, @@ -279,15 +280,23 @@ private void HandleChunk(ushort senderId, BinaryReader reader) Guid id = new Guid(reader.ReadBytes(16)); int chunkIndex = reader.ReadInt32(); int length = reader.ReadInt32(); - byte[] data = reader.ReadBytes(length); if (!_inbound.TryGetValue(id, out InboundTransfer transfer)) return; if (transfer.Sender != senderId) return; if (chunkIndex < 0 || chunkIndex >= transfer.TotalChunks) return; - if (data.Length != length) return; + if (length < 0 || length > BasisImagePickupSettings.ChunkPayloadBytes) return; int offset = chunkIndex * BasisImagePickupSettings.ChunkPayloadBytes; - if (offset < 0 || offset + length > transfer.Buffer.Length) return; + if (offset < 0 || offset >= transfer.Buffer.Length) return; + + int expectedLength = Mathf.Min(BasisImagePickupSettings.ChunkPayloadBytes, transfer.Buffer.Length - offset); + if (length != expectedLength) return; + + long remainingBytes = reader.BaseStream.Length - reader.BaseStream.Position; + if (remainingBytes < length) return; + + byte[] data = reader.ReadBytes(length); + if (data.Length != length) return; if (!transfer.Received[chunkIndex]) { @@ -395,6 +404,45 @@ private void IncrementSenderCount(ushort sender) _imageCountBySender[sender] = count + 1; } + private static string ResolveOwnerName(ushort senderId) + { + if (BasisNetworkPlayer.GetPlayerById(senderId, out BasisNetworkPlayer player)) + { + string name = player.SafeDisplayName; + if (string.IsNullOrEmpty(name)) name = player.displayName; + if (!string.IsNullOrEmpty(name)) return name; + } + return $"Player {senderId}"; + } + + private static bool TrySkipWireString(BinaryReader reader, int maxByteLength) + { + if (!TryRead7BitEncodedInt(reader, out int byteLength)) return false; + if (byteLength < 0 || byteLength > maxByteLength) return false; + + long remainingBytes = reader.BaseStream.Length - reader.BaseStream.Position; + if (remainingBytes < byteLength) return false; + + reader.BaseStream.Position += byteLength; + return true; + } + + private static bool TryRead7BitEncodedInt(BinaryReader reader, out int value) + { + value = 0; + for (int shift = 0; shift < 35; shift += 7) + { + if (reader.BaseStream.Length - reader.BaseStream.Position < 1) return false; + + byte b = reader.ReadByte(); + if (shift == 28 && (b & 0xF0) != 0) return false; + + value |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) return true; + } + return false; + } + private void DecrementSenderCount(ushort sender) { if (_imageCountBySender.TryGetValue(sender, out int count)) diff --git a/Basis/Packages/com.basis.imagepickup/BasisImageSecurity.cs b/Basis/Packages/com.basis.imagepickup/BasisImageSecurity.cs index e3ca08ae8..02f8ab4f8 100644 --- a/Basis/Packages/com.basis.imagepickup/BasisImageSecurity.cs +++ b/Basis/Packages/com.basis.imagepickup/BasisImageSecurity.cs @@ -79,7 +79,7 @@ public static BasisImageValidationResult ValidateBytes(byte[] bytes) if (bytes.Length > BasisImagePickupSettings.MaxImageBytes) { result.Error = "Too large"; return result; } if (!TryReadPngDimensions(bytes, out int w, out int h, out string headerError)) { result.Error = headerError; return result; } if (!DimensionsWithinCaps(w, h, out string capError)) { result.Error = capError; return result; } - return BuildFromBytes(bytes, false, false); + return BuildFromBytes(bytes, true, false); } private static BasisImageValidationResult BuildFromBytes(byte[] bytes, bool reencode, bool allowDownscale)