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, IEnumerable 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 (OSCEndpoint endpoint in endpoints) this.AddEndpoint(endpoint); this._oscServer.Start(); this._logger?.LogInformation($"TCP: {this._oscQuery.TcpPort} UDP/OSC: {portReceive}"); this._listenThread = new(Listen); this._listenThread.Start(); } public OSC(string serviceName, Dictionary endpoints, string ipSend = "127.0.0.1", int portSend = 9000, int? portReceive = null, ILogger? logger = null) : this(serviceName, endpoints.Select(endpoint => new OSCEndpoint(endpoint.Key, endpoint.Value, null)), ipSend, portSend, portReceive, logger) { } 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(OSCEndpoint oscEndpoint) => AddEndpoint(oscEndpoint.Endpoint, oscEndpoint.Type, oscEndpoint.Callback); internal void AddEndpoint(string endpoint, Type type, Action? callback = null) { 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, callback)); } private void HandleParameterChange(string endpoint, Type type, OscMessageValues values, Action? callback) { 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]); callback?.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(); } }