Photon logging support

This commit is contained in:
Natsumi
2021-11-20 23:53:37 +13:00
parent 1c79103c67
commit 3a102a2c2c
14 changed files with 2367 additions and 164 deletions

View File

@@ -18,7 +18,6 @@ using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
using librsync.net;
using System.Net.Sockets;
using System.Text.Json.Serialization;
namespace VRCX
{
@@ -232,6 +231,11 @@ namespace VRCX
return CpuMonitor.Instance.CpuUsage;
}
public string GetImage(string url, string fileId, string version)
{
return ImageCache.GetImage(url, fileId, version);
}
public void DesktopNotification(string BoldText, string Text, string Image)
{
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02);
@@ -239,8 +243,7 @@ namespace VRCX
String imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico");
if (!String.IsNullOrEmpty(Image))
{
imagePath = Path.Combine(Program.AppDataDirectory, "cache\\toast");
File.WriteAllBytes(imagePath, Convert.FromBase64String(Image));
imagePath = Image;
}
stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText));
stringElements[1].AppendChild(toastXml.CreateTextNode(Text));
@@ -274,12 +277,11 @@ namespace VRCX
{
UseBase64Icon = true;
Icon = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHaGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wNC0wOFQxNjozMzoxMCsxMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1NTE2MWIyMi1hYzgxLTY3NDYtODAyYi1kODIzYWFmN2RjYjciIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3ZjJjNTA2ZS02YTVhLWRhNGEtOTg5Mi02NDZiMzQ0MGQxZTgiPiA8cGhvdG9zaG9wOkRvY3VtZW50QW5jZXN0b3JzPiA8cmRmOkJhZz4gPHJkZjpsaT5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NmJmOGE5MTgtY2QzZS03OTRjLTk3NzktMzM0YjYwZWJiNTYyPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6N2YyYzUwNmUtNmE1YS1kYTRhLTk4OTItNjQ2YjM0NDBkMWU4IiBzdEV2dDp3aGVuPSIyMDIxLTA0LTA4VDE0OjU3OjAxKzEyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhM2ZjODI3LTM0ZjQtYjU0OC05ZGFiLTZhMTZlZmQzZjAxMSIgc3RFdnQ6d2hlbj0iMjAyMS0wNC0wOFQxNTowMTozMSsxMjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YTY5MmQzYi03ZTJkLTNiNGUtYTMzZC1hN2MwOTNlOGU0OTkiIHN0RXZ0OndoZW49IjIwMjEtMDQtMDhUMTY6MzM6MTArMTI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4XAd9sAAAFM0lEQVR42u2aWUhjVxjHjVpf3Iraoh3c4ksFx7ZYahV8EHEBqdQHFdsHQRRxpcyDIDNFpdSK+iBKUcTpmy/iglVrtT4oYsEq7hP3RGXcqqY6invy9Xy3OdPEE5PY5pKb5P7hTyA5y/1+Ofc7y70OAOBgz3YQAYgARAAiABGACEAEIAIQAYgADBT6V4HErcRbxCAwy4nriN/DC+UDADb8swADv++fiN3MDeAJ8be0k9HRUbi4uACUWq22qFFvzt5AZ1enNoSvzJ4DiJ5j412dXSBUVf9QTQH08gHgF2x8b2/P0nGqNGa0ML9AAazyAeA3bPzg4MDoFV5fX8PZ2RlcXl7qGL83JjKsVeT2UpHyaqxzdXXFtUVvOVpMYx3JFfK3CZEPAL9i4/v7+0aDwDL5+fmQl5cHBQUFnHNzc6GsrAzW19cNBQ8dHR3q7OxsFamvxnrFxcWQnp4O4+PjRvtdW1ujANYtCgBVWlqqN0vn5ORw/6o+TU1Nga+vL1MnMTERtre3rQvA3d0dZGZmMsG4ublBW1sbU/7k5ATi4+OZ8uHh4bC5uWlSn4ICQC/I39+fCSo0NBRWV1d1M3h1NVPOw8MDenp6HtWfoACg8N92dnZmgisqKuISI2pkZAS8vLyYMngb3dzcWDcAvBUKCwuZ4FxdXWFwcJDLB1FRUczvcXFxcHx8/Ki+BAkAtbW1BZGRkUyQsbGx3Gzh5OSk831QUJBJWd9qAKD6+/vB29tbJ1CJRMIE7+7uDk1NTf+pD0EDwFuhoqKCC9rQZiYrKwtub29tDwBqZ2cHUlNTwdHRkQkcwURHRxtcKFk9ANTAwAB4enoyAHCmqKys/F9tCx4ATnuY9B4a/mFhYTA3N2e7AFpaWoweaKSkpHCbH5sDMDMzw01vxgC4uLhAfX29oAHo3Yoa0vn5OSQnJzPBZmRkQFpaGjMz+Pn5wdjYmGAB3D0WQG1tLRM8Bjk7OwsKhQICAwOZ3xMSEkw6e7AEANVjAAwPD3ObmvsBVlVVgUr1z8FOQ0MD8zsukMrLyx+1JhBcDtjd3YWIiAgmOLwdtP9dTHpJSUl6d4M4bVolADzdKSkpYYIKCAjgdn/3NT8/Dz4+Pkz5mJgYkw5DBAUAh3ZzczOzDcYVYE1NzYNL5bq6Or1LZVw7nJ6eWg8APMHBRQ0ehkilUggODuaSHp4QGdriHh0dcTMDlsV6ISEhXF0cGb29vRYHMGTqqTCWmZiYgKWlJVheXgaZTMatAw4PD43WVSqVMD09zdVD48kRtiWXy98mzYe0Id+gADb4ADCMjSuPlYJ9MKLYVFAAm3wAaMbGFxcXBQugu7ubAviDDwCfY+N4Ro/DVGjCmUIrcX7P1+PxfdpJ68tWGBoagr7+PrMZH3DiwglnBGPCtQOWxeSIM45W8IvEUr4AfEG8xPcj7sbGRqMAVpZX9NWdIv6Ur/cDqD4k/o64j/h34jEzeUTTHhdMX2+fQQCyVzIa9KXmwe0z4hB6kXwCQL2DLyEQ+xK/byZ7EfsRN1AICwsLDwLAKVZTDkfkZ8RO2hfINwA+9YQ+iUYf/nloDADe80/vN2LNAFCRxGsYx4vnL/QmRS0Ar4g/sjUAqC/pKGhvb7dLAKhyCmFyctIuAbxL3EEhaL+eowVARvyxrQJASYlnKAS6IbInAKg44lMKAYU7Ra1p8BNbB4D6hvgGY8MlMG6PNQBWiCPsAYAL8Y96lr+4ivzAHgDQpPiS+EwikfxFPl8Tf00s4RWA+Lq8CEAEIAIQAYgARAA26b8BaVJkoY+4rDoAAAAASUVORK5CYII=";
}
}
else
{
UseBase64Icon = false;
Icon = Path.Combine(Program.AppDataDirectory, "cache\\toast");
File.WriteAllBytes(Icon, Convert.FromBase64String(Image));
Icon = Image;
}
IPAddress broadcastIP = IPAddress.Parse("127.0.0.1");
@@ -306,16 +308,16 @@ namespace VRCX
var Location = Path.Combine(Program.AppDataDirectory, "update.exe");
WebClient client = new WebClient();
client.Headers.Add("user-agent", AppVersion);
client.DownloadFile(new System.Uri(url), Location);
client.DownloadFile(new Uri(url), Location);
}
public void RestartApplication()
{
System.Diagnostics.Process VRCXProcess = new System.Diagnostics.Process();
Process VRCXProcess = new Process();
VRCXProcess.StartInfo.FileName = Path.Combine(Program.BaseDirectory, "VRCX.exe");
VRCXProcess.StartInfo.UseShellExecute = false;
VRCXProcess.Start();
System.Environment.Exit(0);
Environment.Exit(0);
}
public bool CheckForUpdateExe()

