mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Make line endings explicit in editorconfig and share in sub projects, also apply editorconfig everywhere and move some namespaces.
This commit is contained in:
parent
53adb6fa54
commit
2b4a01df06
155 changed files with 1620 additions and 1614 deletions
|
|
@ -1,21 +1,31 @@
|
||||||
|
# Standard properties
|
||||||
[*.proto]
|
charset = utf-8
|
||||||
indent_style=tab
|
end_of_line = lf
|
||||||
indent_size=tab
|
insert_final_newline = true
|
||||||
tab_width=4
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
[*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}]
|
csharp_prefer_braces = true:silent
|
||||||
indent_style=space
|
csharp_style_prefer_method_group_conversion = true:silent
|
||||||
indent_size=4
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
tab_width=4
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
[*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
indent_style=space
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
indent_size=2
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
tab_width=2
|
csharp_style_expression_bodied_lambdas = true:silent
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_prefer_tuple_swap = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_prefer_top_level_statements = true:silent
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
|
|
||||||
# Microsoft .NET properties
|
# Microsoft .NET properties
|
||||||
csharp_indent_braces=false
|
csharp_indent_braces=false
|
||||||
csharp_indent_switch_labels=true
|
csharp_indent_switch_labels=true
|
||||||
|
|
@ -3567,30 +3577,6 @@ resharper_xaml_x_key_attribute_disallowed_highlighting=error
|
||||||
resharper_xml_doc_comment_syntax_problem_highlighting=warning
|
resharper_xml_doc_comment_syntax_problem_highlighting=warning
|
||||||
resharper_xunit_xunit_test_with_console_output_highlighting=warning
|
resharper_xunit_xunit_test_with_console_output_highlighting=warning
|
||||||
|
|
||||||
# Standard properties
|
|
||||||
end_of_line= crlf
|
|
||||||
csharp_indent_labels = one_less_than_current
|
|
||||||
csharp_prefer_simple_using_statement = true:suggestion
|
|
||||||
csharp_prefer_braces = true:silent
|
|
||||||
csharp_style_prefer_method_group_conversion = true:silent
|
|
||||||
csharp_style_expression_bodied_methods = false:silent
|
|
||||||
csharp_style_expression_bodied_constructors = false:silent
|
|
||||||
csharp_style_expression_bodied_operators = false:silent
|
|
||||||
csharp_style_expression_bodied_properties = true:silent
|
|
||||||
csharp_style_expression_bodied_indexers = true:silent
|
|
||||||
csharp_style_expression_bodied_accessors = true:silent
|
|
||||||
csharp_style_expression_bodied_lambdas = true:silent
|
|
||||||
csharp_style_expression_bodied_local_functions = false:silent
|
|
||||||
csharp_style_throw_expression = true:suggestion
|
|
||||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
|
||||||
csharp_prefer_simple_default_expression = true:suggestion
|
|
||||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
|
||||||
csharp_style_prefer_index_operator = true:suggestion
|
|
||||||
csharp_style_prefer_range_operator = true:suggestion
|
|
||||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
|
||||||
csharp_style_prefer_tuple_swap = true:suggestion
|
|
||||||
csharp_style_inlined_variable_declaration = true:suggestion
|
|
||||||
|
|
||||||
[*.{cshtml,htm,html,proto,razor}]
|
[*.{cshtml,htm,html,proto,razor}]
|
||||||
indent_style=tab
|
indent_style=tab
|
||||||
indent_size=tab
|
indent_size=tab
|
||||||
|
|
@ -3601,6 +3587,21 @@ indent_style=space
|
||||||
indent_size=4
|
indent_size=4
|
||||||
tab_width=4
|
tab_width=4
|
||||||
|
|
||||||
|
[ "*.proto" ]
|
||||||
|
indent_style=tab
|
||||||
|
indent_size=tab
|
||||||
|
tab_width=4
|
||||||
|
|
||||||
|
[*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=4
|
||||||
|
tab_width=4
|
||||||
|
|
||||||
|
[*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
tab_width=2
|
||||||
|
|
||||||
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
|
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
|
||||||
indent_style=space
|
indent_style=space
|
||||||
indent_size= 4
|
indent_size= 4
|
||||||
|
|
@ -3621,3 +3622,4 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||||
dotnet_style_namespace_match_folder = true:suggestion
|
dotnet_style_namespace_match_folder = true:suggestion
|
||||||
|
insert_final_newline = true
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 316f3da4a3ce246afe5b98c1568d73fcd7b6b22d
|
Subproject commit 22846625192884c6e9f5ec4429fb579875b519e9
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0507b1f093f5382e03242e5da991752361b70c6e
|
Subproject commit f004e069824a1588244e06080b32bab170f78077
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0e0c1e1ee116c259abd00e1d5c3450ad40f92a98
|
Subproject commit 620a7edf009b92288257ce7d64fffb8fba44d8b5
|
||||||
|
|
@ -633,7 +633,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
|
|
||||||
_modManager.AddMod(dir);
|
_modManager.AddMod(dir);
|
||||||
if (_config.UseFileSystemCompression)
|
if (_config.UseFileSystemCompression)
|
||||||
new FileCompactor(Penumbra.Log).StartMassCompact(dir.EnumerateFiles("*.*", SearchOption.AllDirectories), CompressionAlgorithm.Xpress8K);
|
new FileCompactor(Penumbra.Log).StartMassCompact(dir.EnumerateFiles("*.*", SearchOption.AllDirectories),
|
||||||
|
CompressionAlgorithm.Xpress8K);
|
||||||
return PenumbraApiEc.Success;
|
return PenumbraApiEc.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public class TempModManager : IDisposable
|
||||||
|
|
||||||
public TempModManager(CommunicatorService communicator)
|
public TempModManager(CommunicatorService communicator)
|
||||||
{
|
{
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.TempModManager);
|
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.TempModManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class TempModManager : IDisposable
|
||||||
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
|
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
|
||||||
HashSet<MetaManipulation> manips, int priority)
|
HashSet<MetaManipulation> manips, int priority)
|
||||||
{
|
{
|
||||||
var mod = GetOrCreateMod(tag, collection, priority, out var created);
|
var mod = GetOrCreateMod(tag, collection, priority, out var created);
|
||||||
Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}.");
|
Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}.");
|
||||||
mod.SetAll(dict, manips);
|
mod.SetAll(dict, manips);
|
||||||
ApplyModChange(mod, collection, created, false);
|
ApplyModChange(mod, collection, created, false);
|
||||||
|
|
@ -56,7 +56,7 @@ public class TempModManager : IDisposable
|
||||||
var list = collection == null ? _modsForAllCollections : _mods.TryGetValue(collection, out var l) ? l : null;
|
var list = collection == null ? _modsForAllCollections : _mods.TryGetValue(collection, out var l) ? l : null;
|
||||||
if (list == null)
|
if (list == null)
|
||||||
return RedirectResult.NotRegistered;
|
return RedirectResult.NotRegistered;
|
||||||
|
|
||||||
var removed = list.RemoveAll(m =>
|
var removed = list.RemoveAll(m =>
|
||||||
{
|
{
|
||||||
if (m.Name != tag || priority != null && m.Priority != priority.Value)
|
if (m.Name != tag || priority != null && m.Priority != priority.Value)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Penumbra.Api.Enums;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
namespace Penumbra.Collections.Cache;
|
namespace Penumbra.Collections.Cache;
|
||||||
|
|
||||||
|
|
@ -270,14 +271,14 @@ public class CollectionCache : IDisposable
|
||||||
foreach (var manip in subMod.Manipulations)
|
foreach (var manip in subMod.Manipulations)
|
||||||
AddManipulation(manip, parentMod);
|
AddManipulation(manip, parentMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Invoke only if not in a full recalculation. </summary>
|
/// <summary> Invoke only if not in a full recalculation. </summary>
|
||||||
private void InvokeResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath key, FullPath value,
|
private void InvokeResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath key, FullPath value,
|
||||||
FullPath old, IMod? mod)
|
FullPath old, IMod? mod)
|
||||||
{
|
{
|
||||||
if (Calculating == -1)
|
if (Calculating == -1)
|
||||||
_manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod);
|
_manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a specific file redirection, handling potential conflicts.
|
// Add a specific file redirection, handling potential conflicts.
|
||||||
// For different mods, higher mod priority takes precedence before option group priority,
|
// For different mods, higher mod priority takes precedence before option group priority,
|
||||||
|
|
@ -292,7 +293,7 @@ public class CollectionCache : IDisposable
|
||||||
{
|
{
|
||||||
if (ResolvedFiles.TryAdd(path, new ModPath(mod, file)))
|
if (ResolvedFiles.TryAdd(path, new ModPath(mod, file)))
|
||||||
{
|
{
|
||||||
ModData.AddPath(mod, path);
|
ModData.AddPath(mod, path);
|
||||||
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod);
|
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public readonly struct EqdpCache : IDisposable
|
||||||
foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>())
|
foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>())
|
||||||
{
|
{
|
||||||
var relevant = CharacterUtility.RelevantIndices[file.Index.Value];
|
var relevant = CharacterUtility.RelevantIndices[file.Index.Value];
|
||||||
file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (SetId) m.SetId));
|
file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (SetId)m.SetId));
|
||||||
}
|
}
|
||||||
|
|
||||||
_eqdpManipulations.Clear();
|
_eqdpManipulations.Clear();
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ namespace Penumbra.Collections.Cache;
|
||||||
|
|
||||||
public struct EqpCache : IDisposable
|
public struct EqpCache : IDisposable
|
||||||
{
|
{
|
||||||
private ExpandedEqpFile? _eqpFile = null;
|
private ExpandedEqpFile? _eqpFile = null;
|
||||||
private readonly List< EqpManipulation > _eqpManipulations = new();
|
private readonly List<EqpManipulation> _eqpManipulations = new();
|
||||||
|
|
||||||
public EqpCache()
|
|
||||||
{}
|
|
||||||
|
|
||||||
public void SetFiles(MetaFileManager manager)
|
public EqpCache()
|
||||||
=> manager.SetFile( _eqpFile, MetaIndex.Eqp );
|
{ }
|
||||||
|
|
||||||
|
public void SetFiles(MetaFileManager manager)
|
||||||
|
=> manager.SetFile(_eqpFile, MetaIndex.Eqp);
|
||||||
|
|
||||||
public static void ResetFiles(MetaFileManager manager)
|
public static void ResetFiles(MetaFileManager manager)
|
||||||
=> manager.SetFile( null, MetaIndex.Eqp );
|
=> manager.SetFile(null, MetaIndex.Eqp);
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
|
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
|
||||||
=> manager.TemporarilySetFile( _eqpFile, MetaIndex.Eqp );
|
=> manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp);
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
|
|
@ -31,25 +31,24 @@ public struct EqpCache : IDisposable
|
||||||
|
|
||||||
_eqpFile.Reset(_eqpManipulations.Select(m => m.SetId));
|
_eqpFile.Reset(_eqpManipulations.Select(m => m.SetId));
|
||||||
_eqpManipulations.Clear();
|
_eqpManipulations.Clear();
|
||||||
}
|
|
||||||
|
|
||||||
public bool ApplyMod( MetaFileManager manager, EqpManipulation manip )
|
|
||||||
{
|
|
||||||
_eqpManipulations.AddOrReplace( manip );
|
|
||||||
_eqpFile ??= new ExpandedEqpFile(manager);
|
|
||||||
return manip.Apply( _eqpFile );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RevertMod( MetaFileManager manager, EqpManipulation manip )
|
public bool ApplyMod(MetaFileManager manager, EqpManipulation manip)
|
||||||
{
|
{
|
||||||
var idx = _eqpManipulations.FindIndex( manip.Equals );
|
_eqpManipulations.AddOrReplace(manip);
|
||||||
|
_eqpFile ??= new ExpandedEqpFile(manager);
|
||||||
|
return manip.Apply(_eqpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RevertMod(MetaFileManager manager, EqpManipulation manip)
|
||||||
|
{
|
||||||
|
var idx = _eqpManipulations.FindIndex(manip.Equals);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var def = ExpandedEqpFile.GetDefault( manager, manip.SetId );
|
var def = ExpandedEqpFile.GetDefault(manager, manip.SetId);
|
||||||
manip = new EqpManipulation( def, manip.Slot, manip.SetId );
|
manip = new EqpManipulation(def, manip.Slot, manip.SetId);
|
||||||
return manip.Apply( _eqpFile! );
|
return manip.Apply(_eqpFile!);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -58,4 +57,4 @@ public struct EqpCache : IDisposable
|
||||||
_eqpFile = null;
|
_eqpFile = null;
|
||||||
_eqpManipulations.Clear();
|
_eqpManipulations.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,42 +9,42 @@ namespace Penumbra.Collections.Cache;
|
||||||
|
|
||||||
public struct GmpCache : IDisposable
|
public struct GmpCache : IDisposable
|
||||||
{
|
{
|
||||||
private ExpandedGmpFile? _gmpFile = null;
|
private ExpandedGmpFile? _gmpFile = null;
|
||||||
private readonly List< GmpManipulation > _gmpManipulations = new();
|
private readonly List<GmpManipulation> _gmpManipulations = new();
|
||||||
|
|
||||||
public GmpCache()
|
public GmpCache()
|
||||||
{}
|
{ }
|
||||||
|
|
||||||
public void SetFiles(MetaFileManager manager)
|
public void SetFiles(MetaFileManager manager)
|
||||||
=> manager.SetFile( _gmpFile, MetaIndex.Gmp );
|
=> manager.SetFile(_gmpFile, MetaIndex.Gmp);
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
|
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
|
||||||
=> manager.TemporarilySetFile( _gmpFile, MetaIndex.Gmp );
|
=> manager.TemporarilySetFile(_gmpFile, MetaIndex.Gmp);
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
if( _gmpFile == null )
|
if (_gmpFile == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_gmpFile.Reset( _gmpManipulations.Select( m => m.SetId ) );
|
_gmpFile.Reset(_gmpManipulations.Select(m => m.SetId));
|
||||||
_gmpManipulations.Clear();
|
_gmpManipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( MetaFileManager manager, GmpManipulation manip )
|
public bool ApplyMod(MetaFileManager manager, GmpManipulation manip)
|
||||||
{
|
{
|
||||||
_gmpManipulations.AddOrReplace( manip );
|
_gmpManipulations.AddOrReplace(manip);
|
||||||
_gmpFile ??= new ExpandedGmpFile(manager);
|
_gmpFile ??= new ExpandedGmpFile(manager);
|
||||||
return manip.Apply( _gmpFile );
|
return manip.Apply(_gmpFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RevertMod( MetaFileManager manager, GmpManipulation manip )
|
public bool RevertMod(MetaFileManager manager, GmpManipulation manip)
|
||||||
{
|
{
|
||||||
if (!_gmpManipulations.Remove(manip))
|
if (!_gmpManipulations.Remove(manip))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var def = ExpandedGmpFile.GetDefault( manager, manip.SetId );
|
var def = ExpandedGmpFile.GetDefault(manager, manip.SetId);
|
||||||
manip = new GmpManipulation( def, manip.SetId );
|
manip = new GmpManipulation(def, manip.SetId);
|
||||||
return manip.Apply( _gmpFile! );
|
return manip.Apply(_gmpFile!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -53,4 +53,4 @@ public struct GmpCache : IDisposable
|
||||||
_gmpFile = null;
|
_gmpFile = null;
|
||||||
_gmpManipulations.Clear();
|
_gmpManipulations.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ public readonly struct ImcCache : IDisposable
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
idx = _imcManipulations.Count;
|
idx = _imcManipulations.Count;
|
||||||
_imcManipulations.Add((manip, null!));
|
_imcManipulations.Add((manip, null!));
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = manip.GamePath();
|
var path = manip.GamePath();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -79,13 +79,13 @@ public readonly struct ImcCache : IDisposable
|
||||||
public bool RevertMod(MetaFileManager manager, ModCollection collection, ImcManipulation m)
|
public bool RevertMod(MetaFileManager manager, ModCollection collection, ImcManipulation m)
|
||||||
{
|
{
|
||||||
if (!m.Validate())
|
if (!m.Validate())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(m));
|
var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(m));
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var (_, file) = _imcManipulations[idx];
|
var (_, file) = _imcManipulations[idx];
|
||||||
_imcManipulations.RemoveAt(idx);
|
_imcManipulations.RemoveAt(idx);
|
||||||
|
|
||||||
if (_imcManipulations.All(p => !ReferenceEquals(p.Item2, file)))
|
if (_imcManipulations.All(p => !ReferenceEquals(p.Item2, file)))
|
||||||
|
|
@ -94,8 +94,8 @@ public readonly struct ImcCache : IDisposable
|
||||||
collection._cache!.ForceFile(file.Path, FullPath.Empty);
|
collection._cache!.ForceFile(file.Path, FullPath.Empty);
|
||||||
file.Dispose();
|
file.Dispose();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var def = ImcFile.GetDefault(manager, file.Path, m.EquipSlot, m.Variant.Id, out _);
|
var def = ImcFile.GetDefault(manager, file.Path, m.EquipSlot, m.Variant.Id, out _);
|
||||||
var manip = m.Copy(def);
|
var manip = m.Copy(def);
|
||||||
if (!manip.Apply(file))
|
if (!manip.Apply(file))
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set a single file. </summary>
|
/// <summary> Set a single file. </summary>
|
||||||
public void SetFile(MetaIndex metaIndex)
|
public void SetFile(MetaIndex metaIndex)
|
||||||
{
|
{
|
||||||
|
|
@ -162,7 +162,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the currently relevant IMC files for the collection cache. </summary>
|
/// <summary> Set the currently relevant IMC files for the collection cache. </summary>
|
||||||
public void SetImcFiles(bool fromFullCompute)
|
public void SetImcFiles(bool fromFullCompute)
|
||||||
=> _imcCache.SetFiles(_collection, fromFullCompute);
|
=> _imcCache.SetFiles(_collection, fromFullCompute);
|
||||||
|
|
@ -171,7 +171,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
||||||
=> _eqpCache.TemporarilySetFiles(_manager);
|
=> _eqpCache.TemporarilySetFiles(_manager);
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory)
|
public MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory)
|
||||||
=> _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory);
|
=> _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory);
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetGmpFile()
|
public MetaList.MetaReverter TemporarilySetGmpFile()
|
||||||
=> _gmpCache.TemporarilySetFiles(_manager);
|
=> _gmpCache.TemporarilySetFiles(_manager);
|
||||||
|
|
@ -180,7 +180,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
||||||
=> _cmpCache.TemporarilySetFiles(_manager);
|
=> _cmpCache.TemporarilySetFiles(_manager);
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
|
public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
|
||||||
=> _estCache.TemporarilySetFiles(_manager, type);
|
=> _estCache.TemporarilySetFiles(_manager, type);
|
||||||
|
|
||||||
|
|
||||||
/// <summary> Try to obtain a manipulated IMC file. </summary>
|
/// <summary> Try to obtain a manipulated IMC file. </summary>
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,18 @@ public static class ActiveCollectionMigration
|
||||||
foreach (var (type, _, _) in CollectionTypeExtensions.Special.Where(t => t.Item2.StartsWith("Male ")))
|
foreach (var (type, _, _) in CollectionTypeExtensions.Special.Where(t => t.Item2.StartsWith("Male ")))
|
||||||
{
|
{
|
||||||
var oldName = type.ToString()[4..];
|
var oldName = type.ToString()[4..];
|
||||||
var value = jObject[oldName];
|
var value = jObject[oldName];
|
||||||
if (value == null)
|
if (value == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
jObject.Remove(oldName);
|
jObject.Remove(oldName);
|
||||||
jObject.Add("Male" + oldName, value);
|
jObject.Add("Male" + oldName, value);
|
||||||
jObject.Add("Female" + oldName, value);
|
jObject.Add("Female" + oldName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var stream = File.Open(fileNames.ActiveCollectionsFile, FileMode.Truncate);
|
using var stream = File.Open(fileNames.ActiveCollectionsFile, FileMode.Truncate);
|
||||||
using var writer = new StreamWriter(stream);
|
using var writer = new StreamWriter(stream);
|
||||||
using var j = new JsonTextWriter(writer);
|
using var j = new JsonTextWriter(writer);
|
||||||
j.Formatting = Formatting.Indented;
|
j.Formatting = Formatting.Indented;
|
||||||
jObject.WriteTo(j);
|
jObject.WriteTo(j);
|
||||||
}
|
}
|
||||||
|
|
@ -41,13 +41,14 @@ public static class ActiveCollectionMigration
|
||||||
|
|
||||||
// Load character collections. If a player name comes up multiple times, the last one is applied.
|
// Load character collections. If a player name comes up multiple times, the last one is applied.
|
||||||
var characters = jObject["Characters"]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>();
|
var characters = jObject["Characters"]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>();
|
||||||
var dict = new Dictionary<string, ModCollection>(characters.Count);
|
var dict = new Dictionary<string, ModCollection>(characters.Count);
|
||||||
foreach (var (player, collectionName) in characters)
|
foreach (var (player, collectionName) in characters)
|
||||||
{
|
{
|
||||||
if (!storage.ByName(collectionName, out var collection))
|
if (!storage.ByName(collectionName, out var collection))
|
||||||
{
|
{
|
||||||
Penumbra.Chat.NotificationMessage(
|
Penumbra.Chat.NotificationMessage(
|
||||||
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Name}.", "Load Failure",
|
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Name}.",
|
||||||
|
"Load Failure",
|
||||||
NotificationType.Warning);
|
NotificationType.Warning);
|
||||||
dict.Add(player, ModCollection.Empty);
|
dict.Add(player, ModCollection.Empty);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -442,6 +442,7 @@ public class ActiveCollections : ISavable, IDisposable
|
||||||
var m = ByType(CollectionTypeExtensions.FromParts(race, Gender.Male, false));
|
var m = ByType(CollectionTypeExtensions.FromParts(race, Gender.Male, false));
|
||||||
if (m != null && m != yourself)
|
if (m != null && m != yourself)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var f = ByType(CollectionTypeExtensions.FromParts(race, Gender.Female, false));
|
var f = ByType(CollectionTypeExtensions.FromParts(race, Gender.Female, false));
|
||||||
if (f != null && f != yourself)
|
if (f != null && f != yourself)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
@ -450,26 +451,28 @@ public class ActiveCollections : ISavable, IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
var racialString = racial ? " and Racial Assignments" : string.Empty;
|
var racialString = racial ? " and Racial Assignments" : string.Empty;
|
||||||
var @base = ByType(CollectionType.Default);
|
var @base = ByType(CollectionType.Default);
|
||||||
var male = ByType(CollectionType.MalePlayerCharacter);
|
var male = ByType(CollectionType.MalePlayerCharacter);
|
||||||
var female = ByType(CollectionType.FemalePlayerCharacter);
|
var female = ByType(CollectionType.FemalePlayerCharacter);
|
||||||
if (male == yourself && female == yourself)
|
if (male == yourself && female == yourself)
|
||||||
return
|
return
|
||||||
$"Assignment is redundant due to overwriting Male Players and Female Players{racialString} with an identical collection.\nYou can remove it.";
|
$"Assignment is redundant due to overwriting Male Players and Female Players{racialString} with an identical collection.\nYou can remove it.";
|
||||||
|
|
||||||
if (male == null)
|
if (male == null)
|
||||||
{
|
{
|
||||||
if (female == null && @base == yourself)
|
if (female == null && @base == yourself)
|
||||||
return $"Assignment is redundant due to overwriting Base{racialString} with an identical collection.\nYou can remove it.";
|
return
|
||||||
|
$"Assignment is redundant due to overwriting Base{racialString} with an identical collection.\nYou can remove it.";
|
||||||
if (female == yourself && @base == yourself)
|
if (female == yourself && @base == yourself)
|
||||||
return
|
return
|
||||||
$"Assignment is redundant due to overwriting Base and Female Players{racialString} with an identical collection.\nYou can remove it.";
|
$"Assignment is redundant due to overwriting Base and Female Players{racialString} with an identical collection.\nYou can remove it.";
|
||||||
}
|
}
|
||||||
else if (male == yourself && female == null && @base == yourself)
|
else if (male == yourself && female == null && @base == yourself)
|
||||||
{
|
{
|
||||||
return $"Assignment is redundant due to overwriting Base and Male Players{racialString} with an identical collection.\nYou can remove it.";
|
return
|
||||||
|
$"Assignment is redundant due to overwriting Base and Male Players{racialString} with an identical collection.\nYou can remove it.";
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
// Check individual assignments. We can only be sure of redundancy for world-overlap or ownership overlap.
|
// Check individual assignments. We can only be sure of redundancy for world-overlap or ownership overlap.
|
||||||
case CollectionType.Individual:
|
case CollectionType.Individual:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using OtterGui;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Collections.Manager;
|
namespace Penumbra.Collections.Manager;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using OtterGui.Filesystem;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Collections.Manager;
|
namespace Penumbra.Collections.Manager;
|
||||||
|
|
@ -246,7 +247,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
||||||
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
|
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
|
||||||
DirectoryInfo? newDirectory)
|
DirectoryInfo? newDirectory)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case ModPathChangeType.Added:
|
case ModPathChangeType.Added:
|
||||||
foreach (var collection in this)
|
foreach (var collection in this)
|
||||||
|
|
|
||||||
|
|
@ -427,13 +427,13 @@ public static class CollectionTypeExtensions
|
||||||
public static string ToDescription(this CollectionType collectionType)
|
public static string ToDescription(this CollectionType collectionType)
|
||||||
=> collectionType switch
|
=> collectionType switch
|
||||||
{
|
{
|
||||||
CollectionType.Default => "World, Music, Furniture, baseline for characters and monsters not specialized.",
|
CollectionType.Default => "World, Music, Furniture, baseline for characters and monsters not specialized.",
|
||||||
CollectionType.Interface => "User Interface, Icons, Maps, Styles.",
|
CollectionType.Interface => "User Interface, Icons, Maps, Styles.",
|
||||||
CollectionType.Yourself => "Your characters, regardless of name, race or gender. Applies in the login screen.",
|
CollectionType.Yourself => "Your characters, regardless of name, race or gender. Applies in the login screen.",
|
||||||
CollectionType.MalePlayerCharacter => "Baseline for male player characters.",
|
CollectionType.MalePlayerCharacter => "Baseline for male player characters.",
|
||||||
CollectionType.FemalePlayerCharacter => "Baseline for female player characters.",
|
CollectionType.FemalePlayerCharacter => "Baseline for female player characters.",
|
||||||
CollectionType.MaleNonPlayerCharacter => "Baseline for humanoid male non-player characters.",
|
CollectionType.MaleNonPlayerCharacter => "Baseline for humanoid male non-player characters.",
|
||||||
CollectionType.FemaleNonPlayerCharacter => "Baseline for humanoid female non-player characters.",
|
CollectionType.FemaleNonPlayerCharacter => "Baseline for humanoid female non-player characters.",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Handle generic NPC
|
// Handle generic NPC
|
||||||
var npcIdentifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty, ushort.MaxValue,
|
var npcIdentifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty,
|
||||||
|
ushort.MaxValue,
|
||||||
identifier.Kind, identifier.DataId);
|
identifier.Kind, identifier.DataId);
|
||||||
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -56,7 +57,8 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
if (!_config.UseOwnerNameForCharacterCollection)
|
if (!_config.UseOwnerNameForCharacterCollection)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
identifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
|
identifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName,
|
||||||
|
identifier.HomeWorld.Id,
|
||||||
ObjectKind.None, uint.MaxValue);
|
ObjectKind.None, uint.MaxValue);
|
||||||
return CheckWorlds(identifier, out collection);
|
return CheckWorlds(identifier, out collection);
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +144,8 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
if (_individuals.TryGetValue(identifier, out collection))
|
if (_individuals.TryGetValue(identifier, out collection))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
identifier = _actorService.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind,
|
identifier = _actorService.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue,
|
||||||
|
identifier.Kind,
|
||||||
identifier.DataId);
|
identifier.DataId);
|
||||||
if (identifier.IsValid && _individuals.TryGetValue(identifier, out collection))
|
if (identifier.IsValid && _individuals.TryGetValue(identifier, out collection))
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ public partial class IndividualCollections
|
||||||
{
|
{
|
||||||
if (_actorService.Valid)
|
if (_actorService.Valid)
|
||||||
return ReadJObjectInternal(obj, storage);
|
return ReadJObjectInternal(obj, storage);
|
||||||
|
|
||||||
void Func()
|
void Func()
|
||||||
{
|
{
|
||||||
if (ReadJObjectInternal(obj, storage))
|
if (ReadJObjectInternal(obj, storage))
|
||||||
|
|
@ -35,9 +36,10 @@ public partial class IndividualCollections
|
||||||
Loaded.Invoke();
|
Loaded.Invoke();
|
||||||
_actorService.FinishedCreation -= Func;
|
_actorService.FinishedCreation -= Func;
|
||||||
}
|
}
|
||||||
|
|
||||||
_actorService.FinishedCreation += Func;
|
_actorService.FinishedCreation += Func;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ReadJObjectInternal(JArray? obj, CollectionStorage storage)
|
private bool ReadJObjectInternal(JArray? obj, CollectionStorage storage)
|
||||||
{
|
{
|
||||||
|
|
@ -85,6 +87,7 @@ public partial class IndividualCollections
|
||||||
NotificationType.Error);
|
NotificationType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,8 @@ public sealed partial class IndividualCollections
|
||||||
_ => throw new NotImplementedException(),
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
return table.Where(kvp => kvp.Value == name)
|
return table.Where(kvp => kvp.Value == name)
|
||||||
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id, identifier.Kind,
|
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||||
|
identifier.Kind,
|
||||||
kvp.Key)).ToArray();
|
kvp.Key)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ public partial class ModCollection
|
||||||
// Used for short periods of changed files.
|
// Used for short periods of changed files.
|
||||||
public MetaList.MetaReverter TemporarilySetEqdpFile(CharacterUtility utility, GenderRace genderRace, bool accessory)
|
public MetaList.MetaReverter TemporarilySetEqdpFile(CharacterUtility utility, GenderRace genderRace, bool accessory)
|
||||||
=> _cache?.Meta.TemporarilySetEqdpFile(genderRace, accessory)
|
=> _cache?.Meta.TemporarilySetEqdpFile(genderRace, accessory)
|
||||||
?? utility.TemporarilyResetResource(Interop.Structs.CharacterUtilityData.EqdpIdx(genderRace, accessory));
|
?? utility.TemporarilyResetResource(CharacterUtilityData.EqdpIdx(genderRace, accessory));
|
||||||
|
|
||||||
public MetaList.MetaReverter TemporarilySetEqpFile(CharacterUtility utility)
|
public MetaList.MetaReverter TemporarilySetEqpFile(CharacterUtility utility)
|
||||||
=> _cache?.Meta.TemporarilySetEqpFile()
|
=> _cache?.Meta.TemporarilySetEqpFile()
|
||||||
|
|
@ -109,4 +109,4 @@ public partial class ModCollection
|
||||||
public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type)
|
public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type)
|
||||||
=> _cache?.Meta.TemporarilySetEstFile(type)
|
=> _cache?.Meta.TemporarilySetEstFile(type)
|
||||||
?? utility.TemporarilyResetResource((MetaIndex)type);
|
?? utility.TemporarilyResetResource((MetaIndex)type);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
@ -44,10 +45,10 @@ public partial class ModCollection
|
||||||
/// This is used for material and imc changes.
|
/// This is used for material and imc changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ChangeCounter { get; private set; }
|
public int ChangeCounter { get; private set; }
|
||||||
|
|
||||||
/// <summary> Increment the number of changes in the effective file list. </summary>
|
/// <summary> Increment the number of changes in the effective file list. </summary>
|
||||||
public int IncrementCounter()
|
public int IncrementCounter()
|
||||||
=> ++ChangeCounter;
|
=> ++ChangeCounter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If a ModSetting is null, it can be inherited from other collections.
|
/// If a ModSetting is null, it can be inherited from other collections.
|
||||||
|
|
@ -57,7 +58,7 @@ public partial class ModCollection
|
||||||
|
|
||||||
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
|
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
|
||||||
public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings;
|
public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings;
|
||||||
|
|
||||||
/// <summary> Inheritances stored before they can be applied. </summary>
|
/// <summary> Inheritances stored before they can be applied. </summary>
|
||||||
public IReadOnlyList<string>? InheritanceByName;
|
public IReadOnlyList<string>? InheritanceByName;
|
||||||
|
|
||||||
|
|
@ -118,7 +119,7 @@ public partial class ModCollection
|
||||||
/// <summary> Constructor for reading from files. </summary>
|
/// <summary> Constructor for reading from files. </summary>
|
||||||
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index,
|
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index,
|
||||||
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
|
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
|
||||||
{
|
{
|
||||||
Debug.Assert(index > 0, "Collection read with non-positive index.");
|
Debug.Assert(index > 0, "Collection read with non-positive index.");
|
||||||
var ret = new ModCollection(name, index, 0, version, new List<ModSettings?>(), new List<ModCollection>(), allSettings)
|
var ret = new ModCollection(name, index, 0, version, new List<ModSettings?>(), new List<ModCollection>(), allSettings)
|
||||||
{
|
{
|
||||||
|
|
@ -130,7 +131,7 @@ public partial class ModCollection
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Constructor for temporary collections. </summary>
|
/// <summary> Constructor for temporary collections. </summary>
|
||||||
public static ModCollection CreateTemporary(string name, int index, int changeCounter)
|
public static ModCollection CreateTemporary(string name, int index, int changeCounter)
|
||||||
{
|
{
|
||||||
Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
|
Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
|
||||||
var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
|
var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
|
||||||
|
|
@ -142,9 +143,10 @@ public partial class ModCollection
|
||||||
public static ModCollection CreateEmpty(string name, int index, int modCount)
|
public static ModCollection CreateEmpty(string name, int index, int modCount)
|
||||||
{
|
{
|
||||||
Debug.Assert(index >= 0, "Empty collection created with negative index.");
|
Debug.Assert(index >= 0, "Empty collection created with negative index.");
|
||||||
return new ModCollection(name, index, 0, CurrentVersion, Enumerable.Repeat((ModSettings?) null, modCount).ToList(), new List<ModCollection>(),
|
return new ModCollection(name, index, 0, CurrentVersion, Enumerable.Repeat((ModSettings?)null, modCount).ToList(),
|
||||||
|
new List<ModCollection>(),
|
||||||
new Dictionary<string, ModSettings.SavedSettings>());
|
new Dictionary<string, ModSettings.SavedSettings>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
|
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
|
||||||
internal bool AddMod(Mod mod)
|
internal bool AddMod(Mod mod)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Penumbra.Mods;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
@ -53,7 +54,7 @@ internal readonly struct ModCollectionSave : ISavable
|
||||||
}
|
}
|
||||||
|
|
||||||
list.AddRange(_modCollection.UnusedSettings.Select(kvp => (kvp.Key, kvp.Value)));
|
list.AddRange(_modCollection.UnusedSettings.Select(kvp => (kvp.Key, kvp.Value)));
|
||||||
list.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.OrdinalIgnoreCase));
|
list.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
foreach (var (modDir, settings) in list)
|
foreach (var (modDir, settings) in list)
|
||||||
{
|
{
|
||||||
|
|
@ -64,7 +65,7 @@ internal readonly struct ModCollectionSave : ISavable
|
||||||
j.WriteEndObject();
|
j.WriteEndObject();
|
||||||
|
|
||||||
// Inherit by collection name.
|
// Inherit by collection name.
|
||||||
j.WritePropertyName("Inheritance");
|
j.WritePropertyName("Inheritance");
|
||||||
x.Serialize(j, _modCollection.InheritanceByName ?? _modCollection.DirectlyInheritsFrom.Select(c => c.Name));
|
x.Serialize(j, _modCollection.InheritanceByName ?? _modCollection.DirectlyInheritsFrom.Select(c => c.Name));
|
||||||
j.WriteEndObject();
|
j.WriteEndObject();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ public readonly struct ResolveData
|
||||||
public bool Valid
|
public bool Valid
|
||||||
=> _modCollection != null;
|
=> _modCollection != null;
|
||||||
|
|
||||||
public ResolveData()
|
public ResolveData()
|
||||||
: this(null!, nint.Zero)
|
: this(null!, nint.Zero)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public ResolveData(ModCollection collection, nint gameObject)
|
public ResolveData(ModCollection collection, nint gameObject)
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,8 @@ public class CommandHandler : IDisposable
|
||||||
private bool SetUiMinimumSize(string _)
|
private bool SetUiMinimumSize(string _)
|
||||||
{
|
{
|
||||||
if (_config.MinimumSize.X == Configuration.Constants.MinimumSizeX && _config.MinimumSize.Y == Configuration.Constants.MinimumSizeY)
|
if (_config.MinimumSize.X == Configuration.Constants.MinimumSizeX && _config.MinimumSize.Y == Configuration.Constants.MinimumSizeY)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
_config.MinimumSize.X = Configuration.Constants.MinimumSizeX;
|
_config.MinimumSize.X = Configuration.Constants.MinimumSizeX;
|
||||||
_config.MinimumSize.Y = Configuration.Constants.MinimumSizeY;
|
_config.MinimumSize.Y = Configuration.Constants.MinimumSizeY;
|
||||||
_config.Save();
|
_config.Save();
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ public sealed class CollectionChange : EventWrapper<Action<CollectionType, ModCo
|
||||||
|
|
||||||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnCollectionChange"/>
|
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnCollectionChange"/>
|
||||||
ModFileSystemSelector = 0,
|
ModFileSystemSelector = 0,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CollectionChange()
|
public CollectionChange()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public sealed class ModDataChanged : EventWrapper<Action<ModDataChangeType, Mod,
|
||||||
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDataChange"/>
|
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDataChange"/>
|
||||||
ModCacheManager = 0,
|
ModCacheManager = 0,
|
||||||
|
|
||||||
/// <seealso cref="Mods.ModFileSystem.OnDataChange"/>
|
/// <seealso cref="Mods.Manager.ModFileSystem.OnDataChange"/>
|
||||||
ModFileSystem = 0,
|
ModFileSystem = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.Mods.Manager;
|
||||||
|
|
||||||
namespace Penumbra.Communication;
|
namespace Penumbra.Communication;
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ public sealed class ModDiscoveryFinished : EventWrapper<Action, ModDiscoveryFini
|
||||||
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDiscoveryFinished"/>
|
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDiscoveryFinished"/>
|
||||||
ModCacheManager = 0,
|
ModCacheManager = 0,
|
||||||
|
|
||||||
/// <seealso cref="Mods.ModFileSystem.Reload"/>
|
/// <seealso cref="Mods.Manager.ModFileSystem.Reload"/>
|
||||||
ModFileSystem = 0,
|
ModFileSystem = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public sealed class ModDiscoveryStarted : EventWrapper<Action, ModDiscoveryStart
|
||||||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.StoreCurrentSelection"/>
|
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.StoreCurrentSelection"/>
|
||||||
ModFileSystemSelector = 200,
|
ModFileSystemSelector = 200,
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModDiscoveryStarted()
|
public ModDiscoveryStarted()
|
||||||
: base(nameof(ModDiscoveryStarted))
|
: base(nameof(ModDiscoveryStarted))
|
||||||
{ }
|
{ }
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod,
|
||||||
/// <seealso cref="Mods.Manager.ModExportManager.OnModPathChange"/>
|
/// <seealso cref="Mods.Manager.ModExportManager.OnModPathChange"/>
|
||||||
ModExportManager = 0,
|
ModExportManager = 0,
|
||||||
|
|
||||||
/// <seealso cref="Mods.ModFileSystem.OnModPathChange"/>
|
/// <seealso cref="Mods.Manager.ModFileSystem.OnModPathChange"/>
|
||||||
ModFileSystem = 0,
|
ModFileSystem = 0,
|
||||||
|
|
||||||
/// <seealso cref="Mods.Manager.ModManager.OnModPathChange"/>
|
/// <seealso cref="Mods.Manager.ModManager.OnModPathChange"/>
|
||||||
|
|
@ -48,6 +48,7 @@ public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod,
|
||||||
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeRemoval"/>
|
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeRemoval"/>
|
||||||
CollectionCacheManagerRemoval = 100,
|
CollectionCacheManagerRemoval = 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModPathChanged()
|
public ModPathChanged()
|
||||||
: base(nameof(ModPathChanged))
|
: base(nameof(ModPathChanged))
|
||||||
{ }
|
{ }
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ using OtterGui.Classes;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
using Penumbra.UI.ResourceWatcher;
|
||||||
using Penumbra.UI.Tabs;
|
using Penumbra.UI.Tabs;
|
||||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@
|
||||||
|
|
||||||
global using System;
|
global using System;
|
||||||
global using System.Collections;
|
global using System.Collections;
|
||||||
|
global using System.Collections.Concurrent;
|
||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
global using System.Diagnostics;
|
global using System.Diagnostics;
|
||||||
global using System.IO;
|
global using System.IO;
|
||||||
global using System.Linq;
|
global using System.Linq;
|
||||||
global using System.Numerics;
|
global using System.Numerics;
|
||||||
|
global using System.Reflection;
|
||||||
global using System.Runtime.CompilerServices;
|
global using System.Runtime.CompilerServices;
|
||||||
global using System.Runtime.InteropServices;
|
global using System.Runtime.InteropServices;
|
||||||
global using System.Security.Cryptography;
|
global using System.Security.Cryptography;
|
||||||
global using System.Threading;
|
global using System.Threading;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ public enum ImporterState
|
||||||
ExtractingModFiles,
|
ExtractingModFiles,
|
||||||
DeduplicatingFiles,
|
DeduplicatingFiles,
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,4 @@ public class StreamDisposer : PenumbraSqPackStream, IDisposable
|
||||||
|
|
||||||
File.Delete(filePath);
|
File.Delete(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,19 @@ namespace Penumbra.Import;
|
||||||
|
|
||||||
public partial class TexToolsImporter
|
public partial class TexToolsImporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extract regular compressed archives that are folders containing penumbra-formatted mods.
|
/// Extract regular compressed archives that are folders containing penumbra-formatted mods.
|
||||||
/// The mod has to either contain a meta.json at top level, or one folder deep.
|
/// The mod has to either contain a meta.json at top level, or one folder deep.
|
||||||
/// If the meta.json is one folder deep, all other files have to be in the same folder.
|
/// If the meta.json is one folder deep, all other files have to be in the same folder.
|
||||||
/// The extracted folder gets its name either from that one top-level folder or from the mod name.
|
/// The extracted folder gets its name either from that one top-level folder or from the mod name.
|
||||||
/// All data is extracted without manipulation of the files or metadata.
|
/// All data is extracted without manipulation of the files or metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private DirectoryInfo HandleRegularArchive( FileInfo modPackFile )
|
private DirectoryInfo HandleRegularArchive(FileInfo modPackFile)
|
||||||
{
|
{
|
||||||
using var zfs = modPackFile.OpenRead();
|
using var zfs = modPackFile.OpenRead();
|
||||||
using var archive = ArchiveFactory.Open( zfs );
|
using var archive = ArchiveFactory.Open(zfs);
|
||||||
|
|
||||||
var baseName = FindArchiveModMeta( archive, out var leadDir );
|
var baseName = FindArchiveModMeta(archive, out var leadDir);
|
||||||
var name = string.Empty;
|
var name = string.Empty;
|
||||||
_currentOptionIdx = 0;
|
_currentOptionIdx = 0;
|
||||||
_currentNumOptions = 1;
|
_currentNumOptions = 1;
|
||||||
|
|
@ -42,9 +42,9 @@ public partial class TexToolsImporter
|
||||||
SevenZipArchive s => s.Entries.Count,
|
SevenZipArchive s => s.Entries.Count,
|
||||||
_ => archive.Entries.Count(),
|
_ => archive.Entries.Count(),
|
||||||
};
|
};
|
||||||
Penumbra.Log.Information( $" -> Importing {archive.Type} Archive." );
|
Penumbra.Log.Information($" -> Importing {archive.Type} Archive.");
|
||||||
|
|
||||||
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, Path.GetRandomFileName() );
|
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, Path.GetRandomFileName());
|
||||||
var options = new ExtractionOptions()
|
var options = new ExtractionOptions()
|
||||||
{
|
{
|
||||||
ExtractFullPath = true,
|
ExtractFullPath = true,
|
||||||
|
|
@ -55,40 +55,38 @@ public partial class TexToolsImporter
|
||||||
_currentFileIdx = 0;
|
_currentFileIdx = 0;
|
||||||
var reader = archive.ExtractAllEntries();
|
var reader = archive.ExtractAllEntries();
|
||||||
|
|
||||||
while( reader.MoveToNextEntry() )
|
while (reader.MoveToNextEntry())
|
||||||
{
|
{
|
||||||
_token.ThrowIfCancellationRequested();
|
_token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if( reader.Entry.IsDirectory )
|
if (reader.Entry.IsDirectory)
|
||||||
{
|
{
|
||||||
--_currentNumFiles;
|
--_currentNumFiles;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Penumbra.Log.Information( $" -> Extracting {reader.Entry.Key}" );
|
Penumbra.Log.Information($" -> Extracting {reader.Entry.Key}");
|
||||||
// Check that the mod has a valid name in the meta.json file.
|
// Check that the mod has a valid name in the meta.json file.
|
||||||
if( Path.GetFileName( reader.Entry.Key ) == "meta.json" )
|
if (Path.GetFileName(reader.Entry.Key) == "meta.json")
|
||||||
{
|
{
|
||||||
using var s = new MemoryStream();
|
using var s = new MemoryStream();
|
||||||
using var e = reader.OpenEntryStream();
|
using var e = reader.OpenEntryStream();
|
||||||
e.CopyTo( s );
|
e.CopyTo(s);
|
||||||
s.Seek( 0, SeekOrigin.Begin );
|
s.Seek(0, SeekOrigin.Begin);
|
||||||
using var t = new StreamReader( s );
|
using var t = new StreamReader(s);
|
||||||
using var j = new JsonTextReader( t );
|
using var j = new JsonTextReader(t);
|
||||||
var obj = JObject.Load( j );
|
var obj = JObject.Load(j);
|
||||||
name = obj[ nameof( Mod.Name ) ]?.Value< string >()?.RemoveInvalidPathSymbols() ?? string.Empty;
|
name = obj[nameof(Mod.Name)]?.Value<string>()?.RemoveInvalidPathSymbols() ?? string.Empty;
|
||||||
if( name.Length == 0 )
|
if (name.Length == 0)
|
||||||
{
|
throw new Exception("Invalid mod archive: mod meta has no name.");
|
||||||
throw new Exception( "Invalid mod archive: mod meta has no name." );
|
|
||||||
}
|
|
||||||
|
|
||||||
using var f = File.OpenWrite( Path.Combine( _currentModDirectory.FullName, reader.Entry.Key ) );
|
using var f = File.OpenWrite(Path.Combine(_currentModDirectory.FullName, reader.Entry.Key));
|
||||||
s.Seek( 0, SeekOrigin.Begin );
|
s.Seek(0, SeekOrigin.Begin);
|
||||||
s.WriteTo( f );
|
s.WriteTo(f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader.WriteEntryToDirectory( _currentModDirectory.FullName, options );
|
reader.WriteEntryToDirectory(_currentModDirectory.FullName, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
++_currentFileIdx;
|
++_currentFileIdx;
|
||||||
|
|
@ -97,60 +95,59 @@ public partial class TexToolsImporter
|
||||||
_token.ThrowIfCancellationRequested();
|
_token.ThrowIfCancellationRequested();
|
||||||
var oldName = _currentModDirectory.FullName;
|
var oldName = _currentModDirectory.FullName;
|
||||||
// Use either the top-level directory as the mods base name, or the (fixed for path) name in the json.
|
// Use either the top-level directory as the mods base name, or the (fixed for path) name in the json.
|
||||||
if( leadDir )
|
if (leadDir)
|
||||||
{
|
{
|
||||||
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, baseName, false );
|
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, baseName, false);
|
||||||
Directory.Move( Path.Combine( oldName, baseName ), _currentModDirectory.FullName );
|
Directory.Move(Path.Combine(oldName, baseName), _currentModDirectory.FullName);
|
||||||
Directory.Delete( oldName );
|
Directory.Delete(oldName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, name, false );
|
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, name, false);
|
||||||
Directory.Move( oldName, _currentModDirectory.FullName );
|
Directory.Move(oldName, _currentModDirectory.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentModDirectory.Refresh();
|
_currentModDirectory.Refresh();
|
||||||
_modManager.Creator.SplitMultiGroups( _currentModDirectory );
|
_modManager.Creator.SplitMultiGroups(_currentModDirectory);
|
||||||
|
|
||||||
return _currentModDirectory;
|
return _currentModDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Search the archive for the meta.json file which needs to exist.
|
// Search the archive for the meta.json file which needs to exist.
|
||||||
private static string FindArchiveModMeta( IArchive archive, out bool leadDir )
|
private static string FindArchiveModMeta(IArchive archive, out bool leadDir)
|
||||||
{
|
{
|
||||||
var entry = archive.Entries.FirstOrDefault( e => !e.IsDirectory && Path.GetFileName( e.Key ) == "meta.json" );
|
var entry = archive.Entries.FirstOrDefault(e => !e.IsDirectory && Path.GetFileName(e.Key) == "meta.json");
|
||||||
// None found.
|
// None found.
|
||||||
if( entry == null )
|
if (entry == null)
|
||||||
{
|
throw new Exception("Invalid mod archive: No meta.json contained.");
|
||||||
throw new Exception( "Invalid mod archive: No meta.json contained." );
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret = string.Empty;
|
var ret = string.Empty;
|
||||||
leadDir = false;
|
leadDir = false;
|
||||||
|
|
||||||
// If the file is not at top-level.
|
// If the file is not at top-level.
|
||||||
if( entry.Key != "meta.json" )
|
if (entry.Key != "meta.json")
|
||||||
{
|
{
|
||||||
leadDir = true;
|
leadDir = true;
|
||||||
var directory = Path.GetDirectoryName( entry.Key );
|
var directory = Path.GetDirectoryName(entry.Key);
|
||||||
// Should not happen.
|
// Should not happen.
|
||||||
if( directory.IsNullOrEmpty() )
|
if (directory.IsNullOrEmpty())
|
||||||
{
|
throw new Exception("Invalid mod archive: Unknown error fetching meta.json.");
|
||||||
throw new Exception( "Invalid mod archive: Unknown error fetching meta.json." );
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = directory;
|
ret = directory;
|
||||||
// Check that all other files are also contained in the top-level directory.
|
// Check that all other files are also contained in the top-level directory.
|
||||||
if( ret.IndexOfAny( new[] { '/', '\\' } ) >= 0
|
if (ret.IndexOfAny(new[]
|
||||||
|| !archive.Entries.All( e => e.Key.StartsWith( ret ) && ( e.Key.Length == ret.Length || e.Key[ ret.Length ] is '/' or '\\' ) ) )
|
{
|
||||||
{
|
'/',
|
||||||
|
'\\',
|
||||||
|
})
|
||||||
|
>= 0
|
||||||
|
|| !archive.Entries.All(e => e.Key.StartsWith(ret) && (e.Key.Length == ret.Length || e.Key[ret.Length] is '/' or '\\')))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"Invalid mod archive: meta.json in wrong location. It needs to be either at root or one directory deep, in which all other files must be nested too." );
|
"Invalid mod archive: meta.json in wrong location. It needs to be either at root or one directory deep, in which all other files must be nested too.");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Import;
|
namespace Penumbra.Import;
|
||||||
|
|
@ -20,89 +20,79 @@ public partial class TexToolsImporter
|
||||||
private string _currentOptionName = string.Empty;
|
private string _currentOptionName = string.Empty;
|
||||||
private string _currentFileName = string.Empty;
|
private string _currentFileName = string.Empty;
|
||||||
|
|
||||||
public void DrawProgressInfo( Vector2 size )
|
public void DrawProgressInfo(Vector2 size)
|
||||||
{
|
{
|
||||||
if( _modPackCount == 0 )
|
if (_modPackCount == 0)
|
||||||
{
|
{
|
||||||
ImGuiUtil.Center( "Nothing to extract." );
|
ImGuiUtil.Center("Nothing to extract.");
|
||||||
}
|
}
|
||||||
else if( _modPackCount == _currentModPackIdx )
|
else if (_modPackCount == _currentModPackIdx)
|
||||||
{
|
{
|
||||||
DrawEndState();
|
DrawEndState();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
var percentage = _modPackCount / ( float )_currentModPackIdx;
|
var percentage = _modPackCount / (float)_currentModPackIdx;
|
||||||
ImGui.ProgressBar( percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}" );
|
ImGui.ProgressBar(percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}");
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if( State == ImporterState.DeduplicatingFiles )
|
if (State == ImporterState.DeduplicatingFiles)
|
||||||
{
|
ImGui.TextUnformatted($"Deduplicating {_currentModName}...");
|
||||||
ImGui.TextUnformatted( $"Deduplicating {_currentModName}..." );
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
ImGui.TextUnformatted($"Extracting {_currentModName}...");
|
||||||
ImGui.TextUnformatted( $"Extracting {_currentModName}..." );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( _currentNumOptions > 1 )
|
if (_currentNumOptions > 1)
|
||||||
{
|
{
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / ( float )_currentNumOptions;
|
percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / (float)_currentNumOptions;
|
||||||
ImGui.ProgressBar( percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}" );
|
ImGui.ProgressBar(percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}");
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if( State != ImporterState.DeduplicatingFiles )
|
if (State != ImporterState.DeduplicatingFiles)
|
||||||
{
|
|
||||||
ImGui.TextUnformatted(
|
ImGui.TextUnformatted(
|
||||||
$"Extracting option {( _currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - " )}{_currentOptionName}..." );
|
$"Extracting option {(_currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - ")}{_currentOptionName}...");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / ( float )_currentNumFiles;
|
percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / (float)_currentNumFiles;
|
||||||
ImGui.ProgressBar( percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}" );
|
ImGui.ProgressBar(percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}");
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if( State != ImporterState.DeduplicatingFiles )
|
if (State != ImporterState.DeduplicatingFiles)
|
||||||
{
|
ImGui.TextUnformatted($"Extracting file {_currentFileName}...");
|
||||||
ImGui.TextUnformatted( $"Extracting file {_currentFileName}..." );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DrawEndState()
|
private void DrawEndState()
|
||||||
{
|
{
|
||||||
var success = ExtractedMods.Count( t => t.Error == null );
|
var success = ExtractedMods.Count(t => t.Error == null);
|
||||||
|
|
||||||
ImGui.TextUnformatted( $"Successfully extracted {success} / {ExtractedMods.Count} files." );
|
ImGui.TextUnformatted($"Successfully extracted {success} / {ExtractedMods.Count} files.");
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
using var table = ImRaii.Table( "##files", 2 );
|
using var table = ImRaii.Table("##files", 2);
|
||||||
if( !table )
|
if (!table)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var (file, dir, ex) in ExtractedMods )
|
foreach (var (file, dir, ex) in ExtractedMods)
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted( file.Name );
|
ImGui.TextUnformatted(file.Name);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if( ex == null )
|
if (ex == null)
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.FolderExpanded.Value() );
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
|
||||||
ImGui.TextUnformatted( dir?.FullName[ ( _baseDirectory.FullName.Length + 1 ).. ] ?? "Unknown Directory" );
|
ImGui.TextUnformatted(dir?.FullName[(_baseDirectory.FullName.Length + 1)..] ?? "Unknown Directory");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.ConflictingMod.Value() );
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ConflictingMod.Value());
|
||||||
ImGui.TextUnformatted( ex.Message );
|
ImGui.TextUnformatted(ex.Message);
|
||||||
ImGuiUtil.HoverTooltip( ex.ToString() );
|
ImGuiUtil.HoverTooltip(ex.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DrawCancelButton( Vector2 size )
|
public bool DrawCancelButton(Vector2 size)
|
||||||
=> ImGuiUtil.DrawDisabledButton( "Cancel", size, string.Empty, _token.IsCancellationRequested );
|
=> ImGuiUtil.DrawDisabledButton("Cancel", size, string.Empty, _token.IsCancellationRequested);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ public partial class TexToolsMeta
|
||||||
var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0.
|
var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0.
|
||||||
foreach (var value in values)
|
foreach (var value in values)
|
||||||
{
|
{
|
||||||
if (_keepDefault || !value.Equals(def.GetEntry(partIdx, (Variant) i)))
|
if (_keepDefault || !value.Equals(def.GetEntry(partIdx, (Variant)i)))
|
||||||
{
|
{
|
||||||
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
|
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
|
||||||
value);
|
value);
|
||||||
|
|
|
||||||
|
|
@ -8,72 +8,70 @@ namespace Penumbra.Import;
|
||||||
public partial class TexToolsMeta
|
public partial class TexToolsMeta
|
||||||
{
|
{
|
||||||
// Parse a single rgsp file.
|
// Parse a single rgsp file.
|
||||||
public static TexToolsMeta FromRgspFile( MetaFileManager manager, string filePath, byte[] data, bool keepDefault )
|
public static TexToolsMeta FromRgspFile(MetaFileManager manager, string filePath, byte[] data, bool keepDefault)
|
||||||
{
|
{
|
||||||
if( data.Length != 45 && data.Length != 42 )
|
if (data.Length != 45 && data.Length != 42)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error( "Error while parsing .rgsp file:\n\tInvalid number of bytes." );
|
Penumbra.Log.Error("Error while parsing .rgsp file:\n\tInvalid number of bytes.");
|
||||||
return Invalid;
|
return Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var s = new MemoryStream( data );
|
using var s = new MemoryStream(data);
|
||||||
using var br = new BinaryReader( s );
|
using var br = new BinaryReader(s);
|
||||||
// The first value is a flag that signifies version.
|
// The first value is a flag that signifies version.
|
||||||
// If it is byte.max, the following two bytes are the version,
|
// If it is byte.max, the following two bytes are the version,
|
||||||
// otherwise it is version 1 and signifies the sub race instead.
|
// otherwise it is version 1 and signifies the sub race instead.
|
||||||
var flag = br.ReadByte();
|
var flag = br.ReadByte();
|
||||||
var version = flag != 255 ? ( uint )1 : br.ReadUInt16();
|
var version = flag != 255 ? (uint)1 : br.ReadUInt16();
|
||||||
|
|
||||||
var ret = new TexToolsMeta( manager, filePath, version );
|
var ret = new TexToolsMeta(manager, filePath, version);
|
||||||
|
|
||||||
// SubRace is offset by one due to Unknown.
|
// SubRace is offset by one due to Unknown.
|
||||||
var subRace = ( SubRace )( version == 1 ? flag + 1 : br.ReadByte() + 1 );
|
var subRace = (SubRace)(version == 1 ? flag + 1 : br.ReadByte() + 1);
|
||||||
if( !Enum.IsDefined( typeof( SubRace ), subRace ) || subRace == SubRace.Unknown )
|
if (!Enum.IsDefined(typeof(SubRace), subRace) || subRace == SubRace.Unknown)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error( $"Error while parsing .rgsp file:\n\t{subRace} is not a valid SubRace." );
|
Penumbra.Log.Error($"Error while parsing .rgsp file:\n\t{subRace} is not a valid SubRace.");
|
||||||
return Invalid;
|
return Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next byte is Gender. 1 is Female, 0 is Male.
|
// Next byte is Gender. 1 is Female, 0 is Male.
|
||||||
var gender = br.ReadByte();
|
var gender = br.ReadByte();
|
||||||
if( gender != 1 && gender != 0 )
|
if (gender != 1 && gender != 0)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error( $"Error while parsing .rgsp file:\n\t{gender} is neither Male nor Female." );
|
Penumbra.Log.Error($"Error while parsing .rgsp file:\n\t{gender} is neither Male nor Female.");
|
||||||
return Invalid;
|
return Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the given values to the manipulations if they are not default.
|
// Add the given values to the manipulations if they are not default.
|
||||||
void Add( RspAttribute attribute, float value )
|
void Add(RspAttribute attribute, float value)
|
||||||
{
|
{
|
||||||
var def = CmpFile.GetDefault( manager, subRace, attribute );
|
var def = CmpFile.GetDefault(manager, subRace, attribute);
|
||||||
if( keepDefault || value != def )
|
if (keepDefault || value != def)
|
||||||
{
|
ret.MetaManipulations.Add(new RspManipulation(subRace, attribute, value));
|
||||||
ret.MetaManipulations.Add( new RspManipulation( subRace, attribute, value ) );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( gender == 1 )
|
if (gender == 1)
|
||||||
{
|
{
|
||||||
Add( RspAttribute.FemaleMinSize, br.ReadSingle() );
|
Add(RspAttribute.FemaleMinSize, br.ReadSingle());
|
||||||
Add( RspAttribute.FemaleMaxSize, br.ReadSingle() );
|
Add(RspAttribute.FemaleMaxSize, br.ReadSingle());
|
||||||
Add( RspAttribute.FemaleMinTail, br.ReadSingle() );
|
Add(RspAttribute.FemaleMinTail, br.ReadSingle());
|
||||||
Add( RspAttribute.FemaleMaxTail, br.ReadSingle() );
|
Add(RspAttribute.FemaleMaxTail, br.ReadSingle());
|
||||||
|
|
||||||
Add( RspAttribute.BustMinX, br.ReadSingle() );
|
Add(RspAttribute.BustMinX, br.ReadSingle());
|
||||||
Add( RspAttribute.BustMinY, br.ReadSingle() );
|
Add(RspAttribute.BustMinY, br.ReadSingle());
|
||||||
Add( RspAttribute.BustMinZ, br.ReadSingle() );
|
Add(RspAttribute.BustMinZ, br.ReadSingle());
|
||||||
Add( RspAttribute.BustMaxX, br.ReadSingle() );
|
Add(RspAttribute.BustMaxX, br.ReadSingle());
|
||||||
Add( RspAttribute.BustMaxY, br.ReadSingle() );
|
Add(RspAttribute.BustMaxY, br.ReadSingle());
|
||||||
Add( RspAttribute.BustMaxZ, br.ReadSingle() );
|
Add(RspAttribute.BustMaxZ, br.ReadSingle());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Add( RspAttribute.MaleMinSize, br.ReadSingle() );
|
Add(RspAttribute.MaleMinSize, br.ReadSingle());
|
||||||
Add( RspAttribute.MaleMaxSize, br.ReadSingle() );
|
Add(RspAttribute.MaleMaxSize, br.ReadSingle());
|
||||||
Add( RspAttribute.MaleMinTail, br.ReadSingle() );
|
Add(RspAttribute.MaleMinTail, br.ReadSingle());
|
||||||
Add( RspAttribute.MaleMaxTail, br.ReadSingle() );
|
Add(RspAttribute.MaleMaxTail, br.ReadSingle());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,70 +17,68 @@ namespace Penumbra.Import;
|
||||||
/// TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored.
|
/// TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored.
|
||||||
/// TexTools also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file.</summary>
|
/// TexTools also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file.</summary>
|
||||||
public partial class TexToolsMeta
|
public partial class TexToolsMeta
|
||||||
{
|
{
|
||||||
/// <summary> An empty TexToolsMeta. </summary>
|
/// <summary> An empty TexToolsMeta. </summary>
|
||||||
public static readonly TexToolsMeta Invalid = new(null!, string.Empty, 0);
|
public static readonly TexToolsMeta Invalid = new(null!, string.Empty, 0);
|
||||||
|
|
||||||
// The info class determines the files or table locations the changes need to apply to from the filename.
|
// The info class determines the files or table locations the changes need to apply to from the filename.
|
||||||
public readonly uint Version;
|
public readonly uint Version;
|
||||||
public readonly string FilePath;
|
public readonly string FilePath;
|
||||||
public readonly List< MetaManipulation > MetaManipulations = new();
|
public readonly List<MetaManipulation> MetaManipulations = new();
|
||||||
private readonly bool _keepDefault = false;
|
private readonly bool _keepDefault = false;
|
||||||
|
|
||||||
private readonly MetaFileManager _metaFileManager;
|
|
||||||
|
|
||||||
public TexToolsMeta( MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault )
|
private readonly MetaFileManager _metaFileManager;
|
||||||
{
|
|
||||||
|
public TexToolsMeta(MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault)
|
||||||
|
{
|
||||||
_metaFileManager = metaFileManager;
|
_metaFileManager = metaFileManager;
|
||||||
_keepDefault = keepDefault;
|
_keepDefault = keepDefault;
|
||||||
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 MetaFileInfo( parser, FilePath );
|
var metaInfo = new MetaFileInfo(parser, 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< (MetaManipulation.Type 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 = ( MetaManipulation.Type )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( MetaManipulation.Type 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)
|
||||||
{
|
{
|
||||||
FilePath = "";
|
FilePath = "";
|
||||||
Penumbra.Log.Error( $"Error while parsing .meta file:\n{e}" );
|
Penumbra.Log.Error($"Error while parsing .meta file:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TexToolsMeta( MetaFileManager metaFileManager, string filePath, uint version )
|
private TexToolsMeta(MetaFileManager metaFileManager, string filePath, uint version)
|
||||||
{
|
{
|
||||||
_metaFileManager = metaFileManager;
|
_metaFileManager = metaFileManager;
|
||||||
FilePath = filePath;
|
FilePath = filePath;
|
||||||
|
|
@ -88,14 +86,12 @@ public partial class TexToolsMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a null terminated string from a binary reader.
|
// Read a null terminated string from a binary reader.
|
||||||
private static string ReadNullTerminated( BinaryReader reader )
|
private static string ReadNullTerminated(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() )
|
for (var c = reader.ReadChar(); c != 0; c = reader.ReadChar())
|
||||||
{
|
builder.Append(c);
|
||||||
builder.Append( c );
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ public readonly record struct RgbaPixelData(int Width, int Height, byte[] PixelD
|
||||||
|
|
||||||
public RgbaPixelData((int Width, int Height) size, byte[] pixelData)
|
public RgbaPixelData((int Width, int Height) size, byte[] pixelData)
|
||||||
: this(size.Width, size.Height, pixelData)
|
: this(size.Width, size.Height, pixelData)
|
||||||
{
|
{ }
|
||||||
}
|
|
||||||
|
|
||||||
public Image<Rgba32> ToImage()
|
public Image<Rgba32> ToImage()
|
||||||
=> Image.LoadPixelData<Rgba32>(PixelData, Width, Height);
|
=> Image.LoadPixelData<Rgba32>(PixelData, Width, Height);
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ public static class TexFileParser
|
||||||
w.Write(header.Width);
|
w.Write(header.Width);
|
||||||
w.Write(header.Height);
|
w.Write(header.Height);
|
||||||
w.Write(header.Depth);
|
w.Write(header.Depth);
|
||||||
w.Write((byte) header.MipLevels);
|
w.Write((byte)header.MipLevels);
|
||||||
w.Write((byte) 0); // TODO Lumina Update
|
w.Write((byte)0); // TODO Lumina Update
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
w.Write(header.LodOffset[0]);
|
w.Write(header.LodOffset[0]);
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ public static class TextureDrawer
|
||||||
|
|
||||||
public sealed class PathSelectCombo : FilterComboCache<(string, bool)>
|
public sealed class PathSelectCombo : FilterComboCache<(string, bool)>
|
||||||
{
|
{
|
||||||
private int _skipPrefix = 0;
|
private int _skipPrefix = 0;
|
||||||
|
|
||||||
public PathSelectCombo(TextureManager textures, ModEditor editor)
|
public PathSelectCombo(TextureManager textures, ModEditor editor)
|
||||||
: base(() => CreateFiles(textures, editor))
|
: base(() => CreateFiles(textures, editor))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,8 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase
|
||||||
textureSize[0] = TextureWidth;
|
textureSize[0] = TextureWidth;
|
||||||
textureSize[1] = TextureHeight;
|
textureSize[1] = TextureHeight;
|
||||||
|
|
||||||
using var texture = new SafeTextureHandle(Structs.TextureUtility.Create2D(Device.Instance(), textureSize, 1, 0x2460, 0x80000804, 7), false);
|
using var texture =
|
||||||
|
new SafeTextureHandle(Structs.TextureUtility.Create2D(Device.Instance(), textureSize, 1, 0x2460, 0x80000804, 7), false);
|
||||||
if (texture.IsInvalid)
|
if (texture.IsInvalid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Penumbra.Interop.PathResolving;
|
||||||
public unsafe class AnimationHookService : IDisposable
|
public unsafe class AnimationHookService : IDisposable
|
||||||
{
|
{
|
||||||
private readonly PerformanceTracker _performance;
|
private readonly PerformanceTracker _performance;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly DrawObjectState _drawObjectState;
|
private readonly DrawObjectState _drawObjectState;
|
||||||
private readonly CollectionResolver _resolver;
|
private readonly CollectionResolver _resolver;
|
||||||
|
|
@ -34,7 +34,7 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
_drawObjectState = drawObjectState;
|
_drawObjectState = drawObjectState;
|
||||||
_resolver = resolver;
|
_resolver = resolver;
|
||||||
_conditions = conditions;
|
_conditions = conditions;
|
||||||
|
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
var last = _characterSoundData.Value;
|
var last = _characterSoundData.Value;
|
||||||
_characterSoundData.Value = _collectionResolver.IdentifyCollection((GameObject*)character, true);
|
_characterSoundData.Value = _collectionResolver.IdentifyCollection((GameObject*)character, true);
|
||||||
var ret = _loadCharacterSoundHook.Original(character, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
|
var ret = _loadCharacterSoundHook.Original(character, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
|
||||||
_characterSoundData.Value = last;
|
_characterSoundData.Value = last;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,15 +140,15 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
using var performance = _performance.Measure(PerformanceType.TimelineResources);
|
using var performance = _performance.Measure(PerformanceType.TimelineResources);
|
||||||
// Do not check timeline loading in cutscenes.
|
// Do not check timeline loading in cutscenes.
|
||||||
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
|
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
|
||||||
return _loadTimelineResourcesHook.Original(timeline);
|
return _loadTimelineResourcesHook.Original(timeline);
|
||||||
|
|
||||||
var last = _animationLoadData.Value;
|
var last = _animationLoadData.Value;
|
||||||
_animationLoadData.Value = GetDataFromTimeline(timeline);
|
_animationLoadData.Value = GetDataFromTimeline(timeline);
|
||||||
var ret = _loadTimelineResourcesHook.Original(timeline);
|
var ret = _loadTimelineResourcesHook.Original(timeline);
|
||||||
_animationLoadData.Value = last;
|
_animationLoadData.Value = last;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Probably used when the base idle animation gets loaded.
|
/// Probably used when the base idle animation gets loaded.
|
||||||
/// Make it aware of the correct collection to load the correct pap files.
|
/// Make it aware of the correct collection to load the correct pap files.
|
||||||
|
|
@ -297,12 +297,12 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (timeline != IntPtr.Zero)
|
if (timeline != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc];
|
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc];
|
||||||
var idx = getGameObjectIdx(timeline);
|
var idx = getGameObjectIdx(timeline);
|
||||||
if (idx >= 0 && idx < _objects.Length)
|
if (idx >= 0 && idx < _objects.Length)
|
||||||
{
|
{
|
||||||
var obj = (GameObject*)_objects.GetObjectAddress(idx);
|
var obj = (GameObject*)_objects.GetObjectAddress(idx);
|
||||||
return obj != null ? _collectionResolver.IdentifyCollection(obj, true) : ResolveData.Invalid;
|
return obj != null ? _collectionResolver.IdentifyCollection(obj, true) : ResolveData.Invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -378,17 +378,17 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
if (a6 == nint.Zero)
|
if (a6 == nint.Zero)
|
||||||
return _apricotListenerSoundPlayHook!.Original(a1, a2, a3, a4, a5, a6);
|
return _apricotListenerSoundPlayHook!.Original(a1, a2, a3, a4, a5, a6);
|
||||||
|
|
||||||
var last = _animationLoadData.Value;
|
var last = _animationLoadData.Value;
|
||||||
// a6 is some instance of Apricot.IInstanceListenner, in some cases we can obtain the associated caster via vfunc 1.
|
// a6 is some instance of Apricot.IInstanceListenner, in some cases we can obtain the associated caster via vfunc 1.
|
||||||
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)a6)[1](a6);
|
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)a6)[1](a6);
|
||||||
if (gameObject != null)
|
if (gameObject != null)
|
||||||
{
|
{
|
||||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true);
|
_animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// for VfxListenner we can obtain the associated draw object as its first member,
|
// for VfxListenner we can obtain the associated draw object as its first member,
|
||||||
// if the object has different type, drawObject will contain other values or garbage,
|
// if the object has different type, drawObject will contain other values or garbage,
|
||||||
// but only be used in a dictionary pointer lookup, so this does not hurt.
|
// but only be used in a dictionary pointer lookup, so this does not hurt.
|
||||||
var drawObject = ((DrawObject**)a6)[1];
|
var drawObject = ((DrawObject**)a6)[1];
|
||||||
if (drawObject != null)
|
if (drawObject != null)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public unsafe class CollectionResolver
|
||||||
private readonly HumanModelList _humanModels;
|
private readonly HumanModelList _humanModels;
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorService _actors;
|
||||||
private readonly CutsceneService _cutscenes;
|
private readonly CutsceneService _cutscenes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Penumbra.Interop.PathResolving;
|
||||||
|
|
||||||
public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>
|
public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>
|
||||||
{
|
{
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
private readonly GameEventManager _gameEvents;
|
private readonly GameEventManager _gameEvents;
|
||||||
|
|
||||||
private readonly Dictionary<nint, (nint GameObject, bool IsChild)> _drawObjectToGameObject = new();
|
private readonly Dictionary<nint, (nint GameObject, bool IsChild)> _drawObjectToGameObject = new();
|
||||||
|
|
@ -71,8 +71,8 @@ public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, boo
|
||||||
|
|
||||||
private unsafe void OnWeaponReloaded(nint _, nint gameObject)
|
private unsafe void OnWeaponReloaded(nint _, nint gameObject)
|
||||||
{
|
{
|
||||||
_lastGameObject.Value!.Dequeue();
|
_lastGameObject.Value!.Dequeue();
|
||||||
IterateDrawObjectTree((Object*) ((GameObject*) gameObject)->DrawObject, gameObject, false, false);
|
IterateDrawObjectTree((Object*)((GameObject*)gameObject)->DrawObject, gameObject, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCharacterBaseDestructor(nint characterBase)
|
private void OnCharacterBaseDestructor(nint characterBase)
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A
|
||||||
_events = events;
|
_events = events;
|
||||||
|
|
||||||
_communicator.CollectionChange.Subscribe(CollectionChangeClear, CollectionChange.Priority.IdentifiedCollectionCache);
|
_communicator.CollectionChange.Subscribe(CollectionChangeClear, CollectionChange.Priority.IdentifiedCollectionCache);
|
||||||
_clientState.TerritoryChanged += TerritoryClear;
|
_clientState.TerritoryChanged += TerritoryClear;
|
||||||
_events.CharacterDestructor += OnCharacterDestruct;
|
_events.CharacterDestructor += OnCharacterDestruct;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResolveData Set(ModCollection collection, ActorIdentifier identifier, GameObject* data)
|
public ResolveData Set(ModCollection collection, ActorIdentifier identifier, GameObject* data)
|
||||||
|
|
@ -61,8 +61,8 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_communicator.CollectionChange.Unsubscribe(CollectionChangeClear);
|
_communicator.CollectionChange.Unsubscribe(CollectionChangeClear);
|
||||||
_clientState.TerritoryChanged -= TerritoryClear;
|
_clientState.TerritoryChanged -= TerritoryClear;
|
||||||
_events.CharacterDestructor -= OnCharacterDestruct;
|
_events.CharacterDestructor -= OnCharacterDestruct;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<(nint Address, ActorIdentifier Identifier, ModCollection Collection)> GetEnumerator()
|
public IEnumerator<(nint Address, ActorIdentifier Identifier, ModCollection Collection)> GetEnumerator()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,6 @@ public unsafe class FileReadService : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private nint GetResourceManager()
|
private nint GetResourceManager()
|
||||||
=> !_lastFileThreadResourceManager.IsValueCreated || _lastFileThreadResourceManager.Value == IntPtr.Zero
|
=> !_lastFileThreadResourceManager.IsValueCreated || _lastFileThreadResourceManager.Value == IntPtr.Zero
|
||||||
? (nint) _resourceManager.ResourceManager
|
? (nint)_resourceManager.ResourceManager
|
||||||
: _lastFileThreadResourceManager.Value;
|
: _lastFileThreadResourceManager.Value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ public class ResourceNode
|
||||||
Children = new List<ResourceNode>();
|
Children = new List<ResourceNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceNode(UIData uiData, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath[] possibleGamePaths, FullPath fullPath,
|
public ResourceNode(UIData uiData, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath[] possibleGamePaths,
|
||||||
|
FullPath fullPath,
|
||||||
ulong length, bool @internal)
|
ulong length, bool @internal)
|
||||||
{
|
{
|
||||||
Name = uiData.Name;
|
Name = uiData.Name;
|
||||||
|
|
@ -69,7 +70,7 @@ public class ResourceNode
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceNode WithUIData(string? name, ChangedItemIcon icon)
|
public ResourceNode WithUIData(string? name, ChangedItemIcon icon)
|
||||||
=> string.Equals(Name, name, StringComparison.Ordinal) && Icon == icon ? this : new ResourceNode(new(name, icon), this);
|
=> string.Equals(Name, name, StringComparison.Ordinal) && Icon == icon ? this : new ResourceNode(new UIData(name, icon), this);
|
||||||
|
|
||||||
public ResourceNode WithUIData(UIData uiData)
|
public ResourceNode WithUIData(UIData uiData)
|
||||||
=> string.Equals(Name, uiData.Name, StringComparison.Ordinal) && Icon == uiData.Icon ? this : new ResourceNode(uiData, this);
|
=> string.Equals(Name, uiData.Name, StringComparison.Ordinal) && Icon == uiData.Icon ? this : new ResourceNode(uiData, this);
|
||||||
|
|
@ -77,6 +78,6 @@ public class ResourceNode
|
||||||
public readonly record struct UIData(string? Name, ChangedItemIcon Icon)
|
public readonly record struct UIData(string? Name, ChangedItemIcon Icon)
|
||||||
{
|
{
|
||||||
public readonly UIData PrependName(string prefix)
|
public readonly UIData PrependName(string prefix)
|
||||||
=> Name == null ? this : new(prefix + Name, Icon);
|
=> Name == null ? this : new UIData(prefix + Name, Icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -16,7 +16,7 @@ public class ResourceTree
|
||||||
public readonly nint DrawObjectAddress;
|
public readonly nint DrawObjectAddress;
|
||||||
public readonly bool PlayerRelated;
|
public readonly bool PlayerRelated;
|
||||||
public readonly string CollectionName;
|
public readonly string CollectionName;
|
||||||
public readonly List<ResourceNode> Nodes;
|
public readonly List<ResourceNode> Nodes;
|
||||||
public readonly HashSet<ResourceNode> FlatNodes;
|
public readonly HashSet<ResourceNode> FlatNodes;
|
||||||
|
|
||||||
public int ModelId;
|
public int ModelId;
|
||||||
|
|
@ -26,11 +26,11 @@ public class ResourceTree
|
||||||
public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
|
public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
GameObjectAddress = gameObjectAddress;
|
GameObjectAddress = gameObjectAddress;
|
||||||
DrawObjectAddress = drawObjectAddress;
|
DrawObjectAddress = drawObjectAddress;
|
||||||
PlayerRelated = playerRelated;
|
PlayerRelated = playerRelated;
|
||||||
CollectionName = collectionName;
|
CollectionName = collectionName;
|
||||||
Nodes = new List<ResourceNode>();
|
Nodes = new List<ResourceNode>();
|
||||||
FlatNodes = new HashSet<ResourceNode>();
|
FlatNodes = new HashSet<ResourceNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class ResourceTree
|
||||||
// var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 );
|
// var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 );
|
||||||
ModelId = character->CharacterData.ModelCharaId;
|
ModelId = character->CharacterData.ModelCharaId;
|
||||||
CustomizeData = character->DrawData.CustomizeData;
|
CustomizeData = character->DrawData.CustomizeData;
|
||||||
RaceCode = model->GetModelType() == CharacterBase.ModelType.Human ? (GenderRace) ((Human*)model)->RaceSexId : GenderRace.Unknown;
|
RaceCode = model->GetModelType() == CharacterBase.ModelType.Human ? (GenderRace)((Human*)model)->RaceSexId : GenderRace.Unknown;
|
||||||
|
|
||||||
for (var i = 0; i < model->SlotCount; ++i)
|
for (var i = 0; i < model->SlotCount; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -60,8 +60,8 @@ public class ResourceTree
|
||||||
var mdlNode = context.CreateNodeFromRenderModel(mdl);
|
var mdlNode = context.CreateNodeFromRenderModel(mdl);
|
||||||
if (mdlNode != null)
|
if (mdlNode != null)
|
||||||
Nodes.Add(globalContext.WithUiData ? mdlNode.WithUIData(mdlNode.Name ?? $"Model #{i}", mdlNode.Icon) : mdlNode);
|
Nodes.Add(globalContext.WithUiData ? mdlNode.WithUIData(mdlNode.Name ?? $"Model #{i}", mdlNode.Icon) : mdlNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
|
AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
|
||||||
|
|
||||||
if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc)
|
if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc)
|
||||||
|
|
@ -100,8 +100,8 @@ public class ResourceTree
|
||||||
subObjectNodes.Add(globalContext.WithUiData
|
subObjectNodes.Add(globalContext.WithUiData
|
||||||
? mdlNode.WithUIData(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}", mdlNode.Icon)
|
? mdlNode.WithUIData(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}", mdlNode.Icon)
|
||||||
: mdlNode);
|
: mdlNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddSkeleton(subObjectNodes, subObjectContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, ");
|
AddSkeleton(subObjectNodes, subObjectContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, ");
|
||||||
|
|
||||||
subObject = (CharacterBase*)subObject->DrawObject.Object.NextSiblingObject;
|
subObject = (CharacterBase*)subObject->DrawObject.Object.NextSiblingObject;
|
||||||
|
|
@ -119,19 +119,21 @@ public class ResourceTree
|
||||||
|
|
||||||
var legacyDecalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->LegacyBodyDecal);
|
var legacyDecalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->LegacyBodyDecal);
|
||||||
if (legacyDecalNode != null)
|
if (legacyDecalNode != null)
|
||||||
Nodes.Add(globalContext.WithUiData ? legacyDecalNode.WithUIData(legacyDecalNode.Name ?? "Legacy Body Decal", legacyDecalNode.Icon) : legacyDecalNode);
|
Nodes.Add(globalContext.WithUiData
|
||||||
}
|
? legacyDecalNode.WithUIData(legacyDecalNode.Name ?? "Legacy Body Decal", legacyDecalNode.Icon)
|
||||||
|
: legacyDecalNode);
|
||||||
private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context, Skeleton* skeleton, string prefix = "")
|
}
|
||||||
{
|
|
||||||
if (skeleton == null)
|
private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context, Skeleton* skeleton, string prefix = "")
|
||||||
return;
|
{
|
||||||
|
if (skeleton == null)
|
||||||
for (var i = 0; i < skeleton->PartialSkeletonCount; ++i)
|
return;
|
||||||
{
|
|
||||||
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
|
for (var i = 0; i < skeleton->PartialSkeletonCount; ++i)
|
||||||
if (sklbNode != null)
|
{
|
||||||
nodes.Add(context.WithUiData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode);
|
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
|
||||||
}
|
if (sklbNode != null)
|
||||||
|
nodes.Add(context.WithUiData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,25 @@ namespace Penumbra.Interop.SafeHandles;
|
||||||
|
|
||||||
public unsafe class SafeResourceHandle : SafeHandle
|
public unsafe class SafeResourceHandle : SafeHandle
|
||||||
{
|
{
|
||||||
public ResourceHandle* ResourceHandle => (ResourceHandle*)handle;
|
public ResourceHandle* ResourceHandle
|
||||||
|
=> (ResourceHandle*)handle;
|
||||||
|
|
||||||
public override bool IsInvalid => handle == 0;
|
public override bool IsInvalid
|
||||||
|
=> handle == 0;
|
||||||
|
|
||||||
public SafeResourceHandle(ResourceHandle* handle, bool incRef, bool ownsHandle = true) : base(0, ownsHandle)
|
public SafeResourceHandle(ResourceHandle* handle, bool incRef, bool ownsHandle = true)
|
||||||
|
: base(0, ownsHandle)
|
||||||
{
|
{
|
||||||
if (incRef && !ownsHandle)
|
if (incRef && !ownsHandle)
|
||||||
throw new ArgumentException("Non-owning SafeResourceHandle with IncRef is unsupported");
|
throw new ArgumentException("Non-owning SafeResourceHandle with IncRef is unsupported");
|
||||||
|
|
||||||
if (incRef && handle != null)
|
if (incRef && handle != null)
|
||||||
handle->IncRef();
|
handle->IncRef();
|
||||||
SetHandle((nint)handle);
|
SetHandle((nint)handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SafeResourceHandle CreateInvalid()
|
public static SafeResourceHandle CreateInvalid()
|
||||||
=> new(null, incRef: false);
|
=> new(null, false);
|
||||||
|
|
||||||
protected override bool ReleaseHandle()
|
protected override bool ReleaseHandle()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,18 @@ namespace Penumbra.Interop.SafeHandles;
|
||||||
|
|
||||||
public unsafe class SafeTextureHandle : SafeHandle
|
public unsafe class SafeTextureHandle : SafeHandle
|
||||||
{
|
{
|
||||||
public Texture* Texture => (Texture*)handle;
|
public Texture* Texture
|
||||||
|
=> (Texture*)handle;
|
||||||
|
|
||||||
public override bool IsInvalid => handle == 0;
|
public override bool IsInvalid
|
||||||
|
=> handle == 0;
|
||||||
|
|
||||||
public SafeTextureHandle(Texture* handle, bool incRef, bool ownsHandle = true) : base(0, ownsHandle)
|
public SafeTextureHandle(Texture* handle, bool incRef, bool ownsHandle = true)
|
||||||
|
: base(0, ownsHandle)
|
||||||
{
|
{
|
||||||
if (incRef && !ownsHandle)
|
if (incRef && !ownsHandle)
|
||||||
throw new ArgumentException("Non-owning SafeTextureHandle with IncRef is unsupported");
|
throw new ArgumentException("Non-owning SafeTextureHandle with IncRef is unsupported");
|
||||||
|
|
||||||
if (incRef && handle != null)
|
if (incRef && handle != null)
|
||||||
TextureUtility.IncRef(handle);
|
TextureUtility.IncRef(handle);
|
||||||
SetHandle((nint)handle);
|
SetHandle((nint)handle);
|
||||||
|
|
@ -27,16 +31,17 @@ public unsafe class SafeTextureHandle : SafeHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SafeTextureHandle CreateInvalid()
|
public static SafeTextureHandle CreateInvalid()
|
||||||
=> new(null, incRef: false);
|
=> new(null, false);
|
||||||
|
|
||||||
protected override bool ReleaseHandle()
|
protected override bool ReleaseHandle()
|
||||||
{
|
{
|
||||||
nint handle;
|
nint handle;
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
handle = this.handle;
|
handle = this.handle;
|
||||||
this.handle = 0;
|
this.handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle != 0)
|
if (handle != 0)
|
||||||
TextureUtility.DecRef((Texture*)handle);
|
TextureUtility.DecRef((Texture*)handle);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public sealed unsafe class DecalReverter : IDisposable
|
||||||
public static readonly Utf8GamePath TransparentPath =
|
public static readonly Utf8GamePath TransparentPath =
|
||||||
Utf8GamePath.FromSpan("chara/common/texture/transparent.tex"u8, out var p) ? p : Utf8GamePath.Empty;
|
Utf8GamePath.FromSpan("chara/common/texture/transparent.tex"u8, out var p) ? p : Utf8GamePath.Empty;
|
||||||
|
|
||||||
private readonly CharacterUtility _utility;
|
private readonly CharacterUtility _utility;
|
||||||
private readonly Structs.TextureResourceHandle* _decal;
|
private readonly Structs.TextureResourceHandle* _decal;
|
||||||
private readonly Structs.TextureResourceHandle* _transparent;
|
private readonly Structs.TextureResourceHandle* _transparent;
|
||||||
|
|
||||||
|
|
@ -22,10 +22,10 @@ public sealed unsafe class DecalReverter : IDisposable
|
||||||
{
|
{
|
||||||
_utility = utility;
|
_utility = utility;
|
||||||
var ptr = _utility.Address;
|
var ptr = _utility.Address;
|
||||||
_decal = null;
|
_decal = null;
|
||||||
_transparent = null;
|
_transparent = null;
|
||||||
if (!config.EnableMods)
|
if (!config.EnableMods)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (doDecal)
|
if (doDecal)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ public unsafe class GameEventManager : IDisposable
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
((CreatingCharacterBaseEvent)subscriber).Invoke((nint) (&a), b, c);
|
((CreatingCharacterBaseEvent)subscriber).Invoke((nint)(&a), b, c);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -265,11 +265,13 @@ public unsafe class GameEventManager : IDisposable
|
||||||
private readonly Hook<TestDelegate>? _testHook = null;
|
private readonly Hook<TestDelegate>? _testHook = null;
|
||||||
|
|
||||||
private delegate void TestDelegate(nint a1, int a2);
|
private delegate void TestDelegate(nint a1, int a2);
|
||||||
|
|
||||||
private void TestDetour(nint a1, int a2)
|
private void TestDetour(nint a1, int a2)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Information($"Test: {a1:X} {a2}");
|
Penumbra.Log.Information($"Test: {a1:X} {a2}");
|
||||||
_testHook!.Original(a1, a2);
|
_testHook!.Original(a1, a2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnableDebugHook()
|
private void EnableDebugHook()
|
||||||
=> _testHook?.Enable();
|
=> _testHook?.Enable();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,10 +100,10 @@ public unsafe partial class RedrawService
|
||||||
|
|
||||||
public sealed unsafe partial class RedrawService : IDisposable
|
public sealed unsafe partial class RedrawService : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Framework _framework;
|
private readonly Framework _framework;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
private readonly ITargetManager _targets;
|
private readonly ITargetManager _targets;
|
||||||
private readonly Condition _conditions;
|
private readonly Condition _conditions;
|
||||||
|
|
||||||
private readonly List<int> _queue = new(100);
|
private readonly List<int> _queue = new(100);
|
||||||
private readonly List<int> _afterGPoseQueue = new(GPoseSlots);
|
private readonly List<int> _afterGPoseQueue = new(GPoseSlots);
|
||||||
|
|
@ -207,7 +207,7 @@ public sealed unsafe partial class RedrawService : IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_targets.Target = actor;
|
_targets.Target = actor;
|
||||||
_target = -1;
|
_target = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleRedraw()
|
private void HandleRedraw()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Services;
|
namespace Penumbra.Interop.Services;
|
||||||
|
|
||||||
public unsafe class ResidentResourceManager
|
public unsafe class ResidentResourceManager
|
||||||
|
|
@ -36,4 +36,4 @@ public unsafe class ResidentResourceManager
|
||||||
LoadPlayerResources.Invoke(Address);
|
LoadPlayerResources.Invoke(Address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,23 @@ using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct CharacterUtilityData
|
public unsafe struct CharacterUtilityData
|
||||||
{
|
{
|
||||||
public const int IndexTransparentTex = 72;
|
public const int IndexTransparentTex = 72;
|
||||||
public const int IndexDecalTex = 73;
|
public const int IndexDecalTex = 73;
|
||||||
public const int IndexSkinShpk = 76;
|
public const int IndexSkinShpk = 76;
|
||||||
|
|
||||||
public static readonly MetaIndex[] EqdpIndices = Enum.GetNames< MetaIndex >()
|
public static readonly MetaIndex[] EqdpIndices = Enum.GetNames<MetaIndex>()
|
||||||
.Zip( Enum.GetValues< MetaIndex >() )
|
.Zip(Enum.GetValues<MetaIndex>())
|
||||||
.Where( n => n.First.StartsWith( "Eqdp" ) )
|
.Where(n => n.First.StartsWith("Eqdp"))
|
||||||
.Select( n => n.Second ).ToArray();
|
.Select(n => n.Second).ToArray();
|
||||||
|
|
||||||
public const int TotalNumResources = 87;
|
public const int TotalNumResources = 87;
|
||||||
|
|
||||||
/// <summary> Obtain the index for the eqdp file corresponding to the given race code and accessory. </summary>
|
/// <summary> Obtain the index for the eqdp file corresponding to the given race code and accessory. </summary>
|
||||||
public static MetaIndex EqdpIdx( GenderRace raceCode, bool accessory )
|
public static MetaIndex EqdpIdx(GenderRace raceCode, bool accessory)
|
||||||
=> +( int )raceCode switch
|
=> +(int)raceCode switch
|
||||||
{
|
{
|
||||||
0101 => accessory ? MetaIndex.Eqdp0101Acc : MetaIndex.Eqdp0101,
|
0101 => accessory ? MetaIndex.Eqdp0101Acc : MetaIndex.Eqdp0101,
|
||||||
0201 => accessory ? MetaIndex.Eqdp0201Acc : MetaIndex.Eqdp0201,
|
0201 => accessory ? MetaIndex.Eqdp0201Acc : MetaIndex.Eqdp0201,
|
||||||
|
|
@ -48,53 +48,53 @@ public unsafe struct CharacterUtilityData
|
||||||
1404 => accessory ? MetaIndex.Eqdp1404Acc : MetaIndex.Eqdp1404,
|
1404 => accessory ? MetaIndex.Eqdp1404Acc : MetaIndex.Eqdp1404,
|
||||||
9104 => accessory ? MetaIndex.Eqdp9104Acc : MetaIndex.Eqdp9104,
|
9104 => accessory ? MetaIndex.Eqdp9104Acc : MetaIndex.Eqdp9104,
|
||||||
9204 => accessory ? MetaIndex.Eqdp9204Acc : MetaIndex.Eqdp9204,
|
9204 => accessory ? MetaIndex.Eqdp9204Acc : MetaIndex.Eqdp9204,
|
||||||
_ => ( MetaIndex )( -1 ),
|
_ => (MetaIndex)(-1),
|
||||||
};
|
};
|
||||||
|
|
||||||
[FieldOffset( 0 )]
|
[FieldOffset(0)]
|
||||||
public void* VTable;
|
public void* VTable;
|
||||||
|
|
||||||
[FieldOffset( 8 )]
|
[FieldOffset(8)]
|
||||||
public fixed ulong Resources[TotalNumResources];
|
public fixed ulong Resources[TotalNumResources];
|
||||||
|
|
||||||
[FieldOffset( 8 + ( int )MetaIndex.Eqp * 8 )]
|
[FieldOffset(8 + (int)MetaIndex.Eqp * 8)]
|
||||||
public ResourceHandle* EqpResource;
|
public ResourceHandle* EqpResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + ( int )MetaIndex.Gmp * 8 )]
|
[FieldOffset(8 + (int)MetaIndex.Gmp * 8)]
|
||||||
public ResourceHandle* GmpResource;
|
public ResourceHandle* GmpResource;
|
||||||
|
|
||||||
public ResourceHandle* Resource( int idx )
|
public ResourceHandle* Resource(int idx)
|
||||||
=> ( ResourceHandle* )Resources[ idx ];
|
=> (ResourceHandle*)Resources[idx];
|
||||||
|
|
||||||
public ResourceHandle* Resource( MetaIndex idx )
|
public ResourceHandle* Resource(MetaIndex idx)
|
||||||
=> Resource( ( int )idx );
|
=> Resource((int)idx);
|
||||||
|
|
||||||
public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory )
|
public ResourceHandle* EqdpResource(GenderRace raceCode, bool accessory)
|
||||||
=> Resource( ( int )EqdpIdx( raceCode, accessory ) );
|
=> Resource((int)EqdpIdx(raceCode, accessory));
|
||||||
|
|
||||||
[FieldOffset( 8 + ( int )MetaIndex.HumanCmp * 8 )]
|
[FieldOffset(8 + (int)MetaIndex.HumanCmp * 8)]
|
||||||
public ResourceHandle* HumanCmpResource;
|
public ResourceHandle* HumanCmpResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + ( int )MetaIndex.FaceEst * 8 )]
|
[FieldOffset(8 + (int)MetaIndex.FaceEst * 8)]
|
||||||
public ResourceHandle* FaceEstResource;
|
public ResourceHandle* FaceEstResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + ( int )MetaIndex.HairEst * 8 )]
|
[FieldOffset(8 + (int)MetaIndex.HairEst * 8)]
|
||||||
public ResourceHandle* HairEstResource;
|
public ResourceHandle* HairEstResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + ( int )MetaIndex.BodyEst * 8 )]
|
[FieldOffset(8 + (int)MetaIndex.BodyEst * 8)]
|
||||||
public ResourceHandle* BodyEstResource;
|
public ResourceHandle* BodyEstResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + ( int )MetaIndex.HeadEst * 8 )]
|
[FieldOffset(8 + (int)MetaIndex.HeadEst * 8)]
|
||||||
public ResourceHandle* HeadEstResource;
|
public ResourceHandle* HeadEstResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + IndexTransparentTex * 8 )]
|
[FieldOffset(8 + IndexTransparentTex * 8)]
|
||||||
public TextureResourceHandle* TransparentTexResource;
|
public TextureResourceHandle* TransparentTexResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + IndexDecalTex * 8 )]
|
[FieldOffset(8 + IndexDecalTex * 8)]
|
||||||
public TextureResourceHandle* DecalTexResource;
|
public TextureResourceHandle* DecalTexResource;
|
||||||
|
|
||||||
[FieldOffset( 8 + IndexSkinShpk * 8 )]
|
[FieldOffset(8 + IndexSkinShpk * 8)]
|
||||||
public ResourceHandle* SkinShpkResource;
|
public ResourceHandle* SkinShpkResource;
|
||||||
|
|
||||||
// not included resources have no known use case.
|
// not included resources have no known use case.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct ClipScheduler
|
public unsafe struct ClipScheduler
|
||||||
{
|
{
|
||||||
[FieldOffset( 0 )]
|
[FieldOffset(0)]
|
||||||
public IntPtr* VTable;
|
public IntPtr* VTable;
|
||||||
|
|
||||||
[FieldOffset( 0x38 )]
|
[FieldOffset(0x38)]
|
||||||
public IntPtr SchedulerTimeline;
|
public IntPtr SchedulerTimeline;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ public enum DrawState : uint
|
||||||
MaybeCulled = 0x00_00_04_00,
|
MaybeCulled = 0x00_00_04_00,
|
||||||
MaybeHiddenMinion = 0x00_00_80_00,
|
MaybeHiddenMinion = 0x00_00_80_00,
|
||||||
MaybeHiddenSummon = 0x00_80_00_00,
|
MaybeHiddenSummon = 0x00_80_00_00,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ public enum FileMode : byte
|
||||||
// Probably debug options only.
|
// Probably debug options only.
|
||||||
LoadIndexResource = 0xA, // load index/index2
|
LoadIndexResource = 0xA, // load index/index2
|
||||||
LoadSqPackResource = 0xB,
|
LoadSqPackResource = 0xB,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct HumanExt
|
public unsafe struct HumanExt
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x0 )]
|
[FieldOffset(0x0)]
|
||||||
public Human Human;
|
public Human Human;
|
||||||
|
|
||||||
[FieldOffset( 0x0 )]
|
[FieldOffset(0x0)]
|
||||||
public CharacterBaseExt CharacterBase;
|
public CharacterBaseExt CharacterBase;
|
||||||
|
|
||||||
[FieldOffset( 0x9E8 )]
|
[FieldOffset(0x9E8)]
|
||||||
public ResourceHandle* Decal;
|
public ResourceHandle* Decal;
|
||||||
|
|
||||||
[FieldOffset( 0x9F0 )]
|
[FieldOffset(0x9F0)]
|
||||||
public ResourceHandle* LegacyBodyDecal;
|
public ResourceHandle* LegacyBodyDecal;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,45 +2,46 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit, Size = 0x40 )]
|
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
|
||||||
public unsafe struct Material
|
public unsafe struct Material
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x10 )]
|
[FieldOffset(0x10)]
|
||||||
public MtrlResource* ResourceHandle;
|
public MtrlResource* ResourceHandle;
|
||||||
|
|
||||||
[FieldOffset( 0x18 )]
|
[FieldOffset(0x18)]
|
||||||
public uint ShaderPackageFlags;
|
public uint ShaderPackageFlags;
|
||||||
|
|
||||||
[FieldOffset( 0x20 )]
|
[FieldOffset(0x20)]
|
||||||
public uint* ShaderKeys;
|
public uint* ShaderKeys;
|
||||||
|
|
||||||
public int ShaderKeyCount
|
public int ShaderKeyCount
|
||||||
=> (int)((uint*)Textures - ShaderKeys);
|
=> (int)((uint*)Textures - ShaderKeys);
|
||||||
|
|
||||||
[FieldOffset( 0x28 )]
|
[FieldOffset(0x28)]
|
||||||
public ConstantBuffer* MaterialParameter;
|
public ConstantBuffer* MaterialParameter;
|
||||||
|
|
||||||
[FieldOffset( 0x30 )]
|
[FieldOffset(0x30)]
|
||||||
public TextureEntry* Textures;
|
public TextureEntry* Textures;
|
||||||
|
|
||||||
[FieldOffset( 0x38 )]
|
[FieldOffset(0x38)]
|
||||||
public ushort TextureCount;
|
public ushort TextureCount;
|
||||||
|
|
||||||
public Texture* Texture( int index ) => Textures[index].ResourceHandle->KernelTexture;
|
public Texture* Texture(int index)
|
||||||
|
=> Textures[index].ResourceHandle->KernelTexture;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit, Size = 0x18 )]
|
[StructLayout(LayoutKind.Explicit, Size = 0x18)]
|
||||||
public struct TextureEntry
|
public struct TextureEntry
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset(0x00)]
|
||||||
public uint Id;
|
public uint Id;
|
||||||
|
|
||||||
[FieldOffset( 0x08 )]
|
[FieldOffset(0x08)]
|
||||||
public TextureResourceHandle* ResourceHandle;
|
public TextureResourceHandle* ResourceHandle;
|
||||||
|
|
||||||
[FieldOffset( 0x10 )]
|
[FieldOffset(0x10)]
|
||||||
public uint SamplerFlags;
|
public uint SamplerFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<TextureEntry> TextureSpan
|
public ReadOnlySpan<TextureEntry> TextureSpan
|
||||||
=> new(Textures, TextureCount);
|
=> new(Textures, TextureCount);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,45 @@
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct MtrlResource
|
public unsafe struct MtrlResource
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset(0x00)]
|
||||||
public ResourceHandle Handle;
|
public ResourceHandle Handle;
|
||||||
|
|
||||||
[FieldOffset( 0xC8 )]
|
[FieldOffset(0xC8)]
|
||||||
public ShaderPackageResourceHandle* ShpkResourceHandle;
|
public ShaderPackageResourceHandle* ShpkResourceHandle;
|
||||||
|
|
||||||
[FieldOffset( 0xD0 )]
|
[FieldOffset(0xD0)]
|
||||||
public TextureEntry* TexSpace; // Contains the offsets for the tex files inside the string list.
|
public TextureEntry* TexSpace; // Contains the offsets for the tex files inside the string list.
|
||||||
|
|
||||||
[FieldOffset( 0xE0 )]
|
[FieldOffset(0xE0)]
|
||||||
public byte* StringList;
|
public byte* StringList;
|
||||||
|
|
||||||
[FieldOffset( 0xF8 )]
|
[FieldOffset(0xF8)]
|
||||||
public ushort ShpkOffset;
|
public ushort ShpkOffset;
|
||||||
|
|
||||||
[FieldOffset( 0xFA )]
|
[FieldOffset(0xFA)]
|
||||||
public byte NumTex;
|
public byte NumTex;
|
||||||
|
|
||||||
public byte* ShpkString
|
public byte* ShpkString
|
||||||
=> StringList + ShpkOffset;
|
=> StringList + ShpkOffset;
|
||||||
|
|
||||||
public byte* TexString( int idx )
|
public byte* TexString(int idx)
|
||||||
=> StringList + TexSpace[idx].PathOffset;
|
=> StringList + TexSpace[idx].PathOffset;
|
||||||
|
|
||||||
public bool TexIsDX11( int idx )
|
public bool TexIsDX11(int idx)
|
||||||
=> TexSpace[idx].Flags >= 0x8000;
|
=> TexSpace[idx].Flags >= 0x8000;
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
|
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
|
||||||
public struct TextureEntry
|
public struct TextureEntry
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset(0x00)]
|
||||||
public TextureResourceHandle* ResourceHandle;
|
public TextureResourceHandle* ResourceHandle;
|
||||||
|
|
||||||
[FieldOffset( 0x08 )]
|
[FieldOffset(0x08)]
|
||||||
public ushort PathOffset;
|
public ushort PathOffset;
|
||||||
|
|
||||||
[FieldOffset( 0x0A )]
|
[FieldOffset(0x0A)]
|
||||||
public ushort Flags;
|
public ushort Flags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,39 +2,39 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct RenderModel
|
public unsafe struct RenderModel
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x18 )]
|
[FieldOffset(0x18)]
|
||||||
public RenderModel* PreviousModel;
|
public RenderModel* PreviousModel;
|
||||||
|
|
||||||
[FieldOffset( 0x20 )]
|
[FieldOffset(0x20)]
|
||||||
public RenderModel* NextModel;
|
public RenderModel* NextModel;
|
||||||
|
|
||||||
[FieldOffset( 0x30 )]
|
[FieldOffset(0x30)]
|
||||||
public ResourceHandle* ResourceHandle;
|
public ResourceHandle* ResourceHandle;
|
||||||
|
|
||||||
[FieldOffset( 0x40 )]
|
[FieldOffset(0x40)]
|
||||||
public Skeleton* Skeleton;
|
public Skeleton* Skeleton;
|
||||||
|
|
||||||
[FieldOffset( 0x58 )]
|
[FieldOffset(0x58)]
|
||||||
public void** BoneList;
|
public void** BoneList;
|
||||||
|
|
||||||
[FieldOffset( 0x60 )]
|
[FieldOffset(0x60)]
|
||||||
public int BoneListCount;
|
public int BoneListCount;
|
||||||
|
|
||||||
[FieldOffset( 0x70 )]
|
[FieldOffset(0x70)]
|
||||||
private void* UnkDXBuffer1;
|
private void* UnkDXBuffer1;
|
||||||
|
|
||||||
[FieldOffset( 0x78 )]
|
[FieldOffset(0x78)]
|
||||||
private void* UnkDXBuffer2;
|
private void* UnkDXBuffer2;
|
||||||
|
|
||||||
[FieldOffset( 0x80 )]
|
[FieldOffset(0x80)]
|
||||||
private void* UnkDXBuffer3;
|
private void* UnkDXBuffer3;
|
||||||
|
|
||||||
[FieldOffset( 0x98 )]
|
[FieldOffset(0x98)]
|
||||||
public void** Materials;
|
public void** Materials;
|
||||||
|
|
||||||
[FieldOffset( 0xA0 )]
|
[FieldOffset(0xA0)]
|
||||||
public int MaterialCount;
|
public int MaterialCount;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,15 @@ namespace Penumbra.Interop.Structs;
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct ResidentResourceManager
|
public unsafe struct ResidentResourceManager
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset(0x00)]
|
||||||
public void** VTable;
|
public void** VTable;
|
||||||
|
|
||||||
[FieldOffset( 0x08 )]
|
[FieldOffset(0x08)]
|
||||||
public void** ResourceListVTable;
|
public void** ResourceListVTable;
|
||||||
|
|
||||||
[FieldOffset( 0x14 )]
|
[FieldOffset(0x14)]
|
||||||
public uint NumResources;
|
public uint NumResources;
|
||||||
|
|
||||||
[FieldOffset( 0x18 )]
|
[FieldOffset(0x18)]
|
||||||
public ResourceHandle** ResourceList;
|
public ResourceHandle** ResourceList;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,45 +8,45 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct TextureResourceHandle
|
public unsafe struct TextureResourceHandle
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x0 )]
|
[FieldOffset(0x0)]
|
||||||
public ResourceHandle Handle;
|
public ResourceHandle Handle;
|
||||||
|
|
||||||
[FieldOffset( 0x38 )]
|
[FieldOffset(0x38)]
|
||||||
public IntPtr Unk;
|
public IntPtr Unk;
|
||||||
|
|
||||||
[FieldOffset( 0x118 )]
|
[FieldOffset(0x118)]
|
||||||
public Texture* KernelTexture;
|
public Texture* KernelTexture;
|
||||||
|
|
||||||
[FieldOffset( 0x20 )]
|
[FieldOffset(0x20)]
|
||||||
public IntPtr NewKernelTexture;
|
public IntPtr NewKernelTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct ShaderPackageResourceHandle
|
public unsafe struct ShaderPackageResourceHandle
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x0 )]
|
[FieldOffset(0x0)]
|
||||||
public ResourceHandle Handle;
|
public ResourceHandle Handle;
|
||||||
|
|
||||||
[FieldOffset( 0xB0 )]
|
[FieldOffset(0xB0)]
|
||||||
public ShaderPackage* ShaderPackage;
|
public ShaderPackage* ShaderPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct ResourceHandle
|
public unsafe struct ResourceHandle
|
||||||
{
|
{
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public struct DataIndirection
|
public struct DataIndirection
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset(0x00)]
|
||||||
public void** VTable;
|
public void** VTable;
|
||||||
|
|
||||||
[FieldOffset( 0x10 )]
|
[FieldOffset(0x10)]
|
||||||
public byte* DataPtr;
|
public byte* DataPtr;
|
||||||
|
|
||||||
[FieldOffset( 0x28 )]
|
[FieldOffset(0x28)]
|
||||||
public ulong DataLength;
|
public ulong DataLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,87 +54,83 @@ public unsafe struct ResourceHandle
|
||||||
|
|
||||||
public byte* FileNamePtr()
|
public byte* FileNamePtr()
|
||||||
{
|
{
|
||||||
if( FileNameLength > SsoSize )
|
if (FileNameLength > SsoSize)
|
||||||
{
|
|
||||||
return FileNameData;
|
return FileNameData;
|
||||||
}
|
|
||||||
|
|
||||||
fixed( byte** name = &FileNameData )
|
fixed (byte** name = &FileNameData)
|
||||||
{
|
{
|
||||||
return ( byte* )name;
|
return (byte*)name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteString FileName()
|
public ByteString FileName()
|
||||||
=> ByteString.FromByteStringUnsafe( FileNamePtr(), FileNameLength, true );
|
=> ByteString.FromByteStringUnsafe(FileNamePtr(), FileNameLength, true);
|
||||||
|
|
||||||
public ReadOnlySpan< byte > FileNameAsSpan()
|
public ReadOnlySpan<byte> FileNameAsSpan()
|
||||||
=> new( FileNamePtr(), FileNameLength );
|
=> new(FileNamePtr(), FileNameLength);
|
||||||
|
|
||||||
public bool GamePath( out Utf8GamePath path )
|
public bool GamePath(out Utf8GamePath path)
|
||||||
=> Utf8GamePath.FromSpan( FileNameAsSpan(), out path );
|
=> Utf8GamePath.FromSpan(FileNameAsSpan(), out path);
|
||||||
|
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset(0x00)]
|
||||||
public void** VTable;
|
public void** VTable;
|
||||||
|
|
||||||
[FieldOffset( 0x08 )]
|
[FieldOffset(0x08)]
|
||||||
public ResourceCategory Category;
|
public ResourceCategory Category;
|
||||||
|
|
||||||
[FieldOffset( 0x0C )]
|
[FieldOffset(0x0C)]
|
||||||
public ResourceType FileType;
|
public ResourceType FileType;
|
||||||
|
|
||||||
[FieldOffset( 0x10 )]
|
[FieldOffset(0x10)]
|
||||||
public uint Id;
|
public uint Id;
|
||||||
|
|
||||||
[FieldOffset( 0x28 )]
|
[FieldOffset(0x28)]
|
||||||
public uint FileSize;
|
public uint FileSize;
|
||||||
|
|
||||||
[FieldOffset( 0x2C )]
|
[FieldOffset(0x2C)]
|
||||||
public uint FileSize2;
|
public uint FileSize2;
|
||||||
|
|
||||||
[FieldOffset( 0x34 )]
|
[FieldOffset(0x34)]
|
||||||
public uint FileSize3;
|
public uint FileSize3;
|
||||||
|
|
||||||
[FieldOffset( 0x48 )]
|
[FieldOffset(0x48)]
|
||||||
public byte* FileNameData;
|
public byte* FileNameData;
|
||||||
|
|
||||||
[FieldOffset( 0x58 )]
|
[FieldOffset(0x58)]
|
||||||
public int FileNameLength;
|
public int FileNameLength;
|
||||||
|
|
||||||
[FieldOffset( 0xAC )]
|
[FieldOffset(0xAC)]
|
||||||
public uint RefCount;
|
public uint RefCount;
|
||||||
|
|
||||||
// May return null.
|
// May return null.
|
||||||
public static byte* GetData( ResourceHandle* handle )
|
public static byte* GetData(ResourceHandle* handle)
|
||||||
=> ( ( delegate* unmanaged< ResourceHandle*, byte* > )handle->VTable[ Offsets.ResourceHandleGetDataVfunc ] )( handle );
|
=> ((delegate* unmanaged< ResourceHandle*, byte* >)handle->VTable[Offsets.ResourceHandleGetDataVfunc])(handle);
|
||||||
|
|
||||||
public static ulong GetLength( ResourceHandle* handle )
|
public static ulong GetLength(ResourceHandle* handle)
|
||||||
=> ( ( delegate* unmanaged< ResourceHandle*, ulong > )handle->VTable[ Offsets.ResourceHandleGetLengthVfunc ] )( handle );
|
=> ((delegate* unmanaged< ResourceHandle*, ulong >)handle->VTable[Offsets.ResourceHandleGetLengthVfunc])(handle);
|
||||||
|
|
||||||
|
|
||||||
// Only use these if you know what you are doing.
|
// Only use these if you know what you are doing.
|
||||||
// Those are actually only sure to be accessible for DefaultResourceHandles.
|
// Those are actually only sure to be accessible for DefaultResourceHandles.
|
||||||
[FieldOffset( 0xB0 )]
|
[FieldOffset(0xB0)]
|
||||||
public DataIndirection* Data;
|
public DataIndirection* Data;
|
||||||
|
|
||||||
[FieldOffset( 0xB8 )]
|
[FieldOffset(0xB8)]
|
||||||
public uint DataLength;
|
public uint DataLength;
|
||||||
|
|
||||||
public (IntPtr Data, int Length) GetData()
|
public (IntPtr Data, int Length) GetData()
|
||||||
=> Data != null
|
=> Data != null
|
||||||
? ( ( IntPtr )Data->DataPtr, ( int )Data->DataLength )
|
? ((IntPtr)Data->DataPtr, (int)Data->DataLength)
|
||||||
: ( IntPtr.Zero, 0 );
|
: (IntPtr.Zero, 0);
|
||||||
|
|
||||||
public bool SetData( IntPtr data, int length )
|
public bool SetData(IntPtr data, int length)
|
||||||
{
|
{
|
||||||
if( Data == null )
|
if (Data == null)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
Data->DataPtr = length != 0 ? ( byte* )data : null;
|
Data->DataPtr = length != 0 ? (byte*)data : null;
|
||||||
Data->DataLength = ( ulong )length;
|
Data->DataLength = (ulong)length;
|
||||||
DataLength = ( uint )length;
|
DataLength = (uint)length;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct SeFileDescriptor
|
public unsafe struct SeFileDescriptor
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset(0x00)]
|
||||||
public FileMode FileMode;
|
public FileMode FileMode;
|
||||||
|
|
||||||
[FieldOffset( 0x30 )]
|
[FieldOffset(0x30)]
|
||||||
public void* FileDescriptor; //
|
public void* FileDescriptor;
|
||||||
|
|
||||||
[FieldOffset( 0x50 )]
|
[FieldOffset(0x50)]
|
||||||
public ResourceHandle* ResourceHandle; //
|
public ResourceHandle* ResourceHandle;
|
||||||
|
|
||||||
|
[FieldOffset(0x70)]
|
||||||
[FieldOffset( 0x70 )]
|
public char Utf16FileName;
|
||||||
public char Utf16FileName; //
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
public unsafe static class TextureUtility
|
public static unsafe class TextureUtility
|
||||||
{
|
{
|
||||||
private static readonly Functions Funcs = new();
|
private static readonly Functions Funcs = new();
|
||||||
|
|
||||||
public static Texture* Create2D(Device* device, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk)
|
public static Texture* Create2D(Device* device, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk)
|
||||||
=> ((delegate* unmanaged<Device*, int*, byte, uint, uint, uint, Texture*>)Funcs.TextureCreate2D)(device, size, mipLevel, textureFormat, flags, unk);
|
=> ((delegate* unmanaged<Device*, int*, byte, uint, uint, uint, Texture*>)Funcs.TextureCreate2D)(device, size, mipLevel, textureFormat,
|
||||||
|
flags, unk);
|
||||||
|
|
||||||
public static bool InitializeContents(Texture* texture, void* contents)
|
public static bool InitializeContents(Texture* texture, void* contents)
|
||||||
=> ((delegate* unmanaged<Texture*, void*, bool>)Funcs.TextureInitializeContents)(texture, contents);
|
=> ((delegate* unmanaged<Texture*, void*, bool>)Funcs.TextureInitializeContents)(texture, contents);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public unsafe struct VfxParams
|
public unsafe struct VfxParams
|
||||||
{
|
{
|
||||||
[FieldOffset( 0x118 )]
|
[FieldOffset(0x118)]
|
||||||
public uint GameObjectId;
|
public uint GameObjectId;
|
||||||
|
|
||||||
[FieldOffset( 0x11C )]
|
[FieldOffset(0x11C)]
|
||||||
public byte GameObjectType;
|
public byte GameObjectType;
|
||||||
|
|
||||||
[FieldOffset( 0xD0 )]
|
[FieldOffset(0xD0)]
|
||||||
public ushort TargetCount;
|
public ushort TargetCount;
|
||||||
|
|
||||||
[FieldOffset( 0x120 )]
|
[FieldOffset(0x120)]
|
||||||
public fixed ulong Target[16];
|
public fixed ulong Target[16];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
|
||||||
|
|
||||||
if (FileIndex() == (MetaIndex)(-1))
|
if (FileIndex() == (MetaIndex)(-1))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// No check for set id.
|
// No check for set id.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,73 +9,71 @@ using SharpCompress.Common;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation >
|
public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation>
|
||||||
{
|
{
|
||||||
[JsonConverter( typeof( ForceNumericFlagEnumConverter ) )]
|
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
|
||||||
public EqpEntry Entry { get; private init; }
|
public EqpEntry Entry { get; private init; }
|
||||||
|
|
||||||
public SetId SetId { get; private init; }
|
public SetId SetId { get; private init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EquipSlot Slot { get; private init; }
|
public EquipSlot Slot { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EqpManipulation( EqpEntry entry, EquipSlot slot, SetId setId )
|
public EqpManipulation(EqpEntry entry, EquipSlot slot, SetId setId)
|
||||||
{
|
{
|
||||||
Slot = slot;
|
Slot = slot;
|
||||||
SetId = setId;
|
SetId = setId;
|
||||||
Entry = Eqp.Mask( slot ) & entry;
|
Entry = Eqp.Mask(slot) & entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EqpManipulation Copy( EqpEntry entry )
|
public EqpManipulation Copy(EqpEntry entry)
|
||||||
=> new(entry, Slot, SetId);
|
=> new(entry, Slot, SetId);
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> $"Eqp - {SetId} - {Slot}";
|
=> $"Eqp - {SetId} - {Slot}";
|
||||||
|
|
||||||
public bool Equals( EqpManipulation other )
|
public bool Equals(EqpManipulation other)
|
||||||
=> Slot == other.Slot
|
=> Slot == other.Slot
|
||||||
&& SetId == other.SetId;
|
&& SetId == other.SetId;
|
||||||
|
|
||||||
public override bool Equals( object? obj )
|
public override bool Equals(object? obj)
|
||||||
=> obj is EqpManipulation other && Equals( other );
|
=> obj is EqpManipulation other && Equals(other);
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> HashCode.Combine( ( int )Slot, SetId );
|
=> HashCode.Combine((int)Slot, SetId);
|
||||||
|
|
||||||
public int CompareTo( EqpManipulation other )
|
public int CompareTo(EqpManipulation other)
|
||||||
{
|
{
|
||||||
var set = SetId.Id.CompareTo( other.SetId.Id );
|
var set = SetId.Id.CompareTo(other.SetId.Id);
|
||||||
return set != 0 ? set : Slot.CompareTo( other.Slot );
|
return set != 0 ? set : Slot.CompareTo(other.Slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetaIndex FileIndex()
|
public MetaIndex FileIndex()
|
||||||
=> MetaIndex.Eqp;
|
=> MetaIndex.Eqp;
|
||||||
|
|
||||||
public bool Apply( ExpandedEqpFile file )
|
public bool Apply(ExpandedEqpFile file)
|
||||||
{
|
{
|
||||||
var entry = file[ SetId ];
|
var entry = file[SetId];
|
||||||
var mask = Eqp.Mask( Slot );
|
var mask = Eqp.Mask(Slot);
|
||||||
if( ( entry & mask ) == Entry )
|
if ((entry & mask) == Entry)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
file[ SetId ] = ( entry & ~mask ) | Entry;
|
file[SetId] = (entry & ~mask) | Entry;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Validate()
|
public bool Validate()
|
||||||
{
|
{
|
||||||
var mask = Eqp.Mask(Slot);
|
var mask = Eqp.Mask(Slot);
|
||||||
if (mask == 0)
|
if (mask == 0)
|
||||||
return false;
|
return false;
|
||||||
if ((Entry & mask) != Entry)
|
if ((Entry & mask) != Entry)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// No check for set id.
|
// No check for set id.
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ using Penumbra.Meta.Files;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
|
public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
|
||||||
{
|
{
|
||||||
public enum EstType : byte
|
public enum EstType : byte
|
||||||
{
|
{
|
||||||
|
|
@ -18,7 +18,7 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
|
||||||
Head = MetaIndex.HeadEst,
|
Head = MetaIndex.HeadEst,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToName( EstType type )
|
public static string ToName(EstType type)
|
||||||
=> type switch
|
=> type switch
|
||||||
{
|
{
|
||||||
EstType.Hair => "hair",
|
EstType.Hair => "hair",
|
||||||
|
|
@ -30,19 +30,19 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
|
||||||
|
|
||||||
public ushort Entry { get; private init; } // SkeletonIdx.
|
public ushort Entry { get; private init; } // SkeletonIdx.
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public Gender Gender { get; private init; }
|
public Gender Gender { get; private init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public ModelRace Race { get; private init; }
|
public ModelRace Race { get; private init; }
|
||||||
|
|
||||||
public SetId SetId { get; private init; }
|
public SetId SetId { get; private init; }
|
||||||
|
|
||||||
[JsonConverter( typeof( StringEnumConverter ) )]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EstType Slot { get; private init; }
|
public EstType Slot { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EstManipulation( Gender gender, ModelRace race, EstType slot, SetId setId, ushort entry )
|
public EstManipulation(Gender gender, ModelRace race, EstType slot, SetId setId, ushort entry)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
Gender = gender;
|
Gender = gender;
|
||||||
|
|
@ -51,49 +51,45 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
|
||||||
Slot = slot;
|
Slot = slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EstManipulation Copy( ushort entry )
|
public EstManipulation Copy(ushort entry)
|
||||||
=> new(Gender, Race, Slot, SetId, entry);
|
=> new(Gender, Race, Slot, SetId, entry);
|
||||||
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> $"Est - {SetId} - {Slot} - {Race.ToName()} {Gender.ToName()}";
|
=> $"Est - {SetId} - {Slot} - {Race.ToName()} {Gender.ToName()}";
|
||||||
|
|
||||||
public bool Equals( EstManipulation other )
|
public bool Equals(EstManipulation other)
|
||||||
=> Gender == other.Gender
|
=> Gender == other.Gender
|
||||||
&& Race == other.Race
|
&& Race == other.Race
|
||||||
&& SetId == other.SetId
|
&& SetId == other.SetId
|
||||||
&& Slot == other.Slot;
|
&& Slot == other.Slot;
|
||||||
|
|
||||||
public override bool Equals( object? obj )
|
public override bool Equals(object? obj)
|
||||||
=> obj is EstManipulation other && Equals( other );
|
=> obj is EstManipulation other && Equals(other);
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> HashCode.Combine( ( int )Gender, ( int )Race, SetId, ( int )Slot );
|
=> HashCode.Combine((int)Gender, (int)Race, SetId, (int)Slot);
|
||||||
|
|
||||||
public int CompareTo( EstManipulation other )
|
public int CompareTo(EstManipulation other)
|
||||||
{
|
{
|
||||||
var r = Race.CompareTo( other.Race );
|
var r = Race.CompareTo(other.Race);
|
||||||
if( r != 0 )
|
if (r != 0)
|
||||||
{
|
|
||||||
return r;
|
return r;
|
||||||
}
|
|
||||||
|
|
||||||
var g = Gender.CompareTo( other.Gender );
|
var g = Gender.CompareTo(other.Gender);
|
||||||
if( g != 0 )
|
if (g != 0)
|
||||||
{
|
|
||||||
return g;
|
return g;
|
||||||
}
|
|
||||||
|
|
||||||
var s = Slot.CompareTo( other.Slot );
|
var s = Slot.CompareTo(other.Slot);
|
||||||
return s != 0 ? s : SetId.Id.CompareTo( other.SetId.Id );
|
return s != 0 ? s : SetId.Id.CompareTo(other.SetId.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetaIndex FileIndex()
|
public MetaIndex FileIndex()
|
||||||
=> ( MetaIndex )Slot;
|
=> (MetaIndex)Slot;
|
||||||
|
|
||||||
public bool Apply( EstFile file )
|
public bool Apply(EstFile file)
|
||||||
{
|
{
|
||||||
return file.SetEntry( Names.CombinedRace( Gender, Race ), SetId.Id, Entry ) switch
|
return file.SetEntry(Names.CombinedRace(Gender, Race), SetId.Id, Entry) switch
|
||||||
{
|
{
|
||||||
EstFile.EstEntryChange.Unchanged => false,
|
EstFile.EstEntryChange.Unchanged => false,
|
||||||
EstFile.EstEntryChange.Changed => true,
|
EstFile.EstEntryChange.Changed => true,
|
||||||
|
|
@ -109,7 +105,8 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
|
||||||
return false;
|
return false;
|
||||||
if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown)
|
if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown)
|
||||||
return false;
|
return false;
|
||||||
// No known check for set id or entry.
|
|
||||||
|
// No known check for set id or entry.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,55 +5,51 @@ using Penumbra.Meta.Files;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation >
|
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
|
||||||
{
|
{
|
||||||
public GmpEntry Entry { get; private init; }
|
public GmpEntry Entry { get; private init; }
|
||||||
public SetId SetId { get; private init; }
|
public SetId SetId { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public GmpManipulation( GmpEntry entry, SetId setId )
|
public GmpManipulation(GmpEntry entry, SetId setId)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
SetId = setId;
|
SetId = setId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GmpManipulation Copy( GmpEntry entry )
|
public GmpManipulation Copy(GmpEntry entry)
|
||||||
=> new(entry, SetId);
|
=> new(entry, SetId);
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> $"Gmp - {SetId}";
|
=> $"Gmp - {SetId}";
|
||||||
|
|
||||||
public bool Equals( GmpManipulation other )
|
public bool Equals(GmpManipulation other)
|
||||||
=> SetId == other.SetId;
|
=> SetId == other.SetId;
|
||||||
|
|
||||||
public override bool Equals( object? obj )
|
public override bool Equals(object? obj)
|
||||||
=> obj is GmpManipulation other && Equals( other );
|
=> obj is GmpManipulation other && Equals(other);
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> SetId.GetHashCode();
|
=> SetId.GetHashCode();
|
||||||
|
|
||||||
public int CompareTo( GmpManipulation other )
|
public int CompareTo(GmpManipulation other)
|
||||||
=> SetId.Id.CompareTo( other.SetId.Id );
|
=> SetId.Id.CompareTo(other.SetId.Id);
|
||||||
|
|
||||||
public MetaIndex FileIndex()
|
public MetaIndex FileIndex()
|
||||||
=> MetaIndex.Gmp;
|
=> MetaIndex.Gmp;
|
||||||
|
|
||||||
public bool Apply( ExpandedGmpFile file )
|
public bool Apply(ExpandedGmpFile file)
|
||||||
{
|
{
|
||||||
var entry = file[ SetId ];
|
var entry = file[SetId];
|
||||||
if( entry == Entry )
|
if (entry == Entry)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
file[ SetId ] = Entry;
|
file[SetId] = Entry;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Validate()
|
public bool Validate()
|
||||||
{
|
|
||||||
// No known conditions.
|
// No known conditions.
|
||||||
return true;
|
=> true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using Penumbra.Interop.Services;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Files;
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ public class DuplicateManager
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_cancellationTokenSource.Cancel();
|
_cancellationTokenSource.Cancel();
|
||||||
Worker = Task.CompletedTask;
|
Worker = Task.CompletedTask;
|
||||||
_duplicates.Clear();
|
_duplicates.Clear();
|
||||||
SavedSpace = 0;
|
SavedSpace = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,14 @@ public interface IMod
|
||||||
{
|
{
|
||||||
LowerString Name { get; }
|
LowerString Name { get; }
|
||||||
|
|
||||||
public int Index { get; }
|
public int Index { get; }
|
||||||
public int Priority { get; }
|
public int Priority { get; }
|
||||||
|
|
||||||
public ISubMod Default { get; }
|
public ISubMod Default { get; }
|
||||||
public IReadOnlyList< IModGroup > Groups { get; }
|
public IReadOnlyList<IModGroup> Groups { get; }
|
||||||
|
|
||||||
public IEnumerable< SubMod > AllSubMods { get; }
|
public IEnumerable<SubMod> AllSubMods { get; }
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
public int TotalManipulations { get; }
|
public int TotalManipulations { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using OtterGui.Tasks;
|
using OtterGui.Tasks;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
|
||||||
namespace Penumbra.Mods.Editor;
|
namespace Penumbra.Mods.Editor;
|
||||||
|
|
||||||
/// <summary> Utility to create and apply a zipped backup of a mod. </summary>
|
/// <summary> Utility to create and apply a zipped backup of a mod. </summary>
|
||||||
public class ModBackup
|
public class ModBackup
|
||||||
{
|
{
|
||||||
/// <summary> Set when reading Config and migrating from v4 to v5. </summary>
|
/// <summary> Set when reading Config and migrating from v4 to v5. </summary>
|
||||||
public static bool MigrateModBackups = false;
|
public static bool MigrateModBackups = false;
|
||||||
|
|
||||||
public static bool CreatingBackup { get; private set; }
|
public static bool CreatingBackup { get; private set; }
|
||||||
|
|
||||||
private readonly Mod _mod;
|
private readonly Mod _mod;
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
public readonly bool Exists;
|
public readonly bool Exists;
|
||||||
|
|
||||||
public ModBackup(ModExportManager modExportManager, Mod mod)
|
public ModBackup(ModExportManager modExportManager, Mod mod)
|
||||||
{
|
{
|
||||||
_mod = mod;
|
_mod = mod;
|
||||||
Name = Path.Combine(modExportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp";
|
Name = Path.Combine(modExportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp";
|
||||||
Exists = File.Exists(Name);
|
Exists = File.Exists(Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Migrate file extensions. </summary>
|
/// <summary> Migrate file extensions. </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods.Editor;
|
namespace Penumbra.Mods.Editor;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Penumbra.Mods.Editor;
|
using Penumbra.Mods.Editor;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
@ -7,7 +8,7 @@ namespace Penumbra.Mods;
|
||||||
public class ModFileEditor
|
public class ModFileEditor
|
||||||
{
|
{
|
||||||
private readonly ModFileCollection _files;
|
private readonly ModFileCollection _files;
|
||||||
private readonly ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
|
|
||||||
public bool Changes { get; private set; }
|
public bool Changes { get; private set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Penumbra.Api.Enums;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.ModsTab;
|
using Penumbra.UI.ModsTab;
|
||||||
|
|
@ -174,7 +175,7 @@ public class ModMerger : IDisposable
|
||||||
ret = new FullPath(MergeToMod!.ModPath, relPath);
|
ret = new FullPath(MergeToMod!.ModPath, relPath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var originalOption in mergeOptions)
|
foreach (var originalOption in mergeOptions)
|
||||||
{
|
{
|
||||||
foreach (var manip in originalOption.Manipulations)
|
foreach (var manip in originalOption.Manipulations)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -146,6 +147,7 @@ public class ModMetaEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Split(currentOption.Manipulations);
|
Split(currentOption.Manipulations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using Dalamud.Interface.Internal.Notifications;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Tasks;
|
using OtterGui.Tasks;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
public class ModSwapEditor
|
public class ModSwapEditor
|
||||||
{
|
{
|
||||||
private readonly ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
private readonly Dictionary<Utf8GamePath, FullPath> _swaps = new();
|
private readonly Dictionary<Utf8GamePath, FullPath> _swaps = new();
|
||||||
|
|
||||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps
|
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,29 @@ namespace Penumbra.Mods.ItemSwap;
|
||||||
public static class CustomizationSwap
|
public static class CustomizationSwap
|
||||||
{
|
{
|
||||||
/// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode.
|
/// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode.
|
||||||
public static FileSwap CreateMdl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo )
|
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
||||||
|
SetId idFrom, SetId idTo)
|
||||||
{
|
{
|
||||||
if( idFrom.Id > byte.MaxValue )
|
if (idFrom.Id > byte.MaxValue)
|
||||||
{
|
throw new Exception($"The Customization ID {idFrom} is too large for {slot}.");
|
||||||
throw new Exception( $"The Customization ID {idFrom} is too large for {slot}." );
|
|
||||||
}
|
|
||||||
|
|
||||||
var mdlPathFrom = GamePaths.Character.Mdl.Path( race, slot, idFrom, slot.ToCustomizationType() );
|
var mdlPathFrom = GamePaths.Character.Mdl.Path(race, slot, idFrom, slot.ToCustomizationType());
|
||||||
var mdlPathTo = GamePaths.Character.Mdl.Path( race, slot, idTo, slot.ToCustomizationType() );
|
var mdlPathTo = GamePaths.Character.Mdl.Path(race, slot, idTo, slot.ToCustomizationType());
|
||||||
|
|
||||||
var mdl = FileSwap.CreateSwap( manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo );
|
var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo);
|
||||||
var range = slot == BodySlot.Tail && race is GenderRace.HrothgarMale or GenderRace.HrothgarFemale or GenderRace.HrothgarMaleNpc or GenderRace.HrothgarMaleNpc ? 5 : 1;
|
var range = slot == BodySlot.Tail
|
||||||
|
&& race is GenderRace.HrothgarMale or GenderRace.HrothgarFemale or GenderRace.HrothgarMaleNpc or GenderRace.HrothgarMaleNpc
|
||||||
|
? 5
|
||||||
|
: 1;
|
||||||
|
|
||||||
foreach( ref var materialFileName in mdl.AsMdl()!.Materials.AsSpan() )
|
foreach (ref var materialFileName in mdl.AsMdl()!.Materials.AsSpan())
|
||||||
{
|
{
|
||||||
var name = materialFileName;
|
var name = materialFileName;
|
||||||
foreach( var variant in Enumerable.Range( 1, range ) )
|
foreach (var variant in Enumerable.Range(1, range))
|
||||||
{
|
{
|
||||||
name = materialFileName;
|
name = materialFileName;
|
||||||
var mtrl = CreateMtrl( manager, redirections, slot, race, idFrom, idTo, ( byte )variant, ref name, ref mdl.DataWasChanged );
|
var mtrl = CreateMtrl(manager, redirections, slot, race, idFrom, idTo, (byte)variant, ref name, ref mdl.DataWasChanged);
|
||||||
mdl.ChildSwaps.Add( mtrl );
|
mdl.ChildSwaps.Add(mtrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
materialFileName = name;
|
materialFileName = name;
|
||||||
|
|
@ -39,71 +41,75 @@ public static class CustomizationSwap
|
||||||
return mdl;
|
return mdl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateMtrl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo, byte variant,
|
public static FileSwap CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
||||||
ref string fileName, ref bool dataWasChanged )
|
SetId idFrom, SetId idTo, byte variant,
|
||||||
|
ref string fileName, ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
variant = slot is BodySlot.Face or BodySlot.Zear ? byte.MaxValue : variant;
|
variant = slot is BodySlot.Face or BodySlot.Zear ? byte.MaxValue : variant;
|
||||||
var mtrlFromPath = GamePaths.Character.Mtrl.Path( race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant );
|
var mtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant);
|
||||||
var mtrlToPath = GamePaths.Character.Mtrl.Path( race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant );
|
var mtrlToPath = GamePaths.Character.Mtrl.Path(race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant);
|
||||||
|
|
||||||
var newFileName = fileName;
|
var newFileName = fileName;
|
||||||
newFileName = ItemSwap.ReplaceRace( newFileName, gameRaceTo, race, gameRaceTo != race );
|
newFileName = ItemSwap.ReplaceRace(newFileName, gameRaceTo, race, gameRaceTo != race);
|
||||||
newFileName = ItemSwap.ReplaceBody( newFileName, slot, idTo, idFrom, idFrom != idTo );
|
newFileName = ItemSwap.ReplaceBody(newFileName, slot, idTo, idFrom, idFrom != idTo);
|
||||||
newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_c{race.ToRaceCode()}", gameRaceFrom != race || MaterialHandling.IsSpecialCase( race, idFrom ) );
|
newFileName = ItemSwap.AddSuffix(newFileName, ".mtrl", $"_c{race.ToRaceCode()}",
|
||||||
newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_{slot.ToAbbreviation()}{idFrom.Id:D4}", gameSetIdFrom != idFrom );
|
gameRaceFrom != race || MaterialHandling.IsSpecialCase(race, idFrom));
|
||||||
|
newFileName = ItemSwap.AddSuffix(newFileName, ".mtrl", $"_{slot.ToAbbreviation()}{idFrom.Id:D4}", gameSetIdFrom != idFrom);
|
||||||
|
|
||||||
var actualMtrlFromPath = mtrlFromPath;
|
var actualMtrlFromPath = mtrlFromPath;
|
||||||
if( newFileName != fileName )
|
if (newFileName != fileName)
|
||||||
{
|
{
|
||||||
actualMtrlFromPath = GamePaths.Character.Mtrl.Path( race, slot, idFrom, newFileName, out _, out _, variant );
|
actualMtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, newFileName, out _, out _, variant);
|
||||||
fileName = newFileName;
|
fileName = newFileName;
|
||||||
dataWasChanged = true;
|
dataWasChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mtrl = FileSwap.CreateSwap( manager, ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath );
|
var mtrl = FileSwap.CreateSwap(manager, ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath);
|
||||||
var shpk = CreateShader( manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged );
|
var shpk = CreateShader(manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged);
|
||||||
mtrl.ChildSwaps.Add( shpk );
|
mtrl.ChildSwaps.Add(shpk);
|
||||||
|
|
||||||
foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() )
|
foreach (ref var texture in mtrl.AsMtrl()!.Textures.AsSpan())
|
||||||
{
|
{
|
||||||
var tex = CreateTex( manager, redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged );
|
var tex = CreateTex(manager, redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged);
|
||||||
mtrl.ChildSwaps.Add( tex );
|
mtrl.ChildSwaps.Add(tex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mtrl;
|
return mtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateTex( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, ref MtrlFile.Texture texture,
|
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
||||||
ref bool dataWasChanged )
|
SetId idFrom, ref MtrlFile.Texture texture,
|
||||||
|
ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var path = texture.Path;
|
var path = texture.Path;
|
||||||
var addedDashes = false;
|
var addedDashes = false;
|
||||||
if( texture.DX11 )
|
if (texture.DX11)
|
||||||
{
|
{
|
||||||
var fileName = Path.GetFileName( path );
|
var fileName = Path.GetFileName(path);
|
||||||
if( !fileName.StartsWith( "--" ) )
|
if (!fileName.StartsWith("--"))
|
||||||
{
|
{
|
||||||
path = path.Replace( fileName, $"--{fileName}" );
|
path = path.Replace(fileName, $"--{fileName}");
|
||||||
addedDashes = true;
|
addedDashes = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newPath = ItemSwap.ReplaceAnyRace( path, race );
|
var newPath = ItemSwap.ReplaceAnyRace(path, race);
|
||||||
newPath = ItemSwap.ReplaceAnyBody( newPath, slot, idFrom );
|
newPath = ItemSwap.ReplaceAnyBody(newPath, slot, idFrom);
|
||||||
newPath = ItemSwap.AddSuffix( newPath, ".tex", $"_{Path.GetFileName( texture.Path ).GetStableHashCode():x8}", true );
|
newPath = ItemSwap.AddSuffix(newPath, ".tex", $"_{Path.GetFileName(texture.Path).GetStableHashCode():x8}", true);
|
||||||
if( newPath != path )
|
if (newPath != path)
|
||||||
{
|
{
|
||||||
texture.Path = addedDashes ? newPath.Replace( "--", string.Empty ) : newPath;
|
texture.Path = addedDashes ? newPath.Replace("--", string.Empty) : newPath;
|
||||||
dataWasChanged = true;
|
dataWasChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FileSwap.CreateSwap( manager, ResourceType.Tex, redirections, newPath, path, path );
|
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static FileSwap CreateShader( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged )
|
public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName,
|
||||||
|
ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var path = $"shader/sm5/shpk/{shaderName}";
|
var path = $"shader/sm5/shpk/{shaderName}";
|
||||||
return FileSwap.CreateSwap( manager, ResourceType.Shpk, redirections, path, path );
|
return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -249,10 +249,10 @@ public static class EquipmentSwap
|
||||||
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom,
|
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom,
|
||||||
SetId idFrom, SetId idTo, Variant variantFrom)
|
SetId idFrom, SetId idTo, Variant variantFrom)
|
||||||
{
|
{
|
||||||
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
|
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
|
||||||
var imc = new ImcFile(manager, entry);
|
var imc = new ImcFile(manager, entry);
|
||||||
EquipItem[] items;
|
EquipItem[] items;
|
||||||
Variant[] variants;
|
Variant[] variants;
|
||||||
if (idFrom == idTo)
|
if (idFrom == idTo)
|
||||||
{
|
{
|
||||||
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
|
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
|
||||||
|
|
@ -264,8 +264,9 @@ public static class EquipmentSwap
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
items = identifier.Identify(slotFrom.IsEquipment()
|
items = identifier.Identify(slotFrom.IsEquipment()
|
||||||
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
|
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
|
||||||
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>().ToArray();
|
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>()
|
||||||
|
.ToArray();
|
||||||
variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray();
|
variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,11 +284,13 @@ public static class EquipmentSwap
|
||||||
return new MetaSwap(manips, manipFrom, manipTo);
|
return new MetaSwap(manips, manipFrom, manipTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
|
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
|
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
|
||||||
SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
||||||
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
|
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
|
||||||
|
|
||||||
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips,
|
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
|
Func<MetaManipulation, MetaManipulation> manips,
|
||||||
EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo,
|
EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo,
|
||||||
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
||||||
{
|
{
|
||||||
|
|
@ -401,7 +404,8 @@ public static class EquipmentSwap
|
||||||
ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||||
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);
|
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);
|
||||||
|
|
||||||
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom,
|
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom,
|
||||||
|
EquipSlot slotTo, SetId idFrom,
|
||||||
SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var path = texture.Path;
|
var path = texture.Path;
|
||||||
|
|
@ -428,13 +432,15 @@ public static class EquipmentSwap
|
||||||
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path);
|
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName, ref bool dataWasChanged)
|
public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName,
|
||||||
|
ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var path = $"shader/sm5/shpk/{shaderName}";
|
var path = $"shader/sm5/shpk/{shaderName}";
|
||||||
return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path);
|
return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath, ref bool dataWasChanged)
|
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath,
|
||||||
|
ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var oldPath = filePath;
|
var oldPath = filePath;
|
||||||
filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}");
|
filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}");
|
||||||
|
|
|
||||||
|
|
@ -20,47 +20,45 @@ public static class ItemSwap
|
||||||
{
|
{
|
||||||
public readonly ResourceType Type;
|
public readonly ResourceType Type;
|
||||||
|
|
||||||
public MissingFileException( ResourceType type, object path )
|
public MissingFileException(ResourceType type, object path)
|
||||||
: base($"Could not load {type} File Data for \"{path}\".")
|
: base($"Could not load {type} File Data for \"{path}\".")
|
||||||
=> Type = type;
|
=> Type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool LoadFile( MetaFileManager manager, FullPath path, out byte[] data )
|
private static bool LoadFile(MetaFileManager manager, FullPath path, out byte[] data)
|
||||||
{
|
{
|
||||||
if( path.FullName.Length > 0 )
|
if (path.FullName.Length > 0)
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if( path.IsRooted )
|
if (path.IsRooted)
|
||||||
{
|
{
|
||||||
data = File.ReadAllBytes( path.FullName );
|
data = File.ReadAllBytes(path.FullName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = manager.GameData.GetFile( path.InternalName.ToString() );
|
var file = manager.GameData.GetFile(path.InternalName.ToString());
|
||||||
if( file != null )
|
if (file != null)
|
||||||
{
|
{
|
||||||
data = file.Data;
|
data = file.Data;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Debug( $"Could not load file {path}:\n{e}" );
|
Penumbra.Log.Debug($"Could not load file {path}:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data = Array.Empty< byte >();
|
data = Array.Empty<byte>();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GenericFile : IWritable
|
public class GenericFile : IWritable
|
||||||
{
|
{
|
||||||
public readonly byte[] Data;
|
public readonly byte[] Data;
|
||||||
public bool Valid { get; }
|
public bool Valid { get; }
|
||||||
|
|
||||||
public GenericFile( MetaFileManager manager, FullPath path )
|
public GenericFile(MetaFileManager manager, FullPath path)
|
||||||
=> Valid = LoadFile( manager, path, out Data );
|
=> Valid = LoadFile(manager, path, out Data);
|
||||||
|
|
||||||
public byte[] Write()
|
public byte[] Write()
|
||||||
=> Data;
|
=> Data;
|
||||||
|
|
@ -68,69 +66,67 @@ public static class ItemSwap
|
||||||
public static readonly GenericFile Invalid = new(null!, FullPath.Empty);
|
public static readonly GenericFile Invalid = new(null!, FullPath.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool LoadFile( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out GenericFile? file )
|
public static bool LoadFile(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out GenericFile? file)
|
||||||
{
|
{
|
||||||
file = new GenericFile( manager, path );
|
file = new GenericFile(manager, path);
|
||||||
if( file.Valid )
|
if (file.Valid)
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
file = null;
|
file = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool LoadMdl( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MdlFile? file )
|
public static bool LoadMdl(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out MdlFile? file)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if( LoadFile( manager, path, out byte[] data ) )
|
if (LoadFile(manager, path, out byte[] data))
|
||||||
{
|
{
|
||||||
file = new MdlFile( data );
|
file = new MdlFile(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Debug( $"Could not parse file {path} to Mdl:\n{e}" );
|
Penumbra.Log.Debug($"Could not parse file {path} to Mdl:\n{e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
file = null;
|
file = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool LoadMtrl(MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MtrlFile? file )
|
public static bool LoadMtrl(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out MtrlFile? file)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if( LoadFile( manager, path, out byte[] data ) )
|
if (LoadFile(manager, path, out byte[] data))
|
||||||
{
|
{
|
||||||
file = new MtrlFile( data );
|
file = new MtrlFile(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Debug( $"Could not parse file {path} to Mtrl:\n{e}" );
|
Penumbra.Log.Debug($"Could not parse file {path} to Mtrl:\n{e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
file = null;
|
file = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool LoadAvfx( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out AvfxFile? file )
|
public static bool LoadAvfx(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out AvfxFile? file)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if( LoadFile( manager, path, out byte[] data ) )
|
if (LoadFile(manager, path, out byte[] data))
|
||||||
{
|
{
|
||||||
file = new AvfxFile( data );
|
file = new AvfxFile(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Debug( $"Could not parse file {path} to Avfx:\n{e}" );
|
Penumbra.Log.Debug($"Could not parse file {path} to Avfx:\n{e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
file = null;
|
file = null;
|
||||||
|
|
@ -138,40 +134,41 @@ public static class ItemSwap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static FileSwap CreatePhyb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry )
|
public static FileSwap CreatePhyb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
|
||||||
|
GenderRace race, ushort estEntry)
|
||||||
{
|
{
|
||||||
var phybPath = GamePaths.Skeleton.Phyb.Path( race, EstManipulation.ToName( type ), estEntry );
|
var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry);
|
||||||
return FileSwap.CreateSwap( manager, ResourceType.Phyb, redirections, phybPath, phybPath );
|
return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateSklb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry )
|
public static FileSwap CreateSklb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
|
||||||
|
GenderRace race, ushort estEntry)
|
||||||
{
|
{
|
||||||
var sklbPath = GamePaths.Skeleton.Sklb.Path( race, EstManipulation.ToName( type ), estEntry );
|
var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry);
|
||||||
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath );
|
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
|
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
|
||||||
public static MetaSwap? CreateEst( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EstManipulation.EstType type,
|
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl )
|
Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
|
||||||
|
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl)
|
||||||
{
|
{
|
||||||
if( type == 0 )
|
if (type == 0)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var (gender, race) = genderRace.Split();
|
var (gender, race) = genderRace.Split();
|
||||||
var fromDefault = new EstManipulation( gender, race, type, idFrom, EstFile.GetDefault( manager, type, genderRace, idFrom ) );
|
var fromDefault = new EstManipulation(gender, race, type, idFrom, EstFile.GetDefault(manager, type, genderRace, idFrom));
|
||||||
var toDefault = new EstManipulation( gender, race, type, idTo, EstFile.GetDefault( manager, type, genderRace, idTo ) );
|
var toDefault = new EstManipulation(gender, race, type, idTo, EstFile.GetDefault(manager, type, genderRace, idTo));
|
||||||
var est = new MetaSwap( manips, fromDefault, toDefault );
|
var est = new MetaSwap(manips, fromDefault, toDefault);
|
||||||
|
|
||||||
if( ownMdl && est.SwapApplied.Est.Entry >= 2 )
|
if (ownMdl && est.SwapApplied.Est.Entry >= 2)
|
||||||
{
|
{
|
||||||
var phyb = CreatePhyb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry );
|
var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
|
||||||
est.ChildSwaps.Add( phyb );
|
est.ChildSwaps.Add(phyb);
|
||||||
var sklb = CreateSklb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry );
|
var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
|
||||||
est.ChildSwaps.Add( sklb );
|
est.ChildSwaps.Add(sklb);
|
||||||
}
|
}
|
||||||
else if( est.SwapAppliedIsDefault )
|
else if (est.SwapAppliedIsDefault)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -179,57 +176,55 @@ public static class ItemSwap
|
||||||
return est;
|
return est;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetStableHashCode( this string str )
|
public static int GetStableHashCode(this string str)
|
||||||
{
|
{
|
||||||
unchecked
|
unchecked
|
||||||
{
|
{
|
||||||
var hash1 = 5381;
|
var hash1 = 5381;
|
||||||
var hash2 = hash1;
|
var hash2 = hash1;
|
||||||
|
|
||||||
for( var i = 0; i < str.Length && str[ i ] != '\0'; i += 2 )
|
for (var i = 0; i < str.Length && str[i] != '\0'; i += 2)
|
||||||
{
|
{
|
||||||
hash1 = ( ( hash1 << 5 ) + hash1 ) ^ str[ i ];
|
hash1 = ((hash1 << 5) + hash1) ^ str[i];
|
||||||
if( i == str.Length - 1 || str[ i + 1 ] == '\0' )
|
if (i == str.Length - 1 || str[i + 1] == '\0')
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
hash2 = ( ( hash2 << 5 ) + hash2 ) ^ str[ i + 1 ];
|
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash1 + hash2 * 1566083941;
|
return hash1 + hash2 * 1566083941;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ReplaceAnyId( string path, char idType, SetId id, bool condition = true )
|
public static string ReplaceAnyId(string path, char idType, SetId id, bool condition = true)
|
||||||
=> condition
|
=> condition
|
||||||
? Regex.Replace( path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}" )
|
? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
|
||||||
: path;
|
: path;
|
||||||
|
|
||||||
public static string ReplaceAnyRace( string path, GenderRace to, bool condition = true )
|
public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
|
||||||
=> ReplaceAnyId( path, 'c', ( ushort )to, condition );
|
=> ReplaceAnyId(path, 'c', (ushort)to, condition);
|
||||||
|
|
||||||
public static string ReplaceAnyBody( string path, BodySlot slot, SetId to, bool condition = true )
|
public static string ReplaceAnyBody(string path, BodySlot slot, SetId to, bool condition = true)
|
||||||
=> ReplaceAnyId( path, slot.ToAbbreviation(), to, condition );
|
=> ReplaceAnyId(path, slot.ToAbbreviation(), to, condition);
|
||||||
|
|
||||||
public static string ReplaceId( string path, char type, SetId idFrom, SetId idTo, bool condition = true )
|
public static string ReplaceId(string path, char type, SetId idFrom, SetId idTo, bool condition = true)
|
||||||
=> condition
|
=> condition
|
||||||
? path.Replace( $"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}" )
|
? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
|
||||||
: path;
|
: path;
|
||||||
|
|
||||||
public static string ReplaceSlot( string path, EquipSlot from, EquipSlot to, bool condition = true )
|
public static string ReplaceSlot(string path, EquipSlot from, EquipSlot to, bool condition = true)
|
||||||
=> condition
|
=> condition
|
||||||
? path.Replace( $"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_" )
|
? path.Replace($"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_")
|
||||||
: path;
|
: path;
|
||||||
|
|
||||||
public static string ReplaceRace( string path, GenderRace from, GenderRace to, bool condition = true )
|
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
|
||||||
=> ReplaceId( path, 'c', ( ushort )from, ( ushort )to, condition );
|
=> ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
|
||||||
|
|
||||||
public static string ReplaceBody( string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true )
|
public static string ReplaceBody(string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true)
|
||||||
=> ReplaceId( path, slot.ToAbbreviation(), idFrom, idTo, condition );
|
=> ReplaceId(path, slot.ToAbbreviation(), idFrom, idTo, condition);
|
||||||
|
|
||||||
public static string AddSuffix( string path, string ext, string suffix, bool condition = true )
|
public static string AddSuffix(string path, string ext, string suffix, bool condition = true)
|
||||||
=> condition
|
=> condition
|
||||||
? path.Replace( ext, suffix + ext )
|
? path.Replace(ext, suffix + ext)
|
||||||
: path;
|
: path;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Mods.ItemSwap;
|
namespace Penumbra.Mods.ItemSwap;
|
||||||
|
|
@ -13,18 +14,18 @@ namespace Penumbra.Mods.ItemSwap;
|
||||||
public class ItemSwapContainer
|
public class ItemSwapContainer
|
||||||
{
|
{
|
||||||
private readonly MetaFileManager _manager;
|
private readonly MetaFileManager _manager;
|
||||||
private readonly IdentifierService _identifier;
|
private readonly IdentifierService _identifier;
|
||||||
|
|
||||||
private Dictionary< Utf8GamePath, FullPath > _modRedirections = new();
|
private Dictionary<Utf8GamePath, FullPath> _modRedirections = new();
|
||||||
private HashSet< MetaManipulation > _modManipulations = new();
|
private HashSet<MetaManipulation> _modManipulations = new();
|
||||||
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > ModRedirections
|
public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections
|
||||||
=> _modRedirections;
|
=> _modRedirections;
|
||||||
|
|
||||||
public IReadOnlySet< MetaManipulation > ModManipulations
|
public IReadOnlySet<MetaManipulation> ModManipulations
|
||||||
=> _modManipulations;
|
=> _modManipulations;
|
||||||
|
|
||||||
public readonly List< Swap > Swaps = new();
|
public readonly List<Swap> Swaps = new();
|
||||||
|
|
||||||
public bool Loaded { get; private set; }
|
public bool Loaded { get; private set; }
|
||||||
|
|
||||||
|
|
@ -40,72 +41,69 @@ public class ItemSwapContainer
|
||||||
NoSwaps,
|
NoSwaps,
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WriteMod( ModManager manager, Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null, int groupIndex = -1, int optionIndex = 0 )
|
public bool WriteMod(ModManager manager, Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null,
|
||||||
|
int groupIndex = -1, int optionIndex = 0)
|
||||||
{
|
{
|
||||||
var convertedManips = new HashSet< MetaManipulation >( Swaps.Count );
|
var convertedManips = new HashSet<MetaManipulation>(Swaps.Count);
|
||||||
var convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
var convertedFiles = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
|
||||||
var convertedSwaps = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
var convertedSwaps = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
|
||||||
directory ??= mod.ModPath;
|
directory ??= mod.ModPath;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach( var swap in Swaps.SelectMany( s => s.WithChildren() ) )
|
foreach (var swap in Swaps.SelectMany(s => s.WithChildren()))
|
||||||
{
|
{
|
||||||
switch( swap )
|
switch (swap)
|
||||||
{
|
{
|
||||||
case FileSwap file:
|
case FileSwap file:
|
||||||
// Skip, nothing to do
|
// Skip, nothing to do
|
||||||
if( file.SwapToModdedEqualsOriginal )
|
if (file.SwapToModdedEqualsOriginal)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if( writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged )
|
if (writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged)
|
||||||
{
|
{
|
||||||
convertedSwaps.TryAdd( file.SwapFromRequestPath, file.SwapToModded );
|
convertedSwaps.TryAdd(file.SwapFromRequestPath, file.SwapToModded);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var path = file.GetNewPath( directory.FullName );
|
var path = file.GetNewPath(directory.FullName);
|
||||||
var bytes = file.FileData.Write();
|
var bytes = file.FileData.Write();
|
||||||
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||||
_manager.Compactor.WriteAllBytes( path, bytes );
|
_manager.Compactor.WriteAllBytes(path, bytes);
|
||||||
convertedFiles.TryAdd( file.SwapFromRequestPath, new FullPath( path ) );
|
convertedFiles.TryAdd(file.SwapFromRequestPath, new FullPath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case MetaSwap meta:
|
case MetaSwap meta:
|
||||||
if( !meta.SwapAppliedIsDefault )
|
if (!meta.SwapAppliedIsDefault)
|
||||||
{
|
convertedManips.Add(meta.SwapApplied);
|
||||||
convertedManips.Add( meta.SwapApplied );
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.OptionEditor.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
|
manager.OptionEditor.OptionSetFiles(mod, groupIndex, optionIndex, convertedFiles);
|
||||||
manager.OptionEditor.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
|
manager.OptionEditor.OptionSetFileSwaps(mod, groupIndex, optionIndex, convertedSwaps);
|
||||||
manager.OptionEditor.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
|
manager.OptionEditor.OptionSetManipulations(mod, groupIndex, optionIndex, convertedManips);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error( $"Could not write FileSwapContainer to {mod.ModPath}:\n{e}" );
|
Penumbra.Log.Error($"Could not write FileSwapContainer to {mod.ModPath}:\n{e}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadMod( Mod? mod, ModSettings? settings )
|
public void LoadMod(Mod? mod, ModSettings? settings)
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
if( mod == null )
|
if (mod == null)
|
||||||
{
|
{
|
||||||
_modRedirections = new Dictionary< Utf8GamePath, FullPath >();
|
_modRedirections = new Dictionary<Utf8GamePath, FullPath>();
|
||||||
_modManipulations = new HashSet< MetaManipulation >();
|
_modManipulations = new HashSet<MetaManipulation>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
( _modRedirections, _modManipulations ) = ModSettings.GetResolveData( mod, settings );
|
(_modRedirections, _modManipulations) = ModSettings.GetResolveData(mod, settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,59 +111,61 @@ public class ItemSwapContainer
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_identifier = identifier;
|
_identifier = identifier;
|
||||||
LoadMod( null, null );
|
LoadMod(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func< Utf8GamePath, FullPath > PathResolver( ModCollection? collection )
|
private Func<Utf8GamePath, FullPath> PathResolver(ModCollection? collection)
|
||||||
=> collection != null
|
=> collection != null
|
||||||
? p => collection.ResolvePath( p ) ?? new FullPath( p )
|
? p => collection.ResolvePath(p) ?? new FullPath(p)
|
||||||
: p => ModRedirections.TryGetValue( p, out var path ) ? path : new FullPath( p );
|
: p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p);
|
||||||
|
|
||||||
private Func< MetaManipulation, MetaManipulation > MetaResolver( ModCollection? collection )
|
private Func<MetaManipulation, MetaManipulation> MetaResolver(ModCollection? collection)
|
||||||
{
|
{
|
||||||
var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _modManipulations;
|
var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _modManipulations;
|
||||||
return m => set.TryGetValue( m, out var a ) ? a : m;
|
return m => set.TryGetValue(m, out var a) ? a : m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EquipItem[] LoadEquipment( EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, bool useLeftRing = true )
|
public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true,
|
||||||
|
bool useLeftRing = true)
|
||||||
{
|
{
|
||||||
Swaps.Clear();
|
Swaps.Clear();
|
||||||
Loaded = false;
|
Loaded = false;
|
||||||
var ret = EquipmentSwap.CreateItemSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), from, to, useRightRing, useLeftRing );
|
var ret = EquipmentSwap.CreateItemSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
|
||||||
|
from, to, useRightRing, useLeftRing);
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EquipItem[] LoadTypeSwap( EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null )
|
public EquipItem[] LoadTypeSwap(EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null)
|
||||||
{
|
{
|
||||||
Swaps.Clear();
|
Swaps.Clear();
|
||||||
Loaded = false;
|
Loaded = false;
|
||||||
var ret = EquipmentSwap.CreateTypeSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to );
|
var ret = EquipmentSwap.CreateTypeSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
|
||||||
|
slotFrom, from, slotTo, to);
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadCustomization( MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to, ModCollection? collection = null )
|
public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to,
|
||||||
|
ModCollection? collection = null)
|
||||||
{
|
{
|
||||||
var pathResolver = PathResolver( collection );
|
var pathResolver = PathResolver(collection);
|
||||||
var mdl = CustomizationSwap.CreateMdl( manager, pathResolver, slot, race, from, to );
|
var mdl = CustomizationSwap.CreateMdl(manager, pathResolver, slot, race, from, to);
|
||||||
var type = slot switch
|
var type = slot switch
|
||||||
{
|
{
|
||||||
BodySlot.Hair => EstManipulation.EstType.Hair,
|
BodySlot.Hair => EstManipulation.EstType.Hair,
|
||||||
BodySlot.Face => EstManipulation.EstType.Face,
|
BodySlot.Face => EstManipulation.EstType.Face,
|
||||||
_ => ( EstManipulation.EstType )0,
|
_ => (EstManipulation.EstType)0,
|
||||||
};
|
};
|
||||||
|
|
||||||
var metaResolver = MetaResolver( collection );
|
var metaResolver = MetaResolver(collection);
|
||||||
var est = ItemSwap.CreateEst( manager, pathResolver, metaResolver, type, race, from, to, true );
|
var est = ItemSwap.CreateEst(manager, pathResolver, metaResolver, type, race, from, to, true);
|
||||||
|
|
||||||
Swaps.Add( mdl );
|
Swaps.Add(mdl);
|
||||||
if( est != null )
|
if (est != null)
|
||||||
{
|
Swaps.Add(est);
|
||||||
Swaps.Add( est );
|
|
||||||
}
|
|
||||||
|
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,4 +89,4 @@ public class ModExportManager : IDisposable
|
||||||
new ModBackup(this, mod).Move(null, newDirectory.Name);
|
new ModBackup(this, mod).Move(null, newDirectory.Name);
|
||||||
mod.ModPath = newDirectory;
|
mod.ModPath = newDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.Mods.Manager;
|
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Manager;
|
||||||
|
|
||||||
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.Mods.Editor;
|
using Penumbra.Mods.Editor;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
@ -16,7 +15,7 @@ public enum NewDirectoryState
|
||||||
Identical,
|
Identical,
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Describes the state of a changed mod event. </summary>
|
/// <summary> Describes the state of a changed mod event. </summary>
|
||||||
public enum ModPathChangeType
|
public enum ModPathChangeType
|
||||||
{
|
{
|
||||||
|
|
@ -25,7 +24,7 @@ public enum ModPathChangeType
|
||||||
Moved,
|
Moved,
|
||||||
Reloaded,
|
Reloaded,
|
||||||
StartingReload,
|
StartingReload,
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModManager : ModStorage, IDisposable
|
public sealed class ModManager : ModStorage, IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -46,8 +45,8 @@ public sealed class ModManager : ModStorage, IDisposable
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
DataEditor = dataEditor;
|
DataEditor = dataEditor;
|
||||||
OptionEditor = optionEditor;
|
OptionEditor = optionEditor;
|
||||||
Creator = creator;
|
Creator = creator;
|
||||||
SetBaseDirectory(config.ModDirectory, true);
|
SetBaseDirectory(config.ModDirectory, true);
|
||||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModManager);
|
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModManager);
|
||||||
DiscoverMods();
|
DiscoverMods();
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +241,7 @@ public sealed class ModManager : ModStorage, IDisposable
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case ModPathChangeType.Added:
|
case ModPathChangeType.Added:
|
||||||
SetNew(mod);
|
SetNew(mod);
|
||||||
break;
|
break;
|
||||||
case ModPathChangeType.Deleted:
|
case ModPathChangeType.Deleted:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using Penumbra.Api.Enums;
|
||||||
using Penumbra.Mods.Subclasses;
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods.Manager;
|
namespace Penumbra.Mods.Manager;
|
||||||
|
|
||||||
|
|
@ -19,7 +18,9 @@ public static partial class ModMigration
|
||||||
private static partial Regex GroupStartRegex();
|
private static partial Regex GroupStartRegex();
|
||||||
|
|
||||||
public static bool Migrate(ModCreator creator, SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
public static bool Migrate(ModCreator creator, SaveService saveService, Mod mod, JObject json, ref uint fileVersion)
|
||||||
=> MigrateV0ToV1(creator, saveService, mod, json, ref fileVersion) || MigrateV1ToV2(saveService, mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion);
|
=> MigrateV0ToV1(creator, saveService, mod, json, ref fileVersion)
|
||||||
|
|| MigrateV1ToV2(saveService, mod, ref fileVersion)
|
||||||
|
|| MigrateV2ToV3(mod, ref fileVersion);
|
||||||
|
|
||||||
private static bool MigrateV2ToV3(Mod _, ref uint fileVersion)
|
private static bool MigrateV2ToV3(Mod _, ref uint fileVersion)
|
||||||
{
|
{
|
||||||
|
|
@ -63,8 +64,8 @@ public static partial class ModMigration
|
||||||
|
|
||||||
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
|
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
|
||||||
?? new Dictionary<Utf8GamePath, FullPath>();
|
?? new Dictionary<Utf8GamePath, FullPath>();
|
||||||
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
|
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
|
||||||
var priority = 1;
|
var priority = 1;
|
||||||
var seenMetaFiles = new HashSet<FullPath>();
|
var seenMetaFiles = new HashSet<FullPath>();
|
||||||
foreach (var group in groups.Values)
|
foreach (var group in groups.Values)
|
||||||
ConvertGroup(creator, mod, group, ref priority, seenMetaFiles);
|
ConvertGroup(creator, mod, group, ref priority, seenMetaFiles);
|
||||||
|
|
@ -128,8 +129,8 @@ public static partial class ModMigration
|
||||||
var optionPriority = 0;
|
var optionPriority = 0;
|
||||||
var newMultiGroup = new MultiModGroup()
|
var newMultiGroup = new MultiModGroup()
|
||||||
{
|
{
|
||||||
Name = group.GroupName,
|
Name = group.GroupName,
|
||||||
Priority = priority++,
|
Priority = priority++,
|
||||||
Description = string.Empty,
|
Description = string.Empty,
|
||||||
};
|
};
|
||||||
mod.Groups.Add(newMultiGroup);
|
mod.Groups.Add(newMultiGroup);
|
||||||
|
|
@ -146,8 +147,8 @@ public static partial class ModMigration
|
||||||
|
|
||||||
var newSingleGroup = new SingleModGroup()
|
var newSingleGroup = new SingleModGroup()
|
||||||
{
|
{
|
||||||
Name = group.GroupName,
|
Name = group.GroupName,
|
||||||
Priority = priority++,
|
Priority = priority++,
|
||||||
Description = string.Empty,
|
Description = string.Empty,
|
||||||
};
|
};
|
||||||
mod.Groups.Add(newSingleGroup);
|
mod.Groups.Add(newSingleGroup);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public sealed partial class Mod : IMod
|
public sealed class Mod : IMod
|
||||||
{
|
{
|
||||||
public static readonly TemporaryMod ForcedFiles = new()
|
public static readonly TemporaryMod ForcedFiles = new()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,7 @@ public partial class ModCreator
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes)
|
if (changes)
|
||||||
{
|
|
||||||
_saveService.SaveAllOptionGroups(mod, true);
|
_saveService.SaveAllOptionGroups(mod, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Load the default option for a given mod.</summary>
|
/// <summary> Load the default option for a given mod.</summary>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue