mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
tmp
This commit is contained in:
parent
46581780e0
commit
de082439a4
28 changed files with 766 additions and 835 deletions
|
|
@ -35,7 +35,6 @@ public class Configuration : IPluginConfiguration
|
||||||
public int WaitFrames { get; set; } = 30;
|
public int WaitFrames { get; set; } = 30;
|
||||||
|
|
||||||
public string ModDirectory { get; set; } = string.Empty;
|
public string ModDirectory { get; set; } = string.Empty;
|
||||||
public string TempDirectory { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string CurrentCollection { get; set; } = "Default";
|
public string CurrentCollection { get; set; } = "Default";
|
||||||
public string DefaultCollection { get; set; } = "Default";
|
public string DefaultCollection { get; set; } = "Default";
|
||||||
|
|
|
||||||
|
|
@ -256,11 +256,10 @@ public class TexToolsMeta
|
||||||
ushort i = 0;
|
ushort i = 0;
|
||||||
if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory )
|
if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory )
|
||||||
{
|
{
|
||||||
|
// TODO check against default.
|
||||||
foreach( var value in values )
|
foreach( var value in values )
|
||||||
{
|
{
|
||||||
ImcEntry def;
|
ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) );
|
||||||
if( !value.Equals( def ) )
|
|
||||||
ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) );
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -268,55 +267,64 @@ public class TexToolsMeta
|
||||||
{
|
{
|
||||||
foreach( var value in values )
|
foreach( var value in values )
|
||||||
{
|
{
|
||||||
ImcEntry def;
|
ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) );
|
||||||
if( !value.Equals( def ) )
|
|
||||||
ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) );
|
|
||||||
++i;
|
++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 )
|
public TexToolsMeta( byte[] data )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//using var reader = new BinaryReader( new MemoryStream( data ) );
|
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||||
//Version = reader.ReadUInt32();
|
Version = reader.ReadUInt32();
|
||||||
//FilePath = ReadNullTerminated( reader );
|
FilePath = ReadNullTerminated( reader );
|
||||||
//var metaInfo = new Info( FilePath );
|
var metaInfo = new Info( FilePath );
|
||||||
//var numHeaders = reader.ReadUInt32();
|
var numHeaders = reader.ReadUInt32();
|
||||||
//var headerSize = reader.ReadUInt32();
|
var headerSize = reader.ReadUInt32();
|
||||||
//var headerStart = reader.ReadUInt32();
|
var headerStart = reader.ReadUInt32();
|
||||||
//reader.BaseStream.Seek( headerStart, SeekOrigin.Begin );
|
reader.BaseStream.Seek( headerStart, SeekOrigin.Begin );
|
||||||
//
|
|
||||||
//List< (MetaType type, uint offset, int size) > entries = new();
|
List< (MetaManipulation.Type type, uint offset, int size) > entries = new();
|
||||||
//for( var i = 0; i < numHeaders; ++i )
|
for( var i = 0; i < numHeaders; ++i )
|
||||||
//{
|
{
|
||||||
// var currentOffset = reader.BaseStream.Position;
|
var currentOffset = reader.BaseStream.Position;
|
||||||
// var type = ( MetaType )reader.ReadUInt32();
|
var type = ( MetaManipulation.Type )reader.ReadUInt32();
|
||||||
// var offset = reader.ReadUInt32();
|
var offset = reader.ReadUInt32();
|
||||||
// var size = reader.ReadInt32();
|
var size = reader.ReadInt32();
|
||||||
// entries.Add( ( type, offset, size ) );
|
entries.Add( ( type, offset, size ) );
|
||||||
// reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin );
|
reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin );
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//byte[]? ReadEntry( MetaType type )
|
byte[]? ReadEntry( MetaManipulation.Type type )
|
||||||
//{
|
{
|
||||||
// var idx = entries.FindIndex( t => t.type == type );
|
var idx = entries.FindIndex( t => t.type == type );
|
||||||
// if( idx < 0 )
|
if( idx < 0 )
|
||||||
// {
|
{
|
||||||
// return null;
|
return null;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
|
reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
|
||||||
// return reader.ReadBytes( entries[ idx ].size );
|
return reader.ReadBytes( entries[ idx ].size );
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) );
|
DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) );
|
||||||
//DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) );
|
DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) );
|
||||||
//DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) );
|
DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) );
|
||||||
//DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) );
|
DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) );
|
||||||
//DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) );
|
DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
|
|
@ -362,28 +370,35 @@ public class TexToolsMeta
|
||||||
return Invalid;
|
return Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if( gender == 1 )
|
void Add( RspAttribute attribute, float value )
|
||||||
//{
|
{
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinSize, br.ReadSingle() ) );
|
var def = CmpFile.GetDefault( subRace, attribute );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxSize, br.ReadSingle() ) );
|
if (value != def)
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinTail, br.ReadSingle() ) );
|
ret!.RspManipulations.Add(new RspManipulation(subRace, attribute, value));
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxTail, br.ReadSingle() ) );
|
}
|
||||||
//
|
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinX, br.ReadSingle() ) );
|
if( gender == 1 )
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinY, br.ReadSingle() ) );
|
{
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinZ, br.ReadSingle() ) );
|
Add(RspAttribute.FemaleMinSize, br.ReadSingle() );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxX, br.ReadSingle() ) );
|
Add(RspAttribute.FemaleMaxSize, br.ReadSingle() );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxY, br.ReadSingle() ) );
|
Add(RspAttribute.FemaleMinTail, br.ReadSingle() );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxZ, br.ReadSingle() ) );
|
Add(RspAttribute.FemaleMaxTail, br.ReadSingle() );
|
||||||
//}
|
|
||||||
//else
|
Add(RspAttribute.BustMinX, br.ReadSingle() );
|
||||||
//{
|
Add(RspAttribute.BustMinY, br.ReadSingle() );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinSize, br.ReadSingle() ) );
|
Add(RspAttribute.BustMinZ, br.ReadSingle() );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxSize, br.ReadSingle() ) );
|
Add(RspAttribute.BustMaxX, br.ReadSingle() );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinTail, br.ReadSingle() ) );
|
Add(RspAttribute.BustMaxY, br.ReadSingle() );
|
||||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxTail, 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;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,12 +20,20 @@ public unsafe class CharacterUtility : IDisposable
|
||||||
public Structs.CharacterUtility* Address
|
public Structs.CharacterUtility* Address
|
||||||
=> *_characterUtilityAddress;
|
=> *_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()
|
public CharacterUtility()
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise( this );
|
SignatureHelper.Initialise( this );
|
||||||
LoadDataFilesHook.Enable();
|
|
||||||
|
if( Address->EqpResource != null )
|
||||||
|
{
|
||||||
|
LoadDefaultResources();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LoadDataFilesHook.Enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-disabling hook to set default resources after loading them.
|
// 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.
|
// We store the default data of the resources so we can always restore them.
|
||||||
private void LoadDefaultResources()
|
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 ];
|
var resource = ( Structs.ResourceHandle* )Address->Resources[ i ];
|
||||||
DefaultResources[ i ] = resource->GetData();
|
DefaultResources[ i ] = resource->GetData();
|
||||||
|
|
@ -65,7 +73,7 @@ public unsafe class CharacterUtility : IDisposable
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
for( var i = 0; i < Structs.CharacterUtility.NumResources; ++i )
|
for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i )
|
||||||
{
|
{
|
||||||
ResetResource( i );
|
ResetResource( i );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,7 @@ using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Mods;
|
|
||||||
using Penumbra.Util;
|
|
||||||
using FileMode = Penumbra.Interop.Structs.FileMode;
|
using FileMode = Penumbra.Interop.Structs.FileMode;
|
||||||
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
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 );
|
ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
|
||||||
FileLoaded?.Invoke( gamePath.Path, ret != 0, false );
|
FileLoaded?.Invoke( gamePath.Path, ret != 0, false );
|
||||||
}
|
}
|
||||||
|
else if( ResourceLoadCustomization != null && gamePath.Path[0] == (byte) '|' )
|
||||||
|
{
|
||||||
|
ret = ResourceLoadCustomization.Invoke( gamePath, resourceManager, fileDescriptor, priority, isSync );
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Specify that we are loading unpacked files from the drive.
|
// Specify that we are loading unpacked files from the drive.
|
||||||
|
|
@ -150,11 +151,18 @@ public unsafe partial class ResourceLoader
|
||||||
return ret;
|
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.
|
// Use the default method of path replacement.
|
||||||
public static (FullPath?, object?) DefaultReplacer( Utf8GamePath path )
|
public static (FullPath?, object?) DefaultReplacer( Utf8GamePath path )
|
||||||
{
|
{
|
||||||
var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path );
|
var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path );
|
||||||
return( resolved, null );
|
return ( resolved, null );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeHooks()
|
private void DisposeHooks()
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,16 @@ namespace Penumbra.Interop.Structs;
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
public unsafe struct CharacterUtility
|
public unsafe struct CharacterUtility
|
||||||
{
|
{
|
||||||
public const int NumResources = 85;
|
public const int NumResources = 85;
|
||||||
public const int EqpIdx = 0;
|
public const int NumRelevantResources = 68;
|
||||||
public const int GmpIdx = 1;
|
public const int EqpIdx = 0;
|
||||||
public const int HumanCmpIdx = 63;
|
public const int GmpIdx = 1;
|
||||||
public const int FaceEstIdx = 64;
|
public const int HumanCmpIdx = 63;
|
||||||
public const int HairEstIdx = 65;
|
public const int FaceEstIdx = 64;
|
||||||
public const int BodyEstIdx = 66;
|
public const int HairEstIdx = 65;
|
||||||
public const int HeadEstIdx = 67;
|
public const int BodyEstIdx = 66;
|
||||||
public const int NumEqdpFiles = 2 * 28;
|
public const int HeadEstIdx = 67;
|
||||||
|
public const int NumEqdpFiles = 2 * 28;
|
||||||
|
|
||||||
public static int EqdpIdx( GenderRace raceCode, bool accessory )
|
public static int EqdpIdx( GenderRace raceCode, bool accessory )
|
||||||
=> ( accessory ? 28 : 0 )
|
=> ( accessory ? 28 : 0 )
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||||
{
|
{
|
||||||
var ptr = ( byte* )DefaultData.Data;
|
var ptr = ( byte* )DefaultData.Data;
|
||||||
var controlBlock = *( ulong* )ptr;
|
var controlBlock = *( ulong* )ptr;
|
||||||
*( ulong* )ptr = ulong.MaxValue;
|
*( ulong* )Data = ulong.MaxValue;
|
||||||
for( var i = 0; i < 64; ++i )
|
for( var i = 0; i < 64; ++i )
|
||||||
{
|
{
|
||||||
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
|
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Memory;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
public struct ImcEntry : IEquatable< ImcEntry >
|
public readonly struct ImcEntry : IEquatable< ImcEntry >
|
||||||
{
|
{
|
||||||
public byte MaterialId;
|
public readonly byte MaterialId;
|
||||||
public byte DecalId;
|
public readonly byte DecalId;
|
||||||
private ushort _attributeAndSound;
|
private readonly ushort _attributeAndSound;
|
||||||
public byte VfxId;
|
public readonly byte VfxId;
|
||||||
public byte MaterialAnimationId;
|
public readonly byte MaterialAnimationId;
|
||||||
|
|
||||||
public ushort AttributeMask
|
public ushort AttributeMask
|
||||||
=> ( ushort )( _attributeAndSound & 0x3FF );
|
=> ( ushort )( _attributeAndSound & 0x3FF );
|
||||||
|
|
@ -163,4 +165,26 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
|
|
@ -23,14 +24,15 @@ public unsafe class MetaBaseFile : IDisposable
|
||||||
protected void AllocateData( int length )
|
protected void AllocateData( int length )
|
||||||
{
|
{
|
||||||
Length = length;
|
Length = length;
|
||||||
Data = ( byte* )Marshal.AllocHGlobal( length );
|
Data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )length ); ;
|
||||||
GC.AddMemoryPressure( length );
|
GC.AddMemoryPressure( length );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free memory.
|
// Free memory.
|
||||||
protected void ReleaseUnmanagedResources()
|
protected void ReleaseUnmanagedResources()
|
||||||
{
|
{
|
||||||
Marshal.FreeHGlobal( ( IntPtr )Data );
|
var ptr = ( IntPtr )Data;
|
||||||
|
MemoryHelper.GameFree( ref ptr, (ulong) Length );
|
||||||
GC.RemoveMemoryPressure( Length );
|
GC.RemoveMemoryPressure( Length );
|
||||||
Length = 0;
|
Length = 0;
|
||||||
Data = null;
|
Data = null;
|
||||||
|
|
|
||||||
|
|
@ -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" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Meta.Files;
|
using Penumbra.Meta.Files;
|
||||||
using ImcFile = Lumina.Data.Files.ImcFile;
|
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
|
@ -58,4 +58,33 @@ public readonly struct ImcManipulation : IEquatable< ImcManipulation >
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> HashCode.Combine( PrimaryId, Variant, SecondaryId, ( int )ObjectType, ( int )EquipSlot, ( int )BodySlot );
|
=> 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 );
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Penumbra.GameData.Util;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )]
|
[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
|
public enum Type : byte
|
||||||
{
|
{
|
||||||
|
|
@ -106,4 +107,10 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >
|
||||||
Type.Imc => Imc.GetHashCode(),
|
Type.Imc => Imc.GetHashCode(),
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public unsafe int CompareTo( MetaManipulation other )
|
||||||
|
{
|
||||||
|
fixed(MetaManipulation* lhs = &this)
|
||||||
|
return Functions.MemCmpUnchecked(lhs, &other, sizeof(MetaManipulation));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,14 +93,14 @@ public class MetaCollection
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) )
|
//if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) )
|
||||||
{
|
//{
|
||||||
return false;
|
// return false;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
} // TODO
|
||||||
|
|
||||||
return DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) );
|
return true; //DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-sort all manipulations.
|
// Re-sort all manipulations.
|
||||||
|
|
@ -117,31 +117,33 @@ public class MetaCollection
|
||||||
// Creates the option group and the option if necessary.
|
// Creates the option group and the option if necessary.
|
||||||
private void AddMeta( string group, string option, TexToolsMeta meta )
|
private void AddMeta( string group, string option, TexToolsMeta meta )
|
||||||
{
|
{
|
||||||
if( meta.Manipulations.Count == 0 )
|
var manipulations = meta.EqpManipulations.Select( m => new MetaManipulation( m ) )
|
||||||
{
|
.Concat( meta.EqdpManipulations.Select( m => new MetaManipulation( m ) ) )
|
||||||
return;
|
.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 )
|
if( group.Length == 0 )
|
||||||
{
|
{
|
||||||
DefaultData.AddRange( meta.Manipulations );
|
DefaultData.AddRange( manipulations );
|
||||||
}
|
}
|
||||||
else if( option.Length == 0 )
|
else if( option.Length == 0 )
|
||||||
{ }
|
{ }
|
||||||
else if( !GroupData.TryGetValue( group, out var options ) )
|
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 ) )
|
else if( !options.TryGetValue( option, out var list ) )
|
||||||
{
|
{
|
||||||
options.Add( option, meta.Manipulations.ToList() );
|
options.Add( option, manipulations );
|
||||||
}
|
}
|
||||||
else
|
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,
|
// 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,
|
_ => TexToolsMeta.Invalid,
|
||||||
};
|
};
|
||||||
|
|
||||||
if( metaData.FilePath == string.Empty || metaData.Manipulations.Count == 0 )
|
if( metaData.FilePath == string.Empty )
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,19 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Logging;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Lumina.Data.Files;
|
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.Interop;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Files;
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Meta.Manipulations;
|
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;
|
namespace Penumbra.Meta;
|
||||||
|
|
||||||
public struct TemporaryImcFile : IDisposable
|
|
||||||
{
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MetaManager2 : IDisposable
|
public class MetaManager2 : IDisposable
|
||||||
{
|
{
|
||||||
public readonly List< MetaBaseFile > ChangedData = new(7 + CharacterUtility.NumEqdpFiles);
|
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< EqdpManipulation, Mod.Mod > EqdpManipulations = new();
|
||||||
|
|
||||||
public readonly Dictionary< ImcManipulation, Mod.Mod > ImcManipulations = 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()
|
public void ResetEqp()
|
||||||
{
|
{
|
||||||
|
|
@ -95,10 +132,21 @@ public class MetaManager2 : IDisposable
|
||||||
EqdpManipulations.Clear();
|
EqdpManipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FullPath CreateImcPath( Utf8GamePath path )
|
||||||
|
{
|
||||||
|
var d = new DirectoryInfo( $":{_collection.Name}/" );
|
||||||
|
return new FullPath( d, new Utf8RelPath( path ) );
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetImc()
|
public void ResetImc()
|
||||||
{
|
{
|
||||||
foreach( var file in ImcFiles )
|
foreach( var (path, file) in ImcFiles )
|
||||||
|
{
|
||||||
|
_collection.Cache?.ResolvedFiles.Remove( path );
|
||||||
|
path.Dispose();
|
||||||
file.Dispose();
|
file.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
ImcFiles.Clear();
|
ImcFiles.Clear();
|
||||||
ImcManipulations.Clear();
|
ImcManipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +202,6 @@ public class MetaManager2 : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool ApplyMod( EqpManipulation m, Mod.Mod mod )
|
public bool ApplyMod( EqpManipulation m, Mod.Mod mod )
|
||||||
{
|
{
|
||||||
if( !EqpManipulations.TryAdd( m, mod ) )
|
if( !EqpManipulations.TryAdd( m, mod ) )
|
||||||
|
|
@ -247,16 +294,63 @@ public class MetaManager2 : IDisposable
|
||||||
return true;
|
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 ) )
|
if( !ImcManipulations.TryAdd( m, mod ) )
|
||||||
{
|
{
|
||||||
return false;
|
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;
|
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 )
|
public bool ApplyMod( MetaManipulation m, Mod.Mod mod )
|
||||||
{
|
{
|
||||||
return m.ManipulationType switch
|
return m.ManipulationType switch
|
||||||
|
|
@ -267,133 +361,7 @@ public class MetaManager2 : IDisposable
|
||||||
MetaManipulation.Type.Est => ApplyMod( m.Est, mod ),
|
MetaManipulation.Type.Est => ApplyMod( m.Est, mod ),
|
||||||
MetaManipulation.Type.Rsp => ApplyMod( m.Rsp, mod ),
|
MetaManipulation.Type.Rsp => ApplyMod( m.Rsp, mod ),
|
||||||
MetaManipulation.Type.Imc => ApplyMod( m.Imc, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -67,15 +67,9 @@ public class CollectionManager
|
||||||
|
|
||||||
public void RecreateCaches()
|
public void RecreateCaches()
|
||||||
{
|
{
|
||||||
if( !_manager.TempWritable )
|
|
||||||
{
|
|
||||||
PluginLog.Error( "No temporary directory available." );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
|
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();
|
CreateNecessaryCaches();
|
||||||
|
|
@ -176,15 +170,9 @@ public class CollectionManager
|
||||||
|
|
||||||
private void AddCache( ModCollection collection )
|
private void AddCache( ModCollection collection )
|
||||||
{
|
{
|
||||||
if( !_manager.TempWritable )
|
|
||||||
{
|
|
||||||
PluginLog.Error( "No tmp directory available." );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( collection.Cache == null && collection.Name != string.Empty )
|
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 ];
|
CurrentCollection = Collections[ ModCollection.DefaultCollection ];
|
||||||
if( CurrentCollection.Cache == null )
|
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;
|
config.CurrentCollection = ModCollection.DefaultCollection;
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,9 @@ public class ModCollection
|
||||||
return removeList.Length > 0;
|
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;
|
var changedSettings = false;
|
||||||
foreach( var mod in data )
|
foreach( var mod in data )
|
||||||
{
|
{
|
||||||
|
|
@ -89,7 +89,7 @@ public class ModCollection
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
CalculateEffectiveFileList( modDirectory, true, false );
|
CalculateEffectiveFileList( true, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearCache()
|
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,
|
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name,
|
||||||
withMetaManipulations, activeCollection );
|
withMetaManipulations, activeCollection );
|
||||||
Cache ??= new ModCollectionCache( Name, modDir );
|
Cache ??= new ModCollectionCache( this );
|
||||||
UpdateSettings( false );
|
UpdateSettings( false );
|
||||||
Cache.CalculateEffectiveFileList();
|
Cache.CalculateEffectiveFileList();
|
||||||
if( withMetaManipulations )
|
if( withMetaManipulations )
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ public class ModCollectionCache
|
||||||
private readonly SortedList< string, object? > _changedItems = new();
|
private readonly SortedList< string, object? > _changedItems = new();
|
||||||
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
|
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
|
||||||
public readonly HashSet< FullPath > MissingFiles = new();
|
public readonly HashSet< FullPath > MissingFiles = new();
|
||||||
public readonly HashSet< ulong > Checksums = new();
|
public readonly MetaManager2 MetaManipulations;
|
||||||
public readonly MetaManager MetaManipulations;
|
|
||||||
|
|
||||||
public IReadOnlyDictionary< string, object? > ChangedItems
|
public IReadOnlyDictionary< string, object? > ChangedItems
|
||||||
{
|
{
|
||||||
|
|
@ -39,8 +38,8 @@ public class ModCollectionCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModCollectionCache( string collectionName, DirectoryInfo tempDir )
|
public ModCollectionCache( ModCollection collection )
|
||||||
=> MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir );
|
=> MetaManipulations = new MetaManager2( collection );
|
||||||
|
|
||||||
private static void ResetFileSeen( int size )
|
private static void ResetFileSeen( int size )
|
||||||
{
|
{
|
||||||
|
|
@ -73,11 +72,6 @@ public class ModCollectionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
AddMetaFiles();
|
AddMetaFiles();
|
||||||
Checksums.Clear();
|
|
||||||
foreach( var file in ResolvedFiles )
|
|
||||||
{
|
|
||||||
Checksums.Add( file.Value.Crc64 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetChangedItems()
|
private void SetChangedItems()
|
||||||
|
|
@ -89,11 +83,10 @@ public class ModCollectionCache
|
||||||
|
|
||||||
try
|
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.
|
// 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();
|
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() );
|
identifier.Identify( _changedItems, resolved.ToGamePath() );
|
||||||
}
|
}
|
||||||
|
|
@ -265,18 +258,7 @@ public class ModCollectionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddMetaFiles()
|
private void AddMetaFiles()
|
||||||
{
|
=> MetaManipulations.ApplyImcFiles( ResolvedFiles );
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSwaps( Mod.Mod mod )
|
private void AddSwaps( Mod.Mod mod )
|
||||||
{
|
{
|
||||||
|
|
@ -304,12 +286,12 @@ public class ModCollectionCache
|
||||||
{
|
{
|
||||||
if( !MetaManipulations.TryGetValue( manip, out var oldMod ) )
|
if( !MetaManipulations.TryGetValue( manip, out var oldMod ) )
|
||||||
{
|
{
|
||||||
//MetaManipulations.ApplyMod( manip, mod );
|
MetaManipulations.ApplyMod( manip, mod );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mod.Cache.AddConflict( oldMod, manip );
|
mod.Cache.AddConflict( oldMod!, manip );
|
||||||
if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority )
|
if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod!.Settings.Priority )
|
||||||
{
|
{
|
||||||
oldMod.Cache.AddConflict( mod, manip );
|
oldMod.Cache.AddConflict( mod, manip );
|
||||||
}
|
}
|
||||||
|
|
@ -319,15 +301,13 @@ public class ModCollectionCache
|
||||||
|
|
||||||
public void UpdateMetaManipulations()
|
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 ) )
|
foreach( var mod in AvailableMods.Values.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) )
|
||||||
{
|
{
|
||||||
mod.Cache.ClearMetaConflicts();
|
mod.Cache.ClearMetaConflicts();
|
||||||
AddManipulations( mod );
|
AddManipulations( mod );
|
||||||
}
|
}
|
||||||
|
|
||||||
MetaManipulations.WriteNewFiles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveMod( DirectoryInfo basePath )
|
public void RemoveMod( DirectoryInfo basePath )
|
||||||
|
|
|
||||||
110
Penumbra/Mods/ModManager.Directory.cs
Normal file
110
Penumbra/Mods/ModManager.Directory.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,401 +4,297 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Mod;
|
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.
|
public DirectoryInfo BasePath { get; private set; } = null!;
|
||||||
// It also contains the CollectionManager that handles all collections.
|
|
||||||
public class ModManager
|
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!;
|
SetBaseDirectory( newDir, false );
|
||||||
public DirectoryInfo TempPath { get; private set; } = null!;
|
DiscoverMods();
|
||||||
|
}
|
||||||
|
|
||||||
public Dictionary< string, ModData > Mods { get; } = new();
|
private void SetBaseDirectory( string newPath, bool firstTime )
|
||||||
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
|
{
|
||||||
|
if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
|
||||||
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 )
|
|
||||||
{
|
{
|
||||||
SetBaseDirectory( newDir, false );
|
return;
|
||||||
DiscoverMods();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearOldTmpDir()
|
if( !newPath.Any() )
|
||||||
{
|
{
|
||||||
if( !TempWritable )
|
Valid = false;
|
||||||
{
|
BasePath = new DirectoryInfo( "." );
|
||||||
return;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
TempPath.Refresh();
|
var newDir = new DirectoryInfo( newPath );
|
||||||
if( TempPath.Exists )
|
if( !newDir.Exists )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
TempPath.Delete( true );
|
Directory.CreateDirectory( newDir.FullName );
|
||||||
|
newDir.Refresh();
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
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 ) );
|
Config.Save();
|
||||||
try
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
continue;
|
||||||
tmpDir.Refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory( tmpDir.FullName );
|
Mods.Add( modFolder.Name, mod );
|
||||||
tmpDir.Refresh();
|
}
|
||||||
return true;
|
|
||||||
|
SetModStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.RecreateCaches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteMod( DirectoryInfo modFolder )
|
||||||
|
{
|
||||||
|
modFolder.Refresh();
|
||||||
|
if( modFolder.Exists )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete( modFolder.FullName, true );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
PluginLog.Error( $"Could not create temporary directory {tmpDir.FullName}:\n{e}" );
|
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetModStructure( bool removeOldPaths = false )
|
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
|
||||||
{
|
{
|
||||||
var changes = false;
|
if( SetSortOrderPath( mod, sortOrder ) )
|
||||||
|
|
||||||
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 )
|
|
||||||
{
|
{
|
||||||
Config.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DiscoverMods()
|
if( Mods.ContainsKey( modFolder.Name ) )
|
||||||
{
|
{
|
||||||
Mods.Clear();
|
return false;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteMod( DirectoryInfo modFolder )
|
Mods.Add( modFolder.Name, mod );
|
||||||
|
foreach( var collection in Collections.Collections.Values )
|
||||||
{
|
{
|
||||||
modFolder.Refresh();
|
collection.AddMod( mod );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AddMod( DirectoryInfo modFolder )
|
return true;
|
||||||
{
|
}
|
||||||
var mod = ModData.LoadMod( StructuredMods, modFolder );
|
|
||||||
if( mod == null )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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( 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();
|
Config.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if( Mods.ContainsKey( modFolder.Name ) )
|
|
||||||
{
|
{
|
||||||
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;
|
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
|
||||||
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force;
|
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
|
Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, reloadMeta );
|
||||||
{
|
|
||||||
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
|
|
||||||
ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
|
return true;
|
||||||
// {
|
|
||||||
// #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;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +204,7 @@ public static class ModManagerEditExtensions
|
||||||
collection.Save();
|
collection.Save();
|
||||||
if( collection.Cache != null && settings.Enabled )
|
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 );
|
collection == manager.Collections.ActiveCollection );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ using System.Linq;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
namespace Penumbra;
|
namespace Penumbra;
|
||||||
public class MetaDefaults
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
public class MetaDefaults
|
||||||
|
{ }
|
||||||
|
|
||||||
public class Penumbra : IDalamudPlugin
|
public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name
|
||||||
|
|
@ -41,8 +41,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
public static MetaDefaults MetaDefaults { get; private set; } = null!;
|
public static MetaDefaults MetaDefaults { get; private set; } = null!;
|
||||||
public static ModManager ModManager { get; private set; } = null!;
|
public static ModManager ModManager { get; private set; } = null!;
|
||||||
|
|
||||||
|
public static ResourceLoader ResourceLoader { get; set; } = null!;
|
||||||
public ResourceLoader ResourceLoader { get; }
|
|
||||||
public ResourceLogger ResourceLogger { get; }
|
public ResourceLogger ResourceLogger { get; }
|
||||||
|
|
||||||
//public PathResolver PathResolver { get; }
|
//public PathResolver PathResolver { get; }
|
||||||
|
|
@ -113,12 +112,20 @@ public class Penumbra : IDalamudPlugin
|
||||||
};
|
};
|
||||||
|
|
||||||
ResourceLoader.EnableHooks();
|
ResourceLoader.EnableHooks();
|
||||||
if (Config.EnableMods)
|
if( Config.EnableMods )
|
||||||
|
{
|
||||||
ResourceLoader.EnableReplacements();
|
ResourceLoader.EnableReplacements();
|
||||||
if (Config.DebugMode)
|
}
|
||||||
|
|
||||||
|
if( Config.DebugMode )
|
||||||
|
{
|
||||||
ResourceLoader.EnableDebug();
|
ResourceLoader.EnableDebug();
|
||||||
if (Config.EnableFullResourceLogging)
|
}
|
||||||
|
|
||||||
|
if( Config.EnableFullResourceLogging )
|
||||||
|
{
|
||||||
ResourceLoader.EnableFullLogging();
|
ResourceLoader.EnableFullLogging();
|
||||||
|
}
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using Penumbra.Meta;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.UI.Custom;
|
using Penumbra.UI.Custom;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility;
|
||||||
using ResourceHandle = Penumbra.Interop.Structs.ResourceHandle;
|
using ResourceHandle = Penumbra.Interop.Structs.ResourceHandle;
|
||||||
using Utf8String = Penumbra.GameData.ByteString.Utf8String;
|
using Utf8String = Penumbra.GameData.ByteString.Utf8String;
|
||||||
|
|
||||||
|
|
@ -164,12 +165,6 @@ public partial class SettingsInterface
|
||||||
PrintValue( "Mod Manager BasePath Exists",
|
PrintValue( "Mod Manager BasePath Exists",
|
||||||
manager.BasePath != null ? Directory.Exists( manager.BasePath.FullName ).ToString() : false.ToString() );
|
manager.BasePath != null ? Directory.Exists( manager.BasePath.FullName ).ToString() : false.ToString() );
|
||||||
PrintValue( "Mod Manager Valid", manager.Valid.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() );
|
//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()
|
private void DrawDebugTabIpc()
|
||||||
{
|
{
|
||||||
if( !ImGui.CollapsingHeader( "IPC##Debug" ) )
|
if( !ImGui.CollapsingHeader( "IPC##Debug" ) )
|
||||||
|
|
@ -369,7 +327,7 @@ public partial class SettingsInterface
|
||||||
return;
|
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 ) )
|
if( cache == null || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -397,9 +355,9 @@ public partial class SettingsInterface
|
||||||
return;
|
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 ) )
|
|| !ImGui.BeginTable( "##ReplacedResourcesDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -407,7 +365,7 @@ public partial class SettingsInterface
|
||||||
|
|
||||||
using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable );
|
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 refCountManip = data.ManipulatedResource == null ? 0 : data.ManipulatedResource->RefCount;
|
||||||
var refCountOrig = data.OriginalResource == null ? 0 : data.OriginalResource->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()
|
private unsafe void DrawPathResolverDebug()
|
||||||
{
|
{
|
||||||
if( !ImGui.CollapsingHeader( "Path Resolver##Debug" ) )
|
if( !ImGui.CollapsingHeader( "Path Resolver##Debug" ) )
|
||||||
|
|
@ -479,6 +483,14 @@ public partial class SettingsInterface
|
||||||
|
|
||||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem );
|
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem );
|
||||||
|
|
||||||
|
if( !ImGui.BeginChild( "##DebugChild", -Vector2.One ) )
|
||||||
|
{
|
||||||
|
ImGui.EndChild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
raii.Push( ImGui.EndChild );
|
||||||
|
|
||||||
DrawDebugTabGeneral();
|
DrawDebugTabGeneral();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawDebugTabReplacedResources();
|
DrawDebugTabReplacedResources();
|
||||||
|
|
@ -491,12 +503,12 @@ public partial class SettingsInterface
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawPathResolverDebug();
|
DrawPathResolverDebug();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
|
DrawDebugCharacterUtility();
|
||||||
|
ImGui.NewLine();
|
||||||
DrawDebugTabRedraw();
|
DrawDebugTabRedraw();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawDebugTabPlayers();
|
DrawDebugTabPlayers();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawDebugTabTempFiles();
|
|
||||||
ImGui.NewLine();
|
|
||||||
DrawDebugTabIpc();
|
DrawDebugTabIpc();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,8 +192,9 @@ public partial class SettingsInterface
|
||||||
}
|
}
|
||||||
else if( ( row -= activeResolved ) < activeMeta )
|
else if( ( row -= activeResolved ) < activeMeta )
|
||||||
{
|
{
|
||||||
var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
// TODO
|
||||||
DrawLine( manip.ToString(), mod.Data.Meta.Name );
|
//var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
||||||
|
DrawLine( 0.ToString(), 0.ToString() );
|
||||||
}
|
}
|
||||||
else if( ( row -= activeMeta ) < forcedResolved )
|
else if( ( row -= activeMeta ) < forcedResolved )
|
||||||
{
|
{
|
||||||
|
|
@ -202,9 +203,10 @@ public partial class SettingsInterface
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// TODO
|
||||||
row -= forcedResolved;
|
row -= forcedResolved;
|
||||||
var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
//var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
||||||
DrawLine( manip.ToString(), mod.Data.Meta.Name );
|
DrawLine( 0.ToString(), 0.ToString() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -426,8 +426,7 @@ public partial class SettingsInterface
|
||||||
foreach( var collection in modManager.Collections.Collections.Values
|
foreach( var collection in modManager.Collections.Collections.Values
|
||||||
.Where( c => c.Cache != null && c.Settings[ Mod!.Data.BasePath.Name ].Enabled ) )
|
.Where( c => c.Cache != null && c.Settings[ Mod!.Data.BasePath.Name ].Enabled ) )
|
||||||
{
|
{
|
||||||
collection.CalculateEffectiveFileList( modManager.TempPath, false,
|
collection.CalculateEffectiveFileList( false, collection == modManager.Collections.ActiveCollection );
|
||||||
collection == modManager.Collections.ActiveCollection );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the mod is enabled in the current collection, its conflicts may have changed.
|
// If the mod is enabled in the current collection, its conflicts may have changed.
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Data.Files;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Meta;
|
|
||||||
using Penumbra.Meta.Files;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.UI.Custom;
|
using Penumbra.UI.Custom;
|
||||||
using Penumbra.Util;
|
|
||||||
using ObjectType = Penumbra.GameData.Enums.ObjectType;
|
using ObjectType = Penumbra.GameData.Enums.ObjectType;
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
|
|
@ -255,7 +249,6 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list )
|
private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list )
|
||||||
{
|
{
|
||||||
var defaults = ( GmpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
|
||||||
var ret = false;
|
var ret = false;
|
||||||
//var id = list[ manipIdx ].GmpIdentifier;
|
//var id = list[ manipIdx ].GmpIdentifier;
|
||||||
//var val = list[ manipIdx ].GmpValue;
|
//var val = list[ manipIdx ].GmpValue;
|
||||||
|
|
@ -365,7 +358,6 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list )
|
private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list )
|
||||||
{
|
{
|
||||||
var defaults = ( EqdpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
|
||||||
var ret = false;
|
var ret = false;
|
||||||
//var id = list[ manipIdx ].EqdpIdentifier;
|
//var id = list[ manipIdx ].EqdpIdentifier;
|
||||||
//var val = list[ manipIdx ].EqdpValue;
|
//var val = list[ manipIdx ].EqdpValue;
|
||||||
|
|
@ -402,7 +394,6 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list )
|
private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list )
|
||||||
{
|
{
|
||||||
var defaults = ( ushort )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
|
||||||
var ret = false;
|
var ret = false;
|
||||||
//var id = list[ manipIdx ].EstIdentifier;
|
//var id = list[ manipIdx ].EstIdentifier;
|
||||||
//var val = list[ manipIdx ].EstValue;
|
//var val = list[ manipIdx ].EstValue;
|
||||||
|
|
@ -434,7 +425,6 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list )
|
private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list )
|
||||||
{
|
{
|
||||||
var defaults = ( ImcFile.ImageChangeData )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
|
||||||
var ret = false;
|
var ret = false;
|
||||||
//var id = list[ manipIdx ].ImcIdentifier;
|
//var id = list[ manipIdx ].ImcIdentifier;
|
||||||
//var val = list[ manipIdx ].ImcValue;
|
//var val = list[ manipIdx ].ImcValue;
|
||||||
|
|
@ -493,7 +483,6 @@ namespace Penumbra.UI
|
||||||
|
|
||||||
private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list )
|
private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list )
|
||||||
{
|
{
|
||||||
var defaults = ( float )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
|
||||||
var ret = false;
|
var ret = false;
|
||||||
//var id = list[ manipIdx ].RspIdentifier;
|
//var id = list[ manipIdx ].RspIdentifier;
|
||||||
//var val = list[ manipIdx ].RspValue;
|
//var val = list[ manipIdx ].RspValue;
|
||||||
|
|
|
||||||
|
|
@ -529,8 +529,7 @@ public partial class SettingsInterface
|
||||||
var collection = Penumbra.ModManager.Collections.CurrentCollection;
|
var collection = Penumbra.ModManager.Collections.CurrentCollection;
|
||||||
if( collection.Cache != null )
|
if( collection.Cache != null )
|
||||||
{
|
{
|
||||||
collection.CalculateEffectiveFileList( Penumbra.ModManager.TempPath, metaManips,
|
collection.CalculateEffectiveFileList( metaManips, collection == Penumbra.ModManager.Collections.ActiveCollection );
|
||||||
collection == Penumbra.ModManager.Collections.ActiveCollection );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.Save();
|
collection.Save();
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ public partial class SettingsInterface
|
||||||
if( ImGui.IsItemClicked() )
|
if( ImGui.IsItemClicked() )
|
||||||
{
|
{
|
||||||
var data = ( ( Interop.Structs.ResourceHandle* )r )->GetData();
|
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" ) ) ) );
|
new ReadOnlySpan< byte >( ( byte* )data.Data, data.Length ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
|
||||||
//ImGuiNative.igSetClipboardText( ( byte* )Structs.ResourceHandle.GetData( ( IntPtr )r ) );
|
//ImGuiNative.igSetClipboardText( ( byte* )Structs.ResourceHandle.GetData( ( IntPtr )r ) );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ public partial class SettingsInterface
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private bool _configChanged;
|
private bool _configChanged;
|
||||||
private string _newModDirectory;
|
private string _newModDirectory;
|
||||||
private string _newTempDirectory;
|
|
||||||
|
|
||||||
|
|
||||||
public TabSettings( SettingsInterface ui )
|
public TabSettings( SettingsInterface ui )
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +31,6 @@ public partial class SettingsInterface
|
||||||
_config = Penumbra.Config;
|
_config = Penumbra.Config;
|
||||||
_configChanged = false;
|
_configChanged = false;
|
||||||
_newModDirectory = _config.ModDirectory;
|
_newModDirectory = _config.ModDirectory;
|
||||||
_newTempDirectory = _config.TempDirectory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DrawPressEnterWarning( string old )
|
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()
|
private void DrawRediscoverButton()
|
||||||
{
|
{
|
||||||
if( ImGui.Button( "Rediscover Mods" ) )
|
if( ImGui.Button( "Rediscover Mods" ) )
|
||||||
|
|
@ -326,11 +296,11 @@ public partial class SettingsInterface
|
||||||
{
|
{
|
||||||
if( tmp )
|
if( tmp )
|
||||||
{
|
{
|
||||||
_base._penumbra.ResourceLoader.EnableFullLogging();
|
Penumbra.ResourceLoader.EnableFullLogging();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_base._penumbra.ResourceLoader.DisableFullLogging();
|
Penumbra.ResourceLoader.DisableFullLogging();
|
||||||
}
|
}
|
||||||
|
|
||||||
_config.EnableFullResourceLogging = tmp;
|
_config.EnableFullResourceLogging = tmp;
|
||||||
|
|
@ -348,11 +318,11 @@ public partial class SettingsInterface
|
||||||
{
|
{
|
||||||
if( tmp )
|
if( tmp )
|
||||||
{
|
{
|
||||||
_base._penumbra.ResourceLoader.EnableDebug();
|
Penumbra.ResourceLoader.EnableDebug();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_base._penumbra.ResourceLoader.DisableDebug();
|
Penumbra.ResourceLoader.DisableDebug();
|
||||||
}
|
}
|
||||||
|
|
||||||
_config.DebugMode = tmp;
|
_config.DebugMode = tmp;
|
||||||
|
|
@ -388,7 +358,6 @@ public partial class SettingsInterface
|
||||||
|
|
||||||
private void DrawAdvancedSettings()
|
private void DrawAdvancedSettings()
|
||||||
{
|
{
|
||||||
DrawTempFolder();
|
|
||||||
DrawRequestedResourceLogging();
|
DrawRequestedResourceLogging();
|
||||||
DrawDisableSoundStreamingBox();
|
DrawDisableSoundStreamingBox();
|
||||||
DrawLogLoadedFilesBox();
|
DrawLogLoadedFilesBox();
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ public partial class SettingsInterface : IDisposable
|
||||||
var current = modManager.Collections.CurrentCollection;
|
var current = modManager.Collections.CurrentCollection;
|
||||||
if( current.Cache != null )
|
if( current.Cache != null )
|
||||||
{
|
{
|
||||||
current.CalculateEffectiveFileList( modManager.TempPath, recalculateMeta,
|
current.CalculateEffectiveFileList( recalculateMeta,
|
||||||
current == modManager.Collections.ActiveCollection );
|
current == modManager.Collections.ActiveCollection );
|
||||||
_menu.InstalledTab.Selector.Cache.TriggerFilterReset();
|
_menu.InstalledTab.Selector.Cache.TriggerFilterReset();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue