More sophisticated fix against E4S crashes with working mods in E4S.

This commit is contained in:
Ottermandias 2022-01-04 00:30:18 +01:00
parent a1f02975cb
commit f601812666
13 changed files with 273 additions and 210 deletions

View file

@ -22,7 +22,7 @@ namespace Penumbra.GameData.Util
} }
else else
{ {
_path = ""; _path = string.Empty;
} }
} }

View file

@ -5,7 +5,6 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Logging; using Dalamud.Logging;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Structs; using Penumbra.Structs;
@ -19,10 +18,10 @@ public class ResourceLoader : IDisposable
public Penumbra Penumbra { get; set; } public Penumbra Penumbra { get; set; }
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
public bool HacksEnabled { get; set; }
public Crc32 Crc32 { get; } public Crc32 Crc32 { get; }
public static readonly IntPtr CustomFileFlag = new(0xDEADBEEF);
// Delegate prototypes // Delegate prototypes
[UnmanagedFunctionPointer( CallingConvention.ThisCall )] [UnmanagedFunctionPointer( CallingConvention.ThisCall )]
@ -40,7 +39,7 @@ public class ResourceLoader : IDisposable
, uint* pResourceHash, char* pPath, void* pUnknown, bool isUnknown ); , uint* pResourceHash, char* pPath, void* pUnknown, bool isUnknown );
[UnmanagedFunctionPointer( CallingConvention.ThisCall )] [UnmanagedFunctionPointer( CallingConvention.ThisCall )]
public delegate bool CheckFileStatePrototype( IntPtr unk1, ulong unk2 ); public delegate IntPtr CheckFileStatePrototype( IntPtr unk1, ulong crc );
[UnmanagedFunctionPointer( CallingConvention.ThisCall )] [UnmanagedFunctionPointer( CallingConvention.ThisCall )]
public delegate byte LoadTexFileExternPrototype( IntPtr resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr unk4 ); public delegate byte LoadTexFileExternPrototype( IntPtr resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr unk4 );
@ -64,7 +63,6 @@ public class ResourceLoader : IDisposable
// Unmanaged functions // Unmanaged functions
public ReadFilePrototype? ReadFile { get; private set; } public ReadFilePrototype? ReadFile { get; private set; }
public CheckFileStatePrototype? CheckFileState { get; private set; }
public LoadTexFileLocalPrototype? LoadTexFileLocal { get; private set; } public LoadTexFileLocalPrototype? LoadTexFileLocal { get; private set; }
public LoadMdlFileLocalPrototype? LoadMdlFileLocal { get; private set; } public LoadMdlFileLocalPrototype? LoadMdlFileLocal { get; private set; }
@ -128,37 +126,21 @@ public class ResourceLoader : IDisposable
LoadMdlFileExternHook = new Hook< LoadMdlFileExternPrototype >( loadMdlFileExternAddress, LoadMdlFileExternDetour ); 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 modManager = Service< ModManager >.Get();
var bad = territory?.Unknown40 ?? false; return modManager.CheckCrc64( crc64 ) ? CustomFileFlag : CheckFileStateHook!.Original( ptr, crc64 );
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; 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 static bool CheckFileStateDetour( IntPtr _, ulong _2 ) private byte LoadMdlFileExternDetour( IntPtr resourceHandle, IntPtr unk1, bool unk2, IntPtr ptr )
=> true; => ptr.Equals( CustomFileFlag )
? LoadMdlFileLocal!.Invoke( resourceHandle, unk1, unk2 )
private byte LoadTexFileExternDetour( IntPtr resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr _ ) : LoadMdlFileExternHook!.Original( resourceHandle, unk1, unk2, ptr );
=> LoadTexFileLocal!.Invoke( resourceHandle, unk1, unk2, unk3 );
private byte LoadMdlFileExternDetour( IntPtr resourceHandle, IntPtr unk1, bool unk2, IntPtr _ )
=> LoadMdlFileLocal!.Invoke( resourceHandle, unk1, unk2 );
private unsafe void* GetResourceSyncHandler( private unsafe void* GetResourceSyncHandler(
IntPtr pFileManager, IntPtr pFileManager,
@ -223,11 +205,6 @@ public class ResourceLoader : IDisposable
bool isUnknown bool isUnknown
) )
{ {
if( CheckForTerritory() )
{
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
}
string file; string file;
var modManager = Service< ModManager >.Get(); 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 ) 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 ) if( ReadFile == null || pFileDesc == null || pFileDesc->ResourceHandle == null )
{ {
PluginLog.Error( "THIS SHOULD NOT HAPPEN" ); PluginLog.Error( "THIS SHOULD NOT HAPPEN" );
@ -340,7 +312,6 @@ public class ResourceLoader : IDisposable
LoadMdlFileExternHook.Enable(); LoadMdlFileExternHook.Enable();
IsEnabled = true; IsEnabled = true;
HacksEnabled = true;
} }
public void Disable() public void Disable()
@ -357,7 +328,6 @@ public class ResourceLoader : IDisposable
LoadTexFileExternHook?.Disable(); LoadTexFileExternHook?.Disable();
LoadMdlFileExternHook?.Disable(); LoadMdlFileExternHook?.Disable();
IsEnabled = false; IsEnabled = false;
HacksEnabled = false;
} }
public void Dispose() public void Dispose()

