using System; using System.IO.Enumeration; using System.Net.Http; using System.Text; using System.Threading; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Newtonsoft.Json; namespace SqPackIndexer; public class ApiRequest { public string filename; } public class HttpHelper { private static HttpClient sharedClient = new() { BaseAddress = new Uri("http://127.0.0.1:3500"), }; public async void sendFilename(String filename) { using StringContent jsonContent = new(JsonConvert.SerializeObject(new ApiRequest { filename = filename }), Encoding.UTF8, "application/json"); using HttpResponseMessage response = await sharedClient.PostAsync( "add_hash", jsonContent); } } public unsafe class FileReadService : IDisposable { public FileReadService(ResourceManagerService resourceManager, IGameInteropProvider interop) { _resourceManager = resourceManager; interop.InitializeFromAttributes(this); _readSqPackHook.Enable(); _httpHelper = new HttpHelper(); } /// 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 readonly HttpHelper _httpHelper; private byte ReadSqPackDetour(nint resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync) { Dalamud.Logging.PluginLog.Log("Got detour: " + fileDescriptor->ResourceHandle->FileName); _httpHelper.sendFilename(fileDescriptor->ResourceHandle->FileName.ToString()); 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; }