Skip to content

Commit

Permalink
Merge branch 'dt-shmod'
Browse files Browse the repository at this point in the history
  • Loading branch information
Ottermandias committed Aug 7, 2024
2 parents df58ac7 + fe4a046 commit 1648bfe
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 20 deletions.
2 changes: 1 addition & 1 deletion OtterGui
2 changes: 1 addition & 1 deletion Penumbra.GameData
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ namespace Penumbra.Communication;
/// <item>Parameter is the material resource handle for which the shader package has been loaded. </item>
/// <item>Parameter is the associated game object. </item>
/// </list> </summary>
public sealed class MtrlShpkLoaded() : EventWrapper<nint, nint, MtrlShpkLoaded.Priority>(nameof(MtrlShpkLoaded))
public sealed class MtrlLoaded() : EventWrapper<nint, nint, MtrlLoaded.Priority>(nameof(MtrlLoaded))
{
public enum Priority
{
/// <seealso cref="Interop.Hooks.PostProcessing.ShaderReplacementFixer.OnMtrlShpkLoaded"/>
/// <seealso cref="Interop.Hooks.PostProcessing.ShaderReplacementFixer.OnMtrlLoaded"/>
ShaderReplacementFixer = 0,
}
}
2 changes: 1 addition & 1 deletion Penumbra/Interop/Hooks/HookSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public struct ResourceLoadingHooks
public struct ResourceHooks
{
public bool ApricotResourceLoad;
public bool LoadMtrlShpk;
public bool LoadMtrl;
public bool LoadMtrlTex;
public bool ResolvePathHooks;
public bool ResourceHandleDestructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ public ShaderReplacementFixer(ResourceHandleDestructor resourceHandleDestructor,
_modelRendererOnRenderMaterialHook = hooks.CreateHook<ModelRendererOnRenderMaterialDelegate>("ModelRenderer.OnRenderMaterial",
Sigs.ModelRendererOnRenderMaterial, ModelRendererOnRenderMaterialDetour,
!HookOverrides.Instance.PostProcessing.ModelRendererOnRenderMaterial).Result;
_communicator.MtrlShpkLoaded.Subscribe(OnMtrlShpkLoaded, MtrlShpkLoaded.Priority.ShaderReplacementFixer);
_communicator.MtrlLoaded.Subscribe(OnMtrlLoaded, MtrlLoaded.Priority.ShaderReplacementFixer);
_resourceHandleDestructor.Subscribe(OnResourceHandleDestructor, ResourceHandleDestructor.Priority.ShaderReplacementFixer);
}

public void Dispose()
{
_modelRendererOnRenderMaterialHook.Dispose();
_humanOnRenderMaterialHook.Dispose();
_communicator.MtrlShpkLoaded.Unsubscribe(OnMtrlShpkLoaded);
_communicator.MtrlLoaded.Unsubscribe(OnMtrlLoaded);
_resourceHandleDestructor.Unsubscribe(OnResourceHandleDestructor);
_hairMaskState.ClearMaterials();
_characterOcclusionState.ClearMaterials();
Expand Down Expand Up @@ -147,7 +147,7 @@ private static bool IsMaterialWithShpk(MaterialResourceHandle* mtrlResource, Rea
return shpkName.SequenceEqual(mtrlResource->ShpkNameSpan);
}

private void OnMtrlShpkLoaded(nint mtrlResourceHandle, nint gameObject)
private void OnMtrlLoaded(nint mtrlResourceHandle, nint gameObject)
{
var mtrl = (MaterialResourceHandle*)mtrlResourceHandle;
var shpk = mtrl->ShaderPackageResourceHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@

namespace Penumbra.Interop.Hooks.Resources;

public sealed unsafe class LoadMtrlShpk : FastHook<LoadMtrlShpk.Delegate>
public sealed unsafe class LoadMtrl : FastHook<LoadMtrl.Delegate>
{
private readonly GameState _gameState;
private readonly CommunicatorService _communicator;

public LoadMtrlShpk(HookManager hooks, GameState gameState, CommunicatorService communicator)
public LoadMtrl(HookManager hooks, GameState gameState, CommunicatorService communicator)
{
_gameState = gameState;
_communicator = communicator;
Task = hooks.CreateHook<Delegate>("Load Material Shaders", Sigs.LoadMtrlShpk, Detour, !HookOverrides.Instance.Resources.LoadMtrlShpk);
Task = hooks.CreateHook<Delegate>("Load Material", Sigs.LoadMtrl, Detour, !HookOverrides.Instance.Resources.LoadMtrl);
}

public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle);
public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle, void* unk1, byte unk2);

private byte Detour(MaterialResourceHandle* handle)
private byte Detour(MaterialResourceHandle* handle, void* unk1, byte unk2)
{
var last = _gameState.MtrlData.Value;
var mtrlData = _gameState.LoadSubFileHelper((nint)handle);
_gameState.MtrlData.Value = mtrlData;
var ret = Task.Result.Original(handle);
var ret = Task.Result.Original(handle, unk1, unk2);
_gameState.MtrlData.Value = last;
_communicator.MtrlShpkLoaded.Invoke((nint)handle, mtrlData.AssociatedGameObject);
_communicator.MtrlLoaded.Invoke((nint)handle, mtrlData.AssociatedGameObject);
return ret;
}
}
1 change: 1 addition & 0 deletions Penumbra/Interop/Hooks/Resources/LoadMtrlTex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Penumbra.Interop.Hooks.Resources;

// TODO check if this is still needed, as our hooked function is called by LoadMtrl's hooked function
public sealed unsafe class LoadMtrlTex : FastHook<LoadMtrlTex.Delegate>
{
private readonly GameState _gameState;
Expand Down
88 changes: 88 additions & 0 deletions Penumbra/Interop/Processing/ShpkPathPreProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData.Files;
using Penumbra.GameData.Files.Utility;
using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.String;
using Penumbra.String.Classes;

namespace Penumbra.Interop.Processing;

/// <summary>
/// Path pre-processor for shader packages that reverts redirects to known invalid files, as bad ShPks can crash the game.
/// </summary>
public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager, MessageService messager, ModManager modManager)
: IPathPreProcessor
{
public ResourceType Type
=> ResourceType.Shpk;

public unsafe FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath originalGamePath, bool nonDefault,
FullPath? resolved)
{
messager.CleanTaggedMessages(false);

if (!resolved.HasValue)
return null;

// Skip the sanity check for game files. We are not considering the case where the user has modified game file: it's at their own risk.
var resolvedPath = resolved.Value;
if (!resolvedPath.IsRooted)
return resolvedPath;

// If the ShPk is already loaded, it means that it already passed the sanity check.
var existingResource =
resourceManager.FindResource(ResourceCategory.Shader, ResourceType.Shpk, unchecked((uint)resolvedPath.InternalName.Crc32));
if (existingResource != null)
return resolvedPath;

var checkResult = SanityCheck(resolvedPath.FullName);
if (checkResult == SanityCheckResult.Success)
return resolvedPath;

messager.PrintFileWarning(modManager, resolvedPath.FullName, originalGamePath, WarningMessageComplement(checkResult));

return null;
}

private static SanityCheckResult SanityCheck(string path)
{
try
{
using var file = MmioMemoryManager.CreateFromFile(path);
var bytes = file.GetSpan();

return ShpkFile.FastIsLegacy(bytes)
? SanityCheckResult.Legacy
: SanityCheckResult.Success;
}
catch (FileNotFoundException)
{
return SanityCheckResult.NotFound;
}
catch (IOException)
{
return SanityCheckResult.IoError;
}
}

private static string WarningMessageComplement(SanityCheckResult result)
=> result switch
{
SanityCheckResult.IoError => "Cannot read the modded file.",
SanityCheckResult.NotFound => "The modded file does not exist.",
SanityCheckResult.Legacy => "This mod is not compatible with Dawntrail. Get an updated version, if possible, or disable it.",
_ => string.Empty,
};

private enum SanityCheckResult
{
Success,
IoError,
NotFound,
Legacy,
}
}
4 changes: 4 additions & 0 deletions Penumbra/Interop/ResourceTree/ResourceNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class ResourceNode : ICloneable
public readonly nint ResourceHandle;
public Utf8GamePath[] PossibleGamePaths;
public FullPath FullPath;
public string? ModName;
public string? ModRelativePath;
public CiByteString AdditionalData;
public readonly ulong Length;
public readonly List<ResourceNode> Children;
Expand Down Expand Up @@ -57,6 +59,8 @@ private ResourceNode(ResourceNode other)
ResourceHandle = other.ResourceHandle;
PossibleGamePaths = other.PossibleGamePaths;
FullPath = other.FullPath;
ModName = other.ModName;
ModRelativePath = other.ModRelativePath;
AdditionalData = other.AdditionalData;
Length = other.Length;
Children = other.Children;
Expand Down
19 changes: 18 additions & 1 deletion Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving;
using Penumbra.Meta;
using Penumbra.Mods.Manager;
using Penumbra.String.Classes;

namespace Penumbra.Interop.ResourceTree;
Expand All @@ -21,7 +22,8 @@ public class ResourceTreeFactory(
ObjectIdentification objectIdentifier,
Configuration config,
ActorManager actors,
PathState pathState) : IService
PathState pathState,
ModManager modManager) : IService
{
private TreeBuildCache CreateTreeBuildCache()
=> new(objects, gameData, actors);
Expand Down Expand Up @@ -93,7 +95,10 @@ public IEnumerable<ICharacter> GetLocalPlayerRelatedCharacters()
// This is currently unneeded as we can resolve all paths by querying the draw object:
// ResolveGamePaths(tree, collectionResolveData.ModCollection);
if (globalContext.WithUiData)
{
ResolveUiData(tree);
ResolveModData(tree);
}
FilterFullPaths(tree, (flags & Flags.RedactExternalPaths) != 0 ? config.ModDirectory : null);
Cleanup(tree);

Expand Down Expand Up @@ -123,6 +128,18 @@ private static void ResolveUiData(ResourceTree tree)
});
}

private void ResolveModData(ResourceTree tree)
{
foreach (var node in tree.FlatNodes)
{
if (node.FullPath.IsRooted && modManager.TryIdentifyPath(node.FullPath.FullName, out var mod, out var relativePath))
{
node.ModName = mod.Name;
node.ModRelativePath = relativePath;
}
}
}

