├── .editorconfig ├── .gitignore ├── .vsconfig ├── LICENSE ├── PRIVACY.md ├── README.md ├── VRCFaceTracking.Core ├── Assets │ └── Images │ │ ├── LogoIndicators │ │ ├── Active │ │ │ ├── Bottom.png │ │ │ └── Top.png │ │ ├── Idle │ │ │ ├── Bottom.png │ │ │ └── Top.png │ │ └── Uninitialized │ │ │ ├── Bottom.png │ │ │ └── Top.png │ │ └── VRCFT.ico ├── AvatarConfigFileParser.cs ├── Contracts │ ├── IAvatarInfo.cs │ ├── IOscTarget.cs │ ├── IParameterDefinition.cs │ └── Services │ │ ├── IDispatcherService.cs │ │ ├── IFileService.cs │ │ ├── IIdentityService.cs │ │ ├── ILibManager.cs │ │ ├── ILocalSettingsService.cs │ │ ├── IMainService.cs │ │ └── IModuleDataService.cs ├── Helpers │ ├── Json.cs │ └── LevenshteinDistance.cs ├── Library │ ├── ModuleRuntimeInfo.cs │ └── UnifiedLibManager.cs ├── MainStandalone.cs ├── Models │ ├── InstallableTrackingModule.cs │ ├── MutationConfig.cs │ ├── OscTarget.cs │ ├── ParameterDefinition │ │ ├── FileBased │ │ │ ├── AvatarConfigFile.cs │ │ │ ├── AvatarConfigFileIODef.cs │ │ │ └── AvatarConfigFileParameter.cs │ │ └── NullAvatarDef.cs │ ├── TrackingModuleMetadata.cs │ └── UnifiedMutationConfig.cs ├── OSC │ ├── DataTypes │ │ ├── BaseParameter.cs │ │ └── BinaryBaseParameter.cs │ ├── OSCMessage.cs │ ├── OSCUtils.cs │ ├── Query │ │ ├── AccessValues.cs │ │ ├── HttpHandler.cs │ │ ├── OscQueryAvatarInfo.cs │ │ ├── OscQueryHostInfo.cs │ │ ├── OscQueryNode.cs │ │ ├── OscQueryParameterDef.cs │ │ └── OscQueryRoot.cs │ └── fti_osc.cs ├── OscQueryConfigParser.cs ├── Params │ ├── Data │ │ ├── Mutation │ │ │ ├── Calibration.cs │ │ │ ├── Correctors.cs │ │ │ ├── Filter.cs │ │ │ ├── ParameterAdjustment.cs │ │ │ ├── TrackingMutation.cs │ │ │ └── UI │ │ │ │ ├── MutationAttributes.cs │ │ │ │ ├── MutationComponents.cs │ │ │ │ └── MutationFactory.cs │ │ ├── UnifiedData.cs │ │ └── UnifiedTrackingMutator.cs │ ├── DataTypes │ │ └── ParamContainers.cs │ ├── Expressions │ │ ├── Legacy │ │ │ ├── Eye │ │ │ │ └── EyeTrackingParams.cs │ │ │ └── Lip │ │ │ │ ├── LipShapeConversion.cs │ │ │ │ ├── LipShapeMerger.cs │ │ │ │ └── UnifiedSRanMapper.cs │ │ ├── UnifiedExpressions.cs │ │ ├── UnifiedExpressionsParameters.cs │ │ ├── UnifiedHeadParameters.cs │ │ └── UnifiedSimpleExpressions.cs │ └── Parameter.cs ├── Redirectors.cs ├── Services │ ├── FileService.cs │ ├── LogFile.cs │ ├── ModuleInstaller.cs │ ├── OscQueryService.cs │ ├── OscRecvService.cs │ ├── OscSendService.cs │ └── ParameterSenderService.cs ├── Types │ ├── ImageData.cs │ ├── Vector2.cs │ ├── Vector3.cs │ ├── Vector4.cs │ └── XYParam.cs ├── UnifiedTracking.cs ├── Utils.cs ├── VRCFaceTracking.Core.csproj ├── VRChat.cs ├── Validation │ └── ValidIpAddressAttribute.cs ├── app.config └── mDNS │ ├── AdvertisedService.cs │ ├── DNSPacket.cs │ ├── DNSQuestion.cs │ ├── DNSResource.cs │ ├── DNSSerializer.cs │ ├── MulticastDnsService.cs │ ├── Parsing │ ├── BigReader.cs │ └── BigWriter.cs │ └── Types │ ├── ARecord.cs │ ├── NSRecord.cs │ ├── PTRRecord.cs │ ├── SRVRecord.cs │ └── TXTRecord.cs ├── VRCFaceTracking.SDK ├── ExtTrackingModule.cs ├── ModuleMetadata.cs ├── ModuleState.cs └── VRCFaceTracking.SDK.csproj ├── VRCFaceTracking.sln ├── VRCFaceTracking ├── Activation │ ├── ActivationHandler.cs │ ├── DefaultActivationHandler.cs │ └── IActivationHandler.cs ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── BadgeLogo.scale-100.png │ ├── BadgeLogo.scale-125.png │ ├── BadgeLogo.scale-150.png │ ├── BadgeLogo.scale-200.png │ ├── BadgeLogo.scale-400.png │ ├── LargeTile.scale-100.png │ ├── LargeTile.scale-125.png │ ├── LargeTile.scale-150.png │ ├── LargeTile.scale-200.png │ ├── LargeTile.scale-400.png │ ├── SmallTile.scale-100.png │ ├── SmallTile.scale-125.png │ ├── SmallTile.scale-150.png │ ├── SmallTile.scale-200.png │ ├── SmallTile.scale-400.png │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-100.png │ ├── Square150x150Logo.scale-125.png │ ├── Square150x150Logo.scale-150.png │ ├── Square150x150Logo.scale-200.png │ ├── Square150x150Logo.scale-400.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-16.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-24.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-256.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-32.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-48.png │ ├── Square44x44Logo.altform-unplated_targetsize-16.png │ ├── Square44x44Logo.altform-unplated_targetsize-256.png │ ├── Square44x44Logo.altform-unplated_targetsize-32.png │ ├── Square44x44Logo.altform-unplated_targetsize-48.png │ ├── Square44x44Logo.scale-100.png │ ├── Square44x44Logo.scale-125.png │ ├── Square44x44Logo.scale-150.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.scale-400.png │ ├── Square44x44Logo.targetsize-16.png │ ├── Square44x44Logo.targetsize-24.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── Square44x44Logo.targetsize-256.png │ ├── Square44x44Logo.targetsize-32.png │ ├── Square44x44Logo.targetsize-48.png │ ├── StoreLogo.scale-100.png │ ├── StoreLogo.scale-125.png │ ├── StoreLogo.scale-150.png │ ├── StoreLogo.scale-200.png │ ├── StoreLogo.scale-400.png │ ├── Wide310x150Logo.scale-100.png │ ├── Wide310x150Logo.scale-125.png │ ├── Wide310x150Logo.scale-150.png │ ├── Wide310x150Logo.scale-200.png │ ├── Wide310x150Logo.scale-400.png │ └── WindowIcon.ico ├── Behaviors │ ├── NavigationViewHeaderBehavior.cs │ └── NavigationViewHeaderMode.cs ├── BundleArtifacts │ ├── VRCFaceTracking_x64.appinstaller.xml │ └── artifact.cat ├── Contracts │ ├── Services │ │ ├── IActivationService.cs │ │ ├── INavigationService.cs │ │ ├── INavigationViewService.cs │ │ ├── IPageService.cs │ │ └── IThemeSelectorService.cs │ └── ViewModels │ │ └── INavigationAware.cs ├── Helpers │ ├── ComponentSelection.cs │ ├── EmptyCollectionToVisibilityConverter.cs │ ├── EnumToBooleanConverter.cs │ ├── EnumToStringConverter.cs │ ├── FrameExtensions.cs │ ├── NavigationHelper.cs │ ├── ResourceExtensions.cs │ ├── RuntimeHelper.cs │ ├── SettingsStorageExtensions.cs │ └── StreamToBitmapConverter.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Models │ ├── GithubContributor.cs │ ├── LocalSettingsOptions.cs │ └── RatingObject.cs ├── Package.appinstaller ├── Package.appxmanifest ├── Properties │ ├── PublishProfiles │ │ ├── win10-x64.pubxml │ │ └── win10-x86.pubxml │ └── launchsettings.json ├── README.md ├── Services │ ├── ActivationService.cs │ ├── DispatcherService.cs │ ├── GithubService.cs │ ├── IdentityService.cs │ ├── LocalSettingsService.cs │ ├── LogFileProvider.cs │ ├── LoggingService.cs │ ├── ModuleDataService.cs │ ├── NavigationService.cs │ ├── NavigationViewService.cs │ ├── OutputLogProvider.cs │ ├── PageService.cs │ └── ThemeSelectorService.cs ├── Strings │ ├── en │ │ └── Resources.resw │ ├── es │ │ └── Resources.resw │ ├── ja │ │ └── Resources.resw │ ├── pl │ │ └── Resources.resw │ └── zh-cn │ │ └── Resources.resw ├── Styles │ ├── FontSizes.xaml │ ├── TextBlock.xaml │ └── Thickness.xaml ├── TemplateStudio.xml ├── Usings.cs ├── VRCFaceTracking.csproj ├── ViewModels │ ├── MainViewModel.cs │ ├── ModuleRegistryViewModel.cs │ ├── MutatorViewModel.cs │ ├── OutputViewModel.cs │ ├── ParameterViewModel.cs │ ├── ParametersViewModel.cs │ ├── RiskySettingsViewModel.cs │ ├── SettingsViewModel.cs │ └── ShellViewModel.cs ├── Views │ ├── AvatarInfoControl.xaml │ ├── AvatarInfoControl.xaml.cs │ ├── MainPage.xaml │ ├── MainPage.xaml.cs │ ├── ModuleRegistryDetailControl.xaml │ ├── ModuleRegistryDetailControl.xaml.cs │ ├── ModuleRegistryPage.xaml │ ├── ModuleRegistryPage.xaml.cs │ ├── MutatorPage.xaml │ ├── MutatorPage.xaml.cs │ ├── OutputPage.xaml │ ├── OutputPage.xaml.cs │ ├── ParameterDebugUserControl.xaml │ ├── ParameterDebugUserControl.xaml.cs │ ├── ParametersPage.xaml │ ├── ParametersPage.xaml.cs │ ├── SettingsPage.xaml │ ├── SettingsPage.xaml.cs │ ├── ShellPage.xaml │ ├── ShellPage.xaml.cs │ ├── WarningBlock.xaml │ └── WarningBlock.xaml.cs ├── app.manifest └── appsettings.json ├── fti_osc.dll ├── fti_osc.dylib ├── fti_osc.so └── nuget.config /.gitignore: -------------------------------------------------------------------------------- 1 | # VSCode Files 2 | .vscode/ 3 | 4 | # Visual Studio files 5 | .vs/ 6 | bin/ 7 | obj/ 8 | *.exe 9 | *.pdb 10 | *.dll 11 | *.user 12 | *.suo 13 | *.cache 14 | *.log 15 | *.vspscc 16 | *.vssscc 17 | *.psess 18 | *.dbmdl 19 | *.dbproj* 20 | *.VC.db 21 | *.csproj.user 22 | *.bak 23 | *.cache 24 | *.sln.docstates 25 | *.sln.ide-shadows 26 | [Tt]humbs.db 27 | *.dbmdl 28 | *.dbproj* 29 | *.VC.db 30 | *.csproj.user 31 | *.bak 32 | *.cache 33 | *.sln.docstates 34 | *.sln.ide-shadows 35 | [Tt]humbs.db 36 | 37 | # JetBrains Rider files 38 | .idea/ 39 | *.iml 40 | *.ipr 41 | *.iws 42 | .idea_modules/ 43 | 44 | # Build results 45 | bin/ 46 | obj/ 47 | 48 | # Ignore files in the VRCFaceTracking-Next folder 49 | /VRCFaceTracking/bin/ 50 | /VRCFaceTracking/obj/ 51 | 52 | # Ignore files in the VRCFaceTracking-Next.Core folder 53 | /VRCFaceTracking.Core/bin/ 54 | /VRCFaceTracking.Core/obj/ 55 | -------------------------------------------------------------------------------- /.vsconfig: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "components": [ 4 | "Microsoft.Component.MSBuild", 5 | "Microsoft.NetCore.Component.Runtime.7.0", 6 | "Microsoft.NetCore.Component.SDK", 7 | "Microsoft.VisualStudio.Component.ManagedDesktop.Core", 8 | "Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites", 9 | "Microsoft.VisualStudio.Component.NuGet", 10 | "Microsoft.VisualStudio.Component.Windows10SDK.19041", 11 | "Microsoft.VisualStudio.Component.Windows10SDK", 12 | "Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging", 13 | "Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs", 14 | "Microsoft.VisualStudio.Workload.ManagedDesktop" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # VRCFaceTracking Privacy Policy 2 | 3 | At VRCFaceTracking, we take your privacy seriously. This privacy policy explains how we collect, use, and protect the information you provide when using our VRCFaceTracking application. 4 | 5 | ## Information Collection and Use 6 | We do not actively collect or store any personal user data within our application. However, we use a third-party error tracking service called Sentry to help diagnose and fix issues that may arise. 7 | 8 | ### Sentry Data Collection 9 | Sentry automatically collects and transmits certain data to its servers for error diagnosis and basic usage analytics purposes. The types of data that may be collected and sent to Sentry include: 10 | 11 | - **Error Data**: Details about errors or exceptions that occur in the application, such as stack traces, error messages, and environment information. 12 | - **Application Data**: Information about the application itself, such as the version, environment variables, and other metadata. 13 | - **Network Data**: In rare cases, when a DNS parsing error occurs, the packet that caused the error may be sent to Sentry for diagnosis. This packet may contain data about your local network, such as device names, but no sensitive data beyond that. 14 | - **Avatar Parameter Data**: In some cases, when a parsing error related to avatar parameters occurs, data about the avatar parameters may be sent to Sentry for diagnosis purposes. 15 | 16 | Sentry does not collect any personal or identifiable user data from our application. However, please review Sentry's [Privacy Policy](https://sentry.io/privacy/) for more information on how they handle the data they receive. 17 | 18 | **Note**: The collection of network and avatar parameter data is a temporary measure to help diagnose and resolve early issues with our application. As these issues are addressed, we plan to update this privacy policy to remove the collection of such data. 19 | 20 | ## Data Use 21 | Any data collected by Sentry is used solely for the purpose of error diagnosis, fixing bugs, and improving the performance and stability of our VRCFaceTracking application. We do not share or sell this data with any third parties for marketing or advertising purposes. 22 | 23 | ## Data Security 24 | We take reasonable measures to protect the data collected by Sentry from unauthorized access, alteration, or destruction. However, no method of transmission over the internet or electronic storage is 100% secure. 25 | 26 | ## Changes to This Privacy Policy 27 | We may update this privacy policy from time to time. We encourage you to review this policy periodically for any changes. 28 | 29 | ## Contact Us 30 | If you have any questions or concerns about this privacy policy, please contact us at benaclejames@gmail.com. 31 | 32 | Last updated: 10th April 2024 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👀 VRCFaceTracking 2 | 3 | Provides eye tracking and lip tracking in VRChat by providing a bridge between your tracking hardware and VRChat's OSC server. 4 | ## [Get started here!](https://docs.vrcft.io/docs/intro/getting-started) 5 | 6 | [![Discord](https://discord.com/api/guilds/849300336128032789/widget.png)](https://discord.com/invite/vrcft) 7 | 8 | ## 🎥 Demo 9 | 10 | [![](https://i.imgur.com/iQkw12C.jpg)](https://youtu.be/ZTVnh8aaf9U) 11 | 12 | ## 🛠 Avatar Setup 13 | 14 | For this app to work, you'll need to be using an avatar with the correct parameters or an avatar config file with the correct mappings. The system is designed to control your avatar's eyes and lips via simple blend states but what the parameters control is completely up to you. 15 | 16 | ### [List of Parameters](https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/parameters/) 17 | 18 | ## 👀 [Eye Parameters](https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/parameters/#eye-tracking-parameters) 19 | 20 | ### [Eye Tracking Setup Guide](https://github.com/benaclejames/VRCFaceTracking/wiki/Eye-Tracking-Setup) 21 | 22 | It's not required to use all of these parameters. In fact, you don't need to use any of them if you intend on using VRChat's built-in eye tracking system. Similar to the setup of parameters with Unity Animation Controllers, these are all case-sensitive and must be copied **EXACTLY** as shown into your Avatar's base parameters. A typical setup might look something like this:
23 | ![](https://i.imgur.com/kfJD1Bl.png) 24 | 25 | We strongly encourage you to [consult the docs](https://docs.vrcft.io) for a setup guide and more info as to what each parameter does 26 | 27 | ## :lips: [Lip and Face Parameters](https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/parameters/#expression-tracking-parameters) 28 | 29 | There are a large number of parameters you can use for lip and face tracking. 30 | 31 | ### [Combined Lip Parameters](https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/parameters/#addtional-simplified-tracking-parameters) - Combined parameters to group mutually exclusive face shapes. 32 | 33 | ## ⛓ External Modules 34 | 35 | Use the module registry to download addons and add support for your hardware! 36 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Assets/Images/LogoIndicators/Active/Bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking.Core/Assets/Images/LogoIndicators/Active/Bottom.png -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Assets/Images/LogoIndicators/Active/Top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking.Core/Assets/Images/LogoIndicators/Active/Top.png -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Assets/Images/LogoIndicators/Idle/Bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking.Core/Assets/Images/LogoIndicators/Idle/Bottom.png -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Assets/Images/LogoIndicators/Idle/Top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking.Core/Assets/Images/LogoIndicators/Idle/Top.png -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Assets/Images/LogoIndicators/Uninitialized/Bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking.Core/Assets/Images/LogoIndicators/Uninitialized/Bottom.png -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Assets/Images/LogoIndicators/Uninitialized/Top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking.Core/Assets/Images/LogoIndicators/Uninitialized/Top.png -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Assets/Images/VRCFT.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking.Core/Assets/Images/VRCFT.ico -------------------------------------------------------------------------------- /VRCFaceTracking.Core/AvatarConfigFileParser.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Microsoft.Extensions.Logging; 3 | using VRCFaceTracking.Core.Contracts; 4 | using VRCFaceTracking.Core.Models.ParameterDefinition.FileBased; 5 | using VRCFaceTracking.Core.Params; 6 | using VRCFaceTracking.Core.Services; 7 | 8 | namespace VRCFaceTracking.Core; 9 | 10 | /// 11 | /// ConfigParser is responsible for parsing the traditional JSON OSC config that VRChat produces 12 | /// 13 | public class AvatarConfigParser 14 | { 15 | private readonly ILogger _logger; 16 | 17 | public AvatarConfigParser(ILogger parserLogger) 18 | { 19 | _logger = parserLogger; 20 | } 21 | 22 | public async Task<(IAvatarInfo avatarInfo, List relevantParameters)?> ParseAvatar(string newId) 23 | { 24 | if (string.IsNullOrEmpty(newId)) 25 | { 26 | return null; 27 | } 28 | 29 | var paramList = new List(); 30 | 31 | /*if (newId.StartsWith("local:")) 32 | { 33 | foreach (var parameter in UnifiedTracking.AllParameters_v2.Concat(UnifiedTracking.AllParameters_v1).ToArray()) 34 | { 35 | paramList.AddRange(parameter.ResetParam(Array.Empty())); 36 | } 37 | 38 | // This is a local test avatar, there won't be a config file for it so assume we're using no parameters and just return 39 | _lastAvatarId = newId; 40 | return (new NullAvatarDef(newId.Substring(10), newId), paramList); 41 | }*/ 42 | 43 | AvatarConfigFile avatarConfig = null; 44 | foreach (var userFolder in Directory.GetDirectories(VRChat.VRCOSCDirectory) 45 | .Where(folder => Directory.Exists(Path.Combine(folder, "Avatars")))) 46 | { 47 | foreach (var avatarFile in Directory.GetFiles(Path.Combine(userFolder, "Avatars"))) 48 | { 49 | var configText = await File.ReadAllTextAsync(avatarFile); 50 | var tempConfig = JsonSerializer.Deserialize(configText); 51 | if (tempConfig == null || tempConfig.id != newId) 52 | { 53 | continue; 54 | } 55 | 56 | avatarConfig = tempConfig; 57 | break; 58 | } 59 | } 60 | 61 | if (avatarConfig == null) 62 | { 63 | _logger.LogError("Avatar config file for {avatarId} not found", newId); 64 | return null; 65 | } 66 | 67 | _logger.LogInformation("Parsing config file for avatar: {avatarName}", avatarConfig.name); 68 | ParameterSenderService.Clear(); 69 | var parameters = avatarConfig.parameters.Where(param => param.input != null).ToArray(); 70 | 71 | foreach (var parameter in UnifiedTracking.AllParameters) 72 | { 73 | paramList.AddRange(parameter.ResetParam(parameters)); 74 | } 75 | 76 | //_lastAvatarId = newId; 77 | return (avatarConfig, paramList); 78 | } 79 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/IAvatarInfo.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Contracts; 2 | 3 | public interface IAvatarInfo 4 | { 5 | public string Name { get; } 6 | public string Id { get; } 7 | public IParameterDefinition[] Parameters { get; } 8 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/IOscTarget.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace VRCFaceTracking.Core.Contracts; 4 | 5 | public interface IOscTarget : INotifyPropertyChanged 6 | { 7 | public bool IsConnected 8 | { 9 | get; 10 | set; 11 | } 12 | 13 | public int InPort 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | public int OutPort 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | public string DestinationAddress 26 | { 27 | get; 28 | set; 29 | } 30 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/IParameterDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Contracts; 2 | 3 | public interface IParameterDefinition 4 | { 5 | public string Address { get; } 6 | public string Name { get; } 7 | 8 | public Type Type { get; } 9 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/Services/IDispatcherService.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Contracts.Services; 2 | 3 | // Simple interface to allow for easy mocking of the DispatcherService from the Core project 4 | // allowing us to invoke actions on the UI thread from the Core project. 5 | public interface IDispatcherService 6 | { 7 | public void Run(Action action); 8 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/Services/IFileService.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Contracts.Services; 2 | 3 | public interface IFileService 4 | { 5 | T Read(string folderPath, string fileName); 6 | 7 | Task Save(string folderPath, string fileName, T content); 8 | 9 | void Delete(string folderPath, string fileName); 10 | } 11 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/Services/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Contracts.Services; 2 | 3 | public interface IIdentityService 4 | { 5 | string GetUniqueUserId(); 6 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/Services/ILibManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace VRCFaceTracking.Core.Contracts.Services; 4 | 5 | public interface ILibManager 6 | { 7 | public ObservableCollection LoadedModulesMetadata { get; set; } 8 | public void Initialize(); 9 | void TeardownAllAndResetAsync(); 10 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/Services/ILocalSettingsService.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Contracts.Services; 2 | 3 | public class SavedSettingAttribute : Attribute 4 | { 5 | private readonly string _settingName; 6 | private readonly object? _defaultValue; 7 | private readonly bool _forceLocal; 8 | 9 | public SavedSettingAttribute(string settingName, object? defaultValue = default, bool forceLocal = false) 10 | { 11 | _settingName = settingName; 12 | _defaultValue = defaultValue; 13 | _forceLocal = forceLocal; 14 | } 15 | 16 | public string GetName() => _settingName; 17 | public object? Default() => _defaultValue; 18 | public bool ForceLocal() => _forceLocal; 19 | } 20 | 21 | public interface ILocalSettingsService 22 | { 23 | Task ReadSettingAsync(string key, T? defaultValue = default, bool forceLocal = false); 24 | 25 | Task SaveSettingAsync(string key, T value, bool forceLocal = false); 26 | 27 | Task Save(object target); 28 | Task Load(object target); 29 | } 30 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/Services/IMainService.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Contracts.Services; 2 | public interface IMainService 3 | { 4 | Action ParameterUpdate { get; set; } 5 | 6 | Task Teardown(); 7 | Task InitializeAsync(); 8 | } 9 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Contracts/Services/IModuleDataService.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Models; 2 | 3 | namespace VRCFaceTracking.Core.Contracts.Services; 4 | 5 | public interface IModuleDataService 6 | { 7 | Task> GetRemoteModules(); 8 | Task GetMyRatingAsync(TrackingModuleMetadata moduleMetadata); 9 | Task SetMyRatingAsync(TrackingModuleMetadata moduleMetadata, int rating); 10 | IEnumerable GetInstalledModules(); 11 | Task IncrementDownloadsAsync(TrackingModuleMetadata moduleMetadata); 12 | IEnumerable GetLegacyModules(); 13 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Helpers/Json.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace VRCFaceTracking.Core.Helpers; 4 | 5 | public static class Json 6 | { 7 | public static async Task ToObjectAsync(string value) => 8 | await Task.Run(() => JsonConvert.DeserializeObject(value)); 9 | 10 | public static async Task StringifyAsync(object value) => 11 | await Task.Run(() => JsonConvert.SerializeObject(value)); 12 | } 13 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Helpers/LevenshteinDistance.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Helpers; 2 | 3 | // Yoinked from https://gist.github.com/Davidblkx/e12ab0bb2aff7fd8072632b396538560 4 | public static class LevenshteinDistance 5 | { 6 | /// 7 | /// Calculate the difference between 2 strings using the Levenshtein distance algorithm 8 | /// 9 | /// First string 10 | /// Second string 11 | /// 12 | public static int Calculate(string source1, string source2) //O(n*m) 13 | { 14 | var source1Length = source1.Length; 15 | var source2Length = source2.Length; 16 | 17 | var matrix = new int[source1Length + 1, source2Length + 1]; 18 | 19 | // First calculation, if one entry is empty return full length 20 | if (source1Length == 0) 21 | return source2Length; 22 | 23 | if (source2Length == 0) 24 | return source1Length; 25 | 26 | // Initialization of matrix with row size source1Length and columns size source2Length 27 | for (var i = 0; i <= source1Length; matrix[i, 0] = i++) 28 | { 29 | } 30 | 31 | for (var j = 0; j <= source2Length; matrix[0, j] = j++) 32 | { 33 | } 34 | 35 | // Calculate rows and collumns distances 36 | for (var i = 1; i <= source1Length; i++) 37 | { 38 | for (var j = 1; j <= source2Length; j++) 39 | { 40 | var cost = (source2[j - 1] == source1[i - 1]) ? 0 : 1; 41 | 42 | matrix[i, j] = Math.Min( 43 | Math.Min(matrix[i - 1, j] + 1, matrix[i, j - 1] + 1), 44 | matrix[i - 1, j - 1] + cost); 45 | } 46 | } 47 | 48 | // return result 49 | return matrix[source1Length, source2Length]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Library/ModuleRuntimeInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Loader; 2 | 3 | namespace VRCFaceTracking.Core.Library; 4 | 5 | public struct ModuleRuntimeInfo 6 | { 7 | public ExtTrackingModule Module; 8 | public AssemblyLoadContext AssemblyLoadContext; 9 | public CancellationTokenSource UpdateCancellationToken; 10 | public Thread UpdateThread; 11 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/MainStandalone.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Microsoft.Extensions.Logging; 3 | using VRCFaceTracking.Core.Contracts.Services; 4 | using VRCFaceTracking.Core.Library; 5 | using VRCFaceTracking.Core.Params.Data; 6 | 7 | [assembly: TypeForwardedTo(typeof(VRCFaceTracking.ExtTrackingModule))] 8 | [assembly: TypeForwardedTo(typeof(VRCFaceTracking.ModuleMetadata))] 9 | [assembly: TypeForwardedTo(typeof(ModuleState))] 10 | 11 | namespace VRCFaceTracking.Core; 12 | 13 | public class MainStandalone : IMainService 14 | { 15 | private readonly ILogger _logger; 16 | private readonly ILibManager _libManager; 17 | private readonly UnifiedTrackingMutator _mutator; 18 | 19 | public Action ParameterUpdate { get; set; } = (_, _) => { }; 20 | 21 | public MainStandalone( 22 | ILogger logger, 23 | ILibManager libManager, 24 | UnifiedTrackingMutator mutator 25 | ) 26 | { 27 | _logger = logger; 28 | _libManager = libManager; 29 | _mutator = mutator; 30 | } 31 | 32 | public async Task Teardown() 33 | { 34 | _logger.LogInformation("VRCFT Standalone Exiting!"); 35 | await _mutator.Save(); 36 | 37 | _libManager.TeardownAllAndResetAsync(); 38 | 39 | if (OperatingSystem.IsWindows()) 40 | { 41 | _logger.LogDebug("Resetting our time end period..."); 42 | var timeEndRes = Utils.TimeEndPeriod(1); 43 | if (timeEndRes != 0) 44 | { 45 | _logger.LogWarning($"TimeEndPeriod failed with HRESULT {timeEndRes}"); 46 | } 47 | } 48 | 49 | _logger.LogDebug("Teardown complete. Awaiting exit..."); 50 | } 51 | 52 | public Task InitializeAsync() 53 | { 54 | VRChat.EnsureVRCOSCDirectory(); 55 | 56 | // Ensure OSC is enabled 57 | var isWindows = OperatingSystem.IsWindows(); 58 | 59 | if (isWindows && VRChat.ForceEnableOsc()) // If osc was previously not enabled 60 | { 61 | _logger.LogWarning("VRCFT detected OSC was disabled and automatically enabled it."); 62 | // If we were launched after VRChat 63 | if (VRChat.IsVrChatRunning()) 64 | _logger.LogError( 65 | "However, VRChat was running while this change was made.\n" + 66 | "If parameters do not update, please restart VRChat or manually enable OSC yourself in your avatar's expressions menu."); 67 | } 68 | 69 | _mutator.Load(); 70 | 71 | // Begin main OSC update loop 72 | _logger.LogDebug("Starting OSC update loop..."); 73 | 74 | if (isWindows) 75 | { 76 | Utils.TimeBeginPeriod(1); 77 | } 78 | 79 | return Task.CompletedTask; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/InstallableTrackingModule.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace VRCFaceTracking.Core.Models; 4 | 5 | public enum InstallState 6 | { 7 | NotInstalled, 8 | Installed, 9 | Outdated, 10 | AwaitingRestart 11 | } 12 | 13 | public class InstallableTrackingModule : TrackingModuleMetadata 14 | { 15 | public InstallState InstallationState 16 | { 17 | get; set; 18 | } 19 | 20 | [JsonIgnore] 21 | public string AssemblyLoadPath 22 | { 23 | get; set; 24 | } 25 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/MutationConfig.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Models; 2 | 3 | public struct MutationConfig 4 | { 5 | public string Name; 6 | public float Ceil; // The maximum that the parameter reaches. 7 | public float Floor; // the minimum that the parameter reaches. 8 | //public float SigmoidMult; // How much should this parameter be affected by the sigmoid function. This makes the parameter act more like a toggle. 9 | //public float LogitMult; // How much should this parameter be affected by the logit (inverse of sigmoid) function. This makes the parameter act more within the normalized range. 10 | public float SmoothnessMult; // How much should this parameter be affected by the smoothing function. 11 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/OscTarget.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using VRCFaceTracking.Core.Contracts; 3 | using VRCFaceTracking.Core.Contracts.Services; 4 | using VRCFaceTracking.Core.Validation; 5 | 6 | namespace VRCFaceTracking.Core.Models; 7 | 8 | public partial class OscTarget : ObservableValidator, IOscTarget 9 | { 10 | [ObservableProperty] private bool _isConnected; 11 | 12 | [ObservableProperty] [property: SavedSetting("OSCInPort", 9001)] 13 | 14 | private int _inPort; 15 | 16 | [ObservableProperty] [property: SavedSetting("OSCOutPort", 9000)] 17 | 18 | private int _outPort; 19 | 20 | [ObservableProperty] 21 | [NotifyPropertyChangedFor(nameof(InPort))] [NotifyPropertyChangedFor(nameof(OutPort))] 22 | 23 | [property: SavedSetting("OSCAddress", "127.0.0.1")] 24 | [ValidIpAddress] 25 | private string _destinationAddress; 26 | 27 | public OscTarget(ILocalSettingsService localSettingsService) 28 | { 29 | PropertyChanged += (_, _) => localSettingsService.Save(this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/ParameterDefinition/FileBased/AvatarConfigFile.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using VRCFaceTracking.Core.Contracts; 3 | using VRCFaceTracking.Core.Models.Osc.FileBased; 4 | 5 | namespace VRCFaceTracking.Core.Models.ParameterDefinition.FileBased; 6 | 7 | public class AvatarConfigFile : IAvatarInfo 8 | { 9 | public string id { get; set; } 10 | public string name { get; set; } 11 | public AvatarConfigFileParameter[] parameters { get; set; } 12 | 13 | [JsonIgnore] public string Name => name; 14 | 15 | [JsonIgnore] public string Id => id; 16 | 17 | [JsonIgnore] public IParameterDefinition[] Parameters => parameters; 18 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/ParameterDefinition/FileBased/AvatarConfigFileIODef.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using VRCFaceTracking.OSC; 3 | 4 | namespace VRCFaceTracking.Core.Models.Osc.FileBased; 5 | 6 | public class AvatarConfigFileIODef 7 | { 8 | public string address { get; set; } 9 | public string type { get; set; } 10 | 11 | [JsonIgnore] 12 | public Type Type => OscUtils.TypeConversions.Where(conversion => conversion.Value.configType == type).Select(conversion => conversion.Key).FirstOrDefault().Item1; 13 | 14 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/ParameterDefinition/FileBased/AvatarConfigFileParameter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using VRCFaceTracking.Core.Contracts; 3 | using VRCFaceTracking.Core.Models.Osc.FileBased; 4 | 5 | namespace VRCFaceTracking.Core.Models.ParameterDefinition.FileBased; 6 | 7 | public class AvatarConfigFileParameter : IParameterDefinition 8 | { 9 | public string name { get; set; } 10 | public AvatarConfigFileIODef input { get; set; } 11 | public AvatarConfigFileIODef output { get; set; } 12 | 13 | [JsonIgnore] public string Address => input.address; 14 | 15 | [JsonIgnore] public string Name => name; 16 | 17 | [JsonIgnore] public Type Type => input.Type; 18 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/ParameterDefinition/NullAvatarDef.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Contracts; 2 | 3 | namespace VRCFaceTracking.Core.Models.ParameterDefinition; 4 | 5 | /// 6 | /// NullAvatarDef is used when we don't really have a legitimate avatar definition and need to construct one at runtime. 7 | /// This allows us to ensure the UI has something to display to the user about the avatar. 8 | /// 9 | public class NullAvatarDef : IAvatarInfo 10 | { 11 | private readonly string _name, _id; 12 | 13 | public NullAvatarDef(string name, string id) 14 | { 15 | _name = name; 16 | _id = id; 17 | } 18 | 19 | public string Name => _name; 20 | 21 | public string Id => _id; 22 | 23 | public IParameterDefinition[] Parameters { get; } 24 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/TrackingModuleMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace VRCFaceTracking.Core.Models; 4 | 5 | public class TrackingModuleMetadata 6 | { 7 | public Guid ModuleId 8 | { 9 | get; set; 10 | } 11 | 12 | public DateTime LastUpdated 13 | { 14 | get; set; 15 | } = DateTime.Now; 16 | 17 | public string Version 18 | { 19 | get; 20 | set; 21 | } = "Unknown"; 22 | 23 | public int Downloads 24 | { 25 | get; set; 26 | } 27 | 28 | public int Ratings 29 | { 30 | get; set; 31 | } 32 | 33 | public float Rating 34 | { 35 | get; set; 36 | } 37 | 38 | public string AuthorName 39 | { 40 | get; set; 41 | } = "(No author provided)"; 42 | 43 | public string ModuleName 44 | { 45 | get; set; 46 | } = "(No name provided)"; 47 | 48 | public string ModuleDescription 49 | { 50 | get; set; 51 | } = "(No description provided)"; 52 | 53 | public string UsageInstructions 54 | { 55 | get; set; 56 | } = "(No usage instructions provided. Check the module page for more information.)"; 57 | 58 | public string DownloadUrl 59 | { 60 | get; set; 61 | } = "(No download provided)"; 62 | 63 | public string ModulePageUrl 64 | { 65 | get; 66 | set; 67 | } 68 | 69 | public string DllFileName 70 | { 71 | get; set; 72 | } = "(No DLL provided)"; 73 | } 74 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Models/UnifiedMutationConfig.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Params.Expressions; 2 | 3 | namespace VRCFaceTracking.Core.Models; 4 | 5 | public struct UnifiedMutationConfig 6 | { 7 | public MutationConfig[] ShapeMutations; 8 | public MutationConfig GazeMutationsConfig, OpennessMutationsConfig, PupilMutationsConfig; 9 | 10 | public UnifiedMutationConfig() 11 | { 12 | ShapeMutations = new MutationConfig[(int)UnifiedExpressions.Max + 1]; 13 | for (int i = 0; i < ShapeMutations.Length; i++) 14 | { 15 | ShapeMutations[i] = new MutationConfig() 16 | { 17 | Name = ((UnifiedExpressions)i).ToString() 18 | }; 19 | } 20 | GazeMutationsConfig = new MutationConfig() 21 | { 22 | Name = "GazeMutations" 23 | }; 24 | OpennessMutationsConfig = new MutationConfig() 25 | { 26 | Name = "OpennessMutations" 27 | }; 28 | PupilMutationsConfig = new MutationConfig() 29 | { 30 | Name = "PupilMutations" 31 | }; 32 | } 33 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/OSCUtils.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.OSC; 2 | using VRCFaceTracking.Core.Types; 3 | 4 | namespace VRCFaceTracking.OSC 5 | { 6 | public static class OscUtils 7 | { 8 | public static readonly Dictionary<(Type, char[] typeChar), (OscValueType oscType, string configType)> TypeConversions = 9 | new() 10 | { 11 | {(typeof(bool), new[]{'T', 'F'}), (OscValueType.Bool, "Bool")}, 12 | {(typeof(float), new[]{'f'}), (OscValueType.Float, "Float")}, 13 | {(typeof(int), new[]{'i'}), (OscValueType.Int, "Int")}, 14 | {(typeof(string), new[]{'s'}), (OscValueType.String, "String")}, 15 | }; 16 | } 17 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/Query/AccessValues.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query; 2 | 3 | public enum AccessValues 4 | { 5 | NoValue = 0, 6 | ReadOnly = 1, 7 | WriteOnly = 2, 8 | ReadWrite = 3 9 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/Query/HttpHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.Extensions.Logging; 3 | using VRCFaceTracking.Core.Contracts; 4 | 5 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 6 | 7 | public class HttpHandler : IDisposable 8 | { 9 | private readonly HttpListener _listener = new(); 10 | private IAsyncResult _contextListenerResult; 11 | private readonly IOscTarget _oscTarget; 12 | private readonly ILogger _logger; 13 | private string _appName = "VRCFT"; 14 | private int _oscPort = 9001; 15 | 16 | public Action OnHostInfoQueried = () => { }; 17 | 18 | public HttpHandler(IOscTarget oscTarget, ILogger logger) 19 | { 20 | _oscTarget = oscTarget; 21 | _logger = logger; 22 | } 23 | 24 | public void BindTo(string uri, int oscPort) 25 | { 26 | _oscPort = oscPort; 27 | if (_contextListenerResult != null) 28 | { 29 | _listener.EndGetContext(_contextListenerResult); 30 | } 31 | _listener.Stop(); 32 | _listener.Prefixes.Clear(); 33 | _listener.Prefixes.Add(uri); 34 | _listener.Start(); 35 | _contextListenerResult = _listener.BeginGetContext(HttpListenerLoop, _listener); 36 | } 37 | 38 | public void SetAppName(string newAppName) 39 | { 40 | _appName = newAppName; 41 | } 42 | 43 | private async void HttpListenerLoop(IAsyncResult result) 44 | { 45 | var context = _listener.EndGetContext(result); 46 | _listener.BeginGetContext(HttpListenerLoop, _listener); 47 | string respStr; 48 | if (context.Request.RawUrl.Contains("HOST_INFO")) 49 | { 50 | var hostInfo = new OscQueryHostInfo 51 | { 52 | name = _appName, 53 | oscIP = _oscTarget.DestinationAddress, 54 | oscPort = _oscPort 55 | }; 56 | respStr = hostInfo.ToString(); 57 | OnHostInfoQueried(); 58 | _logger.LogDebug($"Responding to oscquery host info request with {respStr}"); 59 | } 60 | else 61 | { 62 | if (context.Request.Url != null && context.Request.Url.LocalPath != "/") 63 | { 64 | return; // Not properly implementing oscquery protocol because I'm unemployed and not being paid to 65 | } 66 | 67 | var rootNode = new OscQueryRoot(); 68 | rootNode.AddNode(new OscQueryNode("/avatar/change", AccessValues.WriteOnly, "s")); 69 | 70 | respStr = rootNode.ToString(); 71 | } 72 | 73 | // Send Response 74 | context.Response.Headers.Add("pragma:no-cache"); 75 | 76 | context.Response.ContentType = "application/json"; 77 | context.Response.ContentLength64 = respStr.Length; 78 | using (var sw = new StreamWriter(context.Response.OutputStream)) 79 | { 80 | await sw.WriteAsync(respStr); 81 | await sw.FlushAsync(); 82 | } 83 | } 84 | 85 | public void Dispose() 86 | { 87 | _listener.Stop(); 88 | } 89 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/Query/OscQueryAvatarInfo.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Contracts; 2 | 3 | namespace VRCFaceTracking.Core.OSC.Query; 4 | 5 | public class OscQueryAvatarInfo : IAvatarInfo 6 | { 7 | public string Name { get; internal set; } 8 | 9 | public string Id { get; } 10 | 11 | public IParameterDefinition[] Parameters { get; } 12 | 13 | public OscQueryAvatarInfo(OscQueryNode rootNode) 14 | { 15 | Name = "Half-baked OSCQuery impl"; 16 | if (!rootNode.Contents.ContainsKey("change")) 17 | { 18 | // We likely queried while an avatar was still loading. Return without parsing. 19 | return; 20 | } 21 | Id = rootNode.Contents["change"].Value[0] as string; 22 | 23 | //TODO: Figure out a way to reconstruct the traditional address pattern instead of the whole thing. 24 | IEnumerable ConstructParameterArray(Dictionary entries) 25 | { 26 | return entries 27 | .SelectMany(entry => 28 | entry.Value.Contents != null ? ConstructParameterArray(entry.Value.Contents) : !string.IsNullOrEmpty(entry.Value.OscType) ? new[] { new OscQueryParameterDef(entry.Value.FullPath, entry.Value) } : Array.Empty() 29 | ); 30 | } 31 | 32 | Parameters = rootNode.Contents["parameters"].Contents 33 | .SelectMany(entry => 34 | entry.Value.Contents != null ? ConstructParameterArray(entry.Value.Contents) : !string.IsNullOrEmpty(entry.Value.OscType) ? new[] { new OscQueryParameterDef(entry.Value.FullPath, entry.Value) } : Array.Empty() 35 | ) 36 | .ToArray(); 37 | } 38 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/Query/OscQueryHostInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace VRCFaceTracking.Core.OSC.Query; 4 | 5 | public class OscQueryHostInfo 6 | { 7 | [JsonProperty("NAME")] 8 | public string name; 9 | 10 | [JsonProperty("EXTENSIONS")] public Dictionary extensions = new() 11 | { 12 | { "ACCESS", true }, 13 | { "CLIPMODE", false }, 14 | { "RANGE", true }, 15 | { "TYPE", true }, 16 | { "VALUE", true }, 17 | }; 18 | 19 | [JsonProperty("OSC_IP")] 20 | public string oscIP; 21 | 22 | [JsonProperty("OSC_PORT")] 23 | public int oscPort = 6969; 24 | 25 | [JsonProperty("OSC_TRANSPORT")] 26 | public string oscTransport = "UDP"; 27 | 28 | /// 29 | /// Empty Constructor required for JSON Serialization 30 | /// 31 | public OscQueryHostInfo() 32 | { 33 | 34 | } 35 | 36 | public override string ToString() 37 | { 38 | var result = JsonConvert.SerializeObject(this); 39 | return result; 40 | } 41 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/Query/OscQueryNode.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace VRCFaceTracking.Core.OSC.Query; 4 | 5 | public class OscQueryNode 6 | { 7 | // Empty Constructor for Json Serialization 8 | public OscQueryNode(){} 9 | 10 | public OscQueryNode(string fullPath, AccessValues access = AccessValues.NoValue, string oscType = null) 11 | { 12 | FullPath = fullPath; 13 | Access = access; 14 | OscType = oscType; 15 | } 16 | 17 | [JsonProperty("DESCRIPTION")] 18 | public string Description; 19 | 20 | [JsonProperty("FULL_PATH")] public string FullPath; 21 | 22 | [JsonProperty("ACCESS")] 23 | public AccessValues Access; 24 | 25 | [JsonProperty("CONTENTS")] 26 | public Dictionary Contents; 27 | 28 | [JsonProperty("TYPE")] 29 | public string OscType; 30 | 31 | [JsonProperty("VALUE")] 32 | public object[] Value; 33 | 34 | [JsonIgnore] 35 | public string ParentPath { 36 | get 37 | { 38 | var length = Math.Max(1, FullPath.LastIndexOf("/", StringComparison.Ordinal)); 39 | return FullPath.Substring(0, length); 40 | } 41 | 42 | } 43 | 44 | [JsonIgnore] 45 | public string Name => FullPath.Substring(FullPath.LastIndexOf('/')+1); 46 | 47 | public override string ToString() 48 | { 49 | var result = JsonConvert.SerializeObject(this, new JsonSerializerSettings 50 | { 51 | NullValueHandling = NullValueHandling.Ignore, 52 | }); 53 | return result; 54 | } 55 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/Query/OscQueryParameterDef.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Contracts; 2 | using VRCFaceTracking.OSC; 3 | 4 | namespace VRCFaceTracking.Core.OSC.Query; 5 | 6 | public class OscQueryParameterDef : IParameterDefinition 7 | { 8 | public string Address { get; } 9 | public string Name { get; } 10 | public Type Type { get; } 11 | 12 | public OscQueryParameterDef(string address, OscQueryNode node) 13 | { 14 | Address = address; 15 | Name = node.Name; 16 | Type = OscUtils.TypeConversions.First(t => 17 | t.Key.typeChar 18 | .Contains(node.OscType 19 | .First())) 20 | .Key 21 | .Item1; 22 | } 23 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OSC/Query/OscQueryRoot.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query; 2 | 3 | public class OscQueryRoot : OscQueryNode 4 | { 5 | // Acts as a quick lookup for full addresses 6 | private readonly Dictionary _nodes; 7 | 8 | public OscQueryRoot() : base("/") 9 | { 10 | _nodes = new Dictionary 11 | { 12 | { "/", this } 13 | }; 14 | } 15 | 16 | public OscQueryNode AddNode(OscQueryNode node) 17 | { 18 | // First, we check if the parent node exists in _nodes. If it doesn't, call this function with that node and recurse 19 | // if it does, add this node to the contents of that node, as well as _nodes 20 | if (!_nodes.TryGetValue(node.ParentPath, out var parentNode)) 21 | { 22 | parentNode = AddNode(new OscQueryNode(node.ParentPath)); 23 | } 24 | parentNode.Contents ??= new Dictionary(); 25 | parentNode.Contents.Add(node.Name, node); 26 | _nodes.Add(node.FullPath, node); 27 | 28 | return node; 29 | } 30 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/OscQueryConfigParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Newtonsoft.Json; 3 | using VRCFaceTracking.Core.Contracts; 4 | using VRCFaceTracking.Core.mDNS; 5 | using VRCFaceTracking.Core.OSC.Query; 6 | using VRCFaceTracking.Core.Params; 7 | 8 | namespace VRCFaceTracking.Core; 9 | 10 | public class OscQueryConfigParser 11 | { 12 | private readonly ILogger _logger; 13 | private readonly AvatarConfigParser _configParser; 14 | private readonly MulticastDnsService _multicastDnsService; 15 | 16 | public OscQueryConfigParser( 17 | ILogger parserLogger, 18 | AvatarConfigParser configParser, 19 | MulticastDnsService multicastDnsService 20 | ) 21 | { 22 | _logger = parserLogger; 23 | _configParser = configParser; 24 | _multicastDnsService = multicastDnsService; 25 | } 26 | 27 | private readonly HttpClient _httpClient = new(); 28 | 29 | public async Task<(IAvatarInfo avatarInfo, List relevantParameters)?> ParseAvatar(string avatarId = null) 30 | { 31 | try 32 | { 33 | if (_multicastDnsService.VrchatClientEndpoint == null) 34 | { 35 | return null; 36 | } 37 | 38 | // Request on the endpoint + /avatar/parameters 39 | var httpEndpoint = "http://" + _multicastDnsService.VrchatClientEndpoint + "/avatar"; 40 | 41 | // Get the response 42 | var response = await _httpClient.GetAsync(httpEndpoint); 43 | if (!response.IsSuccessStatusCode) 44 | { 45 | return null; 46 | } 47 | 48 | var avatarConfig = 49 | JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); 50 | _logger.LogDebug(avatarConfig.ToString()); 51 | var avatarInfo = new OscQueryAvatarInfo(avatarConfig); 52 | 53 | // Reset all parameters 54 | var paramList = new List(); 55 | foreach (var parameter in UnifiedTracking.AllParameters) 56 | { 57 | paramList.AddRange(parameter.ResetParam(avatarInfo.Parameters)); 58 | } 59 | 60 | // God help me why is this something I need to do to get the avatar name 61 | // this impl is really disappointing vrc 62 | var configFileInfo = await _configParser.ParseAvatar(avatarInfo.Id); 63 | _logger.LogInformation($"Attempting to resolve avatar config file for {avatarInfo.Id}"); 64 | if (!string.IsNullOrEmpty(configFileInfo?.avatarInfo.Name)) 65 | { 66 | avatarInfo.Name = configFileInfo.Value.avatarInfo.Name; 67 | _logger.LogInformation($"Successfully found config containing avatar name {avatarInfo.Name}"); 68 | } 69 | else 70 | { 71 | _logger.LogWarning("Odd. Our attempt to find the legacy osc config json for this avatar failed."); 72 | } 73 | 74 | return (avatarInfo, paramList); 75 | } 76 | catch (Exception e) 77 | { 78 | _logger.LogError(e.Message); 79 | SentrySdk.CaptureException(e, scope => scope.SetExtra("endpoint", _multicastDnsService.VrchatClientEndpoint)); 80 | return null; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Params/Data/Mutation/Correctors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using VRCFaceTracking.Core.Params.Expressions; 8 | 9 | namespace VRCFaceTracking.Core.Params.Data.Mutation; 10 | public class Correctors : TrackingMutation 11 | { 12 | public override string Name => "Unified Correctors"; 13 | public override string Description => "Processes data to conform to Unified Expressions."; 14 | public override MutationPriority Step => MutationPriority.Postprocessor; 15 | public override bool IsActive { get; set; } = true; 16 | 17 | [MutationProperty("MouthClosed/JawOpen Clamp")] 18 | public bool mouthClosedFix = true; 19 | [MutationProperty("LipSuck Limiter")] 20 | public bool lipSuckFix = true; 21 | 22 | public override void MutateData(ref UnifiedTrackingData data) 23 | { 24 | if (mouthClosedFix) 25 | { 26 | data.Shapes[(int)UnifiedExpressions.MouthClosed].Weight = 27 | Math.Min( 28 | data.Shapes[(int)UnifiedExpressions.MouthClosed].Weight, 29 | data.Shapes[(int)UnifiedExpressions.JawOpen].Weight 30 | ); 31 | } 32 | 33 | if (lipSuckFix) 34 | { 35 | data.Shapes[(int)UnifiedExpressions.LipSuckLowerLeft].Weight = 36 | data.Shapes[(int)UnifiedExpressions.LipSuckLowerLeft].Weight * (1f - data.Shapes[(int)UnifiedExpressions.MouthLowerDownLeft].Weight); 37 | data.Shapes[(int)UnifiedExpressions.LipSuckLowerRight].Weight = 38 | data.Shapes[(int)UnifiedExpressions.LipSuckLowerRight].Weight * (1f - data.Shapes[(int)UnifiedExpressions.MouthLowerDownRight].Weight); 39 | data.Shapes[(int)UnifiedExpressions.LipSuckUpperLeft].Weight = 40 | data.Shapes[(int)UnifiedExpressions.LipSuckUpperLeft].Weight * (1f - data.Shapes[(int)UnifiedExpressions.MouthUpperUpLeft].Weight); 41 | data.Shapes[(int)UnifiedExpressions.LipSuckUpperRight].Weight = 42 | data.Shapes[(int)UnifiedExpressions.LipSuckUpperRight].Weight * (1f - data.Shapes[(int)UnifiedExpressions.MouthUpperUpRight].Weight); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Params/Data/Mutation/TrackingMutation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using CommunityToolkit.Mvvm.ComponentModel; 10 | using Microsoft.Extensions.Logging; 11 | using Newtonsoft.Json; 12 | using VRCFaceTracking.Core.Contracts.Services; 13 | using VRCFaceTracking.Core.Params.Data; 14 | 15 | namespace VRCFaceTracking.Core.Params.Data.Mutation; 16 | public enum MutationPriority 17 | { 18 | Preprocessor, 19 | None, 20 | Postprocessor 21 | } 22 | 23 | public abstract partial class TrackingMutation 24 | { 25 | public abstract string Name { get; } 26 | [JsonIgnore] 27 | public abstract string Description { get; } 28 | public abstract MutationPriority Step { get; } 29 | [JsonIgnore] 30 | public ObservableCollection Components { get; set; } 31 | public virtual bool IsSaved { get; } = false; 32 | 33 | public virtual bool IsActive { get; set; } 34 | 35 | [JsonIgnore] 36 | public ILogger Logger { get; set; } 37 | public virtual void Initialize(UnifiedTrackingData data) { } 38 | public abstract void MutateData(ref UnifiedTrackingData data); 39 | public void CreateProperties() => Components = MutationComponentFactory.CreateComponents(this); 40 | public static TrackingMutation[] GetImplementingMutations(bool ordered = true) 41 | { 42 | var types = Assembly.GetExecutingAssembly() 43 | .GetTypes() 44 | .Where(type => type.IsSubclassOf(typeof(TrackingMutation))); 45 | 46 | List mutations = new List(); 47 | foreach (var t in types) 48 | { 49 | var mutation = (TrackingMutation)Activator.CreateInstance(t); 50 | mutations.Add(mutation); 51 | } 52 | 53 | if (ordered) 54 | { 55 | mutations.Sort((a, b) => a.Step.CompareTo(b.Step)); 56 | } 57 | 58 | return mutations.ToArray(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Params/Data/Mutation/UI/MutationAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace VRCFaceTracking.Core.Params.Data.Mutation; 10 | 11 | [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] 12 | public class MutationPropertyAttribute : Attribute 13 | { 14 | public string Name { get; } 15 | public Type EnumType { get; } 16 | public float Min { get; } 17 | public float Max { get; } 18 | 19 | public MutationPropertyAttribute(string name, float min = 0f, float max = 1f) 20 | { 21 | Name = name; 22 | Min = min; 23 | Max = max; 24 | } 25 | 26 | public MutationPropertyAttribute(Type enumType, float min = 0f, float max = 1f) 27 | { 28 | EnumType = enumType; 29 | Min = min; 30 | Max = max; 31 | } 32 | } 33 | 34 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 35 | public class MutationButtonAttribute : Attribute 36 | { 37 | public string Name 38 | { 39 | get; 40 | } 41 | 42 | public MutationButtonAttribute(string name) 43 | { 44 | Name = name; 45 | } 46 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Params/Expressions/Legacy/Lip/LipShapeConversion.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Params.Data; 2 | 3 | namespace VRCFaceTracking.Core.Params.Expressions.Legacy.Lip 4 | { 5 | internal interface ICombinedShape 6 | { 7 | float GetBlendedLipShape(UnifiedTrackingData inputMap); 8 | } 9 | 10 | internal class PositiveNegativeShape : ICombinedShape 11 | { 12 | private readonly SRanipal_LipShape_v2 _positiveShape, _negativeShape; 13 | private float _positiveCache, _negativeCache; 14 | private bool _steps; 15 | 16 | internal PositiveNegativeShape(SRanipal_LipShape_v2 positiveShape, SRanipal_LipShape_v2 negativeShape, bool steps = false) 17 | { 18 | _positiveShape = positiveShape; 19 | _negativeShape = negativeShape; 20 | _steps = steps; 21 | } 22 | 23 | public float GetBlendedLipShape(UnifiedTrackingData inputMap) 24 | { 25 | _positiveCache = UnifiedSRanMapper.GetTransformedShape(_positiveShape, inputMap); 26 | _negativeCache = UnifiedSRanMapper.GetTransformedShape(_negativeShape, inputMap) * -1; 27 | return _steps ? (_positiveCache - _negativeCache) - 1 : _positiveCache + _negativeCache; 28 | } 29 | } 30 | 31 | internal class PositiveNegativeAveragedShape : ICombinedShape 32 | { 33 | private readonly SRanipal_LipShape_v2[] _positiveShapes, _negativeShapes; 34 | private readonly float[] _positiveCache, _negativeCache; 35 | private readonly int _positiveCount, _negativeCount; 36 | private readonly bool _useMax; 37 | 38 | internal PositiveNegativeAveragedShape(SRanipal_LipShape_v2[] positiveShapes, SRanipal_LipShape_v2[] negativeShapes) 39 | { 40 | _positiveShapes = positiveShapes; 41 | _negativeShapes = negativeShapes; 42 | _positiveCache = new float[positiveShapes.Length]; 43 | _negativeCache = new float[negativeShapes.Length]; 44 | _positiveCount = positiveShapes.Length; 45 | _negativeCount = negativeShapes.Length; 46 | } 47 | 48 | internal PositiveNegativeAveragedShape(SRanipal_LipShape_v2[] positiveShapes, SRanipal_LipShape_v2[] negativeShapes, bool useMax) 49 | { 50 | _positiveShapes = positiveShapes; 51 | _negativeShapes = negativeShapes; 52 | _positiveCache = new float[positiveShapes.Length]; 53 | _negativeCache = new float[negativeShapes.Length]; 54 | _positiveCount = positiveShapes.Length; 55 | _negativeCount = negativeShapes.Length; 56 | _useMax = useMax; 57 | } 58 | 59 | public float GetBlendedLipShape(UnifiedTrackingData inputMap) 60 | { 61 | if (!_useMax) 62 | { 63 | float positive = 0; 64 | float negative = 0; 65 | 66 | for (int i = 0; i < _positiveCount; i++) { 67 | _positiveCache[i] = UnifiedSRanMapper.GetTransformedShape(_positiveShapes[i], inputMap); 68 | positive += _positiveCache[i]; 69 | } 70 | 71 | for (int i = 0; i < _negativeCount; i++) { 72 | _negativeCache[i] = UnifiedSRanMapper.GetTransformedShape(_negativeShapes[i], inputMap) * -1.0f; 73 | negative += _negativeCache[i]; 74 | } 75 | 76 | return (positive / _positiveCount) + (negative / _negativeCount); 77 | } 78 | 79 | for (int i = 0; i < _positiveCount; i++) { 80 | _positiveCache[i] = UnifiedSRanMapper.GetTransformedShape(_positiveShapes[i], inputMap); 81 | } 82 | 83 | for (int i = 0; i < _negativeCount; i++) { 84 | _negativeCache[i] = UnifiedSRanMapper.GetTransformedShape(_negativeShapes[i], inputMap); 85 | } 86 | 87 | return _positiveCache.Max() + (-1) * _negativeCache.Max(); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Params/Expressions/UnifiedHeadParameters.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Params.DataTypes; 2 | 3 | namespace VRCFaceTracking.Core.Params.Expressions; 4 | public static class UnifiedHeadParameters 5 | { 6 | public static readonly Parameter[] HeadParameters = { 7 | #region Rotation 8 | 9 | new EParam("v2/Head/Yaw", exp => exp.Head.HeadYaw), 10 | new EParam("v2/Head/Pitch", exp => exp.Head.HeadPitch), 11 | new EParam("v2/Head/Roll", exp => exp.Head.HeadRoll), 12 | 13 | #endregion 14 | 15 | #region Position 16 | 17 | new EParam("v2/Head/PosX", exp => exp.Head.HeadPosX), 18 | new EParam("v2/Head/PosY", exp => exp.Head.HeadPosY), 19 | new EParam("v2/Head/PosZ", exp => exp.Head.HeadPosZ) 20 | 21 | #endregion 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Params/Expressions/UnifiedSimpleExpressions.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Params.Data; 2 | 3 | namespace VRCFaceTracking.Core.Params.Expressions 4 | { 5 | /// 6 | /// Represents the type of transformed and simplified Shape data that is converted from the base UnifiedExpression data in the form of enumerated shapes. 7 | /// 8 | /// 9 | /// These shapes have a basis on general common expressions from different tracking interfaces and standards 10 | /// and are directly transformed from Unified Expressions data. 11 | /// 12 | public enum UnifiedSimpleExpressions 13 | { 14 | BrowUpRight, // Raises the inner and outer right eyebrow. 15 | BrowUpLeft, // Raises the inner and outer left eyebrow. 16 | BrowDownRight, // Lowers and pinches the right eyebrow. 17 | BrowDownLeft, // Lowers and pinches the left eyebrow. 18 | MouthSmileRight, // Moves the right corner of the lip into a smile expression. 19 | MouthSmileLeft, // Moves the left corner of the lip into a smile expression. 20 | MouthSadRight, // Moves the right corner of the lip into a sad expression. 21 | MouthSadLeft, // Moves the left corner of the lip into a sad expression. 22 | } 23 | 24 | public static class UnifiedSimplifier 25 | { 26 | /// 27 | /// Data map of all Unified to Unified Simple expressions. 28 | /// 29 | /// This is to keep all conversion data self contained and easily accessible. 30 | public static Dictionary> ExpressionMap = new Dictionary> 31 | { 32 | { UnifiedSimpleExpressions.BrowUpRight, exp => 33 | exp.Shapes[(int)UnifiedExpressions.BrowOuterUpRight].Weight * .60f + exp.Shapes[(int)UnifiedExpressions.BrowInnerUpRight].Weight * .40f }, 34 | { UnifiedSimpleExpressions.BrowUpLeft, exp => 35 | exp.Shapes[(int)UnifiedExpressions.BrowOuterUpLeft].Weight * .60f + exp.Shapes[(int)UnifiedExpressions.BrowInnerUpLeft].Weight * .40f }, 36 | 37 | { UnifiedSimpleExpressions.BrowDownRight, exp => 38 | exp.Shapes[(int)UnifiedExpressions.BrowLowererRight].Weight * .75f + exp.Shapes[(int)UnifiedExpressions.BrowPinchRight].Weight * .25f }, 39 | { UnifiedSimpleExpressions.BrowDownLeft, exp => 40 | exp.Shapes[(int)UnifiedExpressions.BrowLowererLeft].Weight * .75f + exp.Shapes[(int)UnifiedExpressions.BrowPinchLeft].Weight * .25f }, 41 | 42 | { UnifiedSimpleExpressions.MouthSmileRight, exp => 43 | exp.Shapes[(int)UnifiedExpressions.MouthCornerPullRight].Weight * .8f + exp.Shapes[(int)UnifiedExpressions.MouthCornerSlantRight].Weight * .2f }, 44 | { UnifiedSimpleExpressions.MouthSmileLeft, exp => 45 | exp.Shapes[(int)UnifiedExpressions.MouthCornerPullLeft].Weight * .8f + exp.Shapes[(int)UnifiedExpressions.MouthCornerSlantLeft].Weight * .2f }, 46 | { UnifiedSimpleExpressions.MouthSadRight, exp => 47 | exp.Shapes[(int)UnifiedExpressions.MouthFrownRight].Weight > exp.Shapes[(int)UnifiedExpressions.MouthStretchRight].Weight ? 48 | exp.Shapes[(int)UnifiedExpressions.MouthFrownRight].Weight : exp.Shapes[(int)UnifiedExpressions.MouthStretchRight].Weight }, 49 | { UnifiedSimpleExpressions.MouthSadLeft, exp => 50 | exp.Shapes[(int)UnifiedExpressions.MouthFrownLeft].Weight > exp.Shapes[(int)UnifiedExpressions.MouthStretchLeft].Weight ? 51 | exp.Shapes[(int)UnifiedExpressions.MouthFrownLeft].Weight : exp.Shapes[(int)UnifiedExpressions.MouthStretchLeft].Weight }, 52 | }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Params/Parameter.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Contracts; 2 | 3 | namespace VRCFaceTracking.Core.Params 4 | { 5 | public abstract class Parameter 6 | { 7 | public abstract Parameter[] ResetParam(IParameterDefinition[] newParams); 8 | public abstract (string paramName, Parameter paramLiteral)[] GetParamNames(); 9 | 10 | public virtual bool Deprecated => false; 11 | } 12 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Redirectors.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Principal; 2 | 3 | namespace VRCFaceTracking; 4 | 5 | [Obsolete("Please use VRCFaceTracking.Core.Utils instead")] 6 | public class Utils 7 | { 8 | public static uint TimeBeginPeriod(uint uMilliseconds) => Core.Utils.TimeBeginPeriod(uMilliseconds); 9 | 10 | public static uint TimeEndPeriod(uint uMilliseconds) => Core.Utils.TimeEndPeriod(uMilliseconds); 11 | 12 | // Proc memory read helpers 13 | public const int PROCESS_VM_READ = 0x0010; 14 | 15 | public static IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId) => 16 | Core.Utils.OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); 17 | 18 | public static bool ReadProcessMemory(int hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, 19 | ref int lpNumberOfBytesRead) => 20 | Core.Utils.ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, dwSize, ref lpNumberOfBytesRead); 21 | 22 | public static bool DeleteFile(string lpFileName) => Core.Utils.DeleteFile(lpFileName); 23 | 24 | public static uint GetFileAttributes(string lpFileName) => Core.Utils.GetFileAttributes(lpFileName); 25 | 26 | public static readonly bool HasAdmin = Core.Utils.HasAdmin; 27 | 28 | public static readonly string UserAccessibleDataDirectory = 29 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "VRCFaceTracking"); 30 | 31 | public static readonly string PersistentDataDirectory = 32 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VRCFaceTracking"); 33 | 34 | public static readonly string CustomLibsDirectory = Path.Combine(PersistentDataDirectory, "CustomLibs"); 35 | } 36 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Services/FileService.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using VRCFaceTracking.Core.Contracts.Services; 4 | 5 | namespace VRCFaceTracking.Core.Services; 6 | 7 | public class FileService : IFileService 8 | { 9 | public T Read(string folderPath, string fileName) 10 | { 11 | var path = Path.Combine(folderPath, fileName); 12 | if (File.Exists(path)) 13 | { 14 | var json = File.ReadAllText(path); 15 | return JsonConvert.DeserializeObject(json); 16 | } 17 | 18 | return default; 19 | } 20 | 21 | public async Task Save(string folderPath, string fileName, T content) 22 | { 23 | if (!Directory.Exists(folderPath)) 24 | { 25 | Directory.CreateDirectory(folderPath); 26 | } 27 | 28 | var fileContent = JsonConvert.SerializeObject(content); 29 | await File.WriteAllTextAsync(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8); 30 | } 31 | 32 | public void Delete(string folderPath, string fileName) 33 | { 34 | if (fileName != null && File.Exists(Path.Combine(folderPath, fileName))) 35 | { 36 | File.Delete(Path.Combine(folderPath, fileName)); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Services/LogFile.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace VRCFaceTracking.Core.Services; 4 | 5 | public class LogFileLogger : ILogger 6 | { 7 | private readonly string _categoryName; 8 | private readonly StreamWriter _file; 9 | private static readonly Mutex Mutex = new (); 10 | 11 | public LogFileLogger(string categoryName, StreamWriter file) 12 | { 13 | _categoryName = categoryName; 14 | _file = file; 15 | } 16 | 17 | public IDisposable BeginScope(TState state) where TState : notnull => default!; 18 | 19 | public bool IsEnabled(LogLevel logLevel) => true; 20 | 21 | public void Log( 22 | LogLevel logLevel, 23 | EventId eventId, 24 | TState state, 25 | Exception exception, 26 | Func formatter) 27 | { 28 | Mutex.WaitOne(); // Wait for the semaphore to be released 29 | try 30 | { 31 | _file.Write($"[{_categoryName}] {logLevel}: {formatter(state, exception)}\n"); 32 | _file.Flush(); 33 | } 34 | catch 35 | { 36 | // Ignore cus sandboxing causes a lot of issues here 37 | } 38 | 39 | Mutex.ReleaseMutex(); // Release the semaphore 40 | } 41 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Services/OscSendService.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using Microsoft.Extensions.Logging; 5 | using VRCFaceTracking.Core.Contracts; 6 | using VRCFaceTracking.Core.OSC; 7 | 8 | namespace VRCFaceTracking.Core.Services; 9 | 10 | /** 11 | * OscSendService is responsible for encoding osc messages and sending them over OSC 12 | */ 13 | public class OscSendService 14 | { 15 | private readonly ILogger _logger; 16 | private readonly IOscTarget _oscTarget; 17 | 18 | private Socket _sendSocket; 19 | private readonly byte[] _sendBuffer = new byte[4096]; 20 | 21 | private CancellationTokenSource _cts; 22 | public Action OnMessagesDispatched = _ => { }; 23 | 24 | public OscSendService( 25 | ILogger logger, 26 | IOscTarget oscTarget 27 | ) 28 | { 29 | _logger = logger; 30 | _cts = new CancellationTokenSource(); 31 | 32 | _oscTarget = oscTarget; 33 | 34 | _oscTarget.PropertyChanged += (_, args) => 35 | { 36 | if (args.PropertyName is not nameof(IOscTarget.OutPort)) 37 | { 38 | return; 39 | } 40 | 41 | if (_oscTarget.OutPort == default) 42 | { 43 | return; 44 | } 45 | 46 | if (string.IsNullOrEmpty(_oscTarget.DestinationAddress)) 47 | { 48 | _oscTarget.DestinationAddress = "127.0.0.1"; 49 | } 50 | 51 | UpdateTarget(new IPEndPoint(IPAddress.Parse(_oscTarget.DestinationAddress), _oscTarget.OutPort)); 52 | }; 53 | } 54 | 55 | private void UpdateTarget(IPEndPoint endpoint) 56 | { 57 | _cts.Cancel(); 58 | _sendSocket?.Close(); 59 | _oscTarget.IsConnected = false; 60 | 61 | _sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 62 | 63 | try 64 | { 65 | _sendSocket.Connect(endpoint); 66 | _oscTarget.IsConnected = true; 67 | } 68 | catch (SocketException ex) 69 | { 70 | _logger.LogWarning($"Failed to bind to sender endpoint: {endpoint}. {ex.Message}"); 71 | } 72 | finally 73 | { 74 | _cts = new CancellationTokenSource(); 75 | } 76 | } 77 | 78 | public async Task Send(OscMessage message, CancellationToken ct) 79 | { 80 | var nextByteIndex =await message.Encode(_sendBuffer, ct); 81 | if (nextByteIndex > 4096) 82 | { 83 | _logger.LogError("OSC message too large to send! Skipping this batch of messages."); 84 | return; 85 | } 86 | 87 | await _sendSocket?.SendAsync(_sendBuffer[..nextByteIndex])!; 88 | OnMessagesDispatched(1); 89 | } 90 | 91 | public async Task Send(OscMessage[] messages, CancellationToken ct) 92 | { 93 | var cbt = messages.Select(m => m._meta).ToArray(); 94 | var index = 0; 95 | while (index < cbt.Length) 96 | { 97 | var length = await Task.Run(() => fti_osc.create_osc_bundle(_sendBuffer, cbt, messages.Length, ref index), ct); 98 | await _sendSocket?.SendAsync(_sendBuffer[..length])!; 99 | } 100 | OnMessagesDispatched(index); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Services/ParameterSenderService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using VRCFaceTracking.Core.Contracts; 3 | using VRCFaceTracking.Core.OSC; 4 | 5 | namespace VRCFaceTracking.Core.Services; 6 | 7 | public class ParameterSenderService : BackgroundService 8 | { 9 | // We probably don't need a queue since we use osc message bundles, but for now, we're keeping it as 10 | // we might want to allow a way for the user to specify bundle or single message sends in the future 11 | private static readonly Queue SendQueue = new(); 12 | 13 | private readonly OscSendService _sendService; 14 | 15 | public static bool AllParametersRelevantStatic 16 | { 17 | get; set; 18 | } 19 | public bool AllParametersRelevant 20 | { 21 | get => AllParametersRelevantStatic; 22 | set 23 | { 24 | if (AllParametersRelevantStatic == value) return; 25 | AllParametersRelevantStatic = value; 26 | SendQueue.Clear(); 27 | foreach (var parameter in UnifiedTracking.AllParameters) 28 | { 29 | parameter.ResetParam(Array.Empty()); 30 | } 31 | } 32 | } 33 | 34 | public ParameterSenderService(OscSendService sendService) 35 | { 36 | _sendService = sendService; 37 | } 38 | 39 | public static void Enqueue(OscMessage message) => SendQueue.Enqueue(message); 40 | public static void Clear() => SendQueue.Clear(); 41 | 42 | protected async override Task ExecuteAsync(CancellationToken cancellationToken) 43 | { 44 | while (!cancellationToken.IsCancellationRequested) 45 | { 46 | try 47 | { 48 | await Task.Delay(10, cancellationToken); 49 | 50 | await UnifiedTracking.UpdateData(cancellationToken); 51 | 52 | // Send all messages in OSCParams.SendQueue 53 | if (SendQueue.Count <= 0) 54 | { 55 | continue; 56 | } 57 | 58 | await _sendService.Send(SendQueue.ToArray(), cancellationToken); 59 | 60 | SendQueue.Clear(); 61 | } 62 | catch (Exception e) 63 | { 64 | SentrySdk.CaptureException(e, scope => 65 | { 66 | var i = 0; 67 | foreach (var msg in SendQueue) 68 | { 69 | scope.SetExtra($"Address {i}", msg.Address); 70 | scope.SetExtra($"Values {i}", msg._meta.ValueLength); 71 | scope.SetExtra($"Value 0 {i}", msg.Value); 72 | i++; 73 | } 74 | }); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Types/ImageData.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Types 2 | { 3 | /// Structure that holds all necessary information for an image 4 | public class Image 5 | { 6 | /// 7 | /// Tuple that represents the horizontal and vertical size of the image (in pixels). 8 | /// 9 | public (int x, int y) ImageSize; 10 | 11 | /// 12 | /// Byte that contains the raw image data in RGBA. Length MUST be equal to x * y * 4 13 | /// 14 | /// ImageSize will be used to unwrap the image properly. 15 | public byte[] ImageData; 16 | 17 | /// 18 | /// Used to let VRCFaceTracking know if an image is available from the tracking interface. 19 | /// 20 | public bool SupportsImage; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Types/Vector2.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace VRCFaceTracking.Core.Types 4 | { 5 | // Make a nullable class called Vector2 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct Vector2 8 | { 9 | public float x; 10 | public float y; 11 | 12 | public Vector2(float x, float y) 13 | { 14 | this.x = x; 15 | this.y = y; 16 | } 17 | 18 | // Make an implicit conversion from vector3 to vector2 19 | public static implicit operator Vector2(Vector3 v) => new Vector2(v.x, v.y); 20 | 21 | public static Vector2 operator *(Vector2 a, float d) 22 | => new Vector2(a.x * d, a.y * d); 23 | 24 | public static Vector2 operator /(Vector2 a, float d) 25 | => new Vector2(a.x / d, a.y / d); 26 | 27 | public static Vector2 operator +(Vector2 a, Vector2 b) 28 | => new Vector2(a.x + b.x, a.y + b.y); 29 | 30 | public static Vector2 operator -(Vector2 a, Vector2 b) 31 | => new Vector2(a.x - b.x, a.y - b.y); 32 | 33 | public static Vector2 zero => new Vector2(0, 0); 34 | 35 | // Tobii normalized eye value is r = 1. Used by UnifiedEyeData gaze data. 36 | public Vector2 PolarTo2DCartesian(float r = 1) 37 | => new Vector2(r * (float)Math.Cos(x), r * (float)Math.Sin(y)); 38 | 39 | public Vector2 FlipXCoordinates() 40 | { 41 | x *= -1; 42 | 43 | return this; 44 | } 45 | 46 | public float ToYaw() 47 | => (float)(Math.Atan(x) * (180 / Math.PI)); 48 | 49 | public float ToPitch() 50 | => -(float)(Math.Atan(y) * (180 / Math.PI)); 51 | } 52 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Types/Vector3.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace VRCFaceTracking.Core.Types 4 | { 5 | [StructLayout(LayoutKind.Sequential)] 6 | public struct Vector3 7 | { 8 | public float x; 9 | public float y; 10 | public float z; 11 | 12 | public Vector3(float x, float y, float z) 13 | { 14 | this.x = x; 15 | this.y = y; 16 | this.z = z; 17 | } 18 | 19 | // Make a method that multiplies the vector by a scalar 20 | public Vector3 FlipXCoordinates() 21 | { 22 | x *= -1; 23 | 24 | return this; 25 | } 26 | public static Vector3 operator *(Vector3 a, float d) 27 | => new Vector3(a.x * d, a.y * d, a.z * d); 28 | 29 | public static Vector3 operator /(Vector3 a, float d) 30 | => new Vector3(a.x / d, a.y / d, a.z / d); 31 | 32 | public static Vector3 operator +(Vector3 a, Vector3 b) 33 | => new Vector3(a.x + b.x, a.y + b.y, a.z + b.z); 34 | 35 | public static Vector3 operator -(Vector3 a, Vector3 b) 36 | => new Vector3(a.x - b.x, a.y - b.y, a.z - b.z); 37 | 38 | // Tobii normalized eye value is r = 1. Used by UnifiedEyeData gaze data. 39 | public Vector3 PolarTo2DCartesian(float r = 1) 40 | => new Vector3(r * (float)Math.Cos(x), r * (float)Math.Sin(y), z); 41 | 42 | public static Vector3 zero => new Vector3(0, 0, 0); 43 | } 44 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Types/Vector4.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace VRCFaceTracking.Core.Types; 4 | 5 | [StructLayout(LayoutKind.Sequential)] 6 | public struct Vector4 7 | { 8 | public float w; 9 | public float x; 10 | public float y; 11 | public float z; 12 | 13 | public Vector4(float w, float x, float y, float z) 14 | { 15 | this.w = w; 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Types/XYParam.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Contracts; 2 | using VRCFaceTracking.Core.OSC.DataTypes; 3 | 4 | namespace VRCFaceTracking.Core.Types 5 | { 6 | [Obsolete("Use Vector2 instead")] 7 | public class XYParam 8 | { 9 | public BaseParam X, Y; 10 | 11 | protected Vector2 ParamValue 12 | { 13 | set 14 | { 15 | X.ParamValue = value.x; 16 | Y.ParamValue = value.y; 17 | } 18 | } 19 | 20 | protected XYParam(BaseParam x, BaseParam y) 21 | { 22 | X = x; 23 | Y = y; 24 | } 25 | 26 | protected void ResetParams(IParameterDefinition[] newParams) 27 | { 28 | X.ResetParam(newParams); 29 | Y.ResetParam(newParams); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/UnifiedTracking.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Params; 2 | using VRCFaceTracking.Core.Params.Data; 3 | using VRCFaceTracking.Core.Params.Expressions; 4 | using VRCFaceTracking.Core.Params.Expressions.Legacy.Eye; 5 | using VRCFaceTracking.Core.Params.Expressions.Legacy.Lip; 6 | using VRCFaceTracking.Core.Types; 7 | 8 | namespace VRCFaceTracking 9 | { 10 | /// 11 | /// Class that contains all relevant data 12 | /// 13 | public class UnifiedTracking 14 | { 15 | /// 16 | /// Eye image data sent from the loaded eye module. 17 | /// 18 | public static Image EyeImageData = new Image(); 19 | 20 | /// 21 | /// Lip / Expression image data sent from the loaded expressions module. 22 | /// 23 | public static Image LipImageData = new Image(); 24 | 25 | /// 26 | /// Latest Expression Data accessible and sent by all VRCFaceTracking modules. 27 | /// 28 | public static UnifiedTrackingData Data = new(); 29 | 30 | /// 31 | /// Container of all features and functions that mutates the incoming expression data into output data suitable for driving Unified Expressions. 32 | /// 33 | /// Mutates data on update. 34 | public static UnifiedTrackingMutator Mutator; 35 | 36 | #pragma warning disable CS0618 37 | /// 38 | /// Version 1 (VRCFaceTracking SRanipal) of all accessible output parameters. 39 | /// 40 | /// These parameters are going to be undocumented in the near future and are directly emulated by Version 2 (Unified Expressions) parameters. 41 | public static readonly Parameter[] AllParameters_v1 = LipShapeMerger.AllLipParameters.Union(EyeTrackingParams.ParameterList).ToArray(); 42 | 43 | /// 44 | /// Version 2 (Unified Expressions) of all accessible output parameters. 45 | /// 46 | public static readonly Parameter[] AllParameters_v2 = UnifiedExpressionsParameters.ExpressionParameters; 47 | 48 | /// 49 | /// Head tracking parameters 50 | /// 51 | public static readonly Parameter[] HeadParameters = UnifiedHeadParameters.HeadParameters; 52 | 53 | /// 54 | /// The collection of EVERY possible output parameter 55 | /// 56 | public static readonly Parameter[] AllParameters = AllParameters_v2.Concat(AllParameters_v1).Concat(HeadParameters).ToArray(); 57 | #pragma warning restore CS0618 58 | 59 | /// 60 | /// Central update action for all expression data to subscribe to. 61 | /// 62 | public static Action OnUnifiedDataUpdated = OnUnifiedDataUpdated + (_ => { }) ?? (_ => {}); 63 | 64 | /// 65 | /// Central update function that updates all output parameter data and pushes the latest expressions from VRCFaceTracking modules into the internal expressions buffer. 66 | /// 67 | public static async Task UpdateData(CancellationToken ct) => OnUnifiedDataUpdated.Invoke(await Task.Run(() => Mutator.MutateData(Data), ct)); 68 | } 69 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Security; 3 | using System.Security.Principal; 4 | 5 | namespace VRCFaceTracking.Core; 6 | 7 | /// 8 | /// Windows-centric utilities class 9 | /// 10 | public static class Utils 11 | { 12 | // Timer resolution helpers 13 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity] 14 | [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)] 15 | public static extern uint TimeBeginPeriod(uint uMilliseconds); 16 | 17 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity] 18 | [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", SetLastError = true)] 19 | public static extern uint TimeEndPeriod(uint uMilliseconds); 20 | 21 | // Proc memory read helpers 22 | public const int PROCESS_VM_READ = 0x0010; 23 | 24 | [DllImport("kernel32.dll")] 25 | public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); 26 | 27 | [DllImport("kernel32.dll")] 28 | public static extern bool ReadProcessMemory(int hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead); 29 | 30 | [DllImport("kernel32.dll", SetLastError = true)] 31 | [return: MarshalAs(UnmanagedType.Bool)] 32 | public static extern bool DeleteFile(string lpFileName); 33 | 34 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 35 | public static extern uint GetFileAttributes(string lpFileName); 36 | 37 | public static readonly bool HasAdmin = !OperatingSystem.IsWindows() || new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); 38 | 39 | public static readonly string UserAccessibleDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "VRCFaceTracking"); 40 | public static readonly string PersistentDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VRCFaceTracking"); 41 | public static readonly string CustomLibsDirectory = Path.Combine(PersistentDataDirectory, "CustomLibs"); 42 | } 43 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/VRCFaceTracking.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net7.0 4 | VRCFaceTracking.Core 5 | AnyCPU;x64;x86 6 | x86;x64;arm64;AnyCPU 7 | enable 8 | 5.2.3.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Always 24 | 25 | 26 | 27 | 28 | Always 29 | 30 | 31 | 32 | 33 | Always 34 | 35 | 36 | 37 | 38 | Always 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/Validation/ValidIpAddressAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Net; 4 | 5 | namespace VRCFaceTracking.Core.Validation; 6 | 7 | public class ValidIpAddressAttribute : ValidationAttribute 8 | { 9 | public override bool IsValid(object value) 10 | { 11 | if (value is string ipString) 12 | { 13 | 14 | // Check for empty string 15 | if (string.IsNullOrWhiteSpace(ipString)) 16 | { 17 | return false; 18 | } 19 | 20 | // Attempt to parse the IP address 21 | return IPAddress.TryParse(ipString, out _); 22 | } 23 | 24 | return false; // Return false if the value is not a string 25 | } 26 | 27 | public override string FormatErrorMessage(string name) 28 | { 29 | return $"{name} must be a valid IP address."; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/AdvertisedService.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace VRCFaceTracking.Core.mDNS; 4 | 5 | public record AdvertisedService(string ServiceName, int Port, IPAddress Address) 6 | { 7 | public readonly string ServiceName = ServiceName; 8 | public readonly int Port = Port; 9 | public readonly IPAddress Address = Address; 10 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/DNSPacket.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | public class DnsPacket 4 | { 5 | public ushort ID; 6 | public bool QUERYRESPONSE; 7 | public int OPCODE; 8 | public bool CONFLICT; 9 | public bool TRUNCATION; 10 | public bool TENTATIVE; 11 | public int RESPONSECODE; 12 | public DnsQuestion[] questions = Array.Empty(); 13 | public DnsResource[] answers = Array.Empty(); 14 | public DnsResource[] authorities = Array.Empty(); 15 | public DnsResource[] additionals = Array.Empty(); 16 | 17 | 18 | public DnsPacket(BigReader stream) 19 | { 20 | /** 21 | Bit offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 22 | 0 ID 23 | 16 QR Opcode C TC T Z Z Z Z RCODE 24 | 32 QDCOUNT 25 | 48 ANCOUNT 26 | 64 NSCOUNT 27 | 80 ARCOUNT 28 | */ 29 | 30 | // Read the header 31 | ID = stream.ReadUInt16(); 32 | 33 | var flags = stream.ReadUInt16(); 34 | QUERYRESPONSE = (flags & 0x8000) == 0x8000; 35 | OPCODE = (flags & 0x7800) >> 11; 36 | CONFLICT = (flags & 0x0400) == 0x0400; 37 | TRUNCATION = (flags & 0x0200) == 0x0200; 38 | TENTATIVE = (flags & 0x0100) == 0x0100; 39 | RESPONSECODE = flags & 0x000F; 40 | 41 | questions = new DnsQuestion[stream.ReadUInt16()]; 42 | answers = new DnsResource[stream.ReadUInt16()]; 43 | authorities = new DnsResource[stream.ReadUInt16()]; 44 | additionals = new DnsResource[stream.ReadUInt16()]; 45 | 46 | for (var i = 0; i < questions.Length; i++) 47 | questions[i] = new DnsQuestion(stream); 48 | 49 | for (var i = 0; i < answers.Length; i++) 50 | answers[i] = new DnsResource(stream); 51 | 52 | for (var i = 0; i < authorities.Length; i++) 53 | authorities[i] = new DnsResource(stream); 54 | 55 | for (var i = 0; i < additionals.Length; i++) 56 | additionals[i] = new DnsResource(stream); 57 | } 58 | 59 | public DnsPacket() 60 | { 61 | QUERYRESPONSE = true; 62 | } 63 | 64 | public byte[] Serialize() 65 | { 66 | var bytes = new List(); 67 | 68 | bytes.AddRange(BigWriter.WriteUInt16(ID)); 69 | 70 | ushort flags = 0; 71 | if (QUERYRESPONSE) 72 | flags |= 0x8000; 73 | if (CONFLICT) 74 | flags |= 0x0400; 75 | if (TRUNCATION) 76 | flags |= 0x0200; 77 | 78 | flags |= (ushort)(OPCODE << 11); 79 | flags |= (ushort)(RESPONSECODE & 0x000F); 80 | 81 | bytes.AddRange(BigWriter.WriteUInt16(flags)); 82 | 83 | bytes.AddRange(BigWriter.WriteUInt16((ushort)questions.Length)); 84 | 85 | bytes.AddRange(BigWriter.WriteUInt16((ushort)answers.Length)); 86 | 87 | bytes.AddRange(BigWriter.WriteUInt16((ushort)authorities.Length)); 88 | 89 | bytes.AddRange(BigWriter.WriteUInt16((ushort)additionals.Length)); 90 | 91 | foreach (var question in questions) 92 | bytes.AddRange(question.Serialize()); 93 | 94 | foreach (var answer in answers) 95 | bytes.AddRange(answer.Serialize()); 96 | 97 | foreach (var authority in authorities) 98 | bytes.AddRange(authority.Serialize()); 99 | 100 | foreach (var additional in additionals) 101 | bytes.AddRange(additional.Serialize()); 102 | 103 | return bytes.ToArray(); 104 | } 105 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/DNSQuestion.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | public class DnsQuestion 4 | { 5 | public List Labels; 6 | public ushort Type; 7 | public ushort Class; 8 | 9 | public DnsQuestion(BigReader reader) 10 | { 11 | Labels = reader.ReadDomainLabels(); 12 | Type = reader.ReadUInt16(); 13 | Class = reader.ReadUInt16(); 14 | } 15 | 16 | public DnsQuestion(List labels, ushort type, ushort @class) 17 | { 18 | Labels = labels; 19 | Type = type; 20 | Class = @class; 21 | } 22 | 23 | public virtual byte[] Serialize() 24 | { 25 | List bytes = new List(); 26 | bytes.AddRange(BigWriter.WriteDomainLabels(Labels)); 27 | bytes.AddRange(BigWriter.WriteUInt16(Type)); 28 | bytes.AddRange(BigWriter.WriteUInt16(Class)); 29 | return bytes.ToArray(); 30 | } 31 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/DNSResource.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | public class DnsResource : DnsQuestion 4 | { 5 | private static readonly Dictionary _typeMap = new Dictionary 6 | { 7 | {1, typeof(ARecord)}, 8 | {2, typeof(NSRecord)}, 9 | {12, typeof(PTRRecord)}, 10 | {16, typeof(TXTRecord)}, 11 | {33, typeof(SRVRecord)}, 12 | }; 13 | 14 | public TimeSpan TTL; 15 | public IDnsSerializer Data; 16 | 17 | public DnsResource(BigReader reader) : base(reader) 18 | { 19 | TTL = TimeSpan.FromSeconds(reader.ReadUInt32()); 20 | 21 | var dataLength = (int)reader.ReadUInt16(); 22 | var expectedEnd = reader.BaseStream.Position + dataLength; 23 | 24 | if (_typeMap.TryGetValue(Type, out var type)) 25 | { 26 | Data = (IDnsSerializer)Activator.CreateInstance(type); 27 | Data.Deserialize(reader, dataLength); 28 | } 29 | else 30 | { 31 | reader.ReadBytes(dataLength); 32 | } 33 | 34 | if (reader.BaseStream.Position != expectedEnd) 35 | throw new Exception("Invalid resource record"); 36 | } 37 | 38 | public DnsResource(IDnsSerializer data, List names) : base(names, _typeMap.First(x => x.Value == data.GetType()).Key, 1) 39 | { 40 | Data = data; 41 | TTL = TimeSpan.FromSeconds(120); 42 | } 43 | 44 | public override byte[] Serialize() 45 | { 46 | List bytes = new List(); 47 | bytes.AddRange(base.Serialize()); 48 | 49 | bytes.AddRange(BigWriter.WriteUInt32((uint)TTL.TotalSeconds)); 50 | 51 | var data = Data.Serialize(); 52 | bytes.AddRange(BigWriter.WriteUInt16((ushort)data.Length)); 53 | bytes.AddRange(data); 54 | 55 | return bytes.ToArray(); 56 | } 57 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/DNSSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | public interface IDnsSerializer 4 | { 5 | byte[] Serialize(); 6 | void Deserialize(BigReader reader, int expectedLength); 7 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/Parsing/BigReader.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | // Big endian reader 4 | public class BigReader : BinaryReader 5 | { 6 | private Dictionary> nameCache = new Dictionary>(); 7 | 8 | public BigReader(byte[] data) : base(new MemoryStream(data)) { } 9 | 10 | public override ushort ReadUInt16() => (ushort)((base.ReadByte() << 8) | base.ReadByte()); 11 | 12 | public override uint ReadUInt32() => (uint)((base.ReadByte() << 24) | (base.ReadByte() << 16) | (base.ReadByte() << 8) | base.ReadByte()); 13 | 14 | public override string ReadString() 15 | { 16 | var length = ReadByte(); 17 | var data = ReadBytes(length); 18 | return System.Text.Encoding.ASCII.GetString(data); 19 | } 20 | 21 | // Referenced from https://github.com/meamod/MeaMod.DNS/blob/master/src/Model/WireReader.cs#L189 22 | public List ReadDomainLabels() 23 | { 24 | var pointer = (int)BaseStream.Position; 25 | var length = ReadByte(); 26 | if ((length & 0xC0) == 0xC0) 27 | { 28 | var ptr = (length ^ 0xC0) << 8 | ReadByte(); 29 | var cname = nameCache[ptr]; 30 | nameCache[pointer] = cname; 31 | return cname; 32 | } 33 | 34 | var labels = new List(); 35 | if (length == 0) 36 | return labels; 37 | 38 | var data = ReadBytes(length); 39 | labels.Add(System.Text.Encoding.UTF8.GetString(data, 0, length)); 40 | labels.AddRange(ReadDomainLabels()); 41 | nameCache[pointer] = labels; 42 | 43 | return labels; 44 | } 45 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/Parsing/BigWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 4 | 5 | public static class BigWriter 6 | { 7 | public static byte[] WriteUInt16(ushort value) 8 | { 9 | byte[] bytes = new byte[2]; 10 | bytes[0] = (byte)(value >> 8); 11 | bytes[1] = (byte)(value & 0xFF); 12 | return bytes; 13 | } 14 | 15 | public static byte[] WriteUInt32(uint value) 16 | { 17 | byte[] bytes = new byte[4]; 18 | bytes[0] = (byte)(value >> 24); 19 | bytes[1] = (byte)(value >> 16); 20 | bytes[2] = (byte)(value >> 8); 21 | bytes[3] = (byte)(value & 0xFF); 22 | return bytes; 23 | } 24 | 25 | public static byte[] WriteDomainLabels(List labels) 26 | { 27 | List bytes = new List(); 28 | foreach (string label in labels) 29 | { 30 | bytes.Add((byte)label.Length); 31 | bytes.AddRange(Encoding.ASCII.GetBytes(label)); 32 | } 33 | bytes.Add(0); 34 | return bytes.ToArray(); 35 | } 36 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/Types/ARecord.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 4 | 5 | public class ARecord : IDnsSerializer 6 | { 7 | public IPAddress Address; 8 | 9 | public ARecord() 10 | { 11 | 12 | } 13 | 14 | public byte[] Serialize() 15 | { 16 | return Address.GetAddressBytes(); 17 | } 18 | 19 | public void Deserialize(BigReader reader, int expectedLength) 20 | { 21 | Address = new IPAddress(reader.ReadBytes(expectedLength)); 22 | } 23 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/Types/NSRecord.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | public class NSRecord : IDnsSerializer 4 | { 5 | public List Authority; 6 | 7 | public byte[] Serialize() => throw new NotImplementedException(); 8 | 9 | public void Deserialize(BigReader reader, int expectedLength) 10 | { 11 | Authority = reader.ReadDomainLabels(); 12 | } 13 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/Types/PTRRecord.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | public class PTRRecord : IDnsSerializer 4 | { 5 | public List DomainLabels; 6 | 7 | public PTRRecord() 8 | { 9 | DomainLabels = new List(); 10 | } 11 | 12 | public byte[] Serialize() 13 | { 14 | return BigWriter.WriteDomainLabels(DomainLabels); 15 | } 16 | 17 | public void Deserialize(BigReader reader, int expectedLength) 18 | { 19 | DomainLabels = reader.ReadDomainLabels(); 20 | } 21 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/Types/SRVRecord.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 2 | 3 | public class SRVRecord : IDnsSerializer 4 | { 5 | public ushort Priority; 6 | public ushort Weight; 7 | public ushort Port; 8 | public List Target; 9 | 10 | public byte[] Serialize() 11 | { 12 | // Serialize the SRV record 13 | List bytes = new List(); 14 | 15 | // Write priority as big endian ushort 16 | bytes.AddRange(BigWriter.WriteUInt16(Priority)); 17 | 18 | // Write weight as big endian ushort 19 | bytes.AddRange(BigWriter.WriteUInt16(Weight)); 20 | 21 | // Write port as big endian ushort 22 | bytes.AddRange(BigWriter.WriteUInt16(Port)); 23 | 24 | // Write target 25 | bytes.AddRange(BigWriter.WriteDomainLabels(Target)); 26 | 27 | return bytes.ToArray(); 28 | } 29 | 30 | public void Deserialize(BigReader reader, int expectedLength) 31 | { 32 | Priority = reader.ReadUInt16(); 33 | Weight = reader.ReadUInt16(); 34 | Port = reader.ReadUInt16(); 35 | Target = reader.ReadDomainLabels(); 36 | } 37 | } -------------------------------------------------------------------------------- /VRCFaceTracking.Core/mDNS/Types/TXTRecord.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace VRCFaceTracking.Core.OSC.Query.mDNS; 4 | 5 | public class TXTRecord : IDnsSerializer 6 | { 7 | public List Text; 8 | 9 | public TXTRecord() 10 | { 11 | Text = new List(); 12 | } 13 | 14 | public byte[] Serialize() 15 | { 16 | // Serialize the text to bytes 17 | List bytes = new List(); 18 | foreach (string s in Text) 19 | { 20 | bytes.Add((byte)s.Length); 21 | bytes.AddRange(Encoding.ASCII.GetBytes(s)); 22 | } 23 | return bytes.ToArray(); 24 | } 25 | 26 | public void Deserialize(BigReader reader, int expectedLength) 27 | { 28 | Text = new List(); 29 | while (expectedLength > 0) 30 | { 31 | var currString = reader.ReadString(); 32 | Text.Add(currString); 33 | expectedLength -= Encoding.ASCII.GetByteCount(currString) + 1; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /VRCFaceTracking.SDK/ExtTrackingModule.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using VRCFaceTracking.Core.Library; 3 | 4 | namespace VRCFaceTracking; 5 | 6 | public abstract class ExtTrackingModule 7 | { 8 | // Should UnifiedLibManager try to initialize this module if it's looking for a module that supports eye or lip. 9 | public virtual (bool SupportsEye, bool SupportsExpression) Supported => (false, false); 10 | 11 | public ModuleState Status = ModuleState.Uninitialized; 12 | 13 | public ILogger Logger; 14 | 15 | public ModuleMetadata ModuleInformation; 16 | 17 | public abstract (bool eyeSuccess, bool expressionSuccess) Initialize(bool eyeAvailable, bool expressionAvailable); 18 | 19 | public abstract void Update(); 20 | 21 | public abstract void Teardown(); 22 | } -------------------------------------------------------------------------------- /VRCFaceTracking.SDK/ModuleMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking; 2 | 3 | public struct ModuleMetadata 4 | { 5 | public delegate void ActiveChange(bool state); 6 | public ActiveChange OnActiveChange; 7 | 8 | public List StaticImages { get; set; } 9 | public string Name { get; set; } 10 | private bool _active; 11 | 12 | public bool Active 13 | { 14 | get => _active; 15 | set 16 | { 17 | _active = value; 18 | OnActiveChange?.Invoke(value); 19 | } 20 | } 21 | 22 | //Temporary for the menu display 23 | private bool _usingEye; 24 | private bool _usingExpression; 25 | 26 | public bool UsingEye 27 | { 28 | get => _usingEye; 29 | set => _usingEye = value; 30 | } 31 | 32 | public bool UsingExpression 33 | { 34 | get => _usingExpression; 35 | set => _usingExpression = value; 36 | } 37 | } -------------------------------------------------------------------------------- /VRCFaceTracking.SDK/ModuleState.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Library; 2 | 3 | public enum ModuleState 4 | { 5 | Uninitialized = -1, // If the module is not initialized, we can assume it's not being used 6 | Idle = 0, // Idle and above we can assume the module in question is or has been in use 7 | Active = 1 // We're actively getting tracking data from the module 8 | } -------------------------------------------------------------------------------- /VRCFaceTracking.SDK/VRCFaceTracking.SDK.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /VRCFaceTracking/Activation/ActivationHandler.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Activation; 2 | 3 | // Extend this class to implement new ActivationHandlers. See DefaultActivationHandler for an example. 4 | // https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/activation.md 5 | public abstract class ActivationHandler : IActivationHandler 6 | where T : class 7 | { 8 | // Override this method to add the logic for whether to handle the activation. 9 | protected virtual bool CanHandleInternal(T args) => true; 10 | 11 | // Override this method to add the logic for your activation handler. 12 | protected abstract Task HandleInternalAsync(T args); 13 | 14 | public bool CanHandle(object args) => args is T && CanHandleInternal((args as T)!); 15 | 16 | public async Task HandleAsync(object args) => await HandleInternalAsync((args as T)!); 17 | } 18 | -------------------------------------------------------------------------------- /VRCFaceTracking/Activation/DefaultActivationHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | using VRCFaceTracking.Contracts.Services; 4 | using VRCFaceTracking.ViewModels; 5 | 6 | namespace VRCFaceTracking.Activation; 7 | 8 | public class DefaultActivationHandler : ActivationHandler 9 | { 10 | private readonly INavigationService _navigationService; 11 | 12 | public DefaultActivationHandler(INavigationService navigationService) 13 | { 14 | _navigationService = navigationService; 15 | } 16 | 17 | protected override bool CanHandleInternal(LaunchActivatedEventArgs args) 18 | { 19 | // None of the ActivationHandlers has handled the activation. 20 | return _navigationService.Frame?.Content == null; 21 | } 22 | 23 | protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args) 24 | { 25 | _navigationService.NavigateTo(typeof(MainViewModel).FullName!, args.Arguments); 26 | 27 | await Task.CompletedTask; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /VRCFaceTracking/Activation/IActivationHandler.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Activation; 2 | 3 | public interface IActivationHandler 4 | { 5 | bool CanHandle(object args); 6 | 7 | Task HandleAsync(object args); 8 | } 9 | -------------------------------------------------------------------------------- /VRCFaceTracking/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Transparent 14 | Transparent 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/BadgeLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/BadgeLogo.scale-100.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/BadgeLogo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/BadgeLogo.scale-125.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/BadgeLogo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/BadgeLogo.scale-150.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/BadgeLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/BadgeLogo.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/BadgeLogo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/BadgeLogo.scale-400.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/LargeTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/LargeTile.scale-100.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/LargeTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/LargeTile.scale-125.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/LargeTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/LargeTile.scale-150.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/LargeTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/LargeTile.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/LargeTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/LargeTile.scale-400.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/SmallTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/SmallTile.scale-100.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/SmallTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/SmallTile.scale-125.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/SmallTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/SmallTile.scale-150.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/SmallTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/SmallTile.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/SmallTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/SmallTile.scale-400.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square150x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square150x150Logo.scale-100.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square150x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square150x150Logo.scale-125.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square150x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square150x150Logo.scale-150.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square150x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square150x150Logo.scale-400.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-16.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-256.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-32.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.altform-unplated_targetsize-48.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.scale-100.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.scale-125.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.scale-150.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.scale-400.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.targetsize-16.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.targetsize-24.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.targetsize-256.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.targetsize-32.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Square44x44Logo.targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Square44x44Logo.targetsize-48.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/StoreLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/StoreLogo.scale-100.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/StoreLogo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/StoreLogo.scale-125.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/StoreLogo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/StoreLogo.scale-150.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/StoreLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/StoreLogo.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/StoreLogo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/StoreLogo.scale-400.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Wide310x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Wide310x150Logo.scale-100.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Wide310x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Wide310x150Logo.scale-125.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Wide310x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Wide310x150Logo.scale-150.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/Wide310x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/Wide310x150Logo.scale-400.png -------------------------------------------------------------------------------- /VRCFaceTracking/Assets/WindowIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/Assets/WindowIcon.ico -------------------------------------------------------------------------------- /VRCFaceTracking/Behaviors/NavigationViewHeaderMode.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Behaviors; 2 | 3 | public enum NavigationViewHeaderMode 4 | { 5 | Always, 6 | Never, 7 | Minimal 8 | } 9 | -------------------------------------------------------------------------------- /VRCFaceTracking/BundleArtifacts/VRCFaceTracking_x64.appinstaller.xml: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /VRCFaceTracking/BundleArtifacts/artifact.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benaclejames/VRCFaceTracking/a1d4d3672772ab5171f880c88c6935b6ba196027/VRCFaceTracking/BundleArtifacts/artifact.cat -------------------------------------------------------------------------------- /VRCFaceTracking/Contracts/Services/IActivationService.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Contracts.Services; 2 | 3 | public interface IActivationService 4 | { 5 | Task ActivateAsync(object activationArgs); 6 | } 7 | -------------------------------------------------------------------------------- /VRCFaceTracking/Contracts/Services/INavigationService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | using Microsoft.UI.Xaml.Navigation; 3 | 4 | namespace VRCFaceTracking.Contracts.Services; 5 | 6 | public interface INavigationService 7 | { 8 | event NavigatedEventHandler Navigated; 9 | 10 | bool CanGoBack 11 | { 12 | get; 13 | } 14 | 15 | Frame? Frame 16 | { 17 | get; set; 18 | } 19 | 20 | bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false); 21 | 22 | bool GoBack(); 23 | } 24 | -------------------------------------------------------------------------------- /VRCFaceTracking/Contracts/Services/INavigationViewService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | namespace VRCFaceTracking.Contracts.Services; 4 | 5 | public interface INavigationViewService 6 | { 7 | IList? MenuItems 8 | { 9 | get; 10 | } 11 | 12 | object? SettingsItem 13 | { 14 | get; 15 | } 16 | 17 | void Initialize(NavigationView navigationView); 18 | 19 | void UnregisterEvents(); 20 | 21 | NavigationViewItem? GetSelectedItem(Type pageType); 22 | } 23 | -------------------------------------------------------------------------------- /VRCFaceTracking/Contracts/Services/IPageService.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Contracts.Services; 2 | 3 | public interface IPageService 4 | { 5 | Type GetPageType(string key); 6 | } 7 | -------------------------------------------------------------------------------- /VRCFaceTracking/Contracts/Services/IThemeSelectorService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | namespace VRCFaceTracking.Contracts.Services; 4 | 5 | public interface IThemeSelectorService 6 | { 7 | ElementTheme Theme 8 | { 9 | get; 10 | } 11 | 12 | Task InitializeAsync(); 13 | 14 | Task SetThemeAsync(ElementTheme theme); 15 | 16 | Task SetRequestedThemeAsync(); 17 | } 18 | -------------------------------------------------------------------------------- /VRCFaceTracking/Contracts/ViewModels/INavigationAware.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Contracts.ViewModels; 2 | 3 | public interface INavigationAware 4 | { 5 | void OnNavigatedTo(object parameter); 6 | 7 | void OnNavigatedFrom(); 8 | } 9 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/ComponentSelection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.UI.Xaml; 7 | using Microsoft.UI.Xaml.Controls; 8 | using Microsoft.UI.Xaml.Controls.Primitives; 9 | using VRCFaceTracking.Core.Params.Data.Mutation; 10 | 11 | namespace VRCFaceTracking.Helpers; 12 | 13 | public class ComponentTemplateSelector : DataTemplateSelector 14 | { 15 | public DataTemplate CheckboxTemplate { get; set; } 16 | public DataTemplate TextInputTemplate { get; set; } 17 | public DataTemplate SliderTemplate { get; set; } 18 | public DataTemplate ButtonTemplate { get; set; } 19 | public DataTemplate RangeTemplate { get; set; } 20 | 21 | protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) 22 | { 23 | if (item is MutationProperty property) 24 | return property.Type switch 25 | { 26 | MutationPropertyType.CheckBox => CheckboxTemplate, 27 | MutationPropertyType.TextBox => TextInputTemplate, 28 | MutationPropertyType.Slider => SliderTemplate, 29 | _ => base.SelectTemplateCore(item, container) 30 | }; 31 | if (item is MutationRangeProperty) 32 | return RangeTemplate; 33 | if (item is MutationAction) 34 | return ButtonTemplate; 35 | 36 | return base.SelectTemplateCore(item, container); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/EmptyCollectionToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.UI.Xaml.Data; 8 | using Microsoft.UI.Xaml; 9 | 10 | namespace VRCFaceTracking.Helpers; 11 | 12 | public class EmptyCollectionToVisibilityConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, string language) 15 | { 16 | if (value is IEnumerable collection) 17 | { 18 | return collection.Cast().Any() ? Visibility.Visible : Visibility.Collapsed; 19 | } 20 | return Visibility.Collapsed; 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, string language) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/EnumToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Data; 3 | 4 | namespace VRCFaceTracking.Helpers; 5 | 6 | public class EnumToBooleanConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, string language) 9 | { 10 | if (parameter is string enumString) 11 | { 12 | if (!Enum.IsDefined(typeof(ElementTheme), value)) 13 | { 14 | throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum"); 15 | } 16 | 17 | var enumValue = Enum.Parse(typeof(ElementTheme), enumString); 18 | 19 | return enumValue.Equals(value); 20 | } 21 | 22 | throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, string language) 26 | { 27 | if (parameter is string enumString) 28 | { 29 | return Enum.Parse(typeof(ElementTheme), enumString); 30 | } 31 | 32 | throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/EnumToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Data; 2 | using VRCFaceTracking.Core.Models; 3 | 4 | namespace VRCFaceTracking.Helpers; 5 | 6 | public class EnumToStringConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, string language) 9 | { 10 | if (value is InstallState state) 11 | if (state == InstallState.NotInstalled) 12 | return ""; 13 | return $"({value})"; 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); 17 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/FrameExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | namespace VRCFaceTracking.Helpers; 4 | 5 | public static class FrameExtensions 6 | { 7 | public static object? GetPageViewModel(this Frame frame) => frame?.Content?.GetType().GetProperty("ViewModel")?.GetValue(frame.Content, null); 8 | } 9 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/NavigationHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | 4 | namespace VRCFaceTracking.Helpers; 5 | 6 | // Helper class to set the navigation target for a NavigationViewItem. 7 | // 8 | // Usage in XAML: 9 | // 10 | // 11 | // Usage in code: 12 | // NavigationHelper.SetNavigateTo(navigationViewItem, typeof(MainViewModel).FullName); 13 | public class NavigationHelper 14 | { 15 | public static string GetNavigateTo(NavigationViewItem item) => (string)item.GetValue(NavigateToProperty); 16 | 17 | public static void SetNavigateTo(NavigationViewItem item, string value) => item.SetValue(NavigateToProperty, value); 18 | 19 | public static readonly DependencyProperty NavigateToProperty = 20 | DependencyProperty.RegisterAttached("NavigateTo", typeof(string), typeof(NavigationHelper), new PropertyMetadata(null)); 21 | } 22 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/ResourceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Windows.ApplicationModel.Resources; 2 | 3 | namespace VRCFaceTracking.Helpers; 4 | 5 | public static class ResourceExtensions 6 | { 7 | private static readonly ResourceLoader _resourceLoader = new(); 8 | 9 | public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey); 10 | } 11 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/RuntimeHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace VRCFaceTracking.Helpers; 5 | 6 | public class RuntimeHelper 7 | { 8 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 9 | private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder? packageFullName); 10 | 11 | public static bool IsMSIX 12 | { 13 | get 14 | { 15 | var length = 0; 16 | 17 | return GetCurrentPackageFullName(ref length, null) != 15700L; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /VRCFaceTracking/Helpers/StreamToBitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Data; 2 | using Microsoft.UI.Xaml.Media.Imaging; 3 | 4 | namespace VRCFaceTracking.Helpers; 5 | 6 | public class StreamToBitmapConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, string language) 9 | { 10 | var bitmapImages = new List(); 11 | if (value == null) 12 | return bitmapImages; 13 | 14 | var imageSources = (List)value; 15 | foreach (var imageSource in imageSources) 16 | { 17 | var bitmapImage = new BitmapImage(); 18 | imageSource.Seek(0, SeekOrigin.Begin); 19 | bitmapImage.SetSource(imageSource.AsRandomAccessStream()); 20 | bitmapImages.Add(bitmapImage); 21 | } 22 | return bitmapImages; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); 26 | } -------------------------------------------------------------------------------- /VRCFaceTracking/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /VRCFaceTracking/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Contracts.Services; 2 | using VRCFaceTracking.Helpers; 3 | 4 | namespace VRCFaceTracking; 5 | 6 | public sealed partial class MainWindow : WindowEx 7 | { 8 | public MainWindow() 9 | { 10 | InitializeComponent(); 11 | 12 | AppWindow.Closing += async (window, args) => 13 | { 14 | args.Cancel = true; 15 | await App.GetService().Teardown(); 16 | Close(); 17 | }; 18 | 19 | AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/WindowIcon.ico")); 20 | Content = null; 21 | Title = "AppDisplayName".GetLocalized(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /VRCFaceTracking/Models/GithubContributor.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Models; 2 | 3 | public class GithubContributor 4 | { 5 | public string login { get; set; } 6 | public string html_url { get; set; } 7 | public int contributions { get; set; } 8 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Models/LocalSettingsOptions.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Models; 2 | 3 | public class LocalSettingsOptions 4 | { 5 | public string? ApplicationDataFolder 6 | { 7 | get; set; 8 | } 9 | 10 | public string? LocalSettingsFile 11 | { 12 | get; set; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /VRCFaceTracking/Models/RatingObject.cs: -------------------------------------------------------------------------------- 1 | namespace VRCFaceTracking.Core.Models; 2 | 3 | public struct RatingObject 4 | { 5 | public string UserId { get; set; } 6 | public string ModuleId { get; set; } 7 | public int Rating { get; set; } 8 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Package.appinstaller: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /VRCFaceTracking/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | VRCFaceTracking 22 | benaclejames 23 | Assets\StoreLogo.png 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /VRCFaceTracking/Properties/PublishProfiles/win10-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | FileSystem 5 | x64 6 | win10-x64 7 | bin\\\win10-x64\publish\win10-x64\ 8 | true 9 | false 10 | False 11 | Release 12 | net7.0-windows10.0.19041.0 13 | false 14 | 15 | -------------------------------------------------------------------------------- /VRCFaceTracking/Properties/PublishProfiles/win10-x86.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | FileSystem 5 | x86 6 | win10-x86 7 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ 8 | True 9 | False 10 | False 11 | True 12 | 13 | 14 | -------------------------------------------------------------------------------- /VRCFaceTracking/Properties/launchsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "VRCFaceTracking (Package)": { 4 | "commandName": "MsixPackage" 5 | }, 6 | "VRCFaceTracking (Unpackaged)": { 7 | "commandName": "Project" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /VRCFaceTracking/README.md: -------------------------------------------------------------------------------- 1 | *Recommended Markdown Viewer: [Markdown Editor](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2)* 2 | 3 | ## Getting Started 4 | 5 | Browse and address `TODO:` comments in `View -> Task List` to learn the codebase and understand next steps for turning the generated code into production code. 6 | 7 | Explore the [WinUI Gallery](https://www.microsoft.com/store/productId/9P3JFPWWDZRC) to learn about available controls and design patterns. 8 | 9 | Relaunch Template Studio to modify the project by right-clicking on the project in `View -> Solution Explorer` then selecting `Add -> New Item (Template Studio)`. 10 | 11 | ## Publishing 12 | 13 | For projects with MSIX packaging, right-click on the application project and select `Package and Publish -> Create App Packages...` to create an MSIX package. 14 | 15 | For projects without MSIX packaging, follow the [deployment guide](https://docs.microsoft.com/windows/apps/windows-app-sdk/deploy-unpackaged-apps) or add the `Self-Contained` Feature to enable xcopy deployment. 16 | 17 | ## CI Pipelines 18 | 19 | See [README.md](https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/pipelines/README.md) for guidance on building and testing projects in CI pipelines. 20 | 21 | ## Changelog 22 | 23 | See [releases](https://github.com/microsoft/TemplateStudio/releases) and [milestones](https://github.com/microsoft/TemplateStudio/milestones). 24 | 25 | ## Feedback 26 | 27 | Bugs and feature requests should be filed at https://aka.ms/templatestudio. 28 | -------------------------------------------------------------------------------- /VRCFaceTracking/Services/DispatcherService.cs: -------------------------------------------------------------------------------- 1 | using VRCFaceTracking.Core.Contracts.Services; 2 | 3 | namespace VRCFaceTracking.Services; 4 | 5 | // Simple service to invoke actions on the UI thread from the Core project. 6 | public class DispatcherService : IDispatcherService 7 | { 8 | public void Run(Action action) => App.MainWindow.DispatcherQueue?.TryEnqueue(action.Invoke); 9 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Services/GithubService.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | using System.Text.Json; 3 | using VRCFaceTracking.Models; 4 | 5 | namespace VRCFaceTracking.Services; 6 | 7 | public class GithubService 8 | { 9 | public async Task> GetContributors(string repo) 10 | { 11 | var client = new HttpClient(); 12 | client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("VRCFaceTracking", "1.0")); 13 | var response = await client.GetAsync($"https://api.github.com/repos/{repo}/contributors"); 14 | if (!response.IsSuccessStatusCode) 15 | { 16 | return new List(); 17 | } 18 | var content = await response.Content.ReadAsStringAsync(); 19 | return JsonSerializer.Deserialize>(content); 20 | } 21 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Services/IdentityService.cs: -------------------------------------------------------------------------------- 1 | using Windows.Security.Cryptography; 2 | using Windows.Security.Cryptography.Core; 3 | using Windows.Storage.Streams; 4 | using Windows.System.Profile; 5 | using VRCFaceTracking.Core.Contracts.Services; 6 | 7 | namespace VRCFaceTracking.Services; 8 | 9 | public class IdentityService : IIdentityService 10 | { 11 | private string _uniqueUserId = string.Empty; 12 | 13 | public string GetUniqueUserId() 14 | { 15 | if (!string.IsNullOrEmpty(_uniqueUserId)) 16 | { 17 | return _uniqueUserId; 18 | } 19 | 20 | var systemId = SystemIdentification.GetSystemIdForPublisher(); 21 | 22 | // Convert the binary ID to a string 23 | var binaryId = systemId.Id; 24 | var systemIdString = CryptographicBuffer.EncodeToHexString(binaryId); 25 | 26 | // Hash the string 27 | var hasher = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); 28 | var hashed = 29 | hasher.HashData(CryptographicBuffer.ConvertStringToBinary(systemIdString, BinaryStringEncoding.Utf8)); 30 | _uniqueUserId = CryptographicBuffer.EncodeToHexString(hashed); 31 | 32 | return _uniqueUserId; 33 | } 34 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Services/LogFileProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Extensions.Logging.Abstractions; 4 | 5 | namespace VRCFaceTracking.Core.Services; 6 | 7 | [ProviderAlias("Debug")] 8 | public class LogFileProvider : ILoggerProvider 9 | { 10 | private readonly StreamWriter? _writer; 11 | 12 | public LogFileProvider() 13 | { 14 | try 15 | { 16 | if (!Directory.Exists(Utils.UserAccessibleDataDirectory)) // Eat my ass windows 17 | Directory.CreateDirectory(Utils.UserAccessibleDataDirectory); 18 | 19 | var logPath = Path.Combine(Utils.UserAccessibleDataDirectory, "latest.log"); 20 | 21 | var file = new FileStream(logPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, 4096, 22 | FileOptions.WriteThrough); 23 | _writer = new StreamWriter(file); 24 | } 25 | catch 26 | { 27 | 28 | } 29 | } 30 | 31 | private readonly ConcurrentDictionary _loggers = 32 | new(StringComparer.OrdinalIgnoreCase); 33 | 34 | public ILogger CreateLogger(string categoryName) 35 | { 36 | if (_writer != null) 37 | { 38 | return _loggers.GetOrAdd(categoryName, name => new LogFileLogger(name, _writer)); 39 | } 40 | 41 | return NullLogger.Instance; 42 | } 43 | 44 | public void Dispose() => _loggers.Clear(); 45 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Services/LoggingService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.UI.Dispatching; 4 | 5 | namespace VRCFaceTracking.Services; 6 | 7 | public class OutputPageLogger : ILogger 8 | { 9 | private readonly string _categoryName; 10 | public static readonly ObservableCollection FilteredLogs = new(); 11 | public static readonly ObservableCollection AllLogs = new(); 12 | private static DispatcherQueue? _dispatcher; 13 | 14 | public OutputPageLogger(string categoryName, DispatcherQueue? queue) 15 | { 16 | _categoryName = categoryName; 17 | _dispatcher = queue; 18 | } 19 | 20 | public IDisposable BeginScope(TState state) where TState : notnull => default!; 21 | 22 | public bool IsEnabled(LogLevel logLevel) => true; 23 | 24 | public void Log( 25 | LogLevel logLevel, 26 | EventId eventId, 27 | TState state, 28 | Exception? exception, 29 | Func formatter) 30 | { 31 | // Add to the staticLog from the dispatcher thread 32 | _dispatcher?.TryEnqueue(() => 33 | { 34 | AllLogs.Add($"[{_categoryName}] {logLevel}: {formatter(state, exception)}"); 35 | // Filtered is what the user sees, so show Information scope 36 | if (logLevel >= LogLevel.Information) 37 | { 38 | FilteredLogs.Add($"[{_categoryName}] {logLevel}: {formatter(state, exception)}"); 39 | } 40 | }); 41 | } 42 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Services/NavigationService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | using Microsoft.UI.Xaml.Controls; 4 | using Microsoft.UI.Xaml.Navigation; 5 | 6 | using VRCFaceTracking.Contracts.Services; 7 | using VRCFaceTracking.Contracts.ViewModels; 8 | using VRCFaceTracking.Helpers; 9 | 10 | namespace VRCFaceTracking.Services; 11 | 12 | // For more information on navigation between pages see 13 | // https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/navigation.md 14 | public class NavigationService : INavigationService 15 | { 16 | private readonly IPageService _pageService; 17 | private object? _lastParameterUsed; 18 | private Frame? _frame; 19 | 20 | public event NavigatedEventHandler? Navigated; 21 | 22 | public Frame? Frame 23 | { 24 | get 25 | { 26 | if (_frame == null) 27 | { 28 | _frame = App.MainWindow.Content as Frame; 29 | RegisterFrameEvents(); 30 | } 31 | 32 | return _frame; 33 | } 34 | 35 | set 36 | { 37 | UnregisterFrameEvents(); 38 | _frame = value; 39 | RegisterFrameEvents(); 40 | } 41 | } 42 | 43 | [MemberNotNullWhen(true, nameof(Frame), nameof(_frame))] 44 | public bool CanGoBack => Frame != null && Frame.CanGoBack; 45 | 46 | public NavigationService(IPageService pageService) 47 | { 48 | _pageService = pageService; 49 | } 50 | 51 | private void RegisterFrameEvents() 52 | { 53 | if (_frame != null) 54 | { 55 | _frame.Navigated += OnNavigated; 56 | } 57 | } 58 | 59 | private void UnregisterFrameEvents() 60 | { 61 | if (_frame != null) 62 | { 63 | _frame.Navigated -= OnNavigated; 64 | } 65 | } 66 | 67 | public bool GoBack() 68 | { 69 | if (CanGoBack) 70 | { 71 | var vmBeforeNavigation = _frame.GetPageViewModel(); 72 | _frame.GoBack(); 73 | if (vmBeforeNavigation is INavigationAware navigationAware) 74 | { 75 | navigationAware.OnNavigatedFrom(); 76 | } 77 | 78 | return true; 79 | } 80 | 81 | return false; 82 | } 83 | 84 | public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false) 85 | { 86 | var pageType = _pageService.GetPageType(pageKey); 87 | 88 | if (_frame != null && (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed)))) 89 | { 90 | _frame.Tag = clearNavigation; 91 | var vmBeforeNavigation = _frame.GetPageViewModel(); 92 | var navigated = _frame.Navigate(pageType, parameter); 93 | if (navigated) 94 | { 95 | _lastParameterUsed = parameter; 96 | if (vmBeforeNavigation is INavigationAware navigationAware) 97 | { 98 | navigationAware.OnNavigatedFrom(); 99 | } 100 | } 101 | 102 | return navigated; 103 | } 104 | 105 | return false; 106 | } 107 | 108 | private void OnNavigated(object sender, NavigationEventArgs e) 109 | { 110 | if (sender is Frame frame) 111 | { 112 | var clearNavigation = (bool)frame.Tag; 113 | if (clearNavigation) 114 | { 115 | frame.BackStack.Clear(); 116 | } 117 | 118 | if (frame.GetPageViewModel() is INavigationAware navigationAware) 119 | { 120 | navigationAware.OnNavigatedTo(e.Parameter); 121 | } 122 | 123 | Navigated?.Invoke(sender, e); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /VRCFaceTracking/Services/NavigationViewService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | using Microsoft.UI.Xaml.Controls; 4 | 5 | using VRCFaceTracking.Contracts.Services; 6 | using VRCFaceTracking.Helpers; 7 | using VRCFaceTracking.ViewModels; 8 | 9 | namespace VRCFaceTracking.Services; 10 | 11 | public class NavigationViewService : INavigationViewService 12 | { 13 | private readonly INavigationService _navigationService; 14 | 15 | private readonly IPageService _pageService; 16 | 17 | private NavigationView? _navigationView; 18 | 19 | public IList? MenuItems => _navigationView?.MenuItems; 20 | 21 | public object? SettingsItem => _navigationView?.SettingsItem; 22 | 23 | public NavigationViewService(INavigationService navigationService, IPageService pageService) 24 | { 25 | _navigationService = navigationService; 26 | _pageService = pageService; 27 | } 28 | 29 | [MemberNotNull(nameof(_navigationView))] 30 | public void Initialize(NavigationView navigationView) 31 | { 32 | _navigationView = navigationView; 33 | _navigationView.BackRequested += OnBackRequested; 34 | _navigationView.ItemInvoked += OnItemInvoked; 35 | } 36 | 37 | public void UnregisterEvents() 38 | { 39 | if (_navigationView != null) 40 | { 41 | _navigationView.BackRequested -= OnBackRequested; 42 | _navigationView.ItemInvoked -= OnItemInvoked; 43 | } 44 | } 45 | 46 | public NavigationViewItem? GetSelectedItem(Type pageType) 47 | { 48 | if (_navigationView != null) 49 | { 50 | return GetSelectedItem(_navigationView.MenuItems, pageType) ?? GetSelectedItem(_navigationView.FooterMenuItems, pageType); 51 | } 52 | 53 | return null; 54 | } 55 | 56 | private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) => _navigationService.GoBack(); 57 | 58 | private void OnItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) 59 | { 60 | if (args.IsSettingsInvoked) 61 | { 62 | _navigationService.NavigateTo(typeof(SettingsViewModel).FullName!); 63 | } 64 | else 65 | { 66 | var selectedItem = args.InvokedItemContainer as NavigationViewItem; 67 | 68 | if (selectedItem?.GetValue(NavigationHelper.NavigateToProperty) is string pageKey) 69 | { 70 | _navigationService.NavigateTo(pageKey); 71 | } 72 | } 73 | } 74 | 75 | private NavigationViewItem? GetSelectedItem(IEnumerable menuItems, Type pageType) 76 | { 77 | foreach (var item in menuItems.OfType()) 78 | { 79 | if (IsMenuItemForPageType(item, pageType)) 80 | { 81 | return item; 82 | } 83 | 84 | var selectedChild = GetSelectedItem(item.MenuItems, pageType); 85 | if (selectedChild != null) 86 | { 87 | return selectedChild; 88 | } 89 | } 90 | 91 | return null; 92 | } 93 | 94 | private bool IsMenuItemForPageType(NavigationViewItem menuItem, Type sourcePageType) 95 | { 96 | if (menuItem.GetValue(NavigationHelper.NavigateToProperty) is string pageKey) 97 | { 98 | return _pageService.GetPageType(pageKey) == sourcePageType; 99 | } 100 | 101 | return false; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /VRCFaceTracking/Services/OutputLogProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.UI.Dispatching; 4 | 5 | namespace VRCFaceTracking.Services; 6 | 7 | public sealed class OutputLogProvider : ILoggerProvider 8 | { 9 | private readonly ConcurrentDictionary _loggers = 10 | new(StringComparer.OrdinalIgnoreCase); 11 | 12 | private readonly DispatcherQueue _dispatcher; 13 | 14 | public OutputLogProvider(DispatcherQueue dispatcher) 15 | { 16 | _dispatcher = dispatcher; 17 | } 18 | 19 | public ILogger CreateLogger(string categoryName) => 20 | _loggers.GetOrAdd(categoryName, name => new OutputPageLogger(name, _dispatcher)); 21 | 22 | public void Dispose() => _loggers.Clear(); 23 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Services/PageService.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | using Microsoft.UI.Xaml.Controls; 4 | 5 | using VRCFaceTracking.Contracts.Services; 6 | using VRCFaceTracking.ViewModels; 7 | using VRCFaceTracking.Views; 8 | 9 | namespace VRCFaceTracking.Services; 10 | 11 | public class PageService : IPageService 12 | { 13 | private readonly Dictionary _pages = new(); 14 | 15 | public PageService() 16 | { 17 | Configure(); 18 | Configure(); 19 | Configure(); 20 | Configure(); 21 | Configure(); 22 | Configure(); 23 | } 24 | 25 | public Type GetPageType(string key) 26 | { 27 | Type? pageType; 28 | lock (_pages) 29 | { 30 | if (!_pages.TryGetValue(key, out pageType)) 31 | { 32 | throw new ArgumentException($"Page not found: {key}. Did you forget to call PageService.Configure?"); 33 | } 34 | } 35 | 36 | return pageType; 37 | } 38 | 39 | private void Configure() 40 | where VM : ObservableObject 41 | where V : Page 42 | { 43 | lock (_pages) 44 | { 45 | var key = typeof(VM).FullName!; 46 | if (_pages.ContainsKey(key)) 47 | { 48 | throw new ArgumentException($"The key {key} is already configured in PageService"); 49 | } 50 | 51 | var type = typeof(V); 52 | if (_pages.Any(p => p.Value == type)) 53 | { 54 | throw new ArgumentException($"This type is already configured with key {_pages.First(p => p.Value == type).Key}"); 55 | } 56 | 57 | _pages.Add(key, type); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /VRCFaceTracking/Services/ThemeSelectorService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | using VRCFaceTracking.Contracts.Services; 4 | using VRCFaceTracking.Core.Contracts.Services; 5 | using VRCFaceTracking.Helpers; 6 | 7 | namespace VRCFaceTracking.Services; 8 | 9 | public class ThemeSelectorService : IThemeSelectorService 10 | { 11 | private const string SettingsKey = "AppBackgroundRequestedTheme"; 12 | 13 | public ElementTheme Theme { get; set; } = ElementTheme.Default; 14 | 15 | private readonly ILocalSettingsService _localSettingsService; 16 | 17 | public ThemeSelectorService(ILocalSettingsService localSettingsService) =>_localSettingsService = localSettingsService; 18 | 19 | public async Task InitializeAsync() 20 | { 21 | Theme = await LoadThemeFromSettingsAsync(); 22 | await Task.CompletedTask; 23 | } 24 | 25 | public async Task SetThemeAsync(ElementTheme theme) 26 | { 27 | Theme = theme; 28 | 29 | await SetRequestedThemeAsync(); 30 | await SaveThemeInSettingsAsync(Theme); 31 | } 32 | 33 | public async Task SetRequestedThemeAsync() 34 | { 35 | if (App.MainWindow.Content is FrameworkElement rootElement) 36 | { 37 | rootElement.RequestedTheme = Theme; 38 | } 39 | 40 | await Task.CompletedTask; 41 | } 42 | 43 | private async Task LoadThemeFromSettingsAsync() 44 | { 45 | var themeName = await _localSettingsService.ReadSettingAsync(SettingsKey); 46 | 47 | if (Enum.TryParse(themeName, out ElementTheme cacheTheme)) 48 | { 49 | return cacheTheme; 50 | } 51 | 52 | return ElementTheme.Default; 53 | } 54 | 55 | private async Task SaveThemeInSettingsAsync(ElementTheme theme) 56 | { 57 | await _localSettingsService.SaveSettingAsync(SettingsKey, theme.ToString()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /VRCFaceTracking/Styles/FontSizes.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 24 6 | 7 | 16 8 | 9 | 10 | -------------------------------------------------------------------------------- /VRCFaceTracking/Styles/TextBlock.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 12 | 13 | 19 | 20 | 26 | 27 | 33 | 34 | 38 | 39 | 44 | 45 | 49 | 50 | 51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /VRCFaceTracking/Styles/Thickness.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 0,36,0,0 6 | 0,36,0,36 7 | 8 | 0,24,0,0 9 | 0,24,0,24 10 | 24,0,24,0 11 | 0,0,0,24 12 | 13 | 12,0,0,0 14 | 12,0,12,0 15 | 0,12,0,0 16 | 0,0,12,0 17 | 0,12,0,12 18 | 19 | 8,0,0,0 20 | 0,8,0,0 21 | 8,8,8,8 22 | 23 | 0,4,0,0 24 | 4,4,4,4 25 | 26 | 1,1,0,0 27 | 8,0,0,0 28 | 0,48,0,0 29 | 56,34,0,0 30 | 56,24,56,0 31 | 32 | 36,24,36,0 33 | 34 | -12,4,0,0 35 | 36 | 37 | -------------------------------------------------------------------------------- /VRCFaceTracking/TemplateStudio.xml: -------------------------------------------------------------------------------- 1 |  3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /VRCFaceTracking/Usings.cs: -------------------------------------------------------------------------------- 1 | global using WinUIEx; 2 | -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using Microsoft.UI.Xaml; 3 | using VRCFaceTracking.Core.Contracts; 4 | using VRCFaceTracking.Core.Contracts.Services; 5 | using VRCFaceTracking.Core.OSC; 6 | using VRCFaceTracking.Core.Services; 7 | 8 | namespace VRCFaceTracking.ViewModels; 9 | 10 | public partial class MainViewModel : ObservableRecipient 11 | { 12 | public ILibManager LibManager { get; } 13 | public OscQueryService ParameterOutputService { get; } 14 | public OscRecvService OscRecvService { get; } 15 | public OscSendService OscSendService { get; } 16 | public IOscTarget OscTarget { get; } 17 | 18 | private int _messagesRecvd; 19 | [ObservableProperty] private int _messagesInPerSec; 20 | 21 | private int _messagesSent; 22 | [ObservableProperty] private int _messagesOutPerSec; 23 | 24 | [ObservableProperty] private bool _noModulesInstalled; 25 | 26 | [ObservableProperty] private bool _oscWasDisabled; 27 | 28 | private DispatcherTimer msgCounterTimer; 29 | 30 | public MainViewModel( 31 | ILibManager libManager, 32 | OscQueryService parameterOutputService, 33 | IModuleDataService moduleDataService, 34 | IOscTarget oscTarget, 35 | OscRecvService oscRecvService, 36 | OscSendService oscSendService 37 | ) 38 | { 39 | //Services 40 | LibManager = libManager; 41 | ParameterOutputService = parameterOutputService; 42 | OscTarget = oscTarget; 43 | OscRecvService = oscRecvService; 44 | OscSendService = oscSendService; 45 | 46 | // Modules 47 | var installedNewModules = moduleDataService.GetInstalledModules(); 48 | var installedLegacyModules = moduleDataService.GetLegacyModules().Count(); 49 | NoModulesInstalled = !installedNewModules.Any() && installedLegacyModules == 0; 50 | 51 | // Message Timer 52 | OscRecvService.OnMessageReceived += MessageReceived; 53 | OscSendService.OnMessagesDispatched += MessageDispatched; 54 | msgCounterTimer = new DispatcherTimer 55 | { 56 | Interval = TimeSpan.FromSeconds(1) 57 | }; 58 | msgCounterTimer.Tick += (_, _) => 59 | { 60 | MessagesInPerSec = _messagesRecvd; 61 | _messagesRecvd = 0; 62 | 63 | MessagesOutPerSec = _messagesSent; 64 | _messagesSent = 0; 65 | }; 66 | msgCounterTimer.Start(); 67 | } 68 | 69 | private void MessageReceived(OscMessage msg) => _messagesRecvd++; 70 | private void MessageDispatched(int msgCount) => _messagesSent += msgCount; 71 | 72 | ~MainViewModel() 73 | { 74 | OscRecvService.OnMessageReceived -= MessageReceived; 75 | OscSendService.OnMessagesDispatched -= MessageDispatched; 76 | 77 | msgCounterTimer.Stop(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/ModuleRegistryViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | using CommunityToolkit.Mvvm.ComponentModel; 4 | 5 | using VRCFaceTracking.Contracts.ViewModels; 6 | using VRCFaceTracking.Core.Contracts.Services; 7 | using VRCFaceTracking.Core.Models; 8 | 9 | namespace VRCFaceTracking.ViewModels; 10 | 11 | public partial class ModuleRegistryViewModel : ObservableRecipient, INavigationAware 12 | { 13 | private readonly IModuleDataService _moduleDataService; 14 | [ObservableProperty] private InstallableTrackingModule? _selected; 15 | 16 | public ObservableCollection ModuleInfos { get; } = new(); 17 | 18 | public ModuleRegistryViewModel(IModuleDataService moduleDataService) 19 | { 20 | _moduleDataService = moduleDataService; 21 | } 22 | 23 | public async void OnNavigatedTo(object parameter) 24 | { 25 | ModuleInfos.Clear(); 26 | 27 | var data = await _moduleDataService.GetRemoteModules(); 28 | 29 | // Now comes the tricky bit, we get all locally installed modules and add them to the list. 30 | // If any of the IDs match a remote module and the other data contained within does not match, 31 | // then we need to set the local module install state to outdated. If everything matches then we need to set the install state to installed. 32 | var installedModules = _moduleDataService.GetInstalledModules().Concat(_moduleDataService.GetLegacyModules()); 33 | var localModules = new List(); // dw about it 34 | foreach (var installedModule in installedModules) 35 | { 36 | installedModule.InstallationState = InstallState.Installed; 37 | var remoteModule = data.FirstOrDefault(x => x.ModuleId == installedModule.ModuleId); 38 | if (remoteModule == null) // If this module is completely missing from the remote list, then we need to add it to the list. 39 | { 40 | // This module is installed but not in the remote list, so we need to add it to the list. 41 | localModules.Add(installedModule); 42 | } 43 | else 44 | { 45 | // This module is installed and in the remote list, so we need to update the remote module's install state. 46 | remoteModule.InstallationState = remoteModule.Version != installedModule.Version ? InstallState.Outdated : InstallState.Installed; 47 | } 48 | } 49 | 50 | // Sort our data by name, then place any modules with the author name VRCFT Team at the top of the list. (unbiased) 51 | data = data.OrderByDescending(x => x.InstallationState == InstallState.Installed) 52 | .ThenByDescending(x => x.AuthorName == "VRCFT Team") 53 | .ThenBy(x => x.ModuleName); 54 | 55 | // Then prepend the local modules to the list. 56 | data = localModules.Concat(data); 57 | 58 | foreach (var item in data) 59 | { 60 | ModuleInfos.Add(item); 61 | } 62 | } 63 | 64 | public void OnNavigatedFrom() 65 | { 66 | } 67 | 68 | public void EnsureItemSelected() 69 | { 70 | if (Selected == null && ModuleInfos.Any()) 71 | { 72 | Selected = ModuleInfos.First(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/MutatorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using CommunityToolkit.Mvvm.ComponentModel; 3 | using Microsoft.UI.Xaml.Data; 4 | using VRCFaceTracking.Core.Params.Data; 5 | using VRCFaceTracking.Core.Params.Data.Mutation; 6 | 7 | namespace VRCFaceTracking.ViewModels; 8 | 9 | public class MutatorViewModel : ObservableRecipient 10 | { 11 | private readonly UnifiedTrackingMutator _trackingMutator; 12 | 13 | public ObservableCollection Mutations { get; } 14 | 15 | public MutatorViewModel() 16 | { 17 | _trackingMutator = App.GetService(); 18 | Mutations = _trackingMutator._mutations; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/OutputViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace VRCFaceTracking.ViewModels; 4 | 5 | public class OutputViewModel : ObservableRecipient 6 | { 7 | public OutputViewModel() 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/ParameterViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace VRCFaceTracking.ViewModels; 4 | 5 | public partial class ParameterViewModel : ObservableRecipient 6 | { 7 | public string? ParameterName { get; set; } 8 | 9 | [ObservableProperty] private float _parameterValue; 10 | 11 | public bool CanBeNegative { get; set; } 12 | public float MinValue => CanBeNegative ? -1 : 0; 13 | } -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/ParametersViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace VRCFaceTracking.ViewModels; 4 | 5 | public class ParametersViewModel : ObservableRecipient 6 | { 7 | public ParametersViewModel() 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/RiskySettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using Microsoft.Extensions.Logging; 3 | using VRCFaceTracking.Core; 4 | using VRCFaceTracking.Core.Contracts.Services; 5 | using VRCFaceTracking.Core.Services; 6 | 7 | namespace VRCFaceTracking.ViewModels; 8 | 9 | public partial class RiskySettingsViewModel : ObservableObject 10 | { 11 | private readonly IMainService _mainService; 12 | private readonly ILogger _logger; 13 | private readonly ParameterSenderService _parameterSenderService; 14 | 15 | [ObservableProperty] private bool _enabled; 16 | 17 | public bool AllRelevantDebug 18 | { 19 | get => _parameterSenderService.AllParametersRelevant; 20 | set => _parameterSenderService.AllParametersRelevant = value; 21 | } 22 | 23 | public RiskySettingsViewModel( 24 | IMainService mainService, 25 | ILogger logger, 26 | ParameterSenderService parameterSenderService 27 | ) 28 | { 29 | _mainService = mainService; 30 | _logger = logger; 31 | _parameterSenderService = parameterSenderService; 32 | } 33 | 34 | /// 35 | /// If something goes very wrong some time during init, this can be used to force retry. 36 | /// Most modules likely will not like this. 37 | /// 38 | public void ForceReInit() 39 | { 40 | _logger.LogInformation("Reinitializing VRCFT..."); 41 | 42 | _mainService.Teardown(); 43 | 44 | _mainService.InitializeAsync(); 45 | } 46 | 47 | /// 48 | /// Worse case, will delete all persistent data stored by VRCFT. The same data is erased upon uninstall too. 49 | /// 50 | public void ResetVRCFT() 51 | { 52 | _logger.LogInformation("Resetting VRCFT..."); 53 | 54 | // Create a file in the VRCFT folder called "reset" 55 | // This will cause the app to reset on the next launch 56 | File.Create(Path.Combine(VRCFaceTracking.Core.Utils.PersistentDataDirectory, "reset")); 57 | } 58 | 59 | /// 60 | /// A quick and convenient way of force-deleting all OSC config manifests generated by VRChat in case they get stuck. 61 | /// We really shouldn't have to do this, but hopefully OSCQuery makes this obsolete eventually 62 | /// 63 | public void ResetAvatarOscManifests() 64 | { 65 | _logger.LogInformation("Resetting VRChat avatar configuration..."); 66 | try 67 | { 68 | foreach (var userFolder in Directory.GetDirectories(VRChat.VRCOSCDirectory)) 69 | { 70 | if (Directory.Exists(userFolder + "\\Avatars")) 71 | { 72 | Directory.Delete(userFolder + "\\Avatars", true); 73 | } 74 | } 75 | } 76 | catch (Exception e) 77 | { 78 | _logger.LogError(e, "Failed to reset VRChat avatar configuration! {Message}", e.Message); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | using CommunityToolkit.Mvvm.ComponentModel; 4 | using CommunityToolkit.Mvvm.Input; 5 | 6 | using Microsoft.UI.Xaml; 7 | 8 | using VRCFaceTracking.Contracts.Services; 9 | using VRCFaceTracking.Models; 10 | using VRCFaceTracking.Services; 11 | 12 | namespace VRCFaceTracking.ViewModels; 13 | 14 | public partial class SettingsViewModel : ObservableRecipient 15 | { 16 | private readonly IThemeSelectorService _themeSelectorService; 17 | [ObservableProperty] private ElementTheme _elementTheme; 18 | [ObservableProperty] private List _contributors; 19 | 20 | public ICommand SwitchThemeCommand 21 | { 22 | get; 23 | } 24 | 25 | private GithubService GithubService 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | private async void LoadContributors() 32 | { 33 | Contributors = await GithubService.GetContributors("benaclejames/VRCFaceTracking"); 34 | } 35 | 36 | public SettingsViewModel(IThemeSelectorService themeSelectorService, GithubService githubService) 37 | { 38 | _themeSelectorService = themeSelectorService; 39 | GithubService = githubService; 40 | 41 | _elementTheme = _themeSelectorService.Theme; 42 | 43 | SwitchThemeCommand = new RelayCommand( 44 | async (param) => 45 | { 46 | if (ElementTheme != param) 47 | { 48 | ElementTheme = param; 49 | await _themeSelectorService.SetThemeAsync(param); 50 | } 51 | }); 52 | 53 | LoadContributors(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /VRCFaceTracking/ViewModels/ShellViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | using Microsoft.UI.Xaml.Navigation; 4 | 5 | using VRCFaceTracking.Contracts.Services; 6 | using VRCFaceTracking.Views; 7 | 8 | namespace VRCFaceTracking.ViewModels; 9 | 10 | public partial class ShellViewModel : ObservableRecipient 11 | { 12 | [ObservableProperty] private bool _isBackEnabled; 13 | [ObservableProperty] private object? _selected; 14 | 15 | public INavigationService NavigationService 16 | { 17 | get; 18 | } 19 | 20 | public INavigationViewService NavigationViewService 21 | { 22 | get; 23 | } 24 | 25 | public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService) 26 | { 27 | NavigationService = navigationService; 28 | NavigationService.Navigated += OnNavigated; 29 | NavigationViewService = navigationViewService; 30 | } 31 | 32 | private void OnNavigated(object sender, NavigationEventArgs e) 33 | { 34 | IsBackEnabled = NavigationService.CanGoBack; 35 | 36 | if (e.SourcePageType == typeof(SettingsPage)) 37 | { 38 | Selected = NavigationViewService.SettingsItem; 39 | return; 40 | } 41 | 42 | var selectedItem = NavigationViewService.GetSelectedItem(e.SourcePageType); 43 | if (selectedItem != null) 44 | { 45 | Selected = selectedItem; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/AvatarInfoControl.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/AvatarInfoControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using VRCFaceTracking.Core.Contracts; 4 | using VRCFaceTracking.Core.Params; 5 | 6 | namespace VRCFaceTracking.Views; 7 | 8 | public sealed partial class AvatarInfoControl : UserControl 9 | { 10 | public IAvatarInfo AvatarInfo 11 | { 12 | get => (IAvatarInfo)GetValue(AvatarInfoProperty); 13 | set => SetValue(AvatarInfoProperty, value); 14 | } 15 | 16 | public List AvatarParameters 17 | { 18 | get => (List)GetValue(AvatarParametersProperty); 19 | set => SetValue(AvatarParametersProperty, value); 20 | } 21 | 22 | public static readonly DependencyProperty AvatarInfoProperty = 23 | DependencyProperty.Register( 24 | nameof(AvatarInfo), 25 | typeof(IAvatarInfo), 26 | typeof(AvatarInfoControl), 27 | new PropertyMetadata(null)); 28 | 29 | public static readonly DependencyProperty AvatarParametersProperty = 30 | DependencyProperty.Register( 31 | nameof(AvatarParameters), 32 | typeof(List), 33 | typeof(AvatarInfoControl), 34 | new PropertyMetadata(null)); 35 | 36 | public AvatarInfoControl() 37 | { 38 | InitializeComponent(); 39 | 40 | DataContext = this; 41 | RegisterPropertyChangedCallback(AvatarInfoProperty, OnNewAvatarInfo); 42 | RegisterPropertyChangedCallback(AvatarParametersProperty, OnNewAvatarParameters); 43 | } 44 | 45 | private void OnNewAvatarInfo(DependencyObject d, DependencyProperty dp) 46 | { 47 | var avatar = (IAvatarInfo)GetValue(dp); 48 | LocalTestWarning.Visibility = avatar.Id.StartsWith("local:") ? Visibility.Visible : Visibility.Collapsed; 49 | } 50 | 51 | private void OnNewAvatarParameters(DependencyObject d, DependencyProperty dp) 52 | { 53 | var parameters = (List)GetValue(dp); 54 | var deprecatedParams = parameters.Count(p => p.Deprecated); 55 | 56 | CurrentParametersCount.Text = parameters.Count.ToString(); 57 | LegacyParametersCount.Text = deprecatedParams.ToString(); 58 | 59 | LegacyParamsWarning.Visibility = deprecatedParams > 0 ? Visibility.Visible : Visibility.Collapsed; 60 | } 61 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Views/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using VRCFaceTracking.Contracts.Services; 4 | using VRCFaceTracking.ViewModels; 5 | 6 | namespace VRCFaceTracking.Views; 7 | 8 | public sealed partial class MainPage : Page 9 | { 10 | public MainViewModel ViewModel 11 | { 12 | get; 13 | } 14 | 15 | public INavigationService NavigationService 16 | { 17 | get; 18 | } 19 | 20 | public MainPage() 21 | { 22 | ViewModel = App.GetService(); 23 | NavigationService = App.GetService(); 24 | 25 | InitializeComponent(); 26 | } 27 | 28 | private void NoModuleButton_Click(object sender, RoutedEventArgs e) => NavigationService.NavigateTo(typeof(ModuleRegistryViewModel).FullName!); 29 | } 30 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/ModuleRegistryPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage.Pickers; 2 | using CommunityToolkit.WinUI.UI.Controls; 3 | using Microsoft.UI.Xaml; 4 | using VRCFaceTracking.Core.Contracts.Services; 5 | using VRCFaceTracking.Core.Services; 6 | using VRCFaceTracking.ViewModels; 7 | 8 | namespace VRCFaceTracking.Views; 9 | 10 | public sealed partial class ModuleRegistryPage 11 | { 12 | public ModuleRegistryViewModel ViewModel 13 | { 14 | get; 15 | } 16 | 17 | private ModuleInstaller ModuleInstaller 18 | { 19 | get; 20 | } 21 | 22 | private ILibManager LibManager 23 | { 24 | get; 25 | } 26 | 27 | public ModuleRegistryPage() 28 | { 29 | ViewModel = App.GetService(); 30 | ModuleInstaller = App.GetService(); 31 | LibManager = App.GetService(); 32 | InitializeComponent(); 33 | } 34 | 35 | private void OnViewStateChanged(object sender, ListDetailsViewState e) 36 | { 37 | if (e == ListDetailsViewState.Both) 38 | { 39 | ViewModel.EnsureItemSelected(); 40 | } 41 | } 42 | 43 | private async void InstallCustomModule_OnClick(object sender, RoutedEventArgs e) 44 | { 45 | CustomInstallStatus.Text = ""; 46 | 47 | // Create a file picker 48 | var openPicker = new FileOpenPicker(); 49 | 50 | // Retrieve the window handle (HWND) of the current WinUI 3 window. 51 | var window = App.MainWindow; 52 | var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window); 53 | 54 | // Initialize the file picker with the window handle (HWND). 55 | WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd); 56 | 57 | // Set options for your file picker 58 | openPicker.ViewMode = PickerViewMode.Thumbnail; 59 | openPicker.FileTypeFilter.Add("*"); 60 | 61 | // Open the picker for the user to pick a file 62 | var file = await openPicker.PickSingleFileAsync(); 63 | if (file != null) 64 | { 65 | string? path = null; 66 | try 67 | { 68 | path = await ModuleInstaller.InstallLocalModule(file.Path); 69 | } 70 | finally 71 | { 72 | if (path != null) 73 | { 74 | CustomInstallStatus.Text = "Successfully installed module."; 75 | App.MainWindow.DispatcherQueue.TryEnqueue(() => LibManager.Initialize()); 76 | } 77 | else 78 | { 79 | CustomInstallStatus.Text = "Failed to install module. Check logs for more information."; 80 | } 81 | } 82 | } 83 | else 84 | { 85 | CustomInstallStatus.Text = "Operation cancelled."; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/MutatorPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using Microsoft.UI.Xaml.Controls; 3 | using VRCFaceTracking.Core.Contracts.Services; 4 | using VRCFaceTracking.Core.Params.Data; 5 | using VRCFaceTracking.Services; 6 | using VRCFaceTracking.ViewModels; 7 | 8 | namespace VRCFaceTracking.Views; 9 | 10 | public sealed partial class MutatorPage : Page 11 | { 12 | public MutatorViewModel ViewModel 13 | { 14 | get; 15 | } 16 | 17 | public MutatorPage() 18 | { 19 | var trackingMutator = App.GetService(); 20 | ViewModel = App.GetService(); 21 | DataContext = ViewModel; 22 | InitializeComponent(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/OutputPage.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/ParameterDebugUserControl.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/ParameterDebugUserControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | using VRCFaceTracking.ViewModels; 3 | 4 | namespace VRCFaceTracking.Views; 5 | 6 | public sealed partial class ParameterDebugUserControl : UserControl 7 | { 8 | public ParameterViewModel ViewModel 9 | { 10 | get; 11 | } 12 | 13 | public ParameterDebugUserControl() 14 | { 15 | ViewModel = App.GetService(); 16 | InitializeComponent(); 17 | this.DataContext = this; 18 | } 19 | } -------------------------------------------------------------------------------- /VRCFaceTracking/Views/ParametersPage.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/ParametersPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using Microsoft.UI.Xaml.Controls; 3 | using VRCFaceTracking.Core.Contracts.Services; 4 | using VRCFaceTracking.ViewModels; 5 | 6 | namespace VRCFaceTracking.Views; 7 | 8 | public sealed partial class ParametersPage : Page 9 | { 10 | public ParametersViewModel ViewModel { get; } 11 | 12 | public IMainService MainService 13 | { 14 | get; 15 | } 16 | 17 | private ObservableCollection _trackedParameters = new(); 18 | 19 | public ParametersPage() 20 | { 21 | ViewModel = App.GetService(); 22 | MainService = App.GetService(); 23 | var dispatcher = App.GetService(); 24 | _trackedParameters.Add(new ParameterDebugUserControl() 25 | { 26 | ViewModel = 27 | { 28 | ParameterName = "REEE", 29 | ParameterValue = 0.5f 30 | } 31 | }); 32 | 33 | this.DataContext = this; 34 | InitializeComponent(); 35 | 36 | MainService.ParameterUpdate += (addr, val) => dispatcher.Run(() => OnParameterSend(addr, val)); 37 | } 38 | 39 | private void OnParameterSend(string address, float value) 40 | { 41 | // Gotta extract the name from the address by getting the last split / 42 | var name = address.Split('/').Last(); 43 | 44 | // First we check to see if we already have a control for this parameter 45 | var existingControl = _trackedParameters.FirstOrDefault(x => x.ViewModel.ParameterName == name); 46 | 47 | // If we don't, add one 48 | if (existingControl == null) 49 | { 50 | var newControl = new ParameterDebugUserControl(); 51 | _trackedParameters.Add(newControl); 52 | newControl.ViewModel.ParameterName = name; 53 | newControl.ViewModel.ParameterValue = value; 54 | } 55 | 56 | // If we do, update the value 57 | else 58 | { 59 | existingControl.ViewModel.ParameterValue = value; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/ShellPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Input; 4 | using Microsoft.UI.Xaml.Media; 5 | 6 | using VRCFaceTracking.Contracts.Services; 7 | using VRCFaceTracking.Helpers; 8 | using VRCFaceTracking.ViewModels; 9 | 10 | using Windows.System; 11 | 12 | namespace VRCFaceTracking.Views; 13 | 14 | // TODO: Update NavigationViewItem titles and icons in ShellPage.xaml. 15 | public sealed partial class ShellPage : Page 16 | { 17 | public ShellViewModel ViewModel 18 | { 19 | get; 20 | } 21 | 22 | public ShellPage(ShellViewModel viewModel) 23 | { 24 | ViewModel = viewModel; 25 | InitializeComponent(); 26 | 27 | ViewModel.NavigationService.Frame = NavigationFrame; 28 | ViewModel.NavigationViewService.Initialize(NavigationViewControl); 29 | 30 | // TODO: Set the title bar icon by updating /Assets/WindowIcon.ico. 31 | // A custom title bar is required for full window theme and Mica support. 32 | // https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization 33 | App.MainWindow.ExtendsContentIntoTitleBar = true; 34 | App.MainWindow.SetTitleBar(AppTitleBar); 35 | App.MainWindow.Activated += MainWindow_Activated; 36 | AppTitleBarText.Text = "AppDisplayName".GetLocalized(); 37 | } 38 | 39 | private void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) 40 | { 41 | KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu)); 42 | KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.GoBack)); 43 | } 44 | 45 | private void MainWindow_Activated(object sender, WindowActivatedEventArgs args) 46 | { 47 | var resource = args.WindowActivationState == WindowActivationState.Deactivated ? "WindowCaptionForegroundDisabled" : "WindowCaptionForeground"; 48 | 49 | AppTitleBarText.Foreground = (SolidColorBrush)App.Current.Resources[resource]; 50 | } 51 | 52 | private void NavigationViewControl_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) 53 | { 54 | AppTitleBar.Margin = new Thickness() 55 | { 56 | Left = sender.CompactPaneLength * (sender.DisplayMode == NavigationViewDisplayMode.Minimal ? 2 : 1), 57 | Top = AppTitleBar.Margin.Top, 58 | Right = AppTitleBar.Margin.Right, 59 | Bottom = AppTitleBar.Margin.Bottom 60 | }; 61 | } 62 | 63 | private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null) 64 | { 65 | var keyboardAccelerator = new KeyboardAccelerator() { Key = key }; 66 | 67 | if (modifiers.HasValue) 68 | { 69 | keyboardAccelerator.Modifiers = modifiers.Value; 70 | } 71 | 72 | keyboardAccelerator.Invoked += OnKeyboardAcceleratorInvoked; 73 | 74 | return keyboardAccelerator; 75 | } 76 | 77 | private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) 78 | { 79 | var navigationService = App.GetService(); 80 | 81 | var result = navigationService.GoBack(); 82 | 83 | args.Handled = result; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /VRCFaceTracking/Views/WarningBlock.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |