mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,19 @@ 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 EqpCache()
|
||||||
{}
|
{ }
|
||||||
|
|
||||||
public void SetFiles(MetaFileManager manager)
|
public void SetFiles(MetaFileManager manager)
|
||||||
=> manager.SetFile( _eqpFile, MetaIndex.Eqp );
|
=> 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()
|
||||||
{
|
{
|
||||||
|
|
@ -33,23 +33,22 @@ public struct EqpCache : IDisposable
|
||||||
_eqpManipulations.Clear();
|
_eqpManipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( MetaFileManager manager, EqpManipulation manip )
|
public bool ApplyMod(MetaFileManager manager, EqpManipulation manip)
|
||||||
{
|
{
|
||||||
_eqpManipulations.AddOrReplace( manip );
|
_eqpManipulations.AddOrReplace(manip);
|
||||||
_eqpFile ??= new ExpandedEqpFile(manager);
|
_eqpFile ??= new ExpandedEqpFile(manager);
|
||||||
return manip.Apply( _eqpFile );
|
return manip.Apply(_eqpFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RevertMod( MetaFileManager manager, EqpManipulation manip )
|
public bool RevertMod(MetaFileManager manager, EqpManipulation manip)
|
||||||
{
|
{
|
||||||
var idx = _eqpManipulations.FindIndex( manip.Equals );
|
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()
|
||||||
|
|
|
||||||
|
|
@ -10,41 +10,41 @@ 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()
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ public static class ActiveCollectionMigration
|
||||||
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;
|
||||||
|
|
@ -460,14 +461,16 @@ public class ActiveCollections : ISavable, IDisposable
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,6 +36,7 @@ public partial class IndividualCollections
|
||||||
Loaded.Invoke();
|
Loaded.Invoke();
|
||||||
_actorService.FinishedCreation -= Func;
|
_actorService.FinishedCreation -= Func;
|
||||||
}
|
}
|
||||||
|
|
||||||
_actorService.FinishedCreation += Func;
|
_actorService.FinishedCreation += Func;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -142,7 +143,8 @@ 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>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,7 @@ public class CommandHandler : IDisposable
|
||||||
{
|
{
|
||||||
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,11 +2,13 @@
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ public partial class TexToolsImporter
|
||||||
/// 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,57 +95,56 @@ 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.");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,70 +8,68 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -24,63 +24,61 @@ public partial class TexToolsMeta
|
||||||
// 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;
|
private readonly MetaFileManager _metaFileManager;
|
||||||
|
|
||||||
public TexToolsMeta( MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault )
|
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 );
|
|
||||||
if( idx < 0 )
|
|
||||||
{
|
{
|
||||||
|
var idx = entries.FindIndex(t => t.type == type);
|
||||||
|
if (idx < 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
reader.BaseStream.Seek(entries[idx].offset, SeekOrigin.Begin);
|
||||||
|
return reader.ReadBytes(entries[idx].size);
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
|
DeserializeEqpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Eqp));
|
||||||
return reader.ReadBytes( entries[ idx ].size );
|
DeserializeGmpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Gmp));
|
||||||
|
DeserializeEqdpEntries(metaInfo, ReadEntry(MetaManipulation.Type.Eqdp));
|
||||||
|
DeserializeEstEntries(metaInfo, ReadEntry(MetaManipulation.Type.Est));
|
||||||
|
DeserializeImcEntries(metaInfo, ReadEntry(MetaManipulation.Type.Imc));
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) );
|
|
||||||
DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) );
|
|
||||||
DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) );
|
|
||||||
DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) );
|
|
||||||
DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
{
|
||||||
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,13 +86,11 @@ 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]);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
@ -119,7 +119,9 @@ 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 = "")
|
private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context, Skeleton* skeleton, string prefix = "")
|
||||||
|
|
|
||||||
|
|
@ -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,7 +31,7 @@ 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()
|
||||||
{
|
{
|
||||||
|
|
@ -37,6 +41,7 @@ public unsafe class SafeTextureHandle : SafeHandle
|
||||||
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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,52 +48,52 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
@ -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,42 +2,43 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
@ -9,60 +9,58 @@ 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 mask = Eqp.Mask( Slot );
|
|
||||||
if( ( entry & mask ) == Entry )
|
|
||||||
{
|
{
|
||||||
|
var entry = file[SetId];
|
||||||
|
var mask = Eqp.Mask(Slot);
|
||||||
|
if ((entry & mask) == Entry)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
file[ SetId ] = ( entry & ~mask ) | Entry;
|
file[SetId] = (entry & ~mask) | Entry;
|
||||||
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 );
|
|
||||||
if( r != 0 )
|
|
||||||
{
|
{
|
||||||
|
var r = Race.CompareTo(other.Race);
|
||||||
|
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,6 +105,7 @@ 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 ];
|
|
||||||
if( entry == Entry )
|
|
||||||
{
|
{
|
||||||
|
var entry = file[SetId];
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ public interface IMod
|
||||||
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; }
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,5 +1,6 @@
|
||||||
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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +265,8 @@ public static class EquipmentSwap
|
||||||
{
|
{
|
||||||
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,37 +20,35 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,8 +57,8 @@ public static class ItemSwap
|
||||||
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 );
|
|
||||||
if( file.Valid )
|
|
||||||
{
|
{
|
||||||
|
file = new GenericFile(manager, path);
|
||||||
|
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 ];
|
|
||||||
if( i == str.Length - 1 || str[ i + 1 ] == '\0' )
|
|
||||||
{
|
{
|
||||||
|
hash1 = ((hash1 << 5) + hash1) ^ str[i];
|
||||||
|
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;
|
||||||
|
|
@ -15,16 +16,16 @@ 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,57 +111,59 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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,10 +113,8 @@ 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>
|
||||||
public void LoadDefaultOption(Mod mod)
|
public void LoadDefaultOption(Mod mod)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods.Subclasses;
|
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
public interface ISubMod
|
public interface ISubMod
|
||||||
{
|
{
|
||||||
|
|
@ -11,48 +10,48 @@ public interface ISubMod
|
||||||
public string FullName { get; }
|
public string FullName { get; }
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
|
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files { get; }
|
public IReadOnlyDictionary<Utf8GamePath, FullPath> Files { get; }
|
||||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps { get; }
|
public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps { get; }
|
||||||
public IReadOnlySet< MetaManipulation > Manipulations { get; }
|
public IReadOnlySet<MetaManipulation> Manipulations { get; }
|
||||||
|
|
||||||
public bool IsDefault { get; }
|
public bool IsDefault { get; }
|
||||||
|
|
||||||
public static void WriteSubMod( JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, int? priority )
|
public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, int? priority)
|
||||||
{
|
{
|
||||||
j.WriteStartObject();
|
j.WriteStartObject();
|
||||||
j.WritePropertyName( nameof( Name ) );
|
j.WritePropertyName(nameof(Name));
|
||||||
j.WriteValue( mod.Name );
|
j.WriteValue(mod.Name);
|
||||||
j.WritePropertyName( nameof(Description) );
|
j.WritePropertyName(nameof(Description));
|
||||||
j.WriteValue( mod.Description );
|
j.WriteValue(mod.Description);
|
||||||
if( priority != null )
|
if (priority != null)
|
||||||
{
|
{
|
||||||
j.WritePropertyName( nameof( IModGroup.Priority ) );
|
j.WritePropertyName(nameof(IModGroup.Priority));
|
||||||
j.WriteValue( priority.Value );
|
j.WriteValue(priority.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
j.WritePropertyName( nameof( mod.Files ) );
|
j.WritePropertyName(nameof(mod.Files));
|
||||||
j.WriteStartObject();
|
j.WriteStartObject();
|
||||||
foreach( var (gamePath, file) in mod.Files )
|
foreach (var (gamePath, file) in mod.Files)
|
||||||
{
|
{
|
||||||
if( file.ToRelPath( basePath, out var relPath ) )
|
if (file.ToRelPath(basePath, out var relPath))
|
||||||
{
|
{
|
||||||
j.WritePropertyName( gamePath.ToString() );
|
j.WritePropertyName(gamePath.ToString());
|
||||||
j.WriteValue( relPath.ToString() );
|
j.WriteValue(relPath.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
j.WriteEndObject();
|
j.WriteEndObject();
|
||||||
j.WritePropertyName( nameof( mod.FileSwaps ) );
|
j.WritePropertyName(nameof(mod.FileSwaps));
|
||||||
j.WriteStartObject();
|
j.WriteStartObject();
|
||||||
foreach( var (gamePath, file) in mod.FileSwaps )
|
foreach (var (gamePath, file) in mod.FileSwaps)
|
||||||
{
|
{
|
||||||
j.WritePropertyName( gamePath.ToString() );
|
j.WritePropertyName(gamePath.ToString());
|
||||||
j.WriteValue( file.ToString() );
|
j.WriteValue(file.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
j.WriteEndObject();
|
j.WriteEndObject();
|
||||||
j.WritePropertyName( nameof( mod.Manipulations ) );
|
j.WritePropertyName(nameof(mod.Manipulations));
|
||||||
serializer.Serialize( j, mod.Manipulations );
|
serializer.Serialize(j, mod.Manipulations);
|
||||||
j.WriteEndObject();
|
j.WriteEndObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,9 @@ using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
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.Subclasses;
|
||||||
|
|
||||||
/// <summary> Contains the settings for a given mod. </summary>
|
/// <summary> Contains the settings for a given mod. </summary>
|
||||||
public class ModSettings
|
public class ModSettings
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Mods.Subclasses;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
/// <summary> Groups that allow all available options to be selected at once. </summary>
|
/// <summary> Groups that allow all available options to be selected at once. </summary>
|
||||||
public sealed class MultiModGroup : IModGroup
|
public sealed class MultiModGroup : IModGroup
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Mods.Subclasses;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
/// <summary> Groups that allow only one of their available options to be selected. </summary>
|
/// <summary> Groups that allow only one of their available options to be selected. </summary>
|
||||||
public sealed class SingleModGroup : IModGroup
|
public sealed class SingleModGroup : IModGroup
|
||||||
|
|
@ -18,59 +17,55 @@ public sealed class SingleModGroup : IModGroup
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
public uint DefaultSettings { get; set; }
|
public uint DefaultSettings { get; set; }
|
||||||
|
|
||||||
public readonly List< SubMod > OptionData = new();
|
public readonly List<SubMod> OptionData = new();
|
||||||
|
|
||||||
public int OptionPriority( Index _ )
|
public int OptionPriority(Index _)
|
||||||
=> Priority;
|
=> Priority;
|
||||||
|
|
||||||
public ISubMod this[ Index idx ]
|
public ISubMod this[Index idx]
|
||||||
=> OptionData[ idx ];
|
=> OptionData[idx];
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int Count
|
public int Count
|
||||||
=> OptionData.Count;
|
=> OptionData.Count;
|
||||||
|
|
||||||
public IEnumerator< ISubMod > GetEnumerator()
|
public IEnumerator<ISubMod> GetEnumerator()
|
||||||
=> OptionData.GetEnumerator();
|
=> OptionData.GetEnumerator();
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
=> GetEnumerator();
|
=> GetEnumerator();
|
||||||
|
|
||||||
public static SingleModGroup? Load( Mod mod, JObject json, int groupIdx )
|
public static SingleModGroup? Load(Mod mod, JObject json, int groupIdx)
|
||||||
{
|
{
|
||||||
var options = json[ "Options" ];
|
var options = json["Options"];
|
||||||
var ret = new SingleModGroup
|
var ret = new SingleModGroup
|
||||||
{
|
{
|
||||||
Name = json[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty,
|
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
|
||||||
Description = json[ nameof( Description ) ]?.ToObject< string >() ?? string.Empty,
|
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
|
||||||
Priority = json[ nameof( Priority ) ]?.ToObject< int >() ?? 0,
|
Priority = json[nameof(Priority)]?.ToObject<int>() ?? 0,
|
||||||
DefaultSettings = json[ nameof( DefaultSettings ) ]?.ToObject< uint >() ?? 0u,
|
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<uint>() ?? 0u,
|
||||||
};
|
};
|
||||||
if( ret.Name.Length == 0 )
|
if (ret.Name.Length == 0)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
if (options != null)
|
||||||
|
foreach (var child in options.Children())
|
||||||
|
{
|
||||||
|
var subMod = new SubMod(mod);
|
||||||
|
subMod.SetPosition(groupIdx, ret.OptionData.Count);
|
||||||
|
subMod.Load(mod.ModPath, child, out _);
|
||||||
|
ret.OptionData.Add(subMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
if( options != null )
|
if ((int)ret.DefaultSettings >= ret.Count)
|
||||||
{
|
|
||||||
foreach( var child in options.Children() )
|
|
||||||
{
|
|
||||||
var subMod = new SubMod( mod );
|
|
||||||
subMod.SetPosition( groupIdx, ret.OptionData.Count );
|
|
||||||
subMod.Load( mod.ModPath, child, out _ );
|
|
||||||
ret.OptionData.Add( subMod );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ( int )ret.DefaultSettings >= ret.Count )
|
|
||||||
ret.DefaultSettings = 0;
|
ret.DefaultSettings = 0;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IModGroup Convert( GroupType type )
|
public IModGroup Convert(GroupType type)
|
||||||
{
|
{
|
||||||
switch( type )
|
switch (type)
|
||||||
{
|
{
|
||||||
case GroupType.Single: return this;
|
case GroupType.Single: return this;
|
||||||
case GroupType.Multi:
|
case GroupType.Multi:
|
||||||
|
|
@ -79,47 +74,41 @@ public sealed class SingleModGroup : IModGroup
|
||||||
Name = Name,
|
Name = Name,
|
||||||
Description = Description,
|
Description = Description,
|
||||||
Priority = Priority,
|
Priority = Priority,
|
||||||
DefaultSettings = 1u << ( int )DefaultSettings,
|
DefaultSettings = 1u << (int)DefaultSettings,
|
||||||
};
|
};
|
||||||
multi.PrioritizedOptions.AddRange( OptionData.Select( ( o, i ) => ( o, i ) ) );
|
multi.PrioritizedOptions.AddRange(OptionData.Select((o, i) => (o, i)));
|
||||||
return multi;
|
return multi;
|
||||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
|
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
|
||||||
{
|
|
||||||
if( !OptionData.Move( optionIdxFrom, optionIdxTo ) )
|
|
||||||
{
|
{
|
||||||
|
if (!OptionData.Move(optionIdxFrom, optionIdxTo))
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// Update default settings with the move.
|
// Update default settings with the move.
|
||||||
if( DefaultSettings == optionIdxFrom )
|
if (DefaultSettings == optionIdxFrom)
|
||||||
{
|
{
|
||||||
DefaultSettings = ( uint )optionIdxTo;
|
DefaultSettings = (uint)optionIdxTo;
|
||||||
}
|
}
|
||||||
else if( optionIdxFrom < optionIdxTo )
|
else if (optionIdxFrom < optionIdxTo)
|
||||||
{
|
|
||||||
if( DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo )
|
|
||||||
{
|
{
|
||||||
|
if (DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo)
|
||||||
--DefaultSettings;
|
--DefaultSettings;
|
||||||
}
|
}
|
||||||
}
|
else if (DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo)
|
||||||
else if( DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo )
|
|
||||||
{
|
{
|
||||||
++DefaultSettings;
|
++DefaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
|
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatePositions( int from = 0 )
|
public void UpdatePositions(int from = 0)
|
||||||
{
|
{
|
||||||
foreach( var (o, i) in OptionData.WithIndex().Skip( from ) )
|
foreach (var (o, i) in OptionData.WithIndex().Skip(from))
|
||||||
{
|
o.SetPosition(o.GroupIdx, i);
|
||||||
o.SetPosition( o.GroupIdx, i );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.Import;
|
|
||||||
using Penumbra.Meta;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods.Subclasses;
|
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sub mod is a collection of
|
/// A sub mod is a collection of
|
||||||
|
|
@ -5,7 +5,6 @@ using Penumbra.Mods.Manager;
|
||||||
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;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -23,78 +22,82 @@ public class TemporaryMod : IMod
|
||||||
ISubMod IMod.Default
|
ISubMod IMod.Default
|
||||||
=> Default;
|
=> Default;
|
||||||
|
|
||||||
public IReadOnlyList< IModGroup > Groups
|
public IReadOnlyList<IModGroup> Groups
|
||||||
=> Array.Empty< IModGroup >();
|
=> Array.Empty<IModGroup>();
|
||||||
|
|
||||||
public IEnumerable< SubMod > AllSubMods
|
public IEnumerable<SubMod> AllSubMods
|
||||||
=> new[] { Default };
|
=> new[]
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
};
|
||||||
|
|
||||||
public TemporaryMod()
|
public TemporaryMod()
|
||||||
=> Default = new SubMod( this );
|
=> Default = new SubMod(this);
|
||||||
|
|
||||||
public void SetFile( Utf8GamePath gamePath, FullPath fullPath )
|
public void SetFile(Utf8GamePath gamePath, FullPath fullPath)
|
||||||
=> Default.FileData[ gamePath ] = fullPath;
|
=> Default.FileData[gamePath] = fullPath;
|
||||||
|
|
||||||
public bool SetManipulation( MetaManipulation manip )
|
public bool SetManipulation(MetaManipulation manip)
|
||||||
=> Default.ManipulationData.Remove( manip ) | Default.ManipulationData.Add( manip );
|
=> Default.ManipulationData.Remove(manip) | Default.ManipulationData.Add(manip);
|
||||||
|
|
||||||
public void SetAll( Dictionary< Utf8GamePath, FullPath > dict, HashSet< MetaManipulation > manips )
|
public void SetAll(Dictionary<Utf8GamePath, FullPath> dict, HashSet<MetaManipulation> manips)
|
||||||
{
|
{
|
||||||
Default.FileData = dict;
|
Default.FileData = dict;
|
||||||
Default.ManipulationData = manips;
|
Default.ManipulationData = manips;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SaveTempCollection( Configuration config, SaveService saveService, ModManager modManager, ModCollection collection, string? character = null )
|
public static void SaveTempCollection(Configuration config, SaveService saveService, ModManager modManager, ModCollection collection,
|
||||||
|
string? character = null)
|
||||||
{
|
{
|
||||||
DirectoryInfo? dir = null;
|
DirectoryInfo? dir = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
dir = ModCreator.CreateModFolder( modManager.BasePath, collection.Name );
|
dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Name);
|
||||||
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
|
var fileDir = Directory.CreateDirectory(Path.Combine(dir.FullName, "files"));
|
||||||
modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? config.DefaultModAuthor,
|
modManager.DataEditor.CreateMeta(dir, collection.Name, character ?? config.DefaultModAuthor,
|
||||||
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
|
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null);
|
||||||
var mod = new Mod( dir );
|
var mod = new Mod(dir);
|
||||||
var defaultMod = mod.Default;
|
var defaultMod = mod.Default;
|
||||||
foreach( var (gamePath, fullPath) in collection.ResolvedFiles )
|
foreach (var (gamePath, fullPath) in collection.ResolvedFiles)
|
||||||
{
|
{
|
||||||
if( gamePath.Path.EndsWith( ".imc"u8 ) )
|
if (gamePath.Path.EndsWith(".imc"u8))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetPath = fullPath.Path.FullName;
|
var targetPath = fullPath.Path.FullName;
|
||||||
if( fullPath.Path.Name.StartsWith( '|' ) )
|
if (fullPath.Path.Name.StartsWith('|'))
|
||||||
{
|
{
|
||||||
targetPath = targetPath.Split( '|', 3, StringSplitOptions.RemoveEmptyEntries ).Last();
|
targetPath = targetPath.Split('|', 3, StringSplitOptions.RemoveEmptyEntries).Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
if( Path.IsPathRooted(targetPath) )
|
if (Path.IsPathRooted(targetPath))
|
||||||
{
|
{
|
||||||
var target = Path.Combine( fileDir.FullName, Path.GetFileName(targetPath) );
|
var target = Path.Combine(fileDir.FullName, Path.GetFileName(targetPath));
|
||||||
File.Copy( targetPath, target, true );
|
File.Copy(targetPath, target, true);
|
||||||
defaultMod.FileData[ gamePath ] = new FullPath( target );
|
defaultMod.FileData[gamePath] = new FullPath(target);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
defaultMod.FileSwapData[ gamePath ] = new FullPath(targetPath);
|
defaultMod.FileSwapData[gamePath] = new FullPath(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach( var manip in collection.MetaCache?.Manipulations ?? Array.Empty< MetaManipulation >() )
|
foreach (var manip in collection.MetaCache?.Manipulations ?? Array.Empty<MetaManipulation>())
|
||||||
defaultMod.ManipulationData.Add( manip );
|
defaultMod.ManipulationData.Add(manip);
|
||||||
|
|
||||||
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod));
|
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod));
|
||||||
modManager.AddMod( dir );
|
modManager.AddMod(dir);
|
||||||
Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." );
|
Penumbra.Log.Information($"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}.");
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Error( $"Could not save temporary collection {collection.Name} to permanent Mod:\n{e}" );
|
Penumbra.Log.Error($"Could not save temporary collection {collection.Name} to permanent Mod:\n{e}");
|
||||||
if( dir != null && Directory.Exists( dir.FullName ) )
|
if (dir != null && Directory.Exists(dir.FullName))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.Delete( dir.FullName, true );
|
Directory.Delete(dir.FullName, true);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using Penumbra.UI.Tabs;
|
||||||
using ChangedItemClick = Penumbra.Communication.ChangedItemClick;
|
using ChangedItemClick = Penumbra.Communication.ChangedItemClick;
|
||||||
using ChangedItemHover = Penumbra.Communication.ChangedItemHover;
|
using ChangedItemHover = Penumbra.Communication.ChangedItemHover;
|
||||||
using OtterGui.Tasks;
|
using OtterGui.Tasks;
|
||||||
|
using Penumbra.UI;
|
||||||
|
|
||||||
namespace Penumbra;
|
namespace Penumbra;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Penumbra.Interop.Services;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Editor;
|
using Penumbra.Mods.Editor;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
namespace Penumbra.Services;
|
||||||
|
|
@ -331,7 +332,8 @@ public class ConfigMigrationService
|
||||||
dict = dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority });
|
dict = dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority });
|
||||||
|
|
||||||
var emptyStorage = new ModStorage();
|
var emptyStorage = new ModStorage();
|
||||||
var collection = ModCollection.CreateFromData(_saveService, emptyStorage, ModCollection.DefaultCollectionName, 0, 1, dict, Array.Empty<string>());
|
var collection = ModCollection.CreateFromData(_saveService, emptyStorage, ModCollection.DefaultCollectionName, 0, 1, dict,
|
||||||
|
Array.Empty<string>());
|
||||||
_saveService.ImmediateSave(new ModCollectionSave(emptyStorage, collection));
|
_saveService.ImmediateSave(new ModCollectionSave(emptyStorage, collection));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using Dalamud.Game.Gui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using System.Reflection;
|
|
||||||
using Dalamud.Interface.DragDrop;
|
using Dalamud.Interface.DragDrop;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using Penumbra.UI;
|
||||||
using Penumbra.UI.AdvancedWindow;
|
using Penumbra.UI.AdvancedWindow;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
using Penumbra.UI.ModsTab;
|
using Penumbra.UI.ModsTab;
|
||||||
|
using Penumbra.UI.ResourceWatcher;
|
||||||
using Penumbra.UI.Tabs;
|
using Penumbra.UI.Tabs;
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
namespace Penumbra.Services;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ public class StainService : IDisposable
|
||||||
{
|
{
|
||||||
using var t = timer.Measure(StartTimeType.Stains);
|
using var t = timer.Measure(StartTimeType.Stains);
|
||||||
StainData = new StainData(pluginInterface, dataManager, dataManager.Language);
|
StainData = new StainData(pluginInterface, dataManager, dataManager.Language);
|
||||||
StainCombo = new FilterComboColors(140, StainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
|
StainCombo = new FilterComboColors(140,
|
||||||
|
StainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
|
||||||
StmFile = new StmFile(dataManager);
|
StmFile = new StmFile(dataManager);
|
||||||
TemplateCombo = new StainTemplateCombo(StmFile.Entries.Keys.Prepend((ushort)0));
|
TemplateCombo = new StainTemplateCombo(StmFile.Entries.Keys.Prepend((ushort)0));
|
||||||
Penumbra.Log.Verbose($"[{nameof(StainService)}] Created.");
|
Penumbra.Log.Verbose($"[{nameof(StainService)}] Created.");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Reflection;
|
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
|
|
@ -33,12 +32,13 @@ public class ValidityChecker
|
||||||
|
|
||||||
public void LogExceptions()
|
public void LogExceptions()
|
||||||
{
|
{
|
||||||
if( ImcExceptions.Count > 0 )
|
if (ImcExceptions.Count > 0)
|
||||||
Penumbra.Chat.NotificationMessage( $"{ImcExceptions} IMC Exceptions thrown during Penumbra load. Please repair your game files.", "Warning", NotificationType.Warning );
|
Penumbra.Chat.NotificationMessage($"{ImcExceptions} IMC Exceptions thrown during Penumbra load. Please repair your game files.",
|
||||||
|
"Warning", NotificationType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because remnants of penumbra in devPlugins cause issues, we check for them to warn users to remove them.
|
// Because remnants of penumbra in devPlugins cause issues, we check for them to warn users to remove them.
|
||||||
private static bool CheckDevPluginPenumbra( DalamudPluginInterface pi )
|
private static bool CheckDevPluginPenumbra(DalamudPluginInterface pi)
|
||||||
{
|
{
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
var path = Path.Combine( pi.DalamudAssetDirectory.Parent?.FullName ?? "INVALIDPATH", "devPlugins", "Penumbra" );
|
var path = Path.Combine( pi.DalamudAssetDirectory.Parent?.FullName ?? "INVALIDPATH", "devPlugins", "Penumbra" );
|
||||||
|
|
@ -59,7 +59,7 @@ public class ValidityChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the loaded version of Penumbra itself is in devPlugins.
|
// Check if the loaded version of Penumbra itself is in devPlugins.
|
||||||
private static bool CheckIsNotInstalled( DalamudPluginInterface pi )
|
private static bool CheckIsNotInstalled(DalamudPluginInterface pi)
|
||||||
{
|
{
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
var checkedDirectory = pi.AssemblyLocation.Directory?.Parent?.Parent?.Name;
|
var checkedDirectory = pi.AssemblyLocation.Directory?.Parent?.Parent?.Name;
|
||||||
|
|
@ -76,7 +76,7 @@ public class ValidityChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the loaded version of Penumbra is installed from a valid source repo.
|
// Check if the loaded version of Penumbra is installed from a valid source repo.
|
||||||
private static bool CheckSourceRepo( DalamudPluginInterface pi )
|
private static bool CheckSourceRepo(DalamudPluginInterface pi)
|
||||||
{
|
{
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
return pi.SourceRepository?.Trim().ToLowerInvariant() switch
|
return pi.SourceRepository?.Trim().ToLowerInvariant() switch
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ namespace Penumbra.Services;
|
||||||
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
|
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
|
||||||
{
|
{
|
||||||
public IdentifierService(StartTracker tracker, DalamudPluginInterface pi, IDataManager data, ItemService items)
|
public IdentifierService(StartTracker tracker, DalamudPluginInterface pi, IDataManager data, ItemService items)
|
||||||
: base(nameof(IdentifierService), tracker, StartTimeType.Identifier, () => GameData.GameData.GetIdentifier(pi, data, items.AwaitedService))
|
: base(nameof(IdentifierService), tracker, StartTimeType.Identifier,
|
||||||
|
() => GameData.GameData.GetIdentifier(pi, data, items.AwaitedService))
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,10 +114,8 @@ public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
var ret = false;
|
var ret = false;
|
||||||
if (tab.Mtrl.HasDyeTable)
|
if (tab.Mtrl.HasDyeTable)
|
||||||
{
|
|
||||||
for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||||
ret |= tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, i, dyeId);
|
ret |= tab.Mtrl.ApplyDyeTemplate(_stainService.StmFile, i, dyeId);
|
||||||
}
|
|
||||||
|
|
||||||
tab.UpdateColorTablePreview();
|
tab.UpdateColorTablePreview();
|
||||||
|
|
||||||
|
|
@ -301,7 +299,8 @@ public partial class ModEditWindow
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var tmpFloat = row.SpecularStrength;
|
var tmpFloat = row.SpecularStrength;
|
||||||
ImGui.SetNextItemWidth(floatSize);
|
ImGui.SetNextItemWidth(floatSize);
|
||||||
if (ImGui.DragFloat("##SpecularStrength", ref tmpFloat, 0.01f, 0f, HalfMaxValue, "%.2f") && FixFloat(ref tmpFloat, row.SpecularStrength))
|
if (ImGui.DragFloat("##SpecularStrength", ref tmpFloat, 0.01f, 0f, HalfMaxValue, "%.2f")
|
||||||
|
&& FixFloat(ref tmpFloat, row.SpecularStrength))
|
||||||
{
|
{
|
||||||
row.SpecularStrength = tmpFloat;
|
row.SpecularStrength = tmpFloat;
|
||||||
ret = true;
|
ret = true;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Meta.Files;
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
|
||||||
using Penumbra.Mods.Editor;
|
using Penumbra.Mods.Editor;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
|
@ -66,17 +65,23 @@ public partial class ModEditWindow
|
||||||
if (!child)
|
if (!child)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DrawEditHeader(_editor.MetaEditor.Eqp, "Equipment Parameter Edits (EQP)###EQP", 5, EqpRow.Draw, EqpRow.DrawNew , _editor.MetaEditor.OtherEqpCount);
|
DrawEditHeader(_editor.MetaEditor.Eqp, "Equipment Parameter Edits (EQP)###EQP", 5, EqpRow.Draw, EqpRow.DrawNew,
|
||||||
DrawEditHeader(_editor.MetaEditor.Eqdp, "Racial Model Edits (EQDP)###EQDP", 7, EqdpRow.Draw, EqdpRow.DrawNew, _editor.MetaEditor.OtherEqdpCount);
|
_editor.MetaEditor.OtherEqpCount);
|
||||||
DrawEditHeader(_editor.MetaEditor.Imc, "Variant Edits (IMC)###IMC", 10, ImcRow.Draw, ImcRow.DrawNew , _editor.MetaEditor.OtherImcCount);
|
DrawEditHeader(_editor.MetaEditor.Eqdp, "Racial Model Edits (EQDP)###EQDP", 7, EqdpRow.Draw, EqdpRow.DrawNew,
|
||||||
DrawEditHeader(_editor.MetaEditor.Est, "Extra Skeleton Parameters (EST)###EST", 7, EstRow.Draw, EstRow.DrawNew , _editor.MetaEditor.OtherEstCount);
|
_editor.MetaEditor.OtherEqdpCount);
|
||||||
DrawEditHeader(_editor.MetaEditor.Gmp, "Visor/Gimmick Edits (GMP)###GMP", 7, GmpRow.Draw, GmpRow.DrawNew , _editor.MetaEditor.OtherGmpCount);
|
DrawEditHeader(_editor.MetaEditor.Imc, "Variant Edits (IMC)###IMC", 10, ImcRow.Draw, ImcRow.DrawNew, _editor.MetaEditor.OtherImcCount);
|
||||||
DrawEditHeader(_editor.MetaEditor.Rsp, "Racial Scaling Edits (RSP)###RSP", 5, RspRow.Draw, RspRow.DrawNew , _editor.MetaEditor.OtherRspCount);
|
DrawEditHeader(_editor.MetaEditor.Est, "Extra Skeleton Parameters (EST)###EST", 7, EstRow.Draw, EstRow.DrawNew,
|
||||||
|
_editor.MetaEditor.OtherEstCount);
|
||||||
|
DrawEditHeader(_editor.MetaEditor.Gmp, "Visor/Gimmick Edits (GMP)###GMP", 7, GmpRow.Draw, GmpRow.DrawNew,
|
||||||
|
_editor.MetaEditor.OtherGmpCount);
|
||||||
|
DrawEditHeader(_editor.MetaEditor.Rsp, "Racial Scaling Edits (RSP)###RSP", 5, RspRow.Draw, RspRow.DrawNew,
|
||||||
|
_editor.MetaEditor.OtherRspCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary> The headers for the different meta changes all have basically the same structure for different types.</summary>
|
/// <summary> The headers for the different meta changes all have basically the same structure for different types.</summary>
|
||||||
private void DrawEditHeader<T>(IReadOnlyCollection<T> items, string label, int numColumns, Action<MetaFileManager, T, ModEditor, Vector2> draw,
|
private void DrawEditHeader<T>(IReadOnlyCollection<T> items, string label, int numColumns,
|
||||||
|
Action<MetaFileManager, T, ModEditor, Vector2> draw,
|
||||||
Action<MetaFileManager, ModEditor, Vector2> drawNew, int otherCount)
|
Action<MetaFileManager, ModEditor, Vector2> drawNew, int otherCount)
|
||||||
{
|
{
|
||||||
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV;
|
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV;
|
||||||
|
|
@ -92,6 +97,7 @@ public partial class ModEditWindow
|
||||||
ImGuiUtil.TextColored(ColorId.RedundantAssignment.Value() | 0xFF000000, text);
|
ImGuiUtil.TextColored(ColorId.RedundantAssignment.Value() | 0xFF000000, text);
|
||||||
ImGui.SetCursorPos(newPos);
|
ImGui.SetCursorPos(newPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!header)
|
if (!header)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -223,7 +229,8 @@ public partial class ModEditWindow
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (IdInput("##eqdpId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
|
if (IdInput("##eqdpId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
|
||||||
{
|
{
|
||||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), setId);
|
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race),
|
||||||
|
_new.Slot.IsAccessory(), setId);
|
||||||
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, _new.Race, setId);
|
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, _new.Race, setId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,7 +239,8 @@ public partial class ModEditWindow
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (Combos.Race("##eqdpRace", _new.Race, out var race))
|
if (Combos.Race("##eqdpRace", _new.Race, out var race))
|
||||||
{
|
{
|
||||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, race), _new.Slot.IsAccessory(), _new.SetId);
|
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, race),
|
||||||
|
_new.Slot.IsAccessory(), _new.SetId);
|
||||||
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, race, _new.SetId);
|
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, race, _new.SetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,7 +249,8 @@ public partial class ModEditWindow
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (Combos.Gender("##eqdpGender", _new.Gender, out var gender))
|
if (Combos.Gender("##eqdpGender", _new.Gender, out var gender))
|
||||||
{
|
{
|
||||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(gender, _new.Race), _new.Slot.IsAccessory(), _new.SetId);
|
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(gender, _new.Race),
|
||||||
|
_new.Slot.IsAccessory(), _new.SetId);
|
||||||
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, gender, _new.Race, _new.SetId);
|
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, gender, _new.Race, _new.SetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,7 +259,8 @@ public partial class ModEditWindow
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (Combos.EqdpEquipSlot("##eqdpSlot", _new.Slot, out var slot))
|
if (Combos.EqdpEquipSlot("##eqdpSlot", _new.Slot, out var slot))
|
||||||
{
|
{
|
||||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), slot.IsAccessory(), _new.SetId);
|
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race),
|
||||||
|
slot.IsAccessory(), _new.SetId);
|
||||||
_new = new EqdpManipulation(newDefaultEntry, slot, _new.Gender, _new.Race, _new.SetId);
|
_new = new EqdpManipulation(newDefaultEntry, slot, _new.Gender, _new.Race, _new.SetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,7 +298,8 @@ public partial class ModEditWindow
|
||||||
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
var defaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(), meta.SetId);
|
var defaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(),
|
||||||
|
meta.SetId);
|
||||||
var (defaultBit1, defaultBit2) = defaultEntry.ToBits(meta.Slot);
|
var (defaultBit1, defaultBit2) = defaultEntry.ToBits(meta.Slot);
|
||||||
var (bit1, bit2) = meta.Entry.ToBits(meta.Slot);
|
var (bit1, bit2) = meta.Entry.ToBits(meta.Slot);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -370,7 +381,8 @@ public partial class ModEditWindow
|
||||||
if (_new.ObjectType is ObjectType.Equipment)
|
if (_new.ObjectType is ObjectType.Equipment)
|
||||||
{
|
{
|
||||||
if (Combos.EqpEquipSlot("##imcSlot", 100, _new.EquipSlot, out var slot))
|
if (Combos.EqpEquipSlot("##imcSlot", 100, _new.EquipSlot, out var slot))
|
||||||
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, _new.Entry)
|
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot,
|
||||||
|
_new.Entry)
|
||||||
.Copy(GetDefault(metaFileManager, _new)
|
.Copy(GetDefault(metaFileManager, _new)
|
||||||
?? new ImcEntry());
|
?? new ImcEntry());
|
||||||
|
|
||||||
|
|
@ -379,7 +391,8 @@ public partial class ModEditWindow
|
||||||
else if (_new.ObjectType is ObjectType.Accessory)
|
else if (_new.ObjectType is ObjectType.Accessory)
|
||||||
{
|
{
|
||||||
if (Combos.AccessorySlot("##imcSlot", _new.EquipSlot, out var slot))
|
if (Combos.AccessorySlot("##imcSlot", _new.EquipSlot, out var slot))
|
||||||
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, _new.Entry)
|
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot,
|
||||||
|
_new.Entry)
|
||||||
.Copy(GetDefault(metaFileManager, _new)
|
.Copy(GetDefault(metaFileManager, _new)
|
||||||
?? new ImcEntry());
|
?? new ImcEntry());
|
||||||
|
|
||||||
|
|
@ -388,7 +401,8 @@ public partial class ModEditWindow
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (IdInput("##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId.Id, out var setId2, 0, ushort.MaxValue, false))
|
if (IdInput("##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId.Id, out var setId2, 0, ushort.MaxValue, false))
|
||||||
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant.Id, _new.EquipSlot, _new.Entry)
|
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant.Id, _new.EquipSlot,
|
||||||
|
_new.Entry)
|
||||||
.Copy(GetDefault(metaFileManager, _new)
|
.Copy(GetDefault(metaFileManager, _new)
|
||||||
?? new ImcEntry());
|
?? new ImcEntry());
|
||||||
|
|
||||||
|
|
@ -405,7 +419,8 @@ public partial class ModEditWindow
|
||||||
if (_new.ObjectType is ObjectType.DemiHuman)
|
if (_new.ObjectType is ObjectType.DemiHuman)
|
||||||
{
|
{
|
||||||
if (Combos.EqpEquipSlot("##imcSlot", 70, _new.EquipSlot, out var slot))
|
if (Combos.EqpEquipSlot("##imcSlot", 70, _new.EquipSlot, out var slot))
|
||||||
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, _new.Entry)
|
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot,
|
||||||
|
_new.Entry)
|
||||||
.Copy(GetDefault(metaFileManager, _new)
|
.Copy(GetDefault(metaFileManager, _new)
|
||||||
?? new ImcEntry());
|
?? new ImcEntry());
|
||||||
|
|
||||||
|
|
@ -787,7 +802,8 @@ public partial class ModEditWindow
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||||
def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(),
|
def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(),
|
||||||
def != value);
|
def != value);
|
||||||
if (ImGui.DragFloat("##rspValue", ref value, 0.001f, RspManipulation.MinValue, RspManipulation.MaxValue) && value is >= RspManipulation.MinValue and <= RspManipulation.MaxValue)
|
if (ImGui.DragFloat("##rspValue", ref value, 0.001f, RspManipulation.MinValue, RspManipulation.MaxValue)
|
||||||
|
&& value is >= RspManipulation.MinValue and <= RspManipulation.MaxValue)
|
||||||
editor.MetaEditor.Change(meta.Copy(value));
|
editor.MetaEditor.Change(meta.Copy(value));
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}");
|
ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}");
|
||||||
|
|
|
||||||
|
|
@ -9,125 +9,108 @@ namespace Penumbra.UI.AdvancedWindow;
|
||||||
|
|
||||||
public partial class ModEditWindow
|
public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
private readonly FileEditor< MdlFile > _modelTab;
|
private readonly FileEditor<MdlFile> _modelTab;
|
||||||
|
|
||||||
private static bool DrawModelPanel( MdlFile file, bool disabled )
|
private static bool DrawModelPanel(MdlFile file, bool disabled)
|
||||||
{
|
{
|
||||||
var ret = false;
|
var ret = false;
|
||||||
for( var i = 0; i < file.Materials.Length; ++i )
|
for (var i = 0; i < file.Materials.Length; ++i)
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId( i );
|
using var id = ImRaii.PushId(i);
|
||||||
var tmp = file.Materials[ i ];
|
var tmp = file.Materials[i];
|
||||||
if( ImGui.InputText( string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength,
|
if (ImGui.InputText(string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength,
|
||||||
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None )
|
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)
|
||||||
&& tmp.Length > 0
|
&& tmp.Length > 0
|
||||||
&& tmp != file.Materials[ i ] )
|
&& tmp != file.Materials[i])
|
||||||
{
|
{
|
||||||
file.Materials[ i ] = tmp;
|
file.Materials[i] = tmp;
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret |= DrawOtherModelDetails( file, disabled );
|
ret |= DrawOtherModelDetails(file, disabled);
|
||||||
|
|
||||||
return !disabled && ret;
|
return !disabled && ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DrawOtherModelDetails( MdlFile file, bool _ )
|
private static bool DrawOtherModelDetails(MdlFile file, bool _)
|
||||||
{
|
|
||||||
if( !ImGui.CollapsingHeader( "Further Content" ) )
|
|
||||||
{
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Further Content"))
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
using( var table = ImRaii.Table( "##data", 2, ImGuiTableFlags.SizingFixedFit ) )
|
using (var table = ImRaii.Table("##data", 2, ImGuiTableFlags.SizingFixedFit))
|
||||||
{
|
{
|
||||||
if( table )
|
if (table)
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawTableColumn( "Version" );
|
ImGuiUtil.DrawTableColumn("Version");
|
||||||
ImGuiUtil.DrawTableColumn( file.Version.ToString() );
|
ImGuiUtil.DrawTableColumn(file.Version.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Radius" );
|
ImGuiUtil.DrawTableColumn("Radius");
|
||||||
ImGuiUtil.DrawTableColumn( file.Radius.ToString( CultureInfo.InvariantCulture ) );
|
ImGuiUtil.DrawTableColumn(file.Radius.ToString(CultureInfo.InvariantCulture));
|
||||||
ImGuiUtil.DrawTableColumn( "Model Clip Out Distance" );
|
ImGuiUtil.DrawTableColumn("Model Clip Out Distance");
|
||||||
ImGuiUtil.DrawTableColumn( file.ModelClipOutDistance.ToString( CultureInfo.InvariantCulture ) );
|
ImGuiUtil.DrawTableColumn(file.ModelClipOutDistance.ToString(CultureInfo.InvariantCulture));
|
||||||
ImGuiUtil.DrawTableColumn( "Shadow Clip Out Distance" );
|
ImGuiUtil.DrawTableColumn("Shadow Clip Out Distance");
|
||||||
ImGuiUtil.DrawTableColumn( file.ShadowClipOutDistance.ToString( CultureInfo.InvariantCulture ) );
|
ImGuiUtil.DrawTableColumn(file.ShadowClipOutDistance.ToString(CultureInfo.InvariantCulture));
|
||||||
ImGuiUtil.DrawTableColumn( "LOD Count" );
|
ImGuiUtil.DrawTableColumn("LOD Count");
|
||||||
ImGuiUtil.DrawTableColumn( file.LodCount.ToString() );
|
ImGuiUtil.DrawTableColumn(file.LodCount.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Enable Index Buffer Streaming" );
|
ImGuiUtil.DrawTableColumn("Enable Index Buffer Streaming");
|
||||||
ImGuiUtil.DrawTableColumn( file.EnableIndexBufferStreaming.ToString() );
|
ImGuiUtil.DrawTableColumn(file.EnableIndexBufferStreaming.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Enable Edge Geometry" );
|
ImGuiUtil.DrawTableColumn("Enable Edge Geometry");
|
||||||
ImGuiUtil.DrawTableColumn( file.EnableEdgeGeometry.ToString() );
|
ImGuiUtil.DrawTableColumn(file.EnableEdgeGeometry.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Flags 1" );
|
ImGuiUtil.DrawTableColumn("Flags 1");
|
||||||
ImGuiUtil.DrawTableColumn( file.Flags1.ToString() );
|
ImGuiUtil.DrawTableColumn(file.Flags1.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Flags 2" );
|
ImGuiUtil.DrawTableColumn("Flags 2");
|
||||||
ImGuiUtil.DrawTableColumn( file.Flags2.ToString() );
|
ImGuiUtil.DrawTableColumn(file.Flags2.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Vertex Declarations" );
|
ImGuiUtil.DrawTableColumn("Vertex Declarations");
|
||||||
ImGuiUtil.DrawTableColumn( file.VertexDeclarations.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.VertexDeclarations.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Bone Bounding Boxes" );
|
ImGuiUtil.DrawTableColumn("Bone Bounding Boxes");
|
||||||
ImGuiUtil.DrawTableColumn( file.BoneBoundingBoxes.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.BoneBoundingBoxes.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Bone Tables" );
|
ImGuiUtil.DrawTableColumn("Bone Tables");
|
||||||
ImGuiUtil.DrawTableColumn( file.BoneTables.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.BoneTables.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Element IDs" );
|
ImGuiUtil.DrawTableColumn("Element IDs");
|
||||||
ImGuiUtil.DrawTableColumn( file.ElementIds.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.ElementIds.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Extra LoDs" );
|
ImGuiUtil.DrawTableColumn("Extra LoDs");
|
||||||
ImGuiUtil.DrawTableColumn( file.ExtraLods.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.ExtraLods.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Meshes" );
|
ImGuiUtil.DrawTableColumn("Meshes");
|
||||||
ImGuiUtil.DrawTableColumn( file.Meshes.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.Meshes.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Shape Meshes" );
|
ImGuiUtil.DrawTableColumn("Shape Meshes");
|
||||||
ImGuiUtil.DrawTableColumn( file.ShapeMeshes.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.ShapeMeshes.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "LoDs" );
|
ImGuiUtil.DrawTableColumn("LoDs");
|
||||||
ImGuiUtil.DrawTableColumn( file.Lods.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.Lods.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Vertex Declarations" );
|
ImGuiUtil.DrawTableColumn("Vertex Declarations");
|
||||||
ImGuiUtil.DrawTableColumn( file.VertexDeclarations.Length.ToString() );
|
ImGuiUtil.DrawTableColumn(file.VertexDeclarations.Length.ToString());
|
||||||
ImGuiUtil.DrawTableColumn( "Stack Size" );
|
ImGuiUtil.DrawTableColumn("Stack Size");
|
||||||
ImGuiUtil.DrawTableColumn( file.StackSize.ToString() );
|
ImGuiUtil.DrawTableColumn(file.StackSize.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using( var attributes = ImRaii.TreeNode( "Attributes", ImGuiTreeNodeFlags.DefaultOpen ) )
|
using (var attributes = ImRaii.TreeNode("Attributes", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
{
|
{
|
||||||
if( attributes )
|
if (attributes)
|
||||||
{
|
foreach (var attribute in file.Attributes)
|
||||||
foreach( var attribute in file.Attributes )
|
ImRaii.TreeNode(attribute, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
{
|
|
||||||
ImRaii.TreeNode( attribute, ImGuiTreeNodeFlags.Leaf ).Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using( var bones = ImRaii.TreeNode( "Bones", ImGuiTreeNodeFlags.DefaultOpen ) )
|
using (var bones = ImRaii.TreeNode("Bones", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
{
|
{
|
||||||
if( bones )
|
if (bones)
|
||||||
{
|
foreach (var bone in file.Bones)
|
||||||
foreach( var bone in file.Bones )
|
ImRaii.TreeNode(bone, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
{
|
|
||||||
ImRaii.TreeNode( bone, ImGuiTreeNodeFlags.Leaf ).Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using( var shapes = ImRaii.TreeNode( "Shapes", ImGuiTreeNodeFlags.DefaultOpen ) )
|
using (var shapes = ImRaii.TreeNode("Shapes", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
{
|
{
|
||||||
if( shapes )
|
if (shapes)
|
||||||
{
|
foreach (var shape in file.Shapes)
|
||||||
foreach( var shape in file.Shapes )
|
ImRaii.TreeNode(shape.ShapeName, ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
{
|
|
||||||
ImRaii.TreeNode( shape.ShapeName, ImGuiTreeNodeFlags.Leaf ).Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( file.RemainingData.Length > 0 )
|
if (file.RemainingData.Length > 0)
|
||||||
{
|
{
|
||||||
using var t = ImRaii.TreeNode( $"Additional Data (Size: {file.RemainingData.Length})###AdditionalData" );
|
using var t = ImRaii.TreeNode($"Additional Data (Size: {file.RemainingData.Length})###AdditionalData");
|
||||||
if( t )
|
if (t)
|
||||||
{
|
ImGuiUtil.TextWrapped(string.Join(' ', file.RemainingData.Select(c => $"{c:X2}")));
|
||||||
ImGuiUtil.TextWrapped( string.Join( ' ', file.RemainingData.Select( c => $"{c:X2}" ) ) );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
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