add api to mare, change all to file scoped namespace

This commit is contained in:
Stanley Dimant
2022-09-29 15:52:33 +02:00
parent b10a02f228
commit ac6c46390c
27 changed files with 4436 additions and 4373 deletions

View File

@@ -4,32 +4,31 @@ using System.Security.Cryptography;
using System.Text;
using Dalamud.Game.ClientState.Objects.SubKinds;
namespace MareSynchronos.Utils
namespace MareSynchronos.Utils;
public class Crypto
{
public class Crypto
public static string GetFileHash(string filePath)
{
public static string GetFileHash(string filePath)
{
using SHA1CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "");
}
using SHA1CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "");
}
public static string GetHash(string stringToHash)
{
using SHA1CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
}
public static string GetHash(string stringToHash)
{
using SHA1CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
}
public static string GetHash256(string stringToHash)
{
using SHA256CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
}
public static string GetHash256(string stringToHash)
{
using SHA256CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
}
public static string GetHash256(PlayerCharacter character)
{
using SHA256CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "");
}
public static string GetHash256(PlayerCharacter character)
{
using SHA256CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "");
}
}

View File

@@ -11,232 +11,231 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Utils
namespace MareSynchronos.Utils;
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
public delegate void LogIn();
public delegate void LogOut();
public delegate void ClassJobChanged();
public delegate void FrameworkUpdate();
public delegate void VoidDelegate();
public class DalamudUtil : IDisposable
{
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
private readonly ClientState _clientState;
private readonly ObjectTable _objectTable;
private readonly Framework _framework;
private readonly Condition _condition;
public delegate void LogIn();
public delegate void LogOut();
public delegate void ClassJobChanged();
public event LogIn? LogIn;
public event LogOut? LogOut;
public event FrameworkUpdate? FrameworkUpdate;
public event ClassJobChanged? ClassJobChanged;
private uint? classJobId = 0;
public event FrameworkUpdate? DelayedFrameworkUpdate;
public event VoidDelegate? ZoneSwitchStart;
public event VoidDelegate? ZoneSwitchEnd;
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
private bool _sentBetweenAreas = false;
public delegate void FrameworkUpdate();
public delegate void VoidDelegate();
public class DalamudUtil : IDisposable
public unsafe bool IsGameObjectPresent(IntPtr key)
{
private readonly ClientState _clientState;
private readonly ObjectTable _objectTable;
private readonly Framework _framework;
private readonly Condition _condition;
public event LogIn? LogIn;
public event LogOut? LogOut;
public event FrameworkUpdate? FrameworkUpdate;
public event ClassJobChanged? ClassJobChanged;
private uint? classJobId = 0;
public event FrameworkUpdate? DelayedFrameworkUpdate;
public event VoidDelegate? ZoneSwitchStart;
public event VoidDelegate? ZoneSwitchEnd;
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
private bool _sentBetweenAreas = false;
public unsafe bool IsGameObjectPresent(IntPtr key)
foreach (var obj in _objectTable)
{
foreach (var obj in _objectTable)
if (obj.Address == key)
{
if (obj.Address == key)
{
return true;
}
}
return false;
}
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition)
{
_clientState = clientState;
_objectTable = objectTable;
_framework = framework;
_condition = condition;
_clientState.Login += ClientStateOnLogin;
_clientState.Logout += ClientStateOnLogout;
_framework.Update += FrameworkOnUpdate;
if (IsLoggedIn)
{
classJobId = _clientState.LocalPlayer!.ClassJob.Id;
ClientStateOnLogin(null, EventArgs.Empty);
return true;
}
}
private void FrameworkOnUpdate(Framework framework)
{
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
{
if (!_sentBetweenAreas)
{
Logger.Debug("Zone switch/Gpose start");
_sentBetweenAreas = true;
ZoneSwitchStart?.Invoke();
}
return false;
}
return;
}
else if (_sentBetweenAreas)
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition)
{
_clientState = clientState;
_objectTable = objectTable;
_framework = framework;
_condition = condition;
_clientState.Login += ClientStateOnLogin;
_clientState.Logout += ClientStateOnLogout;
_framework.Update += FrameworkOnUpdate;
if (IsLoggedIn)
{
classJobId = _clientState.LocalPlayer!.ClassJob.Id;
ClientStateOnLogin(null, EventArgs.Empty);
}
}
private void FrameworkOnUpdate(Framework framework)
{
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
{
if (!_sentBetweenAreas)
{
Logger.Debug("Zone switch/Gpose end");
_sentBetweenAreas = false;
ZoneSwitchEnd?.Invoke();
Logger.Debug("Zone switch/Gpose start");
_sentBetweenAreas = true;
ZoneSwitchStart?.Invoke();
}
foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
return;
}
else if (_sentBetweenAreas)
{
Logger.Debug("Zone switch/Gpose end");
_sentBetweenAreas = false;
ZoneSwitchEnd?.Invoke();
}
foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
{
try
{
try
{
frameworkInvocation?.Invoke();
}
catch (Exception ex)
{
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
frameworkInvocation?.Invoke();
}
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid())
catch (Exception ex)
{
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
if (classJobId != newclassJobId)
{
classJobId = newclassJobId;
ClassJobChanged?.Invoke();
}
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
}
foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid())
{
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
if (classJobId != newclassJobId)
{
try
{
frameworkInvocation?.Invoke();
}
catch (Exception ex)
{
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
classJobId = newclassJobId;
ClassJobChanged?.Invoke();
}
_delayedFrameworkUpdateCheck = DateTime.Now;
}
private void ClientStateOnLogout(object? sender, EventArgs e)
foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
{
LogOut?.Invoke();
}
private void ClientStateOnLogin(object? sender, EventArgs e)
{
LogIn?.Invoke();
}
public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference)
{
return _objectTable.CreateObjectReference(reference);
}
public bool IsLoggedIn => _clientState.IsLoggedIn;
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
{
return obj != null && obj.IsValid();
}
public unsafe IntPtr GetMinion()
{
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
}
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
if (playerPointer == null) playerPointer = PlayerPointer;
return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
}
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
if (playerPointer == null) playerPointer = PlayerPointer;
return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
}
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id);
public bool IsInGpose => _objectTable[201] != null;
public List<PlayerCharacter> GetPlayerCharacters()
{
return _objectTable.Where(obj =>
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList();
}
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
{
var objTableObj = _objectTable[index];
if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null;
return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj;
}
public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName)
{
foreach (var item in _objectTable)
try
{
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (item.Name.ToString() == characterName) return (PlayerCharacter)item;
frameworkInvocation?.Invoke();
}
return null;
}
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
{
return await _framework.RunOnFrameworkThread(func);
}
public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
{
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
var obj = (GameObject*)characterAddress;
const int tick = 250;
int curWaitTime = 0;
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
catch (Exception ex)
{
Logger.Verbose($"Waiting for {name} to finish drawing");
curWaitTime += tick;
Thread.Sleep(tick);
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
}
_delayedFrameworkUpdateCheck = DateTime.Now;
}
if (ct?.IsCancellationRequested ?? false) return;
// wait quarter a second just in case
private void ClientStateOnLogout(object? sender, EventArgs e)
{
LogOut?.Invoke();
}
private void ClientStateOnLogin(object? sender, EventArgs e)
{
LogIn?.Invoke();
}
public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference)
{
return _objectTable.CreateObjectReference(reference);
}
public bool IsLoggedIn => _clientState.IsLoggedIn;
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
{
return obj != null && obj.IsValid();
}
public unsafe IntPtr GetMinion()
{
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
}
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
if (playerPointer == null) playerPointer = PlayerPointer;
return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
}
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
if (playerPointer == null) playerPointer = PlayerPointer;
return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
}
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id);
public bool IsInGpose => _objectTable[201] != null;
public List<PlayerCharacter> GetPlayerCharacters()
{
return _objectTable.Where(obj =>
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList();
}
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
{
var objTableObj = _objectTable[index];
if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null;
return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj;
}
public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName)
{
foreach (var item in _objectTable)
{
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (item.Name.ToString() == characterName) return (PlayerCharacter)item;
}
return null;
}
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
{
return await _framework.RunOnFrameworkThread(func);
}
public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
{
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
var obj = (GameObject*)characterAddress;
const int tick = 250;
int curWaitTime = 0;
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
{
Logger.Verbose($"Waiting for {name} to finish drawing");
curWaitTime += tick;
Thread.Sleep(tick);
}
public void Dispose()
{
_clientState.Login -= ClientStateOnLogin;
_clientState.Logout -= ClientStateOnLogout;
_framework.Update -= FrameworkOnUpdate;
}
if (ct?.IsCancellationRequested ?? false) return;
// wait quarter a second just in case
Thread.Sleep(tick);
}
public void Dispose()
{
_clientState.Login -= ClientStateOnLogin;
_clientState.Logout -= ClientStateOnLogout;
_framework.Update -= FrameworkOnUpdate;
}
}