View file

@ -147,7 +147,7 @@ namespace Penumbra.Meta
// Update the whole meta collection by reading all TexTools .meta files in a mod directory anew, // Update the whole meta collection by reading all TexTools .meta files in a mod directory anew,
// combining them with the given ModMeta. // 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(); DefaultData.Clear();
GroupData.Clear(); GroupData.Clear();

View file

@ -17,7 +17,7 @@ namespace Penumbra.Meta
{ {
public readonly object Data; public readonly object Data;
public bool Changed; public bool Changed;
public FileInfo? CurrentFile; public FullPath? CurrentFile;
public FileInformation( object data ) public FileInformation( object data )
=> Data = data; => Data = data;
@ -35,7 +35,7 @@ namespace Penumbra.Meta
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
DisposeFile( CurrentFile ); DisposeFile( CurrentFile );
CurrentFile = TempFile.WriteNew( dir, data, $"_{originalPath.Filename()}" ); CurrentFile = new FullPath(TempFile.WriteNew( dir, data, $"_{originalPath.Filename()}" ));
Changed = false; Changed = false;
} }
} }
@ -45,7 +45,7 @@ namespace Penumbra.Meta
private readonly MetaDefaults _default; private readonly MetaDefaults _default;
private readonly DirectoryInfo _dir; private readonly DirectoryInfo _dir;
private readonly ResidentResources _resourceManagement; 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< MetaManipulation, Mod.Mod > _currentManipulations = new();
private readonly Dictionary< GamePath, FileInformation > _currentFiles = new(); private readonly Dictionary< GamePath, FileInformation > _currentFiles = new();
@ -53,9 +53,9 @@ namespace Penumbra.Meta
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
=> _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) ); => _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) );
public IEnumerable< (GamePath, FileInfo) > Files public IEnumerable< (GamePath, FullPath) > Files
=> _currentFiles.Where( kvp => kvp.Value.CurrentFile != null ) => _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 public int Count
=> _currentManipulations.Count; => _currentManipulations.Count;
@ -63,9 +63,8 @@ namespace Penumbra.Meta
public bool TryGetValue( MetaManipulation manip, out Mod.Mod mod ) public bool TryGetValue( MetaManipulation manip, out Mod.Mod mod )
=> _currentManipulations.TryGetValue( manip, out mod! ); => _currentManipulations.TryGetValue( manip, out mod! );
private static void DisposeFile( FileInfo? file ) private static void DisposeFile( FullPath? file )
{ {
file?.Refresh();
if( !( file?.Exists ?? false ) ) if( !( file?.Exists ?? false ) )
{ {
return; return;
@ -73,11 +72,11 @@ namespace Penumbra.Meta
try try
{ {
file.Delete(); File.Delete( file.Value.FullName );
} }
catch( Exception e ) 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() private void ClearDirectory()
=> ClearDirectory( _dir ); => ClearDirectory( _dir );
public MetaManager( string name, Dictionary< GamePath, FileInfo > resolvedFiles, DirectoryInfo tempDir ) public MetaManager( string name, Dictionary< GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir )
{ {
_resolvedFiles = resolvedFiles; _resolvedFiles = resolvedFiles;
_default = Service< MetaDefaults >.Get(); _default = Service< MetaDefaults >.Get();
@ -139,7 +138,7 @@ namespace Penumbra.Meta
foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) ) foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) )
{ {
kvp.Value.Write( _dir, kvp.Key ); kvp.Value.Write( _dir, kvp.Key );
_resolvedFiles[ kvp.Key ] = kvp.Value.CurrentFile!; _resolvedFiles[ kvp.Key ] = kvp.Value.CurrentFile!.Value;
} }
} }