private static void FilterFullPaths(ResourceTree tree, string? onlyWithinPath)
{
foreach (var node in tree.FlatNodes)
Expand Down
18 changes: 18 additions & 0 deletions Penumbra/Mods/Manager/ModManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,22 @@ private void ScanMods()
Penumbra.Log.Error($"Could not scan for mods:\n{ex}");
}
}

public bool TryIdentifyPath(string path, [NotNullWhen(true)] out Mod? mod, [NotNullWhen(true)] out string? relativePath)
{
var relPath = Path.GetRelativePath(BasePath.FullName, path);
if (relPath != "." && (relPath.StartsWith('.') || Path.IsPathRooted(relPath)))
{
mod = null;
relativePath = null;
return false;
}

var modDirectorySeparator = relPath.IndexOfAny([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]);

var modDirectory = modDirectorySeparator < 0 ? relPath : relPath[..modDirectorySeparator];
relativePath = modDirectorySeparator < 0 ? string.Empty : relPath[(modDirectorySeparator + 1)..];

return TryGetMod(modDirectory, "\0", out mod);
}
}
6 changes: 3 additions & 3 deletions Penumbra/Services/CommunicatorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public CommunicatorService(Logger logger)
/// <inheritdoc cref="Communication.CreatedCharacterBase"/>
public readonly CreatedCharacterBase CreatedCharacterBase = new();

/// <inheritdoc cref="Communication.MtrlShpkLoaded"/>
public readonly MtrlShpkLoaded MtrlShpkLoaded = new();
/// <inheritdoc cref="Communication.MtrlLoaded"/>
public readonly MtrlLoaded MtrlLoaded = new();

/// <inheritdoc cref="Communication.ModDataChanged"/>
public readonly ModDataChanged ModDataChanged = new();
Expand Down Expand Up @@ -87,7 +87,7 @@ public void Dispose()
TemporaryGlobalModChange.Dispose();
CreatingCharacterBase.Dispose();
CreatedCharacterBase.Dispose();
MtrlShpkLoaded.Dispose();
MtrlLoaded.Dispose();
ModDataChanged.Dispose();
ModOptionChanged.Dispose();
ModDiscoveryStarted.Dispose();
Expand Down
16 changes: 16 additions & 0 deletions Penumbra/Services/MessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using OtterGui.Log;
using OtterGui.Services;
using Penumbra.Mods.Manager;
using Penumbra.String.Classes;
using Notification = OtterGui.Classes.Notification;

namespace Penumbra.Services;

Expand Down Expand Up @@ -38,4 +42,16 @@ public void LinkItem(Item item)
Message = payload,
});
}

public void PrintFileWarning(ModManager modManager, string fullPath, Utf8GamePath originalGamePath, string messageComplement)
{
// Don't warn for files managed by other plugins, or files we aren't sure about.
if (!modManager.TryIdentifyPath(fullPath, out var mod, out _))
return;

AddTaggedMessage($"{fullPath}.{messageComplement}",
new Notification(
$"Cowardly refusing to load replacement for {originalGamePath.Filename().ToString().ToLowerInvariant()} by {mod.Name}{(messageComplement.Length > 0 ? ":\n" : ".")}{messageComplement}",
NotificationType.Warning, 10000));
}
}
5 changes: 4 additions & 1 deletion Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,10 @@ string GetAdditionalDataSuffix(CiByteString data)
ImGui.TableNextColumn();
if (resourceNode.FullPath.FullName.Length > 0)
{
ImGui.Selectable(resourceNode.FullPath.ToPath(), false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
var uiFullPathStr = resourceNode.ModName != null && resourceNode.ModRelativePath != null
? $"[{resourceNode.ModName}] {resourceNode.ModRelativePath}"
: resourceNode.FullPath.ToPath();
ImGui.Selectable(uiFullPathStr, false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
if (ImGui.IsItemClicked())
ImGui.SetClipboardText(resourceNode.FullPath.ToPath());
ImGuiUtil.HoverTooltip(
Expand Down

0 comments on commit 1648bfe

Please sign in to comment.