-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8431777
Showing
19 changed files
with
856 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
*.swp | ||
*.*~ | ||
project.lock.json | ||
.DS_Store | ||
*.pyc | ||
nupkg/ | ||
|
||
# Visual Studio Code | ||
.vscode | ||
|
||
# Rider | ||
.idea | ||
|
||
# User-specific files | ||
*.suo | ||
*.user | ||
*.userosscache | ||
*.sln.docstates | ||
|
||
# Build results | ||
[Dd]ebug/ | ||
[Dd]ebugPublic/ | ||
[Rr]elease/ | ||
[Rr]eleases/ | ||
x64/ | ||
x86/ | ||
build/ | ||
bld/ | ||
[Bb]in/ | ||
[Oo]bj/ | ||
[Oo]ut/ | ||
msbuild.log | ||
msbuild.err | ||
msbuild.wrn | ||
|
||
# Visual Studio 2015 | ||
.vs/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 nnaaa-vr | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Simple VRChat log parser leveraging the XSNotifications library at https://github.com/nnaaa-vr/XSNotifications to display toast notifications in VR. Currently supported events include world changes, player joined, player left, portal dropped, and shader keywords exceeded. Logged events are also saved with timestamps to session logs, so previous sessions can be referenced if necessary (see who was where, what world you were in, et cetera). | ||
|
||
On first run, a config.json file will be generated at %AppData%\..\LocalLow\XSOverlay VRChat Parser\config.json. Please see this file for configuration options, and then restart the process after making any changes. | ||
|
||
The process runs in the background and does not have an active window. | ||
The parsing is a bit messy at the moment (but functional). VRChat's log output is very inconsistent and has changed fairly frequently. The parsing function was written around two years ago and has just been patched as time went on. | ||
|
||
There are plans to expand the feature set of this application, including additional event types for those running extended logging in VRChat. Of course, feel free to make PRs yourselves! | ||
|
||
More detailed documentation will come soon. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 16 | ||
VisualStudioVersion = 16.0.31105.61 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XSOverlay VRChat Parser", "XSOverlay VRChat Parser\XSOverlay VRChat Parser.csproj", "{02DC8D37-5DAC-448B-BAFE-14C8FEFBD782}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{02DC8D37-5DAC-448B-BAFE-14C8FEFBD782}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{02DC8D37-5DAC-448B-BAFE-14C8FEFBD782}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{02DC8D37-5DAC-448B-BAFE-14C8FEFBD782}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{02DC8D37-5DAC-448B-BAFE-14C8FEFBD782}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {459F651C-259C-421A-9E69-346F3636C4E4} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System; | ||
|
||
namespace XSOverlay_VRChat_Parser.Helpers | ||
{ | ||
[AttributeUsage(AttributeTargets.Property)] | ||
public class Annotation : Attribute | ||
{ | ||
public string description; | ||
public string groupDescription; | ||
public bool startsGroup; | ||
public Annotation(string _description, bool _startsGroup = false, string _groupDescription = "") | ||
{ | ||
this.description = _description; | ||
this.startsGroup = _startsGroup; | ||
this.groupDescription = _groupDescription; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace XSOverlay_VRChat_Parser.Helpers | ||
{ | ||
public enum EventType | ||
{ | ||
PlayerJoin, | ||
PlayerLeft, | ||
WorldChange, | ||
KeywordsExceeded, | ||
PortalDropped | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace XSOverlay_VRChat_Parser.Helpers | ||
{ | ||
public enum LogEventType | ||
{ | ||
Error, | ||
Event, | ||
Info | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
using System; | ||
using System.IO; | ||
using System.Text; | ||
using System.Threading; | ||
|
||
namespace XSOverlay_VRChat_Parser.Helpers | ||
{ | ||
public class TailSubscription : IDisposable | ||
{ | ||
private string _filePath { get; set; } | ||
public delegate void OnUpdate(string content); | ||
private OnUpdate updateFunc { get; set; } | ||
private Timer timer { get; set; } | ||
private long lastSize { get; set; } | ||
|
||
public TailSubscription(string filePath, OnUpdate func, long dueTimeMilliseconds, long frequencyMilliseconds) | ||
{ | ||
_filePath = filePath; | ||
updateFunc = func; | ||
lastSize = new FileInfo(filePath).Length; | ||
timer = new Timer(new TimerCallback(ExecOnUpdate), null, dueTimeMilliseconds, frequencyMilliseconds); | ||
} | ||
|
||
private void ExecOnUpdate(object timerState) | ||
{ | ||
if (!File.Exists(_filePath)) | ||
{ | ||
Dispose(); | ||
return; | ||
} | ||
|
||
long size = new FileInfo(_filePath).Length; | ||
|
||
if (size > lastSize) | ||
{ | ||
using (FileStream fs = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) | ||
{ | ||
using (StreamReader sr = new StreamReader(fs)) | ||
{ | ||
sr.BaseStream.Seek(lastSize, SeekOrigin.Begin); | ||
|
||
StringBuilder outputContent = new StringBuilder(); | ||
string line = string.Empty; | ||
|
||
while ((line = sr.ReadLine()) != null) | ||
outputContent.Append(line + "\n"); | ||
|
||
lastSize = sr.BaseStream.Position; | ||
|
||
updateFunc(outputContent.ToString()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
timer.Dispose(); | ||
updateFunc = null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Text.Json; | ||
using XSNotifications.Enum; | ||
using XSNotifications.Helpers; | ||
using XSOverlay_VRChat_Parser.Helpers; | ||
|
||
namespace XSOverlay_VRChat_Parser.Models | ||
{ | ||
public class ConfigurationModel | ||
{ | ||
[Annotation("Polling frequency for individual log file updates.", true, "GENERAL CONFIGURATION")] | ||
public long ParseFrequencyMilliseconds { get; set; } | ||
[Annotation("Polling frequency for new logs in OutputLogRoot")] | ||
public long DirectoryPollFrequencyMilliseconds { get; set; } | ||
[Annotation("Absolute path to output log root for VRChat. Environment variables will be expanded.")] | ||
public string OutputLogRoot { get; set; } | ||
[Annotation("Determines whether or not logs of parsed events will be written to the session log in the user folder. Valid values: true, false")] | ||
public bool LogNotificationEvents { get; set; } | ||
[Annotation("Volume for incoming notification sounds. Valid values: 0.0 -> 1.0.")] | ||
public float NotificationVolume { get; set; } | ||
[Annotation("Opacity for toast notifications. Valid values: 0.0 -> 1.0.")] | ||
public float Opacity { get; set; } | ||
|
||
[Annotation("Determines whether or not player join notifications are delivered. Valid values: true, false", true, "PLAYER JOINED")] | ||
public bool DisplayPlayerJoined { get; set; } | ||
[Annotation("Period of time in seconds for the player join notification to remain on screen. Valid values: 0.0 -> float32 max")] | ||
public float PlayerJoinedNotificationTimeoutSeconds { get; set; } | ||
[Annotation("Relative path to icon for player joins. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string PlayerJoinedIconPath { get; set; } | ||
[Annotation("Relative path to ogg-formatted audio for player joins. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string PlayerJoinedAudioPath { get; set; } | ||
|
||
[Annotation("Determines whether or not player left notifications are delivered. Valid values: true, false", true, "PLAYER LEFT")] | ||
public bool DisplayPlayerLeft { get; set; } | ||
[Annotation("Period of time in seconds for the player left notification to remain on screen. Valid values: 0.0 -> float32 max")] | ||
public float PlayerLeftNotificationTimeoutSeconds { get; set; } | ||
[Annotation("Relative path to icon for player left. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string PlayerLeftIconPath { get; set; } | ||
[Annotation("Relative path to ogg-formatted audio for player left. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string PlayerLeftAudioPath { get; set; } | ||
|
||
[Annotation("Determines whether or not world change notifications are delivered. Valid values: true, false", true, "WORLD CHANGED")] | ||
public bool DisplayWorldChanged { get; set; } | ||
[Annotation("Period of time in seconds for player join/leave notifications to be silenced on world join. This is to avoid spam from enumerating everyone currently in the target world. Valid values: 0.0 -> float32 max")] | ||
public long WorldJoinSilenceSeconds { get; set; } | ||
[Annotation("Determines whether or not player join/leave notifications are silenced on world join. Warning, this gets spammy if on! Valid values: true, false")] | ||
public bool DisplayJoinLeaveSilencedOverride { get; set; } | ||
[Annotation("Period of time in seconds for the world changed notification to remain on screen. Value values: 0.0 -> float32 max")] | ||
public float WorldChangedNotificationTimeoutSeconds { get; set; } | ||
[Annotation("Relative path to icon for world changed. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string WorldChangedIconPath { get; set; } | ||
[Annotation("Relative path to ogg-formatted audio for world changed. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string WorldChangedAudioPath { get; set; } | ||
|
||
[Annotation("Determines whether or not shader keywords exceeded notifications are delivered. Valid values: true, false", true, "SHADER KEYWORDS EXCEEDED")] | ||
public bool DisplayMaximumKeywordsExceeded { get; set; } | ||
[Annotation("Period of time in seconds for shader keywords exceeded notification to remain on screen. Valid values: 0.0 -> float32 max")] | ||
public float MaximumKeywordsExceededTimeoutSeconds { get; set; } | ||
[Annotation("Period of time in seconds after a shader keywords exceeded notification is sent to ignore shader keywords exceeded events. Valid values: 0.0 -> float32 max")] | ||
public float MaximumKeywordsExceededCooldownSeconds { get; set; } | ||
[Annotation("Relative path to icon for shader keywords exceeded. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string MaximumKeywordsExceededIconPath { get; set; } | ||
[Annotation("Relative path to ogg-formatted audio for shader keywords exceeded. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string MaximumKeywordsExceededAudioPath { get; set; } | ||
|
||
[Annotation("Determines whether or not portal dropped notifications are delivered. Valid values: true, false", true, "PORTAL DROPPED")] | ||
public bool DisplayPortalDropped { get; set; } | ||
[Annotation("Period of time in seconds for portal dropped notification to remain on screen. Valid values: 0.0 -> float32 max")] | ||
public float PortalDroppedTimeoutSeconds { get; set; } | ||
[Annotation("Relative path to icon for portal dropped. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string PortalDroppedIconPath { get; set; } | ||
[Annotation("Relative path to ogg-formatted audio for portal dropped. Other valid values include: \"\", \"default\", \"warning\", \"error\"")] | ||
public string PortalDroppedAudioPath { get; set; } | ||
|
||
public ConfigurationModel() | ||
{ | ||
ParseFrequencyMilliseconds = 300; | ||
DirectoryPollFrequencyMilliseconds = 5000; | ||
OutputLogRoot = @"%AppData%\..\LocalLow\VRChat\vrchat"; | ||
LogNotificationEvents = true; | ||
NotificationVolume = 0.2f; | ||
Opacity = 0.75f; | ||
|
||
DisplayPlayerJoined = true; | ||
PlayerJoinedNotificationTimeoutSeconds = 2.5f; | ||
PlayerJoinedIconPath = @"\Resources\Icons\player_joined.png"; | ||
PlayerJoinedAudioPath = @"\Resources\Audio\player_joined.ogg"; | ||
|
||
DisplayPlayerLeft = true; | ||
PlayerLeftNotificationTimeoutSeconds = 2.5f; | ||
PlayerLeftIconPath = @"\Resources\Icons\player_left.png"; | ||
PlayerLeftAudioPath = @"\Resources\Audio\player_left.ogg"; | ||
|
||
DisplayWorldChanged = true; | ||
WorldJoinSilenceSeconds = 20; | ||
DisplayJoinLeaveSilencedOverride = false; | ||
WorldChangedNotificationTimeoutSeconds = 3.0f; | ||
WorldChangedIconPath = @"\Resources\Icons\world_changed.png"; | ||
WorldChangedAudioPath = XSGlobals.GetBuiltInAudioSourceString(XSAudioDefault.Default); | ||
|
||
DisplayMaximumKeywordsExceeded = false; | ||
MaximumKeywordsExceededTimeoutSeconds = 3.0f; | ||
MaximumKeywordsExceededCooldownSeconds = 600.0f; | ||
MaximumKeywordsExceededIconPath = @"\Resources\Icons\keywords_exceeded.png"; | ||
MaximumKeywordsExceededAudioPath = XSGlobals.GetBuiltInAudioSourceString(XSAudioDefault.Warning); | ||
|
||
DisplayPortalDropped = true; | ||
PortalDroppedTimeoutSeconds = 3.0f; | ||
PortalDroppedIconPath = @"\Resources\Icons\portal_dropped.png"; | ||
PortalDroppedAudioPath = XSGlobals.GetBuiltInAudioSourceString(XSAudioDefault.Default); | ||
} | ||
|
||
public string GetLocalResourcePath(string path) => Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + path; | ||
|
||
public string AsJson(bool annotated = true) | ||
{ | ||
string asJson = JsonSerializer.Serialize<ConfigurationModel>(this, new JsonSerializerOptions() { WriteIndented = true }); | ||
|
||
// JSON teeeeeeeeechnically doesn't support comments, but we can add them and there's a flag in our json reader for skipping them, so we're good. Theoretically. | ||
if (annotated) | ||
{ | ||
string[] lines = asJson.Split('\n'); | ||
|
||
StringBuilder sb = new StringBuilder(); | ||
|
||
PropertyInfo[] properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); | ||
|
||
Dictionary<string, Tuple<PropertyInfo, Annotation>> propertyAnnotations = new Dictionary<string, Tuple<PropertyInfo, Annotation>>(); | ||
|
||
foreach (PropertyInfo p in properties) | ||
propertyAnnotations.Add(p.Name.ToLower(), new Tuple<PropertyInfo, Annotation>(p, | ||
(Annotation)p.GetCustomAttributes(typeof(Annotation), true).GetValue(0))); | ||
|
||
StringBuilder commentBuilder = new StringBuilder(); | ||
StringBuilder propertyBuilder = new StringBuilder(); | ||
foreach (string line in lines) | ||
{ | ||
if (line.Trim() == "}") | ||
{ | ||
if (propertyBuilder.ToString() != string.Empty) | ||
{ | ||
if (commentBuilder.Length > 0) | ||
sb.Append('\n' + commentBuilder.ToString() + '\n'); | ||
sb.Append(propertyBuilder.ToString() + "}"); | ||
continue; | ||
} | ||
} | ||
else if (!line.Contains(':')) | ||
{ | ||
sb.Append(line + '\n'); | ||
continue; | ||
} | ||
|
||
string propertyNameLower = line.Substring(0, line.IndexOf(':')).Trim().Replace("\"", "").ToLower(); | ||
string whitespace = line.Substring(0, line.IndexOf('\"')); | ||
|
||
if (propertyAnnotations.ContainsKey(propertyNameLower)) | ||
{ | ||
Tuple<PropertyInfo, Annotation> pa = propertyAnnotations[propertyNameLower]; | ||
|
||
if (pa.Item2.startsGroup) | ||
{ | ||
if (commentBuilder.Length > 0) | ||
sb.Append('\n' + commentBuilder.ToString() + '\n'); | ||
if (propertyBuilder.Length > 0) | ||
sb.Append(propertyBuilder.ToString()); | ||
|
||
commentBuilder.Clear(); | ||
propertyBuilder.Clear(); | ||
|
||
commentBuilder.Append($"{whitespace}// {pa.Item2.groupDescription}\n"); | ||
} | ||
|
||
commentBuilder.Append($"{whitespace}// {pa.Item1.Name} : {pa.Item2.description}\n"); | ||
propertyBuilder.Append(line + '\n'); | ||
} | ||
else | ||
propertyBuilder.Append(line + '\n'); | ||
} | ||
|
||
asJson = sb.ToString(); | ||
} | ||
|
||
return asJson; | ||
} | ||
} | ||
} |
Oops, something went wrong.