mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
1435 lines
56 KiB
C#
1435 lines
56 KiB
C#
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Threading;
|
||
using NLog;
|
||
|
||
#if !LINUX
|
||
using CefSharp;
|
||
#endif
|
||
|
||
namespace VRCX
|
||
{
|
||
/// <summary>
|
||
/// Monitors the VRChat log files for changes and provides access to the log data.
|
||
/// </summary>
|
||
public class LogWatcher
|
||
{
|
||
public static readonly LogWatcher Instance;
|
||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||
private Dictionary<string, LogContext> m_LogContextMap; // <FileName, LogContext>
|
||
private DirectoryInfo m_LogDirectoryInfo;
|
||
private List<string[]> m_LogList;
|
||
private ReaderWriterLockSlim m_LogListLock;
|
||
private bool m_FirstRun = true;
|
||
private bool m_ResetLog;
|
||
private bool threadActive;
|
||
private Thread? m_Thread;
|
||
private DateTime tillDate;
|
||
public bool VrcClosedGracefully;
|
||
private readonly ConcurrentQueue<string> m_LogQueue = new ConcurrentQueue<string>(); // for electron
|
||
|
||
// NOTE
|
||
// FileSystemWatcher() is unreliable
|
||
|
||
static LogWatcher()
|
||
{
|
||
Instance = new LogWatcher();
|
||
}
|
||
|
||
public void Init()
|
||
{
|
||
var logPath = Program.AppApiInstance.GetVRChatAppDataLocation();
|
||
m_LogDirectoryInfo = new DirectoryInfo(logPath);
|
||
m_LogContextMap = new Dictionary<string, LogContext>();
|
||
m_LogListLock = new ReaderWriterLockSlim();
|
||
m_LogList = new List<string[]>();
|
||
m_Thread = new Thread(ThreadLoop)
|
||
{
|
||
IsBackground = true
|
||
};
|
||
m_Thread.Start();
|
||
}
|
||
|
||
public void Exit()
|
||
{
|
||
threadActive = false;
|
||
var thread = m_Thread;
|
||
m_Thread = null;
|
||
thread.Interrupt();
|
||
thread.Join();
|
||
}
|
||
|
||
public void Reset()
|
||
{
|
||
m_ResetLog = true;
|
||
m_Thread?.Interrupt();
|
||
}
|
||
|
||
public void SetDateTill(string date)
|
||
{
|
||
tillDate = DateTime.Parse(date, CultureInfo.InvariantCulture, DateTimeStyles.None).ToUniversalTime();
|
||
threadActive = true;
|
||
logger.Info("SetDateTill: {0}", tillDate.ToLocalTime());
|
||
}
|
||
|
||
private void ThreadLoop()
|
||
{
|
||
while (m_Thread != null)
|
||
{
|
||
if (threadActive)
|
||
Update();
|
||
|
||
try
|
||
{
|
||
Thread.Sleep(1000);
|
||
}
|
||
catch (ThreadInterruptedException)
|
||
{
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Updates the log watcher by checking for new log files and updating the log list.
|
||
/// </summary>
|
||
private void Update()
|
||
{
|
||
if (m_ResetLog)
|
||
{
|
||
m_FirstRun = true;
|
||
m_ResetLog = false;
|
||
m_LogContextMap.Clear();
|
||
m_LogListLock.EnterWriteLock();
|
||
try
|
||
{
|
||
m_LogList.Clear();
|
||
}
|
||
finally
|
||
{
|
||
m_LogListLock.ExitWriteLock();
|
||
}
|
||
}
|
||
|
||
var deletedNameSet = new HashSet<string>(m_LogContextMap.Keys);
|
||
m_LogDirectoryInfo.Refresh();
|
||
|
||
if ((m_LogDirectoryInfo.LinkTarget == null && m_LogDirectoryInfo.Exists) || Directory.Exists(m_LogDirectoryInfo.LinkTarget))
|
||
{
|
||
var fileInfos = m_LogDirectoryInfo.GetFiles("output_log_*.txt", SearchOption.TopDirectoryOnly);
|
||
|
||
// sort by creation time
|
||
Array.Sort(fileInfos, (a, b) => a.CreationTimeUtc.CompareTo(b.CreationTimeUtc));
|
||
|
||
foreach (var fileInfo in fileInfos)
|
||
{
|
||
fileInfo.Refresh();
|
||
if (!fileInfo.Exists)
|
||
continue;
|
||
|
||
if (DateTime.Compare(fileInfo.LastWriteTimeUtc, tillDate) < 0)
|
||
continue;
|
||
|
||
if (m_LogContextMap.TryGetValue(fileInfo.Name, out var logContext))
|
||
{
|
||
deletedNameSet.Remove(fileInfo.Name);
|
||
}
|
||
else
|
||
{
|
||
logContext = new LogContext();
|
||
m_LogContextMap.Add(fileInfo.Name, logContext);
|
||
}
|
||
|
||
if (logContext.Length == fileInfo.Length)
|
||
continue;
|
||
|
||
logContext.Length = fileInfo.Length;
|
||
ParseLog(fileInfo, logContext);
|
||
}
|
||
}
|
||
|
||
foreach (var name in deletedNameSet)
|
||
{
|
||
m_LogContextMap.Remove(name);
|
||
}
|
||
|
||
m_FirstRun = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Parses the log file starting from the current position and updates the log context.
|
||
/// </summary>
|
||
/// <param name="fileInfo">The file information of the log file to parse.</param>
|
||
/// <param name="logContext">The log context to update.</param>
|
||
private void ParseLog(FileInfo fileInfo, LogContext logContext)
|
||
{
|
||
var line = string.Empty;
|
||
try
|
||
{
|
||
using var stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 65536, FileOptions.SequentialScan);
|
||
stream.Position = logContext.Position;
|
||
using var streamReader = new StreamReader(stream, Encoding.UTF8);
|
||
while (true)
|
||
{
|
||
line = streamReader.ReadLine();
|
||
if (line == null)
|
||
{
|
||
logContext.Position = stream.Position;
|
||
break;
|
||
}
|
||
|
||
if (line.Length == 0)
|
||
{
|
||
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
|
||
|
||
if (ParseLogUdonException(fileInfo, line))
|
||
continue;
|
||
|
||
if (line.Length <= 36 ||
|
||
line[31] != '-')
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (DateTime.TryParseExact(
|
||
line.Substring(0, 19),
|
||
"yyyy.MM.dd HH:mm:ss",
|
||
CultureInfo.InvariantCulture,
|
||
DateTimeStyles.None,
|
||
out var lineDate
|
||
))
|
||
{
|
||
lineDate = lineDate.ToUniversalTime();
|
||
// check if date is older than last database entry
|
||
if (DateTime.Compare(lineDate, tillDate) <= 0)
|
||
{
|
||
// logger.Warn("Invalid log time, too old: {0}", line);
|
||
continue;
|
||
}
|
||
// check if datetime is over an hour into the future (compensate for gamelog not handling daylight savings time correctly)
|
||
if (DateTime.UtcNow.AddMinutes(61) < lineDate)
|
||
{
|
||
logger.Warn("Invalid log time, too new: {0}", line);
|
||
continue;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
logger.Warn("Failed to parse log date: {0}", line);
|
||
continue;
|
||
}
|
||
|
||
var offset = 34;
|
||
if (line[offset] == '[')
|
||
{
|
||
if (ParseLogOnPlayerJoinedOrLeft(fileInfo, logContext, line, offset) ||
|
||
ParseLogLocation(fileInfo, logContext, line, offset) ||
|
||
ParseLogLocationDestination(fileInfo, logContext, line, offset) ||
|
||
ParseLogPortalSpawn(fileInfo, logContext, line, offset) ||
|
||
ParseLogNotification(fileInfo, logContext, line, offset) ||
|
||
ParseLogAPIRequest(fileInfo, logContext, line, offset) ||
|
||
ParseLogAvatarChange(fileInfo, logContext, line, offset) ||
|
||
ParseLogJoinBlocked(fileInfo, logContext, line, offset) ||
|
||
ParseLogAvatarPedestalChange(fileInfo, logContext, line, offset) ||
|
||
ParseLogVideoError(fileInfo, logContext, line, offset) ||
|
||
ParseLogVideoChange(fileInfo, logContext, line, offset) ||
|
||
ParseLogAVProVideoChange(fileInfo, logContext, line, offset) ||
|
||
ParseLogUsharpVideoPlay(fileInfo, logContext, line, offset) ||
|
||
ParseLogUsharpVideoSync(fileInfo, logContext, line, offset) ||
|
||
ParseLogWorldVRCX(fileInfo, logContext, line, offset) ||
|
||
ParseLogWorldDataVRCX(fileInfo, logContext, line, offset) ||
|
||
ParseLogOnAudioConfigurationChanged(fileInfo, logContext, line, offset) ||
|
||
ParseLogScreenshot(fileInfo, logContext, line, offset) ||
|
||
ParseLogStringDownload(fileInfo, logContext, line, offset) ||
|
||
ParseLogImageDownload(fileInfo, logContext, line, offset) ||
|
||
ParseVoteKick(fileInfo, logContext, line, offset) ||
|
||
ParseFailedToJoin(fileInfo, logContext, line, offset) ||
|
||
ParseInstanceResetWarning(fileInfo, logContext, line, offset) ||
|
||
ParseVoteKickInitiation(fileInfo, logContext, line, offset) ||
|
||
ParseVoteKickSuccess(fileInfo, logContext, line, offset) ||
|
||
ParseStickerSpawn(fileInfo, logContext, line, offset))
|
||
{
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (ParseLogShaderKeywordsLimit(fileInfo, logContext, line, offset) ||
|
||
ParseLogSDK2VideoPlay(fileInfo, logContext, line, offset) ||
|
||
ParseApplicationQuit(fileInfo, logContext, line, offset) ||
|
||
ParseOpenVRInit(fileInfo, logContext, line, offset) ||
|
||
ParseDesktopMode(fileInfo, logContext, line, offset) ||
|
||
ParseOscFailedToStart(fileInfo, logContext, line, offset) ||
|
||
ParseUntrustedUrl(fileInfo, logContext, line, offset))
|
||
{
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Warn(ex, "Failed to parse log file: {0} {1} {2}", fileInfo.FullName, line, ex.Message);
|
||
}
|
||
}
|
||
|
||
private void AppendLog(string[] item)
|
||
{
|
||
m_LogListLock.EnterWriteLock();
|
||
try
|
||
{
|
||
if (!m_FirstRun)
|
||
{
|
||
var logLine = JsonSerializer.Serialize(item);
|
||
#if LINUX
|
||
m_LogQueue.Enqueue(logLine);
|
||
#else
|
||
if (MainForm.Instance != null && MainForm.Instance.Browser != null)
|
||
MainForm.Instance.Browser.ExecuteScriptAsync("window?.$pinia?.gameLog.addGameLogEvent", logLine);
|
||
#endif
|
||
}
|
||
|
||
m_LogList.Add(item);
|
||
}
|
||
finally
|
||
{
|
||
m_LogListLock.ExitWriteLock();
|
||
}
|
||
}
|
||
|
||
public List<string> GetLogLines()
|
||
{
|
||
// for electron
|
||
var logLines = new List<string>();
|
||
while (m_LogQueue.TryDequeue(out var logLine))
|
||
logLines.Add(logLine);
|
||
|
||
return logLines;
|
||
}
|
||
|
||
private string ConvertLogTimeToISO8601(string line)
|
||
{
|
||
// 2020.10.31 23:36:22
|
||
|
||
if (DateTime.TryParseExact(
|
||
line.Substring(0, 19),
|
||
"yyyy.MM.dd HH:mm:ss",
|
||
CultureInfo.InvariantCulture,
|
||
DateTimeStyles.None,
|
||
out var dt
|
||
))
|
||
{
|
||
dt = dt.ToUniversalTime();
|
||
}
|
||
else
|
||
{
|
||
dt = DateTime.UtcNow;
|
||
}
|
||
|
||
// ISO 8601
|
||
return dt.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'", CultureInfo.InvariantCulture);
|
||
}
|
||
|
||
private bool ParseLogLocation(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2020.10.31 23:36:28 Log - [VRCFlowManagerVRC] Destination fetching: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd
|
||
// 2020.10.31 23:36:28 Log - [VRCFlowManagerVRC] Destination set: wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd
|
||
// 2020.10.31 23:36:31 Log - [RoomManager] Entering Room: VRChat Home
|
||
// 2020.10.31 23:36:31 Log - [RoomManager] Joining wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd:67646~private(usr_4f76a584-9d4b-46f6-8209-8305eb683661)~nonce(D9298A536FEEEDDBB61633661A4BDAA09717C5178DEF865C4C09372FE12E09A6)
|
||
// 2020.10.31 23:36:31 Log - [RoomManager] Joining or Creating Room: VRChat Home
|
||
// 2020.10.31 23:36:31 Log - [RoomManager] Successfully joined room
|
||
// 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 (line.Contains("[Behaviour] Entering Room: "))
|
||
{
|
||
var lineOffset = line.LastIndexOf("] Entering Room: ", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
lineOffset += 17;
|
||
if (lineOffset > line.Length)
|
||
return true;
|
||
|
||
var worldName = line.Substring(lineOffset);
|
||
logContext.RecentWorldName = worldName;
|
||
return true;
|
||
}
|
||
|
||
if (line.Contains("[Behaviour] Joining ") && !line.Contains("] Joining or Creating Room: ") && !line.Contains("] Joining friend: "))
|
||
{
|
||
var lineOffset = line.LastIndexOf("] Joining ", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
lineOffset += 10;
|
||
if (lineOffset >= line.Length)
|
||
return true;
|
||
|
||
var location = line.Substring(lineOffset);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"location",
|
||
location,
|
||
logContext.RecentWorldName
|
||
});
|
||
|
||
// logContext.onJoinPhotonDisplayName = string.Empty;
|
||
// logContext.onJoinPhotonDisplayNameDate = string.Empty;
|
||
logContext.LastAudioDevice = string.Empty;
|
||
logContext.VideoPlaybackErrors.Clear();
|
||
VrcClosedGracefully = false;
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool ParseLogScreenshot(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// This won't work with VRChat's new "Multi Layer" camera mode, since it doesn't output any logs like normal pictures.
|
||
// 2023.02.08 12:31:35 Log - [VRC Camera] Took screenshot to: C:\Users\Tea\Pictures\VRChat\2023-02\VRChat_2023-02-08_12-31-35.104_1920x1080.png
|
||
if (!line.Contains("[VRC Camera] Took screenshot to: "))
|
||
return false;
|
||
|
||
var lineOffset = line.LastIndexOf("] Took screenshot to: ", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
|
||
var screenshotPath = line.Substring(lineOffset + 22);
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"screenshot",
|
||
screenshotPath
|
||
});
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogLocationDestination(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 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
|
||
// 2022.08.13 18:57:00 Log - [Behaviour] OnLeftRoom
|
||
// 2024.11.22 15:32:28 Log - [Behaviour] Successfully left room
|
||
// 2025.11.20 01:35:38 Debug - [Behaviour] OnLeftRoom
|
||
|
||
if (line.Contains("[Behaviour] OnLeftRoom"))
|
||
{
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"location-destination",
|
||
logContext.LocationDestination
|
||
});
|
||
|
||
logContext.LocationDestination = string.Empty;
|
||
|
||
return true;
|
||
}
|
||
|
||
if (line.Contains("[Behaviour] Destination fetching: "))
|
||
{
|
||
var lineOffset = line.LastIndexOf("] Destination fetching: ", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
lineOffset += 24;
|
||
if (lineOffset >= line.Length)
|
||
return true;
|
||
|
||
logContext.LocationDestination = line.Substring(lineOffset);
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool ParseLogOnPlayerJoinedOrLeft(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2020.10.31 23:36:58 Log - [NetworkManager] OnPlayerJoined pypy
|
||
// 2020.10.31 23:36:58 Log - [Player] Initialized PlayerAPI "pypy" is local
|
||
// 2020.10.31 23:36:58 Log - [NetworkManager] OnPlayerJoined Rize♡
|
||
// 2020.10.31 23:36:58 Log - [Player] Initialized PlayerAPI "Rize♡" is remote
|
||
|
||
// 2020.11.01 00:07:01 Log - [NetworkManager] OnPlayerLeft Rize♡
|
||
// 2020.11.01 00:07:01 Log - [PlayerManager] Removed player 2 / Rize♡
|
||
// 2020.11.01 00:07:02 Log - [Player] Unregistering Rize♡
|
||
|
||
// 2021.06.23 11:41:16 Log - [Behaviour] Initialized PlayerAPI "Natsumi-sama" is local
|
||
|
||
// 2021.12.12 11:47:22 Log - [Behaviour] OnPlayerJoined Natsumi-sama
|
||
// 2021.12.12 11:47:22 Log - [Behaviour] OnPlayerJoined:Unnamed
|
||
// 2021.12.12 11:53:14 Log - [Behaviour] OnPlayerLeftRoom
|
||
|
||
// Future logs will be formatted like this: [Behaviour] OnPlayerJoined Natsumi-sama (usr_032383a7-748c-4fb2-94e4-bcb928e5de6b)
|
||
|
||
if (line.Contains("[Behaviour] OnPlayerJoined") && !line.Contains("] OnPlayerJoined:"))
|
||
{
|
||
var lineOffset = line.LastIndexOf("] OnPlayerJoined", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
lineOffset += 17;
|
||
if (lineOffset > line.Length)
|
||
return true;
|
||
|
||
var userInfo = ParseUserInfo(line.Substring(lineOffset));
|
||
if (string.IsNullOrEmpty(userInfo.DisplayName) && string.IsNullOrEmpty(userInfo.UserId))
|
||
{
|
||
logger.Warn("Failed to parse user info from log line: {0}", line);
|
||
return true;
|
||
}
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"player-joined",
|
||
userInfo.DisplayName ?? string.Empty,
|
||
userInfo.UserId ?? string.Empty
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
if (line.Contains("[Behaviour] OnPlayerLeft") && !line.Contains("] OnPlayerLeftRoom") && !line.Contains("] OnPlayerLeft:"))
|
||
{
|
||
var lineOffset = line.LastIndexOf("] OnPlayerLeft", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
lineOffset += 15;
|
||
if (lineOffset > line.Length)
|
||
return true;
|
||
|
||
var userInfo = ParseUserInfo(line.Substring(lineOffset));
|
||
if (string.IsNullOrEmpty(userInfo.DisplayName) && string.IsNullOrEmpty(userInfo.UserId))
|
||
{
|
||
logger.Warn("Failed to parse user info from log line: {0}", line);
|
||
return true;
|
||
}
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"player-left",
|
||
userInfo.DisplayName ?? string.Empty,
|
||
userInfo.UserId ?? string.Empty
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool ParseLogPortalSpawn(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 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)
|
||
// 2022.07.29 18:40:37 Log - [Behaviour] Instantiated a (Clone [800004] Portals/PortalInternalDynamic)
|
||
// 2023 - deadge
|
||
|
||
if (line.Contains("[Behaviour] Instantiated a (Clone [") && line.Contains("] Portals/PortalInternalDynamic)"))
|
||
{
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"portal-spawn"
|
||
});
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool ParseLogShaderKeywordsLimit(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 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 (line.Contains("Maximum number (384) of shader global keywords exceeded"))
|
||
{
|
||
if (logContext.ShaderKeywordsLimitReached)
|
||
return true;
|
||
|
||
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 (!line.Contains("] Master is not sending any events! Moving to a new instance."))
|
||
return false;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
"Joining instance blocked by master"
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogAvatarPedestalChange(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 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);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
$"{data} changed avatar pedestal"
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogVideoError(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2021.04.08 06:37:45 Error - [Video Playback] ERROR: Video unavailable
|
||
// 2021.04.08 06:40:07 Error - [Video Playback] ERROR: Private video
|
||
|
||
// 2024.07.31 22:28:47 Error - [AVProVideo] Error: Loading failed. File not found, codec not supported, video resolution too high or insufficient system resources.
|
||
// 2024.07.31 23:04:15 Error - [AVProVideo] Error: Loading failed. File not found, codec not supported, video resolution too high or insufficient system resources.
|
||
|
||
// 2025.05.04 22:38:12 Error - Attempted to play an untrusted URL (Domain: localhost) that is not allowlisted for public instances. If this URL is needed for the world to work, the domain needs to be added to the world's Video Player Allowed Domains list on the website.
|
||
const string youtubeBotError = "Sign in to confirm";
|
||
const string youtubeBotErrorFixUrl = "[VRCX] Fix error with this: https://github.com/EllyVR/VRCVideoCacher";
|
||
|
||
if (line.Contains("[Video Playback] ERROR: "))
|
||
{
|
||
var data = line.Substring(offset + 24);
|
||
if (!logContext.VideoPlaybackErrors.Add(data))
|
||
return true;
|
||
|
||
if (data.Contains(youtubeBotError))
|
||
data = $"{youtubeBotErrorFixUrl}\n{data}";
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
"VideoError: " + data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
if (line.Contains("[AVProVideo] Error: "))
|
||
{
|
||
var data = line.Substring(offset + 20);
|
||
if (!logContext.VideoPlaybackErrors.Add(data))
|
||
return true;
|
||
|
||
if (data.Contains(youtubeBotError))
|
||
data = $"{youtubeBotErrorFixUrl}\n{data}";
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
"VideoError: " + data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool ParseUntrustedUrl(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2025.05.04 22:38:12 Error - Attempted to play an untrusted URL (Domain: localhost) that is not allowlisted for public instances. If this URL is needed for the world to work, the domain needs to be added to the world's Video Player Allowed Domains list on the website.
|
||
|
||
if (line.Contains("Attempted to play an untrusted URL"))
|
||
{
|
||
var data = line.Substring(offset);
|
||
if (!logContext.VideoPlaybackErrors.Add(data))
|
||
return true;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
"VideoError: " + data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool ParseLogWorldVRCX(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// [VRCX] VideoPlay(PyPyDance) "https://jd.pypy.moe/api/v1/videos/-Q3pdlsQxOk.mp4",0.5338666,260.6938,"1339 : Le Freak (Random)"
|
||
|
||
if (string.Compare(line, offset, "[VRCX] ", 0, 7, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
var data = line.Substring(offset + 7);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"vrcx",
|
||
data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogWorldDataVRCX(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// [VRCX-World] store:test:testvalue
|
||
|
||
if (string.Compare(line, offset, "[VRCX-World] ", 0, 13, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
var data = line.Substring(offset + 13);
|
||
|
||
// PWI, deprecated
|
||
logger.Info("VRCX-World data: {0}", data);
|
||
return true;
|
||
}
|
||
|
||
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);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"video-play",
|
||
data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogAVProVideoChange(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.05.12 15:53:48 Log - [Video Playback] Resolving URL 'rtspt://topaz.chat/live/kiriri520'
|
||
|
||
if (string.Compare(line, offset, "[Video Playback] Resolving URL '", 0, 32, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
var pos = line.LastIndexOf('\'');
|
||
if (pos < 0)
|
||
return false;
|
||
|
||
var data = line.Substring(offset + 32);
|
||
data = data.Remove(data.Length - 1);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"video-play",
|
||
data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogSDK2VideoPlay(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 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 ", StringComparison.Ordinal);
|
||
if (pos < 0)
|
||
return false;
|
||
|
||
var displayName = line.Substring(offset + 5, pos - (offset + 5));
|
||
var data = line.Substring(pos + 11);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"video-play",
|
||
data,
|
||
displayName
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogUsharpVideoPlay(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2021.12.12 05:51:58 Log - [USharpVideo] Started video load for URL: https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=1s, requested by ʜ ᴀ ᴘ ᴘ ʏ
|
||
|
||
if (string.Compare(line, offset, "[USharpVideo] Started video load for URL: ", 0, 42, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
var pos = line.LastIndexOf(", requested by ", StringComparison.Ordinal);
|
||
if (pos < 0)
|
||
return false;
|
||
|
||
var data = line.Substring(offset + 42, pos - (offset + 42));
|
||
var displayName = line.Substring(pos + 15);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"video-play",
|
||
data,
|
||
displayName
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogUsharpVideoSync(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2022.01.16 05:20:23 Log - [USharpVideo] Syncing video to 2.52
|
||
|
||
if (string.Compare(line, offset, "[USharpVideo] Syncing video to ", 0, 31, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
var data = line.Substring(offset + 31);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"video-sync",
|
||
data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogNotification(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 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 ", StringComparison.Ordinal);
|
||
if (pos < 0)
|
||
return false;
|
||
|
||
var data = line.Substring(offset + 30, pos - (offset + 30));
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"notification",
|
||
data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogAPIRequest(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2021.10.03 09:49:50 Log - [API] [110] Sending Get request to https://api.vrchat.cloud/api/1/worlds?apiKey=JlE5Jldo5Jibnk5O5hTx6XVqsJu4WJ26&organization=vrchat&userId=usr_032383a7-748c-4fb2-94e4-bcb928e5de6b&n=99&order=descending&offset=0&releaseStatus=public&maxUnityVersion=2019.4.31f1&minUnityVersion=5.5.0f1&maxAssetVersion=4&minAssetVersion=0&platform=standalonewindows
|
||
// 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 ", StringComparison.Ordinal);
|
||
if (pos < 0)
|
||
return false;
|
||
|
||
var data = line.Substring(pos + 25);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"api-request",
|
||
data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogAvatarChange(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.11.05 14:45:57 Log - [Behaviour] Switching K․MOG to avatar MoeSera
|
||
|
||
if (string.Compare(line, offset, "[Behaviour] Switching ", 0, 22, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
var pos = line.LastIndexOf(" to avatar ", StringComparison.Ordinal);
|
||
if (pos < 0)
|
||
return false;
|
||
|
||
var displayName = line.Substring(offset + 22, pos - (offset + 22));
|
||
var avatarName = line.Substring(pos + 11);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"avatar-change",
|
||
displayName,
|
||
avatarName
|
||
});
|
||
|
||
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: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
|
||
//
|
||
// // 2022.03.05 11:29:16 Log - [Behaviour] Initialize ThreePoint Avatar (UnityEngine.Animator) VRCPlayer[Local] 50608765 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 Custom
|
||
//
|
||
// 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("[Behaviour] Initialized player "))
|
||
// {
|
||
// var pos = line.LastIndexOf("[Behaviour] Initialized player ");
|
||
// if (pos < 0)
|
||
// return false;
|
||
//
|
||
// pos += 31;
|
||
// if (pos >= line.Length)
|
||
// return false;
|
||
//
|
||
// var displayName = line.Substring(pos, line.Length - pos);
|
||
// logContext.onJoinPhotonDisplayName = displayName;
|
||
// logContext.onJoinPhotonDisplayNameDate = ConvertLogTimeToISO8601(line);
|
||
//
|
||
// return true;
|
||
// }
|
||
//
|
||
// if (line.Contains("[Behaviour] Configuring remote player VRCPlayer[Remote] "))
|
||
// {
|
||
// if (string.IsNullOrEmpty(logContext.onJoinPhotonDisplayName) ||
|
||
// logContext.onJoinPhotonDisplayNameDate != ConvertLogTimeToISO8601(line))
|
||
// return false;
|
||
//
|
||
// var pos = line.LastIndexOf("[Behaviour] Configuring remote player VRCPlayer[Remote] ");
|
||
// if (pos < 0)
|
||
// return false;
|
||
//
|
||
// pos += 56;
|
||
// var startPos = pos + 9;
|
||
// var length = line.Length - startPos;
|
||
// var photonId = line.Substring(startPos, length);
|
||
//
|
||
// AppendLog(new[]
|
||
// {
|
||
// fileInfo.Name,
|
||
// ConvertLogTimeToISO8601(line),
|
||
// "photon-id",
|
||
// logContext.onJoinPhotonDisplayName,
|
||
// photonId
|
||
// });
|
||
// logContext.onJoinPhotonDisplayName = string.Empty;
|
||
// logContext.onJoinPhotonDisplayNameDate = string.Empty;
|
||
//
|
||
// return true;
|
||
// }
|
||
//
|
||
// return false;
|
||
// }
|
||
|
||
private bool ParseLogOnAudioConfigurationChanged(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2022.03.15 03:40:34 Log - [Always] uSpeak: SetInputDevice 0 (3 total) 'Index HMD Mic (Valve VR Radio & HMD Mic)'
|
||
// 2022.03.15 04:02:22 Log - [Always] uSpeak: OnAudioConfigurationChanged - devicesChanged = True, resetting mic..
|
||
// 2022.03.15 04:02:22 Log - [Always] uSpeak: SetInputDevice by name 'Index HMD Mic (Valve VR Radio & HMD Mic)' (3 total)
|
||
// 2025.01.03 19:11:42 Log - [Always] uSpeak: SetInputDevice 0 (2 total) 'Microphone (NVIDIA Broadcast)'
|
||
|
||
if (line.Contains("[Always] uSpeak: OnAudioConfigurationChanged"))
|
||
{
|
||
logContext.AudioDeviceChanged = true;
|
||
return true;
|
||
}
|
||
|
||
if (line.Contains("[Always] uSpeak: SetInputDevice 0"))
|
||
{
|
||
var lineOffset = line.LastIndexOf(") '", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
lineOffset += 3;
|
||
var endPos = line.Length - 1;
|
||
var length = Math.Min(endPos - lineOffset + 1, line.Length - lineOffset);
|
||
if (length <= 0)
|
||
return true;
|
||
|
||
var audioDevice = line.Substring(lineOffset, length);
|
||
if (string.IsNullOrEmpty(logContext.LastAudioDevice))
|
||
{
|
||
logContext.AudioDeviceChanged = false;
|
||
logContext.LastAudioDevice = audioDevice;
|
||
return true;
|
||
}
|
||
|
||
if (!logContext.AudioDeviceChanged || logContext.LastAudioDevice == audioDevice)
|
||
return true;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
$"Audio device changed, mic set to '{audioDevice}'"
|
||
});
|
||
|
||
logContext.LastAudioDevice = audioDevice;
|
||
logContext.AudioDeviceChanged = false;
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool ParseLogUdonException(FileInfo fileInfo, string line)
|
||
{
|
||
// 2022.11.29 04:27:33 Error - [UdonBehaviour] An exception occurred during Udon execution, this UdonBehaviour will be halted.
|
||
// VRC.Udon.VM.UdonVMException: An exception occurred in an UdonVM, execution will be halted. --->VRC.Udon.VM.UdonVMException: An exception occurred during EXTERN to 'VRCSDKBaseVRCPlayerApi.__get_displayName__SystemString'. --->System.NullReferenceException: Object reference not set to an instance of an object.
|
||
|
||
if (line.Contains("[PyPyDance]"))
|
||
{
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"udon-exception",
|
||
line
|
||
});
|
||
return true;
|
||
}
|
||
|
||
var lineOffset = line.IndexOf(" ---> VRC.Udon.VM.UdonVMException: ", StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return false;
|
||
|
||
var data = line.Substring(lineOffset);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"udon-exception",
|
||
data
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseApplicationQuit(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2022.06.12 01:51:46 Log - VRCApplication: OnApplicationQuit at 1603.499
|
||
// 2024.10.23 21:18:34 Log - VRCApplication: HandleApplicationQuit at 936.5161
|
||
if (string.Compare(line, offset, "VRCApplication: OnApplicationQuit at ", 0, 37, StringComparison.Ordinal) != 0 &&
|
||
string.Compare(line, offset, "VRCApplication: HandleApplicationQuit at ", 0, 41, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"vrc-quit"
|
||
});
|
||
|
||
VrcClosedGracefully = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseOpenVRInit(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2022.07.29 02:52:14 Log - OpenVR initialized!
|
||
|
||
// 2023.04.22 16:52:28 Log - Initializing VRSDK.
|
||
// 2023.04.22 16:52:29 Log - StartVRSDK: Open VR Loader
|
||
|
||
// 2024.07.26 01:48:56 Log - STEAMVR HMD Model: Index
|
||
|
||
if (string.Compare(line, offset, "Initializing VRSDK.", 0, 19, StringComparison.Ordinal) != 0 &&
|
||
string.Compare(line, offset, "STEAMVR HMD Model: ", 0, 20, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"openvr-init"
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseDesktopMode(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.04.22 16:54:18 Log - VR Disabled
|
||
|
||
if (string.Compare(line, offset, "VR Disabled", 0, 11, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"desktop-mode"
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogStringDownload(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.03.23 11:37:21 Log - [String Download] Attempting to load String from URL 'https://pastebin.com/raw/BaW6NL2L'
|
||
var check = "] Attempting to load String from URL '";
|
||
if (!line.Contains(check))
|
||
return false;
|
||
|
||
var lineOffset = line.LastIndexOf(check, StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
|
||
var stringData = line.Substring(lineOffset + check.Length);
|
||
stringData = stringData.Remove(stringData.Length - 1);
|
||
|
||
if (stringData.StartsWith("http://127.0.0.1:22500") || stringData.StartsWith("http://localhost:22500"))
|
||
return true; // ignore own requests
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"resource-load-string",
|
||
stringData
|
||
});
|
||
return true;
|
||
}
|
||
|
||
private bool ParseLogImageDownload(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.03.23 11:32:25 Log - [Image Download] Attempting to load image from URL 'https://i.imgur.com/lCfUMX0.jpeg'
|
||
var check = "] Attempting to load image from URL '";
|
||
if (!line.Contains(check))
|
||
return false;
|
||
|
||
var lineOffset = line.LastIndexOf(check, StringComparison.Ordinal);
|
||
if (lineOffset < 0)
|
||
return true;
|
||
|
||
var imageData = line.Substring(lineOffset + check.Length);
|
||
imageData = imageData.Remove(imageData.Length - 1);
|
||
|
||
if (imageData.StartsWith("http://127.0.0.1:22500") || imageData.StartsWith("http://localhost:22500"))
|
||
return true; // ignore own requests
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"resource-load-image",
|
||
imageData
|
||
});
|
||
return true;
|
||
}
|
||
|
||
private bool ParseVoteKick(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.06.02 01:08:04 Log - [Behaviour] Received executive message: You have been kicked from the instance by majority vote
|
||
// 2023.06.02 01:11:58 Log - [Behaviour] You have been kicked from this world for an hour.
|
||
|
||
if (string.Compare(line, offset, "[Behaviour] Received executive message: ", 0, 40, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
line.Substring(offset + 40)
|
||
});
|
||
return true;
|
||
}
|
||
|
||
private bool ParseFailedToJoin(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.09.01 10:42:19 Warning - [Behaviour] Failed to join instance 'wrld_78eb6b52-fd5a-4954-ba28-972c92c8cc77:82384~hidden(usr_a9bf892d-b447-47ce-a572-20c83dbfffd8)~region(eu)' due to 'That instance is using an outdated version of VRChat. You won't be able to join them until they update!'
|
||
|
||
if (string.Compare(line, offset, "[Behaviour] Failed to join instance ", 0, 36, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
line.Substring(offset + 12)
|
||
});
|
||
return true;
|
||
}
|
||
|
||
private bool ParseOscFailedToStart(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2023.09.26 04:12:57 Warning - Could not Start OSC: Address already in use
|
||
|
||
if (string.Compare(line, offset, "Could not Start OSC: ", 0, 21, StringComparison.Ordinal) != 0)
|
||
return false;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
$"VRChat couldn't start OSC server, \"{line.Substring(offset)}\""
|
||
});
|
||
return true;
|
||
}
|
||
|
||
private bool ParseInstanceResetWarning(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2024.08.30 01:43:40 Log - [ModerationManager] This instance will be reset in 60 minutes due to its age.
|
||
if (!line.Contains("[ModerationManager] This instance will be reset in "))
|
||
return false;
|
||
|
||
int index = line.IndexOf("[ModerationManager] This instance will be reset in ", StringComparison.Ordinal) + 20;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
line[index..]
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseVoteKickInitiation(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2024.08.29 02:04:47 Log - [ModerationManager] A vote kick has been initiated against בורקס במאווררים 849d, do you agree?
|
||
if (!line.Contains("[ModerationManager] A vote kick has been initiated against ", StringComparison.Ordinal))
|
||
return false;
|
||
|
||
int index = line.IndexOf("[ModerationManager] A vote kick has been initiated against ", StringComparison.Ordinal) + 20;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
line[index..]
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseVoteKickSuccess(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// 2024.08.29 02:05:21 Log - [ModerationManager] Vote to kick בורקס במאווררים 849d succeeded
|
||
if (!line.Contains("[ModerationManager] Vote to kick ", StringComparison.Ordinal))
|
||
return false;
|
||
|
||
int index = line.IndexOf("[ModerationManager] Vote to kick ", StringComparison.Ordinal) + 20;
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"event",
|
||
line[index..]
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool ParseStickerSpawn(FileInfo fileInfo, LogContext logContext, string line, int offset)
|
||
{
|
||
// [StickersManager] User usr_032383a7-748c-4fb2-94e4-bcb928e5de6b (Natsumi-sama) spawned sticker inv_8b380ee4-9a8a-484e-a0c3-b01290b92c6a
|
||
var index = line.IndexOf("[StickersManager] User ", StringComparison.Ordinal);
|
||
if (index == -1 || !line.Contains("inv_") || !line.Contains("spawned sticker"))
|
||
return false;
|
||
|
||
var info = line.Substring(index + 23);
|
||
|
||
var (userId, displayName) = ParseUserInfo(info); // it's flipped
|
||
if (string.IsNullOrEmpty(displayName) && string.IsNullOrEmpty(userId))
|
||
{
|
||
logger.Warn("Failed to parse user info from log line: {0}", line);
|
||
return true;
|
||
}
|
||
|
||
var inventoryIdIndex = info.IndexOf("inv_", StringComparison.Ordinal);
|
||
var inventoryId = info.Substring(inventoryIdIndex);
|
||
|
||
AppendLog(new[]
|
||
{
|
||
fileInfo.Name,
|
||
ConvertLogTimeToISO8601(line),
|
||
"sticker-spawn",
|
||
userId ?? string.Empty,
|
||
displayName ?? string.Empty,
|
||
inventoryId
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
public string[][] Get()
|
||
{
|
||
Update();
|
||
|
||
if (m_ResetLog == false &&
|
||
m_LogList.Count > 0)
|
||
{
|
||
m_LogListLock.EnterWriteLock();
|
||
try
|
||
{
|
||
string[][] items;
|
||
|
||
if (m_LogList.Count > 1000)
|
||
{
|
||
items = new string[1000][];
|
||
m_LogList.CopyTo(0, items, 0, 1000);
|
||
m_LogList.RemoveRange(0, 1000);
|
||
}
|
||
else
|
||
{
|
||
items = m_LogList.ToArray();
|
||
m_LogList.Clear();
|
||
}
|
||
|
||
return items;
|
||
}
|
||
finally
|
||
{
|
||
m_LogListLock.ExitWriteLock();
|
||
}
|
||
}
|
||
|
||
return new string[][] { };
|
||
}
|
||
|
||
private static (string? DisplayName, string? UserId) ParseUserInfo(string userInfo)
|
||
{
|
||
string? userDisplayName;
|
||
string? userId;
|
||
|
||
int pos = userInfo.LastIndexOf(" (", StringComparison.Ordinal);
|
||
if (pos >= 0)
|
||
{
|
||
userDisplayName = userInfo.Substring(0, pos);
|
||
userId = userInfo.Substring(pos + 2, userInfo.LastIndexOf(')') - (pos + 2));
|
||
}
|
||
else
|
||
{
|
||
userDisplayName = userInfo;
|
||
userId = null;
|
||
}
|
||
|
||
return (userDisplayName, userId);
|
||
}
|
||
|
||
private class LogContext
|
||
{
|
||
public bool AudioDeviceChanged;
|
||
public string LastAudioDevice;
|
||
public readonly HashSet<string> VideoPlaybackErrors = new(50);
|
||
public long Length;
|
||
public string LocationDestination;
|
||
public long Position;
|
||
public string RecentWorldName;
|
||
public bool ShaderKeywordsLimitReached;
|
||
}
|
||
}
|
||
} |