Make line endings explicit in editorconfig and share in sub projects, also apply editorconfig everywhere and move some namespaces.

This commit is contained in:
Ottermandias 2023-09-18 16:56:16 +02:00
parent 53adb6fa54
commit 2b4a01df06
155 changed files with 1620 additions and 1614 deletions

View file

@ -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

View file

@ -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;
} }

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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()

View file

@ -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()

View file

@ -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);
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
} }

View file

@ -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();
} }

View file

@ -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;

View file

@ -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()

View file

@ -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>());
} }

View file

@ -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;

View file

@ -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();

View file

@ -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()

View file

@ -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,
} }

View file

@ -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,
} }

View file

@ -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))
{ } { }

View file

@ -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))
{ } { }

View file

@ -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;

View file

@ -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;

View file

@ -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.");
}
} }

View file

@ -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);
} }

View file

@ -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);

View file

@ -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;

View file

@ -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();
} }

View file

@ -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);

View file

@ -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]);

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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;

View file

@ -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;
} }

View file

@ -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);
} }
} }

View file

@ -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 = "")

View file

@ -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()
{ {

View file

@ -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);

View file

@ -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();

View file

@ -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.

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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;
} }
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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;
} }
} }

View file

@ -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; //
} }

View file

@ -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);

View file

@ -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];
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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;
}
} }

View file

@ -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;

View file

@ -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;

View file

@ -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; }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
} }

View file

@ -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;

View file

@ -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;

View file

@ -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);
} }
} }

View file

@ -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}");

View file

@ -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;
} }

View file

@ -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;

View file

@ -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
{ {

View file

@ -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;

View file

@ -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;

View file

@ -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)
{ {

View file

@ -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()
{ {

View file

@ -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)

View file

@ -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;

View file

@ -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;

View file

@ -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();
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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 );
}
} }
} }

View file

@ -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

View file

@ -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
{ {

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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;

View file

@ -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.");

View file

@ -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

View file

@ -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))
{ } { }
} }

View file

@ -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;

View file

@ -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;

View file

@ -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.###}");

View file

@ -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