diff --git a/Examples/OpenSeeVRMDriver.cs.meta b/Examples/OpenSeeVRMDriver.cs.meta new file mode 100644 index 0000000..1bc9f41 --- /dev/null +++ b/Examples/OpenSeeVRMDriver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 735f1f2cba9bb364892bc2c7e7a30c19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OpenSeeVRMExpression.cs.meta b/Examples/OpenSeeVRMExpression.cs.meta new file mode 100644 index 0000000..c6c0a02 --- /dev/null +++ b/Examples/OpenSeeVRMExpression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c7910e1fb62bf449920cea2fdf07326 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Licenses/IL2cppStartProcess.txt b/Licenses/IL2cppStartProcess.txt new file mode 100644 index 0000000..067496d --- /dev/null +++ b/Licenses/IL2cppStartProcess.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 June, Michał Petryka and Łukasz Jurczyk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Unity/OpenSeeJobManagement.cs b/Unity/OpenSeeJobManagement.cs index c86fa72..2ee9bff 100644 --- a/Unity/OpenSeeJobManagement.cs +++ b/Unity/OpenSeeJobManagement.cs @@ -85,7 +85,6 @@ namespace OpenSee { return AddProcess(Process.GetProcessById(processId).Handle); } - } #region Helper classes diff --git a/Unity/OpenSeeLauncher.cs b/Unity/OpenSeeLauncher.cs index 1b54f72..8f11f8b 100644 --- a/Unity/OpenSeeLauncher.cs +++ b/Unity/OpenSeeLauncher.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.IO; using System.Net; +using System.Runtime.InteropServices; using System.Text; using System.Collections; using System.Collections.Generic; @@ -11,6 +12,20 @@ using UnityEngine; namespace OpenSee { public class OpenSeeLauncher : MonoBehaviour { + #region DllImport + [DllImport("escapi_x64", CallingConvention = CallingConvention.Cdecl, EntryPoint = "countCaptureDevices")] + private static extern int countCaptureDevices_x64(); + + [DllImport("escapi_x64", CallingConvention = CallingConvention.Cdecl, EntryPoint = "getCaptureDeviceName")] + private static extern void getCaptureDeviceName_x64(int deviceno, [Out] StringBuilder namebuffer, int bufferlength); + + [DllImport("escapi_x86", CallingConvention = CallingConvention.Cdecl, EntryPoint = "countCaptureDevices")] + private static extern int countCaptureDevices_x86(); + + [DllImport("escapi_x86", CallingConvention = CallingConvention.Cdecl, EntryPoint = "getCaptureDeviceName")] + private static extern void getCaptureDeviceName_x86(int deviceno, [Out] StringBuilder namebuffer, int bufferlength); + #endregion + [Header("Settings")] [Tooltip("If this is left empty, it will default to the OpenSee component on the same game object.")] public OpenSee openSeeTarget; @@ -30,6 +45,10 @@ public class OpenSeeLauncher : MonoBehaviour { public string modelPath = "models\\"; [Tooltip("Additional options that should be passed to the face tracker.")] public List commandlineOptions = new List(new string[] { "--silent", "1", "--max-threads", "4" }); + [Tooltip("IL2CPP doesn't support Proocess.Start. When this is enabled, OpenSeeLauncher will create the tracking process by calling the necessary API functions directly through the DLLs. In this case, reading the standard output from the process will not be supported. It will also retrieve the camera list directly through the escapi DLLs, so make sure it is part of your Unity project.")] + public bool usePinvoke = false; + [Tooltip("When this is enabled, even if usePinvoke is disabled, the camera list will be retrieved through escapi DLLs directly, which can be faster. Make sure the DLLs are in your Unity project.")] + public bool usePinvokeListCameras = false; [Header("Runtime information")] [Tooltip("This field shows if the tracker is currently alive.")] public bool trackerAlive = false; @@ -38,6 +57,7 @@ public class OpenSeeLauncher : MonoBehaviour { private Process trackerProcess = null; private StringBuilder trackerSB = null; private bool dontPrintNow = false; + private System.IntPtr processHandle = System.IntPtr.Zero; private Job job = null; private bool CheckSetup(bool requireTarget) { @@ -85,9 +105,37 @@ public class OpenSeeLauncher : MonoBehaviour { return true; } + private string[] EscapiListCameras_x64() { + List cameras = new List(); + int count = countCaptureDevices_x64(); + for (int i = 0; i < count; i++) { + StringBuilder namebuffer = new StringBuilder(2048); + getCaptureDeviceName_x64(i, namebuffer, 2048); + cameras.Add(namebuffer.ToString()); + } + return cameras.ToArray(); + } + + private string[] EscapiListCameras_x86() { + List cameras = new List(); + int count = countCaptureDevices_x86(); + for (int i = 0; i < count; i++) { + StringBuilder namebuffer = new StringBuilder(2048); + getCaptureDeviceName_x86(i, namebuffer, 2048); + cameras.Add(namebuffer.ToString()); + } + return cameras.ToArray(); + } + public string[] ListCameras() { if (!CheckSetup(false)) return null; + if (usePinvoke || usePinvokeListCameras) { + if (Environment.Is64BitProcess) + return EscapiListCameras_x64(); + else + return EscapiListCameras_x86(); + } StringBuilder stringBuilder; ProcessStartInfo processStartInfo; @@ -151,16 +199,6 @@ public class OpenSeeLauncher : MonoBehaviour { if (!CheckSetup(true)) return false; - ProcessStartInfo processStartInfo; - - processStartInfo = new ProcessStartInfo(); - processStartInfo.CreateNoWindow = true; - processStartInfo.RedirectStandardOutput = true; - processStartInfo.RedirectStandardInput = true; - processStartInfo.RedirectStandardError = true; - processStartInfo.UseShellExecute = false; - processStartInfo.FileName = exePath; - List arguments = new List(); arguments.Add("--ip"); arguments.Add(ip); @@ -177,7 +215,13 @@ public class OpenSeeLauncher : MonoBehaviour { arguments.Add(argument); } string argumentString = EscapeArguments(arguments.ToArray()); - processStartInfo.Arguments = argumentString; + + if (processHandle != System.IntPtr.Zero) { + if (OpenSeeProcessInterface.Alive(processHandle)) + OpenSeeProcessInterface.TerminateProcess(processHandle, 0); + OpenSeeProcessInterface.CloseHandle(processHandle); + processHandle = System.IntPtr.Zero; + } if (trackerProcess != null && !trackerProcess.HasExited) { trackerProcess.CloseMainWindow(); @@ -190,32 +234,58 @@ public class OpenSeeLauncher : MonoBehaviour { } } - trackerSB = new StringBuilder(); - trackerProcess = new Process(); - trackerProcess.StartInfo = processStartInfo; - dontPrintNow = dontPrint; - if (!dontPrintNow) { - trackerProcess.EnableRaisingEvents = true; - trackerProcess.OutputDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) { - trackerSB.Append(e.Data); - trackerSB.Append("\n"); - }); - trackerProcess.ErrorDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) { - trackerSB.Append(e.Data); - trackerSB.Append("\n"); - }); - } - trackerProcess.Start(); - job.AddProcess(trackerProcess.Handle); - if (!dontPrintNow) { - trackerProcess.BeginOutputReadLine(); - trackerProcess.BeginErrorReadLine(); - } + if (!usePinvoke) { + ProcessStartInfo processStartInfo; + processStartInfo = new ProcessStartInfo(); + processStartInfo.CreateNoWindow = true; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardInput = true; + processStartInfo.RedirectStandardError = true; + processStartInfo.UseShellExecute = false; + processStartInfo.FileName = exePath; + processStartInfo.Arguments = argumentString; + + trackerSB = new StringBuilder(); + trackerProcess = new Process(); + trackerProcess.StartInfo = processStartInfo; + dontPrintNow = dontPrint; + if (!dontPrintNow) { + trackerProcess.EnableRaisingEvents = true; + trackerProcess.OutputDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) { + trackerSB.Append(e.Data); + trackerSB.Append("\n"); + }); + trackerProcess.ErrorDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) { + trackerSB.Append(e.Data); + trackerSB.Append("\n"); + }); + } + trackerProcess.Start(); + job.AddProcess(trackerProcess.Handle); + if (!dontPrintNow) { + trackerProcess.BeginOutputReadLine(); + trackerProcess.BeginErrorReadLine(); + } - return !trackerProcess.HasExited; + return !trackerProcess.HasExited; + } else { + string dir = Path.GetDirectoryName(exePath); + OpenSeeProcessInterface.Start(exePath, "facetracker " + argumentString, dir, true, out processHandle); + if (processHandle != System.IntPtr.Zero) { + job.AddProcess(processHandle); + return OpenSeeProcessInterface.Alive(processHandle); + } else + return false; + } } public void StopTracker() { + if (processHandle != System.IntPtr.Zero) { + if (OpenSeeProcessInterface.Alive(processHandle)) + OpenSeeProcessInterface.TerminateProcess(processHandle, 0); + OpenSeeProcessInterface.CloseHandle(processHandle); + processHandle = System.IntPtr.Zero; + } if (trackerProcess != null && !trackerProcess.HasExited) { trackerProcess.CloseMainWindow(); trackerProcess.Close(); @@ -246,11 +316,16 @@ public class OpenSeeLauncher : MonoBehaviour { } public void Update() { - if (trackerProcess != null) - trackerAlive = !trackerProcess.HasExited; - else { - trackerAlive = false; + if (processHandle != System.IntPtr.Zero) { + trackerAlive = OpenSeeProcessInterface.Alive(processHandle); return; + } else { + if (trackerProcess != null) + trackerAlive = !trackerProcess.HasExited; + else { + trackerAlive = false; + return; + } } if (dontPrintNow || trackerSB == null) return; diff --git a/Unity/OpenSeeProcessInterface.cs b/Unity/OpenSeeProcessInterface.cs new file mode 100644 index 0000000..2027b12 --- /dev/null +++ b/Unity/OpenSeeProcessInterface.cs @@ -0,0 +1,169 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace OpenSee { + +// Based on https://github.com/josh4364/IL2cppStartProcess/blob/master/StartExternalProcess.cs + +public class OpenSeeProcessInterface { + #region DllImport + [DllImport("kernel32.dll", SetLastError=true)] + private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); + const UInt32 INFINITE = 0xFFFFFFFF; + const UInt32 WAIT_ABANDONED = 0x00000080; + const UInt32 WAIT_OBJECT_0 = 0x00000000; + const UInt32 WAIT_TIMEOUT = 0x00000102; + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CreateProcessW( + string lpApplicationName, + [In] string lpCommandLine, + IntPtr procSecAttrs, + IntPtr threadSecAttrs, + bool bInheritHandles, + ProcessCreationFlags dwCreationFlags, + IntPtr lpEnvironment, + string lpCurrentDirectory, + ref STARTUPINFO lpStartupInfo, + ref PROCESS_INFORMATION lpProcessInformation + ); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool TerminateProcess(IntPtr processHandle, uint exitCode); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(ProcessAccessRights access, bool inherit, uint processId); + + [Flags] + private enum ProcessAccessRights : uint + { + PROCESS_CREATE_PROCESS = 0x0080, // Required to create a process. + PROCESS_CREATE_THREAD = 0x0002, // Required to create a thread. + PROCESS_DUP_HANDLE = 0x0040, // Required to duplicate a handle using DuplicateHandle. + PROCESS_QUERY_INFORMATION = 0x0400, // Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken, GetExitCodeProcess, GetPriorityClass, and IsProcessInJob). + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, // Required to retrieve certain information about a process (see QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted PROCESS_QUERY_LIMITED_INFORMATION. Windows Server 2003 and Windows XP/2000: This access right is not supported. + PROCESS_SET_INFORMATION = 0x0200, // Required to set certain information about a process, such as its priority class (see SetPriorityClass). + PROCESS_SET_QUOTA = 0x0100, // Required to set memory limits using SetProcessWorkingSetSize. + PROCESS_SUSPEND_RESUME = 0x0800, // Required to suspend or resume a process. + PROCESS_TERMINATE = 0x0001, // Required to terminate a process using TerminateProcess. + PROCESS_VM_OPERATION = 0x0008, // Required to perform an operation on the address space of a process (see VirtualProtectEx and WriteProcessMemory). + PROCESS_VM_READ = 0x0010, // Required to read memory in a process using ReadProcessMemory. + PROCESS_VM_WRITE = 0x0020, // Required to write to memory in a process using WriteProcessMemory. + DELETE = 0x00010000, // Required to delete the object. + READ_CONTROL = 0x00020000, // Required to read information in the security descriptor for the object, not including the information in the SACL. To read or write the SACL, you must request the ACCESS_SYSTEM_SECURITY access right. For more information, see SACL Access Right. + SYNCHRONIZE = 0x00100000, // The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. + WRITE_DAC = 0x00040000, // Required to modify the DACL in the security descriptor for the object. + WRITE_OWNER = 0x00080000, // Required to change the owner in the security descriptor for the object. + STANDARD_RIGHTS_REQUIRED = 0x000f0000, + PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF // All possible access rights for a process object. + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_INFORMATION + { + internal IntPtr hProcess; + internal IntPtr hThread; + internal uint dwProcessId; + internal uint dwThreadId; + } + + [StructLayout(LayoutKind.Sequential)] + private struct STARTUPINFO + { + internal uint cb; + internal IntPtr lpReserved; + internal IntPtr lpDesktop; + internal IntPtr lpTitle; + internal uint dwX; + internal uint dwY; + internal uint dwXSize; + internal uint dwYSize; + internal uint dwXCountChars; + internal uint dwYCountChars; + internal uint dwFillAttribute; + internal uint dwFlags; + internal ushort wShowWindow; + internal ushort cbReserved2; + internal IntPtr lpReserved2; + internal IntPtr hStdInput; + internal IntPtr hStdOutput; + internal IntPtr hStdError; + } + + [Flags] + private enum ProcessCreationFlags : uint + { + NONE = 0, + CREATE_BREAKAWAY_FROM_JOB = 0x01000000, + CREATE_DEFAULT_ERROR_MODE = 0x04000000, + CREATE_NEW_CONSOLE = 0x00000010, + CREATE_NEW_PROCESS_GROUP = 0x00000200, + CREATE_NO_WINDOW = 0x08000000, + CREATE_PROTECTED_PROCESS = 0x00040000, + CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, + CREATE_SECURE_PROCESS = 0x00400000, + CREATE_SEPARATE_WOW_VDM = 0x00000800, + CREATE_SHARED_WOW_VDM = 0x00001000, + CREATE_SUSPENDED = 0x00000004, + CREATE_UNICODE_ENVIRONMENT = 0x00000400, + DEBUG_ONLY_THIS_PROCESS = 0x00000002, + DEBUG_PROCESS = 0x00000001, + DETACHED_PROCESS = 0x00000008, + EXTENDED_STARTUPINFO_PRESENT = 0x00080000, + INHERIT_PARENT_AFFINITY = 0x00010000 + } + #endregion + + public static uint Start(string exePath, string commandline, string dir, bool hidden, out IntPtr handle) + { + handle = IntPtr.Zero; + ProcessCreationFlags flags = hidden ? ProcessCreationFlags.CREATE_NO_WINDOW : ProcessCreationFlags.NONE; + STARTUPINFO startupinfo = new STARTUPINFO + { + cb = (uint)Marshal.SizeOf() + }; + PROCESS_INFORMATION processinfo = new PROCESS_INFORMATION(); + if (!CreateProcessW(exePath, commandline, IntPtr.Zero, IntPtr.Zero, false, flags, IntPtr.Zero, dir, ref startupinfo, ref processinfo)) + { + throw new Win32Exception(); + } + handle = OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, processinfo.dwProcessId); + + return processinfo.dwProcessId; + } + + public static bool Alive(IntPtr handle) { + uint ret = WaitForSingleObject(handle, 0); + return ret == WAIT_TIMEOUT; + } + + public static int KillProcess(uint pid) + { + IntPtr handle = OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, pid); + + if (handle == IntPtr.Zero) + { + return -1; + } + if (!TerminateProcess(handle, 0)) + { + throw new Win32Exception(); + } + if (!CloseHandle(handle)) + { + throw new Win32Exception(); + } + + return 0; + } +} + +} \ No newline at end of file diff --git a/Unity/OpenSeeProcessInterface.cs.meta b/Unity/OpenSeeProcessInterface.cs.meta new file mode 100644 index 0000000..939859b --- /dev/null +++ b/Unity/OpenSeeProcessInterface.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8130804d2fedd6d47a6cf0a447058d08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity/Plugins/escapi_x64.dll b/Unity/Plugins/escapi_x64.dll new file mode 100644 index 0000000..e2860b7 Binary files /dev/null and b/Unity/Plugins/escapi_x64.dll differ diff --git a/Unity/Plugins/escapi_x64.dll.meta b/Unity/Plugins/escapi_x64.dll.meta new file mode 100644 index 0000000..172154a --- /dev/null +++ b/Unity/Plugins/escapi_x64.dll.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: dd0c04eb3445c3048bfaa964c6bcb16f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity/Plugins/escapi_x86.dll b/Unity/Plugins/escapi_x86.dll new file mode 100644 index 0000000..e6e9a07 Binary files /dev/null and b/Unity/Plugins/escapi_x86.dll differ diff --git a/Unity/Plugins/escapi_x86.dll.meta b/Unity/Plugins/escapi_x86.dll.meta new file mode 100644 index 0000000..b5dc9a7 --- /dev/null +++ b/Unity/Plugins/escapi_x86.dll.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: a504a0f747083b54c99d6e91731ccecc +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: