Change Skin Material Replacement to accept arbitrary suffix-strings for From and To.

This commit is contained in:
Ottermandias 2022-02-16 15:00:50 +01:00
parent 947e40b1eb
commit aa180dcdf6
2 changed files with 638 additions and 569 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@ -9,10 +10,105 @@ namespace Penumbra.Util;
public static class ModelChanger
{
private const string SkinMaterialString = "/mt_c0201b0001_d.mtrl";
private static readonly byte[] SkinMaterial = Encoding.UTF8.GetBytes( SkinMaterialString );
private static int FindSubSequence( byte[] main, byte[] sub, int from = 0 )
{
if( sub.Length + from > main.Length )
{
return -1;
}
public static int ChangeMtrl( FullPath file, byte from, byte to )
var length = main.Length - sub.Length;
for( var i = from; i < length; ++i )
{
var span = main.AsSpan( i, sub.Length );
if( span.SequenceEqual( sub ) )
{
return i;
}
}
return -1;
}
private static bool ConvertString( string text, out byte[] data )
{
data = Encoding.UTF8.GetBytes( text );
return data.Length == text.Length && !data.Any( b => b > 0b10000000 );
}
public static bool ValidStrings( string from, string to )
=> from.Length != 0
&& to.Length != 0
&& from.Length < 16
&& to.Length < 16
&& from != to
&& Encoding.UTF8.GetByteCount( from ) == from.Length
&& Encoding.UTF8.GetByteCount( to ) == to.Length;
private static bool ConvertName( string name, out byte[] data )
{
if( name.Length != 0 )
{
return ConvertString( $"/mt_c0201b0001_{name}.mtrl", out data );
}
data = Array.Empty< byte >();
return false;
}
private static int ReplaceEqualSequences( byte[] main, byte[] subLhs, byte[] subRhs )
{
if( subLhs.SequenceEqual( subRhs ) )
{
return 0;
}
var i = 0;
var replacements = 0;
while( ( i = FindSubSequence( main, subLhs, i ) ) > 0 )
{
subRhs.CopyTo( main.AsSpan( i ) );
i += subLhs.Length;
++replacements;
}
return replacements;
}
private static int ReplaceSubSequences( ref byte[] main, byte[] subLhs, byte[] subRhs )
{
if( subLhs.Length == subRhs.Length )
{
return ReplaceEqualSequences( main, subLhs, subRhs );
}
var replacements = new List< int >( 4 );
for( var i = FindSubSequence( main, subLhs ); i >= 0; i = FindSubSequence( main, subLhs, i + subLhs.Length ) )
{
replacements.Add( i );
}
var ret = new byte[main.Length + ( subRhs.Length - subLhs.Length ) * replacements.Count];
var last = 0;
var totalLength = 0;
foreach( var i in replacements )
{
var length = i - last;
main.AsSpan( last, length ).CopyTo( ret.AsSpan( totalLength ) );
totalLength += length;
subRhs.CopyTo( ret.AsSpan( totalLength ) );
totalLength += subRhs.Length;
last = i + subLhs.Length;
}
main.AsSpan( last ).CopyTo( ret.AsSpan( totalLength ) );
main = ret;
return replacements.Count;
}
public static int ChangeMtrl( FullPath file, byte[] from, byte[] to )
{
if( !file.Exists )
{
@ -22,53 +118,31 @@ public static class ModelChanger
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 )
var replaced = ReplaceSubSequences( ref text, from, to );
if( replaced > 0 )
{
if( SkinMaterial.Where( ( t, j ) => text[ i + j ] != t ).Any() )
{
continue;
}
text[ i + 15 ] = to;
i += SkinMaterial.Length;
++replaced;
File.WriteAllBytes( file.FullName, text );
}
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}" );
PluginLog.Error( $"Could not write .mdl data for file {file.FullName}:\n{e}" );
return -1;
}
}
public static bool ChangeModMaterials( ModData mod, byte from, byte to )
public static bool ChangeModMaterials( ModData mod, string from, string to )
{
return mod.Resources.ModFiles
.Where( f => f.Extension.Equals( ".mdl", StringComparison.InvariantCultureIgnoreCase ) )
.All( file => ChangeMtrl( file, from, to ) >= 0 );
if( ValidStrings( from, to ) && ConvertName( from, out var lhs ) && ConvertName( to, out var rhs ) )
{
return mod.Resources.ModFiles
.Where( f => f.Extension.Equals( ".mdl", StringComparison.InvariantCultureIgnoreCase ) )
.All( file => ChangeMtrl( file, lhs, rhs ) >= 0 );
}
PluginLog.Warning( $"{from} or {to} can not be valid material suffixes." );
return false;
}
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' );
}