diff --git a/IPCClient.cs b/IPCClient.cs index db9831a2..409d9817 100644 --- a/IPCClient.cs +++ b/IPCClient.cs @@ -5,18 +5,22 @@ // For a copy, see . using CefSharp; +using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.IO.Pipes; using System.Text; namespace VRCX { - class IPCClient + internal class IPCClient { private NamedPipeServerStream _ipcServer; private byte[] _recvBuffer = new byte[1024 * 8]; private string _currentPacket; + private VRCEventDeserialization.EventEntry _eventEntry; + private readonly VRCEventDeserialization eventDeserialization = new VRCEventDeserialization(); public IPCClient(NamedPipeServerStream ipcServer) { @@ -51,6 +55,34 @@ namespace VRCX if (string.IsNullOrEmpty(packet)) continue; + var eventData = System.Text.Json.JsonSerializer.Deserialize(packet); + if (eventData.type == "OnEvent" && eventData.OnEventData.Code == 6) + { + var byteArray = Convert.FromBase64String(eventData.OnEventData.Parameters[245].ToString()); + _eventEntry = eventDeserialization.DeserializeData(byteArray); + if (VRCEventDeserialization.ignoreEvents.Contains(_eventEntry.EventType)) + continue; + + _eventEntry.type = "VRCEvent"; + _eventEntry.dt = eventData.dt; + _eventEntry.senderId = eventData.OnEventData.Sender; + var jsonData = System.Text.Json.JsonSerializer.Serialize(_eventEntry); + MainForm.Instance.Browser.ExecuteScriptAsync("$app.ipcEvent", jsonData); + continue; + } + else if (eventData.type == "OnEvent" && eventData.OnEventData.Code == 7) + { + _eventEntry = new VRCEventDeserialization.EventEntry + { + type = "Event7", + dt = eventData.dt, + senderId = eventData.OnEventData.Sender + }; + var jsonData = System.Text.Json.JsonSerializer.Serialize(_eventEntry); + MainForm.Instance.Browser.ExecuteScriptAsync("$app.ipcEvent", jsonData); + continue; + } + MainForm.Instance.Browser.ExecuteScriptAsync("$app.ipcEvent", packet); } @@ -71,4 +103,4 @@ namespace VRCX ipcClient.EndWrite(asyncResult); } } -} +} \ No newline at end of file diff --git a/VRCEventDeserialization.cs b/VRCEventDeserialization.cs index cf7d63ac..208419ce 100644 --- a/VRCEventDeserialization.cs +++ b/VRCEventDeserialization.cs @@ -6,10 +6,11 @@ using System.Text; namespace VRCX { - class VRCEventDeserialization + internal class VRCEventDeserialization { private byte[] byteData; private int byteOffset; + private static readonly Dictionary DataType = new Dictionary { {2, typeof(byte)}, {3, typeof(double)}, @@ -29,10 +30,55 @@ namespace VRCX public class EventEntry { + public string type { get; set; } + public string dt { get; set; } + public int senderId { get; set; } + + public int Type { get; set; } public string EventType { get; set; } public object Data { get; set; } } + public class EventData + { + public string dt { get; set; } + public string type { get; set; } + public EventDataContainer OnEventData { get; set; } + public OperationContainer OnOperationResponseData { get; set; } + } + + public class EventDataContainer + { + public byte Code { get; set; } + public int Sender { get; set; } + public Dictionary Parameters { get; set; } + public byte SenderKey { get; set; } + public byte CustomDataKey { get; set; } + } + public class OperationContainer + { + public byte OperationCode { get; set; } + public short ReturnCode { get; set; } + public string DebugMessage { get; set; } + public Dictionary Parameters { get; set; } + } + + public static readonly List ignoreEvents = new List + { + "ReceiveVoiceStatsSyncRPC", + "initUSpeakSenderRPC", + "SanityCheck", + "UdonSyncRunProgramAsRPC", + "InformOfBadConnection", + "SetTimerRPC", + "IncrementPortalPlayerCountRPC", + "PlayEffect", + "PlayEmoteRPC", + "CancelRPC", + "_SendOnSpawn", + "RefreshAvatar", + "InternalApplyOverrideRPC" + }; private byte DeserializeByte() { @@ -142,34 +188,49 @@ namespace VRCX { case 1: return null; + case 2: return DeserializeByte(); + case 3: return DeserializeDouble(); + case 4: return DeserializeFloat(); + case 5: return DeserializeInt(); + case 6: return DeserializeShort(); + case 7: return DeserializeInt64(); + case 8: return DeserializeBool(); + case 9: return DeserializeString(); + case 10: return DeserializeObjectArray(); + case 11: return DeserializeTypeArray(); + case 100: return DeserializeVector2(); + case 101: return DeserializeVector3(); + case 102: return DeserializeVector4(); + case 103: return DeserializeQuaternion(); + default: throw new Exception("Ignoring data type: " + type); } @@ -205,4 +266,4 @@ namespace VRCX return eventEntry; } } -} +} \ No newline at end of file diff --git a/html/src/app.js b/html/src/app.js index 85199eef..88d5609a 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -8637,9 +8637,7 @@ speechSynthesis.getVoices(); }; $app.methods.parsePhotonEvent = function (data, gameLogDate) { - if (data.Code === 7) { - this.photonEvent7List.set(data.Sender, gameLogDate); - } else if (data.Code === 226) { + if (data.Code === 226) { // nothing } else if (data.Code === 253) { // SetUserProperties @@ -8831,41 +8829,16 @@ speechSynthesis.getVoices(); hasInstantiated: true }); } - } else if (data.Code === 6) { - // VRC Event - var senderId = data.Parameters[254]; - AppApi.DeserializeVrcEvent(data.Parameters[245]).then((json) => { - try { - var eventData = JSON.parse(json); - } catch { - console.error( - 'error parsing DeserializeVrcEvent json:', - json - ); - return; - } - if ( - eventData.EventType === 'ReceiveVoiceStatsSyncRPC' || - eventData.EventType === 'initUSpeakSenderRPC' || - eventData.EventType === 'SanityCheck' || - (eventData.EventType === 'UdonSyncRunProgramAsRPC' && - eventData.Data[0] !== 'Beep') || - eventData.EventType === 'InformOfBadConnection' || - eventData.EventType === 'SetTimerRPC' || - eventData.EventType === 'IncrementPortalPlayerCountRPC' || - eventData.EventType === 'PlayEffect' || - eventData.EventType === 'PlayEmoteRPC' || - eventData.EventType === 'CancelRPC' || - eventData.EventType === '_SendOnSpawn' || - eventData.EventType === 'RefreshAvatar' || - eventData.EventType === 'InternalApplyOverrideRPC' - ) { - // Trash - return; - } - if (this.debugPhotonLogging) { - console.log('VrcEvent:', eventData); - } + } + }; + + $app.methods.parseVRCEvent = function (eventData) { + // VRC Event + var senderId = eventData.senderId; + var datetime = eventData.dt; + if (this.debugPhotonLogging) { + console.log('VrcEvent:', eventData); + } if ( eventData.EventType === '_InstantiateObject' && eventData.Data[0] === 'Portals/PortalInternalDynamic' @@ -8875,102 +8848,85 @@ speechSynthesis.getVoices(); } else if ( eventData.EventType === '_DestroyObject' && this.lastPortalList.has(eventData.Data[0]) - ) { - var portalId = eventData.Data[0]; - var date = this.lastPortalList.get(portalId); - var time = timeToText(Date.parse(gameLogDate) - date); - this.addEntryPhotonEvent({ - photonId: senderId, - text: `DeletedPortal ${time}`, - type: 'DeletedPortal', - created_at: gameLogDate - }); - return; - } else if (eventData.EventType === 'ConfigurePortal') { + ) { + var portalId = eventData.Data[0]; + var date = this.lastPortalList.get(portalId); + var time = timeToText(Date.parse(datetime) - date); + this.addEntryPhotonEvent({ + photonId: senderId, + text: `DeletedPortal ${time}`, + type: 'DeletedPortal', + created_at: datetime + }); + return; + } else if (eventData.EventType === 'ConfigurePortal') { var instanceId = `${eventData.Data[0]}:${eventData.Data[1]}`; - if (this.lastPortalId) { - this.lastPortalList.set( - this.lastPortalId, - Date.parse(gameLogDate) - ); - this.lastPortalId = ''; - } + if (this.lastPortalId) { + this.lastPortalList.set( + this.lastPortalId, + Date.parse(datetime) + ); + this.lastPortalId = ''; + } var displayName = this.getDisplayNameFromPhotonId(senderId); if (displayName) { var ref1 = { - id: this.getUserIdFromPhotonId(senderId), - displayName - }; - this.parsePhotonPortalSpawn( - gameLogDate, - instanceId, - ref1 - ); - } - return; - } else if (eventData.Type > 34) { - var entry = { - created_at: gameLogDate, - type: 'Event', - data: `${displayName} called non existent RPC ${eventData.Type}` - }; + id: this.getUserIdFromPhotonId(senderId), + displayName + }; + this.parsePhotonPortalSpawn(datetime, instanceId, ref1); + } + return; + } else if (eventData.Type > 34) { + var entry = { + created_at: datetime, + type: 'Event', + data: `${displayName} called non existent RPC ${eventData.Type}` + }; this.addPhotonEventToGameLog(entry); } if (eventData.Type === 14) { if (eventData.EventType === 'ChangeVisibility') { if (eventData.Data[0] === true) { var text = 'EnableCamera'; - } else if (eventData.Data[0] === false) { - var text = 'DisableCamera'; - } - } else if ( - eventData.EventType === 'UdonSyncRunProgramAsRPC' && - eventData.Data[0] === 'Beep' - ) { - if (!this.isRpcWorld(this.lastLocation.location)) { - return; - } - var text = 'Beep'; - } else if ( - eventData.EventType === 'ReloadAvatarNetworkedRPC' - ) { - var text = 'AvatarReset'; - } else if (eventData.EventType === 'SpawnEmojiRPC') { - var text = `SpawnEmoji ${ - this.photonEmojis[eventData.Data] - }`; - } else { - var eventVrc = ''; - if (eventData.Data) { - eventVrc = ` ${JSON.stringify( - eventData.Data - ).replace(/"([^(")"]+)":/g, '$1:')}`; - } - var text = `${eventData.EventType}${eventVrc}`; - } - this.addEntryPhotonEvent({ - photonId: senderId, - text, - type: 'Event', - created_at: gameLogDate - }); - } else { - var eventType = ''; - if (eventData.EventType) { - eventType = ` ${JSON.stringify( - eventData.EventType - ).replace(/"([^(")"]+)":/g, '$1:')}`; - } - if (this.debugPhotonLogging) { - var displayName = - this.getDisplayNameFromPhotonId(senderId); - var feed = `RPC ${displayName} ${ - this.photonEventType[eventData.Type] - }${eventType}`; - console.log('VrcRpc:', feed); - } + } else if (eventData.Data[0] === false) { + var text = 'DisableCamera'; } + } else if (eventData.EventType === 'ReloadAvatarNetworkedRPC') { + var text = 'AvatarReset'; + } else if (eventData.EventType === 'SpawnEmojiRPC') { + var text = `SpawnEmoji ${this.photonEmojis[eventData.Data]}`; + } else { + var eventVrc = ''; + if (eventData.Data) { + eventVrc = ` ${JSON.stringify(eventData.Data).replace( + /"([^(")"]+)":/g, + '$1:' + )}`; + } + var text = `${eventData.EventType}${eventVrc}`; + } + this.addEntryPhotonEvent({ + photonId: senderId, + text, + type: 'Event', + created_at: datetime }); + } else { + var eventType = ''; + if (eventData.EventType) { + eventType = ` ${JSON.stringify(eventData.EventType).replace( + /"([^(")"]+)":/g, + '$1:' + )}`; + } + if (this.debugPhotonLogging) { + var displayName = this.getDisplayNameFromPhotonId(senderId); + var feed = `RPC ${displayName} ${ + this.photonEventType[eventData.Type] + }${eventType}`; + console.log('VrcRpc:', feed); + } } }; @@ -18797,20 +18753,14 @@ speechSynthesis.getVoices(); } switch (data.type) { case 'OnEvent': - if ( - this.debugPhotonLogging && - data.OnEventData.Code !== 6 && - data.OnEventData.Code !== 7 - ) { + if (this.debugPhotonLogging) { console.log( 'OnEvent', data.OnEventData.Code, data.OnEventData ); } - if (data.OnEventData.Code !== 7) { - this.photonEventPulse(); - } + this.photonEventPulse(); this.parsePhotonEvent(data.OnEventData, data.dt); break; case 'OnOperationResponse': @@ -18827,6 +18777,13 @@ speechSynthesis.getVoices(); ); this.photonEventPulse(); break; + case 'VRCEvent': + this.photonEventPulse(); + this.parseVRCEvent(data); + break; + case 'Event7': + this.photonEvent7List.set(data.senderId, data.dt); + break; case 'Ping': if (!this.photonLoggingEnabled) { this.photonLoggingEnabled = true;