View file

@ -3,22 +3,23 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Util;
namespace Penumbra.Mod namespace Penumbra.Mod;
[Flags]
public enum ResourceChange
{ {
[Flags]
public enum ResourceChange
{
None = 0, None = 0,
Files = 1, Files = 1,
Meta = 2, Meta = 2,
} }
// Contains static mod data that should only change on filesystem changes. // Contains static mod data that should only change on filesystem changes.
public class ModResources public class ModResources
{ {
public List< FileInfo > ModFiles { get; private set; } = new(); public List< FullPath > ModFiles { get; private set; } = new();
public List< FileInfo > MetaFiles { get; private set; } = new(); public List< FullPath > MetaFiles { get; private set; } = new();
public MetaCollection MetaManipulations { get; private set; } = new(); public MetaCollection MetaManipulations { get; private set; } = new();
@ -50,11 +51,12 @@ namespace Penumbra.Mod
// returns true if anything changed. // returns true if anything changed.
public ResourceChange RefreshModFiles( DirectoryInfo basePath ) public ResourceChange RefreshModFiles( DirectoryInfo basePath )
{ {
List< FileInfo > tmpFiles = new( ModFiles.Count ); List< FullPath > tmpFiles = new(ModFiles.Count);
List< FileInfo > tmpMetas = new( MetaFiles.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 // 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() foreach( var file in basePath.EnumerateDirectories()
.SelectMany( dir => dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) .SelectMany( dir => dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
.Select( f => new FullPath( f ) )
.OrderBy( f => f.FullName ) ) .OrderBy( f => f.FullName ) )
{ {
switch( file.Extension.ToLowerInvariant() ) switch( file.Extension.ToLowerInvariant() )
@ -84,5 +86,4 @@ namespace Penumbra.Mod
return changes; return changes;
} }
}
} }

View file

@ -5,7 +5,6 @@ using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
@ -26,9 +25,10 @@ namespace Penumbra.Mods
public readonly Dictionary< string, Mod.Mod > AvailableMods = new(); public readonly Dictionary< string, Mod.Mod > AvailableMods = new();
private readonly SortedList< string, object? > _changedItems = 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 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 readonly MetaManager MetaManipulations;
public IReadOnlyDictionary< string, object? > ChangedItems public IReadOnlyDictionary< string, object? > ChangedItems
@ -75,6 +75,9 @@ namespace Penumbra.Mods
} }
AddMetaFiles(); AddMetaFiles();
Checksums.Clear();
foreach( var file in ResolvedFiles )
Checksums.Add( file.Value.Crc64 );
} }
private void SetChangedItems() private void SetChangedItems()
@ -128,7 +131,7 @@ namespace Penumbra.Mods
AddRemainingFiles( mod ); 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 ) ) 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() ) switch( file.Extension.ToLowerInvariant() )
{ {
@ -162,16 +165,15 @@ namespace Penumbra.Mods
{ {
foreach( var (file, paths) in option.OptionFiles ) foreach( var (file, paths) in option.OptionFiles )
{ {
var fullPath = Path.Combine( mod.Data.BasePath.FullName, file ); var fullPath = new FullPath(mod.Data.BasePath, file);
var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.FullName == fullPath ); var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals(fullPath) );
if( idx < 0 ) if( idx < 0 )
{ {
AddMissingFile( new FileInfo( fullPath ) ); AddMissingFile( fullPath );
continue; continue;
} }
var registeredFile = mod.Data.Resources.ModFiles[ idx ]; var registeredFile = mod.Data.Resources.ModFiles[ idx ];
registeredFile.Refresh();
if( !registeredFile.Exists ) if( !registeredFile.Exists )
{ {
AddMissingFile( registeredFile ); AddMissingFile( registeredFile );
@ -230,10 +232,9 @@ namespace Penumbra.Mods
} }
var file = mod.Data.Resources.ModFiles[ i ]; var file = mod.Data.Resources.ModFiles[ i ];
file.Refresh();
if( file.Exists ) if( file.Exists )
{ {
AddFile( mod, new GamePath( file, mod.Data.BasePath ), file ); AddFile( mod, file.ToGamePath( mod.Data.BasePath ), file );
} }
else 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 ) ) if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
{ {
return null; return null;
} }
candidate.Refresh();
if( candidate.FullName.Length >= 260 || !candidate.Exists ) if( candidate.FullName.Length >= 260 || !candidate.Exists )
{ {
return null; return null;

View file

@ -347,6 +347,14 @@ namespace Penumbra.Mods
return true; 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 ) public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
{ {
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath ); var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );

View file

@ -165,7 +165,6 @@ public partial class SettingsInterface
manager.TempPath != null ? Directory.Exists( manager.TempPath.FullName ).ToString() : false.ToString() ); manager.TempPath != null ? Directory.Exists( manager.TempPath.FullName ).ToString() : false.ToString() );
PrintValue( "Mod Manager Temp Path IsWritable", manager.TempWritable.ToString() ); PrintValue( "Mod Manager Temp Path IsWritable", manager.TempWritable.ToString() );
PrintValue( "Resource Loader Enabled", _penumbra.ResourceLoader.IsEnabled.ToString() ); PrintValue( "Resource Loader Enabled", _penumbra.ResourceLoader.IsEnabled.ToString() );
PrintValue( "Resource Loader Hacks Enabled", _penumbra.ResourceLoader.HacksEnabled.ToString() );
} }
private void DrawDebugTabRedraw() private void DrawDebugTabRedraw()
@ -298,7 +297,6 @@ public partial class SettingsInterface
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text( file ); ImGui.Text( file );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
info.CurrentFile?.Refresh();
ImGui.Text( info.CurrentFile?.Exists ?? false ? "Exists" : "Missing" ); ImGui.Text( info.CurrentFile?.Exists ?? false ? "Exists" : "Missing" );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text( info.Changed ? "Data Changed" : "Unchanged" ); ImGui.Text( info.Changed ? "Data Changed" : "Unchanged" );

View file

@ -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 ) ) if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) )
{ {

View file

@ -55,7 +55,7 @@ namespace Penumbra.UI
private Option? _selectedOption; private Option? _selectedOption;
private string _currentGamePaths = ""; 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 Selector _selector;
private readonly SettingsInterface _base; private readonly SettingsInterface _base;

View file

@ -94,7 +94,7 @@ public partial class SettingsInterface
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); 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 ) if( resourceHandler == null )
{ {

85
Penumbra/Util/FullPath.cs Normal file
View 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;
}

View file

@ -3,10 +3,10 @@ using System.IO;
using System.Linq; using System.Linq;
using Penumbra.GameData.Util; 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; public const int MaxRelPathLength = 256;
private readonly string _path; private readonly string _path;
@ -26,8 +26,11 @@ namespace Penumbra.Util
} }
} }
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 ) public RelPath( FileInfo file, DirectoryInfo baseDir )
=> _path = CheckPre( file, baseDir ) ? Trim( Substring( file, baseDir ) ) : ""; => _path = CheckPre( file.FullName, baseDir ) ? Trim( Substring( file.FullName, baseDir ) ) : string.Empty;
public RelPath( GamePath gamePath ) public RelPath( GamePath gamePath )
=> _path = ReplaceSlash( gamePath ); => _path = ReplaceSlash( gamePath );
@ -44,11 +47,11 @@ namespace Penumbra.Util
return GamePath.GenerateUncheckedLower( p.Replace( '\\', '/' ) ); return GamePath.GenerateUncheckedLower( p.Replace( '\\', '/' ) );
} }
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir ) private static bool CheckPre( string file, DirectoryInfo baseDir )
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxRelPathLength; => file.StartsWith( baseDir.FullName ) && file.Length < MaxRelPathLength;
private static string Substring( FileInfo file, DirectoryInfo baseDir ) private static string Substring( string file, DirectoryInfo baseDir )
=> file.FullName.Substring( baseDir.FullName.Length ); => file.Substring( baseDir.FullName.Length );
private static string ReplaceSlash( string path ) private static string ReplaceSlash( string path )
=> path.Replace( '/', '\\' ); => path.Replace( '/', '\\' );
@ -60,7 +63,7 @@ namespace Penumbra.Util
=> relPath._path; => relPath._path;
public static explicit operator RelPath( string relPath ) public static explicit operator RelPath( string relPath )
=> new( relPath ); => new(relPath);
public bool Empty public bool Empty
=> _path.Length == 0; => _path.Length == 0;
@ -77,5 +80,4 @@ namespace Penumbra.Util
public override string ToString() public override string ToString()
=> _path; => _path;
}
} }