├── .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 | [](https://discord.com/invite/vrcft)
7 |
8 | ## 🎥 Demo
9 |
10 | [](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 | 
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