mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Fix IMC handling.
This commit is contained in:
parent
d07355c0f8
commit
d03a3168b0
8 changed files with 77 additions and 41 deletions
|
|
@ -19,13 +19,13 @@ public unsafe partial class ResourceLoader
|
||||||
// Gather some debugging data about penumbra-loaded objects.
|
// Gather some debugging data about penumbra-loaded objects.
|
||||||
public struct DebugData
|
public struct DebugData
|
||||||
{
|
{
|
||||||
public ResourceHandle* OriginalResource;
|
public Structs.ResourceHandle* OriginalResource;
|
||||||
public ResourceHandle* ManipulatedResource;
|
public Structs.ResourceHandle* ManipulatedResource;
|
||||||
public Utf8GamePath OriginalPath;
|
public Utf8GamePath OriginalPath;
|
||||||
public FullPath ManipulatedPath;
|
public FullPath ManipulatedPath;
|
||||||
public ResourceCategory Category;
|
public ResourceCategory Category;
|
||||||
public object? ResolverInfo;
|
public object? ResolverInfo;
|
||||||
public uint Extension;
|
public uint Extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SortedDictionary< FullPath, DebugData > _debugList = new();
|
private readonly SortedDictionary< FullPath, DebugData > _debugList = new();
|
||||||
|
|
@ -44,7 +44,8 @@ public unsafe partial class ResourceLoader
|
||||||
ResourceLoaded -= AddModifiedDebugInfo;
|
ResourceLoaded -= AddModifiedDebugInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddModifiedDebugInfo( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, object? resolverInfo )
|
private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
|
||||||
|
object? resolverInfo )
|
||||||
{
|
{
|
||||||
if( manipulatedPath == null )
|
if( manipulatedPath == null )
|
||||||
{
|
{
|
||||||
|
|
@ -55,7 +56,7 @@ public unsafe partial class ResourceLoader
|
||||||
var originalResource = FindResource( handle->Category, handle->FileType, crc );
|
var originalResource = FindResource( handle->Category, handle->FileType, crc );
|
||||||
_debugList[ manipulatedPath.Value ] = new DebugData()
|
_debugList[ manipulatedPath.Value ] = new DebugData()
|
||||||
{
|
{
|
||||||
OriginalResource = originalResource,
|
OriginalResource = ( Structs.ResourceHandle* )originalResource,
|
||||||
ManipulatedResource = handle,
|
ManipulatedResource = handle,
|
||||||
Category = handle->Category,
|
Category = handle->Category,
|
||||||
Extension = handle->FileType,
|
Extension = handle->FileType,
|
||||||
|
|
@ -172,8 +173,8 @@ public unsafe partial class ResourceLoader
|
||||||
{
|
{
|
||||||
_deleteList.Add( ( data.ManipulatedPath, data with
|
_deleteList.Add( ( data.ManipulatedPath, data with
|
||||||
{
|
{
|
||||||
OriginalResource = regularResource,
|
OriginalResource = ( Structs.ResourceHandle* )regularResource,
|
||||||
ManipulatedResource = modifiedResource,
|
ManipulatedResource = ( Structs.ResourceHandle* )modifiedResource,
|
||||||
} ) );
|
} ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +196,7 @@ public unsafe partial class ResourceLoader
|
||||||
private static void LogPath( Utf8GamePath path, bool synchronous )
|
private static void LogPath( Utf8GamePath path, bool synchronous )
|
||||||
=> PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
|
=> PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
|
||||||
|
|
||||||
private static void LogResource( ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ )
|
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ )
|
||||||
{
|
{
|
||||||
var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString();
|
var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString();
|
||||||
PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );
|
PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ public unsafe partial class ResourceLoader
|
||||||
if( resolvedPath == null )
|
if( resolvedPath == null )
|
||||||
{
|
{
|
||||||
var retUnmodified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
var retUnmodified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
||||||
ResourceLoaded?.Invoke( retUnmodified, gamePath, null, data );
|
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data );
|
||||||
return retUnmodified;
|
return retUnmodified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ public unsafe partial class ResourceLoader
|
||||||
*resourceHash = resolvedPath.Value.InternalName.Crc32;
|
*resourceHash = resolvedPath.Value.InternalName.Crc32;
|
||||||
path = resolvedPath.Value.InternalName.Path;
|
path = resolvedPath.Value.InternalName.Path;
|
||||||
var retModified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
var retModified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
||||||
ResourceLoaded?.Invoke( retModified, gamePath, resolvedPath.Value, data );
|
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retModified, gamePath, resolvedPath.Value, data );
|
||||||
return retModified;
|
return retModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ public unsafe partial class ResourceLoader : IDisposable
|
||||||
// Event fired whenever a resource is returned.
|
// Event fired whenever a resource is returned.
|
||||||
// If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource.
|
// If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource.
|
||||||
// resolveData is additional data returned by the current ResolvePath function and is user-defined.
|
// resolveData is additional data returned by the current ResolvePath function and is user-defined.
|
||||||
public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
|
public delegate void ResourceLoadedDelegate( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
|
||||||
object? resolveData );
|
object? resolveData );
|
||||||
|
|
||||||
public event ResourceLoadedDelegate? ResourceLoaded;
|
public event ResourceLoadedDelegate? ResourceLoaded;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -33,11 +34,11 @@ public unsafe class ObjectReloader : IDisposable
|
||||||
public static DrawState* ActorDrawState( GameObject actor )
|
public static DrawState* ActorDrawState( GameObject actor )
|
||||||
=> ( DrawState* )( actor.Address + 0x0104 );
|
=> ( DrawState* )( actor.Address + 0x0104 );
|
||||||
|
|
||||||
private static delegate*< IntPtr, void > GetDisableDraw( GameObject actor )
|
private static void DisableDraw( GameObject actor )
|
||||||
=> ( ( delegate*< IntPtr, void >** )actor.Address )[ 0 ][ 17 ];
|
=> ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 17 ]( actor.Address );
|
||||||
|
|
||||||
private static delegate*< IntPtr, void > GetEnableDraw( GameObject actor )
|
private static void EnableDraw( GameObject actor )
|
||||||
=> ( ( delegate*< IntPtr, void >** )actor.Address )[ 0 ][ 16 ];
|
=> ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 16 ]( actor.Address );
|
||||||
|
|
||||||
public ObjectReloader( ModManager mods )
|
public ObjectReloader( ModManager mods )
|
||||||
=> _mods = mods;
|
=> _mods = mods;
|
||||||
|
|
@ -57,14 +58,14 @@ public unsafe class ObjectReloader : IDisposable
|
||||||
_changedSettings = false;
|
_changedSettings = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void WriteInvisible( GameObject actor, int actorIdx )
|
private void WriteInvisible( GameObject actor, int actorIdx )
|
||||||
{
|
{
|
||||||
_currentObjectStartState = *ActorDrawState( actor );
|
_currentObjectStartState = *ActorDrawState( actor );
|
||||||
*ActorDrawState( actor ) |= DrawState.Invisibility;
|
*ActorDrawState( actor ) |= DrawState.Invisibility;
|
||||||
|
|
||||||
if( _inGPose )
|
if( _inGPose )
|
||||||
{
|
{
|
||||||
GetDisableDraw( actor )( actor.Address );
|
DisableDraw( actor );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +96,7 @@ public unsafe class ObjectReloader : IDisposable
|
||||||
|
|
||||||
if( _inGPose )
|
if( _inGPose )
|
||||||
{
|
{
|
||||||
GetEnableDraw( actor )( actor.Address );
|
EnableDraw( actor );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
|
@ -40,18 +41,30 @@ public unsafe struct ResourceHandle
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset( 0x00 )]
|
||||||
public void** VTable;
|
public void** VTable;
|
||||||
|
|
||||||
|
[FieldOffset( 0x08 )]
|
||||||
|
public ResourceCategory Category;
|
||||||
|
|
||||||
|
[FieldOffset( 0x0C )]
|
||||||
|
public uint FileType;
|
||||||
|
|
||||||
|
[FieldOffset( 0x10 )]
|
||||||
|
public uint Id;
|
||||||
|
|
||||||
[FieldOffset( 0x48 )]
|
[FieldOffset( 0x48 )]
|
||||||
public byte* FileNameData;
|
public byte* FileNameData;
|
||||||
|
|
||||||
[FieldOffset( 0x58 )]
|
[FieldOffset( 0x58 )]
|
||||||
public int FileNameLength;
|
public int FileNameLength;
|
||||||
|
|
||||||
|
[FieldOffset( 0xAC )]
|
||||||
|
public uint RefCount;
|
||||||
|
|
||||||
// May return null.
|
// May return null.
|
||||||
public static byte* GetData( ResourceHandle* handle )
|
public static byte* GetData( ResourceHandle* handle )
|
||||||
=> ( ( delegate*< ResourceHandle*, byte* > )handle->VTable[ 23 ] )( handle );
|
=> ( ( delegate* unmanaged< ResourceHandle*, byte* > )handle->VTable[ 23 ] )( handle );
|
||||||
|
|
||||||
public static ulong GetLength( ResourceHandle* handle )
|
public static ulong GetLength( ResourceHandle* handle )
|
||||||
=> ( ( delegate*< ResourceHandle*, ulong > )handle->VTable[ 17 ] )( handle );
|
=> ( ( delegate* unmanaged< ResourceHandle*, ulong > )handle->VTable[ 17 ] )( handle );
|
||||||
|
|
||||||
|
|
||||||
// Only use these if you know what you are doing.
|
// Only use these if you know what you are doing.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
@ -8,7 +7,6 @@ using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
|
|
@ -68,8 +66,9 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
public int Count
|
public int Count
|
||||||
=> CountInternal( Data );
|
=> CountInternal( Data );
|
||||||
|
|
||||||
public readonly int NumParts;
|
|
||||||
public readonly Utf8GamePath Path;
|
public readonly Utf8GamePath Path;
|
||||||
|
public readonly int NumParts;
|
||||||
|
public bool ChangesSinceLoad = true;
|
||||||
|
|
||||||
private static int CountInternal( byte* data )
|
private static int CountInternal( byte* data )
|
||||||
=> *( ushort* )data;
|
=> *( ushort* )data;
|
||||||
|
|
@ -179,7 +178,8 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*variantPtr = entry;
|
*variantPtr = entry;
|
||||||
|
ChangesSinceLoad = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +192,8 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
||||||
Functions.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
|
Functions.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChangesSinceLoad = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImcFile( Utf8GamePath path )
|
public ImcFile( Utf8GamePath path )
|
||||||
|
|
@ -209,9 +211,8 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
fixed( byte* ptr = file.Data )
|
fixed( byte* ptr = file.Data )
|
||||||
{
|
{
|
||||||
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
|
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
|
||||||
AllocateData( file.Data.Length + sizeof( ImcEntry ) * 100 * NumParts );
|
AllocateData( file.Data.Length );
|
||||||
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
||||||
Functions.MemSet( Data + file.Data.Length, 0, sizeof( ImcEntry ) * 100 * NumParts );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,6 +240,7 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiredLength = ActualLength;
|
var requiredLength = ActualLength;
|
||||||
|
resource->SetData( (IntPtr) Data, Length );
|
||||||
if( length >= requiredLength )
|
if( length >= requiredLength )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( ( void* )data, Data, requiredLength );
|
Functions.MemCpyUnchecked( ( void* )data, Data, requiredLength );
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Dalamud.Logging;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
|
|
@ -57,7 +58,6 @@ public partial class MetaManager
|
||||||
|
|
||||||
public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod )
|
public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod )
|
||||||
{
|
{
|
||||||
const uint imcExt = 0x00696D63;
|
|
||||||
#if USE_IMC
|
#if USE_IMC
|
||||||
if( !Manipulations.TryAdd( m, mod ) )
|
if( !Manipulations.TryAdd( m, mod ) )
|
||||||
{
|
{
|
||||||
|
|
@ -82,12 +82,6 @@ public partial class MetaManager
|
||||||
_collection.Cache.ResolvedFiles[ path ] = fullPath;
|
_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;
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -109,22 +103,25 @@ public partial class MetaManager
|
||||||
[Conditional( "USE_IMC" )]
|
[Conditional( "USE_IMC" )]
|
||||||
private unsafe void SetupDelegate()
|
private unsafe void SetupDelegate()
|
||||||
{
|
{
|
||||||
Penumbra.ResourceLoader.ResourceLoadCustomization = ImcHandler;
|
Penumbra.ResourceLoader.ResourceLoadCustomization = ImcLoadHandler;
|
||||||
|
Penumbra.ResourceLoader.ResourceLoaded += ImcResourceHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Conditional( "USE_IMC" )]
|
[Conditional( "USE_IMC" )]
|
||||||
private unsafe void RestoreDelegate()
|
private unsafe void RestoreDelegate()
|
||||||
{
|
{
|
||||||
if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcHandler )
|
if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcLoadHandler )
|
||||||
{
|
{
|
||||||
Penumbra.ResourceLoader.ResourceLoadCustomization = _previousDelegate;
|
Penumbra.ResourceLoader.ResourceLoadCustomization = _previousDelegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Penumbra.ResourceLoader.ResourceLoaded -= ImcResourceHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FullPath CreateImcPath( Utf8GamePath path )
|
private FullPath CreateImcPath( Utf8GamePath path )
|
||||||
=> new($"|{_collection.Name}|{path}");
|
=> new($"|{_collection.Name}|{path}");
|
||||||
|
|
||||||
private static unsafe byte ImcHandler( Utf8GamePath gamePath, ResourceManager* resourceManager,
|
private static unsafe byte ImcLoadHandler( Utf8GamePath gamePath, ResourceManager* resourceManager,
|
||||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
||||||
{
|
{
|
||||||
var split = gamePath.Path.Split( ( byte )'|', 2, true );
|
var split = gamePath.Path.Split( ( byte )'|', 2, true );
|
||||||
|
|
@ -137,12 +134,33 @@ public partial class MetaManager
|
||||||
&& collection.Cache.MetaManipulations.Imc.Files.TryGetValue(
|
&& collection.Cache.MetaManipulations.Imc.Files.TryGetValue(
|
||||||
Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) )
|
Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) )
|
||||||
{
|
{
|
||||||
|
PluginLog.Debug( "Loaded {GamePath:l} from file and replaced with IMC from collection {Collection:l}.", gamePath,
|
||||||
|
collection.Name );
|
||||||
file.Replace( fileDescriptor->ResourceHandle );
|
file.Replace( fileDescriptor->ResourceHandle );
|
||||||
|
file.ChangesSinceLoad = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
|
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
|
||||||
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
|
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static unsafe void ImcResourceHandler( ResourceHandle* resource, Utf8GamePath gamePath, FullPath? _2, object? resolveData )
|
||||||
|
{
|
||||||
|
// Only check imcs.
|
||||||
|
if( resource->FileType != 0x00696D63
|
||||||
|
|| resolveData is not ModCollection collection
|
||||||
|
|| collection.Cache == null
|
||||||
|
|| !collection.Cache.MetaManipulations.Imc.Files.TryGetValue( gamePath, out var file )
|
||||||
|
|| !file.ChangesSinceLoad )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginLog.Debug( "File {GamePath:l} was already loaded but IMC in collection {Collection:l} was changed, so reloaded.", gamePath,
|
||||||
|
collection.Name );
|
||||||
|
file.Replace( resource );
|
||||||
|
file.ChangesSinceLoad = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -25,7 +26,7 @@ public class CollectionManager
|
||||||
private readonly ModManager _manager;
|
private readonly ModManager _manager;
|
||||||
|
|
||||||
public string CollectionChangedTo { get; private set; } = string.Empty;
|
public string CollectionChangedTo { get; private set; } = string.Empty;
|
||||||
public Dictionary< string, ModCollection > Collections { get; } = new();
|
public Dictionary< string, ModCollection > Collections { get; } = new(StringComparer.InvariantCultureIgnoreCase);
|
||||||
public Dictionary< string, ModCollection > CharacterCollection { get; } = new();
|
public Dictionary< string, ModCollection > CharacterCollection { get; } = new();
|
||||||
|
|
||||||
public ModCollection CurrentCollection { get; private set; } = ModCollection.Empty;
|
public ModCollection CurrentCollection { get; private set; } = ModCollection.Empty;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue