Skip to content

Commit

Permalink
Add custom log line functionality for OP and downstream plugins (Rain…
Browse files Browse the repository at this point in the history
  • Loading branch information
valarnin authored Sep 16, 2022
1 parent 9367783 commit 576bb7b
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 7 deletions.
183 changes: 183 additions & 0 deletions OverlayPlugin.Core/Integration/FFXIVCustomLogLines.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using System;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace RainbowMage.OverlayPlugin
{
class FFXIVCustomLogLines
{
private ILogger logger;
private FFXIVRepository repository;
private Dictionary<uint, ILogLineRegistryEntry> registry = new Dictionary<uint, ILogLineRegistryEntry>();

private const uint registeredCustomLogLineID = 256;

public FFXIVCustomLogLines(TinyIoCContainer container)
{
logger = container.Resolve<ILogger>();
repository = container.Resolve<FFXIVRepository>();
var main = container.Resolve<PluginMain>();

var pluginDirectory = main.PluginDirectory;

var reservedLogLinesPath = Path.Combine(pluginDirectory, "resources", "reserved_log_lines.json");

try
{
var jsonData = File.ReadAllText(reservedLogLinesPath);
var reservedData = JsonConvert.DeserializeObject<List<ConfigReservedLogLine>>(jsonData);
logger.Log(LogLevel.Warning, $"Parsing {reservedData.Count} reserved log line entries.");
foreach (var reservedDataEntry in reservedData)
{
if (reservedDataEntry.Source == null || reservedDataEntry.Version == null)
{
logger.Log(LogLevel.Warning, $"Reserved log line entry missing Source or Version.");
continue;
}
if (reservedDataEntry.ID == null)
{
if (reservedDataEntry.StartID == null || reservedDataEntry.EndID == null)
{
logger.Log(LogLevel.Warning, $"Reserved log line entry missing StartID ({reservedDataEntry.StartID}) or EndID ({reservedDataEntry.EndID}).");
continue;
}
var Source = reservedDataEntry.Source;
var Version = reservedDataEntry.Version.Value;
var StartID = reservedDataEntry.StartID.Value;
var EndID = reservedDataEntry.EndID.Value;
logger.Log(LogLevel.Debug, $"Reserving log line entries {StartID}-{EndID} for Source {Source}, Version {Version}.");
for (uint ID = StartID; ID < EndID; ++ID)
{
if (registry.ContainsKey(ID))
{
logger.Log(LogLevel.Error, $"Reserved log line entry already registered ({ID}).");
continue;
}
registry[ID] = new LogLineRegistryEntry()
{
ID = ID,
Source = Source,
Version = Version,
};
}
}
else
{
var ID = reservedDataEntry.ID.Value;
if (registry.ContainsKey(ID))
{
logger.Log(LogLevel.Error, $"Reserved log line entry already registered ({ID}).");
continue;
}
var Source = reservedDataEntry.Source;
var Version = reservedDataEntry.Version.Value;
logger.Log(LogLevel.Debug, $"Reserving log line entry for ID {ID}, Source {Source}, Version {Version}.");
registry[ID] = new LogLineRegistryEntry()
{
ID = ID,
Source = Source,
Version = Version,
};
}
}
} catch(Exception ex)
{
logger.Log(LogLevel.Error, string.Format(Resources.ErrorCouldNotLoadReservedLogLines, ex));
}
}

public Func<string, bool> RegisterCustomLogLine(ILogLineRegistryEntry entry)
{
// Don't allow any attempt to write a custom log line with FFXIV_ACT_Plugin as the source.
// This prevents a downstream plugin from attempting to register e.g. `00` lines by just pretending to be FFXIV_ACT_Plugin.
if (entry.Source == "FFXIV_ACT_Plugin")
{
logger.Log(LogLevel.Warning, $"Attempted to register custom log line with reserved source.");
return null;
}
var ID = entry.ID;
if (registry.ContainsKey(ID))
{
// Allow re-registering the handler if the ID and Source match.
// Implicitly don't allow re-registering the same handler if the Version changes to prevent log file confusion.
if (!registry[ID].Equals(entry))
{
logger.Log(LogLevel.Warning, $"Reserved log line entry already registered ({ID}).");
return null;
}
}
// Write out that a new log line has been registered. Prevent newlines in the string input for sanity.
var Source = entry.Source.Replace("\r", "\\r").Replace("\n", "\\n");
repository.WriteLogLineImpl(registeredCustomLogLineID, $"{ID}|{Source}|{entry.Version}");
registry[ID] = entry;
return (line) => {
if (line.Contains("\r") || line.Contains("\n"))
{
logger.Log(LogLevel.Warning, $"Attempted to write custom log line with CR or LF with ID of {ID}");
return false;
}
repository.WriteLogLineImpl(ID, line);
return true;
};
}
}

interface ILogLineRegistryEntry
{
uint ID { get; }
string Source { get; }
uint Version { get; }
}

class LogLineRegistryEntry : ILogLineRegistryEntry
{
public uint ID { get; set; }
public string Source { get; set; }
public uint Version { get; set; }

public override string ToString()
{
return Source + "|" + ID + "|" + Version;
}

public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}

var otherEntry = (ILogLineRegistryEntry)obj;

return ID == otherEntry.ID && Source == otherEntry.Source;
}

public override int GetHashCode()
{
int hash = 17;
hash = hash * 31 + ID.GetHashCode();
hash = hash * 31 + Source.GetHashCode();
return hash;
}
}

internal interface IConfigReservedLogLine
{
uint? ID { get; }
uint? StartID { get; }
uint? EndID { get; }
string Source { get; }
uint? Version { get; }
}

