using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; class Program { static string controlsPath = null; static string controlFile = null; static string statusFile = null; const int UDP_STATUS_PORT = 6000; // send statuses to GUI const int UDP_CONTROL_PORT = 6001; // receive controls from GUI static async Task Main(string[] args) { // first arg optionally path to controls dir controlsPath = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "controls"); controlFile = Path.Combine(controlsPath, "control.json"); statusFile = Path.Combine(controlsPath, "status.json"); Console.WriteLine("VRCFaceTracking simple module"); Console.WriteLine($"Controls dir: {controlsPath}"); Directory.CreateDirectory(controlsPath); // start UDP listener var cts = new CancellationTokenSource(); var listenerTask = Task.Run(() => UdpListenerLoop(cts.Token)); // start file watcher var watcherTask = Task.Run(() => FileWatcherLoop(cts.Token)); Console.WriteLine("Press Ctrl+C to exit..."); Console.CancelKeyPress += (s,e) => { e.Cancel = true; cts.Cancel(); }; try { await Task.WhenAll(listenerTask, watcherTask); } catch (OperationCanceledException) { } } static async Task UdpListenerLoop(CancellationToken token) { using var udp = new UdpClient(UDP_CONTROL_PORT); Console.WriteLine($"Listening for controls on UDP port {UDP_CONTROL_PORT}"); while (!token.IsCancellationRequested) { try { var res = await udp.ReceiveAsync().WaitAsync(token); var json = Encoding.UTF8.GetString(res.Buffer); Console.WriteLine("Received control JSON via UDP"); try { // save to control.json File.WriteAllText(controlFile, json); ApplyAndReport(json); } catch (Exception ex) { Console.WriteLine("Failed to write control.json: " + ex.Message); } } catch (OperationCanceledException) { break; } catch (Exception ex) { Console.WriteLine("UDP receive error: " + ex.Message); await Task.Delay(200, token); } } } static void ApplyAndReport(string controlJson) { try { using var doc = JsonDocument.Parse(controlJson); var root = doc.RootElement; // simple status mirror var status = new { TimestampUtc = DateTime.UtcNow.ToString("o"), Applied = true, AudiolinkEnabled = root.TryGetProperty("audiolinkEnabled", out var ae) ? ae.GetBoolean() : false, AudiolinkIntensity = root.TryGetProperty("audiolinkIntensity", out var ai) ? ai.GetDouble() : 0.0, AudiolinkColor = root.TryGetProperty("audiolinkColor", out var ac) ? ac.GetString() : null, Emotions = root.TryGetProperty("emotions", out var emos) ? emos : JsonDocument.Parse("{}").RootElement, MouthSliders = root.TryGetProperty("mouthSliders", out var ms) ? ms : JsonDocument.Parse("{}").RootElement, Ears = root.TryGetProperty("ears", out var ears) ? ears : JsonDocument.Parse("{}").RootElement }; var opts = new JsonSerializerOptions { WriteIndented = true }; var statusJson = JsonSerializer.Serialize(status, opts); File.WriteAllText(statusFile, statusJson); Console.WriteLine("Wrote status.json"); // also send status via UDP to UI port var udp = new UdpClient(); var bytes = Encoding.UTF8.GetBytes(statusJson); udp.Send(bytes, bytes.Length, "127.0.0.1", UDP_STATUS_PORT); udp.Dispose(); Console.WriteLine("Sent status via UDP"); } catch (Exception ex) { Console.WriteLine("ApplyAndReport error: " + ex.Message); } } static async Task FileWatcherLoop(CancellationToken token) { // if control.json already exists, try to read & report if (File.Exists(controlFile)) { try { var text = File.ReadAllText(controlFile); ApplyAndReport(text); } catch { } } using var watcher = new FileSystemWatcher(Path.GetDirectoryName(controlFile) ?? ".", Path.GetFileName(controlFile)); watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.FileName; var tcs = new TaskCompletionSource(); watcher.Changed += (s,e) => { try { var txt = File.ReadAllText(controlFile); ApplyAndReport(txt); } catch { } }; watcher.EnableRaisingEvents = true; try { await Task.Delay(Timeout.Infinite, token); } catch (OperationCanceledException) { } } }