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 @@
[*.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
# Standard properties
charset = utf-8
end_of_line = lf
insert_final_newline = true
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
csharp_style_prefer_top_level_statements = true:silent
[*]
# Microsoft .NET properties
csharp_indent_braces=false
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_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}]
indent_style=tab
indent_size=tab
@ -3601,6 +3587,21 @@ indent_style=space
indent_size=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}]
indent_style=space
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_simplified_interpolation = 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);
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;
}

View file

@ -25,7 +25,7 @@ public class TempModManager : IDisposable
public TempModManager(CommunicatorService communicator)
{
_communicator = communicator;
_communicator = communicator;
_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,
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}.");
mod.SetAll(dict, manips);
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;
if (list == null)
return RedirectResult.NotRegistered;
var removed = list.RemoveAll(m =>
{
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.String.Classes;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
namespace Penumbra.Collections.Cache;
@ -270,14 +271,14 @@ public class CollectionCache : IDisposable
foreach (var manip in subMod.Manipulations)
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,
FullPath old, IMod? mod)
{
if (Calculating == -1)
_manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod);
}
}
// Add a specific file redirection, handling potential conflicts.
// 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)))
{
ModData.AddPath(mod, path);
ModData.AddPath(mod, path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod);
return;
}

View file

@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using Dalamud.Game;
using OtterGui.Classes;
using Penumbra.Api;

View file

@ -45,7 +45,7 @@ public readonly struct EqdpCache : IDisposable
foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>())
{
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();

View file

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

View file

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

View file

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

View file

@ -136,7 +136,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
_ => false,
};
}
/// <summary> Set a single file. </summary>
public void SetFile(MetaIndex metaIndex)
{
@ -162,7 +162,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
break;
}
}
/// <summary> Set the currently relevant IMC files for the collection cache. </summary>
public void SetImcFiles(bool fromFullCompute)
=> _imcCache.SetFiles(_collection, fromFullCompute);
@ -171,7 +171,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
=> _eqpCache.TemporarilySetFiles(_manager);
public MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory)
=> _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory);
=> _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory);
public MetaList.MetaReverter TemporarilySetGmpFile()
=> _gmpCache.TemporarilySetFiles(_manager);
@ -180,7 +180,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
=> _cmpCache.TemporarilySetFiles(_manager);
public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
=> _estCache.TemporarilySetFiles(_manager, type);
=> _estCache.TemporarilySetFiles(_manager, type);
/// <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 ")))
{
var oldName = type.ToString()[4..];
var value = jObject[oldName];
var value = jObject[oldName];
if (value == null)
continue;
jObject.Remove(oldName);
jObject.Add("Male" + oldName, value);
jObject.Add("Male" + oldName, value);
jObject.Add("Female" + oldName, value);
}
using var stream = File.Open(fileNames.ActiveCollectionsFile, FileMode.Truncate);
using var writer = new StreamWriter(stream);
using var j = new JsonTextWriter(writer);
using var j = new JsonTextWriter(writer);
j.Formatting = Formatting.Indented;
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.
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)
{
if (!storage.ByName(collectionName, out var collection))
{
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);
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));
if (m != null && m != yourself)
return string.Empty;
var f = ByType(CollectionTypeExtensions.FromParts(race, Gender.Female, false));
if (f != null && f != yourself)
return string.Empty;
@ -450,26 +451,28 @@ public class ActiveCollections : ISavable, IDisposable
}
var racialString = racial ? " and Racial Assignments" : string.Empty;
var @base = ByType(CollectionType.Default);
var male = ByType(CollectionType.MalePlayerCharacter);
var female = ByType(CollectionType.FemalePlayerCharacter);
var @base = ByType(CollectionType.Default);
var male = ByType(CollectionType.MalePlayerCharacter);
var female = ByType(CollectionType.FemalePlayerCharacter);
if (male == yourself && female == yourself)
return
$"Assignment is redundant due to overwriting Male Players and Female Players{racialString} with an identical collection.\nYou can remove it.";
if (male == null)
{
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)
return
$"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)
{
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;
// Check individual assignments. We can only be sure of redundancy for world-overlap or ownership overlap.
case CollectionType.Individual:

View file

@ -2,6 +2,7 @@ using OtterGui;
using Penumbra.Api.Enums;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
namespace Penumbra.Collections.Manager;

View file

@ -5,6 +5,7 @@ using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
namespace Penumbra.Collections.Manager;
@ -246,7 +247,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
DirectoryInfo? newDirectory)
{
switch (type)
switch (type)
{
case ModPathChangeType.Added:
foreach (var collection in this)

View file

@ -427,13 +427,13 @@ public static class CollectionTypeExtensions
public static string ToDescription(this CollectionType collectionType)
=> collectionType switch
{
CollectionType.Default => "World, Music, Furniture, baseline for characters and monsters not specialized.",
CollectionType.Interface => "User Interface, Icons, Maps, Styles.",
CollectionType.Yourself => "Your characters, regardless of name, race or gender. Applies in the login screen.",
CollectionType.MalePlayerCharacter => "Baseline for male player characters.",
CollectionType.FemalePlayerCharacter => "Baseline for female player characters.",
CollectionType.MaleNonPlayerCharacter => "Baseline for humanoid male non-player characters.",
CollectionType.Default => "World, Music, Furniture, baseline for characters and monsters not specialized.",
CollectionType.Interface => "User Interface, Icons, Maps, Styles.",
CollectionType.Yourself => "Your characters, regardless of name, race or gender. Applies in the login screen.",
CollectionType.MalePlayerCharacter => "Baseline for male player characters.",
CollectionType.FemalePlayerCharacter => "Baseline for female player characters.",
CollectionType.MaleNonPlayerCharacter => "Baseline for humanoid male 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;
// 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);
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
return true;
@ -56,7 +57,8 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
if (!_config.UseOwnerNameForCharacterCollection)
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);
return CheckWorlds(identifier, out collection);
}
@ -142,7 +144,8 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
if (_individuals.TryGetValue(identifier, out collection))
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);
if (identifier.IsValid && _individuals.TryGetValue(identifier, out collection))
return true;

View file

@ -27,6 +27,7 @@ public partial class IndividualCollections
{
if (_actorService.Valid)
return ReadJObjectInternal(obj, storage);
void Func()
{
if (ReadJObjectInternal(obj, storage))
@ -35,9 +36,10 @@ public partial class IndividualCollections
Loaded.Invoke();
_actorService.FinishedCreation -= Func;
}
_actorService.FinishedCreation += Func;
return false;
}
}
private bool ReadJObjectInternal(JArray? obj, CollectionStorage storage)
{
@ -85,6 +87,7 @@ public partial class IndividualCollections
NotificationType.Error);
}
}
return changes;
}

View file

@ -132,7 +132,8 @@ public sealed partial class IndividualCollections
_ => throw new NotImplementedException(),
};
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();
}

View file

@ -1,5 +1,6 @@
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using Penumbra.Util;

View file

@ -92,7 +92,7 @@ public partial class ModCollection
// Used for short periods of changed files.
public MetaList.MetaReverter TemporarilySetEqdpFile(CharacterUtility utility, GenderRace genderRace, bool 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)
=> _cache?.Meta.TemporarilySetEqpFile()
@ -109,4 +109,4 @@ public partial class ModCollection
public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type)
=> _cache?.Meta.TemporarilySetEstFile(type)
?? utility.TemporarilyResetResource((MetaIndex)type);
}
}

View file

@ -1,6 +1,7 @@
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Collections.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
namespace Penumbra.Collections;
@ -44,10 +45,10 @@ public partial class ModCollection
/// This is used for material and imc changes.
/// </summary>
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()
=> ++ChangeCounter;
=> ++ChangeCounter;
/// <summary>
/// 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>
public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings;
/// <summary> Inheritances stored before they can be applied. </summary>
public IReadOnlyList<string>? InheritanceByName;
@ -118,7 +119,7 @@ public partial class ModCollection
/// <summary> Constructor for reading from files. </summary>
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index,
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
{
{
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)
{
@ -130,7 +131,7 @@ public partial class ModCollection
}
/// <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.");
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)
{
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>());
}
}
/// <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)

View file

@ -3,6 +3,7 @@ using Penumbra.Mods;
using Penumbra.Services;
using Newtonsoft.Json;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Util;
namespace Penumbra.Collections;
@ -53,7 +54,7 @@ internal readonly struct ModCollectionSave : ISavable
}
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)
{
@ -64,7 +65,7 @@ internal readonly struct ModCollectionSave : ISavable
j.WriteEndObject();
// Inherit by collection name.
j.WritePropertyName("Inheritance");
j.WritePropertyName("Inheritance");
x.Serialize(j, _modCollection.InheritanceByName ?? _modCollection.DirectlyInheritsFrom.Select(c => c.Name));
j.WriteEndObject();
}

View file

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

View file

@ -207,7 +207,8 @@ public class CommandHandler : IDisposable
private bool SetUiMinimumSize(string _)
{
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.Y = Configuration.Constants.MinimumSizeY;
_config.Save();

View file

@ -45,7 +45,6 @@ public sealed class CollectionChange : EventWrapper<Action<CollectionType, ModCo
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnCollectionChange"/>
ModFileSystemSelector = 0,
}
public CollectionChange()

View file

@ -21,7 +21,7 @@ public sealed class ModDataChanged : EventWrapper<Action<ModDataChangeType, Mod,
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDataChange"/>
ModCacheManager = 0,
/// <seealso cref="Mods.ModFileSystem.OnDataChange"/>
/// <seealso cref="Mods.Manager.ModFileSystem.OnDataChange"/>
ModFileSystem = 0,
}

View file

@ -1,4 +1,5 @@
using OtterGui.Classes;
using Penumbra.Mods.Manager;
namespace Penumbra.Communication;
@ -19,7 +20,7 @@ public sealed class ModDiscoveryFinished : EventWrapper<Action, ModDiscoveryFini
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDiscoveryFinished"/>
ModCacheManager = 0,
/// <seealso cref="Mods.ModFileSystem.Reload"/>
/// <seealso cref="Mods.Manager.ModFileSystem.Reload"/>
ModFileSystem = 0,
}

View file

@ -16,6 +16,7 @@ public sealed class ModDiscoveryStarted : EventWrapper<Action, ModDiscoveryStart
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.StoreCurrentSelection"/>
ModFileSystemSelector = 200,
}
public ModDiscoveryStarted()
: base(nameof(ModDiscoveryStarted))
{ }

View file

@ -30,7 +30,7 @@ public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod,
/// <seealso cref="Mods.Manager.ModExportManager.OnModPathChange"/>
ModExportManager = 0,
/// <seealso cref="Mods.ModFileSystem.OnModPathChange"/>
/// <seealso cref="Mods.Manager.ModFileSystem.OnModPathChange"/>
ModFileSystem = 0,
/// <seealso cref="Mods.Manager.ModManager.OnModPathChange"/>
@ -48,6 +48,7 @@ public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod,
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeRemoval"/>
CollectionCacheManagerRemoval = 100,
}
public ModPathChanged()
: base(nameof(ModPathChanged))
{ }

View file

@ -6,13 +6,14 @@ using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.Widgets;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.Import.Structs;
using Penumbra.Interop.Services;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.UI;
using Penumbra.UI.Classes;
using Penumbra.UI.ResourceWatcher;
using Penumbra.UI.Tabs;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;

View file

@ -2,13 +2,15 @@
global using System;
global using System.Collections;
global using System.Collections.Concurrent;
global using System.Collections.Generic;
global using System.Diagnostics;
global using System.IO;
global using System.Linq;
global using System.Numerics;
global using System.Reflection;
global using System.Runtime.CompilerServices;
global using System.Runtime.InteropServices;
global using System.Security.Cryptography;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Threading.Tasks;

View file

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

View file

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

View file

@ -15,19 +15,19 @@ namespace Penumbra.Import;
public partial class TexToolsImporter
{
/// <summary>
/// <summary>
/// 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.
/// 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.
/// All data is extracted without manipulation of the files or metadata.
/// </summary>
private DirectoryInfo HandleRegularArchive( FileInfo modPackFile )
/// All data is extracted without manipulation of the files or metadata.
/// </summary>
private DirectoryInfo HandleRegularArchive(FileInfo modPackFile)
{
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;
_currentOptionIdx = 0;
_currentNumOptions = 1;
@ -42,9 +42,9 @@ public partial class TexToolsImporter
SevenZipArchive s => s.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()
{
ExtractFullPath = true,
@ -55,40 +55,38 @@ public partial class TexToolsImporter
_currentFileIdx = 0;
var reader = archive.ExtractAllEntries();
while( reader.MoveToNextEntry() )
while (reader.MoveToNextEntry())
{
_token.ThrowIfCancellationRequested();
if( reader.Entry.IsDirectory )
if (reader.Entry.IsDirectory)
{
--_currentNumFiles;
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.
if( Path.GetFileName( reader.Entry.Key ) == "meta.json" )
if (Path.GetFileName(reader.Entry.Key) == "meta.json")
{
using var s = new MemoryStream();
using var e = reader.OpenEntryStream();
e.CopyTo( s );
s.Seek( 0, SeekOrigin.Begin );
using var t = new StreamReader( s );
using var j = new JsonTextReader( t );
var obj = JObject.Load( j );
name = obj[ nameof( Mod.Name ) ]?.Value< string >()?.RemoveInvalidPathSymbols() ?? string.Empty;
if( name.Length == 0 )
{
throw new Exception( "Invalid mod archive: mod meta has no name." );
}
e.CopyTo(s);
s.Seek(0, SeekOrigin.Begin);
using var t = new StreamReader(s);
using var j = new JsonTextReader(t);
var obj = JObject.Load(j);
name = obj[nameof(Mod.Name)]?.Value<string>()?.RemoveInvalidPathSymbols() ?? string.Empty;
if (name.Length == 0)
throw new Exception("Invalid mod archive: mod meta has no name.");
using var f = File.OpenWrite( Path.Combine( _currentModDirectory.FullName, reader.Entry.Key ) );
s.Seek( 0, SeekOrigin.Begin );
s.WriteTo( f );
using var f = File.OpenWrite(Path.Combine(_currentModDirectory.FullName, reader.Entry.Key));
s.Seek(0, SeekOrigin.Begin);
s.WriteTo(f);
}
else
{
reader.WriteEntryToDirectory( _currentModDirectory.FullName, options );
reader.WriteEntryToDirectory(_currentModDirectory.FullName, options);
}
++_currentFileIdx;
@ -97,60 +95,59 @@ public partial class TexToolsImporter
_token.ThrowIfCancellationRequested();
var oldName = _currentModDirectory.FullName;
// 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 );
Directory.Move( Path.Combine( oldName, baseName ), _currentModDirectory.FullName );
Directory.Delete( oldName );
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, baseName, false);
Directory.Move(Path.Combine(oldName, baseName), _currentModDirectory.FullName);
Directory.Delete(oldName);
}
else
{
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, name, false );
Directory.Move( oldName, _currentModDirectory.FullName );
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, name, false);
Directory.Move(oldName, _currentModDirectory.FullName);
}
_currentModDirectory.Refresh();
_modManager.Creator.SplitMultiGroups( _currentModDirectory );
_modManager.Creator.SplitMultiGroups(_currentModDirectory);
return _currentModDirectory;
}
// 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.
if( entry == null )
{
throw new Exception( "Invalid mod archive: No meta.json contained." );
}
if (entry == null)
throw new Exception("Invalid mod archive: No meta.json contained.");
var ret = string.Empty;
leadDir = false;
// If the file is not at top-level.
if( entry.Key != "meta.json" )
if (entry.Key != "meta.json")
{
leadDir = true;
var directory = Path.GetDirectoryName( entry.Key );
var directory = Path.GetDirectoryName(entry.Key);
// Should not happen.
if( directory.IsNullOrEmpty() )
{
throw new Exception( "Invalid mod archive: Unknown error fetching meta.json." );
}
if (directory.IsNullOrEmpty())
throw new Exception("Invalid mod archive: Unknown error fetching meta.json.");
ret = directory;
// Check that all other files are also contained in the top-level directory.
if( ret.IndexOfAny( new[] { '/', '\\' } ) >= 0
|| !archive.Entries.All( e => e.Key.StartsWith( ret ) && ( e.Key.Length == ret.Length || e.Key[ ret.Length ] is '/' or '\\' ) ) )
{
if (ret.IndexOfAny(new[]
{
'/',
'\\',
})
>= 0
|| !archive.Entries.All(e => e.Key.StartsWith(ret) && (e.Key.Length == ret.Length || e.Key[ret.Length] is '/' or '\\')))
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;
}
}
}

View file

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

View file

@ -8,72 +8,70 @@ namespace Penumbra.Import;
public partial class TexToolsMeta
{
// 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;
}
using var s = new MemoryStream( data );
using var br = new BinaryReader( s );
using var s = new MemoryStream(data);
using var br = new BinaryReader(s);
// The first value is a flag that signifies version.
// If it is byte.max, the following two bytes are the version,
// otherwise it is version 1 and signifies the sub race instead.
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.
var subRace = ( SubRace )( version == 1 ? flag + 1 : br.ReadByte() + 1 );
if( !Enum.IsDefined( typeof( SubRace ), subRace ) || subRace == SubRace.Unknown )
var subRace = (SubRace)(version == 1 ? flag + 1 : br.ReadByte() + 1);
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;
}
// Next byte is Gender. 1 is Female, 0 is Male.
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;
}
// 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 );
if( keepDefault || value != def )
{
ret.MetaManipulations.Add( new RspManipulation( subRace, attribute, value ) );
}
var def = CmpFile.GetDefault(manager, subRace, attribute);
if (keepDefault || value != def)
ret.MetaManipulations.Add(new RspManipulation(subRace, attribute, value));
}
if( gender == 1 )
if (gender == 1)
{
Add( RspAttribute.FemaleMinSize, br.ReadSingle() );
Add( RspAttribute.FemaleMaxSize, br.ReadSingle() );
Add( RspAttribute.FemaleMinTail, br.ReadSingle() );
Add( RspAttribute.FemaleMaxTail, br.ReadSingle() );
Add(RspAttribute.FemaleMinSize, br.ReadSingle());
Add(RspAttribute.FemaleMaxSize, br.ReadSingle());
Add(RspAttribute.FemaleMinTail, br.ReadSingle());
Add(RspAttribute.FemaleMaxTail, br.ReadSingle());
Add( RspAttribute.BustMinX, br.ReadSingle() );
Add( RspAttribute.BustMinY, br.ReadSingle() );
Add( RspAttribute.BustMinZ, br.ReadSingle() );
Add( RspAttribute.BustMaxX, br.ReadSingle() );
Add( RspAttribute.BustMaxY, br.ReadSingle() );
Add( RspAttribute.BustMaxZ, br.ReadSingle() );
Add(RspAttribute.BustMinX, br.ReadSingle());
Add(RspAttribute.BustMinY, br.ReadSingle());
Add(RspAttribute.BustMinZ, br.ReadSingle());
Add(RspAttribute.BustMaxX, br.ReadSingle());
Add(RspAttribute.BustMaxY, br.ReadSingle());
Add(RspAttribute.BustMaxZ, br.ReadSingle());
}
else
{
Add( RspAttribute.MaleMinSize, br.ReadSingle() );
Add( RspAttribute.MaleMaxSize, br.ReadSingle() );
Add( RspAttribute.MaleMinTail, br.ReadSingle() );
Add( RspAttribute.MaleMaxTail, br.ReadSingle() );
Add(RspAttribute.MaleMinSize, br.ReadSingle());
Add(RspAttribute.MaleMaxSize, br.ReadSingle());
Add(RspAttribute.MaleMinTail, br.ReadSingle());
Add(RspAttribute.MaleMaxTail, br.ReadSingle());
}
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 also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file.</summary>
public partial class TexToolsMeta
{
{
/// <summary> An empty TexToolsMeta. </summary>
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.
public readonly uint Version;
public readonly string FilePath;
public readonly List< MetaManipulation > MetaManipulations = new();
private readonly bool _keepDefault = false;
private readonly MetaFileManager _metaFileManager;
public readonly uint Version;
public readonly string FilePath;
public readonly List<MetaManipulation> MetaManipulations = new();
private readonly bool _keepDefault = false;
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;
_keepDefault = keepDefault;
_keepDefault = keepDefault;
try
{
using var reader = new BinaryReader( new MemoryStream( data ) );
using var reader = new BinaryReader(new MemoryStream(data));
Version = reader.ReadUInt32();
FilePath = ReadNullTerminated( reader );
var metaInfo = new MetaFileInfo( parser, FilePath );
FilePath = ReadNullTerminated(reader);
var metaInfo = new MetaFileInfo(parser, FilePath);
var numHeaders = reader.ReadUInt32();
var headerSize = 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();
for( var i = 0; i < numHeaders; ++i )
List<(MetaManipulation.Type type, uint offset, int size)> entries = new();
for (var i = 0; i < numHeaders; ++i)
{
var currentOffset = reader.BaseStream.Position;
var type = ( MetaManipulation.Type )reader.ReadUInt32();
var type = (MetaManipulation.Type)reader.ReadUInt32();
var offset = reader.ReadUInt32();
var size = reader.ReadInt32();
entries.Add( ( type, offset, size ) );
reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin );
entries.Add((type, offset, size));
reader.BaseStream.Seek(currentOffset + headerSize, SeekOrigin.Begin);
}
byte[]? ReadEntry( MetaManipulation.Type type )
byte[]? ReadEntry(MetaManipulation.Type type)
{
var idx = entries.FindIndex( t => t.type == type );
if( idx < 0 )
{
var idx = entries.FindIndex(t => t.type == type);
if (idx < 0)
return null;
}
reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
return reader.ReadBytes( entries[ idx ].size );
reader.BaseStream.Seek(entries[idx].offset, SeekOrigin.Begin);
return reader.ReadBytes(entries[idx].size);
}
DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) );
DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) );
DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) );
DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) );
DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) );
DeserializeEqpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Eqp));
DeserializeGmpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Gmp));
DeserializeEqdpEntries(metaInfo, ReadEntry(MetaManipulation.Type.Eqdp));
DeserializeEstEntries(metaInfo, ReadEntry(MetaManipulation.Type.Est));
DeserializeImcEntries(metaInfo, ReadEntry(MetaManipulation.Type.Imc));
}
catch( Exception e )
catch (Exception e)
{
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;
FilePath = filePath;
@ -88,14 +86,12 @@ public partial class TexToolsMeta
}
// 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();
for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() )
{
builder.Append( c );
}
for (var c = reader.ReadChar(); c != 0; c = reader.ReadChar())
builder.Append(c);
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)
: this(size.Width, size.Height, pixelData)
{
}
{ }
public Image<Rgba32> ToImage()
=> Image.LoadPixelData<Rgba32>(PixelData, Width, Height);

View file

@ -79,8 +79,8 @@ public static class TexFileParser
w.Write(header.Width);
w.Write(header.Height);
w.Write(header.Depth);
w.Write((byte) header.MipLevels);
w.Write((byte) 0); // TODO Lumina Update
w.Write((byte)header.MipLevels);
w.Write((byte)0); // TODO Lumina Update
unsafe
{
w.Write(header.LodOffset[0]);

View file

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

View file

@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using Dalamud.Interface;
using Dalamud.Plugin.Services;
using ImGuiScene;

View file

@ -80,7 +80,8 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase
textureSize[0] = TextureWidth;
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)
return;

View file

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

View file

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

View file

@ -10,7 +10,7 @@ namespace Penumbra.Interop.PathResolving;
public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>
{
private readonly IObjectTable _objects;
private readonly IObjectTable _objects;
private readonly GameEventManager _gameEvents;
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)
{
_lastGameObject.Value!.Dequeue();
IterateDrawObjectTree((Object*) ((GameObject*) gameObject)->DrawObject, gameObject, false, false);
_lastGameObject.Value!.Dequeue();
IterateDrawObjectTree((Object*)((GameObject*)gameObject)->DrawObject, gameObject, false, false);
}
private void OnCharacterBaseDestructor(nint characterBase)

View file

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

View file

@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Enums;

View file

@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using Penumbra.Collections;

View file

@ -81,6 +81,6 @@ public unsafe class FileReadService : IDisposable
/// </summary>
private nint GetResourceManager()
=> !_lastFileThreadResourceManager.IsValueCreated || _lastFileThreadResourceManager.Value == IntPtr.Zero
? (nint) _resourceManager.ResourceManager
? (nint)_resourceManager.ResourceManager
: _lastFileThreadResourceManager.Value;
}

View file

@ -37,7 +37,8 @@ public class 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)
{
Name = uiData.Name;
@ -69,7 +70,7 @@ public class ResourceNode
}
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)
=> 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 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.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -16,7 +16,7 @@ public class ResourceTree
public readonly nint DrawObjectAddress;
public readonly bool PlayerRelated;
public readonly string CollectionName;
public readonly List<ResourceNode> Nodes;
public readonly List<ResourceNode> Nodes;
public readonly HashSet<ResourceNode> FlatNodes;
public int ModelId;
@ -26,11 +26,11 @@ public class ResourceTree
public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
{
Name = name;
GameObjectAddress = gameObjectAddress;
GameObjectAddress = gameObjectAddress;
DrawObjectAddress = drawObjectAddress;
PlayerRelated = playerRelated;
CollectionName = collectionName;
Nodes = new List<ResourceNode>();
Nodes = new List<ResourceNode>();
FlatNodes = new HashSet<ResourceNode>();
}
@ -42,7 +42,7 @@ public class ResourceTree
// var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 );
ModelId = character->CharacterData.ModelCharaId;
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)
{
@ -60,8 +60,8 @@ public class ResourceTree
var mdlNode = context.CreateNodeFromRenderModel(mdl);
if (mdlNode != null)
Nodes.Add(globalContext.WithUiData ? mdlNode.WithUIData(mdlNode.Name ?? $"Model #{i}", mdlNode.Icon) : mdlNode);
}
}
AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc)
@ -100,8 +100,8 @@ public class ResourceTree
subObjectNodes.Add(globalContext.WithUiData
? mdlNode.WithUIData(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}", mdlNode.Icon)
: mdlNode);
}
}
AddSkeleton(subObjectNodes, subObjectContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, ");
subObject = (CharacterBase*)subObject->DrawObject.Object.NextSiblingObject;
@ -119,19 +119,21 @@ public class ResourceTree
var legacyDecalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->LegacyBodyDecal);
if (legacyDecalNode != null)
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)
return;
for (var i = 0; i < skeleton->PartialSkeletonCount; ++i)
{
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
if (sklbNode != null)
nodes.Add(context.WithUiData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode);
}
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)
return;
for (var i = 0; i < skeleton->PartialSkeletonCount; ++i)
{
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 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)
throw new ArgumentException("Non-owning SafeResourceHandle with IncRef is unsupported");
if (incRef && handle != null)
handle->IncRef();
SetHandle((nint)handle);
}
public static SafeResourceHandle CreateInvalid()
=> new(null, incRef: false);
=> new(null, false);
protected override bool ReleaseHandle()
{

View file

@ -5,14 +5,18 @@ namespace Penumbra.Interop.SafeHandles;
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)
throw new ArgumentException("Non-owning SafeTextureHandle with IncRef is unsupported");
if (incRef && handle != null)
TextureUtility.IncRef(handle);
SetHandle((nint)handle);
@ -27,16 +31,17 @@ public unsafe class SafeTextureHandle : SafeHandle
}
public static SafeTextureHandle CreateInvalid()
=> new(null, incRef: false);
=> new(null, false);
protected override bool ReleaseHandle()
{
nint handle;
lock (this)
{
handle = this.handle;
handle = this.handle;
this.handle = 0;
}
if (handle != 0)
TextureUtility.DecRef((Texture*)handle);

View file

@ -14,7 +14,7 @@ public sealed unsafe class DecalReverter : IDisposable
public static readonly Utf8GamePath TransparentPath =
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* _transparent;
@ -22,10 +22,10 @@ public sealed unsafe class DecalReverter : IDisposable
{
_utility = utility;
var ptr = _utility.Address;
_decal = null;
_decal = null;
_transparent = null;
if (!config.EnableMods)
return;
return;
if (doDecal)
{

View file

@ -152,7 +152,7 @@ public unsafe class GameEventManager : IDisposable
{
try
{
((CreatingCharacterBaseEvent)subscriber).Invoke((nint) (&a), b, c);
((CreatingCharacterBaseEvent)subscriber).Invoke((nint)(&a), b, c);
}
catch (Exception ex)
{
@ -265,11 +265,13 @@ public unsafe class GameEventManager : IDisposable
private readonly Hook<TestDelegate>? _testHook = null;
private delegate void TestDelegate(nint a1, int a2);
private void TestDetour(nint a1, int a2)
{
Penumbra.Log.Information($"Test: {a1:X} {a2}");
_testHook!.Original(a1, a2);
}
private void EnableDebugHook()
=> _testHook?.Enable();

View file

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

View file

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

View file

@ -2,23 +2,23 @@ using Penumbra.GameData.Enums;
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct CharacterUtilityData
{
public const int IndexTransparentTex = 72;
public const int IndexDecalTex = 73;
public const int IndexSkinShpk = 76;
public static readonly MetaIndex[] EqdpIndices = Enum.GetNames< MetaIndex >()
.Zip( Enum.GetValues< MetaIndex >() )
.Where( n => n.First.StartsWith( "Eqdp" ) )
.Select( n => n.Second ).ToArray();
public static readonly MetaIndex[] EqdpIndices = Enum.GetNames<MetaIndex>()
.Zip(Enum.GetValues<MetaIndex>())
.Where(n => n.First.StartsWith("Eqdp"))
.Select(n => n.Second).ToArray();
public const int TotalNumResources = 87;
/// <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 )
=> +( int )raceCode switch
public static MetaIndex EqdpIdx(GenderRace raceCode, bool accessory)
=> +(int)raceCode switch
{
0101 => accessory ? MetaIndex.Eqdp0101Acc : MetaIndex.Eqdp0101,
0201 => accessory ? MetaIndex.Eqdp0201Acc : MetaIndex.Eqdp0201,
@ -48,53 +48,53 @@ public unsafe struct CharacterUtilityData
1404 => accessory ? MetaIndex.Eqdp1404Acc : MetaIndex.Eqdp1404,
9104 => accessory ? MetaIndex.Eqdp9104Acc : MetaIndex.Eqdp9104,
9204 => accessory ? MetaIndex.Eqdp9204Acc : MetaIndex.Eqdp9204,
_ => ( MetaIndex )( -1 ),
_ => (MetaIndex)(-1),
};
[FieldOffset( 0 )]
[FieldOffset(0)]
public void* VTable;
[FieldOffset( 8 )]
[FieldOffset(8)]
public fixed ulong Resources[TotalNumResources];
[FieldOffset( 8 + ( int )MetaIndex.Eqp * 8 )]
[FieldOffset(8 + (int)MetaIndex.Eqp * 8)]
public ResourceHandle* EqpResource;
[FieldOffset( 8 + ( int )MetaIndex.Gmp * 8 )]
[FieldOffset(8 + (int)MetaIndex.Gmp * 8)]
public ResourceHandle* GmpResource;
public ResourceHandle* Resource( int idx )
=> ( ResourceHandle* )Resources[ idx ];
public ResourceHandle* Resource(int idx)
=> (ResourceHandle*)Resources[idx];
public ResourceHandle* Resource( MetaIndex idx )
=> Resource( ( int )idx );
public ResourceHandle* Resource(MetaIndex idx)
=> Resource((int)idx);
public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory )
=> Resource( ( int )EqdpIdx( raceCode, accessory ) );
public ResourceHandle* EqdpResource(GenderRace raceCode, bool accessory)
=> Resource((int)EqdpIdx(raceCode, accessory));
[FieldOffset( 8 + ( int )MetaIndex.HumanCmp * 8 )]
[FieldOffset(8 + (int)MetaIndex.HumanCmp * 8)]
public ResourceHandle* HumanCmpResource;
[FieldOffset( 8 + ( int )MetaIndex.FaceEst * 8 )]
[FieldOffset(8 + (int)MetaIndex.FaceEst * 8)]
public ResourceHandle* FaceEstResource;
[FieldOffset( 8 + ( int )MetaIndex.HairEst * 8 )]
[FieldOffset(8 + (int)MetaIndex.HairEst * 8)]
public ResourceHandle* HairEstResource;
[FieldOffset( 8 + ( int )MetaIndex.BodyEst * 8 )]
[FieldOffset(8 + (int)MetaIndex.BodyEst * 8)]
public ResourceHandle* BodyEstResource;
[FieldOffset( 8 + ( int )MetaIndex.HeadEst * 8 )]
[FieldOffset(8 + (int)MetaIndex.HeadEst * 8)]
public ResourceHandle* HeadEstResource;
[FieldOffset( 8 + IndexTransparentTex * 8 )]
[FieldOffset(8 + IndexTransparentTex * 8)]
public TextureResourceHandle* TransparentTexResource;
[FieldOffset( 8 + IndexDecalTex * 8 )]
[FieldOffset(8 + IndexDecalTex * 8)]
public TextureResourceHandle* DecalTexResource;
[FieldOffset( 8 + IndexSkinShpk * 8 )]
[FieldOffset(8 + IndexSkinShpk * 8)]
public ResourceHandle* SkinShpkResource;
// not included resources have no known use case.
}
}

View file

@ -1,11 +1,11 @@
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct ClipScheduler
{
[FieldOffset( 0 )]
[FieldOffset(0)]
public IntPtr* VTable;
[FieldOffset( 0x38 )]
[FieldOffset(0x38)]
public IntPtr SchedulerTimeline;
}
}

View file

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

View file

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

View file

@ -2,18 +2,18 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct HumanExt
{
[FieldOffset( 0x0 )]
[FieldOffset(0x0)]
public Human Human;
[FieldOffset( 0x0 )]
[FieldOffset(0x0)]
public CharacterBaseExt CharacterBase;
[FieldOffset( 0x9E8 )]
[FieldOffset(0x9E8)]
public ResourceHandle* Decal;
[FieldOffset( 0x9F0 )]
[FieldOffset(0x9F0)]
public ResourceHandle* LegacyBodyDecal;
}
}

View file

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

View file

@ -1,45 +1,45 @@
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct MtrlResource
{
[FieldOffset( 0x00 )]
[FieldOffset(0x00)]
public ResourceHandle Handle;
[FieldOffset( 0xC8 )]
[FieldOffset(0xC8)]
public ShaderPackageResourceHandle* ShpkResourceHandle;
[FieldOffset( 0xD0 )]
[FieldOffset(0xD0)]
public TextureEntry* TexSpace; // Contains the offsets for the tex files inside the string list.
[FieldOffset( 0xE0 )]
[FieldOffset(0xE0)]
public byte* StringList;
[FieldOffset( 0xF8 )]
[FieldOffset(0xF8)]
public ushort ShpkOffset;
[FieldOffset( 0xFA )]
[FieldOffset(0xFA)]
public byte NumTex;
public byte* ShpkString
=> StringList + ShpkOffset;
public byte* TexString( int idx )
public byte* TexString(int idx)
=> StringList + TexSpace[idx].PathOffset;
public bool TexIsDX11( int idx )
public bool TexIsDX11(int idx)
=> TexSpace[idx].Flags >= 0x8000;
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
public struct TextureEntry
{
[FieldOffset( 0x00 )]
[FieldOffset(0x00)]
public TextureResourceHandle* ResourceHandle;
[FieldOffset( 0x08 )]
[FieldOffset(0x08)]
public ushort PathOffset;
[FieldOffset( 0x0A )]
[FieldOffset(0x0A)]
public ushort Flags;
}
}
}

View file

@ -2,39 +2,39 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct RenderModel
{
[FieldOffset( 0x18 )]
[FieldOffset(0x18)]
public RenderModel* PreviousModel;
[FieldOffset( 0x20 )]
[FieldOffset(0x20)]
public RenderModel* NextModel;
[FieldOffset( 0x30 )]
[FieldOffset(0x30)]
public ResourceHandle* ResourceHandle;
[FieldOffset( 0x40 )]
[FieldOffset(0x40)]
public Skeleton* Skeleton;
[FieldOffset( 0x58 )]
[FieldOffset(0x58)]
public void** BoneList;
[FieldOffset( 0x60 )]
[FieldOffset(0x60)]
public int BoneListCount;
[FieldOffset( 0x70 )]
[FieldOffset(0x70)]
private void* UnkDXBuffer1;
[FieldOffset( 0x78 )]
[FieldOffset(0x78)]
private void* UnkDXBuffer2;
[FieldOffset( 0x80 )]
[FieldOffset(0x80)]
private void* UnkDXBuffer3;
[FieldOffset( 0x98 )]
[FieldOffset(0x98)]
public void** Materials;
[FieldOffset( 0xA0 )]
[FieldOffset(0xA0)]
public int MaterialCount;
}
}

View file

@ -3,15 +3,15 @@ namespace Penumbra.Interop.Structs;
[StructLayout(LayoutKind.Explicit)]
public unsafe struct ResidentResourceManager
{
[FieldOffset( 0x00 )]
[FieldOffset(0x00)]
public void** VTable;
[FieldOffset( 0x08 )]
[FieldOffset(0x08)]
public void** ResourceListVTable;
[FieldOffset( 0x14 )]
[FieldOffset(0x14)]
public uint NumResources;
[FieldOffset( 0x18 )]
[FieldOffset(0x18)]
public ResourceHandle** ResourceList;
}
}

View file

@ -8,45 +8,45 @@ using Penumbra.String.Classes;
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct TextureResourceHandle
{
[FieldOffset( 0x0 )]
[FieldOffset(0x0)]
public ResourceHandle Handle;
[FieldOffset( 0x38 )]
[FieldOffset(0x38)]
public IntPtr Unk;
[FieldOffset( 0x118 )]
[FieldOffset(0x118)]
public Texture* KernelTexture;
[FieldOffset( 0x20 )]
[FieldOffset(0x20)]
public IntPtr NewKernelTexture;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct ShaderPackageResourceHandle
{
[FieldOffset( 0x0 )]
[FieldOffset(0x0)]
public ResourceHandle Handle;
[FieldOffset( 0xB0 )]
[FieldOffset(0xB0)]
public ShaderPackage* ShaderPackage;
}
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct ResourceHandle
{
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public struct DataIndirection
{
[FieldOffset( 0x00 )]
[FieldOffset(0x00)]
public void** VTable;
[FieldOffset( 0x10 )]
[FieldOffset(0x10)]
public byte* DataPtr;
[FieldOffset( 0x28 )]
[FieldOffset(0x28)]
public ulong DataLength;
}
@ -54,87 +54,83 @@ public unsafe struct ResourceHandle
public byte* FileNamePtr()
{
if( FileNameLength > SsoSize )
{
if (FileNameLength > SsoSize)
return FileNameData;
}
fixed( byte** name = &FileNameData )
fixed (byte** name = &FileNameData)
{
return ( byte* )name;
return (byte*)name;
}
}
public ByteString FileName()
=> ByteString.FromByteStringUnsafe( FileNamePtr(), FileNameLength, true );
=> ByteString.FromByteStringUnsafe(FileNamePtr(), FileNameLength, true);
public ReadOnlySpan< byte > FileNameAsSpan()
=> new( FileNamePtr(), FileNameLength );
public ReadOnlySpan<byte> FileNameAsSpan()
=> new(FileNamePtr(), FileNameLength);
public bool GamePath( out Utf8GamePath path )
=> Utf8GamePath.FromSpan( FileNameAsSpan(), out path );
public bool GamePath(out Utf8GamePath path)
=> Utf8GamePath.FromSpan(FileNameAsSpan(), out path);
[FieldOffset( 0x00 )]
[FieldOffset(0x00)]
public void** VTable;
[FieldOffset( 0x08 )]
[FieldOffset(0x08)]
public ResourceCategory Category;
[FieldOffset( 0x0C )]
[FieldOffset(0x0C)]
public ResourceType FileType;
[FieldOffset( 0x10 )]
[FieldOffset(0x10)]
public uint Id;
[FieldOffset( 0x28 )]
[FieldOffset(0x28)]
public uint FileSize;
[FieldOffset( 0x2C )]
[FieldOffset(0x2C)]
public uint FileSize2;
[FieldOffset( 0x34 )]
[FieldOffset(0x34)]
public uint FileSize3;
[FieldOffset( 0x48 )]
[FieldOffset(0x48)]
public byte* FileNameData;
[FieldOffset( 0x58 )]
[FieldOffset(0x58)]
public int FileNameLength;
[FieldOffset( 0xAC )]
[FieldOffset(0xAC)]
public uint RefCount;
// May return null.
public static byte* GetData( ResourceHandle* handle )
=> ( ( delegate* unmanaged< ResourceHandle*, byte* > )handle->VTable[ Offsets.ResourceHandleGetDataVfunc ] )( handle );
public static byte* GetData(ResourceHandle* handle)
=> ((delegate* unmanaged< ResourceHandle*, byte* >)handle->VTable[Offsets.ResourceHandleGetDataVfunc])(handle);
public static ulong GetLength( ResourceHandle* handle )
=> ( ( delegate* unmanaged< ResourceHandle*, ulong > )handle->VTable[ Offsets.ResourceHandleGetLengthVfunc ] )( handle );
public static ulong GetLength(ResourceHandle* handle)
=> ((delegate* unmanaged< ResourceHandle*, ulong >)handle->VTable[Offsets.ResourceHandleGetLengthVfunc])(handle);
// Only use these if you know what you are doing.
// Those are actually only sure to be accessible for DefaultResourceHandles.
[FieldOffset( 0xB0 )]
[FieldOffset(0xB0)]
public DataIndirection* Data;
[FieldOffset( 0xB8 )]
[FieldOffset(0xB8)]
public uint DataLength;
public (IntPtr Data, int Length) GetData()
=> Data != null
? ( ( IntPtr )Data->DataPtr, ( int )Data->DataLength )
: ( IntPtr.Zero, 0 );
? ((IntPtr)Data->DataPtr, (int)Data->DataLength)
: (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;
}
Data->DataPtr = length != 0 ? ( byte* )data : null;
Data->DataLength = ( ulong )length;
DataLength = ( uint )length;
Data->DataPtr = length != 0 ? (byte*)data : null;
Data->DataLength = (ulong)length;
DataLength = (uint)length;
return true;
}
}
}

View file

@ -1,18 +1,17 @@
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct SeFileDescriptor
{
[FieldOffset( 0x00 )]
[FieldOffset(0x00)]
public FileMode FileMode;
[FieldOffset( 0x30 )]
public void* FileDescriptor; //
[FieldOffset(0x30)]
public void* FileDescriptor;
[FieldOffset( 0x50 )]
public ResourceHandle* ResourceHandle; //
[FieldOffset(0x50)]
public ResourceHandle* ResourceHandle;
[FieldOffset( 0x70 )]
public char Utf16FileName; //
}
[FieldOffset(0x70)]
public char Utf16FileName;
}

View file

@ -4,12 +4,13 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
namespace Penumbra.Interop.Structs;
public unsafe static class TextureUtility
public static unsafe class TextureUtility
{
private static readonly Functions Funcs = new();
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)
=> ((delegate* unmanaged<Texture*, void*, bool>)Funcs.TextureInitializeContents)(texture, contents);

View file

@ -1,17 +1,17 @@
namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct VfxParams
{
[FieldOffset( 0x118 )]
[FieldOffset(0x118)]
public uint GameObjectId;
[FieldOffset( 0x11C )]
[FieldOffset(0x11C)]
public byte GameObjectType;
[FieldOffset( 0xD0 )]
[FieldOffset(0xD0)]
public ushort TargetCount;
[FieldOffset( 0x120 )]
[FieldOffset(0x120)]
public fixed ulong Target[16];
}
}

View file

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

View file

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

View file

@ -7,8 +7,8 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations;
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
{
public enum EstType : byte
{
@ -18,7 +18,7 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
Head = MetaIndex.HeadEst,
}
public static string ToName( EstType type )
public static string ToName(EstType type)
=> type switch
{
EstType.Hair => "hair",
@ -30,19 +30,19 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
public ushort Entry { get; private init; } // SkeletonIdx.
[JsonConverter( typeof( StringEnumConverter ) )]
[JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )]
[JsonConverter(typeof(StringEnumConverter))]
public ModelRace Race { get; private init; }
public SetId SetId { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )]
[JsonConverter(typeof(StringEnumConverter))]
public EstType Slot { get; private init; }
[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;
Gender = gender;
@ -51,49 +51,45 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
Slot = slot;
}
public EstManipulation Copy( ushort entry )
public EstManipulation Copy(ushort entry)
=> new(Gender, Race, Slot, SetId, entry);
public override string ToString()
=> $"Est - {SetId} - {Slot} - {Race.ToName()} {Gender.ToName()}";
public bool Equals( EstManipulation other )
public bool Equals(EstManipulation other)
=> Gender == other.Gender
&& Race == other.Race
&& Race == other.Race
&& SetId == other.SetId
&& Slot == other.Slot;
&& Slot == other.Slot;
public override bool Equals( object? obj )
=> obj is EstManipulation other && Equals( other );
public override bool Equals(object? obj)
=> obj is EstManipulation other && Equals(other);
public override int GetHashCode()
=> HashCode.Combine( ( int )Gender, ( int )Race, SetId, ( int )Slot );
=> HashCode.Combine((int)Gender, (int)Race, SetId, (int)Slot);
public int CompareTo( EstManipulation other )
public int CompareTo(EstManipulation other)
{
var r = Race.CompareTo( other.Race );
if( r != 0 )
{
var r = Race.CompareTo(other.Race);
if (r != 0)
return r;
}
var g = Gender.CompareTo( other.Gender );
if( g != 0 )
{
var g = Gender.CompareTo(other.Gender);
if (g != 0)
return g;
}
var s = Slot.CompareTo( other.Slot );
return s != 0 ? s : SetId.Id.CompareTo( other.SetId.Id );
var s = Slot.CompareTo(other.Slot);
return s != 0 ? s : SetId.Id.CompareTo(other.SetId.Id);
}
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.Changed => true,
@ -109,7 +105,8 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
return false;
if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown)
return false;
// No known check for set id or entry.
// No known check for set id or entry.
return true;
}
}
}

View file

@ -5,55 +5,51 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations;
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation >
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
{
public GmpEntry Entry { get; private init; }
public SetId SetId { get; private init; }
public SetId SetId { get; private init; }
[JsonConstructor]
public GmpManipulation( GmpEntry entry, SetId setId )
public GmpManipulation(GmpEntry entry, SetId setId)
{
Entry = entry;
SetId = setId;
}
public GmpManipulation Copy( GmpEntry entry )
public GmpManipulation Copy(GmpEntry entry)
=> new(entry, SetId);
public override string ToString()
=> $"Gmp - {SetId}";
public bool Equals( GmpManipulation other )
public bool Equals(GmpManipulation other)
=> SetId == other.SetId;
public override bool Equals( object? obj )
=> obj is GmpManipulation other && Equals( other );
public override bool Equals(object? obj)
=> obj is GmpManipulation other && Equals(other);
public override int GetHashCode()
=> SetId.GetHashCode();
public int CompareTo( GmpManipulation other )
=> SetId.Id.CompareTo( other.SetId.Id );
public int CompareTo(GmpManipulation other)
=> SetId.Id.CompareTo(other.SetId.Id);
public MetaIndex FileIndex()
=> MetaIndex.Gmp;
public bool Apply( ExpandedGmpFile file )
public bool Apply(ExpandedGmpFile file)
{
var entry = file[ SetId ];
if( entry == Entry )
{
var entry = file[SetId];
if (entry == Entry)
return false;
}
file[ SetId ] = Entry;
file[SetId] = Entry;
return true;
}
public bool Validate()
{
// No known conditions.
return true;
}
}
=> true;
}

View file

@ -10,6 +10,7 @@ using Penumbra.Interop.Services;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Mods;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;

View file

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

View file

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;

View file

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

View file

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

View file

@ -1,4 +1,5 @@
using OtterGui;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods.Editor;

View file

@ -1,5 +1,6 @@
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
@ -7,7 +8,7 @@ namespace Penumbra.Mods;
public class ModFileEditor
{
private readonly ModFileCollection _files;
private readonly ModManager _modManager;
private readonly ModManager _modManager;
public bool Changes { get; private set; }

View file

@ -5,6 +5,7 @@ using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.UI.ModsTab;
@ -174,7 +175,7 @@ public class ModMerger : IDisposable
ret = new FullPath(MergeToMod!.ModPath, relPath);
return true;
}
foreach (var originalOption in mergeOptions)
{
foreach (var manip in originalOption.Manipulations)

View file

@ -1,5 +1,6 @@
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
namespace Penumbra.Mods;
@ -146,6 +147,7 @@ public class ModMetaEditor
}
}
}
Split(currentOption.Manipulations);
}

View file

@ -2,6 +2,7 @@ using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using OtterGui.Tasks;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
namespace Penumbra.Mods;

View file

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

View file

@ -10,27 +10,29 @@ namespace Penumbra.Mods.ItemSwap;
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.
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 )
{
throw new Exception( $"The Customization ID {idFrom} is too large for {slot}." );
}
if (idFrom.Id > byte.MaxValue)
throw new Exception($"The Customization ID {idFrom} is too large for {slot}.");
var mdlPathFrom = GamePaths.Character.Mdl.Path( race, slot, idFrom, slot.ToCustomizationType() );
var mdlPathTo = GamePaths.Character.Mdl.Path( race, slot, idTo, 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 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 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;
foreach( ref var materialFileName in mdl.AsMdl()!.Materials.AsSpan() )
foreach (ref var materialFileName in mdl.AsMdl()!.Materials.AsSpan())
{
var name = materialFileName;
foreach( var variant in Enumerable.Range( 1, range ) )
foreach (var variant in Enumerable.Range(1, range))
{
name = materialFileName;
var mtrl = CreateMtrl( manager, redirections, slot, race, idFrom, idTo, ( byte )variant, ref name, ref mdl.DataWasChanged );
mdl.ChildSwaps.Add( mtrl );
var mtrl = CreateMtrl(manager, redirections, slot, race, idFrom, idTo, (byte)variant, ref name, ref mdl.DataWasChanged);
mdl.ChildSwaps.Add(mtrl);
}
materialFileName = name;
@ -39,71 +41,75 @@ public static class CustomizationSwap
return mdl;
}
public static FileSwap CreateMtrl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo, byte variant,
ref string fileName, ref bool dataWasChanged )
public static FileSwap CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, SetId idTo, byte variant,
ref string fileName, ref bool dataWasChanged)
{
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 mtrlToPath = GamePaths.Character.Mtrl.Path( race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, 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 newFileName = fileName;
newFileName = ItemSwap.ReplaceRace( newFileName, gameRaceTo, race, gameRaceTo != race );
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", $"_{slot.ToAbbreviation()}{idFrom.Id:D4}", gameSetIdFrom != idFrom );
newFileName = ItemSwap.ReplaceRace(newFileName, gameRaceTo, race, gameRaceTo != race);
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", $"_{slot.ToAbbreviation()}{idFrom.Id:D4}", gameSetIdFrom != idFrom);
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;
dataWasChanged = true;
}
var mtrl = FileSwap.CreateSwap( manager, ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath );
var shpk = CreateShader( manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged );
mtrl.ChildSwaps.Add( shpk );
var mtrl = FileSwap.CreateSwap(manager, ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath);
var shpk = CreateShader(manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged);
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 );
mtrl.ChildSwaps.Add( tex );
var tex = CreateTex(manager, redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged);
mtrl.ChildSwaps.Add(tex);
}
return mtrl;
}
public static FileSwap CreateTex( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, ref MtrlFile.Texture texture,
ref bool dataWasChanged )
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, ref MtrlFile.Texture texture,
ref bool dataWasChanged)
{
var path = texture.Path;
var addedDashes = false;
if( texture.DX11 )
if (texture.DX11)
{
var fileName = Path.GetFileName( path );
if( !fileName.StartsWith( "--" ) )
var fileName = Path.GetFileName(path);
if (!fileName.StartsWith("--"))
{
path = path.Replace( fileName, $"--{fileName}" );
path = path.Replace(fileName, $"--{fileName}");
addedDashes = true;
}
}
var newPath = ItemSwap.ReplaceAnyRace( path, race );
newPath = ItemSwap.ReplaceAnyBody( newPath, slot, idFrom );
newPath = ItemSwap.AddSuffix( newPath, ".tex", $"_{Path.GetFileName( texture.Path ).GetStableHashCode():x8}", true );
if( newPath != path )
var newPath = ItemSwap.ReplaceAnyRace(path, race);
newPath = ItemSwap.ReplaceAnyBody(newPath, slot, idFrom);
newPath = ItemSwap.AddSuffix(newPath, ".tex", $"_{Path.GetFileName(texture.Path).GetStableHashCode():x8}", true);
if (newPath != path)
{
texture.Path = addedDashes ? newPath.Replace( "--", string.Empty ) : newPath;
texture.Path = addedDashes ? newPath.Replace("--", string.Empty) : newPath;
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}";
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,
SetId idFrom, SetId idTo, Variant variantFrom)
{
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry);
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry);
EquipItem[] items;
Variant[] variants;
Variant[] variants;
if (idFrom == idTo)
{
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
@ -264,8 +264,9 @@ public static class EquipmentSwap
else
{
items = identifier.Identify(slotFrom.IsEquipment()
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>().ToArray();
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
: 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();
}
@ -283,11 +284,13 @@ public static class EquipmentSwap
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)
=> 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,
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
{
@ -401,7 +404,8 @@ public static class EquipmentSwap
ref MtrlFile.Texture texture, ref bool 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)
{
var path = texture.Path;
@ -428,13 +432,15 @@ public static class EquipmentSwap
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}";
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;
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 MissingFileException( ResourceType type, object path )
public MissingFileException(ResourceType type, object path)
: base($"Could not load {type} File Data for \"{path}\".")
=> 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
{
if( path.IsRooted )
if (path.IsRooted)
{
data = File.ReadAllBytes( path.FullName );
data = File.ReadAllBytes(path.FullName);
return true;
}
var file = manager.GameData.GetFile( path.InternalName.ToString() );
if( file != null )
var file = manager.GameData.GetFile(path.InternalName.ToString());
if (file != null)
{
data = file.Data;
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;
}
public class GenericFile : IWritable
{
public readonly byte[] Data;
public bool Valid { get; }
public bool Valid { get; }
public GenericFile( MetaFileManager manager, FullPath path )
=> Valid = LoadFile( manager, path, out Data );
public GenericFile(MetaFileManager manager, FullPath path)
=> Valid = LoadFile(manager, path, out Data);
public byte[] Write()
=> Data;
@ -68,69 +66,67 @@ public static class ItemSwap
public static readonly GenericFile Invalid = new(null!, FullPath.Empty);
}
public static bool LoadFile( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out GenericFile? file )
public static bool LoadFile(MetaFileManager manager, FullPath path, [NotNullWhen(true)] out GenericFile? file)
{
file = new GenericFile( manager, path );
if( file.Valid )
{
file = new GenericFile(manager, path);
if (file.Valid)
return true;
}
file = null;
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
{
if( LoadFile( manager, path, out byte[] data ) )
if (LoadFile(manager, path, out byte[] data))
{
file = new MdlFile( data );
file = new MdlFile(data);
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;
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
{
if( LoadFile( manager, path, out byte[] data ) )
if (LoadFile(manager, path, out byte[] data))
{
file = new MtrlFile( data );
file = new MtrlFile(data);
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;
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
{
if( LoadFile( manager, path, out byte[] data ) )
if (LoadFile(manager, path, out byte[] data))
{
file = new AvfxFile( data );
file = new AvfxFile(data);
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;
@ -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 );
return FileSwap.CreateSwap( manager, ResourceType.Phyb, redirections, phybPath, phybPath );
var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry);
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 );
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath );
var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry);
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
}
/// <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,
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl )
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl)
{
if( type == 0 )
{
if (type == 0)
return null;
}
var (gender, race) = genderRace.Split();
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 est = new MetaSwap( manips, fromDefault, toDefault );
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 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 );
est.ChildSwaps.Add( phyb );
var sklb = CreateSklb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry );
est.ChildSwaps.Add( sklb );
var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
est.ChildSwaps.Add(phyb);
var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry);
est.ChildSwaps.Add(sklb);
}
else if( est.SwapAppliedIsDefault )
else if (est.SwapAppliedIsDefault)
{
return null;
}
@ -179,57 +176,55 @@ public static class ItemSwap
return est;
}
public static int GetStableHashCode( this string str )
public static int GetStableHashCode(this string str)
{
unchecked
{
var hash1 = 5381;
var hash2 = hash1;
for( var i = 0; i < str.Length && str[ i ] != '\0'; i += 2 )
for (var i = 0; i < str.Length && str[i] != '\0'; i += 2)
{
hash1 = ( ( hash1 << 5 ) + hash1 ) ^ str[ i ];
if( i == str.Length - 1 || str[ i + 1 ] == '\0' )
{
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1 || str[i + 1] == '\0')
break;
}
hash2 = ( ( hash2 << 5 ) + hash2 ) ^ str[ i + 1 ];
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}
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
? Regex.Replace( path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}" )
? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
: path;
public static string ReplaceAnyRace( string path, GenderRace to, bool condition = true )
=> ReplaceAnyId( path, 'c', ( ushort )to, condition );
public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
=> ReplaceAnyId(path, 'c', (ushort)to, condition);
public static string ReplaceAnyBody( string path, BodySlot slot, SetId to, bool condition = true )
=> ReplaceAnyId( path, slot.ToAbbreviation(), to, condition );
public static string ReplaceAnyBody(string path, BodySlot slot, SetId to, bool condition = true)
=> 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
? path.Replace( $"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}" )
? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
: 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
? path.Replace( $"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_" )
? path.Replace($"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_")
: path;
public static string ReplaceRace( string path, GenderRace from, GenderRace to, bool condition = true )
=> ReplaceId( path, 'c', ( ushort )from, ( ushort )to, condition );
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
=> ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
public static string ReplaceBody( string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true )
=> ReplaceId( path, slot.ToAbbreviation(), idFrom, idTo, condition );
public static string ReplaceBody(string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true)
=> 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
? path.Replace( ext, suffix + ext )
? path.Replace(ext, suffix + ext)
: path;
}
}

View file

@ -6,6 +6,7 @@ using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes;
using Penumbra.Meta;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
namespace Penumbra.Mods.ItemSwap;
@ -13,18 +14,18 @@ namespace Penumbra.Mods.ItemSwap;
public class ItemSwapContainer
{
private readonly MetaFileManager _manager;
private readonly IdentifierService _identifier;
private readonly IdentifierService _identifier;
private Dictionary< Utf8GamePath, FullPath > _modRedirections = new();
private HashSet< MetaManipulation > _modManipulations = new();
private Dictionary<Utf8GamePath, FullPath> _modRedirections = new();
private HashSet<MetaManipulation> _modManipulations = new();
public IReadOnlyDictionary< Utf8GamePath, FullPath > ModRedirections
public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections
=> _modRedirections;
public IReadOnlySet< MetaManipulation > ModManipulations
public IReadOnlySet<MetaManipulation> ModManipulations
=> _modManipulations;
public readonly List< Swap > Swaps = new();
public readonly List<Swap> Swaps = new();
public bool Loaded { get; private set; }
@ -40,72 +41,69 @@ public class ItemSwapContainer
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 convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
var convertedSwaps = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
var convertedManips = new HashSet<MetaManipulation>(Swaps.Count);
var convertedFiles = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
var convertedSwaps = new Dictionary<Utf8GamePath, FullPath>(Swaps.Count);
directory ??= mod.ModPath;
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:
// Skip, nothing to do
if( file.SwapToModdedEqualsOriginal )
{
if (file.SwapToModdedEqualsOriginal)
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
{
var path = file.GetNewPath( directory.FullName );
var path = file.GetNewPath(directory.FullName);
var bytes = file.FileData.Write();
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
_manager.Compactor.WriteAllBytes( path, bytes );
convertedFiles.TryAdd( file.SwapFromRequestPath, new FullPath( path ) );
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
_manager.Compactor.WriteAllBytes(path, bytes);
convertedFiles.TryAdd(file.SwapFromRequestPath, new FullPath(path));
}
break;
case MetaSwap meta:
if( !meta.SwapAppliedIsDefault )
{
convertedManips.Add( meta.SwapApplied );
}
if (!meta.SwapAppliedIsDefault)
convertedManips.Add(meta.SwapApplied);
break;
}
}
manager.OptionEditor.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
manager.OptionEditor.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
manager.OptionEditor.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
manager.OptionEditor.OptionSetFiles(mod, groupIndex, optionIndex, convertedFiles);
manager.OptionEditor.OptionSetFileSwaps(mod, groupIndex, optionIndex, convertedSwaps);
manager.OptionEditor.OptionSetManipulations(mod, groupIndex, optionIndex, convertedManips);
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;
}
}
public void LoadMod( Mod? mod, ModSettings? settings )
public void LoadMod(Mod? mod, ModSettings? settings)
{
Clear();
if( mod == null )
if (mod == null)
{
_modRedirections = new Dictionary< Utf8GamePath, FullPath >();
_modManipulations = new HashSet< MetaManipulation >();
_modRedirections = new Dictionary<Utf8GamePath, FullPath>();
_modManipulations = new HashSet<MetaManipulation>();
}
else
{
( _modRedirections, _modManipulations ) = ModSettings.GetResolveData( mod, settings );
(_modRedirections, _modManipulations) = ModSettings.GetResolveData(mod, settings);
}
}
@ -113,59 +111,61 @@ public class ItemSwapContainer
{
_manager = manager;
_identifier = identifier;
LoadMod( null, null );
LoadMod(null, null);
}
private Func< Utf8GamePath, FullPath > PathResolver( ModCollection? collection )
private Func<Utf8GamePath, FullPath> PathResolver(ModCollection? collection)
=> collection != null
? p => collection.ResolvePath( p ) ?? new FullPath( p )
: p => ModRedirections.TryGetValue( p, out var path ) ? path : new FullPath( p );
? p => collection.ResolvePath(p) ?? 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;
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();
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;
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();
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;
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 mdl = CustomizationSwap.CreateMdl( manager, pathResolver, slot, race, from, to );
var pathResolver = PathResolver(collection);
var mdl = CustomizationSwap.CreateMdl(manager, pathResolver, slot, race, from, to);
var type = slot switch
{
BodySlot.Hair => EstManipulation.EstType.Hair,
BodySlot.Face => EstManipulation.EstType.Face,
_ => ( EstManipulation.EstType )0,
_ => (EstManipulation.EstType)0,
};
var metaResolver = MetaResolver( collection );
var est = ItemSwap.CreateEst( manager, pathResolver, metaResolver, type, race, from, to, true );
var metaResolver = MetaResolver(collection);
var est = ItemSwap.CreateEst(manager, pathResolver, metaResolver, type, race, from, to, true);
Swaps.Add( mdl );
if( est != null )
{
Swaps.Add( est );
}
Swaps.Add(mdl);
if (est != null)
Swaps.Add(est);
Loaded = true;
return true;
}
}
}

View file

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

View file

@ -2,11 +2,9 @@ using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Mods;
namespace Penumbra.Mods.Manager;
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
{

View file

@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Dalamud.Interface.Internal.Notifications;
using Penumbra.Import;

View file

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

View file

@ -6,7 +6,6 @@ using Penumbra.Api.Enums;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods.Manager;
@ -19,7 +18,9 @@ public static partial class ModMigration
private static partial Regex GroupStartRegex();
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)
{
@ -63,8 +64,8 @@ public static partial class ModMigration
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
?? new Dictionary<Utf8GamePath, FullPath>();
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
var priority = 1;
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
var priority = 1;
var seenMetaFiles = new HashSet<FullPath>();
foreach (var group in groups.Values)
ConvertGroup(creator, mod, group, ref priority, seenMetaFiles);
@ -128,8 +129,8 @@ public static partial class ModMigration
var optionPriority = 0;
var newMultiGroup = new MultiModGroup()
{
Name = group.GroupName,
Priority = priority++,
Name = group.GroupName,
Priority = priority++,
Description = string.Empty,
};
mod.Groups.Add(newMultiGroup);
@ -146,8 +147,8 @@ public static partial class ModMigration
var newSingleGroup = new SingleModGroup()
{
Name = group.GroupName,
Priority = priority++,
Name = group.GroupName,
Priority = priority++,
Description = string.Empty,
};
mod.Groups.Add(newSingleGroup);

View file

@ -5,7 +5,7 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods;
public sealed partial class Mod : IMod
public sealed class Mod : IMod
{
public static readonly TemporaryMod ForcedFiles = new()
{

View file

@ -113,9 +113,7 @@ public partial class ModCreator
}
if (changes)
{
_saveService.SaveAllOptionGroups(mod, true);
}
}
/// <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