1
0

Initial Commit

This commit is contained in:
glax 2024-05-13 17:57:10 +02:00
commit 47981d31e5
11 changed files with 230 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

13
.idea/.idea.GlaxOSC/.idea/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

16
GlaxOSC.sln Normal file
View File

@ -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

11
GlaxOSC/AvatarConfig.cs Normal file
View File

@ -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<Parameter> parameters;
}

14
GlaxOSC/GlaxOSC.csproj Normal file
View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BuildSoft.OscCore" Version="1.2.1.1" />
<PackageReference Include="VRChat.OSCQuery" Version="0.0.7" />
</ItemGroup>
</Project>

133
GlaxOSC/OSC.cs Normal file
View File

@ -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<string, object?> ParameterValues = new();
public OSC(string serviceName, Dictionary<string, Type> 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<string, Type> 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<AvatarConfig>(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<Type, string> oscTypeLookup = new Dictionary<Type, string>()
{
{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();
}
}

11
GlaxOSC/Parameter.cs Normal file
View File

@ -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;
}

View File

@ -0,0 +1,9 @@
// ReSharper disable InconsistentNaming
#pragma warning disable CS0169 // Field is never used
namespace GlaxOSC;
public struct ParameterValue
{
private string address, type;
}