53
ImageCache.cs Normal file
View File

@@ -0,0 +1,53 @@
using System;
using System.IO;
using System.Net;
using System.Linq;
namespace VRCX
{
class ImageCache
{
private static readonly string cacheLocation = Path.Combine(Program.AppDataDirectory, "ImageCache");
public static string GetImage(string url, string fileId, string version)
{
var directoryLocation = Path.Combine(cacheLocation, fileId);
var fileLocation = Path.Combine(directoryLocation, $"{version}.png");
if (File.Exists(fileLocation))
{
Directory.SetLastWriteTime(directoryLocation, DateTime.Now);
return fileLocation;
}
if (Directory.Exists(directoryLocation))
Directory.Delete(directoryLocation, true);
Directory.CreateDirectory(directoryLocation);
using (var client = new WebClient())
{
client.Headers.Add("user-agent", "VRCX");
client.DownloadFile(url, fileLocation);
}
int cacheSize = Directory.GetDirectories(cacheLocation).Length;
if (cacheSize > 1100)
CleanImageCache();
return fileLocation;
}
private static void CleanImageCache()
{
DirectoryInfo dirInfo = new DirectoryInfo(cacheLocation);
var folders = dirInfo.GetDirectories().OrderBy(p => p.LastWriteTime);
int i = 0;
foreach (DirectoryInfo folder in folders.Reverse())
{
i++;
if (i > 1000)
folder.Delete(true);
}
}
}
}

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
namespace VRCX
@@ -22,6 +23,11 @@ namespace VRCX
public string RecentWorldName;
public string LastVideoURL;
public bool ShaderKeywordsLimitReached = false;
public bool incomingJson;
public string jsonChunk;
public string jsonDate;
public string onJoinPhotonDisplayName;
public string photonEvent;
}
public static readonly LogWatcher Instance;
@@ -33,6 +39,7 @@ namespace VRCX
private bool m_ResetLog;
private bool m_FirstRun = true;
private static DateTime tillDate = DateTime.Now;
private static IDictionary<int, string> photonEvent7 = new Dictionary<int, string>();
// NOTE
// FileSystemWatcher() is unreliable
@@ -183,6 +190,21 @@ namespace VRCX
break;
}
if (logContext.incomingJson)
{
logContext.jsonChunk += line;
if (line == "}}")
{
var data = logContext.jsonChunk.Replace("{{", "{").Replace("}}", "}");
ParseLogPhotonEvent(fileInfo, data, logContext.jsonDate, logContext.photonEvent);
logContext.incomingJson = false;
logContext.jsonChunk = String.Empty;
logContext.jsonDate = String.Empty;
logContext.photonEvent = String.Empty;
}
continue;
}
// 2020.10.31 23:36:28 Log - [VRCFlowManagerVRC] Destination fetching: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd
// 2021.02.03 10:18:58 Log - [DŽDŽDžDžDžDŽDŽDžDžDŽDžDžDžDžDŽDŽDŽDžDžDŽDŽDžDžDžDžDŽDžDžDžDžDŽDŽDŽDŽDŽDžDŽDžDŽDŽDŽDžDžDŽDžDžDž] Destination fetching: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd
@@ -209,7 +231,21 @@ namespace VRCX
var offset = 34;
if (line[offset] == '[')
{
if (ParseLogOnPlayerJoinedOrLeft(fileInfo, logContext, line, offset) == true ||
if (string.Compare(line, offset, "[Network Data] OnEvent: PLAYER: ", 0, 33, StringComparison.Ordinal) == 0)
{
logContext.photonEvent = line.Substring(offset + 33);
logContext.incomingJson = true;
logContext.jsonChunk = String.Empty;
logContext.jsonDate = ConvertLogTimeToISO8601(line);
}
else if (string.Compare(line, offset, "[Network Data] OnEvent: SYSTEM ", 0, 31, StringComparison.Ordinal) == 0)
{
logContext.photonEvent = line.Substring(offset + 31);
logContext.incomingJson = true;
logContext.jsonChunk = String.Empty;
logContext.jsonDate = ConvertLogTimeToISO8601(line);
}
else if (ParseLogOnPlayerJoinedOrLeft(fileInfo, logContext, line, offset) == true ||
ParseLogLocation(fileInfo, logContext, line, offset) == true ||
ParseLogLocationDestination(fileInfo, logContext, line, offset) == true ||
ParseLogPortalSpawn(fileInfo, logContext, line, offset) == true ||
@@ -218,8 +254,9 @@ namespace VRCX
ParseLogJoinBlocked(fileInfo, logContext, line, offset) == true ||
ParseLogAvatarPedestalChange(fileInfo, logContext, line, offset) == true ||
ParseLogVideoError(fileInfo, logContext, line, offset) == true ||
ParseLogVideoPlay(fileInfo, logContext, line, offset) == true ||
ParseLogWorldVRCX(fileInfo, logContext, line, offset) == true)
ParseLogVideoChange(fileInfo, logContext, line, offset) == true ||
ParseLogWorldVRCX(fileInfo, logContext, line, offset) == true ||
ParseLogPhotonId(fileInfo, logContext, line, offset) == true)
{
continue;
}
@@ -289,16 +326,26 @@ namespace VRCX
// 2021.02.03 10:18:58 Log - [DŽDŽDžDžDžDŽDŽDžDžDŽDžDžDžDžDŽDŽDŽDžDžDŽDŽDžDžDžDžDŽDžDžDžDžDŽDŽDŽDŽDŽDžDŽDžDŽDŽDŽDžDžDŽDžDžDž] Destination fetching: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd
// 2021.06.23 12:02:56 Log - [Behaviour] Entering Room: VRChat Home
if (string.Compare(line, offset, "[Behaviour] Entering Room: ", 0, 27, StringComparison.Ordinal) == 0)
if (line.Contains("] Entering Room: "))
{
var worldName = line.Substring(offset + 27);
var lineOffset = line.LastIndexOf("] Entering Room: ");
if (lineOffset < 0)
return false;
lineOffset += 17;
var worldName = line.Substring(lineOffset);
logContext.RecentWorldName = worldName;
return true;
}
if (string.Compare(line, offset, "[Behaviour] Joining wrld_", 0, 25, StringComparison.Ordinal) == 0)
if (line.Contains("] Joining wrld_"))
{
var location = line.Substring(offset + 20);
var lineOffset = line.LastIndexOf("] Joining ");
if (lineOffset < 0)
return false;
lineOffset += 10;
var location = line.Substring(lineOffset);
AppendLog(new[]
{
@@ -309,6 +356,9 @@ namespace VRCX
logContext.RecentWorldName
});
photonEvent7 = new Dictionary<int, string>();
logContext.onJoinPhotonDisplayName = String.Empty;
return true;
}
@@ -320,9 +370,14 @@ namespace VRCX
// 2021.09.02 00:02:12 Log - [Behaviour] Destination set: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd:15609~private(usr_032383a7-748c-4fb2-94e4-bcb928e5de6b)~nonce(72CC87D420C1D49AEFFBEE8824C84B2DF0E38678E840661E)
// 2021.09.02 00:49:15 Log - [Behaviour] Destination fetching: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd
if (string.Compare(line, offset, "[Behaviour] Destination fetching: ", 0, 34, StringComparison.Ordinal) == 0)
if (line.Contains("] Destination fetching: "))
{
var location = line.Substring(offset + 34);
var lineOffset = line.LastIndexOf("] Destination fetching: ");
if (lineOffset < 0)
return false;
lineOffset += 24;
var location = line.Substring(lineOffset);
AppendLog(new[]
{
@@ -335,20 +390,6 @@ namespace VRCX
return true;
}
// 2021.10.04 11:54:16 Log - [Behaviour] OnLeftRoom
if (string.Compare(line, offset, "[Behaviour] OnLeftRoom", 0, 22, StringComparison.Ordinal) == 0)
{
AppendLog(new[]
{
fileInfo.Name,
ConvertLogTimeToISO8601(line),
"location-destination"
});
return true;
}
return false;
}
@@ -365,16 +406,19 @@ namespace VRCX
// 2021.06.23 11:41:16 Log - [Behaviour] Initialized PlayerAPI "Natsumi-sama" is local
if (string.Compare(line, offset, "[Behaviour] Initialized PlayerAPI \"", 0, 35, StringComparison.Ordinal) == 0)
if (line.Contains("] Initialized PlayerAPI \""))
{
var lineOffset = line.LastIndexOf("] Initialized PlayerAPI \"");
if (lineOffset < 0)
return false;
lineOffset += 25;
var pos = line.LastIndexOf("\" is ");
if (pos < 0)
{
return false;
}
var userDisplayName = line.Substring(offset + 35, pos - (offset + 35));
var userType = line.Substring(pos + 5);
var userDisplayName = line.Substring(lineOffset, pos - lineOffset);
AppendLog(new[]
{
@@ -388,25 +432,14 @@ namespace VRCX
return true;
}
// fallback method
/*if (string.Compare(line, offset, "OnPlayerJoined ", 0, 15, StringComparison.Ordinal) == 0)
if (line.Contains("] OnPlayerLeft "))
{
var userDisplayName = line.Substring(offset + 15);
var lineOffset = line.LastIndexOf("] OnPlayerLeft ");
if (lineOffset < 0)
return false;
lineOffset += 15;
AppendLog(new[]
{
fileInfo.Name,
ConvertLogTimeToISO8601(line),
"player-joined",
userDisplayName
});
return true;
}*/
if (string.Compare(line, offset, "[Behaviour] OnPlayerLeft ", 0, 25, StringComparison.Ordinal) == 0)
{
var userDisplayName = line.Substring(offset + 25);
var userDisplayName = line.Substring(lineOffset);
AppendLog(new[]
{
@@ -427,22 +460,16 @@ namespace VRCX
// 2021.04.06 11:25:45 Log - [Network Processing] RPC invoked ConfigurePortal on (Clone [1600004] Portals/PortalInternalDynamic) for Natsumi-sama
// 2021.07.19 04:24:28 Log - [Behaviour] Will execute SendRPC/AlwaysBufferOne on (Clone [100004] Portals/PortalInternalDynamic) (UnityEngine.GameObject) for Natsumi-sama: S: "ConfigurePortal" I: 7 F: 0 B: 255 (local master owner)
if (string.Compare(line, offset, "[Behaviour] Will execute SendRPC/AlwaysBufferOne on (Clone [", 0, 60, StringComparison.Ordinal) != 0)
{
if (!line.Contains("] Will execute SendRPC/AlwaysBufferOne on (Clone ["))
return false;
}
var pos = line.LastIndexOf("] Portals/PortalInternalDynamic) (UnityEngine.GameObject) for ");
if (pos < 0)
{
return false;
}
var endPos = line.LastIndexOf(": S: \"ConfigurePortal\"");
if (endPos < 0)
{
return false;
}
var data = line.Substring(pos + 62, endPos - (pos + 62));
@@ -462,36 +489,32 @@ namespace VRCX
// 2021.04.04 12:21:06 Error - Maximum number (256) of shader keywords exceeded, keyword _TOGGLESIMPLEBLUR_ON will be ignored.
// 2021.08.20 04:20:69 Error - Maximum number (384) of shader global keywords exceeded, keyword _FOG_EXP2 will be ignored.
if (logContext.ShaderKeywordsLimitReached == true)
if (line.Contains("Maximum number (384) of shader global keywords exceeded"))
{
return false;
if (logContext.ShaderKeywordsLimitReached == true)
return true;
AppendLog(new[]
{
fileInfo.Name,
ConvertLogTimeToISO8601(line),
"event",
"Shader Keyword Limit has been reached"
});
logContext.ShaderKeywordsLimitReached = true;
return true;
}
if (string.Compare(line, offset, "Maximum number (384) of shader global keywords exceeded", 0, 55, StringComparison.Ordinal) != 0)
{
return false;
}
AppendLog(new[]
{
fileInfo.Name,
ConvertLogTimeToISO8601(line),
"event",
"Shader Keyword Limit has been reached"
});
logContext.ShaderKeywordsLimitReached = true;
return true;
return false;
}
private bool ParseLogJoinBlocked(FileInfo fileInfo, LogContext logContext, string line, int offset)
{
// 2021.04.07 09:34:37 Error - [Behaviour] Master is not sending any events! Moving to a new instance.
if (string.Compare(line, offset, "[Behaviour] Master is not sending any events! Moving to a new instance.", 0, 71, StringComparison.Ordinal) != 0)
{
if (!line.Contains("] Master is not sending any events! Moving to a new instance."))
return false;
}
AppendLog(new[]
{
@@ -500,6 +523,7 @@ namespace VRCX
"event",
"Joining instance blocked by master"
});
logContext.ShaderKeywordsLimitReached = true;
return true;
}
@@ -509,9 +533,7 @@ namespace VRCX
// 2021.05.07 10:48:19 Log - [Network Processing] RPC invoked SwitchAvatar on AvatarPedestal for User
if (string.Compare(line, offset, "[Network Processing] RPC invoked SwitchAvatar on AvatarPedestal for ", 0, 68, StringComparison.Ordinal) != 0)
{
return false;
}
var data = line.Substring(offset + 68);
@@ -532,9 +554,7 @@ namespace VRCX
// 2021.04.08 06:40:07 Error - [Video Playback] ERROR: Private video
if (string.Compare(line, offset, "[Video Playback] ERROR: ", 0, 24, StringComparison.Ordinal) != 0)
{
return false;
}
var data = line.Substring(offset + 24);
@@ -549,29 +569,20 @@ namespace VRCX
return true;
}
private bool ParseLogVideoPlay(FileInfo fileInfo, LogContext logContext, string line, int offset)
private bool ParseLogVideoChange(FileInfo fileInfo, LogContext logContext, string line, int offset)
{
// 2021.04.20 13:37:69 Log - [Video Playback] Attempting to resolve URL 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
if (string.Compare(line, offset, "[Video Playback] Attempting to resolve URL '", 0, 44, StringComparison.Ordinal) != 0)
{
return false;
}
var pos = line.LastIndexOf("'");
if (pos < 0)
{
return false;
}
var data = line.Substring(offset + 44);
data = data.Remove(data.Length - 1);
if (logContext.LastVideoURL == data)
{
return false;
}
logContext.LastVideoURL = data;
AppendLog(new[]
{
fileInfo.Name,
@@ -610,23 +621,18 @@ namespace VRCX
// 2021.04.23 13:12:25 Log - User Natsumi-sama added URL https://www.youtube.com/watch?v=dQw4w9WgXcQ
if (string.Compare(line, offset, "User ", 0, 5, StringComparison.Ordinal) != 0)
{
return false;
}
var pos = line.LastIndexOf(" added URL ");
if (pos < 0)
{
return false;
}
var displayName = line.Substring(offset + 5, pos - (offset + 5));
var playerPlayer = line.Substring(offset + 5, pos - (offset + 5));
var data = line.Substring(pos + 11);
if (logContext.LastVideoURL == data)
{
return false;
}
logContext.LastVideoURL = data;
AppendLog(new[]
@@ -635,7 +641,7 @@ namespace VRCX
ConvertLogTimeToISO8601(line),
"video-play",
data,
displayName
playerPlayer
});
return true;
@@ -646,15 +652,11 @@ namespace VRCX
// 2021.01.03 05:48:58 Log - [API] Received Notification: < Notification from username:pypy, sender user id:usr_4f76a584-9d4b-46f6-8209-8305eb683661 to of type: friendRequest, id: not_3a8f66eb-613c-4351-bee3-9980e6b5652c, created at: 01/14/2021 15:38:40 UTC, details: {{}}, type:friendRequest, m seen:False, message: ""> received at 01/02/2021 16:48:58 UTC
if (string.Compare(line, offset, "[API] Received Notification: <", 0, 30, StringComparison.Ordinal) != 0)
{
return false;
}
var pos = line.LastIndexOf("> received at ");
if (pos < 0)
{
return false;
}
var data = line.Substring(offset + 30, pos - (offset + 30));
@@ -675,15 +677,11 @@ namespace VRCX
// 2021.10.03 09:48:43 Log - [API] [101] Sending Get request to https://api.vrchat.cloud/api/1/users/usr_032383a7-748c-4fb2-94e4-bcb928e5de6b?apiKey=JlE5Jldo5Jibnk5O5hTx6XVqsJu4WJ26&organization=vrchat
if (string.Compare(line, offset, "[API] [", 0, 7, StringComparison.Ordinal) != 0)
{
return false;
}
var pos = line.LastIndexOf("] Sending Get request to ");
if (pos < 0)
{
return false;
}
var data = line.Substring(pos + 25);
@@ -698,6 +696,174 @@ namespace VRCX
return true;
}
private bool ParseLogPhotonId(FileInfo fileInfo, LogContext logContext, string line, int offset)
{
// 2021.11.02 02:21:41 Log - [Behaviour] Configuring remote player VRCPlayer[Remote] 22349737 1194
// 2021.11.02 02:21:41 Log - [Behaviour] Initialized player Natsumi-sama
// 2021.11.10 08:06:12 Log - [Behaviour] Natsumi-sama: Limb IK
// 2021.11.10 08:06:12 Log - [Behaviour] NatsumiDa: 3 Point IK
// 2021.11.10 08:10:28 Log - [Behaviour] Initialize Limb Avatar (UnityEngine.Animator) VRCPlayer[Remote] 78614426 59 (DŽDŽDŽDžDŽDžDžDŽDžDŽDŽDžDžDŽDžDŽDžDžDžDŽDŽDŽDžDŽDŽDžDžDŽDžDžDŽDžDžDŽDžDžDžDžDŽDžDŽDžDŽDŽDŽDŽDž) False Loading
// 2021.11.10 08:57:32 Log - [Behaviour] Initialize Limb Avatar (UnityEngine.Animator) VRCPlayer[Local] 59136629 1 (DŽDŽDŽDžDŽDžDžDŽDžDŽDŽDžDžDŽDžDŽDžDžDžDŽDŽDŽDžDŽDŽDžDžDŽDžDžDŽDžDžDŽDžDžDžDžDŽDžDŽDžDŽDŽDŽDŽDž) True Loading
if (line.Contains("] Initialize ") && line.Contains(" Avatar (UnityEngine.Animator) VRCPlayer["))
{
var pos = -1;
if (line.Contains(" Avatar (UnityEngine.Animator) VRCPlayer[Remote] "))
{
pos = line.LastIndexOf(" Avatar (UnityEngine.Animator) VRCPlayer[Remote] ");
pos += 49;
}
if (line.Contains(" Avatar (UnityEngine.Animator) VRCPlayer[Local] "))
{
pos = line.LastIndexOf(" Avatar (UnityEngine.Animator) VRCPlayer[Local] ");
pos += 48;
}
if (pos < 0)
return false;
if (!String.IsNullOrEmpty(logContext.onJoinPhotonDisplayName))
{
var endPos = line.LastIndexOf(" (");
var photonId = line.Substring(pos + 9, endPos - (pos + 9));
AppendLog(new[]
{
fileInfo.Name,
ConvertLogTimeToISO8601(line),
"photon-id",
logContext.onJoinPhotonDisplayName,
photonId
});
logContext.onJoinPhotonDisplayName = String.Empty;
return true;
}
}
if (line.Contains(": 3 Point IK") || line.Contains(": Limb IK"))
{
var lineOffset = line.IndexOf("] ");
if (lineOffset < 0)
return false;
lineOffset += 2;
if (line.Contains(": 3 Point IK"))
{
var endPos = line.LastIndexOf(": 3 Point IK");
logContext.onJoinPhotonDisplayName = line.Substring(lineOffset, endPos - lineOffset);
return true;
}
if (line.Contains(": Limb IK"))
{
var endPos = line.LastIndexOf(": Limb IK");
logContext.onJoinPhotonDisplayName = line.Substring(lineOffset, endPos - lineOffset);
return true;
}
}
return false;
}
public class VrcEvent
{
public int Code { get; set; }
public Parameters Parameters { get; set; }
public int SenderKey { get; set; }
public int CustomDataKey { get; set; }
public int Type { get; set; }
public string EventType { get; set; }
public Object Data { get; set; }
}
public class Parameters
{
[JsonPropertyName("245")]
public _245 _245 { get; set; }
[JsonPropertyName("254")]
public int _254 { get; set; }
}
public class _245
{
[JsonPropertyName("$type")]
public string Type { get; set; }
[JsonPropertyName("$value")]
public string Value { get; set; }
}
private void ParseLogPhotonEvent(FileInfo fileInfo, string data, string date, string photonEvent)
{
// 2021.09.30 04:27:11 Log - [Network Data] OnEvent: PLAYER: 253
// 2021.09.30 04:27:40 Log - [Network Data] OnEvent: SYSTEM 255
if (photonEvent == "1" || photonEvent == "8" || photonEvent == "9" || photonEvent == "210")
{
return;
}
if (photonEvent == "7")
{
var json = System.Text.Json.JsonSerializer.Deserialize<VrcEvent>(data);
var photonId = json.Parameters._254;
if (photonEvent7.ContainsKey(photonId))
{
photonEvent7[photonId] = date;
} else
{
photonEvent7.Add(photonId, date);
}
return;
}
if (photonEvent == "254")
{
var json = System.Text.Json.JsonSerializer.Deserialize<VrcEvent>(data);
photonEvent7.Remove(json.Parameters._254);
}
if (photonEvent == "6")
{
var json = System.Text.Json.JsonSerializer.Deserialize<VrcEvent>(data);
byte[] bytes = Convert.FromBase64String(json.Parameters._245.Value);
try
{
var deserialization = new VRCEventDeserialization();
var eventData = deserialization.DeserializeData(bytes);
json.Data = eventData.Data;
json.Type = eventData.Type;
json.EventType = eventData.EventType;
data = System.Text.Json.JsonSerializer.Serialize<VrcEvent>(json);
}
catch(Exception ex)
{
data = ex.ToString();
}
}
AppendLog(new[]
{
fileInfo.Name,
date,
"photon-event",
data
});
}
public IDictionary<int, string> GetEvent7()
{
return photonEvent7;
}
public void ClearEvent7()
{
photonEvent7 = new Dictionary<int, string>();
}
public string[][] Get()
{
Update();

208
VRCEventDeserialization.cs Normal file
View File

@@ -0,0 +1,208 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
namespace VRCX
{
class VRCEventDeserialization
{
private byte[] byteData;
private int byteOffset;
private static readonly Dictionary<int, Type> DataType = new Dictionary<int, Type> {
{2, typeof(byte)},
{3, typeof(double)},
{4, typeof(float)},
{5, typeof(int)},
{6, typeof(short)},
{7, typeof(long)},
{8, typeof(bool)},
{9, typeof(string)},
{10, typeof(object[])},
{11, typeof(IList)},
{100, typeof(Vector2)},
{101, typeof(Vector3)},
{102, typeof(Vector4)},
{103, typeof(Quaternion)}
};
public class EventEntry
{
public int Type;
public string EventType;
public object Data;
}
private byte DeserializeByte()
{
return byteData[byteOffset++];
}
private int DeserializeInt()
{
int output = BitConverter.ToInt32(byteData, byteOffset);
byteOffset += 4;
return output;
}
private short DeserializeShort()
{
short output = BitConverter.ToInt16(byteData, byteOffset);
byteOffset += 2;
return output;
}
private string DeserializeString()
{
short stringLength = DeserializeShort();
string output = Encoding.UTF8.GetString(byteData, byteOffset, stringLength);
byteOffset += stringLength;
return output;
}
private bool DeserializeBool()
{
bool output = BitConverter.ToBoolean(byteData, byteOffset);
byteOffset++;
return output;
}
private float DeserializeFloat()
{
float output = BitConverter.ToSingle(byteData, byteOffset);
byteOffset += 4;
return output;
}
private double DeserializeDouble()
{
double output = BitConverter.ToDouble(byteData, byteOffset);
byteOffset += 8;
return output;
}
private object DeserializeTypeArray()
{
short length = DeserializeShort();
byte typeCode = DeserializeByte();
var output = Array.CreateInstance(DataType[typeCode], length);
for (var i = 0; i < length; i++)
{
output.SetValue(DeserializeBytes(typeCode), i);
}
return output;
}
private object[] DeserializeObjectArray()
{
short length = DeserializeShort();
object[] output = new object[length];
for (var i = 0; i < output.Length; i++)
{
output[i] = DeserializeBytes();
}
return output;
}
private long DeserializeInt64()
{
long output = BitConverter.ToInt64(byteData, byteOffset);
byteOffset += 8;
return output;
}
private Vector2 DeserializeVector2()
{
return new Vector2(DeserializeFloat(), DeserializeFloat());
}
private Vector3 DeserializeVector3()
{
return new Vector3(DeserializeFloat(), DeserializeFloat(), DeserializeFloat());
}
private Vector4 DeserializeVector4()
{
return new Vector4(DeserializeFloat(), DeserializeFloat(), DeserializeFloat(), DeserializeFloat());
}
private Quaternion DeserializeQuaternion()
{
return new Quaternion(DeserializeFloat(), DeserializeFloat(), DeserializeFloat(), DeserializeFloat());
}
private object DeserializeBytes(byte type = 0)
{
if (type == 0)
{
type = DeserializeByte();
}
switch (type)
{
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);
}
}
public EventEntry DeserializeData(byte[] bytes)
{
EventEntry eventEntry = new EventEntry();
byteOffset = 0;
byteData = bytes;
byte type = DeserializeByte();
if (type == 106)
{
byteOffset += 8;
int length = DeserializeShort();
byteOffset += length;
eventEntry.Type = DeserializeByte();
byteOffset += 10;
eventEntry.EventType = DeserializeString();
byteOffset += 5;
eventEntry.Data = null;
if (byteData.Length > byteOffset + 3)
{
byteData = (byte[])DeserializeTypeArray();
byteOffset = 0;
eventEntry.Data = DeserializeBytes();
}
}
else
{
throw new Exception("Unexpected type: " + type);
}
return eventEntry;
}
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -81,6 +81,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AssetBundleCacher.cs" />
<Compile Include="ImageCache.cs" />
<Compile Include="VRCEventDeserialization.cs" />
<Compile Include="IPCClient.cs" />
<Compile Include="IPCServer.cs" />
<Compile Include="StartupArgs.cs" />

File diff suppressed because it is too large Load Diff

View File

@@ -82,10 +82,14 @@
margin-top: 15px;
}
.el-table__expanded-cell[class*='cell'] {
.el-table--mini .el-table__expanded-cell[class*='cell'] {
padding: 20px 50px;
}
.el-table--mini .el-table__cell {
padding: 5px 0;
}
.el-dialog__body {
padding: 20px;
}
@@ -615,3 +619,20 @@ i.x-user-status.busy {
.el-form-item {
margin-bottom: 4px;
}
.photon-event-table .el-table--mini .el-table__cell,
.current-instance-table .el-table--mini .el-table__cell {
padding: 0;
}
.photon-event-table {
margin-top: 20px;
}
.current-instance-table img.friends-list-avatar {
width: unset;
height: 16px;
margin-right: 0;
margin-left: 3px;
border-radius: 2px;
}

View File

@@ -1 +1 @@
export var appVersion = 'VRCX 2021.11.04';
export var appVersion = 'VRCX 2021.11.20';

View File

@@ -59,6 +59,7 @@ html
span= name
+menuitem('feed', 'Feed', 'el-icon-news')
+menuitem('gameLog', 'Game Log', 'el-icon-s-data')
+menuitem('playerList', 'Player List', 'el-icon-tickets')
+menuitem('search', 'Search', 'el-icon-search')
+menuitem('favorite', 'Favorite', 'el-icon-star-off')
+menuitem('friendLog', 'Friend Log', 'el-icon-notebook-2')
@@ -68,6 +69,98 @@ html
+menuitem('profile', 'Profile', 'el-icon-user')
+menuitem('settings', 'Settings', 'el-icon-s-tools')
//- playerList
.x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'playerList'")
div(style="display:flex;flex-direction:column;height:100%")
div(v-if="currentInstanceWorld.id" style="display:flex")
el-popover(placement="right" width="500px" trigger="click" style="height:120px")
img.x-link(slot="reference" v-lazy="currentInstanceWorld.thumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
img.x-link(v-lazy="currentInstanceWorld.imageUrl" style="width:500px;height:375px" @click="openExternalLink(currentInstanceWorld.imageUrl)")
div(style="margin-left:10px;display:flex;flex-direction:column")
div
span.x-link(@click="showWorldDialog(currentInstanceWorld.id)" style="font-weight:bold;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1")
| #[i.el-icon-s-home(v-show="API.currentUser.$homeLocation && API.currentUser.$homeLocation.worldId === currentInstanceWorld.id")] {{ currentInstanceWorld.name }}
div
span.x-link(v-text="currentInstanceWorld.authorName" @click="showUserDialog(currentInstanceWorld.authorId)" style="color:#909399;font-family:monospace")
div(style="margin-top:5px")
el-tag(v-if="currentInstanceWorld.$isLabs" type="primary" effect="plain" size="mini") Labs
el-tag(v-else-if="currentInstanceWorld.releaseStatus === 'public'" type="success" effect="plain" size="mini") Public
el-tag(v-else-if="currentInstanceWorld.releaseStatus === 'private'" type="danger" effect="plain" size="mini") Private
span(style="margin-left:5px")
span \#{{ currentInstanceLocation.instanceName }} {{ currentInstanceLocation.accessType }}
span.famfamfam-flags(v-if="currentInstanceLocation.region === 'eu'" class="europeanunion" style="display:inline-block;margin-left:5px")
span.famfamfam-flags(v-else-if="currentInstanceLocation.region === 'jp'" class="jp" style="display:inline-block;margin-left:5px")
span.famfamfam-flags(v-else class="us" style="display:inline-block;margin-left:5px")
span(v-if="lastLocation.playerList.size > 0" style="margin-left:5px")
| {{ lastLocation.playerList.size }}
| #[template(v-if="lastLocation.friendList.size > 0") ({{ lastLocation.friendList.size }})]
template(v-if="photonLobbyBots.length > 0")
| &nbsp;
el-tooltip(placement="bottom")
template(#content)
span Photon Bot Id's: {{ photonLobbyBots.toString() }}
span(v-text="photonLobbyBots.length" style="color:red")
//- el-tag(type="info" effect="plain" size="mini" v-text="worldDialog.fileSize" style="margin-right:5px;margin-top:5px")
div(style="margin-top:5px")
span(v-show="currentInstanceWorld.name !== currentInstanceWorld.description" v-text="currentInstanceWorld.description" style="font-size:12px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2")
div.current-instance-table
data-tables(v-bind="currentInstanceUserList" @sort-change="updateTimers" @row-click="selectCurrentInstanceRow" style="margin-top:10px;cursor:pointer")
el-table-column(label="Avatar" width="60" prop="photo")
template(v-once #default="scope")
template(v-if="userImage(scope.row.ref)")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row.ref)")
img.friends-list-avatar(v-lazy="userImageFull(scope.row.ref)" style="height:500px;cursor:pointer" @click="openExternalLink(userImageFull(scope.row.ref))")
el-table-column(label="Timer" width="90" prop="timer" sortable)
template(v-once #default="scope")
timer(:epoch="scope.row.timer")
el-table-column(label="Photon Id" width="100" prop="photonId" sortable)
template(v-once #default="scope")
span(v-text="scope.row.photonId")
el-table-column(label="Display Name" min-width="140" prop="ref.displayName")
template(v-once #default="scope")
span(v-text="scope.row.ref.displayName" :class="scope.row.ref.$trustColor")
el-table-column(label="Status" min-width="180" prop="ref.status")
template(v-once #default="scope")
template(v-if="scope.row.ref.status")
i.x-user-status(:class="statusClass(scope.row.ref.status)")
span
span(v-text="scope.row.ref.statusDescription")
el-table-column(label="Language" width="100" prop="ref.$languages")
template(v-once #default="scope")
el-tooltip(v-for="item in scope.row.ref.$languages" :key="item.key" placement="top")
template(#content)
span {{ item.value }} ({{ item.key }})
span.famfamfam-flags(:class="languageClass(item.key)" style="display:inline-block;margin-left:5px")
el-table-column(label="Bio Links" width="100" prop="ref.bioLinks")
template(v-once #default="scope")
el-tooltip(v-if="link" v-for="(link, index) in scope.row.ref.bioLinks" :key="index")
template(#content)
span(v-text="link")
img(:src="getFaviconUrl(link)" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;cursor:pointer" @click.stop="openExternalLink(link)")
div.photon-event-table(v-if="photonEventTable.data.length > 0")
data-tables(v-bind="photonEventTable" style="margin-bottom:10px")
el-table-column(label="Date" prop="created_at" width="90")
template(v-once #default="scope")
el-tooltip(placement="right")
template(#content)
span {{ scope.row.created_at | formatDate('YYYY-MM-DD HH24:MI:SS') }}
span {{ scope.row.created_at | formatDate('MM-DD HH24:MI') }}
el-table-column(label="Name" prop="photonId" width="160")
template(v-once #default="scope")
span.x-link(v-text="getDisplayNameFromPhotonId(scope.row.photonId)" @click="showUserFromPhotonId(scope.row.photonId)" style="padding-right:10px")
el-table-column(label="Event" prop="text")
template(v-once #default="scope")
span(v-if="scope.row.avatar")
span ChangeAvatar
span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
| &nbsp;
span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") (Public)
span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") (Private)
template(v-if="scope.row.avatar.description && scope.row.avatar.name !== scope.row.avatar.description")
| - {{ scope.row.avatar.description }}
span(v-else v-text="scope.row.text")
//- feed
.x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'feed'")
data-tables(v-bind="feedTable" v-loading="feedTable.loading")
@@ -411,7 +504,7 @@ html
template(#tool)
div(style="margin:0 0 10px;display:flex;align-items:center")
el-select(v-model="playerModerationTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" placeholder="Filter")
el-option(v-once v-for="type in ['block', 'mute', 'unmute', 'hideAvatar', 'showAvatar']" :key="type" :label="type" :value="type")
el-option(v-once v-for="type in ['block', 'unblock', 'mute', 'unmute', 'hideAvatar', 'showAvatar']" :key="type" :label="type" :value="type")
el-input(v-model="playerModerationTable.filters[1].value" placeholder="Search" style="flex:none;width:150px;margin:0 10px")
el-tooltip(placement="bottom" content="Refresh" :disabled="hideTooltips")
el-button(type="default" :loading="API.isPlayerModerationsLoading" @click="API.refreshPlayerModerations()" icon="el-icon-refresh" circle style="flex:none")
@@ -806,7 +899,7 @@ html
span.color-picker(slot="trigger") #[i.el-icon-s-open] Veteran User
div
v-swatches(v-model="trustColor.legendary" show-fallback fallback-input-type="color" popover-x="right" :swatches="trustColorSwatches" class="x-tag-legendary")
span.color-picker(slot="trigger") #[i.el-icon-s-open] Early User
span.color-picker(slot="trigger") #[i.el-icon-s-open] Legend
div
v-swatches(v-model="trustColor.vip" show-fallback fallback-input-type="color" popover-x="right" :swatches="trustColorSwatches" class="x-tag-vip")
span.color-picker(slot="trigger") #[i.el-icon-s-open] VRChat Team
@@ -977,6 +1070,33 @@ html
div.options-container-item
span.name Dance worlds only
el-switch(v-model="progressPieFilter" @change="changeYouTubeApi" :disabled="!openVR")
div.options-container
span.header Photon Logging Overlay
el-tooltip(placement="top" style="margin-left:5px" content="Requires '--log-debug-levels=API;NetworkData' steam launch option")
i.el-icon-warning
div.options-container-item
span.sub-header Photon Event HUD
div.options-container-item
span.name Enable
el-switch(v-model="photonEventOverlay" @change="saveEventOverlay" :disabled="!openVR")
div.options-container-item
span.name Filter
el-radio-group(v-model="photonEventOverlayFilter" @change="saveEventOverlay" size="mini" :disabled="!openVR || !photonEventOverlay")
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
span.sub-header User timeout HUD
div.options-container-item
span.name Enable
el-switch(v-model="timeoutHudOverlay" @change="saveEventOverlay" :disabled="!openVR")
div.options-container-item
span.name Filter
el-radio-group(v-model="timeoutHudOverlayFilter" @change="saveEventOverlay" size="mini" :disabled="!openVR || !timeoutHudOverlay")
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
div.options-container-item
el-button(size="small" icon="el-icon-time" @click="promptPhotonLobbyTimeoutThreshold" :disabled="!openVR") Timeout Threshold
div.options-container
span.header VRCX Instance Cache/Debug
div.options-container-item
@@ -1595,7 +1715,7 @@ html
el-dialog.x-dialog(ref="favoriteDialog" :visible.sync="favoriteDialog.visible" title="Choose Group" width="300px")
div(v-loading="favoriteDialog.loading")
el-button(v-for="group in favoriteDialog.groups" :key="group.name" style="display:block;width:100%;margin:10px 0" @click="addFavorite(group)" :disabled="group.count >= group.capacity") {{ group.displayName }} ({{ group.count }} / {{ group.capacity }})
//- dialog: invite
el-dialog.x-dialog(ref="inviteDialog" :visible.sync="inviteDialog.visible" title="Invite" width="450px")
div(v-loading="inviteDialog.loading")
@@ -2088,10 +2208,42 @@ html
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
br
.toggle-item
span.toggle-name Photon Event Logging
el-tooltip(placement="top" style="margin-left:5px" content="Requires '--log-debug-levels=API;NetworkData' steam launch option")
i.el-icon-warning
.toggle-item
span.toggle-name Lobby Avatar Change
el-radio-group(v-model="sharedFeedFilters.noty.AvatarChange" size="mini")
el-radio-button(label="Off")
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
.toggle-item
span.toggle-name Blocked
el-radio-group(v-model="sharedFeedFilters.noty.Blocked" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Unblocked
el-radio-group(v-model="sharedFeedFilters.noty.Unblocked" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Muted
el-radio-group(v-model="sharedFeedFilters.noty.Muted" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Unmuted
el-radio-group(v-model="sharedFeedFilters.noty.Unmuted" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
template(#footer)
el-button(type="small" @click="cancelSharedFeedFilters") Cancel
el-button(type="primary" size="small" style="margin-left:10px" @click="saveSharedFeedFilters") Save
//- dialog: wrist feed filters
el-dialog.x-dialog(ref="wristFeedFiltersDialog" :visible.sync="wristFeedFiltersDialog.visible" title="Wrist Feed Filters" width="500px")
.toggle-list
@@ -2244,6 +2396,38 @@ html
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
br
.toggle-item
span.toggle-name Photon Event Logging
el-tooltip(placement="top" style="margin-left:5px" content="Requires '--log-debug-levels=API;NetworkData' steam launch option")
i.el-icon-warning
.toggle-item
span.toggle-name Lobby Avatar Change
el-radio-group(v-model="sharedFeedFilters.wrist.AvatarChange" size="mini")
el-radio-button(label="Off")
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
.toggle-item
span.toggle-name Blocked
el-radio-group(v-model="sharedFeedFilters.wrist.Blocked" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Unblocked
el-radio-group(v-model="sharedFeedFilters.wrist.Unblocked" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Muted
el-radio-group(v-model="sharedFeedFilters.wrist.Muted" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Unmuted
el-radio-group(v-model="sharedFeedFilters.wrist.Unmuted" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
template(#footer)
el-button(type="small" @click="cancelSharedFeedFilters") Cancel
el-button(type="primary" size="small" @click="saveSharedFeedFilters") Save

View File

@@ -29,6 +29,9 @@ class Database {
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${Database.userPrefix}_notifications (id TEXT PRIMARY KEY, created_at TEXT, type TEXT, sender_user_id TEXT, sender_username TEXT, receiver_user_id TEXT, message TEXT, world_id TEXT, world_name TEXT, image_url TEXT, invite_message TEXT, request_message TEXT, response_message TEXT, expired INTEGER)`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${Database.userPrefix}_moderation (user_id TEXT PRIMARY KEY, updated_at TEXT, display_name TEXT, block INTEGER, mute INTEGER)`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)`
);
@@ -1104,6 +1107,59 @@ class Database {
}
return date;
}
async getModeration(input) {
var userId = input.replaceAll("'", '');
var row = {};
await sqliteService.execute((dbRow) => {
var block = false;
var mute = false;
if (dbRow[3] === 1) {
block = true;
}
if (dbRow[4] === 1) {
mute = true;
}
row = {
userId: dbRow[0],
updatedAt: dbRow[1],
displayName: dbRow[2],
block,
mute
};
}, `SELECT * FROM ${Database.userPrefix}_moderation WHERE user_id = '${userId}'`);
return row;
}
setModeration(entry) {
var block = 0;
var mute = 0;
if (entry.block) {
block = 1;
}
if (entry.mute) {
mute = 1;
}
sqliteService.executeNonQuery(
`INSERT OR REPLACE INTO ${Database.userPrefix}_moderation (user_id, updated_at, display_name, block, mute) VALUES (@user_id, @updated_at, @display_name, @block, @mute)`,
{
'@user_id': entry.userId,
'@updated_at': entry.updatedAt,
'@display_name': entry.displayName,
'@block': block,
'@mute': mute
}
);
}
deleteModeration(userId) {
sqliteService.executeNonQuery(
`DELETE FROM ${Database.userPrefix}_moderation WHERE user_id = @user_id`,
{
'@user_id': userId
}
);
}
}
var self = new Database();

View File

@@ -51,6 +51,15 @@ class GameLogService {
gameLog.url = args[0];
break;
case 'photon-event':
gameLog.json = args[0];
break;
case 'photon-id':
gameLog.displayName = args[0];
gameLog.photonId = args[1];
break;
default:
break;
}

View File

@@ -11,19 +11,10 @@ import locale from 'element-ui/lib/locale/lang/en';
import MarqueeText from 'vue-marquee-text-component';
Vue.component('marquee-text', MarqueeText);
import configRepository from './repository/config.js';
(async function () {
var $app = null;
await CefSharp.BindObjectAsync(
'AppApi',
'WebApi',
'SharedVariable',
'SQLite'
);
await configRepository.init();
await CefSharp.BindObjectAsync('AppApi');
Noty.overrideDefaults({
animation: {
@@ -170,6 +161,17 @@ import configRepository from './repository/config.js';
}
});
var removeFromArray = function (array, item) {
var {length} = array;
for (var i = 0; i < length; ++i) {
if (array[i] === item) {
array.splice(i, 1);
return true;
}
}
return false;
};
var $app = {
data: {
// 1 = 대시보드랑 손목에 보이는거
@@ -179,6 +181,7 @@ import configRepository from './repository/config.js';
cpuUsage: 0,
config: {},
downloadProgress: 0,
photonLobbyBotSize: 0,
nowPlaying: {
url: '',
name: '',
@@ -289,12 +292,18 @@ import configRepository from './repository/config.js';
$app.methods.configUpdate = function (json) {
this.config = JSON.parse(json);
this.hudFeed = [];
this.hudTimeout = [];
};
$app.methods.updateDownloadProgress = function (progress) {
this.downloadProgress = parseInt(progress, 10);
};
$app.methods.updatePhotonLobbyBotSize = function (size) {
this.photonLobbyBotSize = parseInt(size, 10);
};
$app.methods.nowPlayingUpdate = function (json) {
this.nowPlaying = JSON.parse(json);
if (this.appType === '2') {
@@ -356,7 +365,7 @@ import configRepository from './repository/config.js';
var text = '';
var img = '';
if (image) {
img = `<img class="noty-img" src="data:image/png;base64, ${image}"></img>`;
img = `<img class="noty-img" src="${image}"></img>`;
}
switch (noty.type) {
case 'OnPlayerJoined':
@@ -455,6 +464,18 @@ import configRepository from './repository/config.js';
case 'MutedOnPlayerLeft':
text = `Muted user <strong>${noty.displayName}</strong> has left`;
break;
case 'Blocked':
text = `<strong>${noty.displayName}</strong> has blocked you`;
break;
case 'Unblocked':
text = `<strong>${noty.displayName}</strong> has unblocked you`;
break;
case 'Muted':
text = `<strong>${noty.displayName}</strong> has muted you`;
break;
case 'Unmuted':
text = `<strong>${noty.displayName}</strong> has unmuted you`;
break;
default:
break;
}
@@ -510,6 +531,61 @@ import configRepository from './repository/config.js';
Noty.closeAll();
};
$app.data.hudFeed = [];
$app.data.cleanHudFeedLoopStatus = false;
$app.methods.cleanHudFeedLoop = function () {
if (!this.cleanHudFeedLoopStatus) {
return;
}
this.cleanHudFeed();
if (this.hudFeed.length === 0) {
this.cleanHudFeedLoopStatus = false;
return;
}
setTimeout(() => this.cleanHudFeedLoop(), 500);
};
$app.methods.cleanHudFeed = function () {
var dt = Date.now();
this.hudFeed.forEach((item) => {
if (item.time + 6000 < dt) {
removeFromArray(this.hudFeed, item);
}
});
if (this.hudFeed.length > 10) {
this.hudFeed.length = 10;
}
if (!this.cleanHudFeedLoopStatus) {
this.cleanHudFeedLoopStatus = true;
this.cleanHudFeedLoop();
}
};
$app.methods.addEntryHudFeed = function (json) {
var {displayName, text} = JSON.parse(json);
var combo = 1;
this.hudFeed.forEach((item) => {
if (item.displayName === displayName && item.text === text) {
combo = item.combo + 1;
removeFromArray(this.hudFeed, item);
}
});
this.hudFeed.unshift({
time: Date.now(),
displayName,
text,
combo
});
this.cleanHudFeed();
};
$app.data.hudTimeout = [];
$app.methods.updateHudTimeout = function (json) {
this.hudTimeout = JSON.parse(json);
};
$app = new Vue($app);
window.$app = $app;
})();

View File

@@ -397,6 +397,7 @@ html
span(style="float:right") {{ lastLocationTimer }}
span(style="display:inline-block") {{ lastLocation.playerList.length }}
span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}}
span(v-if="photonLobbyBotSize > 0 && lastLocation.playerList.length > 0" style="display:inline-block;color:red;margin-left:5px") {{ photonLobbyBotSize }}
template(v-else)
template(v-if="downloadProgress === 100")
span(style="display:inline-block;margin-right:5px") Downloading: #[i.el-icon-loading]
@@ -406,11 +407,24 @@ html
span(style="float:right") Timer: {{ lastLocationTimer }}
span(style="display:inline-block") Players: {{ lastLocation.playerList.length }}
span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}}
span(v-if="photonLobbyBotSize > 0 && lastLocation.playerList.length > 0" style="display:inline-block;color:red;margin-left:5px") {{ photonLobbySize }}
br
span(style="float:right") {{ currentTime | formatDate('YYYY-MM-DD HH:MI:SS AMPM') }}
span CPU {{ cpuUsage }}%
template(v-else)
svg(class="np-progress-circle")
circle(class="np-progress-circle-stroke" cx="60" cy="60" stroke="white" r="30" fill="transparent" stroke-width="60")
.hud-feed
div(v-for="feed in hudFeed")
.item <strong>{{ feed.displayName }}</strong> {{ feed.text }}
template(v-if="feed.combo > 1")
span.combo x{{ feed.combo }}
.hud-timeout(v-if="hudTimeout.length > 0")
.hud-timeout-feed
div(v-for="feed in hudTimeout")
p.item ({{ feed.time }}s) {{ feed.displayName }}
svg(version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve")
path(fill="#ED1B24" d="M68.6,96.5L87,78.1c1.6-1.6,1.6-4.1,0-5.7s-4.1-1.6-5.7,0L62.9,90.9L44.5,72.5l18.4-18.4c1.6-1.6,1.6-4.1,0-5.7c-1.6-1.6-4.1-1.6-5.7,0L38.9,66.8l-6.4-6.4L21.2,71.8C11,82,9.7,97.9,17.4,109.5L0,126.9l8.5,8.5L25.9,118c11.6,7.7,27.5,6.4,37.8-3.8L75,102.9C75,102.9,68.6,96.5,68.6,96.5z")
path(fill="#ED1B24" d="M102.9,75l11.3-11.3c10.3-10.3,11.5-26.1,3.8-37.8l17.4-17.4L126.9,0l-17.4,17.4C97.9,9.7,82,11,71.8,21.2L60.5,32.5C102,74,60.8,32.9,102.9,75z")
script(src="vendor.js")
script(src="vr.js")

View File

@@ -179,8 +179,8 @@ button {
font-family: 'Noto Sans JP', 'Noto Sans KR', 'Meiryo UI', 'Malgun Gothic',
'Segoe UI', sans-serif;
line-height: normal;
text-shadow: #000 0px 0px 2px, #000 0px 0px 2px, #000 0px 0px 2px,
#000 0px 0px 2px, #000 0px 0px 2px, #000 0px 0px 2px;
text-shadow: #000 0px 0px 3px, #000 0px 0px 3px, #000 0px 0px 3px,
#000 0px 0px 3px, #000 0px 0px 3px, #000 0px 0px 3px;
}
.x-app {
@@ -350,3 +350,49 @@ i.x-user-status.busy {
transform: rotate(359deg);
}
}
.hud-feed {
position: absolute;
right: 0;
width: 100%;
}
.hud-feed .item,
.hud-timeout .item {
margin: 0;
text-align: right;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.hud-feed .item {
font-size: 32px;
}
.hud-feed .combo {
color: #aaa;
}
.hud-timeout .item {
font-size: 40px;
}
.hud-timeout {
position: absolute;
bottom: 0;
right: 0;
}
.hud-timeout-feed {
position: absolute;
bottom: 150px;
right: 0;
color: #ed1b24;
}
.hud-timeout svg {
position: absolute;
right: -160px;
bottom: 0;
}