mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
tmp
This commit is contained in:
parent
e15d844d4b
commit
0e8f839471
5 changed files with 812 additions and 140 deletions
63
Penumbra.GameData/Util/Functions.cs
Normal file
63
Penumbra.GameData/Util/Functions.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Penumbra.GameData.Util;
|
||||||
|
|
||||||
|
public static class Functions
|
||||||
|
{
|
||||||
|
public 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 static ulong ComputeCrc64( ReadOnlySpan< byte > name )
|
||||||
|
{
|
||||||
|
if( name.Length == 0 )
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastSlash = name.LastIndexOf( ( byte )'/' );
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport( "msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
||||||
|
private static extern unsafe IntPtr memcpy( byte* dest, byte* src, int count );
|
||||||
|
|
||||||
|
public static unsafe void MemCpyUnchecked( byte* dest, byte* src, int count )
|
||||||
|
=> memcpy( dest, src, count );
|
||||||
|
|
||||||
|
|
||||||
|
[DllImport( "msvcrt.dll", EntryPoint = "memcmp", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
||||||
|
private static extern unsafe int memcmp( byte* b1, byte* b2, int count );
|
||||||
|
|
||||||
|
public static unsafe int MemCmpUnchecked( byte* ptr1, byte* ptr2, int count )
|
||||||
|
=> memcmp( ptr1, ptr2, count );
|
||||||
|
|
||||||
|
|
||||||
|
[DllImport( "msvcrt.dll", EntryPoint = "_memicmp", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
||||||
|
private static extern unsafe int memicmp( byte* b1, byte* b2, int count );
|
||||||
|
|
||||||
|
public static unsafe int MemCmpCaseInsensitiveUnchecked( byte* ptr1, byte* ptr2, int count )
|
||||||
|
=> memicmp( ptr1, ptr2, count );
|
||||||
|
}
|
||||||
|
|
@ -1,105 +1,620 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using Dalamud.Utility;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Util
|
namespace Penumbra.GameData.Util;
|
||||||
|
|
||||||
|
public static unsafe class ByteStringFunctions
|
||||||
{
|
{
|
||||||
public readonly struct GamePath : IComparable
|
public class NullTerminator
|
||||||
{
|
{
|
||||||
public const int MaxGamePathLength = 256;
|
public readonly byte* NullBytePtr;
|
||||||
|
|
||||||
private readonly string _path;
|
public NullTerminator()
|
||||||
|
|
||||||
private GamePath( string path, bool _ )
|
|
||||||
=> _path = path;
|
|
||||||
|
|
||||||
public GamePath( string? path )
|
|
||||||
{
|
{
|
||||||
if( path != null && path.Length < MaxGamePathLength )
|
NullBytePtr = ( byte* )Marshal.AllocHGlobal( 1 );
|
||||||
{
|
*NullBytePtr = 0;
|
||||||
_path = Lower( Trim( ReplaceSlash( path ) ) );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_path = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GamePath( FileInfo file, DirectoryInfo baseDir )
|
~NullTerminator()
|
||||||
=> _path = CheckPre( file, baseDir ) ? Lower( Trim( ReplaceSlash( Substring( file, baseDir ) ) ) ) : "";
|
=> Marshal.FreeHGlobal( ( IntPtr )NullBytePtr );
|
||||||
|
|
||||||
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir )
|
|
||||||
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxGamePathLength;
|
|
||||||
|
|
||||||
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( '/' );
|
|
||||||
|
|
||||||
private static string Lower( string path )
|
|
||||||
=> path.ToLowerInvariant();
|
|
||||||
|
|
||||||
public static GamePath GenerateUnchecked( string path )
|
|
||||||
=> new( path, true );
|
|
||||||
|
|
||||||
public static GamePath GenerateUncheckedLower( string path )
|
|
||||||
=> new( Lower( path ), true );
|
|
||||||
|
|
||||||
public static implicit operator string( GamePath gamePath )
|
|
||||||
=> gamePath._path;
|
|
||||||
|
|
||||||
public static explicit operator GamePath( string gamePath )
|
|
||||||
=> new( gamePath );
|
|
||||||
|
|
||||||
public bool Empty
|
|
||||||
=> _path.Length == 0;
|
|
||||||
|
|
||||||
public string Filename()
|
|
||||||
{
|
|
||||||
var idx = _path.LastIndexOf( "/", StringComparison.Ordinal );
|
|
||||||
return idx == -1 ? _path : idx == _path.Length - 1 ? "" : _path.Substring( idx + 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo( object? rhs )
|
|
||||||
{
|
|
||||||
return rhs switch
|
|
||||||
{
|
|
||||||
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
|
|
||||||
GamePath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
|
||||||
_ => -1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
=> _path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GamePathConverter : JsonConverter
|
private static readonly byte[] LowerCaseBytes = Enumerable.Range( 0, 256 )
|
||||||
{
|
.Select( i => ( byte )char.ToLowerInvariant( ( char )i ) )
|
||||||
public override bool CanConvert( Type objectType )
|
.ToArray();
|
||||||
=> objectType == typeof( GamePath );
|
|
||||||
|
|
||||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
public static byte* FromString( string s, out int length )
|
||||||
|
{
|
||||||
|
length = Encoding.UTF8.GetByteCount( s );
|
||||||
|
var path = ( byte* )Marshal.AllocHGlobal( length + 1 );
|
||||||
|
fixed( char* ptr = s )
|
||||||
{
|
{
|
||||||
var token = JToken.Load( reader );
|
Encoding.UTF8.GetBytes( ptr, length, path, length + 1 );
|
||||||
return token.ToObject< GamePath >();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanWrite
|
path[ length ] = 0;
|
||||||
=> true;
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
public static byte* CopyPath( byte* path, int length )
|
||||||
|
{
|
||||||
|
var ret = ( byte* )Marshal.AllocHGlobal( length + 1 );
|
||||||
|
Functions.MemCpyUnchecked( ret, path, length );
|
||||||
|
ret[ length ] = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CheckLength( byte* path )
|
||||||
|
{
|
||||||
|
var end = path + int.MaxValue;
|
||||||
|
for( var it = path; it < end; ++it )
|
||||||
{
|
{
|
||||||
if( value != null )
|
if( *it == 0 )
|
||||||
{
|
{
|
||||||
var v = ( GamePath )value;
|
return ( int )( it - path );
|
||||||
serializer.Serialize( writer, v.ToString() );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new ArgumentOutOfRangeException( "Null-terminated path too long" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Compare( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
||||||
|
{
|
||||||
|
if( lhsLength == rhsLength )
|
||||||
|
{
|
||||||
|
return lhs == rhs ? 0 : Functions.MemCmpUnchecked( lhs, rhs, rhsLength );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( lhsLength < rhsLength )
|
||||||
|
{
|
||||||
|
var cmp = Functions.MemCmpUnchecked( lhs, rhs, lhsLength );
|
||||||
|
return cmp != 0 ? cmp : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmp2 = Functions.MemCmpUnchecked( lhs, rhs, rhsLength );
|
||||||
|
return cmp2 != 0 ? cmp2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Compare( byte* lhs, int lhsLength, byte* rhs )
|
||||||
|
{
|
||||||
|
var end = lhs + lhsLength;
|
||||||
|
for( var tmp = lhs; tmp < end; ++tmp, ++rhs )
|
||||||
|
{
|
||||||
|
if( *rhs == 0 )
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var diff = *tmp - *rhs;
|
||||||
|
if( diff != 0 )
|
||||||
|
{
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Compare( byte* lhs, byte* rhs, int maxLength = int.MaxValue )
|
||||||
|
{
|
||||||
|
var end = lhs + maxLength;
|
||||||
|
for( var tmp = lhs; tmp < end; ++tmp, ++rhs )
|
||||||
|
{
|
||||||
|
if( *lhs == 0 )
|
||||||
|
{
|
||||||
|
return *rhs == 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( *rhs == 0 )
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var diff = *tmp - *rhs;
|
||||||
|
if( diff != 0 )
|
||||||
|
{
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool Equals( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
||||||
|
{
|
||||||
|
if( lhsLength != rhsLength )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( lhs == rhs || lhsLength == 0 )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions.MemCmpUnchecked( lhs, rhs, lhsLength ) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Equal( byte* lhs, int lhsLength, byte* rhs )
|
||||||
|
=> Compare( lhs, lhsLength, rhs ) == 0;
|
||||||
|
|
||||||
|
private static bool Equal( byte* lhs, byte* rhs, int maxLength = int.MaxValue )
|
||||||
|
=> Compare( lhs, rhs, maxLength ) == 0;
|
||||||
|
|
||||||
|
|
||||||
|
public static int AsciiCaselessCompare( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
||||||
|
{
|
||||||
|
if( lhsLength == rhsLength )
|
||||||
|
{
|
||||||
|
return lhs == rhs ? 0 : Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, rhsLength );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( lhsLength < rhsLength )
|
||||||
|
{
|
||||||
|
var cmp = Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, lhsLength );
|
||||||
|
return cmp != 0 ? cmp : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmp2 = Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, rhsLength );
|
||||||
|
return cmp2 != 0 ? cmp2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AsciiCaselessEquals( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
||||||
|
{
|
||||||
|
if( lhsLength != rhsLength )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( lhs == rhs || lhsLength == 0 )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, lhsLength ) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AsciiToLowerInPlace( byte* path, int length )
|
||||||
|
{
|
||||||
|
for( var i = 0; i < length; ++i )
|
||||||
|
{
|
||||||
|
path[ i ] = LowerCaseBytes[ path[ i ] ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte* AsciiToLower( byte* path, int length )
|
||||||
|
{
|
||||||
|
var ptr = ( byte* )Marshal.AllocHGlobal( length + 1 );
|
||||||
|
ptr[ length ] = 0;
|
||||||
|
for( var i = 0; i < length; ++i )
|
||||||
|
{
|
||||||
|
ptr[ i ] = LowerCaseBytes[ path[ i ] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsLowerCase( byte* path, int length )
|
||||||
|
{
|
||||||
|
for( var i = 0; i < length; ++i )
|
||||||
|
{
|
||||||
|
if( path[ i ] != LowerCaseBytes[ path[ i ] ] )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe class AsciiString : IEnumerable< byte >, IEquatable< AsciiString >, IComparable< AsciiString >
|
||||||
|
{
|
||||||
|
private static readonly ByteStringFunctions.NullTerminator Null = new();
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum Flags : byte
|
||||||
|
{
|
||||||
|
IsOwned = 0x01,
|
||||||
|
IsNullTerminated = 0x02,
|
||||||
|
LowerCaseChecked = 0x04,
|
||||||
|
IsLowerCase = 0x08,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly IntPtr Path;
|
||||||
|
public readonly ulong Crc64;
|
||||||
|
public readonly int Length;
|
||||||
|
private Flags _flags;
|
||||||
|
|
||||||
|
public bool IsNullTerminated
|
||||||
|
{
|
||||||
|
get => _flags.HasFlag( Flags.IsNullTerminated );
|
||||||
|
init => _flags = value ? _flags | Flags.IsNullTerminated : _flags & ~ Flags.IsNullTerminated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOwned
|
||||||
|
{
|
||||||
|
get => _flags.HasFlag( Flags.IsOwned );
|
||||||
|
init => _flags = value ? _flags | Flags.IsOwned : _flags & ~Flags.IsOwned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLowerCase
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if( _flags.HasFlag( Flags.LowerCaseChecked ) )
|
||||||
|
{
|
||||||
|
return _flags.HasFlag( Flags.IsLowerCase );
|
||||||
|
}
|
||||||
|
|
||||||
|
_flags |= Flags.LowerCaseChecked;
|
||||||
|
var ret = ByteStringFunctions.IsLowerCase( Ptr, Length );
|
||||||
|
if( ret )
|
||||||
|
{
|
||||||
|
_flags |= Flags.IsLowerCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty
|
||||||
|
=> Length == 0;
|
||||||
|
|
||||||
|
public AsciiString()
|
||||||
|
{
|
||||||
|
Path = ( IntPtr )Null.NullBytePtr;
|
||||||
|
Length = 0;
|
||||||
|
IsNullTerminated = true;
|
||||||
|
IsOwned = false;
|
||||||
|
_flags |= Flags.LowerCaseChecked | Flags.IsLowerCase;
|
||||||
|
Crc64 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool FromString( string? path, out AsciiString ret, bool toLower = false )
|
||||||
|
{
|
||||||
|
if( string.IsNullOrEmpty( path ) )
|
||||||
|
{
|
||||||
|
ret = Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = ByteStringFunctions.FromString( path, out var l );
|
||||||
|
if( l != path.Length )
|
||||||
|
{
|
||||||
|
ret = Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( toLower )
|
||||||
|
{
|
||||||
|
ByteStringFunctions.AsciiToLowerInPlace( p, l );
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = new AsciiString( p, l, true, true, toLower ? true : null );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AsciiString FromStringUnchecked( string? path, bool? isLower )
|
||||||
|
{
|
||||||
|
if( string.IsNullOrEmpty( path ) )
|
||||||
|
{
|
||||||
|
return Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = ByteStringFunctions.FromString( path, out var l );
|
||||||
|
return new AsciiString( p, l, true, true, isLower );
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsciiString( byte* path )
|
||||||
|
: this( path, ByteStringFunctions.CheckLength( path ), true, false )
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected AsciiString( byte* path, int length, bool isNullTerminated, bool isOwned, bool? isLower = null )
|
||||||
|
{
|
||||||
|
Length = length;
|
||||||
|
Path = ( IntPtr )path;
|
||||||
|
IsNullTerminated = isNullTerminated;
|
||||||
|
IsOwned = isOwned;
|
||||||
|
Crc64 = Functions.ComputeCrc64( Span );
|
||||||
|
if( isLower != null )
|
||||||
|
{
|
||||||
|
_flags |= Flags.LowerCaseChecked;
|
||||||
|
if( isLower.Value )
|
||||||
|
{
|
||||||
|
_flags |= Flags.IsLowerCase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan< byte > Span
|
||||||
|
=> new(( void* )Path, Length);
|
||||||
|
|
||||||
|
private byte* Ptr
|
||||||
|
=> ( byte* )Path;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> Encoding.ASCII.GetString( Ptr, Length );
|
||||||
|
|
||||||
|
public IEnumerator< byte > GetEnumerator()
|
||||||
|
{
|
||||||
|
for( var i = 0; i < Length; ++i )
|
||||||
|
{
|
||||||
|
yield return Span[ i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~AsciiString()
|
||||||
|
{
|
||||||
|
if( IsOwned )
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal( Path );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals( AsciiString? other )
|
||||||
|
{
|
||||||
|
if( ReferenceEquals( null, other ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ReferenceEquals( this, other ) )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Crc64 == other.Crc64 && ByteStringFunctions.Equals( Ptr, Length, other.Ptr, other.Length );
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> Crc64.GetHashCode();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> GetEnumerator();
|
||||||
|
|
||||||
|
public int CompareTo( AsciiString? other )
|
||||||
|
{
|
||||||
|
if( ReferenceEquals( this, other ) )
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ReferenceEquals( null, other ) )
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteStringFunctions.Compare( Ptr, Length, other.Ptr, other.Length );
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? IsLowerInternal
|
||||||
|
=> _flags.HasFlag( Flags.LowerCaseChecked ) ? _flags.HasFlag( Flags.IsLowerCase ) : null;
|
||||||
|
|
||||||
|
public AsciiString Clone()
|
||||||
|
=> new(ByteStringFunctions.CopyPath( Ptr, Length ), Length, true, true, IsLowerInternal);
|
||||||
|
|
||||||
|
public AsciiString Substring( int from )
|
||||||
|
=> from < Length
|
||||||
|
? new AsciiString( Ptr + from, Length - from, IsNullTerminated, false, IsLowerInternal )
|
||||||
|
: Empty;
|
||||||
|
|
||||||
|
public AsciiString Substring( int from, int length )
|
||||||
|
{
|
||||||
|
Debug.Assert( from >= 0 );
|
||||||
|
if( from >= Length )
|
||||||
|
{
|
||||||
|
return Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxLength = Length - from;
|
||||||
|
return length < maxLength
|
||||||
|
? new AsciiString( Ptr + from, length, false, false, IsLowerInternal )
|
||||||
|
: new AsciiString( Ptr + from, maxLength, true, false, IsLowerInternal );
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf( byte b, int from = 0 )
|
||||||
|
{
|
||||||
|
var end = Ptr + Length;
|
||||||
|
for( var tmp = Ptr + from; tmp < end; ++tmp )
|
||||||
|
{
|
||||||
|
if( *tmp == b )
|
||||||
|
{
|
||||||
|
return ( int )( tmp - Ptr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int LastIndexOf( byte b, int to = 0 )
|
||||||
|
{
|
||||||
|
var end = Ptr + to;
|
||||||
|
for( var tmp = Ptr + Length - 1; tmp >= end; --tmp )
|
||||||
|
{
|
||||||
|
if( *tmp == b )
|
||||||
|
{
|
||||||
|
return ( int )( tmp - Ptr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly AsciiString Empty = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct NewGamePath
|
||||||
|
{
|
||||||
|
public const int MaxGamePathLength = 256;
|
||||||
|
|
||||||
|
private readonly AsciiString _string;
|
||||||
|
|
||||||
|
private NewGamePath( AsciiString s )
|
||||||
|
=> _string = s;
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly NewGamePath Empty = new(AsciiString.Empty);
|
||||||
|
|
||||||
|
public static NewGamePath FromStringUnchecked( string? s, bool? isLower )
|
||||||
|
=> new(AsciiString.FromStringUnchecked( s, isLower ));
|
||||||
|
|
||||||
|
public static bool FromString( string? s, out NewGamePath path, bool toLower = false )
|
||||||
|
{
|
||||||
|
path = Empty;
|
||||||
|
if( s.IsNullOrEmpty() )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var substring = s.Replace( '\\', '/' );
|
||||||
|
substring.TrimStart( '/' );
|
||||||
|
if( substring.Length > MaxGamePathLength )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( substring.Length == 0 )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !AsciiString.FromString( substring, out var ascii, toLower ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = new NewGamePath( ascii );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out NewGamePath path, bool toLower = false )
|
||||||
|
{
|
||||||
|
path = Empty;
|
||||||
|
if( !file.FullName.StartsWith( baseDir.FullName ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var substring = file.FullName[ baseDir.FullName.Length.. ];
|
||||||
|
return FromString( substring, out path, toLower );
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsciiString Filename()
|
||||||
|
{
|
||||||
|
var idx = _string.LastIndexOf( ( byte )'/' );
|
||||||
|
return idx == -1 ? _string : _string.Substring( idx + 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> _string.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct GamePath : IComparable
|
||||||
|
{
|
||||||
|
public const int MaxGamePathLength = 256;
|
||||||
|
|
||||||
|
private readonly string _path;
|
||||||
|
|
||||||
|
private GamePath( string path, bool _ )
|
||||||
|
=> _path = path;
|
||||||
|
|
||||||
|
public GamePath( string? path )
|
||||||
|
{
|
||||||
|
if( path != null && path.Length < MaxGamePathLength )
|
||||||
|
{
|
||||||
|
_path = Lower( Trim( ReplaceSlash( path ) ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_path = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamePath( FileInfo file, DirectoryInfo baseDir )
|
||||||
|
=> _path = CheckPre( file, baseDir ) ? Lower( Trim( ReplaceSlash( Substring( file, baseDir ) ) ) ) : "";
|
||||||
|
|
||||||
|
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir )
|
||||||
|
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxGamePathLength;
|
||||||
|
|
||||||
|
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( '/' );
|
||||||
|
|
||||||
|
private static string Lower( string path )
|
||||||
|
=> path.ToLowerInvariant();
|
||||||
|
|
||||||
|
public static GamePath GenerateUnchecked( string path )
|
||||||
|
=> new(path, true);
|
||||||
|
|
||||||
|
public static GamePath GenerateUncheckedLower( string path )
|
||||||
|
=> new(Lower( path ), true);
|
||||||
|
|
||||||
|
public static implicit operator string( GamePath gamePath )
|
||||||
|
=> gamePath._path;
|
||||||
|
|
||||||
|
public static explicit operator GamePath( string gamePath )
|
||||||
|
=> new(gamePath);
|
||||||
|
|
||||||
|
public bool Empty
|
||||||
|
=> _path.Length == 0;
|
||||||
|
|
||||||
|
public string Filename()
|
||||||
|
{
|
||||||
|
var idx = _path.LastIndexOf( "/", StringComparison.Ordinal );
|
||||||
|
return idx == -1 ? _path : idx == _path.Length - 1 ? "" : _path[ ( idx + 1 ).. ];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo( object? rhs )
|
||||||
|
{
|
||||||
|
return rhs switch
|
||||||
|
{
|
||||||
|
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
|
||||||
|
GamePath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
|
||||||
|
_ => -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> _path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GamePathConverter : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert( Type objectType )
|
||||||
|
=> objectType == typeof( GamePath );
|
||||||
|
|
||||||
|
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
||||||
|
{
|
||||||
|
var token = JToken.Load( reader );
|
||||||
|
return token.ToObject< GamePath >();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
=> true;
|
||||||
|
|
||||||
|
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
||||||
|
{
|
||||||
|
if( value != null )
|
||||||
|
{
|
||||||
|
var v = ( GamePath )value;
|
||||||
|
serializer.Serialize( writer, v.ToString() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
|
|
@ -6,81 +8,123 @@ using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
using String = FFXIVClientStructs.STD.String;
|
||||||
|
|
||||||
namespace Penumbra.Interop;
|
namespace Penumbra.Interop;
|
||||||
|
|
||||||
public unsafe class PathResolver : IDisposable
|
public unsafe class PathResolver : IDisposable
|
||||||
{
|
{
|
||||||
public delegate IntPtr ResolveMdlPath( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 );
|
public delegate IntPtr ResolveMdlImcPath( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 );
|
||||||
public delegate IntPtr ResolveMtrlPath( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 );
|
public delegate IntPtr ResolveMtrlPath( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 );
|
||||||
|
public delegate void LoadMtrlTex( IntPtr mtrlResourceHandle );
|
||||||
|
|
||||||
[Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 41 ?? 48 83 ?? ?? 45 8B ?? 49 8B ?? 48 8B ?? 48 8B ?? 41" )]
|
[Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 41 ?? 48 83 ?? ?? 45 8B ?? 49 8B ?? 48 8B ?? 48 8B ?? 41",
|
||||||
public Hook< ResolveMdlPath >? ResolveMdlPathHook;
|
DetourName = "ResolveMdlPathDetour" )]
|
||||||
|
public Hook< ResolveMdlImcPath >? ResolveMdlPathHook;
|
||||||
|
|
||||||
[Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 57 48 83 ?? ?? 49 8B ?? 48 8B ?? 48 8B ?? 41 83 ?? ?? 0F" )]
|
[Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 57 48 83 ?? ?? 49 8B ?? 48 8B ?? 48 8B ?? 41 83 ?? ?? 0F",
|
||||||
public Hook<ResolveMtrlPath>? ResolveMtrlPathHook;
|
DetourName = "ResolveMtrlPathDetour" )]
|
||||||
|
public Hook< ResolveMtrlPath >? ResolveMtrlPathHook;
|
||||||
|
|
||||||
|
[Signature( "40 ?? 48 83 ?? ?? 4D 8B ?? 48 8B ?? 41", DetourName = "ResolveImcPathDetour" )]
|
||||||
|
public Hook< ResolveMdlImcPath >? ResolveImcPathHook;
|
||||||
|
|
||||||
|
[Signature( "4C 8B ?? ?? 89 ?? ?? ?? 89 ?? ?? 55 57 41 ?? 41" )]
|
||||||
|
public Hook< LoadMtrlTex >? LoadMtrlTexHook;
|
||||||
|
|
||||||
private global::Dalamud.Game.ClientState.Objects.Types.GameObject? FindParent( IntPtr drawObject )
|
private global::Dalamud.Game.ClientState.Objects.Types.GameObject? FindParent( IntPtr drawObject )
|
||||||
=> Dalamud.Objects.FirstOrDefault( a => ( ( GameObject* )a.Address )->DrawObject == ( DrawObject* )drawObject );
|
=> Dalamud.Objects.FirstOrDefault( a => ( ( GameObject* )a.Address )->DrawObject == ( DrawObject* )drawObject );
|
||||||
|
|
||||||
private readonly byte[] _data = new byte[512];
|
private readonly byte[] _data = new byte[512];
|
||||||
|
|
||||||
private unsafe IntPtr ResolveMdlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 )
|
public static Dictionary< string, ModCollection > Dict = new();
|
||||||
{
|
|
||||||
var ret = ResolveMdlPathHook!.Original( drawObject, path, unk3, unk4 );
|
|
||||||
var n = Marshal.PtrToStringAnsi( ret )!;
|
|
||||||
var name = FindParent( drawObject )?.Name.ToString() ?? string.Empty;
|
|
||||||
PluginLog.Information( $"{drawObject:X} {path:X} {unk3:X} {unk4}\n{n}\n{name}" );
|
|
||||||
if( Service< ModManager >.Get().Collections.CharacterCollection.TryGetValue( name, out var collection ) )
|
|
||||||
{
|
|
||||||
var replacement = collection.ResolveSwappedOrReplacementPath( GamePath.GenerateUncheckedLower( n ) );
|
|
||||||
if( replacement != null )
|
|
||||||
{
|
|
||||||
for( var i = 0; i < replacement.Length; ++i )
|
|
||||||
{
|
|
||||||
_data[ i ] = ( byte )replacement[ i ];
|
|
||||||
}
|
|
||||||
|
|
||||||
_data[ replacement.Length ] = 0;
|
private IntPtr WriteData( string characterName, string path )
|
||||||
fixed( byte* data = _data )
|
{
|
||||||
|
_data[ 0 ] = ( byte )'|';
|
||||||
|
var i = 1;
|
||||||
|
foreach( var c in characterName )
|
||||||
|
{
|
||||||
|
_data[ i++ ] = ( byte )c;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data[ i++ ] = ( byte )'|';
|
||||||
|
|
||||||
|
foreach( var c in path )
|
||||||
|
{
|
||||||
|
_data[ i++ ] = ( byte )c;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data[ i ] = 0;
|
||||||
|
fixed( byte* data = _data )
|
||||||
|
{
|
||||||
|
return ( IntPtr )data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadMtrlTexDetour( IntPtr mtrlResourceHandle )
|
||||||
|
{
|
||||||
|
var handle = ( ResourceHandle* )mtrlResourceHandle;
|
||||||
|
var mtrlName = handle->FileName.ToString();
|
||||||
|
if( Dict.TryGetValue( mtrlName, out var collection ) )
|
||||||
|
{
|
||||||
|
var numTex = *( byte* )( mtrlResourceHandle + 0xFA );
|
||||||
|
if( numTex != 0 )
|
||||||
|
{
|
||||||
|
PluginLog.Information( $"{mtrlResourceHandle:X} -> {mtrlName} ({collection.Name}), {numTex} Texes" );
|
||||||
|
var texSpace = *( byte** )( mtrlResourceHandle + 0xD0 );
|
||||||
|
for( var i = 0; i < numTex; ++i )
|
||||||
{
|
{
|
||||||
return ( IntPtr )data;
|
var texStringPtr = ( IntPtr )( *( ulong* )( mtrlResourceHandle + 0xE0 ) + *( ushort* )( texSpace + 8 + i * 16 ) );
|
||||||
|
var texString = Marshal.PtrToStringAnsi( texStringPtr ) ?? string.Empty;
|
||||||
|
PluginLog.Information( $"{texStringPtr:X}: {texString}" );
|
||||||
|
Dict[ texString ] = collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
LoadMtrlTexHook!.Original( mtrlResourceHandle );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IntPtr ResolvePathDetour( IntPtr drawObject, IntPtr path )
|
||||||
|
{
|
||||||
|
if( path == IntPtr.Zero )
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
var n = Marshal.PtrToStringAnsi( path );
|
||||||
|
if( n == null )
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = FindParent( drawObject )?.Name.ToString() ?? string.Empty;
|
||||||
|
PluginLog.Information( $"{drawObject:X} {path:X}\n{n}\n{name}" );
|
||||||
|
if( Service< ModManager >.Get().Collections.CharacterCollection.TryGetValue( name, out var value ) )
|
||||||
|
{
|
||||||
|
Dict[ n ] = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Dict.Remove( n );
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe IntPtr ResolveMdlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 )
|
||||||
|
=> ResolvePathDetour( drawObject, ResolveMdlPathHook!.Original( drawObject, path, unk3, unk4 ) );
|
||||||
|
|
||||||
|
private unsafe IntPtr ResolveImcPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 )
|
||||||
|
=> ResolvePathDetour( drawObject, ResolveImcPathHook!.Original( drawObject, path, unk3, unk4 ) );
|
||||||
|
|
||||||
private unsafe IntPtr ResolveMtrlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 )
|
private unsafe IntPtr ResolveMtrlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 )
|
||||||
{
|
=> ResolvePathDetour( drawObject, ResolveMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) );
|
||||||
var ret = ResolveMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 );
|
|
||||||
var n = Marshal.PtrToStringAnsi( ret )!;
|
|
||||||
var name = FindParent( drawObject )?.Name.ToString() ?? string.Empty;
|
|
||||||
PluginLog.Information( $"{drawObject:X} {path:X} {unk3:X} {unk4} {unk5:X}\n{n}\n{name}" );
|
|
||||||
if( Service<ModManager>.Get().Collections.CharacterCollection.TryGetValue( name, out var collection ) )
|
|
||||||
{
|
|
||||||
var replacement = collection.ResolveSwappedOrReplacementPath( GamePath.GenerateUncheckedLower( n ) );
|
|
||||||
if( replacement != null )
|
|
||||||
{
|
|
||||||
for( var i = 0; i < replacement.Length; ++i )
|
|
||||||
{
|
|
||||||
_data[i] = ( byte )replacement[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
_data[replacement.Length] = 0;
|
|
||||||
fixed( byte* data = _data )
|
|
||||||
{
|
|
||||||
return ( IntPtr )data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PathResolver()
|
public PathResolver()
|
||||||
{
|
{
|
||||||
|
|
@ -92,17 +136,23 @@ public unsafe class PathResolver : IDisposable
|
||||||
{
|
{
|
||||||
ResolveMdlPathHook?.Enable();
|
ResolveMdlPathHook?.Enable();
|
||||||
ResolveMtrlPathHook?.Enable();
|
ResolveMtrlPathHook?.Enable();
|
||||||
|
ResolveImcPathHook?.Enable();
|
||||||
|
LoadMtrlTexHook?.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Disable()
|
public void Disable()
|
||||||
{
|
{
|
||||||
ResolveMdlPathHook?.Disable();
|
ResolveMdlPathHook?.Disable();
|
||||||
ResolveMtrlPathHook?.Disable();
|
ResolveMtrlPathHook?.Disable();
|
||||||
|
ResolveImcPathHook?.Disable();
|
||||||
|
LoadMtrlTexHook?.Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ResolveMdlPathHook?.Dispose();
|
ResolveMdlPathHook?.Dispose();
|
||||||
ResolveMtrlPathHook?.Dispose();
|
ResolveMtrlPathHook?.Dispose();
|
||||||
|
ResolveImcPathHook?.Dispose();
|
||||||
|
LoadMtrlTexHook?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
|
using ImGuiNET;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Structs;
|
using Penumbra.Structs;
|
||||||
|
|
@ -223,8 +224,10 @@ public class ResourceLoader : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!;
|
file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!;
|
||||||
var gameFsPath = GamePath.GenerateUncheckedLower( file );
|
var gameFsPath = GamePath.GenerateUncheckedLower( file );
|
||||||
var replacementPath = modManager.ResolveSwappedOrReplacementPath( gameFsPath );
|
var replacementPath = PathResolver.Dict.TryGetValue( file, out var collection )
|
||||||
|
? collection.ResolveSwappedOrReplacementPath( gameFsPath )
|
||||||
|
: modManager.ResolveSwappedOrReplacementPath( gameFsPath );
|
||||||
if( LogAllFiles && ( LogFileFilter == null || LogFileFilter.IsMatch( file ) ) )
|
if( LogAllFiles && ( LogFileFilter == null || LogFileFilter.IsMatch( file ) ) )
|
||||||
{
|
{
|
||||||
PluginLog.Information( "[GetResourceHandler] {0}", file );
|
PluginLog.Information( "[GetResourceHandler] {0}", file );
|
||||||
|
|
@ -236,6 +239,8 @@ public class ResourceLoader : IDisposable
|
||||||
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (collection != null)
|
||||||
|
PathResolver.Dict[ replacementPath ] = collection;
|
||||||
var path = Encoding.ASCII.GetBytes( replacementPath );
|
var path = Encoding.ASCII.GetBytes( replacementPath );
|
||||||
|
|
||||||
var bPath = stackalloc byte[path.Length + 1];
|
var bPath = stackalloc byte[path.Length + 1];
|
||||||
|
|
@ -261,15 +266,42 @@ public class ResourceLoader : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
var gameFsPath = Marshal.PtrToStringAnsi( new IntPtr( pFileDesc->ResourceHandle->FileName() ) );
|
var gameFsPath = Marshal.PtrToStringAnsi( new IntPtr( pFileDesc->ResourceHandle->FileName() ) );
|
||||||
|
if( gameFsPath is not { Length: < 260 } )
|
||||||
var isRooted = Path.IsPathRooted( gameFsPath );
|
|
||||||
|
|
||||||
if( gameFsPath == null || gameFsPath.Length >= 260 || !isRooted )
|
|
||||||
{
|
{
|
||||||
return ReadSqpackHook?.Original( pFileHandler, pFileDesc, priority, isSync ) ?? 0;
|
return ReadSqpackHook?.Original( pFileHandler, pFileDesc, priority, isSync ) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginLog.Debug( "loading modded file: {GameFsPath}", gameFsPath );
|
|
||||||
|
//var collection = gameFsPath.StartsWith( '|' );
|
||||||
|
//if( collection )
|
||||||
|
//{
|
||||||
|
// var end = gameFsPath.IndexOf( '|', 1 );
|
||||||
|
// if( end < 0 )
|
||||||
|
// {
|
||||||
|
// PluginLog.Error( $"Unterminated Collection Name {gameFsPath}" );
|
||||||
|
// return ReadSqpackHook?.Original( pFileHandler, pFileDesc, priority, isSync ) ?? 0;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var name = gameFsPath[ 1..end ];
|
||||||
|
// gameFsPath = gameFsPath[ ( end + 1 ).. ];
|
||||||
|
// PluginLog.Debug( "Loading file for {Name}: {GameFsPath}", name, gameFsPath );
|
||||||
|
//
|
||||||
|
// if( !Path.IsPathRooted( gameFsPath ) )
|
||||||
|
// {
|
||||||
|
// var encoding = Encoding.UTF8.GetBytes( gameFsPath );
|
||||||
|
// Marshal.Copy( encoding, 0, new IntPtr( pFileDesc->ResourceHandle->FileName() ), encoding.Length );
|
||||||
|
// pFileDesc->ResourceHandle->FileName()[ encoding.Length ] = 0;
|
||||||
|
// pFileDesc->ResourceHandle->FileNameLength -= name.Length + 2;
|
||||||
|
// return ReadSqpackHook?.Original( pFileHandler, pFileDesc, priority, isSync ) ?? 0;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
if( !Path.IsPathRooted( gameFsPath ) )
|
||||||
|
{
|
||||||
|
return ReadSqpackHook?.Original( pFileHandler, pFileDesc, priority, isSync ) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginLog.Debug( "Loading modded file: {GameFsPath}", gameFsPath );
|
||||||
|
|
||||||
pFileDesc->FileMode = FileMode.LoadUnpackedResource;
|
pFileDesc->FileMode = FileMode.LoadUnpackedResource;
|
||||||
|
|
||||||
|
|
@ -282,8 +314,7 @@ public class ResourceLoader : IDisposable
|
||||||
Marshal.Copy( utfPath, 0, new IntPtr( fd + 0x21 ), utfPath.Length );
|
Marshal.Copy( utfPath, 0, new IntPtr( fd + 0x21 ), utfPath.Length );
|
||||||
|
|
||||||
pFileDesc->FileDescriptor = fd;
|
pFileDesc->FileDescriptor = fd;
|
||||||
var ret = ReadFile( pFileHandler, pFileDesc, priority, isSync );
|
return ReadFile( pFileHandler, pFileDesc, priority, isSync );
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enable()
|
public void Enable()
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,21 @@ public partial class SettingsInterface
|
||||||
ImGui.SetClipboardText( address );
|
ImGui.SetClipboardText( address );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref var name = ref node->KeyValuePair.Item2.Value->FileName;
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text( node->KeyValuePair.Item2.Value->FileName.ToString() );
|
if( name.Capacity > 15 )
|
||||||
|
{
|
||||||
|
ImGuiNative.igTextUnformatted( name.BufferPtr, name.BufferPtr + name.Length );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fixed( byte* ptr = name.Buffer )
|
||||||
|
{
|
||||||
|
ImGuiNative.igTextUnformatted( ptr, ptr + name.Length );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//ImGui.Text( node->KeyValuePair.Item2.Value->FileName.ToString() );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text( node->KeyValuePair.Item2.Value->RefCount.ToString() );
|
ImGui.Text( node->KeyValuePair.Item2.Value->RefCount.ToString() );
|
||||||
node = node->Next();
|
node = node->Next();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue