private ResourceHandle *GetResourceHandler(bool isSync, ResourceManager *resourceManager, ResourceCategory *categoryId, ResourceType *resourceType, int *resourceHash, byte *path, void *unk, bool isUnk) { if (!Utf8GamePath.FromPointer(path, out var gamePath)) { PluginLog.Error("Could not create GamePath from resource path."); return(CallOriginalHandler(isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk)); } CompareHash(gamePath.Path.Crc32, *resourceHash, gamePath); ResourceRequested?.Invoke(gamePath, isSync); // If no replacements are being made, we still want to be able to trigger the event. var(resolvedPath, data) = ResolvePath(gamePath, *categoryId, *resourceType, *resourceHash); PathResolved?.Invoke(gamePath, resolvedPath, data); if (resolvedPath == null) { var retUnmodified = CallOriginalHandler(isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk); ResourceLoaded?.Invoke((Structs.ResourceHandle *)retUnmodified, gamePath, null, data); return(retUnmodified); } // Replace the hash and path with the correct one for the replacement. *resourceHash = resolvedPath.Value.InternalName.Crc32; path = resolvedPath.Value.InternalName.Path; var retModified = CallOriginalHandler(isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk); ResourceLoaded?.Invoke((Structs.ResourceHandle *)retModified, gamePath, resolvedPath.Value, data); return(retModified); }
// We need to set the correct collection for the actual material path that is loaded // before actually loading the file. private bool MtrlLoadHandler(Utf8String split, Utf8String path, ResourceManager *resourceManager, SeFileDescriptor *fileDescriptor, int priority, bool isSync, out byte ret) { ret = 0; if (fileDescriptor->ResourceHandle->FileType != ResourceType.Mtrl) { return(false); } var lastUnderscore = split.LastIndexOf(( byte )'_'); var name = lastUnderscore == -1 ? split.ToString() : split.Substring(0, lastUnderscore).ToString(); if (Penumbra.CollectionManager.ByName(name, out var collection)) { #if DEBUG PluginLog.Verbose("Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path); #endif SetCollection(path, collection); } else { #if DEBUG PluginLog.Verbose("Using MtrlLoadHandler with no collection for path {$Path:l}.", path); #endif } // Force isSync = true for this call. I don't really understand why, // or where the difference even comes from. // Was called with True on my client and with false on other peoples clients, // which caused problems. ret = Penumbra.ResourceLoader.DefaultLoadResource(path, resourceManager, fileDescriptor, priority, true); PathCollections.TryRemove(path, out _); return(true); }
// Load the resource from a path on the users hard drives. private byte DefaultRootedResourceLoad(Utf8String gamePath, ResourceManager *resourceManager, SeFileDescriptor *fileDescriptor, int priority, bool isSync) { // Specify that we are loading unpacked files from the drive. // We need to copy the actual file path in UTF16 (Windows-Unicode) on two locations, // but since we only allow ASCII in the game paths, this is just a matter of upcasting. fileDescriptor->FileMode = FileMode.LoadUnpackedResource; var fd = stackalloc byte[0x20 + 2 * gamePath.Length + 0x16]; fileDescriptor->FileDescriptor = fd; var fdPtr = ( char * )(fd + 0x21); for (var i = 0; i < gamePath.Length; ++i) { (&fileDescriptor->Utf16FileName)[i] = ( char )gamePath.Path[i]; fdPtr[i] = ( char )gamePath.Path[i]; } (&fileDescriptor->Utf16FileName)[gamePath.Length] = '\0'; fdPtr[gamePath.Length] = '\0'; // Use the SE ReadFile function. var ret = ReadFile(resourceManager, fileDescriptor, priority, isSync); FileLoaded?.Invoke(gamePath, ret != 0, true); return(ret); }
// Load the resource from an SqPack and trigger the FileLoaded event. private byte DefaultResourceLoad(Utf8String path, ResourceManager *resourceManager, SeFileDescriptor *fileDescriptor, int priority, bool isSync) { var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original(resourceManager, fileDescriptor, priority, isSync); FileLoaded?.Invoke(path, ret != 0, false); return(ret); }
private byte ReadSqPackDetour(ResourceManager *resourceManager, SeFileDescriptor *fileDescriptor, int priority, bool isSync) { if (!DoReplacements) { return(ReadSqPackHook.Original(resourceManager, fileDescriptor, priority, isSync)); } if (fileDescriptor == null || fileDescriptor->ResourceHandle == null) { PluginLog.Error("Failure to load file from SqPack: invalid File Descriptor."); return(ReadSqPackHook.Original(resourceManager, fileDescriptor, priority, isSync)); } if (!Utf8GamePath.FromSpan(fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false) || gamePath.Length == 0) { return(ReadSqPackHook.Original(resourceManager, fileDescriptor, priority, isSync)); } // Paths starting with a '|' are handled separately to allow for special treatment. // They are expected to also have a closing '|'. if (ResourceLoadCustomization == null || gamePath.Path[0] != ( byte )'|') { return(DefaultLoadResource(gamePath.Path, resourceManager, fileDescriptor, priority, isSync)); } // Split the path into the special-treatment part (between the first and second '|') // and the actual path. byte ret = 0; var split = gamePath.Path.Split(( byte )'|', 3, false); fileDescriptor->ResourceHandle->FileNameData = split[2].Path; fileDescriptor->ResourceHandle->FileNameLength = split[2].Length; var funcFound = ResourceLoadCustomization.GetInvocationList() .Any(f => (( ResourceLoadCustomizationDelegate )f) .Invoke(split[1], split[2], resourceManager, fileDescriptor, priority, isSync, out ret)); if (!funcFound) { ret = DefaultLoadResource(split[2], resourceManager, fileDescriptor, priority, isSync); } // Return original resource handle path so that they can be loaded separately. fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; return(ret); }
private ResourceHandle *CallOriginalHandler(bool isSync, ResourceManager *resourceManager, ResourceCategory *categoryId, ResourceType *resourceType, int *resourceHash, byte *path, void *unk, bool isUnk) => isSync ? GetResourceSyncHook.Original(resourceManager, categoryId, resourceType, resourceHash, path, unk) : GetResourceAsyncHook.Original(resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk);
private ResourceHandle *GetResourceAsyncDetour(ResourceManager *resourceManager, ResourceCategory *categoryId, ResourceType *resourceType, int *resourceHash, byte *path, void *unk, bool isUnk) => GetResourceHandler(false, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk);
private ResourceHandle *GetResourceSyncDetour(ResourceManager *resourceManager, ResourceCategory *categoryId, ResourceType *resourceType, int *resourceHash, byte *path, void *unk) => GetResourceHandler(true, resourceManager, categoryId, resourceType, resourceHash, path, unk, false);
// Load a resource by its path. If it is rooted, it will be loaded from the drive, otherwise from the SqPack. internal byte DefaultLoadResource(Utf8String gamePath, ResourceManager *resourceManager, SeFileDescriptor *fileDescriptor, int priority, bool isSync) => Utf8GamePath.IsRooted(gamePath) ? DefaultRootedResourceLoad(gamePath, resourceManager, fileDescriptor, priority, isSync) : DefaultResourceLoad(gamePath, resourceManager, fileDescriptor, priority, isSync);