View File

@@ -5,108 +5,107 @@ using Dalamud.Logging;
using Dalamud.Utility;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils
namespace MareSynchronos.Utils;
[ProviderAlias("Dalamud")]
public class DalamudLoggingProvider : ILoggerProvider
{
[ProviderAlias("Dalamud")]
public class DalamudLoggingProvider : ILoggerProvider
private readonly ConcurrentDictionary<string, Logger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
public DalamudLoggingProvider()
{
private readonly ConcurrentDictionary<string, Logger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
public DalamudLoggingProvider()
{
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName));
}
public void Dispose()
{
_loggers.Clear();
}
}
internal class Logger : ILogger
public ILogger CreateLogger(string categoryName)
{
private readonly string name;
return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName));
}
public static void Info(string info)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Information($"[{caller}] {info}");
}
public static void Debug(string debug, string stringToHighlight = "")
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty())
{
PluginLog.Warning($"[{caller}] {debug}");
}
else
{
PluginLog.Debug($"[{caller}] {debug}");
}
}
public static void Warn(string warn)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Warning($"[{caller}] {warn}");
}
public static void Verbose(string verbose)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
#if DEBUG
PluginLog.Debug($"[{caller}] {verbose}");
#else
PluginLog.Verbose($"[{caller}] {verbose}");
#endif
}
public Logger(string name)
{
this.name = name;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel)) return;
switch (logLevel)
{
case LogLevel.Debug:
PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Error:
case LogLevel.Critical:
PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Information:
PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Warning:
PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Trace:
default:
#if DEBUG
PluginLog.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}");
#else
PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}");
#endif
break;
}
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state) => default!;
public void Dispose()
{
_loggers.Clear();
}
}
internal class Logger : ILogger
{
private readonly string name;
public static void Info(string info)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Information($"[{caller}] {info}");
}
public static void Debug(string debug, string stringToHighlight = "")
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty())
{
PluginLog.Warning($"[{caller}] {debug}");
}
else
{
PluginLog.Debug($"[{caller}] {debug}");
}
}
public static void Warn(string warn)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Warning($"[{caller}] {warn}");
}
public static void Verbose(string verbose)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
#if DEBUG
PluginLog.Debug($"[{caller}] {verbose}");
#else
PluginLog.Verbose($"[{caller}] {verbose}");
#endif
}
public Logger(string name)
{
this.name = name;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel)) return;
switch (logLevel)
{
case LogLevel.Debug:
PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Error:
case LogLevel.Critical:
PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Information:
PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Warning:
PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Trace:
default:
#if DEBUG
PluginLog.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}");
#else
PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}");
#endif
break;
}
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state) => default!;
}

View File

@@ -6,27 +6,26 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MareSynchronos.Utils
namespace MareSynchronos.Utils;
public static class VariousExtensions
{
public static class VariousExtensions
public static DateTime GetLinkerTime(Assembly assembly)
{
public static DateTime GetLinkerTime(Assembly assembly)
const string BuildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
const string BuildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
var value = attribute.InformationalVersion;
var index = value.IndexOf(BuildVersionMetadataPrefix);
if (index > 0)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(BuildVersionMetadataPrefix);
if (index > 0)
{
value = value[(index + BuildVersionMetadataPrefix.Length)..];
return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture);
}
value = value[(index + BuildVersionMetadataPrefix.Length)..];
return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture);
}
return default;
}
return default;
}
}