[JsonObject(NamingStrategyType = typeof(Newtonsoft.Json.Serialization.DefaultNamingStrategy))]
internal class ConfigReservedLogLine : IConfigReservedLogLine
{
public uint? ID { get; set; }
public uint? StartID { get; set; }
public uint? EndID { get; set; }
public string Source { get; set; }
public uint? Version { get; set; }
}
}
54 changes: 54 additions & 0 deletions OverlayPlugin.Core/Integration/FFXIVRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Advanced_Combat_Tracker;
using FFXIV_ACT_Plugin.Common;
using System.Collections.Generic;
using System.Reflection;

namespace RainbowMage.OverlayPlugin
{
Expand Down Expand Up @@ -60,6 +61,8 @@ class FFXIVRepository
private readonly ILogger logger;
private IDataRepository repository;
private IDataSubscription subscription;
private MethodInfo logOutputWriteLineFunc;
private object logOutput;

public FFXIVRepository(TinyIoCContainer container)
{
Expand Down Expand Up @@ -297,6 +300,57 @@ public string GetLocaleString()
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
internal bool WriteLogLineImpl(uint ID, string line)
{
if (logOutputWriteLineFunc == null)
{
var plugin = GetPluginData();
if (plugin == null) return false;
var field = plugin.pluginObj.GetType().GetField("_iocContainer", BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
{
logger.Log(LogLevel.Error, "Unable to retrieve _iocContainer field information from FFXIV_ACT_Plugin");
return false;
}
var iocContainer = field.GetValue(plugin.pluginObj);
if (iocContainer == null)
{
logger.Log(LogLevel.Error, "Unable to retrieve _iocContainer field value from FFXIV_ACT_Plugin");
return false;
}
var getServiceMethod = iocContainer.GetType().GetMethod("GetService");
if (getServiceMethod == null)
{
logger.Log(LogLevel.Error, "Unable to retrieve _iocContainer field value from FFXIV_ACT_Plugin");
return false;
}
var logfileAssembly = AppDomain.CurrentDomain.GetAssemblies().
SingleOrDefault(assembly => assembly.GetName().Name == "FFXIV_ACT_Plugin.Logfile");
if (logfileAssembly == null)
{
logger.Log(LogLevel.Error, "Unable to retrieve FFXIV_ACT_Plugin.Logfile assembly");
return false;
}
logOutput = getServiceMethod.Invoke(iocContainer, new object[] { logfileAssembly.GetType("FFXIV_ACT_Plugin.Logfile.ILogOutput") });
if (logOutput == null)
{
logger.Log(LogLevel.Error, "Unable to retrieve LogOutput singleton from FFXIV_ACT_Plugin IOC");
return false;
}
logOutputWriteLineFunc = logOutput.GetType().GetMethod("WriteLine");
if (logOutputWriteLineFunc == null)
{
logger.Log(LogLevel.Error, "Unable to retrieve LogOutput singleton from FFXIV_ACT_Plugin IOC");
return false;
}
}

logOutputWriteLineFunc.Invoke(logOutput, new object[] { (int)ID, DateTime.Now, line });

return true;
}

// LogLineDelegate(uint EventType, uint Seconds, string logline);
public void RegisterLogLineHandler(Action<uint, uint, string> handler)
{
Expand Down
4 changes: 4 additions & 0 deletions OverlayPlugin.Core/OverlayPlugin.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<Compile Include="MemoryProcessors\FFXIVMemory.cs" />
<Compile Include="Integration\FFXIVExportVariables.cs" />
<Compile Include="Integration\FFXIVRepository.cs" />
<Compile Include="Integration\FFXIVCustomLogLines.cs" />
<Compile Include="JSApi\IApiBase.cs" />
<Compile Include="Logger.cs" />
<Compile Include="JSApi\MinimalApi.cs" />
Expand Down Expand Up @@ -502,6 +503,9 @@
<Content Include="resources\preview.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="resources\reserved_log_lines.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fleck">
Expand Down
1 change: 1 addition & 0 deletions OverlayPlugin.Core/PluginMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ public void InitPlugin(TabPage pluginScreenSpace, Label pluginStatusText)
_container.Register(new FFXIVRepository(_container));
_container.Register(new NetworkParser(_container));
_container.Register(new TriggIntegration(_container));
_container.Register(new FFXIVCustomLogLines(_container));
// This timer runs on the UI thread (it has to since we create UI controls) but LoadAddons()
// can block for some time. We run it on the background thread to avoid blocking the UI.
Expand Down
23 changes: 16 additions & 7 deletions OverlayPlugin.Core/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions OverlayPlugin.Core/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@
<data name="ErrorCouldNotLoadPresets" xml:space="preserve">
<value>NewOverlayDialog: Failed to load presets: {0}</value>
</data>
<data name="ErrorCouldNotLoadReservedLogLines" xml:space="preserve">
<value>FFXIVCustomLogLines: Failed to load reserved log line: {0}</value>
</data>
<data name="OverlayPreviewName" xml:space="preserve">
<value>Preview</value>
<comment>This is used as the temporary name for the preview overlay.</comment>
Expand Down
22 changes: 22 additions & 0 deletions OverlayPlugin.Core/resources/reserved_log_lines.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"StartID": 0,
"EndID": 255,
"Source": "FFXIV_ACT_Plugin",
"Version": 0,
"Comment": "Reserved for base FFXIV_ACT_Plugin use"
},
{
"ID": 256,
"Source": "OverlayPlugin",
"Version": 1,
"Comment": "Line to be emitted when a new log line is registered"
},
{
"StartID": 257,
"EndID": 512,
"Source": "OverlayPlugin",
"Version": 0,
"Comment": "Reserved for future OverlayPlugin use"
}
]

0 comments on commit 576bb7b

Please sign in to comment.