commit 47981d31e597c29ecab8183be2961eab7d411b3c Author: glax Date: Mon May 13 17:57:10 2024 +0200 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.idea/.idea.GlaxOSC/.idea/.gitignore b/.idea/.idea.GlaxOSC/.idea/.gitignore new file mode 100644 index 0000000..47dfd36 --- /dev/null +++ b/.idea/.idea.GlaxOSC/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/.idea.GlaxOSC.iml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.GlaxOSC/.idea/encodings.xml b/.idea/.idea.GlaxOSC/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.GlaxOSC/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.GlaxOSC/.idea/indexLayout.xml b/.idea/.idea.GlaxOSC/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.GlaxOSC/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.GlaxOSC/.idea/vcs.xml b/.idea/.idea.GlaxOSC/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.GlaxOSC/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GlaxOSC.sln b/GlaxOSC.sln new file mode 100644 index 0000000..00c8e47 --- /dev/null +++ b/GlaxOSC.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlaxOSC", "GlaxOSC\GlaxOSC.csproj", "{58612EF7-9968-4FD9-9236-7108692D3E6E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {58612EF7-9968-4FD9-9236-7108692D3E6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58612EF7-9968-4FD9-9236-7108692D3E6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58612EF7-9968-4FD9-9236-7108692D3E6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58612EF7-9968-4FD9-9236-7108692D3E6E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/GlaxOSC/AvatarConfig.cs b/GlaxOSC/AvatarConfig.cs new file mode 100644 index 0000000..d164c4c --- /dev/null +++ b/GlaxOSC/AvatarConfig.cs @@ -0,0 +1,11 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable CollectionNeverUpdated.Global +#pragma warning disable CS0169 // Field is never used + +namespace GlaxOSC; + +public struct AvatarConfig +{ + public string id, name; + public List parameters; +} \ No newline at end of file diff --git a/GlaxOSC/GlaxOSC.csproj b/GlaxOSC/GlaxOSC.csproj new file mode 100644 index 0000000..3f4933c --- /dev/null +++ b/GlaxOSC/GlaxOSC.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/GlaxOSC/OSC.cs b/GlaxOSC/OSC.cs new file mode 100644 index 0000000..18f2de2 --- /dev/null +++ b/GlaxOSC/OSC.cs @@ -0,0 +1,133 @@ +using System.Net; +using System.Text.Json; +using BuildSoft.OscCore; +using Microsoft.Extensions.Logging; +using VRC.OSCQuery; + +namespace GlaxOSC; + +public class OSC : IDisposable +{ + private readonly OSCQueryService _oscQuery; + private readonly OscServer _oscServer; + public readonly int OSCPort; + public readonly int TCPPort; + public readonly OscClient Client; + private readonly ILogger? _logger; + private bool _keepRunning = true; + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly Thread _listenThread; + private const int UpdateInterval = 10; + internal readonly Dictionary ParameterValues = new(); + + public OSC(string serviceName, Dictionary endpoints, string ipSend = "127.0.0.1", int portSend = 9000, int? portReceive = null, ILogger? logger = null) + { + this._logger = logger; + this.Client = new OscClient(ipSend, portSend); + this.OSCPort = portReceive ?? Extensions.GetAvailableUdpPort(); + this.TCPPort = Extensions.GetAvailableTcpPort(); + this._logger?.LogInformation("Setting up OSCQuery"); + this._oscQuery = new OSCQueryServiceBuilder() + .WithServiceName(serviceName) + .WithUdpPort(OSCPort) + .WithTcpPort(TCPPort) + .WithDefaults() + .Build(); + + this._oscServer = new OscServer(OSCPort); + this.AddEndpoint("/avatar/change", typeof(string)); + foreach (KeyValuePair endpoint in endpoints) + this.AddEndpoint(endpoint.Key, endpoint.Value); + this._oscServer.Start(); + + this._logger?.LogInformation($"TCP: {this._oscQuery.TcpPort} UDP/OSC: {portReceive}"); + + this._listenThread = new(Listen); + this._listenThread.Start(); + } + + private void Listen() + { + while (_keepRunning) + { + this._oscServer.Update(); + Thread.Sleep(UpdateInterval); + } + } + + public AvatarConfig GetAvatarConfig() + { + this._logger?.LogInformation("Waiting for Avatar-Config. Try to \"Reset Avatar.\""); + while (ParameterValues["/avatar/change"] is null) + Thread.Sleep(10); + string oscPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}Low\\VRChat\\VRChat\\OSC\\"; + DirectoryInfo di = new (oscPath); + string[] userPaths = di.GetDirectories().Select(dir => Path.Combine(oscPath, dir.Name, "\\Avatars\\")) + .ToArray(); + string userDir = userPaths.First(path => + { + DirectoryInfo avatarDirInfo = new(path); + if (avatarDirInfo.GetFiles().Any(file => file.Name.Contains((string)ParameterValues["/avatar/change"]!))) + return true; + return false; + }); + string configPath = Path.Combine(userDir, $"{(string)ParameterValues["/avatar/change"]!}.json"); + + string configJson = File.ReadAllText(configPath); + return JsonSerializer.Deserialize(configJson); + } + + public event OnParameterChangeEventHandler? OnParameterChangeEvent; + + public delegate void OnParameterChangeEventHandler(string endpoint, object? oldValue, object? newValue); + + internal void AddEndpoint(string endpoint, Type type) + { + this._logger?.LogDebug($"Adding endpoint {endpoint}"); + Dictionary oscTypeLookup = new Dictionary() + { + {typeof(int), "i"}, + {typeof(uint), "u"}, + {typeof(long), "h"}, + {typeof(float), "f"}, + {typeof(double), "d"}, + {typeof(string), "s"}, + {typeof(char), "c"}, + {typeof(Array), "[,]"}, + {typeof(byte[]), "b"}, + {typeof(bool), "T"}, + }; + this._oscQuery.AddEndpoint(endpoint, oscTypeLookup[type], Attributes.AccessValues.WriteOnly); + this.ParameterValues.Add(endpoint, null); + this._oscServer.TryAddMethod(endpoint, values => HandleParameterChange(endpoint, type, values)); + } + + private void HandleParameterChange(string endpoint, Type type, OscMessageValues values) + { + object? oldValue = ParameterValues[endpoint]; + if (type == typeof(int)) + ParameterValues[endpoint] = values.ReadIntElement(0); + else if (type == typeof(long)) + ParameterValues[endpoint] = values.ReadInt64Element(0); + else if (type == typeof(float)) + ParameterValues[endpoint] = values.ReadFloatElement(0); + else if (type == typeof(double)) + ParameterValues[endpoint] = values.ReadFloat64Element(0); + else if (type == typeof(string)) + ParameterValues[endpoint] = values.ReadStringElement(0); + else if (type == typeof(char)) + ParameterValues[endpoint] = values.ReadAsciiCharElement(0); + else if (type == typeof(bool)) + ParameterValues[endpoint] = values.ReadBooleanElement(0); + OnParameterChangeEvent?.Invoke(endpoint, oldValue, ParameterValues[endpoint]); + this._logger?.LogDebug($"{endpoint} {oldValue} -> {ParameterValues[endpoint]}"); + } + + public void Dispose() + { + this._logger?.LogDebug("Disposing"); + _keepRunning = false; + _oscQuery.Dispose(); + _oscServer.Dispose(); + } +} \ No newline at end of file diff --git a/GlaxOSC/Parameter.cs b/GlaxOSC/Parameter.cs new file mode 100644 index 0000000..859b3c8 --- /dev/null +++ b/GlaxOSC/Parameter.cs @@ -0,0 +1,11 @@ +// ReSharper disable InconsistentNaming +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +#pragma warning disable CS0169 // Field is never used + +namespace GlaxOSC; + +public struct Parameter +{ + public string name; + public ParameterValue input, output; +} \ No newline at end of file diff --git a/GlaxOSC/ParameterValue.cs b/GlaxOSC/ParameterValue.cs new file mode 100644 index 0000000..dfdb275 --- /dev/null +++ b/GlaxOSC/ParameterValue.cs @@ -0,0 +1,9 @@ +// ReSharper disable InconsistentNaming +#pragma warning disable CS0169 // Field is never used + +namespace GlaxOSC; + +public struct ParameterValue +{ + private string address, type; +} \ No newline at end of file