This commit is contained in:
Ottermandias 2022-03-13 11:13:50 +01:00
parent 46581780e0
commit de082439a4
28 changed files with 766 additions and 835 deletions

View file

@ -35,7 +35,6 @@ public class Configuration : IPluginConfiguration
public int WaitFrames { get; set; } = 30;
public string ModDirectory { get; set; } = string.Empty;
public string TempDirectory { get; set; } = string.Empty;
public string CurrentCollection { get; set; } = "Default";
public string DefaultCollection { get; set; } = "Default";

View file

@ -256,11 +256,10 @@ public class TexToolsMeta
ushort i = 0;
if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory )
{
// TODO check against default.
foreach( var value in values )
{
ImcEntry def;
if( !value.Equals( def ) )
ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) );
ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) );
++i;
}
}
@ -268,55 +267,64 @@ public class TexToolsMeta
{
foreach( var value in values )
{
ImcEntry def;
if( !value.Equals( def ) )
ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) );
ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) );
++i;
}
}
}
private static string ReadNullTerminated( BinaryReader reader )
{
var builder = new System.Text.StringBuilder();
for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() )
{
builder.Append( c );
}
return builder.ToString();
}
public TexToolsMeta( byte[] data )
{
try
{
//using var reader = new BinaryReader( new MemoryStream( data ) );
//Version = reader.ReadUInt32();
//FilePath = ReadNullTerminated( reader );
//var metaInfo = new Info( FilePath );
//var numHeaders = reader.ReadUInt32();
//var headerSize = reader.ReadUInt32();
//var headerStart = reader.ReadUInt32();
//reader.BaseStream.Seek( headerStart, SeekOrigin.Begin );
//
//List< (MetaType type, uint offset, int size) > entries = new();
//for( var i = 0; i < numHeaders; ++i )
//{
// var currentOffset = reader.BaseStream.Position;
// var type = ( MetaType )reader.ReadUInt32();
// var offset = reader.ReadUInt32();
// var size = reader.ReadInt32();
// entries.Add( ( type, offset, size ) );
// reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin );
//}
//
//byte[]? ReadEntry( MetaType type )
//{
// var idx = entries.FindIndex( t => t.type == type );
// if( idx < 0 )
// {
// return null;
// }
//
// reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
// return reader.ReadBytes( entries[ idx ].size );
//}
//
//DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) );
//DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) );
//DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) );
//DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) );
//DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) );
using var reader = new BinaryReader( new MemoryStream( data ) );
Version = reader.ReadUInt32();
FilePath = ReadNullTerminated( reader );
var metaInfo = new Info( FilePath );
var numHeaders = reader.ReadUInt32();
var headerSize = reader.ReadUInt32();
var headerStart = reader.ReadUInt32();
reader.BaseStream.Seek( headerStart, SeekOrigin.Begin );
List< (MetaManipulation.Type type, uint offset, int size) > entries = new();
for( var i = 0; i < numHeaders; ++i )
{
var currentOffset = reader.BaseStream.Position;
var type = ( MetaManipulation.Type )reader.ReadUInt32();
var offset = reader.ReadUInt32();
var size = reader.ReadInt32();
entries.Add( ( type, offset, size ) );
reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin );
}
byte[]? ReadEntry( MetaManipulation.Type type )
{
var idx = entries.FindIndex( t => t.type == type );
if( idx < 0 )
{
return null;
}
reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
return reader.ReadBytes( entries[ idx ].size );
}
DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) );
DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) );
DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) );
DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) );
DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) );
}
catch( Exception e )
{
@ -362,28 +370,35 @@ public class TexToolsMeta
return Invalid;
}
//if( gender == 1 )
//{
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinSize, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxSize, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinTail, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxTail, br.ReadSingle() ) );
//
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinX, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinY, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinZ, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxX, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxY, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxZ, br.ReadSingle() ) );
//}
//else
//{
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinSize, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxSize, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinTail, br.ReadSingle() ) );
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxTail, br.ReadSingle() ) );
//}
//
void Add( RspAttribute attribute, float value )
{
var def = CmpFile.GetDefault( subRace, attribute );
if (value != def)
ret!.RspManipulations.Add(new RspManipulation(subRace, attribute, value));
}
if( gender == 1 )
{
Add(RspAttribute.FemaleMinSize, br.ReadSingle() );
Add(RspAttribute.FemaleMaxSize, br.ReadSingle() );
Add(RspAttribute.FemaleMinTail, br.ReadSingle() );
Add(RspAttribute.FemaleMaxTail, br.ReadSingle() );
Add(RspAttribute.BustMinX, br.ReadSingle() );
Add(RspAttribute.BustMinY, br.ReadSingle() );
Add(RspAttribute.BustMinZ, br.ReadSingle() );
Add(RspAttribute.BustMaxX, br.ReadSingle() );
Add(RspAttribute.BustMaxY, br.ReadSingle() );
Add(RspAttribute.BustMaxZ, br.ReadSingle() );
}
else
{
Add(RspAttribute.MaleMinSize, br.ReadSingle() );
Add(RspAttribute.MaleMaxSize, br.ReadSingle() );
Add(RspAttribute.MaleMinTail, br.ReadSingle() );
Add(RspAttribute.MaleMaxTail, br.ReadSingle() );
}
return ret;
}
}

View file

@ -20,12 +20,20 @@ public unsafe class CharacterUtility : IDisposable
public Structs.CharacterUtility* Address
=> *_characterUtilityAddress;
public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[Structs.CharacterUtility.NumResources];
public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[Structs.CharacterUtility.NumRelevantResources];
public CharacterUtility()
{
SignatureHelper.Initialise( this );
LoadDataFilesHook.Enable();
if( Address->EqpResource != null )
{
LoadDefaultResources();
}
else
{
LoadDataFilesHook.Enable();
}
}
// Self-disabling hook to set default resources after loading them.
@ -40,7 +48,7 @@ public unsafe class CharacterUtility : IDisposable
// We store the default data of the resources so we can always restore them.
private void LoadDefaultResources()
{
for( var i = 0; i < Structs.CharacterUtility.NumResources; ++i )
for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i )
{
var resource = ( Structs.ResourceHandle* )Address->Resources[ i ];
DefaultResources[ i ] = resource->GetData();
@ -65,7 +73,7 @@ public unsafe class CharacterUtility : IDisposable
public void Dispose()
{
for( var i = 0; i < Structs.CharacterUtility.NumResources; ++i )
for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i )
{
ResetResource( i );
}

View file

@ -5,10 +5,7 @@ using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
using Penumbra.Mods;
using Penumbra.Util;
using FileMode = Penumbra.Interop.Structs.FileMode;
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
@ -123,6 +120,10 @@ public unsafe partial class ResourceLoader
ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
FileLoaded?.Invoke( gamePath.Path, ret != 0, false );
}
else if( ResourceLoadCustomization != null && gamePath.Path[0] == (byte) '|' )
{
ret = ResourceLoadCustomization.Invoke( gamePath, resourceManager, fileDescriptor, priority, isSync );
}
else
{
// Specify that we are loading unpacked files from the drive.
@ -150,11 +151,18 @@ public unsafe partial class ResourceLoader
return ret;
}
// Customize file loading for any GamePaths that start with "|".
public delegate byte ResourceLoadCustomizationDelegate( Utf8GamePath gamePath, ResourceManager* resourceManager,
SeFileDescriptor* fileDescriptor, int priority, bool isSync );
public ResourceLoadCustomizationDelegate? ResourceLoadCustomization;
// Use the default method of path replacement.
public static (FullPath?, object?) DefaultReplacer( Utf8GamePath path )
{
var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path );
return( resolved, null );
return ( resolved, null );
}
private void DisposeHooks()

View file

@ -7,15 +7,16 @@ namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
public unsafe struct CharacterUtility
{
public const int NumResources = 85;
public const int EqpIdx = 0;
public const int GmpIdx = 1;
public const int HumanCmpIdx = 63;
public const int FaceEstIdx = 64;
public const int HairEstIdx = 65;
public const int BodyEstIdx = 66;
public const int HeadEstIdx = 67;
public const int NumEqdpFiles = 2 * 28;
public const int NumResources = 85;
public const int NumRelevantResources = 68;
public const int EqpIdx = 0;
public const int GmpIdx = 1;
public const int HumanCmpIdx = 63;
public const int FaceEstIdx = 64;
public const int HairEstIdx = 65;
public const int BodyEstIdx = 66;
public const int HeadEstIdx = 67;
public const int NumEqdpFiles = 2 * 28;
public static int EqdpIdx( GenderRace raceCode, bool accessory )
=> ( accessory ? 28 : 0 )

View file

@ -55,7 +55,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
{
var ptr = ( byte* )DefaultData.Data;
var controlBlock = *( ulong* )ptr;
*( ulong* )ptr = ulong.MaxValue;
*( ulong* )Data = ulong.MaxValue;
for( var i = 0; i < 64; ++i )
{
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;

View file

@ -1,19 +1,21 @@
using System;
using System.Numerics;
using Dalamud.Logging;
using Dalamud.Memory;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Files;
public struct ImcEntry : IEquatable< ImcEntry >
public readonly struct ImcEntry : IEquatable< ImcEntry >
{
public byte MaterialId;
public byte DecalId;
private ushort _attributeAndSound;
public byte VfxId;
public byte MaterialAnimationId;
public readonly byte MaterialId;
public readonly byte DecalId;
private readonly ushort _attributeAndSound;
public readonly byte VfxId;
public readonly byte MaterialAnimationId;
public ushort AttributeMask
=> ( ushort )( _attributeAndSound & 0x3FF );
@ -163,4 +165,26 @@ public unsafe class ImcFile : MetaBaseFile
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
}
}
public void Replace( ResourceHandle* resource )
{
var (data, length) = resource->GetData();
if( data == IntPtr.Zero )
{
return;
}
var requiredLength = ActualLength;
if( length >= requiredLength )
{
Functions.MemCpyUnchecked( ( void* )data, Data, requiredLength );
Functions.MemSet( ( byte* )data + requiredLength, 0, length - requiredLength );
return;
}
MemoryHelper.GameFree( ref data, ( ulong )length );
var file = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )requiredLength );
Functions.MemCpyUnchecked( file, Data, requiredLength );
resource->SetData( ( IntPtr )file, requiredLength );
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Memory;
namespace Penumbra.Meta.Files;
@ -23,14 +24,15 @@ public unsafe class MetaBaseFile : IDisposable
protected void AllocateData( int length )
{
Length = length;
Data = ( byte* )Marshal.AllocHGlobal( length );
Data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )length ); ;
GC.AddMemoryPressure( length );
}
// Free memory.
protected void ReleaseUnmanagedResources()
{
Marshal.FreeHGlobal( ( IntPtr )Data );
var ptr = ( IntPtr )Data;
MemoryHelper.GameFree( ref ptr, (ulong) Length );
GC.RemoveMemoryPressure( Length );
Length = 0;
Data = null;

View file

@ -1,83 +0,0 @@
using System;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
namespace Penumbra.Meta.Files
{
// Contains all filenames for meta changes depending on their parameters.
public static class MetaFileNames
{
public static GamePath Eqp()
=> GamePath.GenerateUnchecked( "chara/xls/equipmentparameter/equipmentparameter.eqp" );
public static GamePath Gmp()
=> GamePath.GenerateUnchecked( "chara/xls/equipmentparameter/gimmickparameter.gmp" );
public static GamePath Est( ObjectType type, EquipSlot equip, BodySlot slot )
{
return type switch
{
ObjectType.Equipment => equip switch
{
EquipSlot.Body => GamePath.GenerateUnchecked( "chara/xls/charadb/extra_top.est" ),
EquipSlot.Head => GamePath.GenerateUnchecked( "chara/xls/charadb/extra_met.est" ),
_ => throw new NotImplementedException(),
},
ObjectType.Character => slot switch
{
BodySlot.Hair => GamePath.GenerateUnchecked( "chara/xls/charadb/hairskeletontemplate.est" ),
BodySlot.Face => GamePath.GenerateUnchecked( "chara/xls/charadb/faceskeletontemplate.est" ),
_ => throw new NotImplementedException(),
},
_ => throw new NotImplementedException(),
};
}
public static GamePath Imc( ObjectType type, ushort primaryId, ushort secondaryId )
{
return type switch
{
ObjectType.Accessory => GamePath.GenerateUnchecked( $"chara/accessory/a{primaryId:D4}/a{primaryId:D4}.imc" ),
ObjectType.Equipment => GamePath.GenerateUnchecked( $"chara/equipment/e{primaryId:D4}/e{primaryId:D4}.imc" ),
ObjectType.DemiHuman => GamePath.GenerateUnchecked(
$"chara/demihuman/d{primaryId:D4}/obj/equipment/e{secondaryId:D4}/e{secondaryId:D4}.imc" ),
ObjectType.Monster => GamePath.GenerateUnchecked(
$"chara/monster/m{primaryId:D4}/obj/body/b{secondaryId:D4}/b{secondaryId:D4}.imc" ),
ObjectType.Weapon => GamePath.GenerateUnchecked(
$"chara/weapon/w{primaryId:D4}/obj/body/b{secondaryId:D4}/b{secondaryId:D4}.imc" ),
_ => throw new NotImplementedException(),
};
}
public static GamePath Eqdp( ObjectType type, GenderRace gr )
{
return type switch
{
ObjectType.Accessory => GamePath.GenerateUnchecked( $"chara/xls/charadb/accessorydeformerparameter/c{gr.ToRaceCode()}.eqdp" ),
ObjectType.Equipment => GamePath.GenerateUnchecked( $"chara/xls/charadb/equipmentdeformerparameter/c{gr.ToRaceCode()}.eqdp" ),
_ => throw new NotImplementedException(),
};
}
public static GamePath Eqdp( EquipSlot slot, GenderRace gr )
{
return slot switch
{
EquipSlot.Head => Eqdp( ObjectType.Equipment, gr ),
EquipSlot.Body => Eqdp( ObjectType.Equipment, gr ),
EquipSlot.Feet => Eqdp( ObjectType.Equipment, gr ),
EquipSlot.Hands => Eqdp( ObjectType.Equipment, gr ),
EquipSlot.Legs => Eqdp( ObjectType.Equipment, gr ),
EquipSlot.Neck => Eqdp( ObjectType.Accessory, gr ),
EquipSlot.Ears => Eqdp( ObjectType.Accessory, gr ),
EquipSlot.Wrists => Eqdp( ObjectType.Accessory, gr ),
EquipSlot.LFinger => Eqdp( ObjectType.Accessory, gr ),
EquipSlot.RFinger => Eqdp( ObjectType.Accessory, gr ),
_ => throw new NotImplementedException(),
};
}
public static GamePath Cmp()
=> GamePath.GenerateUnchecked( "chara/xls/charamake/human.cmp" );
}
}

View file

@ -1,8 +1,8 @@
using System;
using System.Runtime.InteropServices;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Meta.Files;
using ImcFile = Lumina.Data.Files.ImcFile;
namespace Penumbra.Meta.Manipulations;
@ -58,4 +58,33 @@ public readonly struct ImcManipulation : IEquatable< ImcManipulation >
public override int GetHashCode()
=> HashCode.Combine( PrimaryId, Variant, SecondaryId, ( int )ObjectType, ( int )EquipSlot, ( int )BodySlot );
public Utf8GamePath GamePath()
{
return ObjectType switch
{
ObjectType.Accessory => Utf8GamePath.FromString( $"chara/accessory/a{PrimaryId:D4}/a{PrimaryId:D4}.imc", out var p )
? p
: Utf8GamePath.Empty,
ObjectType.Equipment => Utf8GamePath.FromString( $"chara/equipment/e{PrimaryId:D4}/e{PrimaryId:D4}.imc", out var p )
? p
: Utf8GamePath.Empty,
ObjectType.DemiHuman => Utf8GamePath.FromString(
$"chara/demihuman/d{PrimaryId:D4}/obj/equipment/e{SecondaryId:D4}/e{SecondaryId:D4}.imc", out var p )
? p
: Utf8GamePath.Empty,
ObjectType.Monster => Utf8GamePath.FromString( $"chara/monster/m{PrimaryId:D4}/obj/body/b{SecondaryId:D4}/b{SecondaryId:D4}.imc",
out var p )
? p
: Utf8GamePath.Empty,
ObjectType.Weapon => Utf8GamePath.FromString( $"chara/weapon/w{PrimaryId:D4}/obj/body/b{SecondaryId:D4}/b{SecondaryId:D4}.imc",
out var p )
? p
: Utf8GamePath.Empty,
_ => throw new NotImplementedException(),
};
}
public bool Apply( ImcFile file )
=> file.SetEntry( ImcFile.PartIndex( EquipSlot ), Variant, Entry );
}

View file

@ -1,10 +1,11 @@
using System;
using System.Runtime.InteropServices;
using Penumbra.GameData.Util;
namespace Penumbra.Meta.Manipulations;
[StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )]
public readonly struct MetaManipulation : IEquatable< MetaManipulation >
public readonly struct MetaManipulation : IEquatable< MetaManipulation >, IComparable<MetaManipulation>
{
public enum Type : byte
{
@ -106,4 +107,10 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >
Type.Imc => Imc.GetHashCode(),
_ => throw new ArgumentOutOfRangeException(),
};
public unsafe int CompareTo( MetaManipulation other )
{
fixed(MetaManipulation* lhs = &this)
return Functions.MemCmpUnchecked(lhs, &other, sizeof(MetaManipulation));
}
}

View file

@ -93,14 +93,14 @@ public class MetaCollection
return false;
}
if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) )
{
return false;
}
//if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) )
//{
// return false;
//}
}
}
} // TODO
return DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) );
return true; //DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) );
}
// Re-sort all manipulations.
@ -117,31 +117,33 @@ public class MetaCollection
// Creates the option group and the option if necessary.
private void AddMeta( string group, string option, TexToolsMeta meta )
{
if( meta.Manipulations.Count == 0 )
{
return;
}
var manipulations = meta.EqpManipulations.Select( m => new MetaManipulation( m ) )
.Concat( meta.EqdpManipulations.Select( m => new MetaManipulation( m ) ) )
.Concat( meta.EstManipulations.Select( m => new MetaManipulation( m ) ) )
.Concat( meta.GmpManipulations.Select( m => new MetaManipulation( m ) ) )
.Concat( meta.RspManipulations.Select( m => new MetaManipulation( m ) ) )
.Concat( meta.ImcManipulations.Select( m => new MetaManipulation( m ) ) ).ToList();
if( group.Length == 0 )
{
DefaultData.AddRange( meta.Manipulations );
DefaultData.AddRange( manipulations );
}
else if( option.Length == 0 )
{ }
else if( !GroupData.TryGetValue( group, out var options ) )
{
GroupData.Add( group, new Dictionary< string, List< MetaManipulation > >() { { option, meta.Manipulations.ToList() } } );
GroupData.Add( group, new Dictionary< string, List< MetaManipulation > >() { { option, manipulations } } );
}
else if( !options.TryGetValue( option, out var list ) )
{
options.Add( option, meta.Manipulations.ToList() );
options.Add( option, manipulations );
}
else
{
list.AddRange( meta.Manipulations );
list.AddRange( manipulations );
}
Count += meta.Manipulations.Count;
Count += manipulations.Count;
}
// Update the whole meta collection by reading all TexTools .meta files in a mod directory anew,
@ -160,7 +162,7 @@ public class MetaCollection
_ => TexToolsMeta.Invalid,
};
if( metaData.FilePath == string.Empty || metaData.Manipulations.Count == 0 )
if( metaData.FilePath == string.Empty )
{
continue;
}

View file

@ -2,26 +2,19 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Lumina.Data.Files;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
using Penumbra.Util;
using Penumbra.Mods;
using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility;
using ImcFile = Penumbra.Meta.Files.ImcFile;
namespace Penumbra.Meta;
public struct TemporaryImcFile : IDisposable
{
public void Dispose()
{
}
}
public class MetaManager2 : IDisposable
{
public readonly List< MetaBaseFile > ChangedData = new(7 + CharacterUtility.NumEqdpFiles);
@ -42,7 +35,51 @@ public class MetaManager2 : IDisposable
public readonly Dictionary< EqdpManipulation, Mod.Mod > EqdpManipulations = new();
public readonly Dictionary< ImcManipulation, Mod.Mod > ImcManipulations = new();
public readonly List< TemporaryImcFile > ImcFiles = new();
public readonly Dictionary< Utf8GamePath, ImcFile > ImcFiles = new();
private readonly ModCollection _collection;
public unsafe void SetFiles()
{
foreach( var file in ChangedData )
{
Penumbra.CharacterUtility.SetResource( file.Index, ( IntPtr )file.Data, file.Length );
}
}
public bool TryGetValue( MetaManipulation manip, out Mod.Mod? mod )
{
mod = manip.ManipulationType switch
{
MetaManipulation.Type.Eqp => EqpManipulations.TryGetValue( manip.Eqp, out var m ) ? m : null,
MetaManipulation.Type.Gmp => GmpManipulations.TryGetValue( manip.Gmp, out var m ) ? m : null,
MetaManipulation.Type.Eqdp => EqdpManipulations.TryGetValue( manip.Eqdp, out var m ) ? m : null,
MetaManipulation.Type.Est => EstManipulations.TryGetValue( manip.Est, out var m ) ? m : null,
MetaManipulation.Type.Rsp => RspManipulations.TryGetValue( manip.Rsp, out var m ) ? m : null,
MetaManipulation.Type.Imc => ImcManipulations.TryGetValue( manip.Imc, out var m ) ? m : null,
_ => throw new ArgumentOutOfRangeException(),
};
return mod != null;
}
public int Count
=> ImcManipulations.Count
+ EqdpManipulations.Count
+ RspManipulations.Count
+ GmpManipulations.Count
+ EstManipulations.Count
+ EqpManipulations.Count;
public MetaManager2( ModCollection collection )
=> _collection = collection;
public void ApplyImcFiles( Dictionary< Utf8GamePath, FullPath > resolvedFiles )
{
foreach( var path in ImcFiles.Keys )
{
resolvedFiles[ path ] = CreateImcPath( path );
}
}
public void ResetEqp()
{
@ -95,10 +132,21 @@ public class MetaManager2 : IDisposable
EqdpManipulations.Clear();
}
private FullPath CreateImcPath( Utf8GamePath path )
{
var d = new DirectoryInfo( $":{_collection.Name}/" );
return new FullPath( d, new Utf8RelPath( path ) );
}
public void ResetImc()
{
foreach( var file in ImcFiles )
foreach( var (path, file) in ImcFiles )
{
_collection.Cache?.ResolvedFiles.Remove( path );
path.Dispose();
file.Dispose();
}
ImcFiles.Clear();
ImcManipulations.Clear();
}
@ -154,7 +202,6 @@ public class MetaManager2 : IDisposable
}
}
public bool ApplyMod( EqpManipulation m, Mod.Mod mod )
{
if( !EqpManipulations.TryAdd( m, mod ) )
@ -247,16 +294,63 @@ public class MetaManager2 : IDisposable
return true;
}
public bool ApplyMod( ImcManipulation m, Mod.Mod mod )
public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod )
{
const uint imcExt = 0x00696D63;
if( !ImcManipulations.TryAdd( m, mod ) )
{
return false;
}
var path = m.GamePath();
if( !ImcFiles.TryGetValue( path, out var file ) )
{
file = new ImcFile( path );
}
if( !m.Apply( file ) )
{
return false;
}
ImcFiles[ path ] = file;
var fullPath = CreateImcPath( path );
if( _collection.Cache != null )
{
_collection.Cache.ResolvedFiles[ path ] = fullPath;
}
var resource = ResourceLoader.FindResource( ResourceCategory.Chara, imcExt, ( uint )path.Path.Crc32 );
if( resource != null )
{
file.Replace( ( ResourceHandle* )resource );
}
return true;
}
public static unsafe byte ImcHandler( Utf8GamePath gamePath, ResourceManager* resourceManager,
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
{
var split = gamePath.Path.Split( ( byte )'|', 2, true );
fileDescriptor->ResourceHandle->FileNameData = split[ 1 ].Path;
fileDescriptor->ResourceHandle->FileNameLength = split[ 1 ].Length;
var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
if( Penumbra.ModManager.Collections.Collections.TryGetValue( split[ 0 ].ToString(), out var collection )
&& collection.Cache != null
&& collection.Cache.MetaManipulations.ImcFiles.TryGetValue(
Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) )
{
file.Replace( fileDescriptor->ResourceHandle );
}
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
return ret;
}
public bool ApplyMod( MetaManipulation m, Mod.Mod mod )
{
return m.ManipulationType switch
@ -267,133 +361,7 @@ public class MetaManager2 : IDisposable
MetaManipulation.Type.Est => ApplyMod( m.Est, mod ),
MetaManipulation.Type.Rsp => ApplyMod( m.Rsp, mod ),
MetaManipulation.Type.Imc => ApplyMod( m.Imc, mod ),
_ => throw new ArgumentOutOfRangeException()
_ => throw new ArgumentOutOfRangeException(),
};
}
}
public class MetaManager : IDisposable
{
internal class FileInformation
{
public readonly object Data;
public bool Changed;
public FullPath? CurrentFile;
public byte[] ByteData = Array.Empty< byte >();
public FileInformation( object data )
=> Data = data;
public void Write( DirectoryInfo dir, Utf8GamePath originalPath )
{
ByteData = Data switch
{
ImcFile imc => imc.WriteBytes(),
_ => throw new NotImplementedException(),
};
DisposeFile( CurrentFile );
CurrentFile = new FullPath( TempFile.WriteNew( dir, ByteData, $"_{originalPath.Filename()}" ) );
Changed = false;
}
}
public const string TmpDirectory = "penumbrametatmp";
private readonly DirectoryInfo _dir;
private readonly Dictionary< Utf8GamePath, FullPath > _resolvedFiles;
private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new();
private readonly Dictionary< Utf8GamePath, FileInformation > _currentFiles = new();
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
=> _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) );
public IEnumerable< (Utf8GamePath, FullPath) > Files
=> _currentFiles.Where( kvp => kvp.Value.CurrentFile != null )
.Select( kvp => ( kvp.Key, kvp.Value.CurrentFile!.Value ) );
public int Count
=> _currentManipulations.Count;
public bool TryGetValue( MetaManipulation manip, out Mod.Mod mod )
=> _currentManipulations.TryGetValue( manip, out mod! );
public byte[] EqpData = Array.Empty< byte >();
private static void DisposeFile( FullPath? file )
{
if( !( file?.Exists ?? false ) )
{
return;
}
try
{
File.Delete( file.Value.FullName );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete temporary file \"{file.Value.FullName}\":\n{e}" );
}
}
public void Reset( bool reload = true )
{
foreach( var file in _currentFiles )
{
_resolvedFiles.Remove( file.Key );
DisposeFile( file.Value.CurrentFile );
}
_currentManipulations.Clear();
_currentFiles.Clear();
ClearDirectory();
if( reload )
{
Penumbra.ResidentResources.Reload();
}
}
public void Dispose()
=> Reset();
private static void ClearDirectory( DirectoryInfo modDir )
{
modDir.Refresh();
if( modDir.Exists )
{
try
{
Directory.Delete( modDir.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not clear temporary metafile directory \"{modDir.FullName}\":\n{e}" );
}
}
}
private void ClearDirectory()
=> ClearDirectory( _dir );
public MetaManager( string name, Dictionary< Utf8GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir )
{
_resolvedFiles = resolvedFiles;
_dir = new DirectoryInfo( Path.Combine( tempDir.FullName, name.ReplaceBadXivSymbols() ) );
ClearDirectory();
}
public void WriteNewFiles()
{
if( _currentFiles.Any() )
{
Directory.CreateDirectory( _dir.FullName );
}
foreach( var (key, value) in _currentFiles.Where( kvp => kvp.Value.Changed ) )
{
value.Write( _dir, key );
_resolvedFiles[ key ] = value.CurrentFile!.Value;
}
}
}

View file

@ -67,15 +67,9 @@ public class CollectionManager
public void RecreateCaches()
{
if( !_manager.TempWritable )
{
PluginLog.Error( "No temporary directory available." );
return;
}
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
{
collection.CreateCache( _manager.TempPath, _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
collection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
}
CreateNecessaryCaches();
@ -176,15 +170,9 @@ public class CollectionManager
private void AddCache( ModCollection collection )
{
if( !_manager.TempWritable )
{
PluginLog.Error( "No tmp directory available." );
return;
}
if( collection.Cache == null && collection.Name != string.Empty )
{
collection.CreateCache( _manager.TempPath, _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
collection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
}
}
@ -289,7 +277,7 @@ public class CollectionManager
CurrentCollection = Collections[ ModCollection.DefaultCollection ];
if( CurrentCollection.Cache == null )
{
CurrentCollection.CreateCache( _manager.TempPath, _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
CurrentCollection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
}
config.CurrentCollection = ModCollection.DefaultCollection;

View file

@ -65,9 +65,9 @@ public class ModCollection
return removeList.Length > 0;
}
public void CreateCache( DirectoryInfo modDirectory, IEnumerable< ModData > data )
public void CreateCache( IEnumerable< ModData > data )
{
Cache = new ModCollectionCache( Name, modDirectory );
Cache = new ModCollectionCache( this );
var changedSettings = false;
foreach( var mod in data )
{
@ -89,7 +89,7 @@ public class ModCollection
Save();
}
CalculateEffectiveFileList( modDirectory, true, false );
CalculateEffectiveFileList( true, false );
}
public void ClearCache()
@ -135,11 +135,11 @@ public class ModCollection
}
}
public void CalculateEffectiveFileList( DirectoryInfo modDir, bool withMetaManipulations, bool activeCollection )
public void CalculateEffectiveFileList( bool withMetaManipulations, bool activeCollection )
{
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name,
withMetaManipulations, activeCollection );
Cache ??= new ModCollectionCache( Name, modDir );
Cache ??= new ModCollectionCache( this );
UpdateSettings( false );
Cache.CalculateEffectiveFileList();
if( withMetaManipulations )

View file

@ -27,8 +27,7 @@ public class ModCollectionCache
private readonly SortedList< string, object? > _changedItems = new();
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
public readonly HashSet< FullPath > MissingFiles = new();
public readonly HashSet< ulong > Checksums = new();
public readonly MetaManager MetaManipulations;
public readonly MetaManager2 MetaManipulations;
public IReadOnlyDictionary< string, object? > ChangedItems
{
@ -39,8 +38,8 @@ public class ModCollectionCache
}
}
public ModCollectionCache( string collectionName, DirectoryInfo tempDir )
=> MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir );
public ModCollectionCache( ModCollection collection )
=> MetaManipulations = new MetaManager2( collection );
private static void ResetFileSeen( int size )
{
@ -73,11 +72,6 @@ public class ModCollectionCache
}
AddMetaFiles();
Checksums.Clear();
foreach( var file in ResolvedFiles )
{
Checksums.Add( file.Value.Crc64 );
}
}
private void SetChangedItems()
@ -89,11 +83,10 @@ public class ModCollectionCache
try
{
// Skip meta files because IMCs would result in far too many false-positive items,
// Skip IMCs because they would result in far too many false-positive items,
// since they are per set instead of per item-slot/item/variant.
var metaFiles = MetaManipulations.Files.Select( p => p.Item1 ).ToHashSet();
var identifier = GameData.GameData.GetIdentifier();
foreach( var resolved in ResolvedFiles.Keys.Where( file => !metaFiles.Contains( file ) ) )
foreach( var resolved in ResolvedFiles.Keys.Where( file => !file.Path.EndsWith( 'i', 'm', 'c' ) ) )
{
identifier.Identify( _changedItems, resolved.ToGamePath() );
}
@ -265,18 +258,7 @@ public class ModCollectionCache
}
private void AddMetaFiles()
{
foreach( var (gamePath, file) in MetaManipulations.Files )
{
if( RegisteredFiles.TryGetValue( gamePath, out var mod ) )
{
PluginLog.Warning(
$"The meta manipulation file {gamePath} was already completely replaced by {mod.Data.Meta.Name}. This is probably a mistake. Using the custom file {file.FullName}." );
}
ResolvedFiles[ gamePath ] = file;
}
}
=> MetaManipulations.ApplyImcFiles( ResolvedFiles );
private void AddSwaps( Mod.Mod mod )
{
@ -304,12 +286,12 @@ public class ModCollectionCache
{
if( !MetaManipulations.TryGetValue( manip, out var oldMod ) )
{
//MetaManipulations.ApplyMod( manip, mod );
MetaManipulations.ApplyMod( manip, mod );
}
else
{
mod.Cache.AddConflict( oldMod, manip );
if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority )
mod.Cache.AddConflict( oldMod!, manip );
if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod!.Settings.Priority )
{
oldMod.Cache.AddConflict( mod, manip );
}
@ -319,15 +301,13 @@ public class ModCollectionCache
public void UpdateMetaManipulations()
{
MetaManipulations.Reset( false );
MetaManipulations.Reset();
foreach( var mod in AvailableMods.Values.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) )
{
mod.Cache.ClearMetaConflicts();
AddManipulations( mod );
}
MetaManipulations.WriteNewFiles();
}
public void RemoveMod( DirectoryInfo basePath )

View file

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dalamud.Logging;
using Penumbra.Mod;
namespace Penumbra.Mods;
public partial class ModManagerNew
{
private readonly List<ModData> _mods = new();
public IReadOnlyList<ModData> Mods
=> _mods;
public void DiscoverMods()
{
//_mods.Clear();
//
//if( CheckValidity() )
//{
// foreach( var modFolder in BasePath.EnumerateDirectories() )
// {
// var mod = ModData.LoadMod( StructuredMods, modFolder );
// if( mod == null )
// {
// continue;
// }
//
// Mods.Add( modFolder.Name, mod );
// }
//
// SetModStructure();
//}
//
//Collections.RecreateCaches();
}
}
public partial class ModManagerNew
{
public DirectoryInfo BasePath { get; private set; } = null!;
public bool Valid { get; private set; }
public event Action< DirectoryInfo >? BasePathChanged;
public ModManagerNew()
{
InitBaseDirectory( Penumbra.Config.ModDirectory );
}
public bool CheckValidity()
{
if( Valid )
{
Valid = Directory.Exists(BasePath.FullName);
}
return Valid;
}
private static (DirectoryInfo, bool) CreateDirectory( string path )
{
var newDir = new DirectoryInfo( path );
if( !newDir.Exists )
{
try
{
Directory.CreateDirectory( newDir.FullName );
newDir.Refresh();
}
catch( Exception e )
{
PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" );
return ( newDir, false );
}
}
return ( newDir, true );
}
private void InitBaseDirectory( string path )
{
if( path.Length == 0 )
{
Valid = false;
BasePath = new DirectoryInfo( "." );
return;
}
( BasePath, Valid ) = CreateDirectory( path );
if( Penumbra.Config.ModDirectory != BasePath.FullName )
{
Penumbra.Config.ModDirectory = BasePath.FullName;
Penumbra.Config.Save();
}
}
private void ChangeBaseDirectory( string path )
{
if( string.Equals( path, Penumbra.Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
{
return;
}
InitBaseDirectory( path );
BasePathChanged?.Invoke( BasePath );
}
}

View file

@ -4,401 +4,297 @@ using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util;
using Penumbra.Meta;
using Penumbra.Mod;
namespace Penumbra.Mods
namespace Penumbra.Mods;
// The ModManager handles the basic mods installed to the mod directory.
// It also contains the CollectionManager that handles all collections.
public class ModManager
{
// The ModManager handles the basic mods installed to the mod directory.
// It also contains the CollectionManager that handles all collections.
public class ModManager
public DirectoryInfo BasePath { get; private set; } = null!;
public Dictionary< string, ModData > Mods { get; } = new();
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
public CollectionManager Collections { get; }
public bool Valid { get; private set; }
public Configuration Config
=> Penumbra.Config;
public void DiscoverMods( string newDir )
{
public DirectoryInfo BasePath { get; private set; } = null!;
public DirectoryInfo TempPath { get; private set; } = null!;
SetBaseDirectory( newDir, false );
DiscoverMods();
}
public Dictionary< string, ModData > Mods { get; } = new();
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
public CollectionManager Collections { get; }
public bool Valid { get; private set; }
public bool TempWritable { get; private set; }
public Configuration Config
=> Penumbra.Config;
public void DiscoverMods( string newDir )
private void SetBaseDirectory( string newPath, bool firstTime )
{
if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
{
SetBaseDirectory( newDir, false );
DiscoverMods();
return;
}
private void ClearOldTmpDir()
if( !newPath.Any() )
{
if( !TempWritable )
{
return;
}
TempPath.Refresh();
if( TempPath.Exists )
Valid = false;
BasePath = new DirectoryInfo( "." );
}
else
{
var newDir = new DirectoryInfo( newPath );
if( !newDir.Exists )
{
try
{
TempPath.Delete( true );
Directory.CreateDirectory( newDir.FullName );
newDir.Refresh();
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete temporary directory {TempPath.FullName}:\n{e}" );
PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" );
}
}
BasePath = newDir;
Valid = true;
if( Config.ModDirectory != BasePath.FullName )
{
Config.ModDirectory = BasePath.FullName;
Config.Save();
}
if( !firstTime )
{
Collections.RecreateCaches();
}
}
}
public ModManager()
{
SetBaseDirectory( Config.ModDirectory, true );
Collections = new CollectionManager( this );
}
private bool SetSortOrderPath( ModData mod, string path )
{
mod.Move( path );
var fixedPath = mod.SortOrder.FullPath;
if( !fixedPath.Any() || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
{
Config.ModSortOrder.Remove( mod.BasePath.Name );
return true;
}
if( path != fixedPath )
{
Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath;
return true;
}
return false;
}
private void SetModStructure( bool removeOldPaths = false )
{
var changes = false;
foreach( var kvp in Config.ModSortOrder.ToArray() )
{
if( kvp.Value.Any() && Mods.TryGetValue( kvp.Key, out var mod ) )
{
changes |= SetSortOrderPath( mod, kvp.Value );
}
else if( removeOldPaths )
{
changes = true;
Config.ModSortOrder.Remove( kvp.Key );
}
}
private static bool CheckTmpDir( string newPath, out DirectoryInfo tmpDir )
if( changes )
{
tmpDir = new DirectoryInfo( Path.Combine( newPath, MetaManager.TmpDirectory ) );
try
Config.Save();
}
}
public void DiscoverMods()
{
Mods.Clear();
BasePath.Refresh();
StructuredMods.SubFolders.Clear();
StructuredMods.Mods.Clear();
if( Valid && BasePath.Exists )
{
foreach( var modFolder in BasePath.EnumerateDirectories() )
{
if( tmpDir.Exists )
var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null )
{
tmpDir.Delete( true );
tmpDir.Refresh();
continue;
}
Directory.CreateDirectory( tmpDir.FullName );
tmpDir.Refresh();
return true;
Mods.Add( modFolder.Name, mod );
}
SetModStructure();
}
Collections.RecreateCaches();
}
public void DeleteMod( DirectoryInfo modFolder )
{
modFolder.Refresh();
if( modFolder.Exists )
{
try
{
Directory.Delete( modFolder.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not create temporary directory {tmpDir.FullName}:\n{e}" );
return false;
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
}
if( Mods.TryGetValue( modFolder.Name, out var mod ) )
{
mod.SortOrder.ParentFolder.RemoveMod( mod );
Mods.Remove( modFolder.Name );
Collections.RemoveModFromCaches( modFolder );
}
}
}
private void SetBaseDirectory( string newPath, bool firstTime )
public bool AddMod( DirectoryInfo modFolder )
{
var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null )
{
if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
{
return;
}
if( !newPath.Any() )
{
Valid = false;
BasePath = new DirectoryInfo( "." );
}
else
{
var newDir = new DirectoryInfo( newPath );
if( !newDir.Exists )
{
try
{
Directory.CreateDirectory( newDir.FullName );
newDir.Refresh();
}
catch( Exception e )
{
PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" );
}
}
BasePath = newDir;
Valid = true;
if( Config.ModDirectory != BasePath.FullName )
{
Config.ModDirectory = BasePath.FullName;
Config.Save();
}
if( !Config.TempDirectory.Any() )
{
if( CheckTmpDir( BasePath.FullName, out var newTmpDir ) )
{
if( !firstTime )
{
ClearOldTmpDir();
}
TempPath = newTmpDir;
TempWritable = true;
}
else
{
TempWritable = false;
}
}
if( !firstTime )
{
Collections.RecreateCaches();
}
}
}
private void SetTempDirectory( string newPath, bool firstTime )
{
if( !Valid || !firstTime && string.Equals( newPath, Config.TempDirectory, StringComparison.InvariantCultureIgnoreCase ) )
{
return;
}
if( !newPath.Any() && CheckTmpDir( BasePath.FullName, out var newTmpDir )
|| newPath.Any() && CheckTmpDir( newPath, out newTmpDir ) )
{
if( !firstTime )
{
ClearOldTmpDir();
}
TempPath = newTmpDir;
TempWritable = true;
var newName = newPath.Any() ? TempPath.Parent!.FullName : string.Empty;
if( Config.TempDirectory != newName )
{
Config.TempDirectory = newName;
Config.Save();
}
if( !firstTime )
{
Collections.RecreateCaches();
}
}
else
{
TempWritable = false;
}
}
public void SetTempDirectory( string newPath )
=> SetTempDirectory( newPath, false );
public ModManager()
{
SetBaseDirectory( Config.ModDirectory, true );
SetTempDirectory( Config.TempDirectory, true );
Collections = new CollectionManager( this );
}
private bool SetSortOrderPath( ModData mod, string path )
{
mod.Move( path );
var fixedPath = mod.SortOrder.FullPath;
if( !fixedPath.Any() || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
{
Config.ModSortOrder.Remove( mod.BasePath.Name );
return true;
}
if( path != fixedPath )
{
Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath;
return true;
}
return false;
}
private void SetModStructure( bool removeOldPaths = false )
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
var changes = false;
foreach( var kvp in Config.ModSortOrder.ToArray() )
{
if( kvp.Value.Any() && Mods.TryGetValue( kvp.Key, out var mod ) )
{
changes |= SetSortOrderPath( mod, kvp.Value );
}
else if( removeOldPaths )
{
changes = true;
Config.ModSortOrder.Remove( kvp.Key );
}
}
if( changes )
if( SetSortOrderPath( mod, sortOrder ) )
{
Config.Save();
}
}
public void DiscoverMods()
if( Mods.ContainsKey( modFolder.Name ) )
{
Mods.Clear();
BasePath.Refresh();
StructuredMods.SubFolders.Clear();
StructuredMods.Mods.Clear();
if( Valid && BasePath.Exists )
{
foreach( var modFolder in BasePath.EnumerateDirectories() )
{
var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null )
{
continue;
}
Mods.Add( modFolder.Name, mod );
}
SetModStructure();
}
Collections.RecreateCaches();
return false;
}
public void DeleteMod( DirectoryInfo modFolder )
Mods.Add( modFolder.Name, mod );
foreach( var collection in Collections.Collections.Values )
{
modFolder.Refresh();
if( modFolder.Exists )
{
try
{
Directory.Delete( modFolder.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
}
if( Mods.TryGetValue( modFolder.Name, out var mod ) )
{
mod.SortOrder.ParentFolder.RemoveMod( mod );
Mods.Remove( modFolder.Name );
Collections.RemoveModFromCaches( modFolder );
}
}
collection.AddMod( mod );
}
public bool AddMod( DirectoryInfo modFolder )
{
var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null )
{
return false;
}
return true;
}
public bool UpdateMod( ModData mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
{
var oldName = mod.Meta.Name;
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force;
var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath );
if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 )
{
return false;
}
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
{
mod.ComputeChangedItems();
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
if( SetSortOrderPath( mod, sortOrder ) )
mod.Move( sortOrder );
var path = mod.SortOrder.FullPath;
if( path != sortOrder )
{
Config.ModSortOrder[ mod.BasePath.Name ] = path;
Config.Save();
}
}
if( Mods.ContainsKey( modFolder.Name ) )
else
{
return false;
mod.SortOrder = new SortOrder( StructuredMods, mod.Meta.Name );
}
Mods.Add( modFolder.Name, mod );
foreach( var collection in Collections.Collections.Values )
{
collection.AddMod( mod );
}
return true;
}
public bool UpdateMod( ModData mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
if( recomputeMeta )
{
var oldName = mod.Meta.Name;
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force;
var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath );
if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 )
{
return false;
}
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
{
mod.ComputeChangedItems();
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
mod.Move( sortOrder );
var path = mod.SortOrder.FullPath;
if( path != sortOrder )
{
Config.ModSortOrder[ mod.BasePath.Name ] = path;
Config.Save();
}
}
else
{
mod.SortOrder = new SortOrder( StructuredMods, mod.Meta.Name );
}
}
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
if( recomputeMeta )
{
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
}
Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, reloadMeta );
return true;
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
}
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
{
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
return ret;
}
Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, reloadMeta );
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
// {
// #if DEBUG
// PluginLog.Verbose( "file changed: {FullPath}", e.FullPath );
// #endif
//
// if( _plugin.ImportInProgress )
// {
// return;
// }
//
// if( _plugin.Configuration.DisableFileSystemNotifications )
// {
// return;
// }
//
// var file = e.FullPath;
//
// if( !ResolvedFiles.Any( x => x.Value.FullName == file ) )
// {
// return;
// }
//
// PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file );
// _plugin.GameUtils.ReloadPlayerResources();
// }
//
// private void FileSystemPasta()
// {
// haha spaghet
// _fileSystemWatcher?.Dispose();
// _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName )
// {
// NotifyFilter = NotifyFilters.LastWrite |
// NotifyFilters.FileName |
// NotifyFilters.DirectoryName,
// IncludeSubdirectories = true,
// EnableRaisingEvents = true
// };
//
// _fileSystemWatcher.Changed += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Created += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Deleted += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Renamed += FileSystemWatcherOnChanged;
// }
return true;
}
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
{
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
return ret;
}
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
// {
// #if DEBUG
// PluginLog.Verbose( "file changed: {FullPath}", e.FullPath );
// #endif
//
// if( _plugin.ImportInProgress )
// {
// return;
// }
//
// if( _plugin.Configuration.DisableFileSystemNotifications )
// {
// return;
// }
//
// var file = e.FullPath;
//
// if( !ResolvedFiles.Any( x => x.Value.FullName == file ) )
// {
// return;
// }
//
// PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file );
// _plugin.GameUtils.ReloadPlayerResources();
// }
//
// private void FileSystemPasta()
// {
// haha spaghet
// _fileSystemWatcher?.Dispose();
// _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName )
// {
// NotifyFilter = NotifyFilters.LastWrite |
// NotifyFilters.FileName |
// NotifyFilters.DirectoryName,
// IncludeSubdirectories = true,
// EnableRaisingEvents = true
// };
//
// _fileSystemWatcher.Changed += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Created += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Deleted += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Renamed += FileSystemWatcherOnChanged;
// }
}

View file

@ -204,7 +204,7 @@ public static class ModManagerEditExtensions
collection.Save();
if( collection.Cache != null && settings.Enabled )
{
collection.CalculateEffectiveFileList( manager.TempPath, mod.Resources.MetaManipulations.Count > 0,
collection.CalculateEffectiveFileList( mod.Resources.MetaManipulations.Count > 0,
collection == manager.Collections.ActiveCollection );
}
}

View file

@ -18,10 +18,10 @@ using System.Linq;
using Penumbra.Meta.Manipulations;
namespace Penumbra;
public class MetaDefaults
{
}
public class MetaDefaults
{ }
public class Penumbra : IDalamudPlugin
{
public string Name
@ -41,8 +41,7 @@ public class Penumbra : IDalamudPlugin
public static MetaDefaults MetaDefaults { get; private set; } = null!;
public static ModManager ModManager { get; private set; } = null!;
public ResourceLoader ResourceLoader { get; }
public static ResourceLoader ResourceLoader { get; set; } = null!;
public ResourceLogger ResourceLogger { get; }
//public PathResolver PathResolver { get; }
@ -113,12 +112,20 @@ public class Penumbra : IDalamudPlugin
};
ResourceLoader.EnableHooks();
if (Config.EnableMods)
if( Config.EnableMods )
{
ResourceLoader.EnableReplacements();
if (Config.DebugMode)
}
if( Config.DebugMode )
{
ResourceLoader.EnableDebug();
if (Config.EnableFullResourceLogging)
}
if( Config.EnableFullResourceLogging )
{
ResourceLoader.EnableFullLogging();
}
unsafe
{

View file

@ -20,6 +20,7 @@ using Penumbra.Meta;
using Penumbra.Mods;
using Penumbra.UI.Custom;
using Penumbra.Util;
using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility;
using ResourceHandle = Penumbra.Interop.Structs.ResourceHandle;
using Utf8String = Penumbra.GameData.ByteString.Utf8String;
@ -164,12 +165,6 @@ public partial class SettingsInterface
PrintValue( "Mod Manager BasePath Exists",
manager.BasePath != null ? Directory.Exists( manager.BasePath.FullName ).ToString() : false.ToString() );
PrintValue( "Mod Manager Valid", manager.Valid.ToString() );
PrintValue( "Mod Manager Temp Path", manager.TempPath?.FullName ?? "NULL" );
PrintValue( "Mod Manager Temp Path IsRooted",
( !Penumbra.Config.TempDirectory.Any() || Path.IsPathRooted( Penumbra.Config.TempDirectory ) ).ToString() );
PrintValue( "Mod Manager Temp Path Exists",
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() );
}
@ -273,43 +268,6 @@ public partial class SettingsInterface
}
}
private static void DrawDebugTabTempFiles()
{
if( !ImGui.CollapsingHeader( "Temporary Files##Debug" ) )
{
return;
}
if( !ImGui.BeginTable( "##tempFileTable", 4, ImGuiTableFlags.SizingFixedFit ) )
{
return;
}
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable );
foreach( var collection in Penumbra.ModManager.Collections.Collections.Values.Where( c => c.Cache != null ) )
{
var manip = collection.Cache!.MetaManipulations;
var files = ( Dictionary< GamePath, MetaManager.FileInformation >? )manip.GetType()
.GetField( "_currentFiles", BindingFlags.NonPublic | BindingFlags.Instance )?.GetValue( manip )
?? new Dictionary< GamePath, MetaManager.FileInformation >();
foreach( var (file, info) in files )
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text( info.CurrentFile?.FullName ?? "None" );
ImGui.TableNextColumn();
ImGui.Text( file );
ImGui.TableNextColumn();
ImGui.Text( info.CurrentFile?.Exists ?? false ? "Exists" : "Missing" );
ImGui.TableNextColumn();
ImGui.Text( info.Changed ? "Data Changed" : "Unchanged" );
}
}
}
private void DrawDebugTabIpc()
{
if( !ImGui.CollapsingHeader( "IPC##Debug" ) )
@ -369,7 +327,7 @@ public partial class SettingsInterface
return;
}
var cache = Penumbra.ModManager.Collections.CurrentCollection.Cache;
var cache = Penumbra.ModManager.Collections.CurrentCollection.Cache;
if( cache == null || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) )
{
return;
@ -397,9 +355,9 @@ public partial class SettingsInterface
return;
}
_penumbra.ResourceLoader.UpdateDebugInfo();
Penumbra.ResourceLoader.UpdateDebugInfo();
if( _penumbra.ResourceLoader.DebugList.Count == 0
if( Penumbra.ResourceLoader.DebugList.Count == 0
|| !ImGui.BeginTable( "##ReplacedResourcesDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) )
{
return;
@ -407,7 +365,7 @@ public partial class SettingsInterface
using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable );
foreach( var data in _penumbra.ResourceLoader.DebugList.Values.ToArray() )
foreach( var data in Penumbra.ResourceLoader.DebugList.Values.ToArray() )
{
var refCountManip = data.ManipulatedResource == null ? 0 : data.ManipulatedResource->RefCount;
var refCountOrig = data.OriginalResource == null ? 0 : data.OriginalResource->RefCount;
@ -426,6 +384,52 @@ public partial class SettingsInterface
}
}
public unsafe void DrawDebugCharacterUtility()
{
if( !ImGui.CollapsingHeader( "Character Utility##Debug" ) )
{
return;
}
if( !ImGui.BeginTable( "##CharacterUtilityDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) )
{
return;
}
using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable );
for( var i = 0; i < CharacterUtility.NumRelevantResources; ++i )
{
var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ i ];
ImGui.TableNextColumn();
ImGui.Text( $"0x{( ulong )resource:X}" );
ImGui.TableNextColumn();
ImGuiNative.igTextUnformatted( resource->FileName(), resource->FileName() + resource->FileNameLength );
ImGui.TableNextColumn();
ImGui.Text( $"0x{resource->GetData().Data:X}" );
if( ImGui.IsItemClicked() )
{
var (data, length) = resource->GetData();
ImGui.SetClipboardText( string.Join( " ",
new ReadOnlySpan< byte >( ( byte* )data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
}
ImGui.TableNextColumn();
ImGui.Text( $"{resource->GetData().Length}" );
ImGui.TableNextColumn();
ImGui.Text( $"0x{Penumbra.CharacterUtility.DefaultResources[ i ].Address:X}" );
if( ImGui.IsItemClicked() )
{
ImGui.SetClipboardText( string.Join( " ",
new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResources[ i ].Address,
Penumbra.CharacterUtility.DefaultResources[ i ].Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
}
ImGui.TableNextColumn();
ImGui.Text( $"{Penumbra.CharacterUtility.DefaultResources[ i ].Size}" );
}
}
private unsafe void DrawPathResolverDebug()
{
if( !ImGui.CollapsingHeader( "Path Resolver##Debug" ) )
@ -479,6 +483,14 @@ public partial class SettingsInterface
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem );
if( !ImGui.BeginChild( "##DebugChild", -Vector2.One ) )
{
ImGui.EndChild();
return;
}
raii.Push( ImGui.EndChild );
DrawDebugTabGeneral();
ImGui.NewLine();
DrawDebugTabReplacedResources();
@ -491,12 +503,12 @@ public partial class SettingsInterface
ImGui.NewLine();
DrawPathResolverDebug();
ImGui.NewLine();
DrawDebugCharacterUtility();
ImGui.NewLine();
DrawDebugTabRedraw();
ImGui.NewLine();
DrawDebugTabPlayers();
ImGui.NewLine();
DrawDebugTabTempFiles();
ImGui.NewLine();
DrawDebugTabIpc();
ImGui.NewLine();
}

View file

@ -192,8 +192,9 @@ public partial class SettingsInterface
}
else if( ( row -= activeResolved ) < activeMeta )
{
var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( manip.ToString(), mod.Data.Meta.Name );
// TODO
//var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( 0.ToString(), 0.ToString() );
}
else if( ( row -= activeMeta ) < forcedResolved )
{
@ -202,9 +203,10 @@ public partial class SettingsInterface
}
else
{
// TODO
row -= forcedResolved;
var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( manip.ToString(), mod.Data.Meta.Name );
//var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( 0.ToString(), 0.ToString() );
}
}
}

View file

@ -426,8 +426,7 @@ public partial class SettingsInterface
foreach( var collection in modManager.Collections.Collections.Values
.Where( c => c.Cache != null && c.Settings[ Mod!.Data.BasePath.Name ].Enabled ) )
{
collection.CalculateEffectiveFileList( modManager.TempPath, false,
collection == modManager.Collections.ActiveCollection );
collection.CalculateEffectiveFileList( false, collection == modManager.Collections.ActiveCollection );
}
// If the mod is enabled in the current collection, its conflicts may have changed.

View file

@ -1,18 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using ImGuiNET;
using Lumina.Data.Files;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
using Penumbra.UI.Custom;
using Penumbra.Util;
using ObjectType = Penumbra.GameData.Enums.ObjectType;
namespace Penumbra.UI
@ -255,7 +249,6 @@ namespace Penumbra.UI
private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list )
{
var defaults = ( GmpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
var ret = false;
//var id = list[ manipIdx ].GmpIdentifier;
//var val = list[ manipIdx ].GmpValue;
@ -365,7 +358,6 @@ namespace Penumbra.UI
private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list )
{
var defaults = ( EqdpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
var ret = false;
//var id = list[ manipIdx ].EqdpIdentifier;
//var val = list[ manipIdx ].EqdpValue;
@ -402,7 +394,6 @@ namespace Penumbra.UI
private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list )
{
var defaults = ( ushort )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
var ret = false;
//var id = list[ manipIdx ].EstIdentifier;
//var val = list[ manipIdx ].EstValue;
@ -434,7 +425,6 @@ namespace Penumbra.UI
private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list )
{
var defaults = ( ImcFile.ImageChangeData )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
var ret = false;
//var id = list[ manipIdx ].ImcIdentifier;
//var val = list[ manipIdx ].ImcValue;
@ -493,7 +483,6 @@ namespace Penumbra.UI
private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list )
{
var defaults = ( float )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
var ret = false;
//var id = list[ manipIdx ].RspIdentifier;
//var val = list[ manipIdx ].RspValue;

View file

@ -529,8 +529,7 @@ public partial class SettingsInterface
var collection = Penumbra.ModManager.Collections.CurrentCollection;
if( collection.Cache != null )
{
collection.CalculateEffectiveFileList( Penumbra.ModManager.TempPath, metaManips,
collection == Penumbra.ModManager.Collections.ActiveCollection );
collection.CalculateEffectiveFileList( metaManips, collection == Penumbra.ModManager.Collections.ActiveCollection );
}
collection.Save();

View file

@ -82,7 +82,7 @@ public partial class SettingsInterface
if( ImGui.IsItemClicked() )
{
var data = ( ( Interop.Structs.ResourceHandle* )r )->GetData();
ImGui.SetClipboardText( ((IntPtr)( ( Interop.Structs.ResourceHandle* )r )->Data->VTable).ToString("X") + string.Join( " ",
ImGui.SetClipboardText( string.Join( " ",
new ReadOnlySpan< byte >( ( byte* )data.Data, data.Length ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
//ImGuiNative.igSetClipboardText( ( byte* )Structs.ResourceHandle.GetData( ( IntPtr )r ) );
}

View file

@ -24,8 +24,6 @@ public partial class SettingsInterface
private readonly Configuration _config;
private bool _configChanged;
private string _newModDirectory;
private string _newTempDirectory;
public TabSettings( SettingsInterface ui )
{
@ -33,7 +31,6 @@ public partial class SettingsInterface
_config = Penumbra.Config;
_configChanged = false;
_newModDirectory = _config.ModDirectory;
_newTempDirectory = _config.TempDirectory;
}
private static bool DrawPressEnterWarning( string old )
@ -91,33 +88,6 @@ public partial class SettingsInterface
}
}
private void DrawTempFolder()
{
ImGui.BeginGroup();
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
var save = ImGui.InputText( "Temp Directory", ref _newTempDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue );
ImGui.SameLine();
ImGuiComponents.HelpMarker( "This is where Penumbra will store temporary meta manipulation files.\n"
+ "Leave this blank if you have no reason not to.\n"
+ "A directory 'penumbrametatmp' will be created as a sub-directory to the specified directory.\n"
+ "If none is specified (i.e. this is blank) this directory will be created in the root directory instead.\n" );
ImGui.SameLine();
var modManager = Penumbra.ModManager;
DrawOpenDirectoryButton( 1, modManager.TempPath, modManager.TempWritable );
ImGui.EndGroup();
if( _newTempDirectory == _config.TempDirectory )
{
return;
}
if( save || DrawPressEnterWarning( _config.TempDirectory ) )
{
modManager.SetTempDirectory( _newTempDirectory );
_newTempDirectory = _config.TempDirectory;
}
}
private void DrawRediscoverButton()
{
if( ImGui.Button( "Rediscover Mods" ) )
@ -326,11 +296,11 @@ public partial class SettingsInterface
{
if( tmp )
{
_base._penumbra.ResourceLoader.EnableFullLogging();
Penumbra.ResourceLoader.EnableFullLogging();
}
else
{
_base._penumbra.ResourceLoader.DisableFullLogging();
Penumbra.ResourceLoader.DisableFullLogging();
}
_config.EnableFullResourceLogging = tmp;
@ -348,11 +318,11 @@ public partial class SettingsInterface
{
if( tmp )
{
_base._penumbra.ResourceLoader.EnableDebug();
Penumbra.ResourceLoader.EnableDebug();
}
else
{
_base._penumbra.ResourceLoader.DisableDebug();
Penumbra.ResourceLoader.DisableDebug();
}
_config.DebugMode = tmp;
@ -388,7 +358,6 @@ public partial class SettingsInterface
private void DrawAdvancedSettings()
{
DrawTempFolder();
DrawRequestedResourceLogging();
DrawDisableSoundStreamingBox();
DrawLogLoadedFilesBox();

View file

@ -68,7 +68,7 @@ public partial class SettingsInterface : IDisposable
var current = modManager.Collections.CurrentCollection;
if( current.Cache != null )
{
current.CalculateEffectiveFileList( modManager.TempPath, recalculateMeta,
current.CalculateEffectiveFileList( recalculateMeta,
current == modManager.Collections.ActiveCollection );
_menu.InstalledTab.Selector.Cache.TriggerFilterReset();
}