mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
128 lines
6.4 KiB
C#
128 lines
6.4 KiB
C#
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
|
using OtterGui.Services;
|
|
using Penumbra.Api.Enums;
|
|
using Penumbra.Collections;
|
|
using Penumbra.Collections.Manager;
|
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
|
using Penumbra.Interop.Processing;
|
|
using Penumbra.String.Classes;
|
|
using Penumbra.Util;
|
|
|
|
namespace Penumbra.Interop.PathResolving;
|
|
|
|
public class PathResolver : IDisposable, IService
|
|
{
|
|
private readonly PerformanceTracker _performance;
|
|
private readonly Configuration _config;
|
|
private readonly CollectionManager _collectionManager;
|
|
private readonly ResourceLoader _loader;
|
|
|
|
private readonly SubfileHelper _subfileHelper;
|
|
private readonly PathState _pathState;
|
|
private readonly MetaState _metaState;
|
|
private readonly GameState _gameState;
|
|
private readonly CollectionResolver _collectionResolver;
|
|
private readonly GamePathPreProcessService _preprocessor;
|
|
|
|
public PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ResourceLoader loader,
|
|
SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState,
|
|
GamePathPreProcessService preprocessor)
|
|
{
|
|
_performance = performance;
|
|
_config = config;
|
|
_collectionManager = collectionManager;
|
|
_subfileHelper = subfileHelper;
|
|
_pathState = pathState;
|
|
_metaState = metaState;
|
|
_gameState = gameState;
|
|
_preprocessor = preprocessor;
|
|
_collectionResolver = collectionResolver;
|
|
_loader = loader;
|
|
_loader.ResolvePath = ResolvePath;
|
|
}
|
|
|
|
/// <summary> Try to resolve the given game path to the replaced path. </summary>
|
|
public (FullPath?, ResolveData) ResolvePath(Utf8GamePath path, ResourceCategory category, ResourceType resourceType)
|
|
{
|
|
// Check if mods are enabled or if we are in a inc-ref at 0 reference count situation.
|
|
if (!_config.EnableMods)
|
|
return (null, ResolveData.Invalid);
|
|
|
|
// Do not allow manipulating layers to prevent very obvious cheating and softlocks.
|
|
if (resourceType is ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb)
|
|
return (null, ResolveData.Invalid);
|
|
|
|
return category switch
|
|
{
|
|
// Only Interface collection.
|
|
ResourceCategory.Ui => ResolveUi(path),
|
|
// Never allow changing scripts.
|
|
ResourceCategory.UiScript => (null, ResolveData.Invalid),
|
|
ResourceCategory.GameScript => (null, ResolveData.Invalid),
|
|
// Use actual resolving.
|
|
ResourceCategory.Chara => Resolve(path, resourceType),
|
|
ResourceCategory.Shader => Resolve(path, resourceType),
|
|
ResourceCategory.Vfx => Resolve(path, resourceType),
|
|
ResourceCategory.Sound => Resolve(path, resourceType),
|
|
// EXD Modding in general should probably be prohibited but is currently used for fan translations.
|
|
// We prevent WebURL specifically because it technically allows launching arbitrary programs / to execute arbitrary code.
|
|
ResourceCategory.Exd => path.Path.StartsWith("exd/weburl"u8)
|
|
? (null, ResolveData.Invalid)
|
|
: DefaultResolver(path),
|
|
// None of these files are ever associated with specific characters,
|
|
// always use the default resolver for now,
|
|
// except that common/font is conceptually more UI.
|
|
ResourceCategory.Common => path.Path.StartsWith("common/font"u8)
|
|
? ResolveUi(path)
|
|
: DefaultResolver(path),
|
|
ResourceCategory.BgCommon => DefaultResolver(path),
|
|
ResourceCategory.Bg => DefaultResolver(path),
|
|
ResourceCategory.Cut => DefaultResolver(path),
|
|
ResourceCategory.Music => DefaultResolver(path),
|
|
_ => DefaultResolver(path),
|
|
};
|
|
}
|
|
|
|
public (FullPath?, ResolveData) Resolve(Utf8GamePath gamePath, ResourceType type)
|
|
{
|
|
using var performance = _performance.Measure(PerformanceType.CharacterResolver);
|
|
// Check if the path was marked for a specific collection,
|
|
// or if it is a file loaded by a material, and if we are currently in a material load,
|
|
// or if it is a face decal path and the current mod collection is set.
|
|
// If not use the default collection.
|
|
// We can remove paths after they have actually been loaded.
|
|
// A potential next request will add the path anew.
|
|
var nonDefault = _subfileHelper.HandleSubFiles(type, out var resolveData)
|
|
|| _pathState.Consume(gamePath.Path, out resolveData)
|
|
|| _gameState.HandleFiles(_collectionResolver, type, gamePath, out resolveData)
|
|
|| _metaState.HandleDecalFile(type, gamePath, out resolveData);
|
|
if (!nonDefault || !resolveData.Valid)
|
|
resolveData = _collectionManager.Active.Default.ToResolveData();
|
|
|
|
// Resolve using character/default collection first, otherwise forced, as usual.
|
|
var resolved = resolveData.ModCollection.ResolvePath(gamePath);
|
|
|
|
// Since mtrl files load their files separately, we need to add the new, resolved path
|
|
// so that the functions loading tex and shpk can find that path and use its collection.
|
|
// We also need to handle defaulted materials against a non-default collection.
|
|
var path = resolved == null ? gamePath.Path : resolved.Value.InternalName;
|
|
return _preprocessor.PreProcess(resolveData, path, nonDefault, type, resolved, gamePath);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_loader.ResetResolvePath();
|
|
}
|
|
|
|
/// <summary> Use the default method of path replacement. </summary>
|
|
private (FullPath?, ResolveData) DefaultResolver(Utf8GamePath path)
|
|
{
|
|
var resolved = _collectionManager.Active.Default.ResolvePath(path);
|
|
return (resolved, _collectionManager.Active.Default.ToResolveData());
|
|
}
|
|
|
|
/// <summary> Resolve a path from the interface collection. </summary>
|
|
private (FullPath?, ResolveData) ResolveUi(Utf8GamePath path)
|
|
=> (_collectionManager.Active.Interface.ResolvePath(path),
|
|
_collectionManager.Active.Interface.ToResolveData());
|
|
}
|