diff --git a/Penumbra/Importer/TexToolsMeta.cs b/Penumbra/Importer/TexToolsMeta.cs index 18b5e5a8..e801a477 100644 --- a/Penumbra/Importer/TexToolsMeta.cs +++ b/Penumbra/Importer/TexToolsMeta.cs @@ -39,8 +39,8 @@ namespace Penumbra.Importer private const string Ext = @"\.meta"; // These are the valid regexes for .meta files that we are able to support at the moment. - private static readonly Regex HousingMeta = new( $"bgcommon/hou/{Pt}/general/{Pi}/{Pir}{Ext}" ); - private static readonly Regex CharaMeta = new( $"chara/{Pt}/{Pp}{Pi}(/obj/{St}/{Sp}{Si})?/{File}{Slot}{Ext}" ); + private static readonly Regex HousingMeta = new($"bgcommon/hou/{Pt}/general/{Pi}/{Pir}{Ext}"); + private static readonly Regex CharaMeta = new($"chara/{Pt}/{Pp}{Pi}(/obj/{St}/{Sp}{Si})?/{File}{Slot}{Ext}"); public readonly ObjectType PrimaryType; public readonly BodySlot SecondaryType; @@ -129,7 +129,7 @@ namespace Penumbra.Importer } if( match.Groups[ "SecondaryType" ].Success - && Names.StringToBodySlot.TryGetValue( match.Groups[ "SecondaryType" ].Value, out SecondaryType ) ) + && Names.StringToBodySlot.TryGetValue( match.Groups[ "SecondaryType" ].Value, out SecondaryType ) ) { SecondaryId = ushort.Parse( match.Groups[ "SecondaryId" ].Value ); } @@ -234,8 +234,8 @@ namespace Penumbra.Importer var id = reader.ReadUInt16(); var value = reader.ReadUInt16(); if( !gr.IsValid() - || info.PrimaryType == ObjectType.Character && info.SecondaryType != BodySlot.Face && info.SecondaryType != BodySlot.Hair - || info.PrimaryType == ObjectType.Equipment && info.EquipSlot != EquipSlot.Head && info.EquipSlot != EquipSlot.Body ) + || info.PrimaryType == ObjectType.Character && info.SecondaryType != BodySlot.Face && info.SecondaryType != BodySlot.Hair + || info.PrimaryType == ObjectType.Equipment && info.EquipSlot != EquipSlot.Head && info.EquipSlot != EquipSlot.Body ) { continue; } @@ -323,7 +323,7 @@ namespace Penumbra.Importer Version = version; } - public static TexToolsMeta Invalid = new( string.Empty, 0 ); + public static TexToolsMeta Invalid = new(string.Empty, 0); public static TexToolsMeta FromRgspFile( string filePath, byte[] data ) { diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs index e2e819bf..f0b2dbe3 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs @@ -499,6 +499,42 @@ namespace Penumbra.UI + "Experimental - Use at own risk!" ); } + private void DrawMaterialChangeButtons() + { + if( ImGui.Button( "Skin Material B to D" ) ) + { + ModelChanger.ChangeMtrlBToD( Mod!.Data ); + } + ImGuiCustom.HoverTooltip( "Change the skin material all models in this mod reference from B to D.\n" + + "This is usually to convert Bibo+ models to T&F3 skins.\n" + + "This overwrites .mdl files, use at your own risk!" ); + + ImGui.SameLine(); + if( ImGui.Button( "Skin Material D to B" ) ) + { + ModelChanger.ChangeMtrlDToB( Mod!.Data ); + } + ImGuiCustom.HoverTooltip( "Change the skin material all models in this mod reference from D to B.\n" + + "This is usually to convert T&F3 models to Bibo+ skins.\n" + + "This overwrites .mdl files, use at your own risk!" ); + + ImGui.SameLine(); + if( ImGui.Button( "Skin Material A to E" ) ) + { + ModelChanger.ChangeMtrlAToE( Mod!.Data ); + } + ImGuiCustom.HoverTooltip( "Change the material all models in this mod reference from A to E.\n" + + "This overwrites .mdl files, use at your own risk!" ); + + ImGui.SameLine(); + if( ImGui.Button( "Skin Material E to A" ) ) + { + ModelChanger.ChangeMtrlEToA( Mod!.Data ); + } + ImGuiCustom.HoverTooltip( "Change the material all models in this mod reference from E to A.\n" + + "This overwrites .mdl files, use at your own risk!" ); + } + private void DrawEditLine() { DrawOpenModFolderButton(); @@ -519,6 +555,8 @@ namespace Penumbra.UI ImGui.SameLine(); DrawSplitButton(); + DrawMaterialChangeButtons(); + DrawSortOrder( Mod!.Data, _modManager, _selector ); } diff --git a/Penumbra/Util/ModelChanger.cs b/Penumbra/Util/ModelChanger.cs new file mode 100644 index 00000000..70c88354 --- /dev/null +++ b/Penumbra/Util/ModelChanger.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Dalamud.Logging; +using Penumbra.Mod; + +namespace Penumbra.Util; + +public static class ModelChanger +{ + private const string SkinMaterialString = "/mt_c0201b0001_d.mtrl"; + private static readonly byte[] SkinMaterial = Encoding.UTF8.GetBytes( SkinMaterialString ); + + public static int ChangeMtrl( FullPath file, byte from, byte to ) + { + if( !file.Exists ) + { + return 0; + } + + try + { + var text = File.ReadAllBytes( file.FullName ); + var replaced = 0; + + var length = text.Length - SkinMaterial.Length; + SkinMaterial[ 15 ] = from; + for( var i = 0; i < length; ++i ) + { + if( SkinMaterial.Where( ( t, j ) => text[ i + j ] != t ).Any() ) + { + continue; + } + + text[ i + 15 ] = to; + i += SkinMaterial.Length; + ++replaced; + } + + if( replaced == 0 ) + { + return 0; + } + + File.WriteAllBytes( file.FullName, text ); + return replaced; + } + catch( Exception e ) + { + PluginLog.Error( $"Could not write .mdl data for file {file.FullName}, replacing {( char )from} with {( char )to}:\n{e}" ); + return -1; + } + } + + public static bool ChangeModMaterials( ModData mod, byte from, byte to ) + { + return mod.Resources.ModFiles + .Where( f => f.Extension.Equals( ".mdl", StringComparison.InvariantCultureIgnoreCase ) ) + .All( file => ChangeMtrl( file, from, to ) >= 0 ); + } + + public static bool ChangeMtrlBToD( ModData mod ) + => ChangeModMaterials( mod, ( byte )'b', ( byte )'d' ); + + public static bool ChangeMtrlDToB( ModData mod ) + => ChangeModMaterials( mod, ( byte )'d', ( byte )'b' ); + + public static bool ChangeMtrlEToA( ModData mod ) + => ChangeModMaterials( mod, ( byte )'e', ( byte )'a' ); + + public static bool ChangeMtrlAToE( ModData mod ) + => ChangeModMaterials( mod, ( byte )'a', ( byte )'e' ); +} \ No newline at end of file