diff --git a/AppApi.cs b/AppApi.cs index eafafdbb..56049482 100644 --- a/AppApi.cs +++ b/AppApi.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; -using System.IO.Pipes; using System.Linq; using System.Net; using System.Net.Sockets; @@ -346,12 +345,20 @@ namespace VRCX public void IPCAnnounceStart() { - var ipcClient = new NamedPipeClientStream(".", "vrcx-ipc", PipeDirection.InOut); - ipcClient.Connect(); - if (!ipcClient.IsConnected) - return; - var buffer = Encoding.UTF8.GetBytes("{\"type\":\"VRCXLaunch\"}" + (char)0x00); - ipcClient.BeginWrite(buffer, 0, buffer.Length, IPCClient.OnSend, ipcClient); + IPCServer.Send(new IPCPacket + { + Type = "VRCXLaunch" + }); + } + + public void SendIpc(string type, string data) + { + IPCServer.Send(new IPCPacket + { + Type = "VrcxMessage", + MsgType = type, + Data = data + }); } public void ExecuteAppFunction(string function, string json) diff --git a/IPCClient.cs b/IPCClient.cs index 27bc4ba6..dd9fac46 100644 --- a/IPCClient.cs +++ b/IPCClient.cs @@ -4,23 +4,33 @@ // This work is licensed under the terms of the MIT license. // For a copy, see . -using CefSharp; -using Newtonsoft.Json; using System; -using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.IO.Pipes; using System.Text; +using System.Threading.Tasks; +using CefSharp; +using Newtonsoft.Json; namespace VRCX { internal class IPCClient { - private NamedPipeServerStream _ipcServer; - private byte[] _recvBuffer = new byte[1024 * 8]; + private static readonly UTF8Encoding noBomEncoding = new UTF8Encoding(false, false); + private readonly NamedPipeServerStream _ipcServer; + private readonly byte[] _recvBuffer = new byte[1024 * 8]; + private readonly MemoryStream memoryStream; + private readonly byte[] packetBuffer = new byte[1024 * 1024]; + private readonly Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); private string _currentPacket; public IPCClient(NamedPipeServerStream ipcServer) { + memoryStream = new MemoryStream(packetBuffer); + serializer.Culture = CultureInfo.InvariantCulture; + serializer.Formatting = Formatting.None; + _ipcServer = ipcServer; } @@ -29,6 +39,28 @@ namespace VRCX _ipcServer.BeginRead(_recvBuffer, 0, _recvBuffer.Length, OnRead, _ipcServer); } + public async Task Send(IPCPacket ipcPacket) + { + try + { + memoryStream.Seek(0, SeekOrigin.Begin); + using (var streamWriter = new StreamWriter(memoryStream, noBomEncoding, 65535, true)) + using (var writer = new JsonTextWriter(streamWriter)) + { + serializer.Serialize(writer, ipcPacket); + streamWriter.Write((char)0x00); + streamWriter.Flush(); + } + + var length = (int)memoryStream.Position; + _ipcServer?.BeginWrite(packetBuffer, 0, length, OnSend, null); + } + catch + { + IPCServer.Clients.Remove(this); + } + } + private void OnRead(IAsyncResult asyncResult) { try @@ -37,6 +69,7 @@ namespace VRCX if (bytesRead <= 0) { + IPCServer.Clients.Remove(this); _ipcServer.Close(); return; } @@ -69,7 +102,14 @@ namespace VRCX public static void OnSend(IAsyncResult asyncResult) { var ipcClient = (NamedPipeClientStream)asyncResult.AsyncState; - ipcClient.EndWrite(asyncResult); + ipcClient?.EndWrite(asyncResult); + } + + public static void Close(IAsyncResult asyncResult) + { + var ipcClient = (NamedPipeClientStream)asyncResult.AsyncState; + ipcClient?.EndWrite(asyncResult); + ipcClient?.Close(); } } } \ No newline at end of file diff --git a/IPCPacket.cs b/IPCPacket.cs new file mode 100644 index 00000000..29c3e047 --- /dev/null +++ b/IPCPacket.cs @@ -0,0 +1,9 @@ +namespace VRCX +{ + public class IPCPacket + { + public string Type { get; set; } + public string Data { get; set; } + public string MsgType { get; set; } + } +} \ No newline at end of file diff --git a/IPCServer.cs b/IPCServer.cs index 6d1b6d03..bb9c0967 100644 --- a/IPCServer.cs +++ b/IPCServer.cs @@ -5,13 +5,16 @@ // For a copy, see . using System; +using System.Collections.Generic; using System.IO.Pipes; +using System.Threading.Tasks; namespace VRCX { - class IPCServer + internal class IPCServer { public static readonly IPCServer Instance; + public static readonly List Clients = new List(); static IPCServer() { @@ -23,10 +26,18 @@ namespace VRCX new IPCServer().CreateIPCServer(); } + public static async Task Send(IPCPacket ipcPacket) + { + foreach (var client in Clients) + { + await client.Send(ipcPacket); + } + } + public void CreateIPCServer() { var ipcServer = new NamedPipeServerStream("vrcx-ipc", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); - ipcServer.BeginWaitForConnection(asyncResult => DoAccept(asyncResult), ipcServer); + ipcServer.BeginWaitForConnection(DoAccept, ipcServer); } private void DoAccept(IAsyncResult asyncResult) @@ -42,8 +53,10 @@ namespace VRCX Console.WriteLine(e); } - new IPCClient(ipcServer).BeginRead(); + var ipcClient = new IPCClient(ipcServer); + Clients.Add(ipcClient); + ipcClient.BeginRead(); CreateIPCServer(); } } -} +} \ No newline at end of file diff --git a/StartupArgs.cs b/StartupArgs.cs index cdfd0b7f..658cf35c 100644 --- a/StartupArgs.cs +++ b/StartupArgs.cs @@ -19,17 +19,17 @@ namespace VRCX public static void ArgsCheck() { - string[] args = Environment.GetCommandLineArgs(); + var args = Environment.GetCommandLineArgs(); processList = Process.GetProcessesByName("VRCX"); - bool isDebug = false; + var isDebug = false; Debug.Assert(isDebug = true); - - foreach (string arg in args) + + foreach (var arg in args) { if (arg.Contains("--gpufix")) Program.GPUFix = true; - + if (arg.Length > 12 && arg.Substring(0, 12) == "/uri=vrcx://") LaunchCommand = arg.Substring(12); @@ -40,7 +40,7 @@ namespace VRCX Program.LaunchDebug = true; } - if (processList.Length > 1 && String.IsNullOrEmpty(LaunchCommand)) + if (processList.Length > 1 && string.IsNullOrEmpty(LaunchCommand)) { var result = MessageBox.Show("VRCX is already running, start another instance?", "VRCX", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) @@ -63,7 +63,7 @@ namespace VRCX if (ipcClient.IsConnected) { var buffer = Encoding.UTF8.GetBytes($"{{\"type\":\"LaunchCommand\",\"command\":\"{LaunchCommand}\"}}" + (char)0x00); - ipcClient.BeginWrite(buffer, 0, buffer.Length, IPCClient.OnSend, ipcClient); + ipcClient.BeginWrite(buffer, 0, buffer.Length, IPCClient.Close, ipcClient); } } } diff --git a/VRCX.csproj b/VRCX.csproj index eb825f71..47e0e4f9 100644 --- a/VRCX.csproj +++ b/VRCX.csproj @@ -1,4 +1,4 @@ - + @@ -160,6 +160,7 @@ + PreserveNewest diff --git a/html/src/app.js b/html/src/app.js index 31974c30..66db5ebb 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -2583,7 +2583,15 @@ speechSynthesis.getVoices(); }; this.cachedAvatars.set(ref.id, ref); } else { + var {unityPackages} = ref; Object.assign(ref, json); + if ( + json.unityPackages.length > 0 && + unityPackages.length > 0 && + !json.unityPackages.assetUrl + ) { + ref.unityPackages = unityPackages; + } } ref.name = $app.replaceBioSymbols(ref.name); ref.description = $app.replaceBioSymbols(ref.description); @@ -10774,10 +10782,24 @@ speechSynthesis.getVoices(); } if (block === row.block && mute === row.mute) { // no change - type = ''; + if (type) { + this.addEntryPhotonEvent({ + photonId, + text: `Moderation ${text}`, + type: 'Moderation', + color: 'yellow', + created_at: gameLogDate + }); + } + return; } } - if (text) { + this.moderationAgainstTable.forEach((item) => { + if (item.userId === ref.id && item.type === type) { + removeFromArray(this.moderationAgainstTable, item); + } + }); + if (type) { this.addEntryPhotonEvent({ photonId, text: `Moderation ${text}`, @@ -10785,8 +10807,6 @@ speechSynthesis.getVoices(); color: 'yellow', created_at: gameLogDate }); - } - if (type) { var noty = { created_at: new Date().toJSON(), userId: ref.id, @@ -10800,12 +10820,9 @@ speechSynthesis.getVoices(); displayName: ref.displayName, type }; - this.moderationAgainstTable.forEach((item) => { - if (item.userId === ref.id && item.type === type) { - removeFromArray(this.moderationAgainstTable, item); - } - }); this.moderationAgainstTable.push(entry); + } + if (block || mute || block !== row.block || mute !== row.mute) { this.updateSharedFeed(true); } if (block || mute) { @@ -10816,7 +10833,7 @@ speechSynthesis.getVoices(); block, mute }); - } else { + } else if (row.block || row.mute) { database.deleteModeration(ref.id); } }); @@ -14875,6 +14892,9 @@ speechSynthesis.getVoices(); } ); } + if (this.ipcEnabled) { + AppApi.SendIpc('ShowUserDialog', userId); + } API.getCachedUser({ userId }) @@ -21617,6 +21637,8 @@ speechSynthesis.getVoices(); this.ipcEnabled = true; this.ipcTimeout = 60; // 30secs break; + case 'MsgPing': + break; case 'LaunchCommand': AppApi.FocusWindow(); this.eventLaunchCommand(data.command); @@ -21644,10 +21666,22 @@ speechSynthesis.getVoices(); $app.data.customUserTags = new Map(); $app.methods.addCustomTag = function (data) { - this.customUserTags.set(data.UserId, { - tag: data.Tag, + if (data.Tag) { + this.customUserTags.set(data.UserId, { + tag: data.Tag, + colour: data.TagColour + }); + } else { + this.customUserTags.delete(data.UserId); + } + var feedUpdate = { + userId: data.UserId, colour: data.TagColour - }); + }; + AppApi.ExecuteVrOverlayFunction( + 'updateHudFeedTag', + JSON.stringify(feedUpdate) + ); var ref = API.cachedUsers.get(data.UserId); if (typeof ref !== 'undefined') { ref.$customTag = data.Tag; diff --git a/html/src/vr.js b/html/src/vr.js index e7b4e6ae..ffca80d6 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -726,6 +726,15 @@ Vue.component('marquee-text', MarqueeText); this.cleanHudFeed(); }; + $app.methods.updateHudFeedTag = function (json) { + var ref = JSON.parse(json); + this.hudFeed.forEach((item) => { + if (item.userId === ref.userId) { + item.colour = ref.colour; + } + }); + }; + $app.data.hudTimeout = []; $app.methods.updateHudTimeout = function (json) {