mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Make line endings explicit in editorconfig and share in sub projects, also apply editorconfig everywhere and move some namespaces.
This commit is contained in:
parent
53adb6fa54
commit
2b4a01df06
155 changed files with 1620 additions and 1614 deletions
|
|
@ -1,21 +1,31 @@
|
|||
|
||||
[*.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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Dalamud.Game;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ public sealed class CollectionChange : EventWrapper<Action<CollectionType, ModCo
|
|||
|
||||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnCollectionChange"/>
|
||||
ModFileSystemSelector = 0,
|
||||
|
||||
}
|
||||
|
||||
public CollectionChange()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ public sealed class ModDiscoveryStarted : EventWrapper<Action, ModDiscoveryStart
|
|||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.StoreCurrentSelection"/>
|
||||
ModFileSystemSelector = 200,
|
||||
}
|
||||
|
||||
public ModDiscoveryStarted()
|
||||
: base(nameof(ModDiscoveryStarted))
|
||||
{ }
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{ }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ public enum ImporterState
|
|||
ExtractingModFiles,
|
||||
DeduplicatingFiles,
|
||||
Done,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,4 +20,4 @@ public class StreamDisposer : PenumbraSqPackStream, IDisposable
|
|||
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin.Services;
|
||||
using ImGuiScene;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Penumbra.Collections;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ public enum DrawState : uint
|
|||
MaybeCulled = 0x00_00_04_00,
|
||||
MaybeHiddenMinion = 0x00_00_80_00,
|
||||
MaybeHiddenSummon = 0x00_80_00_00,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ public enum FileMode : byte
|
|||
// Probably debug options only.
|
||||
LoadIndexResource = 0xA, // load index/index2
|
||||
LoadSqPackResource = 0xB,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class DuplicateManager
|
|||
public void Clear()
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
Worker = Task.CompletedTask;
|
||||
Worker = Task.CompletedTask;
|
||||
_duplicates.Clear();
|
||||
SavedSpace = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using OtterGui;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods.Editor;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,4 +89,4 @@ public class ModExportManager : IDisposable
|
|||
new ModBackup(this, mod).Move(null, newDirectory.Name);
|
||||
mod.ModPath = newDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Penumbra.Import;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue