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

@ -25,7 +25,7 @@ public class TempModManager : IDisposable
public TempModManager(CommunicatorService communicator) public TempModManager(CommunicatorService communicator)
{ {
_communicator = communicator; _communicator = communicator;
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.TempModManager); _communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.TempModManager);
} }
@ -43,7 +43,7 @@ public class TempModManager : IDisposable
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict, public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
HashSet<MetaManipulation> manips, int priority) HashSet<MetaManipulation> manips, int priority)
{ {
var mod = GetOrCreateMod(tag, collection, priority, out var created); var mod = GetOrCreateMod(tag, collection, priority, out var created);
Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}."); Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}.");
mod.SetAll(dict, manips); mod.SetAll(dict, manips);
ApplyModChange(mod, collection, created, false); ApplyModChange(mod, collection, created, false);
@ -56,7 +56,7 @@ public class TempModManager : IDisposable
var list = collection == null ? _modsForAllCollections : _mods.TryGetValue(collection, out var l) ? l : null; var list = collection == null ? _modsForAllCollections : _mods.TryGetValue(collection, out var l) ? l : null;
if (list == null) if (list == null)
return RedirectResult.NotRegistered; return RedirectResult.NotRegistered;
var removed = list.RemoveAll(m => var removed = list.RemoveAll(m =>
{ {
if (m.Name != tag || priority != null && m.Priority != priority.Value) if (m.Name != tag || priority != null && m.Priority != priority.Value)

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;
@ -270,14 +271,14 @@ public class CollectionCache : IDisposable
foreach (var manip in subMod.Manipulations) foreach (var manip in subMod.Manipulations)
AddManipulation(manip, parentMod); AddManipulation(manip, parentMod);
} }
/// <summary> Invoke only if not in a full recalculation. </summary> /// <summary> Invoke only if not in a full recalculation. </summary>
private void InvokeResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath key, FullPath value, private void InvokeResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath key, FullPath value,
FullPath old, IMod? mod) FullPath old, IMod? mod)
{ {
if (Calculating == -1) if (Calculating == -1)
_manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod); _manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod);
} }
// Add a specific file redirection, handling potential conflicts. // Add a specific file redirection, handling potential conflicts.
// For different mods, higher mod priority takes precedence before option group priority, // For different mods, higher mod priority takes precedence before option group priority,
@ -292,7 +293,7 @@ public class CollectionCache : IDisposable
{ {
if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) if (ResolvedFiles.TryAdd(path, new ModPath(mod, file)))
{ {
ModData.AddPath(mod, path); ModData.AddPath(mod, path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod); InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod);
return; return;
} }

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

@ -9,20 +9,20 @@ namespace Penumbra.Collections.Cache;
public struct EqpCache : IDisposable public struct EqpCache : IDisposable
{ {
private ExpandedEqpFile? _eqpFile = null; private ExpandedEqpFile? _eqpFile = null;
private readonly List< EqpManipulation > _eqpManipulations = new(); private readonly List<EqpManipulation> _eqpManipulations = new();
public EqpCache()
{}
public void SetFiles(MetaFileManager manager) public EqpCache()
=> manager.SetFile( _eqpFile, MetaIndex.Eqp ); { }
public void SetFiles(MetaFileManager manager)
=> manager.SetFile(_eqpFile, MetaIndex.Eqp);
public static void ResetFiles(MetaFileManager manager) public static void ResetFiles(MetaFileManager manager)
=> manager.SetFile( null, MetaIndex.Eqp ); => manager.SetFile(null, MetaIndex.Eqp);
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
=> manager.TemporarilySetFile( _eqpFile, MetaIndex.Eqp ); => manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp);
public void Reset() public void Reset()
{ {
@ -31,25 +31,24 @@ public struct EqpCache : IDisposable
_eqpFile.Reset(_eqpManipulations.Select(m => m.SetId)); _eqpFile.Reset(_eqpManipulations.Select(m => m.SetId));
_eqpManipulations.Clear(); _eqpManipulations.Clear();
}
public bool ApplyMod( MetaFileManager manager, EqpManipulation manip )
{
_eqpManipulations.AddOrReplace( manip );
_eqpFile ??= new ExpandedEqpFile(manager);
return manip.Apply( _eqpFile );
} }
public bool RevertMod( MetaFileManager manager, EqpManipulation manip ) public bool ApplyMod(MetaFileManager manager, EqpManipulation manip)
{ {
var idx = _eqpManipulations.FindIndex( manip.Equals ); _eqpManipulations.AddOrReplace(manip);
_eqpFile ??= new ExpandedEqpFile(manager);
return manip.Apply(_eqpFile);
}
public bool RevertMod(MetaFileManager manager, EqpManipulation manip)
{
var idx = _eqpManipulations.FindIndex(manip.Equals);
if (idx < 0) if (idx < 0)
return false; return false;
var def = ExpandedEqpFile.GetDefault( manager, manip.SetId ); var def = ExpandedEqpFile.GetDefault(manager, manip.SetId);
manip = new EqpManipulation( def, manip.Slot, manip.SetId ); manip = new EqpManipulation(def, manip.Slot, manip.SetId);
return manip.Apply( _eqpFile! ); return manip.Apply(_eqpFile!);
} }
public void Dispose() public void Dispose()
@ -58,4 +57,4 @@ public struct EqpCache : IDisposable
_eqpFile = null; _eqpFile = null;
_eqpManipulations.Clear(); _eqpManipulations.Clear();
} }
} }

View file

@ -9,42 +9,42 @@ namespace Penumbra.Collections.Cache;
public struct GmpCache : IDisposable public struct GmpCache : IDisposable
{ {
private ExpandedGmpFile? _gmpFile = null; private ExpandedGmpFile? _gmpFile = null;
private readonly List< GmpManipulation > _gmpManipulations = new(); private readonly List<GmpManipulation> _gmpManipulations = new();
public GmpCache() public GmpCache()
{} { }
public void SetFiles(MetaFileManager manager) public void SetFiles(MetaFileManager manager)
=> manager.SetFile( _gmpFile, MetaIndex.Gmp ); => manager.SetFile(_gmpFile, MetaIndex.Gmp);
public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
=> manager.TemporarilySetFile( _gmpFile, MetaIndex.Gmp ); => manager.TemporarilySetFile(_gmpFile, MetaIndex.Gmp);
public void Reset() public void Reset()
{ {
if( _gmpFile == null ) if (_gmpFile == null)
return; return;
_gmpFile.Reset( _gmpManipulations.Select( m => m.SetId ) ); _gmpFile.Reset(_gmpManipulations.Select(m => m.SetId));
_gmpManipulations.Clear(); _gmpManipulations.Clear();
} }
public bool ApplyMod( MetaFileManager manager, GmpManipulation manip ) public bool ApplyMod(MetaFileManager manager, GmpManipulation manip)
{ {
_gmpManipulations.AddOrReplace( manip ); _gmpManipulations.AddOrReplace(manip);
_gmpFile ??= new ExpandedGmpFile(manager); _gmpFile ??= new ExpandedGmpFile(manager);
return manip.Apply( _gmpFile ); return manip.Apply(_gmpFile);
} }
public bool RevertMod( MetaFileManager manager, GmpManipulation manip ) public bool RevertMod(MetaFileManager manager, GmpManipulation manip)
{ {
if (!_gmpManipulations.Remove(manip)) if (!_gmpManipulations.Remove(manip))
return false; return false;
var def = ExpandedGmpFile.GetDefault( manager, manip.SetId ); var def = ExpandedGmpFile.GetDefault(manager, manip.SetId);
manip = new GmpManipulation( def, manip.SetId ); manip = new GmpManipulation(def, manip.SetId);
return manip.Apply( _gmpFile! ); return manip.Apply(_gmpFile!);
} }
public void Dispose() public void Dispose()
@ -53,4 +53,4 @@ public struct GmpCache : IDisposable
_gmpFile = null; _gmpFile = null;
_gmpManipulations.Clear(); _gmpManipulations.Clear();
} }
} }

View file

@ -44,9 +44,9 @@ public readonly struct ImcCache : IDisposable
if (idx < 0) if (idx < 0)
{ {
idx = _imcManipulations.Count; idx = _imcManipulations.Count;
_imcManipulations.Add((manip, null!)); _imcManipulations.Add((manip, null!));
} }
var path = manip.GamePath(); var path = manip.GamePath();
try try
{ {
@ -79,13 +79,13 @@ public readonly struct ImcCache : IDisposable
public bool RevertMod(MetaFileManager manager, ModCollection collection, ImcManipulation m) public bool RevertMod(MetaFileManager manager, ModCollection collection, ImcManipulation m)
{ {
if (!m.Validate()) if (!m.Validate())
return false; return false;
var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(m)); var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(m));
if (idx < 0) if (idx < 0)
return false; return false;
var (_, file) = _imcManipulations[idx]; var (_, file) = _imcManipulations[idx];
_imcManipulations.RemoveAt(idx); _imcManipulations.RemoveAt(idx);
if (_imcManipulations.All(p => !ReferenceEquals(p.Item2, file))) if (_imcManipulations.All(p => !ReferenceEquals(p.Item2, file)))
@ -94,8 +94,8 @@ public readonly struct ImcCache : IDisposable
collection._cache!.ForceFile(file.Path, FullPath.Empty); collection._cache!.ForceFile(file.Path, FullPath.Empty);
file.Dispose(); file.Dispose();
return true; return true;
} }
var def = ImcFile.GetDefault(manager, file.Path, m.EquipSlot, m.Variant.Id, out _); var def = ImcFile.GetDefault(manager, file.Path, m.EquipSlot, m.Variant.Id, out _);
var manip = m.Copy(def); var manip = m.Copy(def);
if (!manip.Apply(file)) if (!manip.Apply(file))

View file

@ -136,7 +136,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
_ => false, _ => false,
}; };
} }
/// <summary> Set a single file. </summary> /// <summary> Set a single file. </summary>
public void SetFile(MetaIndex metaIndex) public void SetFile(MetaIndex metaIndex)
{ {
@ -162,7 +162,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
break; break;
} }
} }
/// <summary> Set the currently relevant IMC files for the collection cache. </summary> /// <summary> Set the currently relevant IMC files for the collection cache. </summary>
public void SetImcFiles(bool fromFullCompute) public void SetImcFiles(bool fromFullCompute)
=> _imcCache.SetFiles(_collection, fromFullCompute); => _imcCache.SetFiles(_collection, fromFullCompute);
@ -171,7 +171,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
=> _eqpCache.TemporarilySetFiles(_manager); => _eqpCache.TemporarilySetFiles(_manager);
public MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) public MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory)
=> _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory); => _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory);
public MetaList.MetaReverter TemporarilySetGmpFile() public MetaList.MetaReverter TemporarilySetGmpFile()
=> _gmpCache.TemporarilySetFiles(_manager); => _gmpCache.TemporarilySetFiles(_manager);
@ -180,7 +180,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
=> _cmpCache.TemporarilySetFiles(_manager); => _cmpCache.TemporarilySetFiles(_manager);
public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type) public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
=> _estCache.TemporarilySetFiles(_manager, type); => _estCache.TemporarilySetFiles(_manager, type);
/// <summary> Try to obtain a manipulated IMC file. </summary> /// <summary> Try to obtain a manipulated IMC file. </summary>

View file

@ -16,18 +16,18 @@ public static class ActiveCollectionMigration
foreach (var (type, _, _) in CollectionTypeExtensions.Special.Where(t => t.Item2.StartsWith("Male "))) foreach (var (type, _, _) in CollectionTypeExtensions.Special.Where(t => t.Item2.StartsWith("Male ")))
{ {
var oldName = type.ToString()[4..]; var oldName = type.ToString()[4..];
var value = jObject[oldName]; var value = jObject[oldName];
if (value == null) if (value == null)
continue; continue;
jObject.Remove(oldName); jObject.Remove(oldName);
jObject.Add("Male" + oldName, value); jObject.Add("Male" + oldName, value);
jObject.Add("Female" + oldName, value); jObject.Add("Female" + oldName, value);
} }
using var stream = File.Open(fileNames.ActiveCollectionsFile, FileMode.Truncate); using var stream = File.Open(fileNames.ActiveCollectionsFile, FileMode.Truncate);
using var writer = new StreamWriter(stream); using var writer = new StreamWriter(stream);
using var j = new JsonTextWriter(writer); using var j = new JsonTextWriter(writer);
j.Formatting = Formatting.Indented; j.Formatting = Formatting.Indented;
jObject.WriteTo(j); jObject.WriteTo(j);
} }
@ -41,13 +41,14 @@ public static class ActiveCollectionMigration
// Load character collections. If a player name comes up multiple times, the last one is applied. // Load character collections. If a player name comes up multiple times, the last one is applied.
var characters = jObject["Characters"]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>(); var characters = jObject["Characters"]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>();
var dict = new Dictionary<string, ModCollection>(characters.Count); var dict = new Dictionary<string, ModCollection>(characters.Count);
foreach (var (player, collectionName) in characters) foreach (var (player, collectionName) in characters)
{ {
if (!storage.ByName(collectionName, out var collection)) if (!storage.ByName(collectionName, out var collection))
{ {
Penumbra.Chat.NotificationMessage( Penumbra.Chat.NotificationMessage(
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Name}.", "Load Failure", $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Name}.",
"Load Failure",
NotificationType.Warning); NotificationType.Warning);
dict.Add(player, ModCollection.Empty); dict.Add(player, ModCollection.Empty);
} }

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;
@ -450,26 +451,28 @@ public class ActiveCollections : ISavable, IDisposable
} }
var racialString = racial ? " and Racial Assignments" : string.Empty; var racialString = racial ? " and Racial Assignments" : string.Empty;
var @base = ByType(CollectionType.Default); var @base = ByType(CollectionType.Default);
var male = ByType(CollectionType.MalePlayerCharacter); var male = ByType(CollectionType.MalePlayerCharacter);
var female = ByType(CollectionType.FemalePlayerCharacter); var female = ByType(CollectionType.FemalePlayerCharacter);
if (male == yourself && female == yourself) if (male == yourself && female == yourself)
return return
$"Assignment is redundant due to overwriting Male Players and Female Players{racialString} with an identical collection.\nYou can remove it."; $"Assignment is redundant due to overwriting Male Players and Female Players{racialString} with an identical collection.\nYou can remove it.";
if (male == null) if (male == null)
{ {
if (female == null && @base == yourself) if (female == null && @base == yourself)
return $"Assignment is redundant due to overwriting Base{racialString} with an identical collection.\nYou can remove it."; return
$"Assignment is redundant due to overwriting Base{racialString} with an identical collection.\nYou can remove it.";
if (female == yourself && @base == yourself) if (female == yourself && @base == yourself)
return return
$"Assignment is redundant due to overwriting Base and Female Players{racialString} with an identical collection.\nYou can remove it."; $"Assignment is redundant due to overwriting Base and Female Players{racialString} with an identical collection.\nYou can remove it.";
} }
else if (male == yourself && female == null && @base == yourself) else if (male == yourself && female == null && @base == yourself)
{ {
return $"Assignment is redundant due to overwriting Base and Male Players{racialString} with an identical collection.\nYou can remove it."; return
$"Assignment is redundant due to overwriting Base and Male Players{racialString} with an identical collection.\nYou can remove it.";
} }
break; break;
// Check individual assignments. We can only be sure of redundancy for world-overlap or ownership overlap. // Check individual assignments. We can only be sure of redundancy for world-overlap or ownership overlap.
case CollectionType.Individual: case CollectionType.Individual:

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;
@ -246,7 +247,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
DirectoryInfo? newDirectory) DirectoryInfo? newDirectory)
{ {
switch (type) switch (type)
{ {
case ModPathChangeType.Added: case ModPathChangeType.Added:
foreach (var collection in this) foreach (var collection in this)

View file

@ -427,13 +427,13 @@ public static class CollectionTypeExtensions
public static string ToDescription(this CollectionType collectionType) public static string ToDescription(this CollectionType collectionType)
=> collectionType switch => collectionType switch
{ {
CollectionType.Default => "World, Music, Furniture, baseline for characters and monsters not specialized.", CollectionType.Default => "World, Music, Furniture, baseline for characters and monsters not specialized.",
CollectionType.Interface => "User Interface, Icons, Maps, Styles.", CollectionType.Interface => "User Interface, Icons, Maps, Styles.",
CollectionType.Yourself => "Your characters, regardless of name, race or gender. Applies in the login screen.", CollectionType.Yourself => "Your characters, regardless of name, race or gender. Applies in the login screen.",
CollectionType.MalePlayerCharacter => "Baseline for male player characters.", CollectionType.MalePlayerCharacter => "Baseline for male player characters.",
CollectionType.FemalePlayerCharacter => "Baseline for female player characters.", CollectionType.FemalePlayerCharacter => "Baseline for female player characters.",
CollectionType.MaleNonPlayerCharacter => "Baseline for humanoid male non-player characters.", CollectionType.MaleNonPlayerCharacter => "Baseline for humanoid male non-player characters.",
CollectionType.FemaleNonPlayerCharacter => "Baseline for humanoid female non-player characters.", CollectionType.FemaleNonPlayerCharacter => "Baseline for humanoid female non-player characters.",
_ => string.Empty, _ => string.Empty,
}; };
} }

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,9 +36,10 @@ public partial class IndividualCollections
Loaded.Invoke(); Loaded.Invoke();
_actorService.FinishedCreation -= Func; _actorService.FinishedCreation -= Func;
} }
_actorService.FinishedCreation += Func; _actorService.FinishedCreation += Func;
return false; return false;
} }
private bool ReadJObjectInternal(JArray? obj, CollectionStorage storage) private bool ReadJObjectInternal(JArray? obj, CollectionStorage storage)
{ {
@ -85,6 +87,7 @@ public partial class IndividualCollections
NotificationType.Error); NotificationType.Error);
} }
} }
return changes; return changes;
} }

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()
@ -109,4 +109,4 @@ public partial class ModCollection
public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type) public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type)
=> _cache?.Meta.TemporarilySetEstFile(type) => _cache?.Meta.TemporarilySetEstFile(type)
?? utility.TemporarilyResetResource((MetaIndex)type); ?? utility.TemporarilyResetResource((MetaIndex)type);
} }

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;
@ -44,10 +45,10 @@ public partial class ModCollection
/// This is used for material and imc changes. /// This is used for material and imc changes.
/// </summary> /// </summary>
public int ChangeCounter { get; private set; } public int ChangeCounter { get; private set; }
/// <summary> Increment the number of changes in the effective file list. </summary> /// <summary> Increment the number of changes in the effective file list. </summary>
public int IncrementCounter() public int IncrementCounter()
=> ++ChangeCounter; => ++ChangeCounter;
/// <summary> /// <summary>
/// If a ModSetting is null, it can be inherited from other collections. /// If a ModSetting is null, it can be inherited from other collections.
@ -57,7 +58,7 @@ public partial class ModCollection
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary> /// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings; public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings;
/// <summary> Inheritances stored before they can be applied. </summary> /// <summary> Inheritances stored before they can be applied. </summary>
public IReadOnlyList<string>? InheritanceByName; public IReadOnlyList<string>? InheritanceByName;
@ -118,7 +119,7 @@ public partial class ModCollection
/// <summary> Constructor for reading from files. </summary> /// <summary> Constructor for reading from files. </summary>
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index, public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index,
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances) Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
{ {
Debug.Assert(index > 0, "Collection read with non-positive index."); Debug.Assert(index > 0, "Collection read with non-positive index.");
var ret = new ModCollection(name, index, 0, version, new List<ModSettings?>(), new List<ModCollection>(), allSettings) var ret = new ModCollection(name, index, 0, version, new List<ModSettings?>(), new List<ModCollection>(), allSettings)
{ {
@ -130,7 +131,7 @@ public partial class ModCollection
} }
/// <summary> Constructor for temporary collections. </summary> /// <summary> Constructor for temporary collections. </summary>
public static ModCollection CreateTemporary(string name, int index, int changeCounter) public static ModCollection CreateTemporary(string name, int index, int changeCounter)
{ {
Debug.Assert(index < 0, "Temporary collection created with non-negative index."); Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(), var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
@ -142,9 +143,10 @@ public partial class ModCollection
public static ModCollection CreateEmpty(string name, int index, int modCount) public static ModCollection CreateEmpty(string name, int index, int modCount)
{ {
Debug.Assert(index >= 0, "Empty collection created with negative index."); Debug.Assert(index >= 0, "Empty collection created with negative index.");
return new ModCollection(name, index, 0, CurrentVersion, Enumerable.Repeat((ModSettings?) null, modCount).ToList(), new List<ModCollection>(), return new ModCollection(name, index, 0, CurrentVersion, Enumerable.Repeat((ModSettings?)null, modCount).ToList(),
new List<ModCollection>(),
new Dictionary<string, ModSettings.SavedSettings>()); new Dictionary<string, ModSettings.SavedSettings>());
} }
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary> /// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
internal bool AddMod(Mod mod) internal bool AddMod(Mod mod)

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;
@ -53,7 +54,7 @@ internal readonly struct ModCollectionSave : ISavable
} }
list.AddRange(_modCollection.UnusedSettings.Select(kvp => (kvp.Key, kvp.Value))); list.AddRange(_modCollection.UnusedSettings.Select(kvp => (kvp.Key, kvp.Value)));
list.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.OrdinalIgnoreCase)); list.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.OrdinalIgnoreCase));
foreach (var (modDir, settings) in list) foreach (var (modDir, settings) in list)
{ {
@ -64,7 +65,7 @@ internal readonly struct ModCollectionSave : ISavable
j.WriteEndObject(); j.WriteEndObject();
// Inherit by collection name. // Inherit by collection name.
j.WritePropertyName("Inheritance"); j.WritePropertyName("Inheritance");
x.Serialize(j, _modCollection.InheritanceByName ?? _modCollection.DirectlyInheritsFrom.Select(c => c.Name)); x.Serialize(j, _modCollection.InheritanceByName ?? _modCollection.DirectlyInheritsFrom.Select(c => c.Name));
j.WriteEndObject(); j.WriteEndObject();
} }

View file

@ -14,8 +14,8 @@ public readonly struct ResolveData
public bool Valid public bool Valid
=> _modCollection != null; => _modCollection != null;
public ResolveData() public ResolveData()
: this(null!, nint.Zero) : this(null!, nint.Zero)
{ } { }
public ResolveData(ModCollection collection, nint gameObject) public ResolveData(ModCollection collection, nint gameObject)

View file

@ -207,7 +207,8 @@ public class CommandHandler : IDisposable
private bool SetUiMinimumSize(string _) private bool SetUiMinimumSize(string _)
{ {
if (_config.MinimumSize.X == Configuration.Constants.MinimumSizeX && _config.MinimumSize.Y == Configuration.Constants.MinimumSizeY) if (_config.MinimumSize.X == Configuration.Constants.MinimumSizeX && _config.MinimumSize.Y == Configuration.Constants.MinimumSizeY)
return false; return false;
_config.MinimumSize.X = Configuration.Constants.MinimumSizeX; _config.MinimumSize.X = Configuration.Constants.MinimumSizeX;
_config.MinimumSize.Y = Configuration.Constants.MinimumSizeY; _config.MinimumSize.Y = Configuration.Constants.MinimumSizeY;
_config.Save(); _config.Save();

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,13 +2,15 @@
global using System; global using System;
global using System.Collections; global using System.Collections;
global using System.Collections.Concurrent;
global using System.Collections.Generic; global using System.Collections.Generic;
global using System.Diagnostics; global using System.Diagnostics;
global using System.IO; global using System.IO;
global using System.Linq; global using System.Linq;
global using System.Numerics; global using System.Numerics;
global using System.Reflection;
global using System.Runtime.CompilerServices; global using System.Runtime.CompilerServices;
global using System.Runtime.InteropServices; global using System.Runtime.InteropServices;
global using System.Security.Cryptography; global using System.Security.Cryptography;
global using System.Threading; global using System.Threading;
global using System.Threading.Tasks; global using System.Threading.Tasks;

View file

@ -7,4 +7,4 @@ public enum ImporterState
ExtractingModFiles, ExtractingModFiles,
DeduplicatingFiles, DeduplicatingFiles,
Done, Done,
} }

View file

@ -20,4 +20,4 @@ public class StreamDisposer : PenumbraSqPackStream, IDisposable
File.Delete(filePath); File.Delete(filePath);
} }
} }

View file

@ -15,19 +15,19 @@ namespace Penumbra.Import;
public partial class TexToolsImporter public partial class TexToolsImporter
{ {
/// <summary> /// <summary>
/// Extract regular compressed archives that are folders containing penumbra-formatted mods. /// Extract regular compressed archives that are folders containing penumbra-formatted mods.
/// The mod has to either contain a meta.json at top level, or one folder deep. /// The mod has to either contain a meta.json at top level, or one folder deep.
/// If the meta.json is one folder deep, all other files have to be in the same folder. /// If the meta.json is one folder deep, all other files have to be in the same folder.
/// The extracted folder gets its name either from that one top-level folder or from the mod name. /// The extracted folder gets its name either from that one top-level folder or from the mod name.
/// All data is extracted without manipulation of the files or metadata. /// All data is extracted without manipulation of the files or metadata.
/// </summary> /// </summary>
private DirectoryInfo HandleRegularArchive( FileInfo modPackFile ) private DirectoryInfo HandleRegularArchive(FileInfo modPackFile)
{ {
using var zfs = modPackFile.OpenRead(); using var zfs = modPackFile.OpenRead();
using var archive = ArchiveFactory.Open( zfs ); using var archive = ArchiveFactory.Open(zfs);
var baseName = FindArchiveModMeta( archive, out var leadDir ); var baseName = FindArchiveModMeta(archive, out var leadDir);
var name = string.Empty; var name = string.Empty;
_currentOptionIdx = 0; _currentOptionIdx = 0;
_currentNumOptions = 1; _currentNumOptions = 1;
@ -42,9 +42,9 @@ public partial class TexToolsImporter
SevenZipArchive s => s.Entries.Count, SevenZipArchive s => s.Entries.Count,
_ => archive.Entries.Count(), _ => archive.Entries.Count(),
}; };
Penumbra.Log.Information( $" -> Importing {archive.Type} Archive." ); Penumbra.Log.Information($" -> Importing {archive.Type} Archive.");
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, Path.GetRandomFileName() ); _currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, Path.GetRandomFileName());
var options = new ExtractionOptions() var options = new ExtractionOptions()
{ {
ExtractFullPath = true, ExtractFullPath = true,
@ -55,40 +55,38 @@ public partial class TexToolsImporter
_currentFileIdx = 0; _currentFileIdx = 0;
var reader = archive.ExtractAllEntries(); var reader = archive.ExtractAllEntries();
while( reader.MoveToNextEntry() ) while (reader.MoveToNextEntry())
{ {
_token.ThrowIfCancellationRequested(); _token.ThrowIfCancellationRequested();
if( reader.Entry.IsDirectory ) if (reader.Entry.IsDirectory)
{ {
--_currentNumFiles; --_currentNumFiles;
continue; continue;
} }
Penumbra.Log.Information( $" -> Extracting {reader.Entry.Key}" ); Penumbra.Log.Information($" -> Extracting {reader.Entry.Key}");
// Check that the mod has a valid name in the meta.json file. // Check that the mod has a valid name in the meta.json file.
if( Path.GetFileName( reader.Entry.Key ) == "meta.json" ) if (Path.GetFileName(reader.Entry.Key) == "meta.json")
{ {
using var s = new MemoryStream(); using var s = new MemoryStream();
using var e = reader.OpenEntryStream(); using var e = reader.OpenEntryStream();
e.CopyTo( s ); e.CopyTo(s);
s.Seek( 0, SeekOrigin.Begin ); s.Seek(0, SeekOrigin.Begin);
using var t = new StreamReader( s ); using var t = new StreamReader(s);
using var j = new JsonTextReader( t ); using var j = new JsonTextReader(t);
var obj = JObject.Load( j ); var obj = JObject.Load(j);
name = obj[ nameof( Mod.Name ) ]?.Value< string >()?.RemoveInvalidPathSymbols() ?? string.Empty; name = obj[nameof(Mod.Name)]?.Value<string>()?.RemoveInvalidPathSymbols() ?? string.Empty;
if( name.Length == 0 ) if (name.Length == 0)
{ throw new Exception("Invalid mod archive: mod meta has no name.");
throw new Exception( "Invalid mod archive: mod meta has no name." );
}
using var f = File.OpenWrite( Path.Combine( _currentModDirectory.FullName, reader.Entry.Key ) ); using var f = File.OpenWrite(Path.Combine(_currentModDirectory.FullName, reader.Entry.Key));
s.Seek( 0, SeekOrigin.Begin ); s.Seek(0, SeekOrigin.Begin);
s.WriteTo( f ); s.WriteTo(f);
} }
else else
{ {
reader.WriteEntryToDirectory( _currentModDirectory.FullName, options ); reader.WriteEntryToDirectory(_currentModDirectory.FullName, options);
} }
++_currentFileIdx; ++_currentFileIdx;
@ -97,60 +95,59 @@ public partial class TexToolsImporter
_token.ThrowIfCancellationRequested(); _token.ThrowIfCancellationRequested();
var oldName = _currentModDirectory.FullName; var oldName = _currentModDirectory.FullName;
// Use either the top-level directory as the mods base name, or the (fixed for path) name in the json. // Use either the top-level directory as the mods base name, or the (fixed for path) name in the json.
if( leadDir ) if (leadDir)
{ {
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, baseName, false ); _currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, baseName, false);
Directory.Move( Path.Combine( oldName, baseName ), _currentModDirectory.FullName ); Directory.Move(Path.Combine(oldName, baseName), _currentModDirectory.FullName);
Directory.Delete( oldName ); Directory.Delete(oldName);
} }
else else
{ {
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, name, false ); _currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, name, false);
Directory.Move( oldName, _currentModDirectory.FullName ); Directory.Move(oldName, _currentModDirectory.FullName);
} }
_currentModDirectory.Refresh(); _currentModDirectory.Refresh();
_modManager.Creator.SplitMultiGroups( _currentModDirectory ); _modManager.Creator.SplitMultiGroups(_currentModDirectory);
return _currentModDirectory; return _currentModDirectory;
} }
// Search the archive for the meta.json file which needs to exist. // Search the archive for the meta.json file which needs to exist.
private static string FindArchiveModMeta( IArchive archive, out bool leadDir ) private static string FindArchiveModMeta(IArchive archive, out bool leadDir)
{ {
var entry = archive.Entries.FirstOrDefault( e => !e.IsDirectory && Path.GetFileName( e.Key ) == "meta.json" ); var entry = archive.Entries.FirstOrDefault(e => !e.IsDirectory && Path.GetFileName(e.Key) == "meta.json");
// None found. // None found.
if( entry == null ) if (entry == null)
{ throw new Exception("Invalid mod archive: No meta.json contained.");
throw new Exception( "Invalid mod archive: No meta.json contained." );
}
var ret = string.Empty; var ret = string.Empty;
leadDir = false; leadDir = false;
// If the file is not at top-level. // If the file is not at top-level.
if( entry.Key != "meta.json" ) if (entry.Key != "meta.json")
{ {
leadDir = true; leadDir = true;
var directory = Path.GetDirectoryName( entry.Key ); var directory = Path.GetDirectoryName(entry.Key);
// Should not happen. // Should not happen.
if( directory.IsNullOrEmpty() ) if (directory.IsNullOrEmpty())
{ throw new Exception("Invalid mod archive: Unknown error fetching meta.json.");
throw new Exception( "Invalid mod archive: Unknown error fetching meta.json." );
}
ret = directory; ret = directory;
// Check that all other files are also contained in the top-level directory. // Check that all other files are also contained in the top-level directory.
if( ret.IndexOfAny( new[] { '/', '\\' } ) >= 0 if (ret.IndexOfAny(new[]
|| !archive.Entries.All( e => e.Key.StartsWith( ret ) && ( e.Key.Length == ret.Length || e.Key[ ret.Length ] is '/' or '\\' ) ) ) {
{ '/',
'\\',
})
>= 0
|| !archive.Entries.All(e => e.Key.StartsWith(ret) && (e.Key.Length == ret.Length || e.Key[ret.Length] is '/' or '\\')))
throw new Exception( throw new Exception(
"Invalid mod archive: meta.json in wrong location. It needs to be either at root or one directory deep, in which all other files must be nested too." ); "Invalid mod archive: meta.json in wrong location. It needs to be either at root or one directory deep, in which all other files must be nested too.");
}
} }
return ret; return ret;
} }
} }

View file

@ -1,7 +1,7 @@
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.Import.Structs; using Penumbra.Import.Structs;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
namespace Penumbra.Import; namespace Penumbra.Import;
@ -20,89 +20,79 @@ public partial class TexToolsImporter
private string _currentOptionName = string.Empty; private string _currentOptionName = string.Empty;
private string _currentFileName = string.Empty; private string _currentFileName = string.Empty;
public void DrawProgressInfo( Vector2 size ) public void DrawProgressInfo(Vector2 size)
{ {
if( _modPackCount == 0 ) if (_modPackCount == 0)
{ {
ImGuiUtil.Center( "Nothing to extract." ); ImGuiUtil.Center("Nothing to extract.");
} }
else if( _modPackCount == _currentModPackIdx ) else if (_modPackCount == _currentModPackIdx)
{ {
DrawEndState(); DrawEndState();
} }
else else
{ {
ImGui.NewLine(); ImGui.NewLine();
var percentage = _modPackCount / ( float )_currentModPackIdx; var percentage = _modPackCount / (float)_currentModPackIdx;
ImGui.ProgressBar( percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}" ); ImGui.ProgressBar(percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}");
ImGui.NewLine(); ImGui.NewLine();
if( State == ImporterState.DeduplicatingFiles ) if (State == ImporterState.DeduplicatingFiles)
{ ImGui.TextUnformatted($"Deduplicating {_currentModName}...");
ImGui.TextUnformatted( $"Deduplicating {_currentModName}..." );
}
else else
{ ImGui.TextUnformatted($"Extracting {_currentModName}...");
ImGui.TextUnformatted( $"Extracting {_currentModName}..." );
}
if( _currentNumOptions > 1 ) if (_currentNumOptions > 1)
{ {
ImGui.NewLine(); ImGui.NewLine();
ImGui.NewLine(); ImGui.NewLine();
percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / ( float )_currentNumOptions; percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / (float)_currentNumOptions;
ImGui.ProgressBar( percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}" ); ImGui.ProgressBar(percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}");
ImGui.NewLine(); ImGui.NewLine();
if( State != ImporterState.DeduplicatingFiles ) if (State != ImporterState.DeduplicatingFiles)
{
ImGui.TextUnformatted( ImGui.TextUnformatted(
$"Extracting option {( _currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - " )}{_currentOptionName}..." ); $"Extracting option {(_currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - ")}{_currentOptionName}...");
}
} }
ImGui.NewLine(); ImGui.NewLine();
ImGui.NewLine(); ImGui.NewLine();
percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / ( float )_currentNumFiles; percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / (float)_currentNumFiles;
ImGui.ProgressBar( percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}" ); ImGui.ProgressBar(percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}");
ImGui.NewLine(); ImGui.NewLine();
if( State != ImporterState.DeduplicatingFiles ) if (State != ImporterState.DeduplicatingFiles)
{ ImGui.TextUnformatted($"Extracting file {_currentFileName}...");
ImGui.TextUnformatted( $"Extracting file {_currentFileName}..." );
}
} }
} }
private void DrawEndState() private void DrawEndState()
{ {
var success = ExtractedMods.Count( t => t.Error == null ); var success = ExtractedMods.Count(t => t.Error == null);
ImGui.TextUnformatted( $"Successfully extracted {success} / {ExtractedMods.Count} files." ); ImGui.TextUnformatted($"Successfully extracted {success} / {ExtractedMods.Count} files.");
ImGui.NewLine(); ImGui.NewLine();
using var table = ImRaii.Table( "##files", 2 ); using var table = ImRaii.Table("##files", 2);
if( !table ) if (!table)
{
return; return;
}
foreach( var (file, dir, ex) in ExtractedMods ) foreach (var (file, dir, ex) in ExtractedMods)
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted( file.Name ); ImGui.TextUnformatted(file.Name);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if( ex == null ) if (ex == null)
{ {
using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.FolderExpanded.Value() ); using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
ImGui.TextUnformatted( dir?.FullName[ ( _baseDirectory.FullName.Length + 1 ).. ] ?? "Unknown Directory" ); ImGui.TextUnformatted(dir?.FullName[(_baseDirectory.FullName.Length + 1)..] ?? "Unknown Directory");
} }
else else
{ {
using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.ConflictingMod.Value() ); using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ConflictingMod.Value());
ImGui.TextUnformatted( ex.Message ); ImGui.TextUnformatted(ex.Message);
ImGuiUtil.HoverTooltip( ex.ToString() ); ImGuiUtil.HoverTooltip(ex.ToString());
} }
} }
} }
public bool DrawCancelButton( Vector2 size ) public bool DrawCancelButton(Vector2 size)
=> ImGuiUtil.DrawDisabledButton( "Cancel", size, string.Empty, _token.IsCancellationRequested ); => ImGuiUtil.DrawDisabledButton("Cancel", size, string.Empty, _token.IsCancellationRequested);
} }

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,72 +8,70 @@ namespace Penumbra.Import;
public partial class TexToolsMeta public partial class TexToolsMeta
{ {
// Parse a single rgsp file. // Parse a single rgsp file.
public static TexToolsMeta FromRgspFile( MetaFileManager manager, string filePath, byte[] data, bool keepDefault ) public static TexToolsMeta FromRgspFile(MetaFileManager manager, string filePath, byte[] data, bool keepDefault)
{ {
if( data.Length != 45 && data.Length != 42 ) if (data.Length != 45 && data.Length != 42)
{ {
Penumbra.Log.Error( "Error while parsing .rgsp file:\n\tInvalid number of bytes." ); Penumbra.Log.Error("Error while parsing .rgsp file:\n\tInvalid number of bytes.");
return Invalid; return Invalid;
} }
using var s = new MemoryStream( data ); using var s = new MemoryStream(data);
using var br = new BinaryReader( s ); using var br = new BinaryReader(s);
// The first value is a flag that signifies version. // The first value is a flag that signifies version.
// If it is byte.max, the following two bytes are the version, // If it is byte.max, the following two bytes are the version,
// otherwise it is version 1 and signifies the sub race instead. // otherwise it is version 1 and signifies the sub race instead.
var flag = br.ReadByte(); var flag = br.ReadByte();
var version = flag != 255 ? ( uint )1 : br.ReadUInt16(); var version = flag != 255 ? (uint)1 : br.ReadUInt16();
var ret = new TexToolsMeta( manager, filePath, version ); var ret = new TexToolsMeta(manager, filePath, version);
// SubRace is offset by one due to Unknown. // SubRace is offset by one due to Unknown.
var subRace = ( SubRace )( version == 1 ? flag + 1 : br.ReadByte() + 1 ); var subRace = (SubRace)(version == 1 ? flag + 1 : br.ReadByte() + 1);
if( !Enum.IsDefined( typeof( SubRace ), subRace ) || subRace == SubRace.Unknown ) if (!Enum.IsDefined(typeof(SubRace), subRace) || subRace == SubRace.Unknown)
{ {
Penumbra.Log.Error( $"Error while parsing .rgsp file:\n\t{subRace} is not a valid SubRace." ); Penumbra.Log.Error($"Error while parsing .rgsp file:\n\t{subRace} is not a valid SubRace.");
return Invalid; return Invalid;
} }
// Next byte is Gender. 1 is Female, 0 is Male. // Next byte is Gender. 1 is Female, 0 is Male.
var gender = br.ReadByte(); var gender = br.ReadByte();
if( gender != 1 && gender != 0 ) if (gender != 1 && gender != 0)
{ {
Penumbra.Log.Error( $"Error while parsing .rgsp file:\n\t{gender} is neither Male nor Female." ); Penumbra.Log.Error($"Error while parsing .rgsp file:\n\t{gender} is neither Male nor Female.");
return Invalid; return Invalid;
} }
// Add the given values to the manipulations if they are not default. // Add the given values to the manipulations if they are not default.
void Add( RspAttribute attribute, float value ) void Add(RspAttribute attribute, float value)
{ {
var def = CmpFile.GetDefault( manager, subRace, attribute ); var def = CmpFile.GetDefault(manager, subRace, attribute);
if( keepDefault || value != def ) if (keepDefault || value != def)
{ ret.MetaManipulations.Add(new RspManipulation(subRace, attribute, value));
ret.MetaManipulations.Add( new RspManipulation( subRace, attribute, value ) );
}
} }
if( gender == 1 ) if (gender == 1)
{ {
Add( RspAttribute.FemaleMinSize, br.ReadSingle() ); Add(RspAttribute.FemaleMinSize, br.ReadSingle());
Add( RspAttribute.FemaleMaxSize, br.ReadSingle() ); Add(RspAttribute.FemaleMaxSize, br.ReadSingle());
Add( RspAttribute.FemaleMinTail, br.ReadSingle() ); Add(RspAttribute.FemaleMinTail, br.ReadSingle());
Add( RspAttribute.FemaleMaxTail, br.ReadSingle() ); Add(RspAttribute.FemaleMaxTail, br.ReadSingle());
Add( RspAttribute.BustMinX, br.ReadSingle() ); Add(RspAttribute.BustMinX, br.ReadSingle());
Add( RspAttribute.BustMinY, br.ReadSingle() ); Add(RspAttribute.BustMinY, br.ReadSingle());
Add( RspAttribute.BustMinZ, br.ReadSingle() ); Add(RspAttribute.BustMinZ, br.ReadSingle());
Add( RspAttribute.BustMaxX, br.ReadSingle() ); Add(RspAttribute.BustMaxX, br.ReadSingle());
Add( RspAttribute.BustMaxY, br.ReadSingle() ); Add(RspAttribute.BustMaxY, br.ReadSingle());
Add( RspAttribute.BustMaxZ, br.ReadSingle() ); Add(RspAttribute.BustMaxZ, br.ReadSingle());
} }
else else
{ {
Add( RspAttribute.MaleMinSize, br.ReadSingle() ); Add(RspAttribute.MaleMinSize, br.ReadSingle());
Add( RspAttribute.MaleMaxSize, br.ReadSingle() ); Add(RspAttribute.MaleMaxSize, br.ReadSingle());
Add( RspAttribute.MaleMinTail, br.ReadSingle() ); Add(RspAttribute.MaleMinTail, br.ReadSingle());
Add( RspAttribute.MaleMaxTail, br.ReadSingle() ); Add(RspAttribute.MaleMaxTail, br.ReadSingle());
} }
return ret; return ret;
} }
} }

View file

@ -17,70 +17,68 @@ namespace Penumbra.Import;
/// TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored. /// TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored.
/// TexTools also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file.</summary> /// TexTools also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file.</summary>
public partial class TexToolsMeta public partial class TexToolsMeta
{ {
/// <summary> An empty TexToolsMeta. </summary> /// <summary> An empty TexToolsMeta. </summary>
public static readonly TexToolsMeta Invalid = new(null!, string.Empty, 0); public static readonly TexToolsMeta Invalid = new(null!, string.Empty, 0);
// The info class determines the files or table locations the changes need to apply to from the filename. // The info class determines the files or table locations the changes need to apply to from the filename.
public readonly uint Version; public readonly uint Version;
public readonly string FilePath; public readonly string FilePath;
public readonly List< MetaManipulation > MetaManipulations = new(); public readonly List<MetaManipulation> MetaManipulations = new();
private readonly bool _keepDefault = false; private readonly bool _keepDefault = false;
private readonly MetaFileManager _metaFileManager;
public TexToolsMeta( MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault ) private readonly MetaFileManager _metaFileManager;
{
public TexToolsMeta(MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault)
{
_metaFileManager = metaFileManager; _metaFileManager = metaFileManager;
_keepDefault = keepDefault; _keepDefault = keepDefault;
try try
{ {
using var reader = new BinaryReader( new MemoryStream( data ) ); using var reader = new BinaryReader(new MemoryStream(data));
Version = reader.ReadUInt32(); Version = reader.ReadUInt32();
FilePath = ReadNullTerminated( reader ); FilePath = ReadNullTerminated(reader);
var metaInfo = new MetaFileInfo( parser, FilePath ); var metaInfo = new MetaFileInfo(parser, FilePath);
var numHeaders = reader.ReadUInt32(); var numHeaders = reader.ReadUInt32();
var headerSize = reader.ReadUInt32(); var headerSize = reader.ReadUInt32();
var headerStart = reader.ReadUInt32(); var headerStart = reader.ReadUInt32();
reader.BaseStream.Seek( headerStart, SeekOrigin.Begin ); reader.BaseStream.Seek(headerStart, SeekOrigin.Begin);
List< (MetaManipulation.Type type, uint offset, int size) > entries = new(); List<(MetaManipulation.Type type, uint offset, int size)> entries = new();
for( var i = 0; i < numHeaders; ++i ) for (var i = 0; i < numHeaders; ++i)
{ {
var currentOffset = reader.BaseStream.Position; var currentOffset = reader.BaseStream.Position;
var type = ( MetaManipulation.Type )reader.ReadUInt32(); var type = (MetaManipulation.Type)reader.ReadUInt32();
var offset = reader.ReadUInt32(); var offset = reader.ReadUInt32();
var size = reader.ReadInt32(); var size = reader.ReadInt32();
entries.Add( ( type, offset, size ) ); entries.Add((type, offset, size));
reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin ); reader.BaseStream.Seek(currentOffset + headerSize, SeekOrigin.Begin);
} }
byte[]? ReadEntry( MetaManipulation.Type type ) byte[]? ReadEntry(MetaManipulation.Type type)
{ {
var idx = entries.FindIndex( t => t.type == type ); var idx = entries.FindIndex(t => t.type == type);
if( idx < 0 ) if (idx < 0)
{
return null; return null;
}
reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin ); reader.BaseStream.Seek(entries[idx].offset, SeekOrigin.Begin);
return reader.ReadBytes( entries[ idx ].size ); return reader.ReadBytes(entries[idx].size);
} }
DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) ); DeserializeEqpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Eqp));
DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) ); DeserializeGmpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Gmp));
DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) ); DeserializeEqdpEntries(metaInfo, ReadEntry(MetaManipulation.Type.Eqdp));
DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) ); DeserializeEstEntries(metaInfo, ReadEntry(MetaManipulation.Type.Est));
DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) ); DeserializeImcEntries(metaInfo, ReadEntry(MetaManipulation.Type.Imc));
} }
catch( Exception e ) catch (Exception e)
{ {
FilePath = ""; FilePath = "";
Penumbra.Log.Error( $"Error while parsing .meta file:\n{e}" ); Penumbra.Log.Error($"Error while parsing .meta file:\n{e}");
} }
} }
private TexToolsMeta( MetaFileManager metaFileManager, string filePath, uint version ) private TexToolsMeta(MetaFileManager metaFileManager, string filePath, uint version)
{ {
_metaFileManager = metaFileManager; _metaFileManager = metaFileManager;
FilePath = filePath; FilePath = filePath;
@ -88,14 +86,12 @@ public partial class TexToolsMeta
} }
// Read a null terminated string from a binary reader. // Read a null terminated string from a binary reader.
private static string ReadNullTerminated( BinaryReader reader ) private static string ReadNullTerminated(BinaryReader reader)
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() ) for (var c = reader.ReadChar(); c != 0; c = reader.ReadChar())
{ builder.Append(c);
builder.Append( c );
}
return builder.ToString(); return builder.ToString();
} }
} }

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

@ -103,7 +103,7 @@ public static class TextureDrawer
public sealed class PathSelectCombo : FilterComboCache<(string, bool)> public sealed class PathSelectCombo : FilterComboCache<(string, bool)>
{ {
private int _skipPrefix = 0; private int _skipPrefix = 0;
public PathSelectCombo(TextureManager textures, ModEditor editor) public PathSelectCombo(TextureManager textures, ModEditor editor)
: base(() => CreateFiles(textures, editor)) : base(() => CreateFiles(textures, editor))

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

@ -17,7 +17,7 @@ namespace Penumbra.Interop.PathResolving;
public unsafe class AnimationHookService : IDisposable public unsafe class AnimationHookService : IDisposable
{ {
private readonly PerformanceTracker _performance; private readonly PerformanceTracker _performance;
private readonly IObjectTable _objects; private readonly IObjectTable _objects;
private readonly CollectionResolver _collectionResolver; private readonly CollectionResolver _collectionResolver;
private readonly DrawObjectState _drawObjectState; private readonly DrawObjectState _drawObjectState;
private readonly CollectionResolver _resolver; private readonly CollectionResolver _resolver;
@ -34,7 +34,7 @@ public unsafe class AnimationHookService : IDisposable
_collectionResolver = collectionResolver; _collectionResolver = collectionResolver;
_drawObjectState = drawObjectState; _drawObjectState = drawObjectState;
_resolver = resolver; _resolver = resolver;
_conditions = conditions; _conditions = conditions;
SignatureHelper.Initialise(this); SignatureHelper.Initialise(this);
@ -122,7 +122,7 @@ public unsafe class AnimationHookService : IDisposable
var last = _characterSoundData.Value; var last = _characterSoundData.Value;
_characterSoundData.Value = _collectionResolver.IdentifyCollection((GameObject*)character, true); _characterSoundData.Value = _collectionResolver.IdentifyCollection((GameObject*)character, true);
var ret = _loadCharacterSoundHook.Original(character, unk1, unk2, unk3, unk4, unk5, unk6, unk7); var ret = _loadCharacterSoundHook.Original(character, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
_characterSoundData.Value = last; _characterSoundData.Value = last;
return ret; return ret;
} }
@ -140,15 +140,15 @@ public unsafe class AnimationHookService : IDisposable
using var performance = _performance.Measure(PerformanceType.TimelineResources); using var performance = _performance.Measure(PerformanceType.TimelineResources);
// Do not check timeline loading in cutscenes. // Do not check timeline loading in cutscenes.
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78]) if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
return _loadTimelineResourcesHook.Original(timeline); return _loadTimelineResourcesHook.Original(timeline);
var last = _animationLoadData.Value; var last = _animationLoadData.Value;
_animationLoadData.Value = GetDataFromTimeline(timeline); _animationLoadData.Value = GetDataFromTimeline(timeline);
var ret = _loadTimelineResourcesHook.Original(timeline); var ret = _loadTimelineResourcesHook.Original(timeline);
_animationLoadData.Value = last; _animationLoadData.Value = last;
return ret; return ret;
} }
/// <summary> /// <summary>
/// Probably used when the base idle animation gets loaded. /// Probably used when the base idle animation gets loaded.
/// Make it aware of the correct collection to load the correct pap files. /// Make it aware of the correct collection to load the correct pap files.
@ -297,12 +297,12 @@ public unsafe class AnimationHookService : IDisposable
try try
{ {
if (timeline != IntPtr.Zero) if (timeline != IntPtr.Zero)
{ {
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc]; var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc];
var idx = getGameObjectIdx(timeline); var idx = getGameObjectIdx(timeline);
if (idx >= 0 && idx < _objects.Length) if (idx >= 0 && idx < _objects.Length)
{ {
var obj = (GameObject*)_objects.GetObjectAddress(idx); var obj = (GameObject*)_objects.GetObjectAddress(idx);
return obj != null ? _collectionResolver.IdentifyCollection(obj, true) : ResolveData.Invalid; return obj != null ? _collectionResolver.IdentifyCollection(obj, true) : ResolveData.Invalid;
} }
} }
@ -378,17 +378,17 @@ public unsafe class AnimationHookService : IDisposable
if (a6 == nint.Zero) if (a6 == nint.Zero)
return _apricotListenerSoundPlayHook!.Original(a1, a2, a3, a4, a5, a6); return _apricotListenerSoundPlayHook!.Original(a1, a2, a3, a4, a5, a6);
var last = _animationLoadData.Value; var last = _animationLoadData.Value;
// a6 is some instance of Apricot.IInstanceListenner, in some cases we can obtain the associated caster via vfunc 1. // a6 is some instance of Apricot.IInstanceListenner, in some cases we can obtain the associated caster via vfunc 1.
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)a6)[1](a6); var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)a6)[1](a6);
if (gameObject != null) if (gameObject != null)
{ {
_animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true); _animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true);
} }
else else
{ {
// for VfxListenner we can obtain the associated draw object as its first member, // for VfxListenner we can obtain the associated draw object as its first member,
// if the object has different type, drawObject will contain other values or garbage, // if the object has different type, drawObject will contain other values or garbage,
// but only be used in a dictionary pointer lookup, so this does not hurt. // but only be used in a dictionary pointer lookup, so this does not hurt.
var drawObject = ((DrawObject**)a6)[1]; var drawObject = ((DrawObject**)a6)[1];
if (drawObject != null) if (drawObject != null)

View file

@ -20,7 +20,7 @@ public unsafe class CollectionResolver
private readonly HumanModelList _humanModels; private readonly HumanModelList _humanModels;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly ActorService _actors; private readonly ActorService _actors;
private readonly CutsceneService _cutscenes; private readonly CutsceneService _cutscenes;

View file

@ -10,7 +10,7 @@ namespace Penumbra.Interop.PathResolving;
public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)> public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>
{ {
private readonly IObjectTable _objects; private readonly IObjectTable _objects;
private readonly GameEventManager _gameEvents; private readonly GameEventManager _gameEvents;
private readonly Dictionary<nint, (nint GameObject, bool IsChild)> _drawObjectToGameObject = new(); private readonly Dictionary<nint, (nint GameObject, bool IsChild)> _drawObjectToGameObject = new();
@ -71,8 +71,8 @@ public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, boo
private unsafe void OnWeaponReloaded(nint _, nint gameObject) private unsafe void OnWeaponReloaded(nint _, nint gameObject)
{ {
_lastGameObject.Value!.Dequeue(); _lastGameObject.Value!.Dequeue();
IterateDrawObjectTree((Object*) ((GameObject*) gameObject)->DrawObject, gameObject, false, false); IterateDrawObjectTree((Object*)((GameObject*)gameObject)->DrawObject, gameObject, false, false);
} }
private void OnCharacterBaseDestructor(nint characterBase) private void OnCharacterBaseDestructor(nint characterBase)

View file

@ -25,8 +25,8 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A
_events = events; _events = events;
_communicator.CollectionChange.Subscribe(CollectionChangeClear, CollectionChange.Priority.IdentifiedCollectionCache); _communicator.CollectionChange.Subscribe(CollectionChangeClear, CollectionChange.Priority.IdentifiedCollectionCache);
_clientState.TerritoryChanged += TerritoryClear; _clientState.TerritoryChanged += TerritoryClear;
_events.CharacterDestructor += OnCharacterDestruct; _events.CharacterDestructor += OnCharacterDestruct;
} }
public ResolveData Set(ModCollection collection, ActorIdentifier identifier, GameObject* data) public ResolveData Set(ModCollection collection, ActorIdentifier identifier, GameObject* data)
@ -61,8 +61,8 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A
public void Dispose() public void Dispose()
{ {
_communicator.CollectionChange.Unsubscribe(CollectionChangeClear); _communicator.CollectionChange.Unsubscribe(CollectionChangeClear);
_clientState.TerritoryChanged -= TerritoryClear; _clientState.TerritoryChanged -= TerritoryClear;
_events.CharacterDestructor -= OnCharacterDestruct; _events.CharacterDestructor -= OnCharacterDestruct;
} }
public IEnumerator<(nint Address, ActorIdentifier Identifier, ModCollection Collection)> GetEnumerator() public IEnumerator<(nint Address, ActorIdentifier Identifier, ModCollection Collection)> GetEnumerator()

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

@ -1,6 +1,6 @@
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -16,7 +16,7 @@ public class ResourceTree
public readonly nint DrawObjectAddress; public readonly nint DrawObjectAddress;
public readonly bool PlayerRelated; public readonly bool PlayerRelated;
public readonly string CollectionName; public readonly string CollectionName;
public readonly List<ResourceNode> Nodes; public readonly List<ResourceNode> Nodes;
public readonly HashSet<ResourceNode> FlatNodes; public readonly HashSet<ResourceNode> FlatNodes;
public int ModelId; public int ModelId;
@ -26,11 +26,11 @@ public class ResourceTree
public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName) public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
{ {
Name = name; Name = name;
GameObjectAddress = gameObjectAddress; GameObjectAddress = gameObjectAddress;
DrawObjectAddress = drawObjectAddress; DrawObjectAddress = drawObjectAddress;
PlayerRelated = playerRelated; PlayerRelated = playerRelated;
CollectionName = collectionName; CollectionName = collectionName;
Nodes = new List<ResourceNode>(); Nodes = new List<ResourceNode>();
FlatNodes = new HashSet<ResourceNode>(); FlatNodes = new HashSet<ResourceNode>();
} }
@ -42,7 +42,7 @@ public class ResourceTree
// var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 ); // var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 );
ModelId = character->CharacterData.ModelCharaId; ModelId = character->CharacterData.ModelCharaId;
CustomizeData = character->DrawData.CustomizeData; CustomizeData = character->DrawData.CustomizeData;
RaceCode = model->GetModelType() == CharacterBase.ModelType.Human ? (GenderRace) ((Human*)model)->RaceSexId : GenderRace.Unknown; RaceCode = model->GetModelType() == CharacterBase.ModelType.Human ? (GenderRace)((Human*)model)->RaceSexId : GenderRace.Unknown;
for (var i = 0; i < model->SlotCount; ++i) for (var i = 0; i < model->SlotCount; ++i)
{ {
@ -60,8 +60,8 @@ public class ResourceTree
var mdlNode = context.CreateNodeFromRenderModel(mdl); var mdlNode = context.CreateNodeFromRenderModel(mdl);
if (mdlNode != null) if (mdlNode != null)
Nodes.Add(globalContext.WithUiData ? mdlNode.WithUIData(mdlNode.Name ?? $"Model #{i}", mdlNode.Icon) : mdlNode); Nodes.Add(globalContext.WithUiData ? mdlNode.WithUIData(mdlNode.Name ?? $"Model #{i}", mdlNode.Icon) : mdlNode);
} }
AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton); AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc) if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc)
@ -100,8 +100,8 @@ public class ResourceTree
subObjectNodes.Add(globalContext.WithUiData subObjectNodes.Add(globalContext.WithUiData
? mdlNode.WithUIData(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}", mdlNode.Icon) ? mdlNode.WithUIData(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}", mdlNode.Icon)
: mdlNode); : mdlNode);
} }
AddSkeleton(subObjectNodes, subObjectContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, "); AddSkeleton(subObjectNodes, subObjectContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, ");
subObject = (CharacterBase*)subObject->DrawObject.Object.NextSiblingObject; subObject = (CharacterBase*)subObject->DrawObject.Object.NextSiblingObject;
@ -119,19 +119,21 @@ public class ResourceTree
var legacyDecalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->LegacyBodyDecal); var legacyDecalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->LegacyBodyDecal);
if (legacyDecalNode != null) if (legacyDecalNode != null)
Nodes.Add(globalContext.WithUiData ? legacyDecalNode.WithUIData(legacyDecalNode.Name ?? "Legacy Body Decal", legacyDecalNode.Icon) : legacyDecalNode); Nodes.Add(globalContext.WithUiData
} ? legacyDecalNode.WithUIData(legacyDecalNode.Name ?? "Legacy Body Decal", legacyDecalNode.Icon)
: legacyDecalNode);
private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context, Skeleton* skeleton, string prefix = "") }
{
if (skeleton == null) private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context, Skeleton* skeleton, string prefix = "")
return; {
if (skeleton == null)
for (var i = 0; i < skeleton->PartialSkeletonCount; ++i) return;
{
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]); for (var i = 0; i < skeleton->PartialSkeletonCount; ++i)
if (sklbNode != null) {
nodes.Add(context.WithUiData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode); var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
} if (sklbNode != null)
nodes.Add(context.WithUiData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode);
}
} }
} }

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,16 +31,17 @@ public unsafe class SafeTextureHandle : SafeHandle
} }
public static SafeTextureHandle CreateInvalid() public static SafeTextureHandle CreateInvalid()
=> new(null, incRef: false); => new(null, false);
protected override bool ReleaseHandle() protected override bool ReleaseHandle()
{ {
nint handle; nint handle;
lock (this) lock (this)
{ {
handle = this.handle; handle = this.handle;
this.handle = 0; this.handle = 0;
} }
if (handle != 0) if (handle != 0)
TextureUtility.DecRef((Texture*)handle); TextureUtility.DecRef((Texture*)handle);

View file

@ -14,7 +14,7 @@ public sealed unsafe class DecalReverter : IDisposable
public static readonly Utf8GamePath TransparentPath = public static readonly Utf8GamePath TransparentPath =
Utf8GamePath.FromSpan("chara/common/texture/transparent.tex"u8, out var p) ? p : Utf8GamePath.Empty; Utf8GamePath.FromSpan("chara/common/texture/transparent.tex"u8, out var p) ? p : Utf8GamePath.Empty;
private readonly CharacterUtility _utility; private readonly CharacterUtility _utility;
private readonly Structs.TextureResourceHandle* _decal; private readonly Structs.TextureResourceHandle* _decal;
private readonly Structs.TextureResourceHandle* _transparent; private readonly Structs.TextureResourceHandle* _transparent;
@ -22,10 +22,10 @@ public sealed unsafe class DecalReverter : IDisposable
{ {
_utility = utility; _utility = utility;
var ptr = _utility.Address; var ptr = _utility.Address;
_decal = null; _decal = null;
_transparent = null; _transparent = null;
if (!config.EnableMods) if (!config.EnableMods)
return; return;
if (doDecal) if (doDecal)
{ {

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

@ -100,10 +100,10 @@ public unsafe partial class RedrawService
public sealed unsafe partial class RedrawService : IDisposable public sealed unsafe partial class RedrawService : IDisposable
{ {
private readonly Framework _framework; private readonly Framework _framework;
private readonly IObjectTable _objects; private readonly IObjectTable _objects;
private readonly ITargetManager _targets; private readonly ITargetManager _targets;
private readonly Condition _conditions; private readonly Condition _conditions;
private readonly List<int> _queue = new(100); private readonly List<int> _queue = new(100);
private readonly List<int> _afterGPoseQueue = new(GPoseSlots); private readonly List<int> _afterGPoseQueue = new(GPoseSlots);
@ -207,7 +207,7 @@ public sealed unsafe partial class RedrawService : IDisposable
return; return;
_targets.Target = actor; _targets.Target = actor;
_target = -1; _target = -1;
} }
private void HandleRedraw() private void HandleRedraw()

View file

@ -1,6 +1,6 @@
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using Penumbra.GameData; using Penumbra.GameData;
namespace Penumbra.Interop.Services; namespace Penumbra.Interop.Services;
public unsafe class ResidentResourceManager public unsafe class ResidentResourceManager
@ -36,4 +36,4 @@ public unsafe class ResidentResourceManager
LoadPlayerResources.Invoke(Address); LoadPlayerResources.Invoke(Address);
} }
} }
} }

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,53 +48,53 @@ public unsafe struct CharacterUtilityData
1404 => accessory ? MetaIndex.Eqdp1404Acc : MetaIndex.Eqdp1404, 1404 => accessory ? MetaIndex.Eqdp1404Acc : MetaIndex.Eqdp1404,
9104 => accessory ? MetaIndex.Eqdp9104Acc : MetaIndex.Eqdp9104, 9104 => accessory ? MetaIndex.Eqdp9104Acc : MetaIndex.Eqdp9104,
9204 => accessory ? MetaIndex.Eqdp9204Acc : MetaIndex.Eqdp9204, 9204 => accessory ? MetaIndex.Eqdp9204Acc : MetaIndex.Eqdp9204,
_ => ( MetaIndex )( -1 ), _ => (MetaIndex)(-1),
}; };
[FieldOffset( 0 )] [FieldOffset(0)]
public void* VTable; public void* VTable;
[FieldOffset( 8 )] [FieldOffset(8)]
public fixed ulong Resources[TotalNumResources]; public fixed ulong Resources[TotalNumResources];
[FieldOffset( 8 + ( int )MetaIndex.Eqp * 8 )] [FieldOffset(8 + (int)MetaIndex.Eqp * 8)]
public ResourceHandle* EqpResource; public ResourceHandle* EqpResource;
[FieldOffset( 8 + ( int )MetaIndex.Gmp * 8 )] [FieldOffset(8 + (int)MetaIndex.Gmp * 8)]
public ResourceHandle* GmpResource; public ResourceHandle* GmpResource;
public ResourceHandle* Resource( int idx ) public ResourceHandle* Resource(int idx)
=> ( ResourceHandle* )Resources[ idx ]; => (ResourceHandle*)Resources[idx];
public ResourceHandle* Resource( MetaIndex idx ) public ResourceHandle* Resource(MetaIndex idx)
=> Resource( ( int )idx ); => Resource((int)idx);
public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory ) public ResourceHandle* EqdpResource(GenderRace raceCode, bool accessory)
=> Resource( ( int )EqdpIdx( raceCode, accessory ) ); => Resource((int)EqdpIdx(raceCode, accessory));
[FieldOffset( 8 + ( int )MetaIndex.HumanCmp * 8 )] [FieldOffset(8 + (int)MetaIndex.HumanCmp * 8)]
public ResourceHandle* HumanCmpResource; public ResourceHandle* HumanCmpResource;
[FieldOffset( 8 + ( int )MetaIndex.FaceEst * 8 )] [FieldOffset(8 + (int)MetaIndex.FaceEst * 8)]
public ResourceHandle* FaceEstResource; public ResourceHandle* FaceEstResource;
[FieldOffset( 8 + ( int )MetaIndex.HairEst * 8 )] [FieldOffset(8 + (int)MetaIndex.HairEst * 8)]
public ResourceHandle* HairEstResource; public ResourceHandle* HairEstResource;
[FieldOffset( 8 + ( int )MetaIndex.BodyEst * 8 )] [FieldOffset(8 + (int)MetaIndex.BodyEst * 8)]
public ResourceHandle* BodyEstResource; public ResourceHandle* BodyEstResource;
[FieldOffset( 8 + ( int )MetaIndex.HeadEst * 8 )] [FieldOffset(8 + (int)MetaIndex.HeadEst * 8)]
public ResourceHandle* HeadEstResource; public ResourceHandle* HeadEstResource;
[FieldOffset( 8 + IndexTransparentTex * 8 )] [FieldOffset(8 + IndexTransparentTex * 8)]
public TextureResourceHandle* TransparentTexResource; public TextureResourceHandle* TransparentTexResource;
[FieldOffset( 8 + IndexDecalTex * 8 )] [FieldOffset(8 + IndexDecalTex * 8)]
public TextureResourceHandle* DecalTexResource; public TextureResourceHandle* DecalTexResource;
[FieldOffset( 8 + IndexSkinShpk * 8 )] [FieldOffset(8 + IndexSkinShpk * 8)]
public ResourceHandle* SkinShpkResource; public ResourceHandle* SkinShpkResource;
// not included resources have no known use case. // not included resources have no known use case.
} }

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

@ -9,4 +9,4 @@ public enum DrawState : uint
MaybeCulled = 0x00_00_04_00, MaybeCulled = 0x00_00_04_00,
MaybeHiddenMinion = 0x00_00_80_00, MaybeHiddenMinion = 0x00_00_80_00,
MaybeHiddenSummon = 0x00_80_00_00, MaybeHiddenSummon = 0x00_80_00_00,
} }

View file

@ -8,4 +8,4 @@ public enum FileMode : byte
// Probably debug options only. // Probably debug options only.
LoadIndexResource = 0xA, // load index/index2 LoadIndexResource = 0xA, // load index/index2
LoadSqPackResource = 0xB, LoadSqPackResource = 0xB,
} }

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,45 +2,46 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
namespace Penumbra.Interop.Structs; namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit, Size = 0x40 )] [StructLayout(LayoutKind.Explicit, Size = 0x40)]
public unsafe struct Material public unsafe struct Material
{ {
[FieldOffset( 0x10 )] [FieldOffset(0x10)]
public MtrlResource* ResourceHandle; public MtrlResource* ResourceHandle;
[FieldOffset( 0x18 )] [FieldOffset(0x18)]
public uint ShaderPackageFlags; public uint ShaderPackageFlags;
[FieldOffset( 0x20 )] [FieldOffset(0x20)]
public uint* ShaderKeys; public uint* ShaderKeys;
public int ShaderKeyCount public int ShaderKeyCount
=> (int)((uint*)Textures - ShaderKeys); => (int)((uint*)Textures - ShaderKeys);
[FieldOffset( 0x28 )] [FieldOffset(0x28)]
public ConstantBuffer* MaterialParameter; public ConstantBuffer* MaterialParameter;
[FieldOffset( 0x30 )] [FieldOffset(0x30)]
public TextureEntry* Textures; public TextureEntry* Textures;
[FieldOffset( 0x38 )] [FieldOffset(0x38)]
public ushort TextureCount; public ushort TextureCount;
public Texture* Texture( int index ) => Textures[index].ResourceHandle->KernelTexture; public Texture* Texture(int index)
=> Textures[index].ResourceHandle->KernelTexture;
[StructLayout( LayoutKind.Explicit, Size = 0x18 )] [StructLayout(LayoutKind.Explicit, Size = 0x18)]
public struct TextureEntry public struct TextureEntry
{ {
[FieldOffset( 0x00 )] [FieldOffset(0x00)]
public uint Id; public uint Id;
[FieldOffset( 0x08 )] [FieldOffset(0x08)]
public TextureResourceHandle* ResourceHandle; public TextureResourceHandle* ResourceHandle;
[FieldOffset( 0x10 )] [FieldOffset(0x10)]
public uint SamplerFlags; public uint SamplerFlags;
} }
public ReadOnlySpan<TextureEntry> TextureSpan public ReadOnlySpan<TextureEntry> TextureSpan
=> new(Textures, TextureCount); => new(Textures, TextureCount);
} }

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

@ -101,8 +101,8 @@ public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
if (FileIndex() == (MetaIndex)(-1)) if (FileIndex() == (MetaIndex)(-1))
return false; return false;
// No check for set id. // No check for set id.
return true; return true;
} }
} }

View file

@ -9,73 +9,71 @@ using SharpCompress.Common;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout( LayoutKind.Sequential, Pack = 1 )] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation > public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation>
{ {
[JsonConverter( typeof( ForceNumericFlagEnumConverter ) )] [JsonConverter(typeof(ForceNumericFlagEnumConverter))]
public EqpEntry Entry { get; private init; } public EqpEntry Entry { get; private init; }
public SetId SetId { get; private init; } public SetId SetId { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public EquipSlot Slot { get; private init; } public EquipSlot Slot { get; private init; }
[JsonConstructor] [JsonConstructor]
public EqpManipulation( EqpEntry entry, EquipSlot slot, SetId setId ) public EqpManipulation(EqpEntry entry, EquipSlot slot, SetId setId)
{ {
Slot = slot; Slot = slot;
SetId = setId; SetId = setId;
Entry = Eqp.Mask( slot ) & entry; Entry = Eqp.Mask(slot) & entry;
} }
public EqpManipulation Copy( EqpEntry entry ) public EqpManipulation Copy(EqpEntry entry)
=> new(entry, Slot, SetId); => new(entry, Slot, SetId);
public override string ToString() public override string ToString()
=> $"Eqp - {SetId} - {Slot}"; => $"Eqp - {SetId} - {Slot}";
public bool Equals( EqpManipulation other ) public bool Equals(EqpManipulation other)
=> Slot == other.Slot => Slot == other.Slot
&& SetId == other.SetId; && SetId == other.SetId;
public override bool Equals( object? obj ) public override bool Equals(object? obj)
=> obj is EqpManipulation other && Equals( other ); => obj is EqpManipulation other && Equals(other);
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine( ( int )Slot, SetId ); => HashCode.Combine((int)Slot, SetId);
public int CompareTo( EqpManipulation other ) public int CompareTo(EqpManipulation other)
{ {
var set = SetId.Id.CompareTo( other.SetId.Id ); var set = SetId.Id.CompareTo(other.SetId.Id);
return set != 0 ? set : Slot.CompareTo( other.Slot ); return set != 0 ? set : Slot.CompareTo(other.Slot);
} }
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> MetaIndex.Eqp; => MetaIndex.Eqp;
public bool Apply( ExpandedEqpFile file ) public bool Apply(ExpandedEqpFile file)
{ {
var entry = file[ SetId ]; var entry = file[SetId];
var mask = Eqp.Mask( Slot ); var mask = Eqp.Mask(Slot);
if( ( entry & mask ) == Entry ) if ((entry & mask) == Entry)
{
return false; return false;
}
file[ SetId ] = ( entry & ~mask ) | Entry; file[SetId] = (entry & ~mask) | Entry;
return true; return true;
} }
public bool Validate() public bool Validate()
{ {
var mask = Eqp.Mask(Slot); var mask = Eqp.Mask(Slot);
if (mask == 0) if (mask == 0)
return false; return false;
if ((Entry & mask) != Entry) if ((Entry & mask) != Entry)
return false; return false;
// No check for set id. // No check for set id.
return true; return true;
} }
} }

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 ); var r = Race.CompareTo(other.Race);
if( r != 0 ) if (r != 0)
{
return r; return r;
}
var g = Gender.CompareTo( other.Gender ); var g = Gender.CompareTo(other.Gender);
if( g != 0 ) if (g != 0)
{
return g; return g;
}
var s = Slot.CompareTo( other.Slot ); var s = Slot.CompareTo(other.Slot);
return s != 0 ? s : SetId.Id.CompareTo( other.SetId.Id ); return s != 0 ? s : SetId.Id.CompareTo(other.SetId.Id);
} }
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> ( MetaIndex )Slot; => (MetaIndex)Slot;
public bool Apply( EstFile file ) public bool Apply(EstFile file)
{ {
return file.SetEntry( Names.CombinedRace( Gender, Race ), SetId.Id, Entry ) switch return file.SetEntry(Names.CombinedRace(Gender, Race), SetId.Id, Entry) switch
{ {
EstFile.EstEntryChange.Unchanged => false, EstFile.EstEntryChange.Unchanged => false,
EstFile.EstEntryChange.Changed => true, EstFile.EstEntryChange.Changed => true,
@ -109,7 +105,8 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
return false; return false;
if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown) if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown)
return false; return false;
// No known check for set id or entry.
// No known check for set id or entry.
return true; return true;
} }
} }

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 ]; var entry = file[SetId];
if( entry == Entry ) if (entry == Entry)
{
return false; return false;
}
file[ SetId ] = Entry; file[SetId] = Entry;
return true; return true;
} }
public bool Validate() public bool Validate()
{
// No known conditions. // No known conditions.
return true; => true;
} }
}

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

@ -59,7 +59,7 @@ public class DuplicateManager
public void Clear() public void Clear()
{ {
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
Worker = Task.CompletedTask; Worker = Task.CompletedTask;
_duplicates.Clear(); _duplicates.Clear();
SavedSpace = 0; SavedSpace = 0;
} }

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

@ -7,14 +7,14 @@ public interface IMod
{ {
LowerString Name { get; } LowerString Name { get; }
public int Index { get; } public int Index { get; }
public int Priority { get; } public int Priority { get; }
public ISubMod Default { get; } public ISubMod Default { get; }
public IReadOnlyList< IModGroup > Groups { get; } public IReadOnlyList<IModGroup> Groups { get; }
public IEnumerable< SubMod > AllSubMods { get; } public IEnumerable<SubMod> AllSubMods { get; }
// Cache // Cache
public int TotalManipulations { get; } public int TotalManipulations { get; }
} }

View file

@ -1,25 +1,26 @@
using System.IO.Compression; using System.IO.Compression;
using OtterGui.Tasks; using OtterGui.Tasks;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
namespace Penumbra.Mods.Editor; namespace Penumbra.Mods.Editor;
/// <summary> Utility to create and apply a zipped backup of a mod. </summary> /// <summary> Utility to create and apply a zipped backup of a mod. </summary>
public class ModBackup public class ModBackup
{ {
/// <summary> Set when reading Config and migrating from v4 to v5. </summary> /// <summary> Set when reading Config and migrating from v4 to v5. </summary>
public static bool MigrateModBackups = false; public static bool MigrateModBackups = false;
public static bool CreatingBackup { get; private set; } public static bool CreatingBackup { get; private set; }
private readonly Mod _mod; private readonly Mod _mod;
public readonly string Name; public readonly string Name;
public readonly bool Exists; public readonly bool Exists;
public ModBackup(ModExportManager modExportManager, Mod mod) public ModBackup(ModExportManager modExportManager, Mod mod)
{ {
_mod = mod; _mod = mod;
Name = Path.Combine(modExportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp"; Name = Path.Combine(modExportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp";
Exists = File.Exists(Name); Exists = File.Exists(Name);
} }
/// <summary> Migrate file extensions. </summary> /// <summary> Migrate file extensions. </summary>

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;
@ -7,7 +8,7 @@ namespace Penumbra.Mods;
public class ModFileEditor public class ModFileEditor
{ {
private readonly ModFileCollection _files; private readonly ModFileCollection _files;
private readonly ModManager _modManager; private readonly ModManager _modManager;
public bool Changes { get; private set; } public bool Changes { get; private set; }

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;
@ -174,7 +175,7 @@ public class ModMerger : IDisposable
ret = new FullPath(MergeToMod!.ModPath, relPath); ret = new FullPath(MergeToMod!.ModPath, relPath);
return true; return true;
} }
foreach (var originalOption in mergeOptions) foreach (var originalOption in mergeOptions)
{ {
foreach (var manip in originalOption.Manipulations) foreach (var manip in originalOption.Manipulations)

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,11 +1,12 @@
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Util; using Penumbra.Util;
public class ModSwapEditor public class ModSwapEditor
{ {
private readonly ModManager _modManager; private readonly ModManager _modManager;
private readonly Dictionary<Utf8GamePath, FullPath> _swaps = new(); private readonly Dictionary<Utf8GamePath, FullPath> _swaps = new();
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps

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

@ -249,10 +249,10 @@ public static class EquipmentSwap
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom, private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom,
SetId idFrom, SetId idTo, Variant variantFrom) SetId idFrom, SetId idTo, Variant variantFrom)
{ {
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default); var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry); var imc = new ImcFile(manager, entry);
EquipItem[] items; EquipItem[] items;
Variant[] variants; Variant[] variants;
if (idFrom == idTo) if (idFrom == idTo)
{ {
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray(); items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
@ -264,8 +264,9 @@ public static class EquipmentSwap
else else
{ {
items = identifier.Identify(slotFrom.IsEquipment() items = identifier.Identify(slotFrom.IsEquipment()
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom) ? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>().ToArray(); : GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>()
.ToArray();
variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray(); variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray();
} }
@ -283,11 +284,13 @@ public static class EquipmentSwap
return new MetaSwap(manips, manipFrom, manipTo); return new MetaSwap(manips, manipFrom, manipTo);
} }
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo); => CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips,
EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo,
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
{ {
@ -401,7 +404,8 @@ public static class EquipmentSwap
ref MtrlFile.Texture texture, ref bool dataWasChanged) ref MtrlFile.Texture texture, ref bool dataWasChanged)
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged); => CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom,
EquipSlot slotTo, SetId idFrom,
SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged) SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
{ {
var path = texture.Path; var path = texture.Path;
@ -428,13 +432,15 @@ public static class EquipmentSwap
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path); return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path);
} }
public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName, ref bool dataWasChanged) public static FileSwap CreateShader(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string shaderName,
ref bool dataWasChanged)
{ {
var path = $"shader/sm5/shpk/{shaderName}"; var path = $"shader/sm5/shpk/{shaderName}";
return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path); return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path);
} }
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath, ref bool dataWasChanged) public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath,
ref bool dataWasChanged)
{ {
var oldPath = filePath; var oldPath = filePath;
filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}"); filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}");

View file

@ -20,47 +20,45 @@ public static class ItemSwap
{ {
public readonly ResourceType Type; public readonly ResourceType Type;
public MissingFileException( ResourceType type, object path ) public MissingFileException(ResourceType type, object path)
: base($"Could not load {type} File Data for \"{path}\".") : base($"Could not load {type} File Data for \"{path}\".")
=> Type = type; => Type = type;
} }
private static bool LoadFile( MetaFileManager manager, FullPath path, out byte[] data ) private static bool LoadFile(MetaFileManager manager, FullPath path, out byte[] data)
{ {
if( path.FullName.Length > 0 ) if (path.FullName.Length > 0)
{
try try
{ {
if( path.IsRooted ) if (path.IsRooted)
{ {
data = File.ReadAllBytes( path.FullName ); data = File.ReadAllBytes(path.FullName);
return true; return true;
} }
var file = manager.GameData.GetFile( path.InternalName.ToString() ); var file = manager.GameData.GetFile(path.InternalName.ToString());
if( file != null ) if (file != null)
{ {
data = file.Data; data = file.Data;
return true; return true;
} }
} }
catch( Exception e ) catch (Exception e)
{ {
Penumbra.Log.Debug( $"Could not load file {path}:\n{e}" ); Penumbra.Log.Debug($"Could not load file {path}:\n{e}");
} }
}
data = Array.Empty< byte >(); data = Array.Empty<byte>();
return false; return false;
} }
public class GenericFile : IWritable public class GenericFile : IWritable
{ {
public readonly byte[] Data; public readonly byte[] Data;
public bool Valid { get; } public bool Valid { get; }
public GenericFile( MetaFileManager manager, FullPath path ) public GenericFile(MetaFileManager manager, FullPath path)
=> Valid = LoadFile( manager, path, out Data ); => Valid = LoadFile(manager, path, out Data);
public byte[] Write() public byte[] Write()
=> Data; => Data;
@ -68,69 +66,67 @@ public static class ItemSwap
public static readonly GenericFile Invalid = new(null!, FullPath.Empty); public static readonly GenericFile Invalid = new(null!, FullPath.Empty);
} }
public static bool LoadFile( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out GenericFile? file ) public static bool LoadFile(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out GenericFile? file)
{ {
file = new GenericFile( manager, path ); file = new GenericFile(manager, path);
if( file.Valid ) if (file.Valid)
{
return true; return true;
}
file = null; file = null;
return false; return false;
} }
public static bool LoadMdl( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MdlFile? file ) public static bool LoadMdl(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out MdlFile? file)
{ {
try try
{ {
if( LoadFile( manager, path, out byte[] data ) ) if (LoadFile(manager, path, out byte[] data))
{ {
file = new MdlFile( data ); file = new MdlFile(data);
return true; return true;
} }
} }
catch( Exception e ) catch (Exception e)
{ {
Penumbra.Log.Debug( $"Could not parse file {path} to Mdl:\n{e}" ); Penumbra.Log.Debug($"Could not parse file {path} to Mdl:\n{e}");
} }
file = null; file = null;
return false; return false;
} }
public static bool LoadMtrl(MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MtrlFile? file ) public static bool LoadMtrl(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out MtrlFile? file)
{ {
try try
{ {
if( LoadFile( manager, path, out byte[] data ) ) if (LoadFile(manager, path, out byte[] data))
{ {
file = new MtrlFile( data ); file = new MtrlFile(data);
return true; return true;
} }
} }
catch( Exception e ) catch (Exception e)
{ {
Penumbra.Log.Debug( $"Could not parse file {path} to Mtrl:\n{e}" ); Penumbra.Log.Debug($"Could not parse file {path} to Mtrl:\n{e}");
} }
file = null; file = null;
return false; return false;
} }
public static bool LoadAvfx( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out AvfxFile? file ) public static bool LoadAvfx(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out AvfxFile? file)
{ {
try try
{ {
if( LoadFile( manager, path, out byte[] data ) ) if (LoadFile(manager, path, out byte[] data))
{ {
file = new AvfxFile( data ); file = new AvfxFile(data);
return true; return true;
} }
} }
catch( Exception e ) catch (Exception e)
{ {
Penumbra.Log.Debug( $"Could not parse file {path} to Avfx:\n{e}" ); Penumbra.Log.Debug($"Could not parse file {path} to Avfx:\n{e}");
} }
file = null; file = null;
@ -138,40 +134,41 @@ public static class ItemSwap
} }
public static FileSwap CreatePhyb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry ) public static FileSwap CreatePhyb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
GenderRace race, ushort estEntry)
{ {
var phybPath = GamePaths.Skeleton.Phyb.Path( race, EstManipulation.ToName( type ), estEntry ); var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry);
return FileSwap.CreateSwap( manager, ResourceType.Phyb, redirections, phybPath, phybPath ); return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath);
} }
public static FileSwap CreateSklb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry ) public static FileSwap CreateSklb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstManipulation.EstType type,
GenderRace race, ushort estEntry)
{ {
var sklbPath = GamePaths.Skeleton.Sklb.Path( race, EstManipulation.ToName( type ), estEntry ); var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry);
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath ); return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
} }
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks> /// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
public static MetaSwap? CreateEst( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EstManipulation.EstType type, public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl ) Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl)
{ {
if( type == 0 ) if (type == 0)
{
return null; return null;
}
var (gender, race) = genderRace.Split(); var (gender, race) = genderRace.Split();
var fromDefault = new EstManipulation( gender, race, type, idFrom, EstFile.GetDefault( manager, type, genderRace, idFrom ) ); var fromDefault = new EstManipulation(gender, race, type, idFrom, EstFile.GetDefault(manager, type, genderRace, idFrom));
var toDefault = new EstManipulation( gender, race, type, idTo, EstFile.GetDefault( manager, type, genderRace, idTo ) ); var toDefault = new EstManipulation(gender, race, type, idTo, EstFile.GetDefault(manager, type, genderRace, idTo));
var est = new MetaSwap( manips, fromDefault, toDefault ); var est = new MetaSwap(manips, fromDefault, toDefault);
if( ownMdl && est.SwapApplied.Est.Entry >= 2 ) if (ownMdl && est.SwapApplied.Est.Entry >= 2)
{ {
var phyb = CreatePhyb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry ); var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
est.ChildSwaps.Add( phyb ); est.ChildSwaps.Add(phyb);
var sklb = CreateSklb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry ); var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
est.ChildSwaps.Add( sklb ); est.ChildSwaps.Add(sklb);
} }
else if( est.SwapAppliedIsDefault ) else if (est.SwapAppliedIsDefault)
{ {
return null; return null;
} }
@ -179,57 +176,55 @@ public static class ItemSwap
return est; return est;
} }
public static int GetStableHashCode( this string str ) public static int GetStableHashCode(this string str)
{ {
unchecked unchecked
{ {
var hash1 = 5381; var hash1 = 5381;
var hash2 = hash1; var hash2 = hash1;
for( var i = 0; i < str.Length && str[ i ] != '\0'; i += 2 ) for (var i = 0; i < str.Length && str[i] != '\0'; i += 2)
{ {
hash1 = ( ( hash1 << 5 ) + hash1 ) ^ str[ i ]; hash1 = ((hash1 << 5) + hash1) ^ str[i];
if( i == str.Length - 1 || str[ i + 1 ] == '\0' ) if (i == str.Length - 1 || str[i + 1] == '\0')
{
break; break;
}
hash2 = ( ( hash2 << 5 ) + hash2 ) ^ str[ i + 1 ]; hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
} }
return hash1 + hash2 * 1566083941; return hash1 + hash2 * 1566083941;
} }
} }
public static string ReplaceAnyId( string path, char idType, SetId id, bool condition = true ) public static string ReplaceAnyId(string path, char idType, SetId id, bool condition = true)
=> condition => condition
? Regex.Replace( path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}" ) ? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
: path; : path;
public static string ReplaceAnyRace( string path, GenderRace to, bool condition = true ) public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
=> ReplaceAnyId( path, 'c', ( ushort )to, condition ); => ReplaceAnyId(path, 'c', (ushort)to, condition);
public static string ReplaceAnyBody( string path, BodySlot slot, SetId to, bool condition = true ) public static string ReplaceAnyBody(string path, BodySlot slot, SetId to, bool condition = true)
=> ReplaceAnyId( path, slot.ToAbbreviation(), to, condition ); => ReplaceAnyId(path, slot.ToAbbreviation(), to, condition);
public static string ReplaceId( string path, char type, SetId idFrom, SetId idTo, bool condition = true ) public static string ReplaceId(string path, char type, SetId idFrom, SetId idTo, bool condition = true)
=> condition => condition
? path.Replace( $"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}" ) ? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
: path; : path;
public static string ReplaceSlot( string path, EquipSlot from, EquipSlot to, bool condition = true ) public static string ReplaceSlot(string path, EquipSlot from, EquipSlot to, bool condition = true)
=> condition => condition
? path.Replace( $"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_" ) ? path.Replace($"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_")
: path; : path;
public static string ReplaceRace( string path, GenderRace from, GenderRace to, bool condition = true ) public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
=> ReplaceId( path, 'c', ( ushort )from, ( ushort )to, condition ); => ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
public static string ReplaceBody( string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true ) public static string ReplaceBody(string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true)
=> ReplaceId( path, slot.ToAbbreviation(), idFrom, idTo, condition ); => ReplaceId(path, slot.ToAbbreviation(), idFrom, idTo, condition);
public static string AddSuffix( string path, string ext, string suffix, bool condition = true ) public static string AddSuffix(string path, string ext, string suffix, bool condition = true)
=> condition => condition
? path.Replace( ext, suffix + ext ) ? path.Replace(ext, suffix + ext)
: path; : path;
} }

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;
@ -13,18 +14,18 @@ namespace Penumbra.Mods.ItemSwap;
public class ItemSwapContainer public class ItemSwapContainer
{ {
private readonly MetaFileManager _manager; private readonly MetaFileManager _manager;
private readonly IdentifierService _identifier; private readonly IdentifierService _identifier;
private Dictionary< Utf8GamePath, FullPath > _modRedirections = new(); private Dictionary<Utf8GamePath, FullPath> _modRedirections = new();
private HashSet< MetaManipulation > _modManipulations = new(); private HashSet<MetaManipulation> _modManipulations = new();
public IReadOnlyDictionary< Utf8GamePath, FullPath > ModRedirections public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections
=> _modRedirections; => _modRedirections;
public IReadOnlySet< MetaManipulation > ModManipulations public IReadOnlySet<MetaManipulation> ModManipulations
=> _modManipulations; => _modManipulations;
public readonly List< Swap > Swaps = new(); public readonly List<Swap> Swaps = new();
public bool Loaded { get; private set; } public bool Loaded { get; private set; }
@ -40,72 +41,69 @@ public class ItemSwapContainer
NoSwaps, NoSwaps,
} }
public bool WriteMod( ModManager manager, Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null, int groupIndex = -1, int optionIndex = 0 ) public bool WriteMod(ModManager manager, Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null,
int groupIndex = -1, int optionIndex = 0)
{ {
var convertedManips = new HashSet< MetaManipulation >( Swaps.Count ); var convertedManips = new HashSet<MetaManipulation>(Swaps.Count);
var convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count ); var convertedFiles = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
var convertedSwaps = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count ); var convertedSwaps = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
directory ??= mod.ModPath; directory ??= mod.ModPath;
try try
{ {
foreach( var swap in Swaps.SelectMany( s => s.WithChildren() ) ) foreach (var swap in Swaps.SelectMany(s => s.WithChildren()))
{ {
switch( swap ) switch (swap)
{ {
case FileSwap file: case FileSwap file:
// Skip, nothing to do // Skip, nothing to do
if( file.SwapToModdedEqualsOriginal ) if (file.SwapToModdedEqualsOriginal)
{
continue; continue;
}
if( writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged ) if (writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged)
{ {
convertedSwaps.TryAdd( file.SwapFromRequestPath, file.SwapToModded ); convertedSwaps.TryAdd(file.SwapFromRequestPath, file.SwapToModded);
} }
else else
{ {
var path = file.GetNewPath( directory.FullName ); var path = file.GetNewPath(directory.FullName);
var bytes = file.FileData.Write(); var bytes = file.FileData.Write();
Directory.CreateDirectory( Path.GetDirectoryName( path )! ); Directory.CreateDirectory(Path.GetDirectoryName(path)!);
_manager.Compactor.WriteAllBytes( path, bytes ); _manager.Compactor.WriteAllBytes(path, bytes);
convertedFiles.TryAdd( file.SwapFromRequestPath, new FullPath( path ) ); convertedFiles.TryAdd(file.SwapFromRequestPath, new FullPath(path));
} }
break; break;
case MetaSwap meta: case MetaSwap meta:
if( !meta.SwapAppliedIsDefault ) if (!meta.SwapAppliedIsDefault)
{ convertedManips.Add(meta.SwapApplied);
convertedManips.Add( meta.SwapApplied );
}
break; break;
} }
} }
manager.OptionEditor.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles ); manager.OptionEditor.OptionSetFiles(mod, groupIndex, optionIndex, convertedFiles);
manager.OptionEditor.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps ); manager.OptionEditor.OptionSetFileSwaps(mod, groupIndex, optionIndex, convertedSwaps);
manager.OptionEditor.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips ); manager.OptionEditor.OptionSetManipulations(mod, groupIndex, optionIndex, convertedManips);
return true; return true;
} }
catch( Exception e ) catch (Exception e)
{ {
Penumbra.Log.Error( $"Could not write FileSwapContainer to {mod.ModPath}:\n{e}" ); Penumbra.Log.Error($"Could not write FileSwapContainer to {mod.ModPath}:\n{e}");
return false; return false;
} }
} }
public void LoadMod( Mod? mod, ModSettings? settings ) public void LoadMod(Mod? mod, ModSettings? settings)
{ {
Clear(); Clear();
if( mod == null ) if (mod == null)
{ {
_modRedirections = new Dictionary< Utf8GamePath, FullPath >(); _modRedirections = new Dictionary<Utf8GamePath, FullPath>();
_modManipulations = new HashSet< MetaManipulation >(); _modManipulations = new HashSet<MetaManipulation>();
} }
else else
{ {
( _modRedirections, _modManipulations ) = ModSettings.GetResolveData( mod, settings ); (_modRedirections, _modManipulations) = ModSettings.GetResolveData(mod, settings);
} }
} }
@ -113,59 +111,61 @@ public class ItemSwapContainer
{ {
_manager = manager; _manager = manager;
_identifier = identifier; _identifier = identifier;
LoadMod( null, null ); LoadMod(null, null);
} }
private Func< Utf8GamePath, FullPath > PathResolver( ModCollection? collection ) private Func<Utf8GamePath, FullPath> PathResolver(ModCollection? collection)
=> collection != null => collection != null
? p => collection.ResolvePath( p ) ?? new FullPath( p ) ? p => collection.ResolvePath(p) ?? new FullPath(p)
: p => ModRedirections.TryGetValue( p, out var path ) ? path : new FullPath( p ); : p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p);
private Func< MetaManipulation, MetaManipulation > MetaResolver( ModCollection? collection ) private Func<MetaManipulation, MetaManipulation> MetaResolver(ModCollection? collection)
{ {
var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _modManipulations; var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _modManipulations;
return m => set.TryGetValue( m, out var a ) ? a : m; return m => set.TryGetValue(m, out var a) ? a : m;
} }
public EquipItem[] LoadEquipment( EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, bool useLeftRing = true ) public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true,
bool useLeftRing = true)
{ {
Swaps.Clear(); Swaps.Clear();
Loaded = false; Loaded = false;
var ret = EquipmentSwap.CreateItemSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), from, to, useRightRing, useLeftRing ); var ret = EquipmentSwap.CreateItemSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
from, to, useRightRing, useLeftRing);
Loaded = true; Loaded = true;
return ret; return ret;
} }
public EquipItem[] LoadTypeSwap( EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null ) public EquipItem[] LoadTypeSwap(EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null)
{ {
Swaps.Clear(); Swaps.Clear();
Loaded = false; Loaded = false;
var ret = EquipmentSwap.CreateTypeSwap( _manager, _identifier.AwaitedService, Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to ); var ret = EquipmentSwap.CreateTypeSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
slotFrom, from, slotTo, to);
Loaded = true; Loaded = true;
return ret; return ret;
} }
public bool LoadCustomization( MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to, ModCollection? collection = null ) public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to,
ModCollection? collection = null)
{ {
var pathResolver = PathResolver( collection ); var pathResolver = PathResolver(collection);
var mdl = CustomizationSwap.CreateMdl( manager, pathResolver, slot, race, from, to ); var mdl = CustomizationSwap.CreateMdl(manager, pathResolver, slot, race, from, to);
var type = slot switch var type = slot switch
{ {
BodySlot.Hair => EstManipulation.EstType.Hair, BodySlot.Hair => EstManipulation.EstType.Hair,
BodySlot.Face => EstManipulation.EstType.Face, BodySlot.Face => EstManipulation.EstType.Face,
_ => ( EstManipulation.EstType )0, _ => (EstManipulation.EstType)0,
}; };
var metaResolver = MetaResolver( collection ); var metaResolver = MetaResolver(collection);
var est = ItemSwap.CreateEst( manager, pathResolver, metaResolver, type, race, from, to, true ); var est = ItemSwap.CreateEst(manager, pathResolver, metaResolver, type, race, from, to, true);
Swaps.Add( mdl ); Swaps.Add(mdl);
if( est != null ) if (est != null)
{ Swaps.Add(est);
Swaps.Add( est );
}
Loaded = true; Loaded = true;
return true; return true;
} }
} }

View file

@ -89,4 +89,4 @@ public class ModExportManager : IDisposable
new ModBackup(this, mod).Move(null, newDirectory.Name); new ModBackup(this, mod).Move(null, newDirectory.Name);
mod.ModPath = newDirectory; mod.ModPath = newDirectory;
} }
} }

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;
@ -16,7 +15,7 @@ public enum NewDirectoryState
Identical, Identical,
Empty, Empty,
} }
/// <summary> Describes the state of a changed mod event. </summary> /// <summary> Describes the state of a changed mod event. </summary>
public enum ModPathChangeType public enum ModPathChangeType
{ {
@ -25,7 +24,7 @@ public enum ModPathChangeType
Moved, Moved,
Reloaded, Reloaded,
StartingReload, StartingReload,
} }
public sealed class ModManager : ModStorage, IDisposable public sealed class ModManager : ModStorage, IDisposable
{ {
@ -46,8 +45,8 @@ public sealed class ModManager : ModStorage, IDisposable
_communicator = communicator; _communicator = communicator;
DataEditor = dataEditor; DataEditor = dataEditor;
OptionEditor = optionEditor; OptionEditor = optionEditor;
Creator = creator; Creator = creator;
SetBaseDirectory(config.ModDirectory, true); SetBaseDirectory(config.ModDirectory, true);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModManager); _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModManager);
DiscoverMods(); DiscoverMods();
} }
@ -242,7 +241,7 @@ public sealed class ModManager : ModStorage, IDisposable
{ {
switch (type) switch (type)
{ {
case ModPathChangeType.Added: case ModPathChangeType.Added:
SetNew(mod); SetNew(mod);
break; break;
case ModPathChangeType.Deleted: case ModPathChangeType.Deleted:

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)
{ {
@ -63,8 +64,8 @@ public static partial class ModMigration
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>() var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
?? new Dictionary<Utf8GamePath, FullPath>(); ?? new Dictionary<Utf8GamePath, FullPath>();
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>(); var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
var priority = 1; var priority = 1;
var seenMetaFiles = new HashSet<FullPath>(); var seenMetaFiles = new HashSet<FullPath>();
foreach (var group in groups.Values) foreach (var group in groups.Values)
ConvertGroup(creator, mod, group, ref priority, seenMetaFiles); ConvertGroup(creator, mod, group, ref priority, seenMetaFiles);
@ -128,8 +129,8 @@ public static partial class ModMigration
var optionPriority = 0; var optionPriority = 0;
var newMultiGroup = new MultiModGroup() var newMultiGroup = new MultiModGroup()
{ {
Name = group.GroupName, Name = group.GroupName,
Priority = priority++, Priority = priority++,
Description = string.Empty, Description = string.Empty,
}; };
mod.Groups.Add(newMultiGroup); mod.Groups.Add(newMultiGroup);
@ -146,8 +147,8 @@ public static partial class ModMigration
var newSingleGroup = new SingleModGroup() var newSingleGroup = new SingleModGroup()
{ {
Name = group.GroupName, Name = group.GroupName,
Priority = priority++, Priority = priority++,
Description = string.Empty, Description = string.Empty,
}; };
mod.Groups.Add(newSingleGroup); mod.Groups.Add(newSingleGroup);

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,9 +113,7 @@ public partial class ModCreator
} }
if (changes) if (changes)
{
_saveService.SaveAllOptionGroups(mod, true); _saveService.SaveAllOptionGroups(mod, true);
}
} }
/// <summary> Load the default option for a given mod.</summary> /// <summary> Load the default option for a given mod.</summary>

Some files were not shown because too many files have changed in this diff Show more