From b9cc1b0e1b5f97c7989c7308586140807a5c46b6 Mon Sep 17 00:00:00 2001 From: Hyblocker Date: Sat, 7 Mar 2026 00:32:48 +0100 Subject: [PATCH 1/5] fix: forward head tracking data to vrcft main process --- .../Library/UnifiedLibManager.cs | 1 + .../Sandboxing/IPC/ReplyUpdatePacket.cs | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/VRCFaceTracking.Core/Library/UnifiedLibManager.cs b/VRCFaceTracking.Core/Library/UnifiedLibManager.cs index 9f6cad4b..c4ab973d 100644 --- a/VRCFaceTracking.Core/Library/UnifiedLibManager.cs +++ b/VRCFaceTracking.Core/Library/UnifiedLibManager.cs @@ -279,6 +279,7 @@ public void Initialize() { replyUpdatePacket.UpdateGlobalExpressionState(); } + replyUpdatePacket.UpdateHeadState(); } break; diff --git a/VRCFaceTracking.Core/Sandboxing/IPC/ReplyUpdatePacket.cs b/VRCFaceTracking.Core/Sandboxing/IPC/ReplyUpdatePacket.cs index e1278f1e..f4f6c0c4 100644 --- a/VRCFaceTracking.Core/Sandboxing/IPC/ReplyUpdatePacket.cs +++ b/VRCFaceTracking.Core/Sandboxing/IPC/ReplyUpdatePacket.cs @@ -29,6 +29,14 @@ internal class UpdateDataContiguous internal float Eye_Right_PupilDiameter_MM; internal float Eye_Right_Openness; + internal float Head_Yaw; + internal float Head_Pitch; + internal float Head_Roll; + + internal float Head_PosX; + internal float Head_PosY; + internal float Head_PosZ; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = EXPRESSION_COUNT)] internal float[] Expression_Shapes; } @@ -64,6 +72,14 @@ public override byte[] GetBytes() _contiguousUnifiedData.Eye_MaxDilation = UnifiedTracking.Data.Eye._maxDilation; _contiguousUnifiedData.Eye_MinDilation = UnifiedTracking.Data.Eye._minDilation; + _contiguousUnifiedData.Head_Yaw = UnifiedTracking.Data.Head.HeadPitch; + _contiguousUnifiedData.Head_Pitch = UnifiedTracking.Data.Head.HeadRoll; + _contiguousUnifiedData.Head_Roll = UnifiedTracking.Data.Head.HeadYaw; + + _contiguousUnifiedData.Head_PosX = UnifiedTracking.Data.Head.HeadPosX; + _contiguousUnifiedData.Head_PosY = UnifiedTracking.Data.Head.HeadPosY; + _contiguousUnifiedData.Head_PosZ = UnifiedTracking.Data.Head.HeadPosZ; + // Copy face tracking for ( int i = 0; i < _contiguousUnifiedData.Expression_Shapes.Length; i++ ) { @@ -153,6 +169,23 @@ public void UpdateGlobalEyeState() UnifiedTracking.Data.Eye._minDilation = _contiguousUnifiedData.Eye_MinDilation; } + public void UpdateHeadState() + { + if ( _contiguousUnifiedData.Head_Yaw != INVALID_FLOAT ) + UnifiedTracking.Data.Head.HeadYaw = _contiguousUnifiedData.Head_Yaw; + if ( _contiguousUnifiedData.Head_Pitch != INVALID_FLOAT ) + UnifiedTracking.Data.Head.HeadPitch = _contiguousUnifiedData.Head_Pitch; + if ( _contiguousUnifiedData.Head_Roll != INVALID_FLOAT ) + UnifiedTracking.Data.Head.HeadRoll = _contiguousUnifiedData.Head_Roll; + + if ( _contiguousUnifiedData.Head_PosX != INVALID_FLOAT ) + UnifiedTracking.Data.Head.HeadPosX = _contiguousUnifiedData.Head_PosX; + if ( _contiguousUnifiedData.Head_PosY != INVALID_FLOAT ) + UnifiedTracking.Data.Head.HeadPosY = _contiguousUnifiedData.Head_PosY; + if ( _contiguousUnifiedData.Head_PosZ != INVALID_FLOAT ) + UnifiedTracking.Data.Head.HeadPosZ = _contiguousUnifiedData.Head_PosZ; + } + public void UpdateGlobalExpressionState() { // Copy face tracking From 81dfa5d89a1022f08a3087f60ff4c59463c390b4 Mon Sep 17 00:00:00 2001 From: Hyblocker Date: Sat, 7 Mar 2026 00:33:14 +0100 Subject: [PATCH 2/5] fix: head tracking params are -1 to 1, not 0 to 1 --- .../Params/Data/Mutation/ParameterAdjustment.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/VRCFaceTracking.Core/Params/Data/Mutation/ParameterAdjustment.cs b/VRCFaceTracking.Core/Params/Data/Mutation/ParameterAdjustment.cs index feee07b8..374623e2 100644 --- a/VRCFaceTracking.Core/Params/Data/Mutation/ParameterAdjustment.cs +++ b/VRCFaceTracking.Core/Params/Data/Mutation/ParameterAdjustment.cs @@ -45,12 +45,12 @@ public void Reset() [MutationProperty("Tongue Directions")] public (float, float) tongueMove = new(0, 1); [MutationProperty("Tongue Miscellaneous")] public (float, float) tongueOther = new(0, 1); - [MutationProperty("Head Rotation (Side-to-Side)")] public (float, float) headRotationYaw = new(0, 1); - [MutationProperty("Head Rotation (Up-Down Tilt)")] public (float, float) headRotationPitch = new(0, 1); - [MutationProperty("Head Rotation (Side Tilt)")] public (float, float) headRotationRoll = new(0, 1); - [MutationProperty("Head Position (Side-to-Side)")] public (float, float) headPositionX = new(0, 1); - [MutationProperty("Head Position (Up-Down)")] public (float, float) headPositionY = new(0, 1); - [MutationProperty("Head Position (Forward-Back)")] public (float, float) headPositionZ = new(0, 1); + [MutationProperty("Head Rotation (Side-to-Side)")] public (float, float) headRotationYaw = new(-1, 1); + [MutationProperty("Head Rotation (Up-Down Tilt)")] public (float, float) headRotationPitch = new(-1, 1); + [MutationProperty("Head Rotation (Side Tilt)")] public (float, float) headRotationRoll = new(-1, 1); + [MutationProperty("Head Position (Side-to-Side)")] public (float, float) headPositionX = new(-1, 1); + [MutationProperty("Head Position (Up-Down)")] public (float, float) headPositionY = new(-1, 1); + [MutationProperty("Head Position (Forward-Back)")] public (float, float) headPositionZ = new(-1, 1); public override string Name => "Parameter Adjustment"; From ebfad432fba9975b6a2b5cb43b7734bce2110df0 Mon Sep 17 00:00:00 2001 From: Hyblocker Date: Sat, 7 Mar 2026 00:33:52 +0100 Subject: [PATCH 3/5] fix: actually take the sandboxing processes to agartha ongod frfr --- .../Library/UnifiedLibManager.cs | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/VRCFaceTracking.Core/Library/UnifiedLibManager.cs b/VRCFaceTracking.Core/Library/UnifiedLibManager.cs index c4ab973d..e35a9519 100644 --- a/VRCFaceTracking.Core/Library/UnifiedLibManager.cs +++ b/VRCFaceTracking.Core/Library/UnifiedLibManager.cs @@ -446,45 +446,54 @@ private bool TeardownModuleSandboxed(ModuleRuntimeInfo module) _sandboxServer.SendData(eventTeardownPacket, module.SandboxProcessPort); // Kill the update thread - module.UpdateCancellationToken.Cancel(); - if ( module.UpdateThread.IsAlive ) - { - // Edge case, we wait for the thread to finish before unloading the assembly - _logger.LogDebug("Waiting for {module}'s thread to join...", module.ModuleClassName); - module.UpdateThread.Join(); - } - + module.UpdateCancellationToken?.Cancel(); // Give the module 100ms to kill itself Thread.Sleep(100); // Only bother tearing down a module if it's actually shutdown - if ( !module.Process.HasExited ) + if ( !(module.Process?.HasExited ?? true) ) { _logger.LogDebug("Module process has not yet exited"); - // @Note: Forcefully kill the process. We'll try to kill it 1000 times and then give up. - int tries = 0; - while ( tries < 1000 ) - { - try + try { + if (!(module.Process?.WaitForExit(200) ?? false)) { - tries++; - if ( !module.Process.HasExited ) - module.Process.Kill(); - if ( module.Process.HasExited ) + _logger.LogDebug("Module {id} didn't exit gracefully. Forcing kill...", module.Process?.Id ?? -1); + module.Process?.Kill(entireProcessTree: true); + if (!(module.Process?.WaitForExit(2000) ?? false)) { - tries = int.MaxValue; - break; + // on windows we can use taskkill /F /T /PID {procId} to force kill a process very aggressively. this has a higher success rate than process.kill! + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + using var killer = Process.Start(new ProcessStartInfo + { + FileName = "taskkill", + Arguments = $"/F /T /PID {module.Process.Id}", + CreateNoWindow = true, + UseShellExecute = false + }); + killer?.WaitForExit(2000); + } else { + _logger.LogCritical("Process {id} is a zombie or stuck in Kernel I/O. Manual intervention required.", module.Process.Id); + } + return false; } - } catch ( System.ComponentModel.Win32Exception ex ) { - // Can fail to call OpenProcessEx due to some error such as ACCESS_DENIED (process has higher priveleges, eg Sraniple) - _logger.LogError($"Tried killing process with PID {module.Process.Id}. Got win32 error ({ex.ToString()}"); - } catch ( Exception ex ) { - // Tell the user why we got an exception so that we can hopefully fix it. - _logger.LogError($"Tried killing process with PID {module.Process.Id}. Got exception ({ex.HResult}) {ex.Message}"); } + } catch ( System.ComponentModel.Win32Exception ex ) { + // Can fail to call OpenProcessEx due to some error such as ACCESS_DENIED (process has higher priveleges, eg Sraniple) + _logger.LogError($"Tried killing process with PID {module.Process.Id}. Got win32 error ({ex.ToString()}"); + } catch ( Exception ex ) { + // Tell the user why we got an exception so that we can hopefully fix it. + _logger.LogError($"Tried killing process with PID {module.Process.Id}. Got exception ({ex.HResult}) {ex.Message}"); } } + if (module.UpdateThread?.IsAlive ?? false) + { + // Edge case, we wait for the thread to finish before unloading the assembly + var moduleName = module.ModuleInformation?.Name ?? module.ModuleClassName ?? "Unknown"; + _logger.LogDebug("Waiting for {module}'s thread to join...", moduleName); + module.UpdateThread?.Join(500); + } + return true; } @@ -496,6 +505,8 @@ public void TeardownAllAndResetAsync() foreach ( var module in _moduleThreads ) { var success = false; + if (module == null || (module.Process?.HasExited ?? true)) + continue; try { success = TeardownModuleSandboxed(module); @@ -511,18 +522,11 @@ public void TeardownAllAndResetAsync() _moduleThreads.Clear(); - EyeStatus = ModuleState.Uninitialized; - ExpressionStatus = ModuleState.Uninitialized; - } - - // Signal all active modules to gracefully shut down their respective runtimes - public void TeardownAllAndResetAsyncLegacy() - { - _logger.LogInformation("Tearing down all modules..."); - - foreach ( var module in _moduleThreads ) + foreach ( var module in AvailableSandboxModules ) { var success = false; + if (module == null || (module.Process?.HasExited ?? true)) // c# objects may be null, use null coalesce to detect if a module has been destroyed but we have a lingering ref to it + continue; try { success = TeardownModuleSandboxed(module); @@ -530,13 +534,14 @@ public void TeardownAllAndResetAsyncLegacy() { if ( !success ) { - _logger.LogWarning($"Module: {module.Module.ModuleInformation.Name} failed to shut down. Killing its thread."); - module.UpdateThread.Interrupt(); + var moduleName = module.ModuleInformation?.Name ?? module.ModuleClassName ?? "Unknown"; + _logger.LogWarning($"Module: {moduleName} failed to shut down. Killing its thread."); + module.UpdateThread?.Interrupt(); } } } - _moduleThreads.Clear(); + AvailableSandboxModules.Clear(); EyeStatus = ModuleState.Uninitialized; ExpressionStatus = ModuleState.Uninitialized; From 610b68900a6058a5e248b7aa9d3de6f986632de9 Mon Sep 17 00:00:00 2001 From: Hyblocker Date: Sat, 7 Mar 2026 00:41:02 +0100 Subject: [PATCH 4/5] fix: make kill all processes of name more aggressive --- VRCFaceTracking.Core/Utils.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/VRCFaceTracking.Core/Utils.cs b/VRCFaceTracking.Core/Utils.cs index af700fb7..b03a48f8 100644 --- a/VRCFaceTracking.Core/Utils.cs +++ b/VRCFaceTracking.Core/Utils.cs @@ -70,7 +70,22 @@ public static void KillAllProcessesOfName(string name) try { - proc.Kill(); + proc.Kill(entireProcessTree: true); + if (!(proc.WaitForExit(2000) ?? false)) + { + // on windows we can use taskkill /F /T /PID {procId} to force kill a process very aggressively. this has a higher success rate than process.kill! + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + using var killer = Process.Start(new ProcessStartInfo + { + FileName = "taskkill", + Arguments = $"/F /T /PID {proc.Id}", + CreateNoWindow = true, + UseShellExecute = false + }); + killer?.WaitForExit(2000); + } + } + } catch { From 290863810d9fad0342b34a7579c6d97a9bbffd2d Mon Sep 17 00:00:00 2001 From: Hyblocker Date: Sat, 7 Mar 2026 00:42:16 +0100 Subject: [PATCH 5/5] fix: compile error --- VRCFaceTracking.Core/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCFaceTracking.Core/Utils.cs b/VRCFaceTracking.Core/Utils.cs index b03a48f8..c091bd25 100644 --- a/VRCFaceTracking.Core/Utils.cs +++ b/VRCFaceTracking.Core/Utils.cs @@ -71,7 +71,7 @@ public static void KillAllProcessesOfName(string name) try { proc.Kill(entireProcessTree: true); - if (!(proc.WaitForExit(2000) ?? false)) + if (!proc.WaitForExit(2000)) { // on windows we can use taskkill /F /T /PID {procId} to force kill a process very aggressively. this has a higher success rate than process.kill! if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {