private unsafe byte ReadSqpackHandler(IntPtr pFileHandler, SeFileDescriptor *pFileDesc, int priority, bool isSync) { var gameFsPath = Marshal.PtrToStringAnsi(new IntPtr(pFileDesc->ResourceHandle->FileName)); var isRooted = Path.IsPathRooted(gameFsPath); if (gameFsPath == null || gameFsPath.Length >= 260 || !isRooted) { return(ReadSqpackHook.OriginalFunction(pFileHandler, pFileDesc, priority, isSync)); } #if DEBUG PluginLog.Log("loading modded file: {GameFsPath}", gameFsPath); #endif pFileDesc->FileMode = FileMode.LoadUnpackedResource; // note: must be utf16 var utfPath = Encoding.Unicode.GetBytes(gameFsPath); Marshal.Copy(utfPath, 0, new IntPtr(&pFileDesc->UtfFileName), utfPath.Length); var fd = stackalloc byte[0x20 + utfPath.Length + 0x16]; Marshal.Copy(utfPath, 0, new IntPtr(fd + 0x21), utfPath.Length); pFileDesc->FileDescriptor = fd; return(ReadFile(pFileHandler, pFileDesc, priority, isSync)); }
// 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); }
// 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);