mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
More sophisticated fix against E4S crashes with working mods in E4S.
This commit is contained in:
parent
a1f02975cb
commit
f601812666
13 changed files with 273 additions and 210 deletions
|
|
@ -22,7 +22,7 @@ namespace Penumbra.GameData.Util
|
|||
}
|
||||
else
|
||||
{
|
||||
_path = "";
|
||||
_path = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Text;
|
|||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Structs;
|
||||
|
|
@ -19,10 +18,10 @@ public class ResourceLoader : IDisposable
|
|||
public Penumbra Penumbra { get; set; }
|
||||
|
||||
public bool IsEnabled { get; set; }
|
||||
public bool HacksEnabled { get; set; }
|
||||
|
||||
public Crc32 Crc32 { get; }
|
||||
|
||||
public static readonly IntPtr CustomFileFlag = new(0xDEADBEEF);
|
||||
|
||||
// Delegate prototypes
|
||||
[UnmanagedFunctionPointer( CallingConvention.ThisCall )]
|
||||
|
|
@ -40,7 +39,7 @@ public class ResourceLoader : IDisposable
|
|||
, uint* pResourceHash, char* pPath, void* pUnknown, bool isUnknown );
|
||||
|
||||
[UnmanagedFunctionPointer( CallingConvention.ThisCall )]
|
||||
public delegate bool CheckFileStatePrototype( IntPtr unk1, ulong unk2 );
|
||||
public delegate IntPtr CheckFileStatePrototype( IntPtr unk1, ulong crc );
|
||||
|
||||
[UnmanagedFunctionPointer( CallingConvention.ThisCall )]
|
||||
public delegate byte LoadTexFileExternPrototype( IntPtr resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr unk4 );
|
||||
|
|
@ -64,7 +63,6 @@ public class ResourceLoader : IDisposable
|
|||
|
||||
// Unmanaged functions
|
||||
public ReadFilePrototype? ReadFile { get; private set; }
|
||||
public CheckFileStatePrototype? CheckFileState { get; private set; }
|
||||
public LoadTexFileLocalPrototype? LoadTexFileLocal { get; private set; }
|
||||
public LoadMdlFileLocalPrototype? LoadMdlFileLocal { get; private set; }
|
||||
|
||||
|
|
@ -128,37 +126,21 @@ public class ResourceLoader : IDisposable
|
|||
LoadMdlFileExternHook = new Hook< LoadMdlFileExternPrototype >( loadMdlFileExternAddress, LoadMdlFileExternDetour );
|
||||
}
|
||||
|
||||
private bool CheckForTerritory()
|
||||
private IntPtr CheckFileStateDetour( IntPtr ptr, ulong crc64 )
|
||||
{
|
||||
var territory = Dalamud.GameData.GetExcelSheet< TerritoryType >()?.GetRow( Dalamud.ClientState.TerritoryType );
|
||||
var bad = territory?.Unknown40 ?? false;
|
||||
switch( bad )
|
||||
{
|
||||
case true when HacksEnabled:
|
||||
CheckFileStateHook?.Disable();
|
||||
LoadTexFileExternHook?.Disable();
|
||||
LoadMdlFileExternHook?.Disable();
|
||||
HacksEnabled = false;
|
||||
return bad;
|
||||
case false when Penumbra.Config.IsEnabled && !HacksEnabled:
|
||||
CheckFileStateHook?.Enable();
|
||||
LoadTexFileExternHook?.Enable();
|
||||
LoadMdlFileExternHook?.Enable();
|
||||
HacksEnabled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return bad;
|
||||
var modManager = Service< ModManager >.Get();
|
||||
return modManager.CheckCrc64( crc64 ) ? CustomFileFlag : CheckFileStateHook!.Original( ptr, crc64 );
|
||||
}
|
||||
|
||||
private static bool CheckFileStateDetour( IntPtr _, ulong _2 )
|
||||
=> true;
|
||||
private byte LoadTexFileExternDetour( IntPtr resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr ptr )
|
||||
=> ptr.Equals( CustomFileFlag )
|
||||
? LoadTexFileLocal!.Invoke( resourceHandle, unk1, unk2, unk3 )
|
||||
: LoadTexFileExternHook!.Original( resourceHandle, unk1, unk2, unk3, ptr );
|
||||
|
||||
private byte LoadTexFileExternDetour( IntPtr resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr _ )
|
||||
=> LoadTexFileLocal!.Invoke( resourceHandle, unk1, unk2, unk3 );
|
||||
|
||||
private byte LoadMdlFileExternDetour( IntPtr resourceHandle, IntPtr unk1, bool unk2, IntPtr _ )
|
||||
=> LoadMdlFileLocal!.Invoke( resourceHandle, unk1, unk2 );
|
||||
private byte LoadMdlFileExternDetour( IntPtr resourceHandle, IntPtr unk1, bool unk2, IntPtr ptr )
|
||||
=> ptr.Equals( CustomFileFlag )
|
||||
? LoadMdlFileLocal!.Invoke( resourceHandle, unk1, unk2 )
|
||||
: LoadMdlFileExternHook!.Original( resourceHandle, unk1, unk2, ptr );
|
||||
|
||||
private unsafe void* GetResourceSyncHandler(
|
||||
IntPtr pFileManager,
|
||||
|
|
@ -223,11 +205,6 @@ public class ResourceLoader : IDisposable
|
|||
bool isUnknown
|
||||
)
|
||||
{
|
||||
if( CheckForTerritory() )
|
||||
{
|
||||
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||
}
|
||||
|
||||
string file;
|
||||
var modManager = Service< ModManager >.Get();
|
||||
|
||||
|
|
@ -277,11 +254,6 @@ public class ResourceLoader : IDisposable
|
|||
|
||||
private unsafe byte ReadSqpackHandler( IntPtr pFileHandler, SeFileDescriptor* pFileDesc, int priority, bool isSync )
|
||||
{
|
||||
if( CheckForTerritory() )
|
||||
{
|
||||
return ReadSqpackHook?.Original( pFileHandler, pFileDesc, priority, isSync ) ?? 0;
|
||||
}
|
||||
|
||||
if( ReadFile == null || pFileDesc == null || pFileDesc->ResourceHandle == null )
|
||||
{
|
||||
PluginLog.Error( "THIS SHOULD NOT HAPPEN" );
|
||||
|
|
@ -340,7 +312,6 @@ public class ResourceLoader : IDisposable
|
|||
LoadMdlFileExternHook.Enable();
|
||||
|
||||
IsEnabled = true;
|
||||
HacksEnabled = true;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
|
|
@ -357,7 +328,6 @@ public class ResourceLoader : IDisposable
|
|||
LoadTexFileExternHook?.Disable();
|
||||
LoadMdlFileExternHook?.Disable();
|
||||
IsEnabled = false;
|
||||
HacksEnabled = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ namespace Penumbra.Meta
|
|||
|
||||
// Update the whole meta collection by reading all TexTools .meta files in a mod directory anew,
|
||||
// combining them with the given ModMeta.
|
||||
public void Update( IEnumerable< FileInfo > files, DirectoryInfo basePath, ModMeta modMeta )
|
||||
public void Update( IEnumerable< FullPath > files, DirectoryInfo basePath, ModMeta modMeta )
|
||||
{
|
||||
DefaultData.Clear();
|
||||
GroupData.Clear();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Penumbra.Meta
|
|||
{
|
||||
public readonly object Data;
|
||||
public bool Changed;
|
||||
public FileInfo? CurrentFile;
|
||||
public FullPath? CurrentFile;
|
||||
|
||||
public FileInformation( object data )
|
||||
=> Data = data;
|
||||
|
|
@ -35,7 +35,7 @@ namespace Penumbra.Meta
|
|||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
DisposeFile( CurrentFile );
|
||||
CurrentFile = TempFile.WriteNew( dir, data, $"_{originalPath.Filename()}" );
|
||||
CurrentFile = new FullPath(TempFile.WriteNew( dir, data, $"_{originalPath.Filename()}" ));
|
||||
Changed = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ namespace Penumbra.Meta
|
|||
private readonly MetaDefaults _default;
|
||||
private readonly DirectoryInfo _dir;
|
||||
private readonly ResidentResources _resourceManagement;
|
||||
private readonly Dictionary< GamePath, FileInfo > _resolvedFiles;
|
||||
private readonly Dictionary< GamePath, FullPath > _resolvedFiles;
|
||||
|
||||
private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new();
|
||||
private readonly Dictionary< GamePath, FileInformation > _currentFiles = new();
|
||||
|
|
@ -53,9 +53,9 @@ namespace Penumbra.Meta
|
|||
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
|
||||
=> _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) );
|
||||
|
||||
public IEnumerable< (GamePath, FileInfo) > Files
|
||||
public IEnumerable< (GamePath, FullPath) > Files
|
||||
=> _currentFiles.Where( kvp => kvp.Value.CurrentFile != null )
|
||||
.Select( kvp => ( kvp.Key, kvp.Value.CurrentFile! ) );
|
||||
.Select( kvp => ( kvp.Key, kvp.Value.CurrentFile!.Value ) );
|
||||
|
||||
public int Count
|
||||
=> _currentManipulations.Count;
|
||||
|
|
@ -63,9 +63,8 @@ namespace Penumbra.Meta
|
|||
public bool TryGetValue( MetaManipulation manip, out Mod.Mod mod )
|
||||
=> _currentManipulations.TryGetValue( manip, out mod! );
|
||||
|
||||
private static void DisposeFile( FileInfo? file )
|
||||
private static void DisposeFile( FullPath? file )
|
||||
{
|
||||
file?.Refresh();
|
||||
if( !( file?.Exists ?? false ) )
|
||||
{
|
||||
return;
|
||||
|
|
@ -73,11 +72,11 @@ namespace Penumbra.Meta
|
|||
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
File.Delete( file.Value.FullName );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete temporary file \"{file.FullName}\":\n{e}" );
|
||||
PluginLog.Error( $"Could not delete temporary file \"{file.Value.FullName}\":\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +119,7 @@ namespace Penumbra.Meta
|
|||
private void ClearDirectory()
|
||||
=> ClearDirectory( _dir );
|
||||
|
||||
public MetaManager( string name, Dictionary< GamePath, FileInfo > resolvedFiles, DirectoryInfo tempDir )
|
||||
public MetaManager( string name, Dictionary< GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir )
|
||||
{
|
||||
_resolvedFiles = resolvedFiles;
|
||||
_default = Service< MetaDefaults >.Get();
|
||||
|
|
@ -139,7 +138,7 @@ namespace Penumbra.Meta
|
|||
foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) )
|
||||
{
|
||||
kvp.Value.Write( _dir, kvp.Key );
|
||||
_resolvedFiles[ kvp.Key ] = kvp.Value.CurrentFile!;
|
||||
_resolvedFiles[ kvp.Key ] = kvp.Value.CurrentFile!.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,86 +3,87 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mod
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
[Flags]
|
||||
public enum ResourceChange
|
||||
{
|
||||
[Flags]
|
||||
public enum ResourceChange
|
||||
None = 0,
|
||||
Files = 1,
|
||||
Meta = 2,
|
||||
}
|
||||
|
||||
// Contains static mod data that should only change on filesystem changes.
|
||||
public class ModResources
|
||||
{
|
||||
public List< FullPath > ModFiles { get; private set; } = new();
|
||||
public List< FullPath > MetaFiles { get; private set; } = new();
|
||||
|
||||
public MetaCollection MetaManipulations { get; private set; } = new();
|
||||
|
||||
|
||||
private void ForceManipulationsUpdate( ModMeta meta, DirectoryInfo basePath )
|
||||
{
|
||||
None = 0,
|
||||
Files = 1,
|
||||
Meta = 2,
|
||||
MetaManipulations.Update( MetaFiles, basePath, meta );
|
||||
MetaManipulations.SaveToFile( MetaCollection.FileName( basePath ) );
|
||||
}
|
||||
|
||||
// Contains static mod data that should only change on filesystem changes.
|
||||
public class ModResources
|
||||
public void SetManipulations( ModMeta meta, DirectoryInfo basePath, bool validate = true )
|
||||
{
|
||||
public List< FileInfo > ModFiles { get; private set; } = new();
|
||||
public List< FileInfo > MetaFiles { get; private set; } = new();
|
||||
|
||||
public MetaCollection MetaManipulations { get; private set; } = new();
|
||||
|
||||
|
||||
private void ForceManipulationsUpdate( ModMeta meta, DirectoryInfo basePath )
|
||||
var newManipulations = MetaCollection.LoadFromFile( MetaCollection.FileName( basePath ) );
|
||||
if( newManipulations == null )
|
||||
{
|
||||
MetaManipulations.Update( MetaFiles, basePath, meta );
|
||||
MetaManipulations.SaveToFile( MetaCollection.FileName( basePath ) );
|
||||
ForceManipulationsUpdate( meta, basePath );
|
||||
}
|
||||
|
||||
public void SetManipulations( ModMeta meta, DirectoryInfo basePath, bool validate = true )
|
||||
else
|
||||
{
|
||||
var newManipulations = MetaCollection.LoadFromFile( MetaCollection.FileName( basePath ) );
|
||||
if( newManipulations == null )
|
||||
MetaManipulations = newManipulations;
|
||||
if( validate && !MetaManipulations.Validate( meta ) )
|
||||
{
|
||||
ForceManipulationsUpdate( meta, basePath );
|
||||
}
|
||||
else
|
||||
{
|
||||
MetaManipulations = newManipulations;
|
||||
if( validate && !MetaManipulations.Validate( meta ) )
|
||||
{
|
||||
ForceManipulationsUpdate( meta, basePath );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the current set of files used by the mod,
|
||||
// returns true if anything changed.
|
||||
public ResourceChange RefreshModFiles( DirectoryInfo basePath )
|
||||
{
|
||||
List< FileInfo > tmpFiles = new( ModFiles.Count );
|
||||
List< FileInfo > tmpMetas = new( MetaFiles.Count );
|
||||
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
|
||||
foreach( var file in basePath.EnumerateDirectories()
|
||||
.SelectMany( dir => dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||
.OrderBy( f => f.FullName ) )
|
||||
{
|
||||
switch( file.Extension.ToLowerInvariant() )
|
||||
{
|
||||
case ".meta":
|
||||
case ".rgsp":
|
||||
tmpMetas.Add( file );
|
||||
break;
|
||||
default:
|
||||
tmpFiles.Add( file );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceChange changes = 0;
|
||||
if( !tmpFiles.Select( f => f.FullName ).SequenceEqual( ModFiles.Select( f => f.FullName ) ) )
|
||||
{
|
||||
ModFiles = tmpFiles;
|
||||
changes |= ResourceChange.Files;
|
||||
}
|
||||
|
||||
if( !tmpMetas.Select( f => f.FullName ).SequenceEqual( MetaFiles.Select( f => f.FullName ) ) )
|
||||
{
|
||||
MetaFiles = tmpMetas;
|
||||
changes |= ResourceChange.Meta;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the current set of files used by the mod,
|
||||
// returns true if anything changed.
|
||||
public ResourceChange RefreshModFiles( DirectoryInfo basePath )
|
||||
{
|
||||
List< FullPath > tmpFiles = new(ModFiles.Count);
|
||||
List< FullPath > tmpMetas = new(MetaFiles.Count);
|
||||
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
|
||||
foreach( var file in basePath.EnumerateDirectories()
|
||||
.SelectMany( dir => dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||
.Select( f => new FullPath( f ) )
|
||||
.OrderBy( f => f.FullName ) )
|
||||
{
|
||||
switch( file.Extension.ToLowerInvariant() )
|
||||
{
|
||||
case ".meta":
|
||||
case ".rgsp":
|
||||
tmpMetas.Add( file );
|
||||
break;
|
||||
default:
|
||||
tmpFiles.Add( file );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceChange changes = 0;
|
||||
if( !tmpFiles.Select( f => f.FullName ).SequenceEqual( ModFiles.Select( f => f.FullName ) ) )
|
||||
{
|
||||
ModFiles = tmpFiles;
|
||||
changes |= ResourceChange.Files;
|
||||
}
|
||||
|
||||
if( !tmpMetas.Select( f => f.FullName ).SequenceEqual( MetaFiles.Select( f => f.FullName ) ) )
|
||||
{
|
||||
MetaFiles = tmpMetas;
|
||||
changes |= ResourceChange.Meta;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ using System.ComponentModel;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Meta;
|
||||
|
|
@ -26,9 +25,10 @@ namespace Penumbra.Mods
|
|||
public readonly Dictionary< string, Mod.Mod > AvailableMods = new();
|
||||
|
||||
private readonly SortedList< string, object? > _changedItems = new();
|
||||
public readonly Dictionary< GamePath, FileInfo > ResolvedFiles = new();
|
||||
public readonly Dictionary< GamePath, FullPath > ResolvedFiles = new();
|
||||
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
|
||||
public readonly HashSet< FileInfo > MissingFiles = new();
|
||||
public readonly HashSet< FullPath > MissingFiles = new();
|
||||
public readonly HashSet< ulong > Checksums = new();
|
||||
public readonly MetaManager MetaManipulations;
|
||||
|
||||
public IReadOnlyDictionary< string, object? > ChangedItems
|
||||
|
|
@ -75,6 +75,9 @@ namespace Penumbra.Mods
|
|||
}
|
||||
|
||||
AddMetaFiles();
|
||||
Checksums.Clear();
|
||||
foreach( var file in ResolvedFiles )
|
||||
Checksums.Add( file.Value.Crc64 );
|
||||
}
|
||||
|
||||
private void SetChangedItems()
|
||||
|
|
@ -128,7 +131,7 @@ namespace Penumbra.Mods
|
|||
AddRemainingFiles( mod );
|
||||
}
|
||||
|
||||
private void AddFile( Mod.Mod mod, GamePath gamePath, FileInfo file )
|
||||
private void AddFile( Mod.Mod mod, GamePath gamePath, FullPath file )
|
||||
{
|
||||
if( !RegisteredFiles.TryGetValue( gamePath, out var oldMod ) )
|
||||
{
|
||||
|
|
@ -145,7 +148,7 @@ namespace Penumbra.Mods
|
|||
}
|
||||
}
|
||||
|
||||
private void AddMissingFile( FileInfo file )
|
||||
private void AddMissingFile( FullPath file )
|
||||
{
|
||||
switch( file.Extension.ToLowerInvariant() )
|
||||
{
|
||||
|
|
@ -162,16 +165,15 @@ namespace Penumbra.Mods
|
|||
{
|
||||
foreach( var (file, paths) in option.OptionFiles )
|
||||
{
|
||||
var fullPath = Path.Combine( mod.Data.BasePath.FullName, file );
|
||||
var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.FullName == fullPath );
|
||||
var fullPath = new FullPath(mod.Data.BasePath, file);
|
||||
var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals(fullPath) );
|
||||
if( idx < 0 )
|
||||
{
|
||||
AddMissingFile( new FileInfo( fullPath ) );
|
||||
AddMissingFile( fullPath );
|
||||
continue;
|
||||
}
|
||||
|
||||
var registeredFile = mod.Data.Resources.ModFiles[ idx ];
|
||||
registeredFile.Refresh();
|
||||
if( !registeredFile.Exists )
|
||||
{
|
||||
AddMissingFile( registeredFile );
|
||||
|
|
@ -230,10 +232,9 @@ namespace Penumbra.Mods
|
|||
}
|
||||
|
||||
var file = mod.Data.Resources.ModFiles[ i ];
|
||||
file.Refresh();
|
||||
if( file.Exists )
|
||||
{
|
||||
AddFile( mod, new GamePath( file, mod.Data.BasePath ), file );
|
||||
AddFile( mod, file.ToGamePath( mod.Data.BasePath ), file );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -350,14 +351,13 @@ namespace Penumbra.Mods
|
|||
}
|
||||
}
|
||||
|
||||
public FileInfo? GetCandidateForGameFile( GamePath gameResourcePath )
|
||||
public FullPath? GetCandidateForGameFile( GamePath gameResourcePath )
|
||||
{
|
||||
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
candidate.Refresh();
|
||||
if( candidate.FullName.Length >= 260 || !candidate.Exists )
|
||||
{
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -347,6 +347,14 @@ namespace Penumbra.Mods
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool CheckCrc64( ulong crc )
|
||||
{
|
||||
if( Collections.ActiveCollection.Cache?.Checksums.Contains( crc ) ?? false )
|
||||
return true;
|
||||
|
||||
return Collections.ForcedCollection.Cache?.Checksums.Contains( crc ) ?? false;
|
||||
}
|
||||
|
||||
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
|
||||
{
|
||||
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
|
||||
|
|
|
|||
|
|
@ -165,7 +165,6 @@ public partial class SettingsInterface
|
|||
manager.TempPath != null ? Directory.Exists( manager.TempPath.FullName ).ToString() : false.ToString() );
|
||||
PrintValue( "Mod Manager Temp Path IsWritable", manager.TempWritable.ToString() );
|
||||
PrintValue( "Resource Loader Enabled", _penumbra.ResourceLoader.IsEnabled.ToString() );
|
||||
PrintValue( "Resource Loader Hacks Enabled", _penumbra.ResourceLoader.HacksEnabled.ToString() );
|
||||
}
|
||||
|
||||
private void DrawDebugTabRedraw()
|
||||
|
|
@ -298,7 +297,6 @@ public partial class SettingsInterface
|
|||
ImGui.TableNextColumn();
|
||||
ImGui.Text( file );
|
||||
ImGui.TableNextColumn();
|
||||
info.CurrentFile?.Refresh();
|
||||
ImGui.Text( info.CurrentFile?.Exists ?? false ? "Exists" : "Missing" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( info.Changed ? "Data Changed" : "Unchanged" );
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace Penumbra.UI
|
|||
}
|
||||
}
|
||||
|
||||
private bool CheckFilters( KeyValuePair< GamePath, FileInfo > kvp )
|
||||
private bool CheckFilters( KeyValuePair< GamePath, FullPath > kvp )
|
||||
{
|
||||
if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Penumbra.UI
|
|||
private Option? _selectedOption;
|
||||
private string _currentGamePaths = "";
|
||||
|
||||
private (FileInfo name, bool selected, uint color, RelPath relName)[]? _fullFilenameList;
|
||||
private (FullPath name, bool selected, uint color, RelPath relName)[]? _fullFilenameList;
|
||||
|
||||
private readonly Selector _selector;
|
||||
private readonly SettingsInterface _base;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public partial class SettingsInterface
|
|||
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem );
|
||||
|
||||
var resourceHandler = *( ResourceManager** )( Dalamud.SigScanner.Module.BaseAddress + 0x1E5B440 );
|
||||
var resourceHandler = *( ResourceManager** )( Dalamud.SigScanner.Module.BaseAddress + 0x1E603C0 );
|
||||
|
||||
if( resourceHandler == null )
|
||||
{
|
||||
|
|
|
|||
85
Penumbra/Util/FullPath.cs
Normal file
85
Penumbra/Util/FullPath.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
||||
public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
||||
{
|
||||
public readonly string FullName;
|
||||
public readonly string InternalName;
|
||||
public readonly ulong Crc64;
|
||||
|
||||
public FullPath( DirectoryInfo baseDir, RelPath relPath )
|
||||
{
|
||||
FullName = Path.Combine( baseDir.FullName, relPath );
|
||||
InternalName = FullName.Replace( '\\', '/' ).ToLowerInvariant().Trim();
|
||||
Crc64 = ComputeCrc64( InternalName );
|
||||
}
|
||||
|
||||
public FullPath( FileInfo file )
|
||||
{
|
||||
FullName = file.FullName;
|
||||
InternalName = FullName.Replace( '\\', '/' ).ToLowerInvariant().Trim();
|
||||
Crc64 = ComputeCrc64( InternalName );
|
||||
}
|
||||
|
||||
public bool Exists
|
||||
=> File.Exists( FullName );
|
||||
|
||||
public string Extension
|
||||
=> Path.GetExtension( FullName );
|
||||
|
||||
public string Name
|
||||
=> Path.GetFileName( FullName );
|
||||
|
||||
public GamePath ToGamePath( DirectoryInfo dir )
|
||||
=> FullName.StartsWith(dir.FullName) ? GamePath.GenerateUnchecked( InternalName[(dir.FullName.Length+1)..]) : GamePath.GenerateUnchecked( string.Empty );
|
||||
|
||||
private static ulong ComputeCrc64( string name )
|
||||
{
|
||||
if( name.Length == 0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var lastSlash = name.LastIndexOf( '/' );
|
||||
if( lastSlash == -1 )
|
||||
{
|
||||
return Lumina.Misc.Crc32.Get( name );
|
||||
}
|
||||
|
||||
var folder = name[ ..lastSlash ];
|
||||
var file = name[ ( lastSlash + 1 ).. ];
|
||||
return ( ( ulong )Lumina.Misc.Crc32.Get( folder ) << 32 ) | Lumina.Misc.Crc32.Get( file );
|
||||
}
|
||||
|
||||
public int CompareTo( object? obj )
|
||||
=> obj switch
|
||||
{
|
||||
FullPath p => string.Compare( InternalName, p.InternalName, StringComparison.InvariantCulture ),
|
||||
FileInfo f => string.Compare( FullName, f.FullName, StringComparison.InvariantCultureIgnoreCase ),
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
public bool Equals( FullPath other )
|
||||
{
|
||||
if( Crc64 != other.Crc64 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( FullName.Length == 0 || other.FullName.Length == 0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return InternalName.Equals( other.InternalName );
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Crc64.GetHashCode();
|
||||
|
||||
public override string ToString()
|
||||
=> FullName;
|
||||
}
|
||||
|
|
@ -3,79 +3,81 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.Util
|
||||
namespace Penumbra.Util;
|
||||
|
||||
public readonly struct RelPath : IComparable
|
||||
{
|
||||
public readonly struct RelPath : IComparable
|
||||
public const int MaxRelPathLength = 256;
|
||||
|
||||
private readonly string _path;
|
||||
|
||||
private RelPath( string path, bool _ )
|
||||
=> _path = path;
|
||||
|
||||
private RelPath( string? path )
|
||||
{
|
||||
public const int MaxRelPathLength = 256;
|
||||
|
||||
private readonly string _path;
|
||||
|
||||
private RelPath( string path, bool _ )
|
||||
=> _path = path;
|
||||
|
||||
private RelPath( string? path )
|
||||
if( path != null && path.Length < MaxRelPathLength )
|
||||
{
|
||||
if( path != null && path.Length < MaxRelPathLength )
|
||||
{
|
||||
_path = Trim( ReplaceSlash( path ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
_path = "";
|
||||
}
|
||||
_path = Trim( ReplaceSlash( path ) );
|
||||
}
|
||||
|
||||
public RelPath( FileInfo file, DirectoryInfo baseDir )
|
||||
=> _path = CheckPre( file, baseDir ) ? Trim( Substring( file, baseDir ) ) : "";
|
||||
|
||||
public RelPath( GamePath gamePath )
|
||||
=> _path = ReplaceSlash( gamePath );
|
||||
|
||||
public GamePath ToGamePath( int skipFolders = 0 )
|
||||
else
|
||||
{
|
||||
string p = this;
|
||||
if( skipFolders > 0 )
|
||||
{
|
||||
p = string.Join( "/", p.Split( '\\' ).Skip( skipFolders ) );
|
||||
return GamePath.GenerateUncheckedLower( p );
|
||||
}
|
||||
|
||||
return GamePath.GenerateUncheckedLower( p.Replace( '\\', '/' ) );
|
||||
_path = "";
|
||||
}
|
||||
|
||||
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir )
|
||||
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxRelPathLength;
|
||||
|
||||
private static string Substring( FileInfo file, DirectoryInfo baseDir )
|
||||
=> file.FullName.Substring( baseDir.FullName.Length );
|
||||
|
||||
private static string ReplaceSlash( string path )
|
||||
=> path.Replace( '/', '\\' );
|
||||
|
||||
private static string Trim( string path )
|
||||
=> path.TrimStart( '\\' );
|
||||
|
||||
public static implicit operator string( RelPath relPath )
|
||||
=> relPath._path;
|
||||
|
||||
public static explicit operator RelPath( string relPath )
|
||||
=> new( relPath );
|
||||
|
||||
public bool Empty
|
||||
=> _path.Length == 0;
|
||||
|
||||
public int CompareTo( object? rhs )
|
||||
{
|
||||
return rhs switch
|
||||
{
|
||||
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
|
||||
RelPath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> _path;
|
||||
}
|
||||
|
||||
public RelPath( FullPath file, DirectoryInfo baseDir )
|
||||
=> _path = CheckPre( file.FullName, baseDir ) ? ReplaceSlash( Trim( Substring( file.FullName, baseDir ) ) ) : string.Empty;
|
||||
|
||||
public RelPath( FileInfo file, DirectoryInfo baseDir )
|
||||
=> _path = CheckPre( file.FullName, baseDir ) ? Trim( Substring( file.FullName, baseDir ) ) : string.Empty;
|
||||
|
||||
public RelPath( GamePath gamePath )
|
||||
=> _path = ReplaceSlash( gamePath );
|
||||
|
||||
public GamePath ToGamePath( int skipFolders = 0 )
|
||||
{
|
||||
string p = this;
|
||||
if( skipFolders > 0 )
|
||||
{
|
||||
p = string.Join( "/", p.Split( '\\' ).Skip( skipFolders ) );
|
||||
return GamePath.GenerateUncheckedLower( p );
|
||||
}
|
||||
|
||||
return GamePath.GenerateUncheckedLower( p.Replace( '\\', '/' ) );
|
||||
}
|
||||
|
||||
private static bool CheckPre( string file, DirectoryInfo baseDir )
|
||||
=> file.StartsWith( baseDir.FullName ) && file.Length < MaxRelPathLength;
|
||||
|
||||
private static string Substring( string file, DirectoryInfo baseDir )
|
||||
=> file.Substring( baseDir.FullName.Length );
|
||||
|
||||
private static string ReplaceSlash( string path )
|
||||
=> path.Replace( '/', '\\' );
|
||||
|
||||
private static string Trim( string path )
|
||||
=> path.TrimStart( '\\' );
|
||||
|
||||
public static implicit operator string( RelPath relPath )
|
||||
=> relPath._path;
|
||||
|
||||
public static explicit operator RelPath( string relPath )
|
||||
=> new(relPath);
|
||||
|
||||
public bool Empty
|
||||
=> _path.Length == 0;
|
||||
|
||||
public int CompareTo( object? rhs )
|
||||
{
|
||||
return rhs switch
|
||||
{
|
||||
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
|
||||
RelPath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> _path;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue