using System; using System.Threading; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; namespace SqPackIndexer; public unsafe class FileReadService : IDisposable { public FileReadService(ResourceManagerService resourceManager, IGameInteropProvider interop) { _resourceManager = resourceManager; interop.InitializeFromAttributes(this); _readSqPackHook.Enable(); } /// Invoked when a file is supposed to be read from SqPack. /// The file descriptor containing what file to read. /// The games priority. Should not generally be changed. /// Whether the file needs to be loaded synchronously. Should not generally be changed. /// The return value. If this is set, original will not be called. public delegate void ReadSqPackDelegate(SeFileDescriptor* fileDescriptor, ref int priority, ref bool isSync, ref byte? returnValue); /// /// /// Subscribers should be exception-safe. /// public event ReadSqPackDelegate? ReadSqPack; /// /// Use the games ReadFile function to read a file from the hard drive instead of an SqPack. /// /// The file to load. /// The games priority. /// Whether the file needs to be loaded synchronously. /// Unknown, not directly success/failure. public byte ReadFile(SeFileDescriptor* fileDescriptor, int priority, bool isSync) => _readFile.Invoke(GetResourceManager(), fileDescriptor, priority, isSync); public byte ReadDefaultSqPack(SeFileDescriptor* fileDescriptor, int priority, bool isSync) => _readSqPackHook.Original(GetResourceManager(), fileDescriptor, priority, isSync); public void Dispose() { _readSqPackHook.Dispose(); } private readonly ResourceManagerService _resourceManager; private delegate byte ReadSqPackPrototype(nint resourceManager, SeFileDescriptor* pFileDesc, int priority, bool isSync); [Signature(Sigs.ReadSqPack, DetourName = nameof(ReadSqPackDetour))] private readonly Hook _readSqPackHook = null!; private byte ReadSqPackDetour(nint resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync) { Dalamud.Logging.PluginLog.Log("Got detour: " + fileDescriptor->ResourceHandle->FileName); byte? ret = null; _lastFileThreadResourceManager.Value = resourceManager; ReadSqPack?.Invoke(fileDescriptor, ref priority, ref isSync, ref ret); _lastFileThreadResourceManager.Value = IntPtr.Zero; return ret ?? _readSqPackHook.Original(resourceManager, fileDescriptor, priority, isSync); } private delegate byte ReadFileDelegate(nint resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync); /// We need to use the ReadFile function to load local, uncompressed files instead of loading them from the SqPacks. [Signature(Sigs.ReadFile)] private readonly ReadFileDelegate _readFile = null!; private readonly ThreadLocal _lastFileThreadResourceManager = new(true); /// /// Usually files are loaded using the resource manager as a first pointer, but it seems some rare cases are using something else. /// So we keep track of them per thread and use them. /// private nint GetResourceManager() => !_lastFileThreadResourceManager.IsValueCreated || _lastFileThreadResourceManager.Value == IntPtr.Zero ? (nint)_resourceManager.ResourceManager : _lastFileThreadResourceManager.Value; }