mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Add crash handler stuff.
This commit is contained in:
parent
9ba6e4d0af
commit
e08e9c4d13
35 changed files with 1472 additions and 237 deletions
119
Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs
Normal file
119
Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler.Buffers;
|
||||||
|
|
||||||
|
/// <summary> The types of currently hooked and relevant animation loading functions. </summary>
|
||||||
|
public enum AnimationInvocationType : int
|
||||||
|
{
|
||||||
|
PapLoad,
|
||||||
|
ActionLoad,
|
||||||
|
ScheduleClipUpdate,
|
||||||
|
LoadTimelineResources,
|
||||||
|
LoadCharacterVfx,
|
||||||
|
LoadCharacterSound,
|
||||||
|
ApricotSoundPlay,
|
||||||
|
LoadAreaVfx,
|
||||||
|
CharacterBaseLoadAnimation,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> The full crash entry for an invoked vfx function. </summary>
|
||||||
|
public record struct VfxFuncInvokedEntry(
|
||||||
|
double Age,
|
||||||
|
DateTimeOffset Timestamp,
|
||||||
|
int ThreadId,
|
||||||
|
string InvocationType,
|
||||||
|
string CharacterName,
|
||||||
|
string CharacterAddress,
|
||||||
|
string CollectionName) : ICrashDataEntry;
|
||||||
|
|
||||||
|
/// <summary> Only expose the write interface for the buffer. </summary>
|
||||||
|
public interface IAnimationInvocationBufferWriter
|
||||||
|
{
|
||||||
|
/// <summary> Write a line into the buffer with the given data. </summary>
|
||||||
|
/// <param name="characterAddress"> The address of the related character, if known. </param>
|
||||||
|
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
|
||||||
|
/// <param name="collectionName"> The name of the associated collection. Not anonymized. </param>
|
||||||
|
/// <param name="type"> The type of VFX func called. </param>
|
||||||
|
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, AnimationInvocationType type);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimationInvocationBufferWriter, IBufferReader
|
||||||
|
{
|
||||||
|
private const int _version = 1;
|
||||||
|
private const int _lineCount = 64;
|
||||||
|
private const int _lineCapacity = 256;
|
||||||
|
private const string _name = "Penumbra.AnimationInvocation";
|
||||||
|
|
||||||
|
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, AnimationInvocationType type)
|
||||||
|
{
|
||||||
|
var accessor = GetCurrentLineLocking();
|
||||||
|
lock (accessor)
|
||||||
|
{
|
||||||
|
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||||
|
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||||
|
accessor.Write(12, (int)type);
|
||||||
|
accessor.Write(16, characterAddress);
|
||||||
|
var span = GetSpan(accessor, 24, 104);
|
||||||
|
WriteSpan(characterName, span);
|
||||||
|
span = GetSpan(accessor, 128);
|
||||||
|
WriteString(collectionName, span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint TotalCount
|
||||||
|
=> TotalWrittenLines;
|
||||||
|
|
||||||
|
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime)
|
||||||
|
{
|
||||||
|
var lineCount = (int)CurrentLineCount;
|
||||||
|
for (var i = lineCount - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
var line = GetLine(i);
|
||||||
|
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||||
|
var thread = BitConverter.ToInt32(line[8..]);
|
||||||
|
var type = (AnimationInvocationType)BitConverter.ToInt32(line[12..]);
|
||||||
|
var address = BitConverter.ToUInt64(line[16..]);
|
||||||
|
var characterName = ReadString(line[24..]);
|
||||||
|
var collectionName = ReadString(line[128..]);
|
||||||
|
yield return new JsonObject()
|
||||||
|
{
|
||||||
|
[nameof(VfxFuncInvokedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||||
|
[nameof(VfxFuncInvokedEntry.Timestamp)] = timestamp,
|
||||||
|
[nameof(VfxFuncInvokedEntry.ThreadId)] = thread,
|
||||||
|
[nameof(VfxFuncInvokedEntry.InvocationType)] = ToName(type),
|
||||||
|
[nameof(VfxFuncInvokedEntry.CharacterName)] = characterName,
|
||||||
|
[nameof(VfxFuncInvokedEntry.CharacterAddress)] = address.ToString("X"),
|
||||||
|
[nameof(VfxFuncInvokedEntry.CollectionName)] = collectionName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IBufferReader CreateReader()
|
||||||
|
=> new AnimationInvocationBuffer(false);
|
||||||
|
|
||||||
|
public static IAnimationInvocationBufferWriter CreateWriter()
|
||||||
|
=> new AnimationInvocationBuffer();
|
||||||
|
|
||||||
|
private AnimationInvocationBuffer(bool writer)
|
||||||
|
: base(_name, _version)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private AnimationInvocationBuffer()
|
||||||
|
: base(_name, _version, _lineCount, _lineCapacity)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private static string ToName(AnimationInvocationType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
AnimationInvocationType.PapLoad => "PAP Load",
|
||||||
|
AnimationInvocationType.ActionLoad => "Action Load",
|
||||||
|
AnimationInvocationType.ScheduleClipUpdate => "Schedule Clip Update",
|
||||||
|
AnimationInvocationType.LoadTimelineResources => "Load Timeline Resources",
|
||||||
|
AnimationInvocationType.LoadCharacterVfx => "Load Character VFX",
|
||||||
|
AnimationInvocationType.LoadCharacterSound => "Load Character Sound",
|
||||||
|
AnimationInvocationType.ApricotSoundPlay => "Apricot Sound Play",
|
||||||
|
AnimationInvocationType.LoadAreaVfx => "Load Area VFX",
|
||||||
|
AnimationInvocationType.CharacterBaseLoadAnimation => "Load Animation (CharacterBase)",
|
||||||
|
_ => $"Unknown ({(int)type})",
|
||||||
|
};
|
||||||
|
}
|
||||||
85
Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs
Normal file
85
Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler.Buffers;
|
||||||
|
|
||||||
|
/// <summary> Only expose the write interface for the buffer. </summary>
|
||||||
|
public interface ICharacterBaseBufferWriter
|
||||||
|
{
|
||||||
|
/// <summary> Write a line into the buffer with the given data. </summary>
|
||||||
|
/// <param name="characterAddress"> The address of the related character, if known. </param>
|
||||||
|
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
|
||||||
|
/// <param name="collectionName"> The name of the associated collection. Not anonymized. </param>
|
||||||
|
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> The full crash entry for a loaded character base. </summary>
|
||||||
|
public record struct CharacterLoadedEntry(
|
||||||
|
double Age,
|
||||||
|
DateTimeOffset Timestamp,
|
||||||
|
int ThreadId,
|
||||||
|
string CharacterName,
|
||||||
|
string CharacterAddress,
|
||||||
|
string CollectionName) : ICrashDataEntry;
|
||||||
|
|
||||||
|
internal sealed class CharacterBaseBuffer : MemoryMappedBuffer, ICharacterBaseBufferWriter, IBufferReader
|
||||||
|
{
|
||||||
|
private const int _version = 1;
|
||||||
|
private const int _lineCount = 10;
|
||||||
|
private const int _lineCapacity = 256;
|
||||||
|
private const string _name = "Penumbra.CharacterBase";
|
||||||
|
|
||||||
|
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName)
|
||||||
|
{
|
||||||
|
var accessor = GetCurrentLineLocking();
|
||||||
|
lock (accessor)
|
||||||
|
{
|
||||||
|
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||||
|
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||||
|
accessor.Write(12, characterAddress);
|
||||||
|
var span = GetSpan(accessor, 20, 108);
|
||||||
|
WriteSpan(characterName, span);
|
||||||
|
span = GetSpan(accessor, 128);
|
||||||
|
WriteString(collectionName, span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime)
|
||||||
|
{
|
||||||
|
var lineCount = (int)CurrentLineCount;
|
||||||
|
for (var i = lineCount - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
var line = GetLine(i);
|
||||||
|
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||||
|
var thread = BitConverter.ToInt32(line[8..]);
|
||||||
|
var address = BitConverter.ToUInt64(line[12..]);
|
||||||
|
var characterName = ReadString(line[20..]);
|
||||||
|
var collectionName = ReadString(line[128..]);
|
||||||
|
yield return new JsonObject
|
||||||
|
{
|
||||||
|
[nameof(CharacterLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||||
|
[nameof(CharacterLoadedEntry.Timestamp)] = timestamp,
|
||||||
|
[nameof(CharacterLoadedEntry.ThreadId)] = thread,
|
||||||
|
[nameof(CharacterLoadedEntry.CharacterName)] = characterName,
|
||||||
|
[nameof(CharacterLoadedEntry.CharacterAddress)] = address.ToString("X"),
|
||||||
|
[nameof(CharacterLoadedEntry.CollectionName)] = collectionName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint TotalCount
|
||||||
|
=> TotalWrittenLines;
|
||||||
|
|
||||||
|
public static IBufferReader CreateReader()
|
||||||
|
=> new CharacterBaseBuffer(false);
|
||||||
|
|
||||||
|
public static ICharacterBaseBufferWriter CreateWriter()
|
||||||
|
=> new CharacterBaseBuffer();
|
||||||
|
|
||||||
|
private CharacterBaseBuffer(bool writer)
|
||||||
|
: base(_name, _version)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private CharacterBaseBuffer()
|
||||||
|
: base(_name, _version, _lineCount, _lineCapacity)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
215
Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs
Normal file
215
Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO.MemoryMappedFiles;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler.Buffers;
|
||||||
|
|
||||||
|
public class MemoryMappedBuffer : IDisposable
|
||||||
|
{
|
||||||
|
private const int MinHeaderLength = 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4;
|
||||||
|
|
||||||
|
private readonly MemoryMappedFile _file;
|
||||||
|
private readonly MemoryMappedViewAccessor _header;
|
||||||
|
private readonly MemoryMappedViewAccessor[] _lines;
|
||||||
|
|
||||||
|
public readonly int Version;
|
||||||
|
public readonly uint LineCount;
|
||||||
|
public readonly uint LineCapacity;
|
||||||
|
private readonly uint _lineMask;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
protected uint CurrentLineCount
|
||||||
|
{
|
||||||
|
get => _header.ReadUInt32(16);
|
||||||
|
set => _header.Write(16, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected uint CurrentLinePosition
|
||||||
|
{
|
||||||
|
get => _header.ReadUInt32(20);
|
||||||
|
set => _header.Write(20, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint TotalWrittenLines
|
||||||
|
{
|
||||||
|
get => _header.ReadUInt32(24);
|
||||||
|
protected set => _header.Write(24, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryMappedBuffer(string mapName, int version, uint lineCount, uint lineCapacity)
|
||||||
|
{
|
||||||
|
Version = version;
|
||||||
|
LineCount = BitOperations.RoundUpToPowerOf2(Math.Clamp(lineCount, 2, int.MaxValue >> 3));
|
||||||
|
LineCapacity = BitOperations.RoundUpToPowerOf2(Math.Clamp(lineCapacity, 2, int.MaxValue >> 3));
|
||||||
|
_lineMask = LineCount - 1;
|
||||||
|
var fileName = Encoding.UTF8.GetBytes(mapName);
|
||||||
|
var headerLength = (uint)(4 + 4 + 4 + 4 + 4 + 4 + 4 + fileName.Length + 1);
|
||||||
|
headerLength = (headerLength & 0b111) > 0 ? (headerLength & ~0b111u) + 0b1000 : headerLength;
|
||||||
|
var capacity = LineCount * LineCapacity + headerLength;
|
||||||
|
_file = MemoryMappedFile.CreateNew(mapName, capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None,
|
||||||
|
HandleInheritability.Inheritable);
|
||||||
|
_header = _file.CreateViewAccessor(0, headerLength);
|
||||||
|
_header.Write(0, headerLength);
|
||||||
|
_header.Write(4, Version);
|
||||||
|
_header.Write(8, LineCount);
|
||||||
|
_header.Write(12, LineCapacity);
|
||||||
|
_header.WriteArray(28, fileName, 0, fileName.Length);
|
||||||
|
_header.Write(fileName.Length + 28, (byte)0);
|
||||||
|
_lines = Enumerable.Range(0, (int)LineCount).Select(i
|
||||||
|
=> _file.CreateViewAccessor(headerLength + i * LineCapacity, LineCapacity, MemoryMappedFileAccess.ReadWrite))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryMappedBuffer(string mapName, int? expectedVersion = null, uint? expectedMinLineCount = null,
|
||||||
|
uint? expectedMinLineCapacity = null)
|
||||||
|
{
|
||||||
|
_file = MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.ReadWrite, HandleInheritability.Inheritable);
|
||||||
|
using var headerLine = _file.CreateViewAccessor(0, 4, MemoryMappedFileAccess.Read);
|
||||||
|
var headerLength = headerLine.ReadUInt32(0);
|
||||||
|
if (headerLength < MinHeaderLength)
|
||||||
|
Throw($"Map {mapName} did not contain a valid header.");
|
||||||
|
|
||||||
|
_header = _file.CreateViewAccessor(0, headerLength, MemoryMappedFileAccess.ReadWrite);
|
||||||
|
Version = _header.ReadInt32(4);
|
||||||
|
LineCount = _header.ReadUInt32(8);
|
||||||
|
LineCapacity = _header.ReadUInt32(12);
|
||||||
|
_lineMask = LineCount - 1;
|
||||||
|
if (expectedVersion.HasValue && expectedVersion.Value != Version)
|
||||||
|
Throw($"Map {mapName} has version {Version} instead of {expectedVersion.Value}.");
|
||||||
|
|
||||||
|
if (LineCount < expectedMinLineCount)
|
||||||
|
Throw($"Map {mapName} has line count {LineCount} but line count >= {expectedMinLineCount.Value} is required.");
|
||||||
|
|
||||||
|
if (LineCapacity < expectedMinLineCapacity)
|
||||||
|
Throw($"Map {mapName} has line capacity {LineCapacity} but line capacity >= {expectedMinLineCapacity.Value} is required.");
|
||||||
|
|
||||||
|
var name = ReadString(GetSpan(_header, 28));
|
||||||
|
if (name != mapName)
|
||||||
|
Throw($"Map {mapName} does not contain its map name at the expected location.");
|
||||||
|
|
||||||
|
_lines = Enumerable.Range(0, (int)LineCount).Select(i
|
||||||
|
=> _file.CreateViewAccessor(headerLength + i * LineCapacity, LineCapacity, MemoryMappedFileAccess.ReadWrite))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
[DoesNotReturn]
|
||||||
|
void Throw(string text)
|
||||||
|
{
|
||||||
|
_file.Dispose();
|
||||||
|
_disposed = true;
|
||||||
|
throw new Exception(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string ReadString(Span<byte> span)
|
||||||
|
{
|
||||||
|
if (span.IsEmpty)
|
||||||
|
throw new Exception("String from empty span requested.");
|
||||||
|
|
||||||
|
var termination = span.IndexOf((byte)0);
|
||||||
|
if (termination < 0)
|
||||||
|
throw new Exception("String in span is not terminated.");
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(span[..termination]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int WriteString(string text, Span<byte> span)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(text);
|
||||||
|
var length = bytes.Length + 1;
|
||||||
|
if (length > span.Length)
|
||||||
|
throw new Exception($"String {text} is too long to write into span.");
|
||||||
|
|
||||||
|
bytes.CopyTo(span);
|
||||||
|
span[bytes.Length] = 0;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int WriteSpan(ReadOnlySpan<byte> input, Span<byte> span)
|
||||||
|
{
|
||||||
|
var length = input.Length + 1;
|
||||||
|
if (length > span.Length)
|
||||||
|
throw new Exception("Byte array is too long to write into span.");
|
||||||
|
|
||||||
|
input.CopyTo(span);
|
||||||
|
span[input.Length] = 0;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Span<byte> GetLine(int i)
|
||||||
|
{
|
||||||
|
if (i < 0 || i > LineCount)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
lock (_header)
|
||||||
|
{
|
||||||
|
var lineIdx = CurrentLinePosition + i & _lineMask;
|
||||||
|
if (lineIdx > CurrentLineCount)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return GetSpan(_lines[lineIdx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MemoryMappedViewAccessor GetCurrentLineLocking()
|
||||||
|
{
|
||||||
|
MemoryMappedViewAccessor view;
|
||||||
|
lock (_header)
|
||||||
|
{
|
||||||
|
var currentLineCount = CurrentLineCount;
|
||||||
|
if (currentLineCount == LineCount)
|
||||||
|
{
|
||||||
|
var currentLinePos = CurrentLinePosition;
|
||||||
|
view = _lines[currentLinePos]!;
|
||||||
|
CurrentLinePosition = currentLinePos + 1 & _lineMask;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
view = _lines[currentLineCount];
|
||||||
|
++CurrentLineCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
++TotalWrittenLines;
|
||||||
|
_header.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Span<byte> GetSpan(MemoryMappedViewAccessor accessor, int offset = 0)
|
||||||
|
=> GetSpan(accessor, offset, (int)accessor.Capacity - offset);
|
||||||
|
|
||||||
|
protected static unsafe Span<byte> GetSpan(MemoryMappedViewAccessor accessor, int offset, int size)
|
||||||
|
{
|
||||||
|
byte* ptr = null;
|
||||||
|
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
|
||||||
|
size = Math.Min(size, (int)accessor.Capacity - offset);
|
||||||
|
if (size < 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var span = new Span<byte>(ptr + offset + accessor.PointerOffset, size);
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_header.Dispose();
|
||||||
|
foreach (var line in _lines)
|
||||||
|
line.Dispose();
|
||||||
|
_file.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
~MemoryMappedBuffer()
|
||||||
|
=> Dispose(false);
|
||||||
|
}
|
||||||
99
Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs
Normal file
99
Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler.Buffers;
|
||||||
|
|
||||||
|
/// <summary> Only expose the write interface for the buffer. </summary>
|
||||||
|
public interface IModdedFileBufferWriter
|
||||||
|
{
|
||||||
|
/// <summary> Write a line into the buffer with the given data. </summary>
|
||||||
|
/// <param name="characterAddress"> The address of the related character, if known. </param>
|
||||||
|
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
|
||||||
|
/// <param name="collectionName"> The name of the associated collection. Not anonymized. </param>
|
||||||
|
/// <param name="requestedFileName"> The file name as requested by the game. </param>
|
||||||
|
/// <param name="actualFileName"> The actual modded file name loaded. </param>
|
||||||
|
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, ReadOnlySpan<byte> requestedFileName,
|
||||||
|
ReadOnlySpan<byte> actualFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> The full crash entry for a loaded modded file. </summary>
|
||||||
|
public record struct ModdedFileLoadedEntry(
|
||||||
|
double Age,
|
||||||
|
DateTimeOffset Timestamp,
|
||||||
|
int ThreadId,
|
||||||
|
string CharacterName,
|
||||||
|
string CharacterAddress,
|
||||||
|
string CollectionName,
|
||||||
|
string RequestedFileName,
|
||||||
|
string ActualFileName) : ICrashDataEntry;
|
||||||
|
|
||||||
|
internal sealed class ModdedFileBuffer : MemoryMappedBuffer, IModdedFileBufferWriter, IBufferReader
|
||||||
|
{
|
||||||
|
private const int _version = 1;
|
||||||
|
private const int _lineCount = 128;
|
||||||
|
private const int _lineCapacity = 1024;
|
||||||
|
private const string _name = "Penumbra.ModdedFile";
|
||||||
|
|
||||||
|
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, ReadOnlySpan<byte> requestedFileName,
|
||||||
|
ReadOnlySpan<byte> actualFileName)
|
||||||
|
{
|
||||||
|
var accessor = GetCurrentLineLocking();
|
||||||
|
lock (accessor)
|
||||||
|
{
|
||||||
|
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||||
|
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||||
|
accessor.Write(12, characterAddress);
|
||||||
|
var span = GetSpan(accessor, 20, 80);
|
||||||
|
WriteSpan(characterName, span);
|
||||||
|
span = GetSpan(accessor, 92, 80);
|
||||||
|
WriteString(collectionName, span);
|
||||||
|
span = GetSpan(accessor, 172, 260);
|
||||||
|
WriteSpan(requestedFileName, span);
|
||||||
|
span = GetSpan(accessor, 432);
|
||||||
|
WriteSpan(actualFileName, span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint TotalCount
|
||||||
|
=> TotalWrittenLines;
|
||||||
|
|
||||||
|
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime)
|
||||||
|
{
|
||||||
|
var lineCount = (int)CurrentLineCount;
|
||||||
|
for (var i = lineCount - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
var line = GetLine(i);
|
||||||
|
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||||
|
var thread = BitConverter.ToInt32(line[8..]);
|
||||||
|
var address = BitConverter.ToUInt64(line[12..]);
|
||||||
|
var characterName = ReadString(line[20..]);
|
||||||
|
var collectionName = ReadString(line[92..]);
|
||||||
|
var requestedFileName = ReadString(line[172..]);
|
||||||
|
var actualFileName = ReadString(line[432..]);
|
||||||
|
yield return new JsonObject()
|
||||||
|
{
|
||||||
|
[nameof(ModdedFileLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||||
|
[nameof(ModdedFileLoadedEntry.Timestamp)] = timestamp,
|
||||||
|
[nameof(ModdedFileLoadedEntry.ThreadId)] = thread,
|
||||||
|
[nameof(ModdedFileLoadedEntry.CharacterName)] = characterName,
|
||||||
|
[nameof(ModdedFileLoadedEntry.CharacterAddress)] = address.ToString("X"),
|
||||||
|
[nameof(ModdedFileLoadedEntry.CollectionName)] = collectionName,
|
||||||
|
[nameof(ModdedFileLoadedEntry.RequestedFileName)] = requestedFileName,
|
||||||
|
[nameof(ModdedFileLoadedEntry.ActualFileName)] = actualFileName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IBufferReader CreateReader()
|
||||||
|
=> new ModdedFileBuffer(false);
|
||||||
|
|
||||||
|
public static IModdedFileBufferWriter CreateWriter()
|
||||||
|
=> new ModdedFileBuffer();
|
||||||
|
|
||||||
|
private ModdedFileBuffer(bool writer)
|
||||||
|
: base(_name, _version)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private ModdedFileBuffer()
|
||||||
|
: base(_name, _version, _lineCount, _lineCapacity)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
62
Penumbra.CrashHandler/CrashData.cs
Normal file
62
Penumbra.CrashHandler/CrashData.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler;
|
||||||
|
|
||||||
|
/// <summary> A base entry for crash data. </summary>
|
||||||
|
public interface ICrashDataEntry
|
||||||
|
{
|
||||||
|
/// <summary> The timestamp of the event. </summary>
|
||||||
|
DateTimeOffset Timestamp { get; }
|
||||||
|
|
||||||
|
/// <summary> The thread invoking the event. </summary>
|
||||||
|
int ThreadId { get; }
|
||||||
|
|
||||||
|
/// <summary> The age of the event compared to the crash. (Redundantly with the timestamp) </summary>
|
||||||
|
double Age { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> A full set of crash data. </summary>
|
||||||
|
public class CrashData
|
||||||
|
{
|
||||||
|
/// <summary> The mode this data was obtained - manually or from a crash. </summary>
|
||||||
|
public string Mode { get; set; } = "Unknown";
|
||||||
|
|
||||||
|
/// <summary> The time this crash data was generated. </summary>
|
||||||
|
public DateTimeOffset CrashTime { get; set; } = DateTimeOffset.UnixEpoch;
|
||||||
|
|
||||||
|
/// <summary> The FFXIV process ID when this data was generated. </summary>
|
||||||
|
public int ProcessId { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary> The FFXIV Exit Code (if any) when this data was generated. </summary>
|
||||||
|
public int ExitCode { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary> The total amount of characters loaded during this session. </summary>
|
||||||
|
public int TotalCharactersLoaded { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary> The total amount of modded files loaded during this session. </summary>
|
||||||
|
public int TotalModdedFilesLoaded { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary> The total amount of vfx functions invoked during this session. </summary>
|
||||||
|
public int TotalVFXFuncsInvoked { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary> The last character loaded before this crash data was generated. </summary>
|
||||||
|
public CharacterLoadedEntry? LastCharacterLoaded
|
||||||
|
=> LastCharactersLoaded.Count == 0 ? default : LastCharactersLoaded[0];
|
||||||
|
|
||||||
|
/// <summary> The last modded file loaded before this crash data was generated. </summary>
|
||||||
|
public ModdedFileLoadedEntry? LastModdedFileLoaded
|
||||||
|
=> LastModdedFilesLoaded.Count == 0 ? default : LastModdedFilesLoaded[0];
|
||||||
|
|
||||||
|
/// <summary> The last vfx function invoked before this crash data was generated. </summary>
|
||||||
|
public VfxFuncInvokedEntry? LastVfxFuncInvoked
|
||||||
|
=> LastVfxFuncsInvoked.Count == 0 ? default : LastVfxFuncsInvoked[0];
|
||||||
|
|
||||||
|
/// <summary> A collection of the last few characters loaded before this crash data was generated. </summary>
|
||||||
|
public List<CharacterLoadedEntry> LastCharactersLoaded { get; } = [];
|
||||||
|
|
||||||
|
/// <summary> A collection of the last few modded files loaded before this crash data was generated. </summary>
|
||||||
|
public List<ModdedFileLoadedEntry> LastModdedFilesLoaded { get; } = [];
|
||||||
|
|
||||||
|
/// <summary> A collection of the last few vfx functions invoked before this crash data was generated. </summary>
|
||||||
|
public List<VfxFuncInvokedEntry> LastVfxFuncsInvoked { get; } = [];
|
||||||
|
}
|
||||||
53
Penumbra.CrashHandler/GameEventLogReader.cs
Normal file
53
Penumbra.CrashHandler/GameEventLogReader.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler;
|
||||||
|
|
||||||
|
public interface IBufferReader
|
||||||
|
{
|
||||||
|
public uint TotalCount { get; }
|
||||||
|
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GameEventLogReader : IDisposable
|
||||||
|
{
|
||||||
|
public readonly (IBufferReader Reader, string TypeSingular, string TypePlural)[] Readers =
|
||||||
|
[
|
||||||
|
(CharacterBaseBuffer.CreateReader(), "CharacterLoaded", "CharactersLoaded"),
|
||||||
|
(ModdedFileBuffer.CreateReader(), "ModdedFileLoaded", "ModdedFilesLoaded"),
|
||||||
|
(AnimationInvocationBuffer.CreateReader(), "VFXFuncInvoked", "VFXFuncsInvoked"),
|
||||||
|
];
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var (reader, _, _) in Readers)
|
||||||
|
(reader as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public JsonObject Dump(string mode, int processId, int exitCode)
|
||||||
|
{
|
||||||
|
var crashTime = DateTimeOffset.UtcNow;
|
||||||
|
var obj = new JsonObject
|
||||||
|
{
|
||||||
|
[nameof(CrashData.Mode)] = mode,
|
||||||
|
[nameof(CrashData.CrashTime)] = DateTimeOffset.UtcNow,
|
||||||
|
[nameof(CrashData.ProcessId)] = processId,
|
||||||
|
[nameof(CrashData.ExitCode)] = exitCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var (reader, singular, _) in Readers)
|
||||||
|
obj["Last" + singular] = reader.GetLines(crashTime).FirstOrDefault();
|
||||||
|
|
||||||
|
foreach (var (reader, _, plural) in Readers)
|
||||||
|
{
|
||||||
|
obj["Total" + plural] = reader.TotalCount;
|
||||||
|
var array = new JsonArray();
|
||||||
|
foreach (var file in reader.GetLines(crashTime))
|
||||||
|
array.Add(file);
|
||||||
|
obj["Last" + plural] = array;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Penumbra.CrashHandler/GameEventLogWriter.cs
Normal file
17
Penumbra.CrashHandler/GameEventLogWriter.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler;
|
||||||
|
|
||||||
|
public sealed class GameEventLogWriter : IDisposable
|
||||||
|
{
|
||||||
|
public readonly ICharacterBaseBufferWriter CharacterBase = CharacterBaseBuffer.CreateWriter();
|
||||||
|
public readonly IModdedFileBufferWriter FileLoaded = ModdedFileBuffer.CreateWriter();
|
||||||
|
public readonly IAnimationInvocationBufferWriter AnimationFuncInvoked = AnimationInvocationBuffer.CreateWriter();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
(CharacterBase as IDisposable)?.Dispose();
|
||||||
|
(FileLoaded as IDisposable)?.Dispose();
|
||||||
|
(AnimationFuncInvoked as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Penumbra.CrashHandler/Penumbra.CrashHandler.csproj
Normal file
28
Penumbra.CrashHandler/Penumbra.CrashHandler.csproj
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<LangVersion>preview</LangVersion>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
|
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Linux'))">$(HOME)/.xlcore/dalamud/Hooks/dev/</DalamudLibPath>
|
||||||
|
<DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
30
Penumbra.CrashHandler/Program.cs
Normal file
30
Penumbra.CrashHandler/Program.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Penumbra.CrashHandler;
|
||||||
|
|
||||||
|
public class CrashHandler
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length < 2 || !int.TryParse(args[1], out var pid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var reader = new GameEventLogReader();
|
||||||
|
var parent = Process.GetProcessById(pid);
|
||||||
|
|
||||||
|
parent.WaitForExit();
|
||||||
|
var exitCode = parent.ExitCode;
|
||||||
|
var obj = reader.Dump("Crash", pid, exitCode);
|
||||||
|
using var fs = File.Open(args[0], FileMode.Create);
|
||||||
|
using var w = new Utf8JsonWriter(fs, new JsonWriterOptions { Indented = true });
|
||||||
|
obj.WriteTo(w, new JsonSerializerOptions() { WriteIndented = true });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
File.WriteAllText(args[0], $"{DateTime.UtcNow} {pid} {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Ap
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Penumbra.CrashHandler", "Penumbra.CrashHandler\Penumbra.CrashHandler.csproj", "{EE834491-A98F-4395-BE0D-6861AE5AD953}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -44,6 +46,10 @@ Global
|
||||||
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Communication;
|
namespace Penumbra.Communication;
|
||||||
|
|
||||||
|
|
@ -19,5 +20,8 @@ public sealed class CreatingCharacterBase()
|
||||||
{
|
{
|
||||||
/// <seealso cref="PenumbraApi.CreatingCharacterBase"/>
|
/// <seealso cref="PenumbraApi.CreatingCharacterBase"/>
|
||||||
Api = 0,
|
Api = 0,
|
||||||
|
|
||||||
|
/// <seealso cref="CrashHandlerService.OnCreatingCharacterBase"/>
|
||||||
|
CrashHandler = 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ public class Configuration : IPluginConfiguration, ISavable
|
||||||
public string ModDirectory { get; set; } = string.Empty;
|
public string ModDirectory { get; set; } = string.Empty;
|
||||||
public string ExportDirectory { get; set; } = string.Empty;
|
public string ExportDirectory { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool UseCrashHandler { get; set; } = true;
|
||||||
public bool OpenWindowAtStart { get; set; } = false;
|
public bool OpenWindowAtStart { get; set; } = false;
|
||||||
public bool HideUiInGPose { get; set; } = false;
|
public bool HideUiInGPose { get; set; } = false;
|
||||||
public bool HideUiInCutscenes { get; set; } = true;
|
public bool HideUiInCutscenes { get; set; } = true;
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public class EphemeralConfig : ISavable, IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public EphemeralConfig(SaveService saveService, ModPathChanged modPathChanged)
|
public EphemeralConfig(SaveService saveService, ModPathChanged modPathChanged)
|
||||||
{
|
{
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
_modPathChanged = modPathChanged;
|
_modPathChanged = modPathChanged;
|
||||||
Load();
|
Load();
|
||||||
_modPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.EphemeralConfig);
|
_modPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.EphemeralConfig);
|
||||||
|
|
@ -94,13 +94,13 @@ public class EphemeralConfig : ISavable, IDisposable
|
||||||
|
|
||||||
public void Save(StreamWriter writer)
|
public void Save(StreamWriter writer)
|
||||||
{
|
{
|
||||||
using var jWriter = new JsonTextWriter(writer);
|
using var jWriter = new JsonTextWriter(writer);
|
||||||
jWriter.Formatting = Formatting.Indented;
|
jWriter.Formatting = Formatting.Indented;
|
||||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||||
serializer.Serialize(jWriter, this);
|
serializer.Serialize(jWriter, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Overwrite the last saved mod path if it changes. </summary>
|
/// <summary> Overwrite the last saved mod path if it changes. </summary>
|
||||||
private void OnModPathChanged(ModPathChangeType type, Mod mod, DirectoryInfo? old, DirectoryInfo? _)
|
private void OnModPathChanged(ModPathChangeType type, Mod mod, DirectoryInfo? old, DirectoryInfo? _)
|
||||||
{
|
{
|
||||||
if (type is not ModPathChangeType.Moved || !string.Equals(old?.Name, LastModPath, StringComparison.OrdinalIgnoreCase))
|
if (type is not ModPathChangeType.Moved || !string.Equals(old?.Name, LastModPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,25 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
/// <summary> Called for some sound effects caused by animations or VFX. </summary>
|
/// <summary> Called for some sound effects caused by animations or VFX. </summary>
|
||||||
public sealed unsafe class ApricotListenerSoundPlay : FastHook<ApricotListenerSoundPlay.Delegate>
|
public sealed unsafe class ApricotListenerSoundPlay : FastHook<ApricotListenerSoundPlay.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public ApricotListenerSoundPlay(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
public ApricotListenerSoundPlay(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("Apricot Listener Sound Play", Sigs.ApricotListenerSoundPlay, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Apricot Listener Sound Play", Sigs.ApricotListenerSoundPlay, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,6 +50,7 @@ public sealed unsafe class ApricotListenerSoundPlay : FastHook<ApricotListenerSo
|
||||||
newData = _collectionResolver.IdentifyCollection(drawObject, true);
|
newData = _collectionResolver.IdentifyCollection(drawObject, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.ApricotSoundPlay);
|
||||||
var last = _state.SetAnimationData(newData);
|
var last = _state.SetAnimationData(newData);
|
||||||
var ret = Task.Result.Original(a1, a2, a3, a4, a5, a6);
|
var ret = Task.Result.Original(a1, a2, a3, a4, a5, a6);
|
||||||
_state.RestoreAnimationData(last);
|
_state.RestoreAnimationData(last);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
|
|
@ -12,16 +14,18 @@ namespace Penumbra.Interop.Hooks.Animation;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed unsafe class CharacterBaseLoadAnimation : FastHook<CharacterBaseLoadAnimation.Delegate>
|
public sealed unsafe class CharacterBaseLoadAnimation : FastHook<CharacterBaseLoadAnimation.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly DrawObjectState _drawObjectState;
|
private readonly DrawObjectState _drawObjectState;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public CharacterBaseLoadAnimation(HookManager hooks, GameState state, CollectionResolver collectionResolver,
|
public CharacterBaseLoadAnimation(HookManager hooks, GameState state, CollectionResolver collectionResolver,
|
||||||
DrawObjectState drawObjectState)
|
DrawObjectState drawObjectState, CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
_drawObjectState = drawObjectState;
|
_drawObjectState = drawObjectState;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("CharacterBase Load Animation", Sigs.CharacterBaseLoadAnimation, Detour, true);
|
Task = hooks.CreateHook<Delegate>("CharacterBase Load Animation", Sigs.CharacterBaseLoadAnimation, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +37,9 @@ public sealed unsafe class CharacterBaseLoadAnimation : FastHook<CharacterBaseLo
|
||||||
var lastObj = _state.LastGameObject;
|
var lastObj = _state.LastGameObject;
|
||||||
if (lastObj == nint.Zero && _drawObjectState.TryGetValue((nint)drawObject, out var p))
|
if (lastObj == nint.Zero && _drawObjectState.TryGetValue((nint)drawObject, out var p))
|
||||||
lastObj = p.Item1;
|
lastObj = p.Item1;
|
||||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*)lastObj, true));
|
var data = _collectionResolver.IdentifyCollection((GameObject*)lastObj, true);
|
||||||
|
var last = _state.SetAnimationData(data);
|
||||||
|
_crashHandler.LogAnimation(data.AssociatedGameObject, data.ModCollection, AnimationInvocationType.CharacterBaseLoadAnimation);
|
||||||
Penumbra.Log.Excessive($"[CharacterBase Load Animation] Invoked on {(nint)drawObject:X}");
|
Penumbra.Log.Excessive($"[CharacterBase Load Animation] Invoked on {(nint)drawObject:X}");
|
||||||
Task.Result.Original(drawObject);
|
Task.Result.Original(drawObject);
|
||||||
_state.RestoreAnimationData(last);
|
_state.RestoreAnimationData(last);
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
/// <summary> Load a ground-based area VFX. </summary>
|
/// <summary> Load a ground-based area VFX. </summary>
|
||||||
public sealed unsafe class LoadAreaVfx : FastHook<LoadAreaVfx.Delegate>
|
public sealed unsafe class LoadAreaVfx : FastHook<LoadAreaVfx.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public LoadAreaVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
public LoadAreaVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("Load Area VFX", Sigs.LoadAreaVfx, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Load Area VFX", Sigs.LoadAreaVfx, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,10 +29,11 @@ public sealed unsafe class LoadAreaVfx : FastHook<LoadAreaVfx.Delegate>
|
||||||
private nint Detour(uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3)
|
private nint Detour(uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3)
|
||||||
{
|
{
|
||||||
var newData = caster != null
|
var newData = caster != null
|
||||||
? _collectionResolver.IdentifyCollection(caster, true)
|
? _collectionResolver.IdentifyCollection(caster, true)
|
||||||
: ResolveData.Invalid;
|
: ResolveData.Invalid;
|
||||||
|
|
||||||
var last = _state.SetAnimationData(newData);
|
var last = _state.SetAnimationData(newData);
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadAreaVfx);
|
||||||
var ret = Task.Result.Original(vfxId, pos, caster, unk1, unk2, unk3);
|
var ret = Task.Result.Original(vfxId, pos, caster, unk1, unk2, unk3);
|
||||||
Penumbra.Log.Excessive(
|
Penumbra.Log.Excessive(
|
||||||
$"[Load Area VFX] Invoked with {vfxId}, [{pos[0]} {pos[1]} {pos[2]}], 0x{(nint)caster:X}, {unk1}, {unk2}, {unk3} -> 0x{ret:X}.");
|
$"[Load Area VFX] Invoked with {vfxId}, [{pos[0]} {pos[1]} {pos[2]}], 0x{(nint)caster:X}, {unk1}, {unk2}, {unk3} -> 0x{ret:X}.");
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
/// <summary> Characters load some of their voice lines or whatever with this function. </summary>
|
/// <summary> Characters load some of their voice lines or whatever with this function. </summary>
|
||||||
public sealed unsafe class LoadCharacterSound : FastHook<LoadCharacterSound.Delegate>
|
public sealed unsafe class LoadCharacterSound : FastHook<LoadCharacterSound.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public LoadCharacterSound(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
public LoadCharacterSound(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("Load Character Sound",
|
Task = hooks.CreateHook<Delegate>("Load Character Sound",
|
||||||
(nint)FFXIVClientStructs.FFXIV.Client.Game.Character.Character.VfxContainer.MemberFunctionPointers.LoadCharacterSound, Detour,
|
(nint)FFXIVClientStructs.FFXIV.Client.Game.Character.Character.VfxContainer.MemberFunctionPointers.LoadCharacterSound, Detour,
|
||||||
true);
|
true);
|
||||||
|
|
@ -25,7 +29,9 @@ public sealed unsafe class LoadCharacterSound : FastHook<LoadCharacterSound.Dele
|
||||||
private nint Detour(nint container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7)
|
private nint Detour(nint container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7)
|
||||||
{
|
{
|
||||||
var character = *(GameObject**)(container + 8);
|
var character = *(GameObject**)(container + 8);
|
||||||
var last = _state.SetSoundData(_collectionResolver.IdentifyCollection(character, true));
|
var newData = _collectionResolver.IdentifyCollection(character, true);
|
||||||
|
var last = _state.SetSoundData(newData);
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadCharacterSound);
|
||||||
var ret = Task.Result.Original(container, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
|
var ret = Task.Result.Original(container, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
|
||||||
Penumbra.Log.Excessive($"[Load Character Sound] Invoked with {container:X} {unk1} {unk2} {unk3} {unk4} {unk5} {unk6} {unk7} -> {ret}.");
|
Penumbra.Log.Excessive($"[Load Character Sound] Invoked with {container:X} {unk1} {unk2} {unk3} {unk4} {unk5} {unk6} {unk7} -> {ret}.");
|
||||||
_state.RestoreSoundData(last);
|
_state.RestoreSoundData(last);
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.Services;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
@ -12,15 +14,18 @@ namespace Penumbra.Interop.Hooks.Animation;
|
||||||
/// <summary> Load a VFX specifically for a character. </summary>
|
/// <summary> Load a VFX specifically for a character. </summary>
|
||||||
public sealed unsafe class LoadCharacterVfx : FastHook<LoadCharacterVfx.Delegate>
|
public sealed unsafe class LoadCharacterVfx : FastHook<LoadCharacterVfx.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public LoadCharacterVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects)
|
public LoadCharacterVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects,
|
||||||
|
CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("Load Character VFX", Sigs.LoadCharacterVfx, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Load Character VFX", Sigs.LoadCharacterVfx, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +50,7 @@ public sealed unsafe class LoadCharacterVfx : FastHook<LoadCharacterVfx.Delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
var last = _state.SetAnimationData(newData);
|
var last = _state.SetAnimationData(newData);
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadCharacterVfx);
|
||||||
var ret = Task.Result.Original(vfxPath, vfxParams, unk1, unk2, unk3, unk4);
|
var ret = Task.Result.Original(vfxPath, vfxParams, unk1, unk2, unk3, unk4);
|
||||||
Penumbra.Log.Excessive(
|
Penumbra.Log.Excessive(
|
||||||
$"[Load Character VFX] Invoked with {new ByteString(vfxPath)}, 0x{vfxParams->GameObjectId:X}, {vfxParams->TargetCount}, {unk1}, {unk2}, {unk3}, {unk4} -> 0x{ret:X}.");
|
$"[Load Character VFX] Invoked with {new ByteString(vfxPath)}, 0x{vfxParams->GameObjectId:X}, {vfxParams->TargetCount}, {unk1}, {unk2}, {unk3}, {unk4} -> 0x{ret:X}.");
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.CrashHandler;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
|
|
@ -14,19 +16,21 @@ namespace Penumbra.Interop.Hooks.Animation;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResources.Delegate>
|
public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResources.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly ICondition _conditions;
|
private readonly ICondition _conditions;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public LoadTimelineResources(HookManager hooks, GameState state, CollectionResolver collectionResolver, ICondition conditions,
|
public LoadTimelineResources(HookManager hooks, GameState state, CollectionResolver collectionResolver, ICondition conditions,
|
||||||
IObjectTable objects)
|
IObjectTable objects, CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
_conditions = conditions;
|
_conditions = conditions;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
Task = hooks.CreateHook<Delegate>("Load Timeline Resources", Sigs.LoadTimelineResources, Detour, true);
|
_crashHandler = crashHandler;
|
||||||
|
Task = hooks.CreateHook<Delegate>("Load Timeline Resources", Sigs.LoadTimelineResources, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate ulong Delegate(nint timeline);
|
public delegate ulong Delegate(nint timeline);
|
||||||
|
|
@ -39,7 +43,13 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
|
||||||
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
|
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
|
||||||
return Task.Result.Original(timeline);
|
return Task.Result.Original(timeline);
|
||||||
|
|
||||||
var last = _state.SetAnimationData(GetDataFromTimeline(_objects, _collectionResolver, timeline));
|
var newData = GetDataFromTimeline(_objects, _collectionResolver, timeline);
|
||||||
|
var last = _state.SetAnimationData(newData);
|
||||||
|
|
||||||
|
#if false
|
||||||
|
// This is called far too often and spams the log too much.
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadTimelineResources);
|
||||||
|
#endif
|
||||||
var ret = Task.Result.Original(timeline);
|
var ret = Task.Result.Original(timeline);
|
||||||
_state.RestoreAnimationData(last);
|
_state.RestoreAnimationData(last);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,28 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.GameData;
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
using Penumbra.Services;
|
||||||
|
|
||||||
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
/// <summary> Called when some action timelines update. </summary>
|
/// <summary> Called when some action timelines update. </summary>
|
||||||
public sealed unsafe class ScheduleClipUpdate : FastHook<ScheduleClipUpdate.Delegate>
|
public sealed unsafe class ScheduleClipUpdate : FastHook<ScheduleClipUpdate.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public ScheduleClipUpdate(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects)
|
public ScheduleClipUpdate(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects,
|
||||||
|
CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("Schedule Clip Update", Sigs.ScheduleClipUpdate, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Schedule Clip Update", Sigs.ScheduleClipUpdate, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,8 +32,9 @@ public sealed unsafe class ScheduleClipUpdate : FastHook<ScheduleClipUpdate.Dele
|
||||||
private void Detour(ClipScheduler* clipScheduler)
|
private void Detour(ClipScheduler* clipScheduler)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Excessive($"[Schedule Clip Update] Invoked on {(nint)clipScheduler:X}.");
|
Penumbra.Log.Excessive($"[Schedule Clip Update] Invoked on {(nint)clipScheduler:X}.");
|
||||||
var last = _state.SetAnimationData(
|
var newData = LoadTimelineResources.GetDataFromTimeline(_objects, _collectionResolver, clipScheduler->SchedulerTimeline);
|
||||||
LoadTimelineResources.GetDataFromTimeline(_objects, _collectionResolver, clipScheduler->SchedulerTimeline));
|
var last = _state.SetAnimationData(newData);
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.ScheduleClipUpdate);
|
||||||
Task.Result.Original(clipScheduler);
|
Task.Result.Original(clipScheduler);
|
||||||
_state.RestoreAnimationData(last);
|
_state.RestoreAnimationData(last);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.GameData;
|
using Penumbra.CrashHandler.Buffers;
|
||||||
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
|
||||||
|
|
||||||
/// <summary> Seems to load character actions when zoning or changing class, maybe. </summary>
|
/// <summary> Seems to load character actions when zoning or changing class, maybe. </summary>
|
||||||
public sealed unsafe class SomeActionLoad : FastHook<SomeActionLoad.Delegate>
|
public sealed unsafe class SomeActionLoad : FastHook<SomeActionLoad.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public SomeActionLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
public SomeActionLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("Some Action Load", Sigs.LoadSomeAction, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Some Action Load", Sigs.LoadSomeAction, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,8 +28,10 @@ public sealed unsafe class SomeActionLoad : FastHook<SomeActionLoad.Delegate>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private void Detour(ActionTimelineManager* timelineManager)
|
private void Detour(ActionTimelineManager* timelineManager)
|
||||||
{
|
{
|
||||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*)timelineManager->Parent, true));
|
var newData = _collectionResolver.IdentifyCollection((GameObject*)timelineManager->Parent, true);
|
||||||
|
var last = _state.SetAnimationData(newData);
|
||||||
Penumbra.Log.Excessive($"[Some Action Load] Invoked on 0x{(nint)timelineManager:X}.");
|
Penumbra.Log.Excessive($"[Some Action Load] Invoked on 0x{(nint)timelineManager:X}.");
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.ActionLoad);
|
||||||
Task.Result.Original(timelineManager);
|
Task.Result.Original(timelineManager);
|
||||||
_state.RestoreAnimationData(last);
|
_state.RestoreAnimationData(last);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,28 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Animation;
|
namespace Penumbra.Interop.Hooks.Animation;
|
||||||
|
|
||||||
/// <summary> Unknown what exactly this is, but it seems to load a bunch of paps. </summary>
|
/// <summary> Unknown what exactly this is, but it seems to load a bunch of paps. </summary>
|
||||||
public sealed unsafe class SomePapLoad : FastHook<SomePapLoad.Delegate>
|
public sealed unsafe class SomePapLoad : FastHook<SomePapLoad.Delegate>
|
||||||
{
|
{
|
||||||
private readonly GameState _state;
|
private readonly GameState _state;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
public SomePapLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects)
|
public SomePapLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects,
|
||||||
|
CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
|
_crashHandler = crashHandler;
|
||||||
Task = hooks.CreateHook<Delegate>("Some PAP Load", Sigs.LoadSomePap, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Some PAP Load", Sigs.LoadSomePap, Detour, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,8 +38,9 @@ public sealed unsafe class SomePapLoad : FastHook<SomePapLoad.Delegate>
|
||||||
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
|
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
|
||||||
if (actorIdx >= 0 && actorIdx < _objects.Length)
|
if (actorIdx >= 0 && actorIdx < _objects.Length)
|
||||||
{
|
{
|
||||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*)_objects.GetObjectAddress(actorIdx),
|
var newData = _collectionResolver.IdentifyCollection((GameObject*)_objects.GetObjectAddress(actorIdx), true);
|
||||||
true));
|
var last = _state.SetAnimationData(newData);
|
||||||
|
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.PapLoad);
|
||||||
Task.Result.Original(a1, a2, a3, a4);
|
Task.Result.Original(a1, a2, a3, a4);
|
||||||
_state.RestoreAnimationData(last);
|
_state.RestoreAnimationData(last);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0-windows</TargetFramework>
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
|
|
@ -69,8 +69,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.2" />
|
|
||||||
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
<PackageReference Include="SharpGLTF.Core" Version="1.0.0-alpha0030" />
|
<PackageReference Include="SharpGLTF.Core" Version="1.0.0-alpha0030" />
|
||||||
|
|
@ -79,8 +78,10 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
|
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
|
||||||
|
<ProjectReference Include="..\Penumbra.CrashHandler\Penumbra.CrashHandler.csproj" />
|
||||||
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
||||||
|
<ProjectReference Include="..\Penumbra.String\Penumbra.String.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -103,4 +104,4 @@
|
||||||
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
|
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
296
Penumbra/Services/CrashHandlerService.cs
Normal file
296
Penumbra/Services/CrashHandlerService.cs
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Communication;
|
||||||
|
using Penumbra.CrashHandler;
|
||||||
|
using Penumbra.CrashHandler.Buffers;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.Interop.ResourceLoading;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
using FileMode = System.IO.FileMode;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public sealed class CrashHandlerService : IDisposable, IService
|
||||||
|
{
|
||||||
|
private readonly FilenameService _files;
|
||||||
|
private readonly CommunicatorService _communicator;
|
||||||
|
private readonly ActorManager _actors;
|
||||||
|
private readonly ResourceLoader _resourceLoader;
|
||||||
|
private readonly Configuration _config;
|
||||||
|
|
||||||
|
public CrashHandlerService(FilenameService files, CommunicatorService communicator, ActorManager actors, ResourceLoader resourceLoader,
|
||||||
|
Configuration config)
|
||||||
|
{
|
||||||
|
_files = files;
|
||||||
|
_communicator = communicator;
|
||||||
|
_actors = actors;
|
||||||
|
_resourceLoader = resourceLoader;
|
||||||
|
_config = config;
|
||||||
|
|
||||||
|
if (!_config.UseCrashHandler)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OpenEventWriter();
|
||||||
|
LaunchCrashHandler();
|
||||||
|
if (_eventWriter != null)
|
||||||
|
Subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
CloseEventWriter();
|
||||||
|
_eventWriter?.Dispose();
|
||||||
|
if (_child != null)
|
||||||
|
{
|
||||||
|
_child.Kill();
|
||||||
|
Penumbra.Log.Debug($"Killed crash handler child process {_child.Id}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Process? _child;
|
||||||
|
private GameEventLogWriter? _eventWriter;
|
||||||
|
|
||||||
|
public string CopiedExe = string.Empty;
|
||||||
|
|
||||||
|
public string OriginalExe
|
||||||
|
=> _files.CrashHandlerExe;
|
||||||
|
|
||||||
|
public string LogPath
|
||||||
|
=> _files.LogFileName;
|
||||||
|
|
||||||
|
public int ChildProcessId
|
||||||
|
=> _child?.Id ?? -1;
|
||||||
|
|
||||||
|
public int ProcessId
|
||||||
|
=> Environment.ProcessId;
|
||||||
|
|
||||||
|
public bool IsRunning
|
||||||
|
=> _eventWriter != null && _child is { HasExited: false };
|
||||||
|
|
||||||
|
public int ChildExitCode
|
||||||
|
=> IsRunning ? 0 : _child?.ExitCode ?? 0;
|
||||||
|
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
if (_config.UseCrashHandler)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_config.UseCrashHandler = true;
|
||||||
|
_config.Save();
|
||||||
|
OpenEventWriter();
|
||||||
|
LaunchCrashHandler();
|
||||||
|
if (_eventWriter != null)
|
||||||
|
Subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
if (!_config.UseCrashHandler)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_config.UseCrashHandler = false;
|
||||||
|
_config.Save();
|
||||||
|
CloseEventWriter();
|
||||||
|
CloseCrashHandler();
|
||||||
|
Unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject? Load(string fileName)
|
||||||
|
{
|
||||||
|
if (!File.Exists(fileName))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = File.ReadAllText(fileName);
|
||||||
|
return JsonNode.Parse(data) as JsonObject;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not parse crash dump at {fileName}:\n{ex}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseCrashHandler()
|
||||||
|
{
|
||||||
|
if (_child == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_child.HasExited)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_child.Kill();
|
||||||
|
Penumbra.Log.Debug($"Closed Crash Handler at {CopiedExe}.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_child = null;
|
||||||
|
Penumbra.Log.Debug($"Closed not close Crash Handler at {CopiedExe}:\n{ex}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LaunchCrashHandler()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CloseCrashHandler();
|
||||||
|
CopiedExe = CopyExecutables();
|
||||||
|
var info = new ProcessStartInfo()
|
||||||
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
FileName = CopiedExe,
|
||||||
|
};
|
||||||
|
info.ArgumentList.Add(_files.LogFileName);
|
||||||
|
info.ArgumentList.Add(Environment.ProcessId.ToString());
|
||||||
|
_child = Process.Start(info);
|
||||||
|
if (_child == null)
|
||||||
|
throw new Exception("Child Process could not be created.");
|
||||||
|
|
||||||
|
Penumbra.Log.Information($"Opened Crash Handler at {CopiedExe}, PID {_child.Id}.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not launch crash handler process:\n{ex}");
|
||||||
|
CloseCrashHandler();
|
||||||
|
_child = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject? Dump()
|
||||||
|
{
|
||||||
|
if (_eventWriter == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var reader = new GameEventLogReader();
|
||||||
|
JsonObject jObj;
|
||||||
|
lock (_eventWriter)
|
||||||
|
{
|
||||||
|
jObj = reader.Dump("Manual Dump", Environment.ProcessId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logFile = _files.LogFileName;
|
||||||
|
using var s = File.Open(logFile, FileMode.Create);
|
||||||
|
using var jw = new Utf8JsonWriter(s, new JsonWriterOptions() { Indented = true });
|
||||||
|
jObj.WriteTo(jw);
|
||||||
|
Penumbra.Log.Information($"Dumped crash handler memory to {logFile}.");
|
||||||
|
return jObj;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Error dumping crash handler memory to file:\n{ex}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CopyExecutables()
|
||||||
|
{
|
||||||
|
var parent = Path.GetDirectoryName(_files.CrashHandlerExe)!;
|
||||||
|
var folder = Path.Combine(parent, "temp");
|
||||||
|
Directory.CreateDirectory(folder);
|
||||||
|
foreach (var file in Directory.EnumerateFiles(parent, "Penumbra.CrashHandler.*"))
|
||||||
|
File.Copy(file, Path.Combine(folder, Path.GetFileName(file)), true);
|
||||||
|
return Path.Combine(folder, Path.GetFileName(_files.CrashHandlerExe));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogAnimation(nint character, ModCollection collection, AnimationInvocationType type)
|
||||||
|
{
|
||||||
|
if (_eventWriter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = GetActorName(character);
|
||||||
|
lock (_eventWriter)
|
||||||
|
{
|
||||||
|
_eventWriter?.AnimationFuncInvoked.WriteLine(character, name.Span, collection.Name, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCreatingCharacterBase(nint address, string collection, nint _1, nint _2, nint _3)
|
||||||
|
{
|
||||||
|
if (_eventWriter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = GetActorName(address);
|
||||||
|
|
||||||
|
lock (_eventWriter)
|
||||||
|
{
|
||||||
|
_eventWriter?.CharacterBase.WriteLine(address, name.Span, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe ByteString GetActorName(nint address)
|
||||||
|
{
|
||||||
|
var obj = (GameObject*)address;
|
||||||
|
if (obj == null)
|
||||||
|
return ByteString.FromSpanUnsafe("Unknown"u8, true, false, true);
|
||||||
|
|
||||||
|
var id = _actors.FromObject(obj, out _, false, true, false);
|
||||||
|
return id.IsValid ? ByteString.FromStringUnsafe(id.Incognito(null), false) :
|
||||||
|
obj->Name[0] != 0 ? new ByteString(obj->Name) : ByteString.FromStringUnsafe($"Actor #{obj->ObjectIndex}", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolveData)
|
||||||
|
{
|
||||||
|
if (manipulatedPath == null || _eventWriter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dashIdx = manipulatedPath.Value.InternalName[0] == (byte)'|' ? manipulatedPath.Value.InternalName.IndexOf((byte)'|', 1) : -1;
|
||||||
|
if (dashIdx >= 0 && !Utf8GamePath.IsRooted(manipulatedPath.Value.InternalName.Substring(dashIdx + 1)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = GetActorName(resolveData.AssociatedGameObject);
|
||||||
|
lock (_eventWriter)
|
||||||
|
{
|
||||||
|
_eventWriter!.FileLoaded.WriteLine(resolveData.AssociatedGameObject, name.Span, resolveData.ModCollection.Name,
|
||||||
|
manipulatedPath.Value.InternalName.Span, originalPath.Path.Span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseEventWriter()
|
||||||
|
{
|
||||||
|
if (_eventWriter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_eventWriter.Dispose();
|
||||||
|
_eventWriter = null;
|
||||||
|
Penumbra.Log.Debug("Closed Event Writer for crash handler.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenEventWriter()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CloseEventWriter();
|
||||||
|
_eventWriter = new GameEventLogWriter();
|
||||||
|
Penumbra.Log.Debug("Opened new Event Writer for crash handler.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not open Event Writer:\n{ex}");
|
||||||
|
CloseEventWriter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void Subscribe()
|
||||||
|
{
|
||||||
|
_communicator.CreatingCharacterBase.Subscribe(OnCreatingCharacterBase, CreatingCharacterBase.Priority.CrashHandler);
|
||||||
|
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void Unsubscribe()
|
||||||
|
{
|
||||||
|
_communicator.CreatingCharacterBase.Unsubscribe(OnCreatingCharacterBase);
|
||||||
|
_resourceLoader.ResourceLoaded -= OnResourceLoaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,12 @@ public class FilenameService(DalamudPluginInterface pi) : IService
|
||||||
public readonly string FilesystemFile = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json");
|
public readonly string FilesystemFile = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json");
|
||||||
public readonly string ActiveCollectionsFile = Path.Combine(pi.ConfigDirectory.FullName, "active_collections.json");
|
public readonly string ActiveCollectionsFile = Path.Combine(pi.ConfigDirectory.FullName, "active_collections.json");
|
||||||
|
|
||||||
|
public readonly string CrashHandlerExe =
|
||||||
|
Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Penumbra.CrashHandler.exe");
|
||||||
|
|
||||||
|
public readonly string LogFileName =
|
||||||
|
Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(pi.ConfigDirectory.FullName)!)!, "Penumbra.log");
|
||||||
|
|
||||||
/// <summary> Obtain the path of a collection file given its name.</summary>
|
/// <summary> Obtain the path of a collection file given its name.</summary>
|
||||||
public string CollectionFile(ModCollection collection)
|
public string CollectionFile(ModCollection collection)
|
||||||
=> CollectionFile(collection.Name);
|
=> CollectionFile(collection.Name);
|
||||||
|
|
|
||||||
|
|
@ -12,22 +12,13 @@ using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
public class ChangedItemsTab : ITab
|
public class ChangedItemsTab(
|
||||||
|
CollectionManager collectionManager,
|
||||||
|
CollectionSelectHeader collectionHeader,
|
||||||
|
ChangedItemDrawer drawer,
|
||||||
|
CommunicatorService communicator)
|
||||||
|
: ITab
|
||||||
{
|
{
|
||||||
private readonly CollectionManager _collectionManager;
|
|
||||||
private readonly ChangedItemDrawer _drawer;
|
|
||||||
private readonly CollectionSelectHeader _collectionHeader;
|
|
||||||
private readonly CommunicatorService _communicator;
|
|
||||||
|
|
||||||
public ChangedItemsTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader, ChangedItemDrawer drawer,
|
|
||||||
CommunicatorService communicator)
|
|
||||||
{
|
|
||||||
_collectionManager = collectionManager;
|
|
||||||
_collectionHeader = collectionHeader;
|
|
||||||
_drawer = drawer;
|
|
||||||
_communicator = communicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
=> "Changed Items"u8;
|
=> "Changed Items"u8;
|
||||||
|
|
||||||
|
|
@ -36,8 +27,8 @@ public class ChangedItemsTab : ITab
|
||||||
|
|
||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
{
|
{
|
||||||
_collectionHeader.Draw(true);
|
collectionHeader.Draw(true);
|
||||||
_drawer.DrawTypeFilter();
|
drawer.DrawTypeFilter();
|
||||||
var varWidth = DrawFilters();
|
var varWidth = DrawFilters();
|
||||||
using var child = ImRaii.Child("##changedItemsChild", -Vector2.One);
|
using var child = ImRaii.Child("##changedItemsChild", -Vector2.One);
|
||||||
if (!child)
|
if (!child)
|
||||||
|
|
@ -54,7 +45,7 @@ public class ChangedItemsTab : ITab
|
||||||
ImGui.TableSetupColumn("mods", flags, varWidth - 130 * UiHelpers.Scale);
|
ImGui.TableSetupColumn("mods", flags, varWidth - 130 * UiHelpers.Scale);
|
||||||
ImGui.TableSetupColumn("id", flags, 130 * UiHelpers.Scale);
|
ImGui.TableSetupColumn("id", flags, 130 * UiHelpers.Scale);
|
||||||
|
|
||||||
var items = _collectionManager.Active.Current.ChangedItems;
|
var items = collectionManager.Active.Current.ChangedItems;
|
||||||
var rest = ImGuiClip.FilteredClippedDraw(items, skips, FilterChangedItem, DrawChangedItemColumn);
|
var rest = ImGuiClip.FilteredClippedDraw(items, skips, FilterChangedItem, DrawChangedItemColumn);
|
||||||
ImGuiClip.DrawEndDummy(rest, height);
|
ImGuiClip.DrawEndDummy(rest, height);
|
||||||
}
|
}
|
||||||
|
|
@ -75,21 +66,21 @@ public class ChangedItemsTab : ITab
|
||||||
|
|
||||||
/// <summary> Apply the current filters. </summary>
|
/// <summary> Apply the current filters. </summary>
|
||||||
private bool FilterChangedItem(KeyValuePair<string, (SingleArray<IMod>, object?)> item)
|
private bool FilterChangedItem(KeyValuePair<string, (SingleArray<IMod>, object?)> item)
|
||||||
=> _drawer.FilterChangedItem(item.Key, item.Value.Item2, _changedItemFilter)
|
=> drawer.FilterChangedItem(item.Key, item.Value.Item2, _changedItemFilter)
|
||||||
&& (_changedItemModFilter.IsEmpty || item.Value.Item1.Any(m => m.Name.Contains(_changedItemModFilter)));
|
&& (_changedItemModFilter.IsEmpty || item.Value.Item1.Any(m => m.Name.Contains(_changedItemModFilter)));
|
||||||
|
|
||||||
/// <summary> Draw a full column for a changed item. </summary>
|
/// <summary> Draw a full column for a changed item. </summary>
|
||||||
private void DrawChangedItemColumn(KeyValuePair<string, (SingleArray<IMod>, object?)> item)
|
private void DrawChangedItemColumn(KeyValuePair<string, (SingleArray<IMod>, object?)> item)
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_drawer.DrawCategoryIcon(item.Key, item.Value.Item2);
|
drawer.DrawCategoryIcon(item.Key, item.Value.Item2);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
_drawer.DrawChangedItem(item.Key, item.Value.Item2);
|
drawer.DrawChangedItem(item.Key, item.Value.Item2);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
DrawModColumn(item.Value.Item1);
|
DrawModColumn(item.Value.Item1);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_drawer.DrawModelData(item.Value.Item2);
|
drawer.DrawModelData(item.Value.Item2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawModColumn(SingleArray<IMod> mods)
|
private void DrawModColumn(SingleArray<IMod> mods)
|
||||||
|
|
@ -102,7 +93,7 @@ public class ChangedItemsTab : ITab
|
||||||
if (ImGui.Selectable(first.Name, false, ImGuiSelectableFlags.None, new Vector2(0, ImGui.GetFrameHeight()))
|
if (ImGui.Selectable(first.Name, false, ImGuiSelectableFlags.None, new Vector2(0, ImGui.GetFrameHeight()))
|
||||||
&& ImGui.GetIO().KeyCtrl
|
&& ImGui.GetIO().KeyCtrl
|
||||||
&& first is Mod mod)
|
&& first is Mod mod)
|
||||||
_communicator.SelectTab.Invoke(TabType.Mods, mod);
|
communicator.SelectTab.Invoke(TabType.Mods, mod);
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ public class ConfigTabBar : IDisposable
|
||||||
Watcher = watcher;
|
Watcher = watcher;
|
||||||
OnScreen = onScreen;
|
OnScreen = onScreen;
|
||||||
Messages = messages;
|
Messages = messages;
|
||||||
Tabs = new ITab[]
|
Tabs =
|
||||||
{
|
[
|
||||||
Settings,
|
Settings,
|
||||||
Collections,
|
Collections,
|
||||||
Mods,
|
Mods,
|
||||||
|
|
@ -56,7 +56,7 @@ public class ConfigTabBar : IDisposable
|
||||||
Resource,
|
Resource,
|
||||||
Watcher,
|
Watcher,
|
||||||
Messages,
|
Messages,
|
||||||
};
|
];
|
||||||
_communicator.SelectTab.Subscribe(OnSelectTab, Communication.SelectTab.Priority.ConfigTabBar);
|
_communicator.SelectTab.Subscribe(OnSelectTab, Communication.SelectTab.Priority.ConfigTabBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
104
Penumbra/UI/Tabs/Debug/CrashDataExtensions.cs
Normal file
104
Penumbra/UI/Tabs/Debug/CrashDataExtensions.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.CrashHandler;
|
||||||
|
|
||||||
|
namespace Penumbra.UI.Tabs.Debug;
|
||||||
|
|
||||||
|
public static class CrashDataExtensions
|
||||||
|
{
|
||||||
|
public static void DrawMeta(this CrashData data)
|
||||||
|
{
|
||||||
|
using (ImRaii.Group())
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(nameof(data.Mode));
|
||||||
|
ImGui.TextUnformatted(nameof(data.CrashTime));
|
||||||
|
ImGui.TextUnformatted(nameof(data.ExitCode));
|
||||||
|
ImGui.TextUnformatted(nameof(data.ProcessId));
|
||||||
|
ImGui.TextUnformatted(nameof(data.TotalModdedFilesLoaded));
|
||||||
|
ImGui.TextUnformatted(nameof(data.TotalCharactersLoaded));
|
||||||
|
ImGui.TextUnformatted(nameof(data.TotalVFXFuncsInvoked));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImRaii.Group())
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(data.Mode);
|
||||||
|
ImGui.TextUnformatted(data.CrashTime.ToString());
|
||||||
|
ImGui.TextUnformatted(data.ExitCode.ToString());
|
||||||
|
ImGui.TextUnformatted(data.ProcessId.ToString());
|
||||||
|
ImGui.TextUnformatted(data.TotalModdedFilesLoaded.ToString());
|
||||||
|
ImGui.TextUnformatted(data.TotalCharactersLoaded.ToString());
|
||||||
|
ImGui.TextUnformatted(data.TotalVFXFuncsInvoked.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawCharacters(this CrashData data)
|
||||||
|
{
|
||||||
|
using var tree = ImRaii.TreeNode("Last Characters");
|
||||||
|
if (!tree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var table = ImRaii.Table("##characterTable", 6,
|
||||||
|
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiClip.ClippedDraw(data.LastCharactersLoaded, character =>
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn(character.Age.ToString(CultureInfo.InvariantCulture));
|
||||||
|
ImGuiUtil.DrawTableColumn(character.ThreadId.ToString());
|
||||||
|
ImGuiUtil.DrawTableColumn(character.CharacterName);
|
||||||
|
ImGuiUtil.DrawTableColumn(character.CollectionName);
|
||||||
|
ImGuiUtil.DrawTableColumn(character.CharacterAddress);
|
||||||
|
ImGuiUtil.DrawTableColumn(character.Timestamp.ToString());
|
||||||
|
}, ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawFiles(this CrashData data)
|
||||||
|
{
|
||||||
|
using var tree = ImRaii.TreeNode("Last Files");
|
||||||
|
if (!tree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var table = ImRaii.Table("##filesTable", 8,
|
||||||
|
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiClip.ClippedDraw(data.LastModdedFilesLoaded, file =>
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn(file.Age.ToString(CultureInfo.InvariantCulture));
|
||||||
|
ImGuiUtil.DrawTableColumn(file.ThreadId.ToString());
|
||||||
|
ImGuiUtil.DrawTableColumn(file.ActualFileName);
|
||||||
|
ImGuiUtil.DrawTableColumn(file.RequestedFileName);
|
||||||
|
ImGuiUtil.DrawTableColumn(file.CharacterName);
|
||||||
|
ImGuiUtil.DrawTableColumn(file.CollectionName);
|
||||||
|
ImGuiUtil.DrawTableColumn(file.CharacterAddress);
|
||||||
|
ImGuiUtil.DrawTableColumn(file.Timestamp.ToString());
|
||||||
|
}, ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawVfxInvocations(this CrashData data)
|
||||||
|
{
|
||||||
|
using var tree = ImRaii.TreeNode("Last VFX Invocations");
|
||||||
|
if (!tree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var table = ImRaii.Table("##vfxTable", 7,
|
||||||
|
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGuiClip.ClippedDraw(data.LastVfxFuncsInvoked, vfx =>
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn(vfx.Age.ToString(CultureInfo.InvariantCulture));
|
||||||
|
ImGuiUtil.DrawTableColumn(vfx.ThreadId.ToString());
|
||||||
|
ImGuiUtil.DrawTableColumn(vfx.InvocationType);
|
||||||
|
ImGuiUtil.DrawTableColumn(vfx.CharacterName);
|
||||||
|
ImGuiUtil.DrawTableColumn(vfx.CollectionName);
|
||||||
|
ImGuiUtil.DrawTableColumn(vfx.CharacterAddress);
|
||||||
|
ImGuiUtil.DrawTableColumn(vfx.Timestamp.ToString());
|
||||||
|
}, ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Penumbra/UI/Tabs/Debug/CrashHandlerPanel.cs
Normal file
136
Penumbra/UI/Tabs/Debug/CrashHandlerPanel.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using Dalamud.Interface.DragDrop;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.CrashHandler;
|
||||||
|
using Penumbra.Services;
|
||||||
|
|
||||||
|
namespace Penumbra.UI.Tabs.Debug;
|
||||||
|
|
||||||
|
public class CrashHandlerPanel(CrashHandlerService _service, Configuration _config, IDragDropManager _dragDrop) : IService
|
||||||
|
{
|
||||||
|
private CrashData? _lastDump;
|
||||||
|
private string _lastLoadedFile = string.Empty;
|
||||||
|
private CrashData? _lastLoad;
|
||||||
|
private Exception? _lastLoadException;
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
DrawDropSource();
|
||||||
|
DrawData();
|
||||||
|
DrawDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawData()
|
||||||
|
{
|
||||||
|
using var _ = ImRaii.Group();
|
||||||
|
using var header = ImRaii.CollapsingHeader("Crash Handler");
|
||||||
|
if (!header)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawButtons();
|
||||||
|
DrawMainData();
|
||||||
|
DrawObject("Last Manual Dump", _lastDump, null);
|
||||||
|
DrawObject(_lastLoadedFile.Length > 0 ? $"Loaded File ({_lastLoadedFile})###Loaded File" : "Loaded File", _lastLoad,
|
||||||
|
_lastLoadException);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMainData()
|
||||||
|
{
|
||||||
|
using var table = ImRaii.Table("##CrashHandlerTable", 2, ImGuiTableFlags.SizingFixedFit);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PrintValue("Enabled", _config.UseCrashHandler);
|
||||||
|
PrintValue("Copied Executable Path", _service.CopiedExe);
|
||||||
|
PrintValue("Original Executable Path", _service.OriginalExe);
|
||||||
|
PrintValue("Log File Path", _service.LogPath);
|
||||||
|
PrintValue("XIV Process ID", _service.ProcessId.ToString());
|
||||||
|
PrintValue("Crash Handler Running", _service.IsRunning.ToString());
|
||||||
|
PrintValue("Crash Handler Process ID", _service.ChildProcessId.ToString());
|
||||||
|
PrintValue("Crash Handler Exit Code", _service.ChildExitCode.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawButtons()
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Dump Crash Handler Memory"))
|
||||||
|
_lastDump = _service.Dump()?.Deserialize<CrashData>();
|
||||||
|
|
||||||
|
if (ImGui.Button("Enable"))
|
||||||
|
_service.Enable();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Disable"))
|
||||||
|
_service.Disable();
|
||||||
|
|
||||||
|
if (ImGui.Button("Shutdown Crash Handler"))
|
||||||
|
_service.CloseCrashHandler();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Relaunch Crash Handler"))
|
||||||
|
_service.LaunchCrashHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDropSource()
|
||||||
|
{
|
||||||
|
_dragDrop.CreateImGuiSource("LogDragDrop", m => m.Files.Any(f => f.EndsWith("Penumbra.log")), m =>
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("Dragging Penumbra.log for import.");
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDropTarget()
|
||||||
|
{
|
||||||
|
if (!_dragDrop.CreateImGuiTarget("LogDragDrop", out var files, out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var file = files.FirstOrDefault(f => f.EndsWith("Penumbra.log"));
|
||||||
|
if (file == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastLoadedFile = file;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jObj = _service.Load(file);
|
||||||
|
_lastLoad = jObj?.Deserialize<CrashData>();
|
||||||
|
_lastLoadException = null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_lastLoad = null;
|
||||||
|
_lastLoadException = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawObject(string name, CrashData? data, Exception? ex)
|
||||||
|
{
|
||||||
|
using var tree = ImRaii.TreeNode(name);
|
||||||
|
if (!tree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ex != null)
|
||||||
|
{
|
||||||
|
ImGuiUtil.TextWrapped(ex.ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("Nothing loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.DrawMeta();
|
||||||
|
data.DrawFiles();
|
||||||
|
data.DrawCharacters();
|
||||||
|
data.DrawVfxInvocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintValue<T>(string label, in T data)
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn(label);
|
||||||
|
ImGuiUtil.DrawTableColumn(data?.ToString() ?? "NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,7 +53,7 @@ public class Diagnostics(IServiceProvider provider)
|
||||||
foreach (var type in typeof(ActorManager).Assembly.GetTypes()
|
foreach (var type in typeof(ActorManager).Assembly.GetTypes()
|
||||||
.Where(t => t is { IsAbstract: false, IsInterface: false } && t.IsAssignableTo(typeof(IAsyncDataContainer))))
|
.Where(t => t is { IsAbstract: false, IsInterface: false } && t.IsAssignableTo(typeof(IAsyncDataContainer))))
|
||||||
{
|
{
|
||||||
var container = (IAsyncDataContainer) provider.GetRequiredService(type);
|
var container = (IAsyncDataContainer)provider.GetRequiredService(type);
|
||||||
ImGuiUtil.DrawTableColumn(container.Name);
|
ImGuiUtil.DrawTableColumn(container.Name);
|
||||||
ImGuiUtil.DrawTableColumn(container.Time.ToString());
|
ImGuiUtil.DrawTableColumn(container.Time.ToString());
|
||||||
ImGuiUtil.DrawTableColumn(Functions.HumanReadableSize(container.Memory));
|
ImGuiUtil.DrawTableColumn(Functions.HumanReadableSize(container.Memory));
|
||||||
|
|
@ -88,18 +88,22 @@ public class DebugTab : Window, ITab
|
||||||
private readonly TextureManager _textureManager;
|
private readonly TextureManager _textureManager;
|
||||||
private readonly ShaderReplacementFixer _shaderReplacementFixer;
|
private readonly ShaderReplacementFixer _shaderReplacementFixer;
|
||||||
private readonly RedrawService _redraws;
|
private readonly RedrawService _redraws;
|
||||||
private readonly DictEmote _emotes;
|
private readonly DictEmote _emotes;
|
||||||
private readonly Diagnostics _diagnostics;
|
private readonly Diagnostics _diagnostics;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly IpcTester _ipcTester;
|
private readonly IpcTester _ipcTester;
|
||||||
|
private readonly CrashHandlerPanel _crashHandlerPanel;
|
||||||
|
|
||||||
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, IObjectTable objects, IClientState clientState,
|
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, IObjectTable objects,
|
||||||
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains, CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
IClientState clientState,
|
||||||
|
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains,
|
||||||
|
CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
||||||
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
||||||
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
||||||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
||||||
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes, Diagnostics diagnostics, IpcTester ipcTester)
|
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes,
|
||||||
|
Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel)
|
||||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
||||||
{
|
{
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
|
|
@ -134,7 +138,8 @@ public class DebugTab : Window, ITab
|
||||||
_redraws = redraws;
|
_redraws = redraws;
|
||||||
_emotes = emotes;
|
_emotes = emotes;
|
||||||
_diagnostics = diagnostics;
|
_diagnostics = diagnostics;
|
||||||
_ipcTester = ipcTester;
|
_ipcTester = ipcTester;
|
||||||
|
_crashHandlerPanel = crashHandlerPanel;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +163,9 @@ public class DebugTab : Window, ITab
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DrawDebugTabGeneral();
|
DrawDebugTabGeneral();
|
||||||
|
ImGui.NewLine();
|
||||||
|
_crashHandlerPanel.Draw();
|
||||||
|
ImGui.NewLine();
|
||||||
_diagnostics.DrawDiagnostics();
|
_diagnostics.DrawDiagnostics();
|
||||||
DrawPerformanceTab();
|
DrawPerformanceTab();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
|
|
@ -257,6 +265,7 @@ public class DebugTab : Window, ITab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var issues = _modManager.WithIndex().Count(p => p.Index != p.Value.Index);
|
var issues = _modManager.WithIndex().Count(p => p.Index != p.Value.Index);
|
||||||
using (var tree = TreeNode($"Mods ({issues} Issues)###Mods"))
|
using (var tree = TreeNode($"Mods ({issues} Issues)###Mods"))
|
||||||
{
|
{
|
||||||
|
|
@ -394,7 +403,7 @@ public class DebugTab : Window, ITab
|
||||||
private void DrawPerformanceTab()
|
private void DrawPerformanceTab()
|
||||||
{
|
{
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if (ImGui.CollapsingHeader("Performance"))
|
if (!ImGui.CollapsingHeader("Performance"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using (var start = TreeNode("Startup Performance", ImGuiTreeNodeFlags.DefaultOpen))
|
using (var start = TreeNode("Startup Performance", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
|
|
|
||||||
|
|
@ -8,31 +8,22 @@ using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Cache;
|
using Penumbra.Collections.Cache;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
|
||||||
using Penumbra.Mods.Editor;
|
using Penumbra.Mods.Editor;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
public class EffectiveTab : ITab
|
public class EffectiveTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader)
|
||||||
|
: ITab
|
||||||
{
|
{
|
||||||
private readonly CollectionManager _collectionManager;
|
|
||||||
private readonly CollectionSelectHeader _collectionHeader;
|
|
||||||
|
|
||||||
public EffectiveTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader)
|
|
||||||
{
|
|
||||||
_collectionManager = collectionManager;
|
|
||||||
_collectionHeader = collectionHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
=> "Effective Changes"u8;
|
=> "Effective Changes"u8;
|
||||||
|
|
||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
{
|
{
|
||||||
SetupEffectiveSizes();
|
SetupEffectiveSizes();
|
||||||
_collectionHeader.Draw(true);
|
collectionHeader.Draw(true);
|
||||||
DrawFilters();
|
DrawFilters();
|
||||||
using var child = ImRaii.Child("##EffectiveChangesTab", -Vector2.One, false);
|
using var child = ImRaii.Child("##EffectiveChangesTab", -Vector2.One, false);
|
||||||
if (!child)
|
if (!child)
|
||||||
|
|
@ -48,7 +39,7 @@ public class EffectiveTab : ITab
|
||||||
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, _effectiveArrowLength);
|
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, _effectiveArrowLength);
|
||||||
ImGui.TableSetupColumn("##file", ImGuiTableColumnFlags.WidthFixed, _effectiveRightTextLength);
|
ImGui.TableSetupColumn("##file", ImGuiTableColumnFlags.WidthFixed, _effectiveRightTextLength);
|
||||||
|
|
||||||
DrawEffectiveRows(_collectionManager.Active.Current, skips, height,
|
DrawEffectiveRows(collectionManager.Active.Current, skips, height,
|
||||||
_effectiveFilePathFilter.Length > 0 || _effectiveGamePathFilter.Length > 0);
|
_effectiveFilePathFilter.Length > 0 || _effectiveGamePathFilter.Length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,68 +17,53 @@ using Penumbra.Collections.Manager;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
public class ModsTab : ITab
|
public class ModsTab(
|
||||||
|
ModManager modManager,
|
||||||
|
CollectionManager collectionManager,
|
||||||
|
ModFileSystemSelector selector,
|
||||||
|
ModPanel panel,
|
||||||
|
TutorialService tutorial,
|
||||||
|
RedrawService redrawService,
|
||||||
|
Configuration config,
|
||||||
|
IClientState clientState,
|
||||||
|
CollectionSelectHeader collectionHeader,
|
||||||
|
ITargetManager targets,
|
||||||
|
IObjectTable objectTable)
|
||||||
|
: ITab
|
||||||
{
|
{
|
||||||
private readonly ModFileSystemSelector _selector;
|
private readonly ActiveCollections _activeCollections = collectionManager.Active;
|
||||||
private readonly ModPanel _panel;
|
|
||||||
private readonly TutorialService _tutorial;
|
|
||||||
private readonly ModManager _modManager;
|
|
||||||
private readonly ActiveCollections _activeCollections;
|
|
||||||
private readonly RedrawService _redrawService;
|
|
||||||
private readonly Configuration _config;
|
|
||||||
private readonly IClientState _clientState;
|
|
||||||
private readonly CollectionSelectHeader _collectionHeader;
|
|
||||||
private readonly ITargetManager _targets;
|
|
||||||
private readonly IObjectTable _objectTable;
|
|
||||||
|
|
||||||
public ModsTab(ModManager modManager, CollectionManager collectionManager, ModFileSystemSelector selector, ModPanel panel,
|
|
||||||
TutorialService tutorial, RedrawService redrawService, Configuration config, IClientState clientState,
|
|
||||||
CollectionSelectHeader collectionHeader, ITargetManager targets, IObjectTable objectTable)
|
|
||||||
{
|
|
||||||
_modManager = modManager;
|
|
||||||
_activeCollections = collectionManager.Active;
|
|
||||||
_selector = selector;
|
|
||||||
_panel = panel;
|
|
||||||
_tutorial = tutorial;
|
|
||||||
_redrawService = redrawService;
|
|
||||||
_config = config;
|
|
||||||
_clientState = clientState;
|
|
||||||
_collectionHeader = collectionHeader;
|
|
||||||
_targets = targets;
|
|
||||||
_objectTable = objectTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsVisible
|
public bool IsVisible
|
||||||
=> _modManager.Valid;
|
=> modManager.Valid;
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
=> "Mods"u8;
|
=> "Mods"u8;
|
||||||
|
|
||||||
public void DrawHeader()
|
public void DrawHeader()
|
||||||
=> _tutorial.OpenTutorial(BasicTutorialSteps.Mods);
|
=> tutorial.OpenTutorial(BasicTutorialSteps.Mods);
|
||||||
|
|
||||||
public Mod SelectMod
|
public Mod SelectMod
|
||||||
{
|
{
|
||||||
set => _selector.SelectByValue(value);
|
set => selector.SelectByValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_selector.Draw(GetModSelectorSize(_config));
|
selector.Draw(GetModSelectorSize(config));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using var group = ImRaii.Group();
|
using var group = ImRaii.Group();
|
||||||
_collectionHeader.Draw(false);
|
collectionHeader.Draw(false);
|
||||||
|
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
using (var child = ImRaii.Child("##ModsTabMod", new Vector2(-1, _config.HideRedrawBar ? 0 : -ImGui.GetFrameHeight()),
|
using (var child = ImRaii.Child("##ModsTabMod", new Vector2(-1, config.HideRedrawBar ? 0 : -ImGui.GetFrameHeight()),
|
||||||
true, ImGuiWindowFlags.HorizontalScrollbar))
|
true, ImGuiWindowFlags.HorizontalScrollbar))
|
||||||
{
|
{
|
||||||
style.Pop();
|
style.Pop();
|
||||||
if (child)
|
if (child)
|
||||||
_panel.Draw();
|
panel.Draw();
|
||||||
|
|
||||||
style.Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
style.Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
|
@ -89,14 +74,14 @@ public class ModsTab : ITab
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error($"Exception thrown during ModPanel Render:\n{e}");
|
Penumbra.Log.Error($"Exception thrown during ModPanel Render:\n{e}");
|
||||||
Penumbra.Log.Error($"{_modManager.Count} Mods\n"
|
Penumbra.Log.Error($"{modManager.Count} Mods\n"
|
||||||
+ $"{_activeCollections.Current.AnonymizedName} Current Collection\n"
|
+ $"{_activeCollections.Current.AnonymizedName} Current Collection\n"
|
||||||
+ $"{_activeCollections.Current.Settings.Count} Settings\n"
|
+ $"{_activeCollections.Current.Settings.Count} Settings\n"
|
||||||
+ $"{_selector.SortMode.Name} Sort Mode\n"
|
+ $"{selector.SortMode.Name} Sort Mode\n"
|
||||||
+ $"{_selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n"
|
+ $"{selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n"
|
||||||
+ $"{_selector.Selected?.Name ?? "NULL"} Selected Mod\n"
|
+ $"{selector.Selected?.Name ?? "NULL"} Selected Mod\n"
|
||||||
+ $"{string.Join(", ", _activeCollections.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n"
|
+ $"{string.Join(", ", _activeCollections.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n"
|
||||||
+ $"{_selector.SelectedSettingCollection.AnonymizedName} Collection\n");
|
+ $"{selector.SelectedSettingCollection.AnonymizedName} Collection\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,9 +100,9 @@ public class ModsTab : ITab
|
||||||
|
|
||||||
private void DrawRedrawLine()
|
private void DrawRedrawLine()
|
||||||
{
|
{
|
||||||
if (_config.HideRedrawBar)
|
if (config.HideRedrawBar)
|
||||||
{
|
{
|
||||||
_tutorial.SkipTutorial(BasicTutorialSteps.Redrawing);
|
tutorial.SkipTutorial(BasicTutorialSteps.Redrawing);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,15 +120,15 @@ public class ModsTab : ITab
|
||||||
}
|
}
|
||||||
|
|
||||||
var hovered = ImGui.IsItemHovered();
|
var hovered = ImGui.IsItemHovered();
|
||||||
_tutorial.OpenTutorial(BasicTutorialSteps.Redrawing);
|
tutorial.OpenTutorial(BasicTutorialSteps.Redrawing);
|
||||||
if (hovered)
|
if (hovered)
|
||||||
ImGui.SetTooltip($"The supported modifiers for '/penumbra redraw' are:\n{TutorialService.SupportedRedrawModifiers}");
|
ImGui.SetTooltip($"The supported modifiers for '/penumbra redraw' are:\n{TutorialService.SupportedRedrawModifiers}");
|
||||||
|
|
||||||
using var id = ImRaii.PushId("Redraw");
|
using var id = ImRaii.PushId("Redraw");
|
||||||
using var disabled = ImRaii.Disabled(_clientState.LocalPlayer == null);
|
using var disabled = ImRaii.Disabled(clientState.LocalPlayer == null);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var buttonWidth = frameHeight with { X = ImGui.GetContentRegionAvail().X / 5 };
|
var buttonWidth = frameHeight with { X = ImGui.GetContentRegionAvail().X / 5 };
|
||||||
var tt = _objectTable.GetObjectAddress(0) == nint.Zero
|
var tt = objectTable.GetObjectAddress(0) == nint.Zero
|
||||||
? "\nCan only be used when you are logged in and your character is available."
|
? "\nCan only be used when you are logged in and your character is available."
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
DrawButton(buttonWidth, "All", string.Empty, tt);
|
DrawButton(buttonWidth, "All", string.Empty, tt);
|
||||||
|
|
@ -151,13 +136,13 @@ public class ModsTab : ITab
|
||||||
DrawButton(buttonWidth, "Self", "self", tt);
|
DrawButton(buttonWidth, "Self", "self", tt);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
tt = _targets.Target == null && _targets.GPoseTarget == null
|
tt = targets.Target == null && targets.GPoseTarget == null
|
||||||
? "\nCan only be used when you have a target."
|
? "\nCan only be used when you have a target."
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
DrawButton(buttonWidth, "Target", "target", tt);
|
DrawButton(buttonWidth, "Target", "target", tt);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
tt = _targets.FocusTarget == null
|
tt = targets.FocusTarget == null
|
||||||
? "\nCan only be used when you have a focus target."
|
? "\nCan only be used when you have a focus target."
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
DrawButton(buttonWidth, "Focus", "focus", tt);
|
DrawButton(buttonWidth, "Focus", "focus", tt);
|
||||||
|
|
@ -176,9 +161,9 @@ public class ModsTab : ITab
|
||||||
if (ImGui.Button(label, size))
|
if (ImGui.Button(label, size))
|
||||||
{
|
{
|
||||||
if (lower.Length > 0)
|
if (lower.Length > 0)
|
||||||
_redrawService.RedrawObject(lower, RedrawType.Redraw);
|
redrawService.RedrawObject(lower, RedrawType.Redraw);
|
||||||
else
|
else
|
||||||
_redrawService.RedrawAll(RedrawType.Redraw);
|
redrawService.RedrawAll(RedrawType.Redraw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ namespace Penumbra.UI.Tabs;
|
||||||
public class OnScreenTab : ITab
|
public class OnScreenTab : ITab
|
||||||
{
|
{
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private ResourceTreeViewer _viewer;
|
private readonly ResourceTreeViewer _viewer;
|
||||||
|
|
||||||
public OnScreenTab(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer)
|
public OnScreenTab(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,24 +12,14 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
public class ResourceTab : ITab
|
public class ResourceTab(Configuration config, ResourceManagerService resourceManager, ISigScanner sigScanner)
|
||||||
|
: ITab
|
||||||
{
|
{
|
||||||
private readonly Configuration _config;
|
|
||||||
private readonly ResourceManagerService _resourceManager;
|
|
||||||
private readonly ISigScanner _sigScanner;
|
|
||||||
|
|
||||||
public ResourceTab(Configuration config, ResourceManagerService resourceManager, ISigScanner sigScanner)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
_resourceManager = resourceManager;
|
|
||||||
_sigScanner = sigScanner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
=> "Resource Manager"u8;
|
=> "Resource Manager"u8;
|
||||||
|
|
||||||
public bool IsVisible
|
public bool IsVisible
|
||||||
=> _config.DebugMode;
|
=> config.DebugMode;
|
||||||
|
|
||||||
/// <summary> Draw a tab to iterate over the main resource maps and see what resources are currently loaded. </summary>
|
/// <summary> Draw a tab to iterate over the main resource maps and see what resources are currently loaded. </summary>
|
||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
|
|
@ -44,15 +34,15 @@ public class ResourceTab : ITab
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
_resourceManager.IterateGraphs(DrawCategoryContainer);
|
resourceManager.IterateGraphs(DrawCategoryContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted(
|
ImGui.TextUnformatted(
|
||||||
$"Static Address: 0x{(ulong)_resourceManager.ResourceManagerAddress:X} (+0x{(ulong)_resourceManager.ResourceManagerAddress - (ulong)_sigScanner.Module.BaseAddress:X})");
|
$"Static Address: 0x{(ulong)resourceManager.ResourceManagerAddress:X} (+0x{(ulong)resourceManager.ResourceManagerAddress - (ulong)sigScanner.Module.BaseAddress:X})");
|
||||||
ImGui.TextUnformatted($"Actual Address: 0x{(ulong)_resourceManager.ResourceManager:X}");
|
ImGui.TextUnformatted($"Actual Address: 0x{(ulong)resourceManager.ResourceManager:X}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +72,7 @@ public class ResourceTab : ITab
|
||||||
ImGui.TableSetupColumn("Refs", ImGuiTableColumnFlags.WidthFixed, _refsColumnWidth);
|
ImGui.TableSetupColumn("Refs", ImGuiTableColumnFlags.WidthFixed, _refsColumnWidth);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
_resourceManager.IterateResourceMap(map, (hash, r) =>
|
resourceManager.IterateResourceMap(map, (hash, r) =>
|
||||||
{
|
{
|
||||||
// Filter unwanted names.
|
// Filter unwanted names.
|
||||||
if (_resourceManagerFilter.Length != 0
|
if (_resourceManagerFilter.Length != 0
|
||||||
|
|
@ -125,7 +115,7 @@ public class ResourceTab : ITab
|
||||||
if (tree)
|
if (tree)
|
||||||
{
|
{
|
||||||
SetTableWidths();
|
SetTableWidths();
|
||||||
_resourceManager.IterateExtMap(map, (ext, m) => DrawResourceMap(category, ext, m));
|
resourceManager.IterateExtMap(map, (ext, m) => DrawResourceMap(category, ext, m));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,6 @@
|
||||||
"Unosquare.Swan.Lite": "3.0.0"
|
"Unosquare.Swan.Lite": "3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.CodeAnalysis.Common": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[4.8.0, )",
|
|
||||||
"resolved": "4.8.0",
|
|
||||||
"contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.CodeAnalysis.Analyzers": "3.3.4",
|
|
||||||
"System.Collections.Immutable": "7.0.0",
|
|
||||||
"System.Reflection.Metadata": "7.0.0",
|
|
||||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.DependencyInjection": {
|
"Microsoft.Extensions.DependencyInjection": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[7.0.0, )",
|
"requested": "[7.0.0, )",
|
||||||
|
|
@ -55,29 +43,15 @@
|
||||||
},
|
},
|
||||||
"SixLabors.ImageSharp": {
|
"SixLabors.ImageSharp": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[2.1.2, )",
|
"requested": "[3.1.3, )",
|
||||||
"resolved": "2.1.2",
|
"resolved": "3.1.3",
|
||||||
"contentHash": "In0pC521LqJXJXZgFVHegvSzES10KkKRN31McxqA1+fKtKsNe+EShWavBFQnKRlXCdeAmfx/wDjLILbvCaq+8Q==",
|
"contentHash": "wybtaqZQ1ZRZ4ZeU+9h+PaSeV14nyiGKIy7qRbDfSHzHq4ybqyOcjoifeaYbiKLO1u+PVxLBuy7MF/DMmwwbfg=="
|
||||||
"dependencies": {
|
|
||||||
"System.Runtime.CompilerServices.Unsafe": "5.0.0",
|
|
||||||
"System.Text.Encoding.CodePages": "5.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.CodeAnalysis.Analyzers": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "3.3.4",
|
|
||||||
"contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
|
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "7.0.0",
|
"resolved": "7.0.0",
|
||||||
"contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw=="
|
"contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw=="
|
||||||
},
|
},
|
||||||
"Microsoft.NETCore.Platforms": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "5.0.0",
|
|
||||||
"contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ=="
|
|
||||||
},
|
|
||||||
"SharpGLTF.Runtime": {
|
"SharpGLTF.Runtime": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "1.0.0-alpha0030",
|
"resolved": "1.0.0-alpha0030",
|
||||||
|
|
@ -86,32 +60,6 @@
|
||||||
"SharpGLTF.Core": "1.0.0-alpha0030"
|
"SharpGLTF.Core": "1.0.0-alpha0030"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"System.Collections.Immutable": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "7.0.0",
|
|
||||||
"contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ=="
|
|
||||||
},
|
|
||||||
"System.Reflection.Metadata": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "7.0.0",
|
|
||||||
"contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==",
|
|
||||||
"dependencies": {
|
|
||||||
"System.Collections.Immutable": "7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"System.Runtime.CompilerServices.Unsafe": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "6.0.0",
|
|
||||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
|
||||||
},
|
|
||||||
"System.Text.Encoding.CodePages": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "5.0.0",
|
|
||||||
"contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.NETCore.Platforms": "5.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"System.ValueTuple": {
|
"System.ValueTuple": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.5.0",
|
"resolved": "4.5.0",
|
||||||
|
|
@ -134,11 +82,14 @@
|
||||||
"penumbra.api": {
|
"penumbra.api": {
|
||||||
"type": "Project"
|
"type": "Project"
|
||||||
},
|
},
|
||||||
|
"penumbra.crashhandler": {
|
||||||
|
"type": "Project"
|
||||||
|
},
|
||||||
"penumbra.gamedata": {
|
"penumbra.gamedata": {
|
||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"OtterGui": "[1.0.0, )",
|
"OtterGui": "[1.0.0, )",
|
||||||
"Penumbra.Api": "[1.0.13, )",
|
"Penumbra.Api": "[1.0.14, )",
|
||||||
"Penumbra.String": "[1.0.4, )"
|
"Penumbra.String": "[1.0.4, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue