예제 #1
0
    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);
    }
예제 #2
0
 private static void CompareHash(int local, int game, Utf8GamePath path)
 {
     if (local != game)
     {
         PluginLog.Warning("Hash function appears to have changed. Computed {Hash1:X8} vs Game {Hash2:X8} for {Path}.", local, game, path);
     }
 }
예제 #3
0
    private void OnResourceRequested(Utf8GamePath data, bool synchronous)
    {
        var path = Match(data.Path);

        if (path != null)
        {
            PluginLog.Information($"{path} was requested {( synchronous ? "synchronously." : "asynchronously." )}");
        }
    }
예제 #4
0
    private static string ResolvePath(string path, Mod.Manager _, ModCollection collection)
    {
        if (!Penumbra.Config.EnableMods)
        {
            return(path);
        }

        var gamePath = Utf8GamePath.FromString(path, out var p, true) ? p : Utf8GamePath.Empty;
        var ret      = collection.ResolvePath(gamePath);

        return(ret?.ToString() ?? path);
    }
예제 #5
0
    // Create the default data file from all unused files that were not handled before
    // and are used in sub mods.
    internal static void CreateDefaultFiles(DirectoryInfo directory)
    {
        var mod = new Mod(directory);

        mod.Reload(out _);
        foreach (var file in mod.FindUnusedFiles())
        {
            if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath, true))
            {
                mod._default.FileData.TryAdd(gamePath, file);
            }
        }

        mod._default.IncorporateMetaChanges(directory, true);
        mod.SaveDefaultMod();
    }
예제 #6
0
    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);
    }
예제 #7
0
    // Create the data for a given sub mod from its data and the folder it is based on.
    internal static ISubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option)
    {
        var list = optionFolder.EnumerateFiles("*.*", SearchOption.AllDirectories)
                   .Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f)))
                   .Where(t => t.Item1);

        var mod = new SubMod
        {
            Name = option.Name,
        };

        foreach (var(_, gamePath, file) in list)
        {
            mod.FileData.TryAdd(gamePath, file);
        }

        mod.IncorporateMetaChanges(baseFolder, true);
        return(mod);
    }
예제 #8
0
        // If any conflicts exist, show them in this tab.
        private unsafe void DrawConflictsTab()
        {
            using var tab = DrawTab(ConflictTabHeader, Tabs.Conflicts);
            if (!tab)
            {
                return;
            }

            using var box = ImRaii.ListBox("##conflicts", -Vector2.One);
            if (!box)
            {
                return;
            }

            foreach (var conflict in Penumbra.CollectionManager.Current.Conflicts(_mod))
            {
                if (ImGui.Selectable(conflict.Mod2.Name) && conflict.Mod2 is Mod mod)
                {
                    _window._selector.SelectByValue(mod);
                }

                ImGui.SameLine();
                using (var color = ImRaii.PushColor(ImGuiCol.Text,
                                                    conflict.HasPriority ? ColorId.HandledConflictMod.Value() : ColorId.ConflictingMod.Value()))
                {
                    var priority = conflict.Mod2.Index < 0
                        ? conflict.Mod2.Priority
                        : Penumbra.CollectionManager.Current[conflict.Mod2.Index].Settings !.Priority;
                    ImGui.TextUnformatted($"(Priority {priority})");
                }

                using var indent = ImRaii.PushIndent(30f);
                foreach (var data in conflict.Conflicts)
                {
                    var _ = data switch
                    {
                        Utf8GamePath p => ImGuiNative.igSelectable_Bool(p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) > 0,
                        MetaManipulation m => ImGui.Selectable(m.Manipulation?.ToString() ?? string.Empty),
                        _ => false,
                    };
                }
            }
        }
예제 #9
0
    // Try all resolve path subscribers or use the default replacer.
    private (FullPath?, object?) ResolvePath(Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash)
    {
        if (!DoReplacements)
        {
            return(null, null);
        }

        path = path.ToLower();
        if (ResolvePathCustomization != null)
        {
            foreach (var resolver in ResolvePathCustomization.GetInvocationList())
            {
                if ((( ResolvePathDelegate )resolver).Invoke(path, category, resourceType, resourceHash, out var ret))
                {
                    return(ret);
                }
            }
        }

        return(DefaultResolver(path));
    }
예제 #10
0
 // The modified resolver that handles game path resolving.
 private bool CharacterResolver(Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, object?) data)
예제 #11
0
 // Force a file resolve to be removed.
 internal void RemoveFile(Utf8GamePath path)
 => _cache !.ResolvedFiles.Remove(path);
예제 #12
0
 // Force a file to be resolved to a specific path regardless of conflicts.
 internal void ForceFile(Utf8GamePath path, FullPath fullPath)
 => _cache !.ResolvedFiles[path] = new ModPath(Mod.ForcedFiles, fullPath);
예제 #13
0
 public FullPath?ResolvePath(Utf8GamePath path)
 => _cache?.ResolvePath(path);
예제 #14
0
    // Use the default method of path replacement.
    public static (FullPath?, object?) DefaultResolver(Utf8GamePath path)
    {
        var resolved = Penumbra.CollectionManager.Default.ResolvePath(path);

        return(resolved, null);
    }
예제 #15
0
 // 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);